From 9ada452632cae3421be7e97dedfe621b6b636f8c Mon Sep 17 00:00:00 2001 From: lucy Date: Tue, 3 Mar 2026 10:16:36 +0100 Subject: [PATCH] push before moving to nixOS --- Makefile | 3 +- Makefile.orig | 51 + boxdraw.c | 194 ++++ boxdraw.o | Bin 0 -> 7672 bytes boxdraw_data.h | 214 ++++ config.def.h | 23 +- config.def.h.orig | 484 ++++++++ config.h | 495 ++++++++ st | Bin 0 -> 113488 bytes st.c | 128 ++- st.c.orig | 2776 +++++++++++++++++++++++++++++++++++++++++++++ st.h | 12 + st.h.orig | 128 +++ st.o | Bin 0 -> 82168 bytes x.c | 122 +- x.c.orig | 2209 ++++++++++++++++++++++++++++++++++++ x.o | Bin 0 -> 79552 bytes 17 files changed, 6805 insertions(+), 34 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 config.h create mode 100755 st create mode 100644 st.c.orig create mode 100644 st.h.orig create mode 100644 st.o create mode 100644 x.c.orig create mode 100644 x.o 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..0b20e9bb40a03b814beeff66a0ca96b29bb6567a GIT binary patch literal 7672 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 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 2cd740a..e152145 100644 --- a/config.def.h +++ b/config.def.h @@ -5,7 +5,12 @@ * * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html */ -static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +static char *font = "Lilex Nerd Font Mono:pixelsize=12:antialias=true:autohint=true"; +/* Spare fonts */ +static char *font2[] = { + "Symbols Nerd Font:pixelsize=12" +}; + static int borderpx = 2; /* @@ -67,6 +72,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 @@ -176,6 +193,8 @@ static uint forcemousemod = ShiftMask; */ static MouseShortcut mshortcuts[] = { /* mask button function argument release */ + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, @@ -201,6 +220,8 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Y, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, }; /* diff --git a/config.def.h.orig b/config.def.h.orig new file mode 100644 index 0000000..498a6c1 --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,484 @@ +/* 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 */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* 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 */ + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { 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} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * 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 new file mode 100644 index 0000000..acf6fb8 --- /dev/null +++ b/config.h @@ -0,0 +1,495 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Maple Mono:pixelsize=14:antialias=true:autohint=true"; +/* Spare fonts */ +static char *font2[] = { + "Symbols Nerd Font:pixelsize=14" +}; + +static int borderpx = 16; + +/* + * 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; + +/* + * 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 = 1; + +/* + * 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 */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "#1e1e2e", /* 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 */ + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { 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} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * 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/st b/st new file mode 100755 index 0000000000000000000000000000000000000000..776a58dcc76db2254e02ef687dc85778ed226daf GIT binary patch literal 113488 zcmeFadw3K@+BQCuOhdqg4w#X6h(ruZ6fhtGMi|Jz03A2M2s9>9PCJ&ZyG4FQDw?x(st)2VfS@B4lKeb=?| zV!H0To~nB4T=i6SyJCFCgb1tEV*Eu~Mq3D_&C!@N-6QM$kW89ogr$ci&5~&8W9b5a z8~zz`{4+KZp7=AKQ5v)mP2sld{`~VQUCwwKsne6fh5JjJsQDOAORR<|oIuVj4fD^f z8#Uf|n!~a5Vs#C;XX$V)obfb=qq3>W)WTKW+xgjYlNK)ODeY;5ivXl>mb>)y@;X1$ zNC1Dv(F+%NegB%o|Fdt(UbnonyBf=KNI0ce~Iij zRA*C;c;-^L|Ip9STbJkc%+kXp4%E`)pHp?kjHeDQw<8=YPd(oM>7h3l%Va&haP^nf zON+^P>b1nmBn@8F)+SE6!-DFnCSCOGSt3d`Y{*+JR_5OgGV~XrW5b`q#^E1@r}&Cy zk9KZbc-%gGNY-_8H$45abj&sIru@7)!-n3JKX+h$UV(S7SB-#rRy*kK|8>(WhJ2KbGGYM$ezZ@ZX@o|0vzRhRIh#m~?xG;U5UYe;lS9PKVKRS2%r8 zZi$_0N&~aPlv`Gq{QAS_^JEx)OBj9JVd9^PhWSV3yey2K^TVY3QW(BZ7~U0ze+b3& zNBTSwM*hAq`Tc8{`q~;MzstkK?+p`wZkTvRgyE-!iRYd$`IW-xgKGK1c6Du-_B=8S ze<)14r^4vJKTLbLElfPU!s!2gnE0#0#D8a)cm{+ihik&r`@Lb}`5}xx-Z1>|FnYcl zrhg3xBmZ(3`LZzS#)WA=1H$kZ!sNGWnD~DUBmYVm{d=n7=4Pv#1j!Fp2#rubu0}3W|(}<3KM^27(H(cqvw}l`tPwY z?enoP{M%vlKMw`MoPlxs40Mr-#vJQ5ZfpOgw$Ulv`4m za{D!m{BvRQYY9{Do5I91G>o1RVe+*zO#G|E=)XKny3dEH$6;a8jSrLGqA>M)HjJLp zVf2y0=%!DaQW!n|9Y&uw z!ss(4Ogw)I!`Fn-=h-mvFAAg2n_=qf+c5P$Axye)Val^EOnDZBk^d6<_eC1~-$w8~ zEmvAz_9Gzv47-43X8P33nR9cBbMDJ4@#GXw%^a6sSdcSy_MH42%gmYgEhsFQS>l;p z?3p=J6a7QMX<0c1bMp%BvrL^1&akKOD=giKYZ<&@}fVc`=p27u2@T|PW3uYHt zrcFV5dLk2ZJX3P=bFw{og$1`QD#$^^Q*u1%nRm|4_vVyXrcKs!7AB25XKRX0%0c$$ zW}py|*1XvT_vQRKuV8NBBA3TgoHxhop{Nk=pED=TUXVk&mmraG6AH7v@W`B9q)Ah9 zMYD^Mdjn?_7S8t;O+h47JXy1gOL8XW6fVf|6r)U_yC#=aTv(J->;W|)-&-=z$nL~( zmTB&s+}YlI&y?)qoSXuJ`8l4Pki4dSx+a)ckYf}s(=|Q2ut3w7Qp%L_3Ovwu0%Gcj z*PT=3L3$9MnzPswiWfviVfO6&oXo!EJ%0?vkUVfKC{jrWM|Fx zP~&%di}Lfb(JL$+lxyzXv4w^CAp&*lF7#4|?T|q$0idd9g&6XHEcL4Ukfuqv#8X_j z1O)WT&AV@kcTR~X&x1Bk)V$fnC|NhUhbE&d1}#DnQLoON?a7`OiWL4GN*PVq9B+C- z9(r0XYjxund(dM_?m+PpH zyq;_ehL&PN#0ybHahG4nfK**Jb4(&X43EuoIP*;%-q>|`IcN79WWXd6k#~> zWS3y1orBIc-+~4(*IQ&+kh5Sx;ldn(9<;)eB^WmsSP)&&A}?akEi7iD`53Hb&dl~K zDax5SXC{)gAdFr18)S zWPV5vkXnZxtPmkBu zeT^JHQ|FsFK3nJabNqce-^}szbzbH8LY;5n_!6D3n$dYZI-$3QH*)+UUA~&*AJX|o zj$fhkO&ssj`TZQfTIZWNUeS4#;~&-e7LI>h=i4~`Nu9UM?3~|cbUupXU(oqjj(3k8#@7DRn9KToROF8~)ov+~d13F*H@rQK&X^#I<=U?V{Rp-}nd{F0`IR27@ zTK{e4_@})s@QH_6@6?x?1B~IDWOxw{iSh zowv;HT%J$qd=$q&uk*1S|GLh{bNs`)o{1d)jxOJy)A<&Te_H2by`A-cLFZFA{%<;;#qsNOzKG-B)%i+}|EJDZ zar_55-^B56T&3yP!ttAQ`PhY>_1~)VDI8y;^I06fL+6V){u7u8XF6ykm(H~Pdey=W{#qq|tRK)RL8~Stn0iCbn_#bt?iQ|Jh-@@^ybUt=* zXZ_FWd3r;x&iY@g^C=vE zgU)Ah{9v6g;`k9dU&-;tIA6u_#yH=^@niIOS~z}!&c{C3S^rF(PvQ8fI-kYy({;Xx zK8xeab-sw>*XVpD$Nxp= zt2q8yop0j!fmdq!wQ&4kosV7GS^rn`cv3k2b)C=Rcw>Ak;`kCh{z{HF=A9~zH|Ccn zjyL9)7LGUOm)K>U^*8316pr`n`ebqZV>(~N@y7g8$??YgQpNGc{L;kn#{AO4@o!zF z=^eYgv;OOJK853r^U+;do=+61$?a{>Hi` zh2xENOBTn!qvyAXisQf5`6iBU*7+8W|54{-S9aDvsPicte@f@GIR3287je8LL5sJN zU&Zkob-sz?t98DG{wJNU;`m>6zKP?1*ZCHXw_UC29s6)+{iAh0h2wkad=|%F zq4Px?-&f}=IsOKnuj2S2I^V?cBXqunKywMJ0<<9yW?IeZcje5-Dc%z()INq4oDmmVmU#d9X80VWf-WYFNINlf!V;||P zzcF5=aJ(^|WO2OF--|fj=#P~gZ}gWcjyKwKHOHUQ+fO6M|LX=#?U;;9P5H*x%7-5%b;@!dbv z;)z|;S^q!j_Wu-)kJI_AFnkfmm+E%=N{-*4mroVP7wdc@#~bo0#~bod+&wcx&;A^L zi*Dyn;do=cDsj9apT+UZ^>#9k;|=*DjyKk^6&!EKS8{x*9)DAzRxdd0wOp!I75m+9 z;_2N1#qud$AGH=B6# z`$npXk2A@)n0UL1Z!__inRrWnsNQ><_$U(}Z{lN3{N*M--o#&F;uB5$l_tKwiNDIk zrnsz0tx0~KiBB@|MJB$ViC=8u zuQTzbCjNR8Ut!|=oA^o-pKRivHt{!@_?J!mjV6Abi63a#rg+-)a+| zVv=t(@qRTH0T;#*AoFcaTq;zyWx%Ysn*cbNDn6Mu_| zk2UclO?YCce_d|H;HZZQ?Ub{L3aj)5Nbc z@smt^m5HZ!293XsCO*p`@vJuSx10Dz6F=F+H<|b;CVsz(pK9WpP5d1uUN!M|n)ntI z|7R25X5y!rcniG%fIpgJ?_w_PC(6X%ZQ^51{B#o^Z{qJU@rfq>UK8Kn#LqDCDJFiV zi63F&XPNjk6F=L;OD0~w%Eo-NOuT;ejq%e={9IE!vrK%BiJxcUb4`4aiNDXpFE;V> zOnj+{&ol8ACjNdCUuoj!oA{?qe7=c)*~Bj}@#{={fr+m&@r5RSqlqsv@zo~&0TbV7 z;)_julZh`e@%v4@$HX_Ac&~|9P5eR=-(unynfNvnzu3fE^mQ_||0O0q%EUit;$uzx zLnc1n#4k1Ri6(xTiSKXXmz($$6Tia5k1+A2CO*x?uQc(JiC<;nvrN3t#7{HvWhQ=> zi7z+t^Gy81CcenTuQu_EO}uR4OHKSECceVND<;0u#IG^&Pn&qZiGSI|uQl=OO#GuJ zzRJW`n)r<-{xK6@ZQ>s{@r@?_FDCxK&i|Ice@o!MCGg)8_-_gPw*>xM0{^d-z-jU7 zbCU0;C`l1&`dBSed9^2^y-D(IkE&x?X;1Bon>+3OlJG0`NwbhV+wTMpx3{(Ct0629emsP`5MC9+w3rVRhj0|(IU(GQ@Z=EgPIznxUqX0D z2$LZ=&^Lr*2=@x%9)u%8SR{PvVn=>s2_FvOo`m;=a4*8OA$%#}^&w0K-oUFNOorXS z;~`82-N334CPQwZIE2Z78<-QqWVj7X4q-Cb2F8Xk8EOMVLYNG+fxaP}K)6>3lR-8R z5yE7M4V=2rk^e-(heP-p!h1rvFX7q{zLxO%5KbceY6$lu{CEgoM|f2TUr)F=g!>bo z6T-=aCx`G2gvW;Pjf97U@BqSnLwF$JULib)a6|~-MEKP29r;fod^m&$6W$ZTLkQP~ z@XdtRhcFpF1FwcK89W1zhcFpB1FJ%KIN{EUL_)mm~gm4Doz9F1RxK{{IA{-IIw-G*dt|R|h zgb#=C?S%J)@MOZZAv}ff`Vc0AVBpmdCPQH0@en2hU|>}Uli@E=9KvMq3(N^&GV}!| zhcFrV0%Jp%410kgAxs9nK;IC)mvFBTCIem|B816s7dUmcBmc7q9}eN!g!hE-9Ky9B zoK1Lr2+t+_Y6#~LemsP839ky_`v@0@Fik*#IU$@!cyb8ePk3wy&nG-2g!2ja4dDfZ zdxdZT;fN3}Bz&r^BmYH&4~Osrg!hDSG2z+}E+M==gvlTlcr}E{5EghmgvkIFSQWx# z_zDz2+s*&negNgeuVJY z5LO5e3E?$_`-ZTeaIX+vOE{tfxA)t2nbndj_QAMC>z(#~SNH0SFXM0z4tL@3#h#tR zw{rLdhmUgjAcw!=@GcJ5b9ftvH*okp4!^HVarin8U(MmmINXE7T{wI(maBgbpWyIO4j<(3R~+8O;d%~lIQ$}qpW^UZ4nNG{My( zaJUPHFN$3KbNB>@k8=1Rhri5UO_&N?>&Ed;9+=IhiIDD}OSN|M7!QrDEKFHy(IJ}F) z^&H;D;SC&qkHc?p_(cvs#o@Iaewf3{IlPd=1su-h@C**$$>B*Hp1|SJ93IBuK^(r0 z!&h_oG7k6Pa2F0=jN$5^!zVa=l*0!({1u0Hak!qt+c>;|!|!!qtc3o5|7bll_~&$a z_Y6rsEct$H$(lNTaP{C%B){`7l@?38T|rd8<_;43(4x;`0iS#$>`Z>4FFhP>9y?4= zlORN^ov)+ra9qCi} zz#=jQbz~vrf}GM-^4tGfZ?TNDtuKy;dp_I_xT6PJ_FF8@_O2GoqU46I7uqeB&GfS| z0=Zjxj`)QMc!CoDS}ks~`q#5`s(0)N}DtcB3Tk zl4_3HTHQl_vwGvptCz<~)>FBaz6Vxe%q5&b*LkM#t^Gib0{LWQc~=j6sx7d z7C~7;9&M7N(Hp4SMv}oDAJe0}b;-Xpya{Xwbhiw0x zJ%lDae5rm!Z&SCEptvC>AE>n^!jq=h7r?0q^YQCDbg?34G91Xka3y99!nysfcE?$l zIH^r`roaaoxvk!DN}SXr+mnEy$ZU#UwQgpbe#G<%?vYzsg|6_AU)swN82kF~Bt z`uYy(Sh1&lC8QudEn4iEKyIZdPFWnQij*QUl%WV8{s~1Es+j8?Eh{2XgpP}Ho0w5A zW?q!*<rQAg7t0Rf?p0&%UQMVezukqn(YtEn zCB>Z-C1qQrnr0g+S(4pJ@k&aAJBjSPXx*L%Fw{)H7DLTN>?twMiBD38hge+j&SZ*A zvHzYC4bB-6Dg&Pn)u*f zOBB#|IVGDjTR$EXrm6Vk*o> zuS9(Z?n5H1_eLoL-T8@Q;y%@ z%ZPs#L18)l+Kcepk;)VRmda!%!Q|}Z?vO||B!iE*j8ik_1xrZ6U4HYL5x-6R?~X!H5$2`SE5g{eyY~|+TOmADuO&Tcf*MwX+>DElRC999wfJs@qO?w_3R=29z~e zBkx4)L$%nqCK|D48?jT-q-JW~FLrplJG@u=T@grmnA~M7Qnayz#E0iWFPe-}Z(YaK zp4$6(R z{F{WS<7!DhEy>?V@(H(mOx=cfFm^4Dk*qtU%<`X?G+>SV?>EtcXTeu-jz)y4 z{ipW!2H_?+kzRZ7Z)|c@ZW1@#YbpEIGbmmDU0rp8?78BGZzUzR)qS@$&3m=v+hI+Y z2PA1?-XAv7!BFs0?GY{c$HUhK-|-fjLF+oryEs9PxeX`Cn{5cQjd~Q+noMdfIYm|K zd|)g{vFreR)GkOnUEaP0Zio&1a~g(k|G*KD&hV!WU=Qb&cmxw9CG{*`u~Ka(DUv#? zH1Y-d(x&L6rKY%HX#%Z-(-m7%t2+x>X!5mp_w@C($9P8g+OPE7A)m{TPoR~3fH<^P zHXYgoKY+5Yf(j{{A-AhdzrgERc+tn+LSD$n6K_E2f5JPk7{v*RoK6xM@)`9novbf z{&7gWEppL3$#>4WaJ1w*Z(TToMIt%AkmRoyz zyRkE{N{*J27sU;6U0w2Sm+w%utMqKC=iv0Zj{XvlrCl^ShD-iZ{uC4FOZeCX7GfLG zd`drx_--_m_1-AA`Bay$&AI3rxf-6|{zQFoRIP+%fGGJrNztrkRWFTK*=YR2N(9H! zYkzw7c1hkL)f};jA7A^I#094uL#Myf4bteq$|r%SVL)X+CK`b3T!@wZo0obs=xXLY5_~&o}f>S zfjW@`5N)Q-5{1SSuPs;WT6fqy&q}o9zzPQ&DoqW0Et9u>le==3t^Gp4i^`%+E3Jj2 zP+m3~K>T>JrM#MfW{NgpTQpn3aOOL1^`t7EBpX)he_TJ*?#B2&DXoaUwB|$?3gG=o zMgWxT2Om-ojEgw4r|(oEV%`GLJ6C4 zqW6_xts0Gi)91{A8kXf(Okv$h{$3q{#4z|c5;Ebj;>7g2Ix6A@;hd9FvsfC0whmVW zL?GVIL|7U4oO|JG^+Vt;GBT(DBRC7{@8kO{|MKe(|K#IZ_`5zv7JcTzb;se2DNd?J2%MdyG1VGndFc z!3KwOE?hcI>ie8`gJ}2kYY>LOq20=L}nJNU{2kF(#O=r`do`~4BWsufFepK~Le7EkvE z;csxzAf%*jfJ+?pHe?#v2lgXxN58Rnczd`Vsj1`;#yd`W z&~C2+aO@B>cgXdM(3L=)IH?X};b$1}edyp8@d>H`Yqc!wABi^J(T~|Apgx4*CwScF z+yXzVcUSOTpYz{vwu@zT&=+O+AwGnq84?^q9el*{x8cE7Si)a{9c@dODKUOXVedrv z+|uRPsJ&Q&3BY0Zf)`(?6*ug0?DXCb+4+inHaJJqirMlnkuf<4A;-+bZ)ECha;Hwm zui|u&%uez7YR6ge`I>Hxj*ECiHF|B1Q_H%l_1KUEuTdvs#S^?jvBx8bW8aF{O;F4d ze8kbX;#u`Ih*0;h>%2OMcgYuB@@I~Ha+5fz!Hun@T;q0}kXu~hZ9ClJZCKspCq=pB z9jLJi8gv!m=q=cTz^KtE-nLtwniM@Awuy%@#%i?N(L%A%PPO7@v>QcOZ+NKiIF2fa zvMipW2+wJ>l93c;L={CNf(y0xXm5l}&&HVF93f$4BW1fWGSe;|LsV>FJ@mjXKAL5( z)TP-%{9vLOvq{}Rdm|yR809xEN)di!j_BNmahRDKA`>A+3o48^F2{+bcc9f=24ynj zA8w7jYJ^)pfj#hkEa5TkZw|iy$HVXc$hwe%Ul)pfGG;$UQu(iA~@vsJ~^?WuSq8?(EgiDb| z@_8B*Pm8x5EZ^g~3p2nV2O5!3jc)+@v#e5ft=T}p_J%EaEwn?Go03~(;c3WWp!Zcr zNQ$Qh7L>hMshu7H(jSMp;TBAmVC5m`+bqWRmFIRL@SHFOo^F5A zjV^yi5~|}pM1Xc9pI~+^)k~Y`)Ett*RB=;~YV6NI$@?8URxFlS)=tnC3!R!uKnSWA=@tV)u4*1qZIr1__$;8H;$dl zKJhtUqz^rM1U96d`BpxN#&8ymA$UUD2g?_fgy$jRNO&8+o<1n(@}F6bM7MJVDEXio zgUqn0_TIY9RL^YcQ=DG-su?uCd}hRDs<-Lz?No2GP;U!|Q@usO%U?+)`#)CQ0eoh& zQ`I>m5g2x?p@<39cg|dWsmCClOgXFyp!j3<11drnNK!rRNa5nrVT}T++2jLgn0)Ncv*Qg3?D3m|m8Na+)dsv0zS}Hmnp!kwkBRD22PHv*7atEv1^@qa)7xGH>>JVJU)W0;R~#HFbI8# z6K1wzR3H2SK1s@@zH@ORH6%2t^laP18P*oGrEO^-D2Focpkd8y%W(W4`kn=e+Wu6z zbmcixN_?~eqvpzU^go9b*WL}ce^or@I$zu1MQdQ(&$g}3u(lyOY9rlzG2M~Rh+vhk zpB0V^!8bvBL7#rWYKg_ty5Dg)P6jJ=>F659t@ucT&=qOZpCY6p zd1eLty$D-Cp%uR_cr*yV!WD_X2H^x;^vCwsOb4y2Ogz%AA9ihp3wHm(dfgu(N2j`f zi%u#}{D_JOTD>1lXG`*|Y0}D{_9I>iEe!k1sYxjk7Q`bmu!Kp=fJ-W?_6!%3-AP$u zaz@fDF?niIk(m5^QYpfS$$v}2DJ`CVPkNa>|CLn5o*yMuw4x-xJ1LPZoaiK&+1tm&U?#f%TjY($D+T#&-r``TQ6@$pH-Y|0B&?H$1e_#6yYs$ zR9nS=wACDoz+tFbMhyvS#=EiZhbOH!TpdQx>A!unFK$9}B*A6SWmB!gcn_P;G)CAoYWE5*lw{@11W*&w|XVg2!wn-=R< z7~CrWP+`%`4vl#AbF$Z&{8hEV&qLf}I>+9qKs@91coyZa#mM(3j$=nFkL| zQ7^+tOML=HUzmU!)Q|U5qpg=IjIrG@iL!|hvq~4Z)kj&^soi9U z2y48OeDbvP4$k3^C8Gu!gdg#snX@8c7hJxW_W3xGr*4g-5Jdjc#+h2=!B@7-pqxIt zC5s+^=@<*nkiMa@pb-$ccty{>-#Mlu7u}HyJy?9F!HEn?Fz(sdLRfJoLlHF=@xWMq zvqQK7&|8S&$6V|?y22~8Zg2Oxe9bE`MvSs9{}aZWNL&im#+x_JVU%@iIwUY1YL(Re z{gG9_b1Uq0np7#I#0|fKdlw_9#^r<42K60#kt3vMGCa{h7hk8ogNRLw#g#wN|0+uI zMWcE6eu__*FPgGdmJ|g~FB`CN(mFFF+KT z{sDcY<`$>wXcgW_JD6QNc zkDO?ROtzn-azni8-_c@&pQ%rxoQ*M_wbBvn2;@wb^-O|!pllBs+cq>$DnG5B-bA8u18h^*Gm}vT-T&5B z1^4c0ZzpU1-RR9q!m;a6=%~g19a#~}U&We8-jBK*f!xpyg3TMy`nS1Q{NHQwPo(&T z4^*=0iZb0EQh!C+qfLKC?Lw}h6CHnQ4eptvlU?4$Sh{_Twh@p>e1|6fA`@rz>vz5h zaV>sZ8`T31=rHU?xfsceFzl|1T7gh#_fGYD6o@P&AQ!&2c26dHSRUD))ib-H%ssd> zkRVVF{i$zZarbdgWI@eGk6PDe>|q`LbK#FO zmt%-;4J8F7RUaJcB>b>~W@@xF0ip0L3>_v!)x^-3KrA=gdf;stqO`1&ekjz6I}wgp zUwef3DB1yRy-d*^4Tu5AwvVbdh zGM%V>NMfTA3*7IMdx++Km)zHpTe+DHHt>IixLPWR5cec;?}5{V&_n&sdx?9N$(1A; zfgd2UyhBh)O~&Q~!ZV0p0A5=wvf6p?a|1$Ei~3Rrq;gX$J0RMqMP1neFsGR=|~ezweJ^HbX4&L9Ydq?8~isRWWY}yWErjmc`jGmSRs?- ztPaW9kfa=U5LrYh^ZS=BLm`XqlL47#Peq7C@l%PTaSj6fJqMXDua-pjjtrb$#IiLT zO_GUF8pVP&S-M;wn63rhrAd1($&k-cByv;mlXUrHaJ%Y8Cc;e=itsR+eXeXvQiM|G zawkRP(pgC^*3ezBeBBmDjm~+juhBF2?qXdEcNII0>4UT}y*G*qCDww{qtV!(`X)w5 z^z1PaM))IWZ#Z3e4}!+5r?q_N3Z$VvMIn7y2a~5F9P39t*1G(5w-WO`+_-A>9UlI) zi<$nZ)ln}00~hH!jpVD3bNS9%mtT>tVEOIBO6V8y!`Sk3bdp<*#apAdB^`#ROh=P< zAL~BanM~bAYCSA1nDftXrIN+@EK;zpNWh#epOjCi0>%zH4_1V`NE_AaP?!Ip*s}x1(1{l)k8}GXv`UKx$SsV@{;w6gkY&H$RE4tN1 zrk+m1t%aXyQA2kMTgX1R6uxH;Y7^=K6)o?3>UJH)L~-;|>~}(HBfIv};;3CtQLUq> zj#5BcK?2f8>FWM>a1_Ngj zmV;VFW+03^x!IvE`!|^rzZD-P^DtU;thH`rLNUTD8zyB3aUd3nIPTZt=&yFu;+$jP zvnV8lN1V7-HVW(t+$OpQdiVz%pr!;P*L5Vz4&IaCQZuRV%R+nydl|9AHMTpk{-G~W zQ1lsPHoUIVyjH-SnSBh_zt@>B?cZofQk?ISRO_!S+E?ICuldoI?k|W>Us?b3Xv70U zKDPXtAwLQhm5iiVw|ovgbM7jZ%Ig=6x%5vMyaIDDer9B&ED;a#?kqs3G)lrh^rj5? zu=*M+1?N5Fqf?cj0$XV$LQ!NmeuD`dMU7FB&L{6;%8jQgT=qR1HWZwaYL}Bu{X`<_+e@p@iD9D*G~VRM%-z_uN(VgdWaiR z7vYI=cHyiFThlm;OMIz%{53Uh`7>s0LC=b!k(~|-lW;VMX4KyeL&<=&vY*BBbI7n! z65^Wf&{b71On(fAMZE!fkg;Vi)*4abhlm4BDG9mg??!bFkexrypqV7~5FT>O_xQ!Z z;dcatZy2!eqen&Rmv~^Lphcr`^^}dsfpTg;xSp-pza|m?Rga*dCfCw!J(#T6;6(er zUmGB%*gs&=1aXP_n-ak{(&E7t&Cd>^N&9U+-1S5EuQsh7$_|d&6n}p5yb<3@8EIz)(o=NSG zQ+FCi^(8e$PNS)o=9|-zKN|Hr4X2WPzSGWN#NcXQd!L24_Orrsvwxy~T<2@+v*SbX;*yQuI~|?_w>YRY-sz zOeE{5XazgKI-6&p@BEeG>Ni38&UXigPz$T5Cg^C)z)*6v~PZlP$LN{7d5MPmrj%p$-=G&)L^O4=67*?jZ1C_{0V+i_HVRPh*nq824eyk|CCbSIi!|DzDmNN zE6^sge?}|1k(NeM%X0iZw1}Xxt0d95vJYHz+S9wBvZ4NtgVuq`sL+_ zKymKCL!ERIiO9kxqS&rQ{$7f7zD-a*uHO0yEkvE~00v*ydYvE3p+FM~LJ^*rj&;vs z3=WUNpfV^(zwwNY4;QR7|+#d^A`6}@3^ct~x& z7Y`3g^e7VB^e2e0K8U>|>={_kK-$;5*Vl;KoTK0l^gzCGL2e_hpOg2?!c8E9BbAs) zJQe%dyD2}Zhw=D$3?$U4*gPY0`41w84IE-YpYsPiEMob;(1jX=AOZXZUI)~&evz#A zbUXJs_+b{_jz@#7lH`J$QBzm_h5|=j|E-?Nw4QB|2oJmw-LspK_q|T!y&%3drO3 z01l(>WUVjmF^nvu+Ye(5dg}_x_gE{c^hM^3`D?t@QgKyJUog#ge)Pg<$$617`S9mx zIc{{n09GgfcGTydL~W|;CPAm_UnE0$s1jp*~B+kPq1M@A{cR^pXlxQ35~ za!#`|SyGCr1eNF{$r12WN!wT>r)KVtOTl%yi&8@^QIa^WR{2W?BJvko<@d>fp;<2b zOEg?AU;9Y0d=bP-9k*<}1#!E?@eS1LvMDHzlQ&o^gip{OB>w}@++NTd&Pe-x#5leZ zeT~?Pi5t?b6)~@a@~MfV?hsdhiqkK8AINVPJ9m6J5PVM&hC!0HvodOkcEYdX86cnt zS28ab_D-@O!mXs7#`n;1~>c3&`V4nj~oPj?hba%pgV5w7+aY7xSLtS6Lqwx7Y z@DaIVzJWV2P(^NME}}$-cntu}+j$D*i{`wej><38yeDD%8(0AuM{u3P7eH;#4$09z! zpjIZk;f@q~{~2Y{prl@kr}`2`*TCY?53C zccjn&3AtVyxiU!bEhae2Bsi6#Ons4})CI34L9#e1!f+<&xdHb&#qyWCP;<1?|MWMs zT2P#ik`}ceBTNRuKwuIG3?_^4z+i&%(mn@*yDOK4+Q9+1*-)DVF|}$Zg2zyj_U$CJ z2L-cNwr_+R`wL?9=JPh(xXX`_>h~1pYbs=CB}ud+%mvw52DiG2vapp^fCrdf%9+Vd zS7^dRJ1+lHpkaL-PJ3|%Mseb9z%PpPFCds@QxVpXU(8Ml9l$F`qQ8KmOWjJscedl* zBdt_@-y)?cETy{<0yUhVq-Mc|=GoHPoVtlCO&olRLaj4T2RVQ9SF7g8w9Lk0v`gCV(;Lo zC0s|Ie_@`_ve4flZdMwaKb>3#O5k7W><1roo?IrJ08rkFBL7WJ?Fl!V8}Kd%CFuMP zVa71A*0_+^akzD{F(meH947?gnb_whu{yXFXAC49&ffSHAN}2xM-VbWsRZVaw8Y97ZkKtK_W zTpEh#Yq-zshvBe0RwaQREa(oC|0cNcW}V{veKLycB$l*+0CxPy?{n}`VkkWF`#2tK z(W(AOMN$tDC1EQ_eICNzd+k5TQs5u>Tm>fX)ZCT*o=1!pZjTP6GWN$ko7Mfb;CE9zJ^k&IB?vpGJU_fiW7`0`R@|9X4OT_W(W)^dp~@{ayxG z(H`gxB*{l*7{{Hr^+Ex^!|X9)#YL=-WT6yNfzxm*!rt9fNQFck24Mucf&wkn1AW2* z-9|FoSfJ-wpwUFUYYKFvCjz+;DDYRX{;`LBZCw{#A`3l9u#8drbt;0W2N?ARQ9XRe zC(Cv<3oKbA{bQfk*?VOBSHzA7i#6h9#Tcg*=S?RJtHa(J%#I(C{oq&ZpfTJ?tvW=ggYQjcyzGTAMx7t-|E z2kV?b7Az2q*@i0}XqC=s2&dY}D-~X)&SSA?EZ&)l@BH&gJxQt-16cy}nybi|>GlUbiFTLC^;ReFA+r$_1eG;g7=-($C- z5e3mqE8s{A-YagyO*-Us*yo7Lx)p^2v+|9Y0GGfYJ>_cf5?;jf$9%LJMI5~n@1Z}4 zBr4X#(%S>UtElOvRu-|^!qJ>X9RMYRLh1Pt9s)|G*z7q2inl z%Ib=g;CJ%ZZut}}UmxR4%%YBir5u*BkuOX`buaw^{Z;G3BZY1VuL=8gO>4i&T~XpIAfs9_i>cpZH>tL}(U?k6##*b}avL)?7&GYK z0Rx!`!CJni`qRWYIyeNisAa_U1E{IcYeUZCMvwmitue3(VU^VFc(Gh@ZpN>=A1#Cy z3&#)`W%@QaDSO~c^uTKgDBd4q;EWv5)GE9V(&~Y6qJkV^InAf7!lM8Yp34>cd^i;0 zpAdJ;XRW&(HR76GkWid2GO8_7NQZ=@ZTZkv;cgZ~**^GdS)~(0B!44#B%eqRE%_Wh zc_l^!sn`Yb@~58o69$Fv)og4NwnGi zAMWS;#*TOJd5i2%@fii2s*H+K`);JRQ-P5FDOlWK2m34;KJI6!Czw3?h?!dAClFnyyVmX~Cu#gi}8NV%%E%FZ4J5{-!1=Jt8iU9 zGYKw1lfX;nE^&ifpbJMfw|oIMIL|H^#%#W}J_|Sc+E#ep_h*rPZP7+tF$pC%3h{kB zoe~~G2|uU4gkmLI=M>aRNf%m*C!(y9PhnP?dlW7!_T$;$)$86zL&On&Pn=~Vt2b!l zRrPxGrsp=GYDTD@57?lIxv#X)7;+9^aFy#LQ{xeZoZ5>efYc$=1+l9OF|;Z6^8!`A zY(I@>`JNR5GB>@B8X{Jf&VxrteCP=EbwnThvEwrfD}SO150xazznQGCWl40PG`QOJ z5p@O0|9ny@rB~kSwFfYnTGSJWMuCaeCYDE%gxoAOfQJjml#*k=w?*=oC6yB4e(Va zf@ZN@a;POQL7@1?jO5=kt-l5CAPpY-7i$UkcSQJRM}%MOh5zH2e4t_QQ}udO12f0O_HGrT;E3`h z;w9c&ktl8)dA-2|R+Kp7A!}PV*pGtybkp0vph@T6>YWJ-zwx3Y^1UsF$_n9@{)39A z?pD9M9~#oPxEHV=l_(|S!qZ7LpW;CmPgc{Dw|m~jITdaf#CSiJ{P$YfRgw${O@vVJ zYxQQ7j_Xc(EAkIc4)7rYHXGvjIted{PEG1ThUysgO((TP`;vUkMDa9~5;sgu64CpU zdf?#=lLF?n)Umg{yG)bYpUAOOOh%*C=K5rMQ-dfIzX*S9?d0@p(TqlNhJ z9?GZRp2z~EK7|0-Cv-h*1bG)V#oOFBP!QjCX}W7h9VQfnJ~$sE-Pf3x2WjCO4jJJW zAbfBe&StorxpBv7Cq4s=!Q`X=RgUf`W_l)c&QZa;9XYxiI#7;Q52D78NrcLUwE*=o z)IeYuHPQFb_c{&!^Ex%SX@je=|E)u2=qj;a=#Mz*0!V*FXx3PiosgijBNU-tW7EL) z(pkyZZ^by6+A+OKRxC}0ukcHMuv|}sv)&eKDIEa=VHxaATgVI5F$f-#Z2MZ=7VA4V z(sOg@DmX5i-GIT#s`+4iqWc|Vt@RNIHU%fN=VLwHaTzU$D_yn0BQE)SIUx^`VS`_m zj0Bj$nOQzitxXuv)bp%C@4%ZDuK&;;PSFi$K?#}y#|_{Yh@wwhiP$E)9B z`Fe8vT%f)5WK)*f5}9i1<*>73NGzLyKy(a*gz4;DnQH5gMs+5S3eQ>mz0s)RF~S6P zA}*}@tm4{gxZr=T9RvLNG0c5}+&94;cuh}x?X#NHm*it7AKCCJ)qFOS&ld72WIhr2 z=nOfW`(3CdvFt7;mhc?JB>5h?2=Kl7BDFu%h_Cr~RFbW=HWC9wtyH5%p!ua))wS$K z4o0%lk%-|aRW>ez`3Kf7p=$~_&8543&D+>g1>HGcz~R6d^>dA2Q+$OxsjFMu)=d0| z9fC_W!8bs-N-wl~?x!y}c;>pqZTqn3!MGq;Ts6OAZP4s%j&#)=iNM=_-C8OlU8U{q z9*5ji(}piZVHBB~)DAtfgn$J!5g3IMOY*)iihM)>wg;_pzvmr^Z@-8+s2; zeX*IWk3?Z3N3^r;LzjdP3`e=u;3qJgs?S#!5yV^VxZM~05?k+&4xkbCM#kB$4ztWJ z_d)z<6>2aF;?ykGK1YWdN`m7ewT|wn@gtC-;uXz z;Cj*HAk+{3j*_R@@LSKNP`|AO&4a5wUD4#d%?#w+l7c$pWQX3qdx^p*DWPqlKwuY==;~nc$l)V2e!oP}O zYSu7R&(q|(iu%r4xNyjth?KO?L|lhh=rvMeyMsm9UkZw@?MNdBR{oCm?_1BtFrkFN zhO7D`=O3xt-qzYbzIvqeYITpbrg^ve&Ug1*=Q|(cNyDME=U$mC8V9pk*8cIWk+nNe zv-l8-67xK$PM^B?8${K5lw)uuyqEFbkC6A9@D9wRabAg;&PyyHiE@%~Y7!2Tz~I!; z1P|muBAX=2!MZ-i53(dYb{+%S%oTG--MgY_RF)@y)V*Tav*%eNt=}LDvjJ%ZMUjXo z#BKJw5Ds}<2?`DCQ;2E}>j8M~NuxHHNE!$s*pCY_Q5?HRTB8iyK zNa6u{DLS)W7FNR#Ev6bImcQ9M*o@EVf)6!?=8V+m(SH#DNKD?OR9&Z_DRVl-WOT0kQ;dVLd)@KeEqCHdrq2*Wa%g zPjdLW1}=%nr?knW{uWResOYN=GcXHp9tjQnwx@j~ES_WX!3K&FIe!VXxOB4!E=9=0FYLnjRqr2#WSq0$bUV&?ySW`q z*mDWag2;P6yiv<^<;1vh(f`(g$M3J;1_Ssgo+RECgakb6qT91J3YIa zrpj9*ao@Nosy&5Xtt%eNVvc3japNkBdM$dKx{nsDNVE?3z{*%bkpIfmw~++BLQ$to zO!KL+i!W2`K2R`Cko}dpvzbF&ky3m}?Li9erA;c1H5L!Ud-$r_{i;KP$ z{UbauDBl7fcGY1pg;InY2sd90o}iGvepdiqtYg7^n6!GVJbLmad=TrR_F03@cX6WR z-;~?3+{&V1>MNS5Uu%Z;!DxJ?1T8BO?<{(pj`{K4Q|^%^Ht$bvWhCZTx=fUb zCW;BG7ohLxMPf!W#sib@L$U2sqF8={0&B)x?He_PRxtb)G>~4eqpli+>*1z9(D886 zXJp2xV?To*&e%b)zJcNAmDx;Y00rH*j=byH?i0(-{uEAe_9Ut`a;KfH<@QWKrF!AN>ajgAe1io#0ZB1JrGEdRh1m1c3?HjPXOO$uh z_ZS=f(P`FC@G*f#Rtvd#js5%QuB;h890TU~-MMmHlJ=2QE!6QutgQZTqner&A!Rq1 zH8{sUc<7Hyv+j}GlAHcm16=u($rPPi#O~(E%uy>TchGXntZhexd)!GG~9rHJ(nhp zpf`7cgfUR+v^cF6EUq6?(|=lfkq_G|?EH{UG^N94AFR}(AdcNx5-f)_&mx&of%o8_ z*17|mn(NTG49SC}CM020P0|xfi$Y57q)}R0xo37*qQtVU2&29|7FqbPnkHi$pt83~ zsJYNBcHE@IM;v#u>p@C-q9k{h$0D9soWz|L2h`=tW25ne?r`-${P!e{K>YB<%cm&F zXj#SEQELYpGN& zvBLbRXb#Xc!b6D)8L;S`7Vm0A5_qu}xweUA76haGd+b{fHzru&5k2pLb~LFi$PkfE zgQSmqvdgd_TZU<+6xa)rib_(766t!h;#>d?)W(<5Fpt0wV=?}G&cj0yow)W4KrY^! zO$jDsME2VQ(&fND!*hGO{|netC`9~*Q3d!TMrhU$^WQsnayIhN?# z&?hS4JJxYe-zYCE6x->I+<5UrivGIs4iutyU1v5HkomjtAv&~^c&ypt9l``kb|#7% zueY-ITIq|Ns6Ct$OvNsdsx#8w4?Ezc?OW3StW#H|X zj0`!zf@(4^AQUMPkNNy&EU1JL_!a%zQBxV&{cZ%Hnm}zO!HKV|pgK}iW8rjmM6g3vg2Bx4iQBY`dDJ(y1k~HOL%S+@xzJK6p2;u^~EX*bq z^iv6ZM;A!_9mm9&e{X|35Qm<0BsHO3#E+L7+<0P^Q;Y%giKGe$4aU9g6NxaUlBJ<0 zn0Th1);lio$sOax4L{S1(u#e{V3gB743%yE6|soa<`(}E^D4;k@-7z!4!{8Z9hXm? z+0$Apc$TNN*4sQWKIbwNH>?50=<=@C?KlneIfrRfJ}4A@G*ugJR?$2?2-g4Fn&VOH z(P^h*aIe)IH`Fv^hH)niko@^cv&b&kKOF`i#l8dzV2V1-9?on$)b~)V!E31CE}{JU zoI@!!&r|;&d+!1kRkg+s?+uKi0z(DmwWh)X6Ga8Z3&{xRps1*rmX;z6f+7&i2zF5n zu*PxB%F6Cm*0FP}?5cJX?{?R&W;ZHN4Tmmfre@`Qzjv*7;jm-ZInVQb|L^;rr_s&3 z_Iq3Jde?pJwb%CIL?Q8fE;NzypR?1Ny0 z4E9AZipc#aLFqvWT8|izpfhBGZpEGdQz{_d*hhiFezM@$c3A7FeY9@M;MkH76|VfNM6ge_=~DAbX%r%eA+_XWa_`5)P^@S zdUj*S>9H5+p`~mP-an3tfJOV!z7be8y;KU?hwiy9Md(Wt`YuuWtm7juCQOhnZ|oVs z(9n4~RKfUgGzPTU75nu=Q-1G9_O#$H*|Xc~izJFS^54K@dHjeYlOCN>;~-xDA^ZK) z(E?C`OhxwrFbsXIh#N{3*0((eHi|S2^c>jc z50~MjZC|0b&5TQ?;!+V*T)H5B6i$}_X+I~znslVKc@Okai{vW~=Nk44SdLkigTsbh zg&;T6^Am64n;WRI{2P_!{pNVW^x7&gsDJvq&Go=g)hXKVZ>>8V7L|v?=Cx-YB7}kO z3XqmZ20j3HH4lN}EmYu;$-|-@W!N$Q3GN(1JlVY3WZ|PLh_M z(1Q0Saj!SBw#J{VS6O;P*r2gpP5*|?{*9!GjP-XwiRE)ElN_EAcC(&Scx~Q?(SqHx*Rx;rV)TmCgGW&q@b*PBJq_|EV|1mN4t0hX^5XMLC`;1z0{FjzvGg?fJ9=$at9=7o z-tnT=L2zx6Tn`b~r&PePn)t|VQXAgxB<@3}TJS9b{_^V{%MP&8Pd+cr@maQDlbGz7 z39YJ3E@^!6c4+dZZJ_W(*1Ch=1A-flweb+y@bPl>CA)uN));_WnS_GsvX zR8ct&OZgyV`mZ-(?B^KnT)$kPeu5ibTL`L`*NPxVZe!A zzwHdvO{Z?qp`TabSdGv9({IH>7Nxl5fWPM&J{IE-+Ysu~S`xryZMYtP>(UP5Ce*re zDQ=MbN6_Qdj!d-Hufe_lMjGPKTjqG`L#@pb4BBr4Kb5>9YyWKvdjk{c0p^Hx=H4QQ zb@7{2NQCFHeQ&{9WU@EqIdX+J?G>o> zd%z~M2r~4QSj=b|$SyqS7%NoaVd>}o$w>7Jg-A8E%(xGMbLAoeZichiVky?`v{(-k z+k0BgB3u}?5PWbKD;X|-L%wwlDyR|v5;QOC&0u_T7r_pCy)s1s_f z7S@JwM607`!gyelHXilZAGrzjlM=Yck{yrjtI+mJI;r%gW#Qic^$X-3%TR+R}VEq)|n_-UcaOS~%<##l`>h^H41|YkIzrF88H`iPU`VFF{hs+AAT8yer#v z#5z4J?g(DjjDv?in@DL8X*^8H@TKe*n9u!R;1>A~E=m3o_sw0U0ZwVajbN1q3?Ku> zNuom%=d}`Ji0G8WOC)i8EAcoU@rvznNLQ33Cbbd|5|KV$;2(r~+qyQ5i3?Voj<%_* zINr&DhZe8jOitXzH5A8Z^wd!@;blstEQO9ch~+=tl>YEEoS%wgH2k5|E?}0Um@<%B z8t2pW`h<|C_jV(Rb+2H}unAfV(a`&cDYxG_fhI}c_#Z;!Lc(7d8Lnf7rOeO`2IlyB zPkuzoEk|}ayj9qXfL_$Ijb>inwA-;t;7_7>_hiIFArdn`$>E)1^M=sTonym1>8|hfgpw5_NE;69ux_PSWPO=B*T~~PdNRU9)=^V~#-nwy>nqY;rw;i+ zgY$#`8=q=$-l5wAxTTrVzE<>$@hJ57p#EV=GYZus3}2`SxB7lS zcJ9fDMylcb$d9)nd%7bm>#{Gckfba~syvdvJ;lR_Wt35IJ?+ihlM%y;Zy?1zf)rmw zQX9~KH?O1u^(ExFkF;U?2b{Q4z30w4!XpOVkCT$MU7U zL($%i;Tg3R&&$;8N3+F@@2=o>l<(_W1^1I+J`3I<1#dkqcsB_m8@(2=@1)R3gpkv> zG1Fcofza{9@US#&7iiM3j$~MZiU5mDohavns!a}9{m)7rnQ_9nC>TYZF|-y^q3idB zb@>UD5NpE`{3Wvwoe#7BjT;%=Wv$Vz*@)=G5|D6ES}S1*5hk^IF0PeOM1-qB@NY$1 zN;{5{+BCE@Y~)pSEXKKyQJg5%w7g2gYIwpj@@t9g`o}@o>oC;+foP<0-iajwDU;0* zo+r#l11ujp_)^wq!wXNWgdPl@@EL{3r?Hs#LVxI24MjpGI6gr$jmIqSL#veZ(DUMJ zAuQT@>GC^8bQ$vLiKFh?6Pl|#HG4FSm8&1thNWaV4P|%CLkdv-uv`^+A@tC9qM#kS zYzE@)g>ZD+h+z=IXqU%>BI9`YCQ7m;lUOlWgVk<&b>O#`AahBO)emRD>Wg5t=%K`M z_cAn})`7+&!D?XZUy3|{8hM&W>)7^F@cq%PE*sn$@L;)PKjm0%n;df>$GjHLc`!{J zO=DRG1r85-c08Xwjja$^WF<8u)ExGtKYi&>s>oebLk+pym7xCu;nW@20687^-pKo* z$m59drrbh-ifF8lQXsc4-u<9JM*N>^__7B*PJ^hY7 zp*0A2s%)OBuAXsmo^d^oS?&Za)RpO2+C|3*%xkH|y*RM?S`olOg9{O;`!Z=WjqB=8 zM!RA+cN8I;s|)tTc5AF_I#Jiy$&=xrsR`w7I95KeQlC5J4_ho{OW{o!1e=k;Jpuf8 z!mrf+Q2W&5MugEmIa=E%H$E${Gj|5&KWH3O=25*S4MWGoyUxT-n7m1K4FPlbj=CjD zHa8Zn#-!874DewGygcLncU|wobLjxmPwFCPAP^#lD=!{FuDmle-vkpVo}dN3 zx^Er$CQ6%3$AJQ>)w^Z1D6F~A>Hif*iu!8z;+3ou=i!Xp6FuFd#$!n=zQsZy6(41B z4G>G`IoJdjPaBPF&0?(UjqD4ZFZ6_SRV^F_-Tup{tAkTgTb*KU_z&_CX>IdrJ+Y`NaytI>DkSOYW?2`69a3|0P*8(=zW*`Z^WW(*Z| zS0{1k+Q*+MqaO9xk>_u4$q314-f?o?;%jk8fc>VC0U#r_me;?nMDDq^eg z*PGG_W_XS-{YI>33lq8W`*QRey!U3knI6hz`DW@Pa1^_a4eMcc>-g83*8n>-kzU@H zlL>FiR)ml5j;_VHul@&6vAi8kYuVj_2kH}`u%;M^JJGanr7|0Qs8rDTIoK}a`Ssne zG8U=uhf$KHOm)rmh z4rRi5%{Uk`+}^3N?(1l~emGO#h7+4a;k4Jvwy>$LaRK&0Q(Ksu=^LwO;&~MIJKQr0 zwVEauvO}AE1N^)jWev}|aU#YyB3^&PJF=jiUIDT;JPsM~;cS>4LoHokw6);?ibTtY z-jv&MXZQSQU;2fxb64FDVc5d9@MTZpH|Rw?v7gdkPNL@*`wQeLYyE{NYhu1A6KfZ# zZO((vrN4hh95*il$I_EBya3_4ZD^doL1NBFV$MfmKC!n+Ov6%4->JAa_XoHCYZO~q zSiR>_nL3*)EMD27=;{7}{pyrbxx=9tg}P1Sik-eR+&PcFUqzh@9({W=QgOr$R+ola zW&kfg3W7f3Jo!Z*Ob_E7-fO$k6j6S?LRjwq0gVGqIYW>RY7M3bHODmHLM9c{&I;;} zpjHmsyi;JO)He^s02*^ur)YzjTQJ_id4r%r|`PmLBG0_6V; z`A+2kdy4SEzExnPN>MkmldCVXqy}-&HW!P3G)(nc_MVNxy1nkp-#xMP&ORQ7xG|tW zZw_9vNAg|+opq7-fPwq(U;jg$g%=2$OK!7}>rcM4Z1~MT=xtP$&BB!mpF;Q^gkQU( zb%F;k6Dx;DMF>WlFWZ?o+t?bN{k~=q>Pq z2ZEO&h?k$5Gd=%-jDH?h(s1Ir{Opm~PPRjew%{n88L9q}w0(n4C;ZIc;S;LYnR8lQ2GsSE*#?Wp!(x0?(H(}#0KR7+j^?G5y#5RH|Io_zHq^B=Df47W z!uWkI)NT9wACrcVgOAM=sdisxcNwT2{fcP#BK< zKMLSKFIW|?Dcg)q2UZo@x_}IOx2com)3eA0v!IAQN-h{AJW5F&`;nXtzzr_UEHJJJ zs@=beqAO;|a*g=u6Y#`GSl$<%kKb}V?Dx`upVn#6!dw#DhYT-y$B!{HjrWf@tbMTO z<~2~df+A*{K)rUvdR1gOR!*>Jxb5W-M1whKWDyWp`q*1F#&L;dKkUZFqF_ahj!Po9 z1Ff`R;(AqN0&5`!iL4;qdfTpaJXgjUafX7k4o+)03!wZb!H87mp zM^T}&kE8{EEWYK$#ZdFwbxS(W^TXXg+P&^v?{I$>5{Z(IJr~z@ z4NQq{DbDgPKqvBhz&+S&vBPva{wNG@aC^2d{gO6bqLT^lt-%1a&p1`L7kxDfCfT~9 z$D{EkHths{tKLE1lIRD*zAJi^qPx2%GLkR*QktS#uW#ZE1cFuy+%#YW5n8P zyJ}0OiwCQ&4!8+%jl)fZ=IUpCKBHTszhix)dnnosOo2akt)wj5e4VH~t-Y%q5w+Z2 zdu=xj=iIgIYtJ9n`bpG=48O$vz4uyd+4$Z+@B`tJ&&Amu*Wj;jdXwkCUB?&2c(7-69uPP+3AfP=AIrhdgoEh8C+@>3e;>eg3pb#& zAFw`Yae&if>4(48CliOzO=55S#q8i#0n(}f{P!;*yVB+os=$&9oAI!zqa*Z2Khl-y zO;7bk9=F1h*zfQcvJ*(?7ZRe%Wf>z?ts!g%(vR~`1ou|_746VgsL}ib4nUybC-IuC zH?2EyU-A&?utXv3O<6%gX*b}ne<*G2l+ATe)m-}RW)#(++F2jB9tbpyz4AylaWeiQ z9S@L-<#0OcevWTlToYn%>0I}c2%O~_MoW?2v_-^q=o$?0$U0i|?uZ*q={{i2v)yla z?-i>S-VJe>wP-C^%8z^~isFc^Bj8KfjTca$YXR>3gVu=xg4Y&5_g{q8#qJw35;eze z&EA6-HmQb*tjhMhhga3J-#ujajRqYj*JSUObbPM)M|!0bwKAI8oK!N50!b7=`#cH+0!!Mv&T1{0=!yvj!HkQjO4PhOmZ3iIr&p|m|1^OIB9vWHCs zm@Lv^bZ??yqWpN?gi3!pS{!~`bsg?$Y&YBCTP?h&_HVa&e--bi(zG=qr&Hn1*%azZ z{YA8+Qx0yuY0>1I`6w4O`Mey}3MuJ{eTwlr(7anJ#~?`#EJmwFfA10N<&iUil^ip- z;ocI*T>C_#sN+FDav;Y#YFBf2X!Ms-6+>b4rQI<|jp*LL2RI};SN3H?7vff!iJhqz z!Zdd$c2rB0f58s42ZwQwY^DsI83~&=p@O10Mbmz-NW6SRYbVkMs>$;C*8XZyYVh)~ z{3=McH**&)n7N)pBNo`#@=rV=Z4+&0%)K!-e;6^L1?;K0@g$q})1ide#PFL!@s!=8 zu}IRI-DFvFIm(sfhKFr*rblZJ`YLtBd!+&p<3S`p5w?*W{Y4fjn-2Fz$6BBm?%j^( zvT*NJfYag7_$EXqcaXxnP1YNA#I}_dXZ-t|gLg-^EG`v*~eBSSJ0D!62aElcr zcNdKH0EbQ+fn`MuR=w3D(D+|=62dez<@TBeq?R3>=^d5D{l$4}&*O&o^M+a;ryDOU zOFN6AyQ+%j0jmW&P7U@YCejHAcog=|FHq~dKGpxK3{?0VMd0Q2QBB-x$%b_c`25S?!k<*k8Mxi#VJSv?L64@w2cRfKTfXlk$5`a&YUAPF zv}vF#s^%4lm!9>aVGKtV!={06P}0rRvYj1Kk}P*ZeH;0`Tx!ExhX`VI8lQz&eOe_S zds>S5i}17>SBvJ^zcU!EUdXRY`#2mW#C08B<9kiG-10gBv6b$CW9W`5OsGFXYkfN` zLceuNfV3q5{=2C;ps$#CEq5xJP)j`phwiA{i%ow^5Jo+{5n1sfE_x#NNDYCM0|N*o z-rN_B?l8P8F&Ug;VBVUJ%Hoe+1I>6I4`j<``cm-m%_Q#6x>Ll6SKE5^2CZrUttZNT zx{TpFvM&52M8Fsq;|Tg1K953yDm533sDv?-$mY}3u}d9dL&?L zQ-ClFQL%M(6nqZBD#|x+k0UTEraGbx-(FvQJX2#W!stS5Fw}w`6D`>ZI4LW!{CkpdFd5|jp zo(?!Ix7OP5BZi6AO?YrywDc=qXB<9Se>*a+?sXeJ$nat(oL4BMR?#;|9NyY!00hsBHoo>o+-%1hV4PLhP()O%9W_iVe#SM(C z!3*1PgHykjRAar=dh;9Ph!Y`;XJ?|F1O=aN!dI*!8;EIJ6t)ceeN&^UGmo}!p}{ta zM*_U+KU#sQ++CzF_PKhq3V~-2=6M*pVH`zYE^0=OT>9-ax);#Lqh{YI{|x>^x<+UWLJP4 zoA0bozC}X+u|64_LkhM>XJW0`E^DWd`4MRUOG4(NU>Us30HJe)kku$ew)<~{5c20J z&M@Kp`8R^TY%AxFv`$*)C4tKzKo#fosxOxD#foq1%ZL73ua=dV+M4t>wG~gIFj2ye z;j~j&nWSCl;k6?*H+JD$z7q(l!YC5qay>H){*!Y>Z`ysZ%O8)}QIXIdVQ6r==NS(R z($OAaSc__eb_wgIDc3;&{&bSuVXO>epnn&k&_a7tW=W!hh(DDP5gX$D*?98W^05CM z0!v0S|_ZK6o|JMGONFny;ksRBU6U?a{-9YGo)KcSgAaM)?JKl8J?)-1@CE=DlGCP%81RCVqu_hm$>`aO!;x@K zMDit>o@W1Tn676H{nom&0QCVw|CKWU9eN9VoDdqInPQtef)U&PH!&%fU;O zTTa(7*&^*mYZ{k>_C*YMi0U~11llUmI3lt(e2)PWIOFh{f@o_)le|m9T~}+vJGg7U2KM404E$|I zDaSJG5~^6i4^6F`GRdJ$NaB%ThrT8pwHpS*p(leIe@VgGEsd!mNW0lP?KAV}`xs8Q zc-0_no;DAS*E?tybJ5=sQ0wvJT-2r>q725yVDy3#87n~AF);d{LODd=-x*y9jTG?Q z9$1^}BJ68k3{e`(d1Ej{NjX3q-n4Bb^acshc-(7wnYmgH2xGjK6JVhE7goN$#h!#G z(=vdl+Y?>nN&7d2cI!Z<^btWIQ^;!32f~Us5XQ7VCLX}EM~>TUB^;Ni8Ke}eHG|O# zQ7_=A+nbKT-ccbJ`3Qh|7M(ySZ`zpv{_CkBBy%D4D&gyzwzlBKHu~z#W2dnEcBIqx z;~$*A8;{(wj(Ae;0BXSD^)|OI-eUDXv7N<9LNw)d0XnNQ?P@Td9k)Ht;>%I4Kghh zDIfbglH;2nZl}+yp{&F6a+wpuv7=FCWLEGCQ`dR706M# zcxex-e}l1n(Nb1h6Qa2f(Wga7W3xw?@dcJQ=tm+LwCmRA!@Acao*iiP>->2mF0|hG z1cEq(1RrUw&(WL22Lm#@>o`TkeTKuECC&{=5H>L>(X!H05#)o$fyy?R7hVe*P z%Q^n7SP$@j<3tOFYKgB6A#ISOn~n}o;{7CyGrR#bYyD`%jJ4A~eMlqkSl&YaQxVz-G%Q;w z>Hc4E2gA2h+~ng96ssS87TkDFo$Hm3KjIXekC24 zd0M_b4sT@ya=#vTIE zmv{ClPwYX2j%~A%NbGJQ@+CwfUl$^`2$9J>6SjLT&yh%G$SIHIUkKs;8qzOb=X-_Z zXryfENgZqPeu$7l)t0wKy6Hd%hCyfuuJjQmQ$d)acYy`68DiJgK-l8W#K1 zc{=uNV2Jn-^kP7DCjj0hBFWf~@s}2sylEc+>#r{)8)%XBAZ}5SlW_i-wVpoy3c;sI zByv6e%I&^nr{9kFn(qPwMmUBF&{~k8@3mhrB0h;-l>T=c@#oh$WaV`bzz_>H=6Y0z zfNOv%`6%C6WSOt(H@qit=^_e74kzF_=--MMu`RfH18Hj@ZI446z7q@y@^^C<2!C!( zGFekf@>wLmKv?r<=KqSes(CmG#E`&BDO2Q)|`M48lvV(fIB8T9%;f=8MgbSlNY-v`2*G6tCfYA#WSqo^axcOuNCI8T-0Pw;{? z3gdnNZ{&H9@y5oIq$tH_k?pX*NV!0Ov`_&5XR>LYD^Z6LjsFd7mV$4hsVSL&5#B3n$TQxwcZvAcRiu}v zD*J%};RPb3P!mNPm9cf@NovY*-i0LJL*(6X9+r|~=|#P+@_oJT67-aEHpnfOa+Ol9 zfaF#N$)Uj_add73Fmk8(AlLd%+W?Ztnaw-EXpgHI}iVvNGaXFEEu^;q8Ghio@ zI#m6ZneZ3cM-zEp+7>YQKSlQXHLRHW7aZa>U{6{xvIUwR62M}XT&&j9p z`dCIH6uH8ep455+cB@P4LkR2b+kipIlBepNzjsHv^?6=F^hMT!rKoPiBF*h@!RwIN zW|MZ5W{j>VZ%2BYTZ+2QtxNktps|M}On0Acef}J@h!ml9Q&{c}j0J1o=n6pCM8+0H zYPH7s?b2^=f^xj+7?Xp+7T&VR!3X5M@1GRfj z4SfwC6Pj?+SKY<#Ibp|Pe0ZF9c-yJaxmtPfj*Yyxxqr>Ty^HX0aH+D9FZwXQj!*uw2|baGBO*P zmT&^hF}{dPh0KpYQS6r@<|Of^t#rk!M<9D>24r2374DpY1`LlFlQnC@gBB6&t6A%x zfg@;+kAXy_=-oV@AO9R{6G`-0O%b6z;Wvs=KaQO$ahdwhAjWxqN!aF#xg^UMfej>B z`IeF9QKaRF6zQjOmz1#0^Phx+2Cja@l?|?zVW{ZeLDpt{@_S!ampT-p>|NVVgpzN3 z5gikDW7ns6p}sYTkNgPPNXTaC-)CbMPs#49Jgnb?tqr@SEQ|&L4p%|P{gAnA?Nj@JX7xb>~+wE8j#c7p( zS)=HKkv89Gi|0t)_7H3KcAR2Kwaj-}5D$ulFKnSN*Mf7?tqx+ro;g`>4nwW1H(g++ ztT+8-2=!)?3}Kb0LWXSChPz}a-P*8KhH9<#v~v$Bj~V5Qup#r$r6n#~R>Bu>mOY_0 zJ9eRZ`5;wu4YLr>VAh^ zu`ISrL};h1YAZOI1Q(DXeF}u4B*Eakg}EK1xnyf2$*bZMOTtMcd@h8=0XI9gwVbE# z*#9dMYeT~ZH`=#-d!Ak2Y2W`FIn+Itoaru5s}0P*G!?-cNogJ>Xi z6*^f=;I#zo)W;JcI)B6ZB<7me#id&}O+@%LeXq|UixbZK4xz&grqj_J-~t0cI}BIQ zfgO?oV86aExImJIne-H4HjUt>%hNmV{T_Wd#=!!l-2vc#>Iz!rr_y!0kcbBd#YC-} zhS7(3rkXEB$4b!|L>x^ynTKjf^7!PaXcdE8aS<`up`p12Oyp4HSe(9%*yHU9I65HM zaVNe5_gTa(4u_E3@%lE(H6!mE#QV)<;C%#|RRxLrW6nKHjIR*mGBSaB7^=$=9hD5( zA;nZ7tsoIEiClTwYS_W0;}87l#6N@hhY&wk+V%@a4Dt3S-dOO8))=i#U;0w!Z73bw zGUsG^sL_l|_*obXXJz{0tcZUw4xmi<2&?kRo(~iDcyK@(I><~5vQ#$D&wUH^`=$m0 zXf`gydUrCc$w|P68QQX~B(}%EhR%X|h+9sgB}M0t+azjOf5Gj{xTNNfDR3C$k{od= zCX^*p7ycP6MK}q@p=0WMDfJ`q6eD#7=%rAJ+w_m;QFOx)9rY)zRZ!Wi6SXI(Yyk{v zIEvN*7UYP}AYsv5j5IztF1*_pX^o)*HT$5(BHm=Aj9fmPG65+Z9Ms2ADORSBCKdTmfrOEBMpI+m zHtiP@%cWpxE<1sm1TJhhQoAg(raL`F0d};+P<$~ky-`^hi+XH`l z;BOE7?Sa2N@V|Qi8<8M{XB}F4I!QY#E1gvZ@qq3d(U?;Ifsu7tC>1+RBS; z75SC<3!E-zWtA$?i^b|d1-kiSloTZ|5nCe{d0?IZ22x% zrLA|N&0RLXth~DH5BeBytEenlnD27hs+_L=u+ZtUEhsN^LXss^{EFX!!eO@FRh_kh;*!!r8`8I+qzv&D+6vuHo2%SbRWi3Mzw~r` zRW8?})6TG!I?Lv|iosZJD=jaZ+gWqL$dYTE!)!1NQHeW>7pi&}*0h?f{9WiQ%6FH# z;B*tGAXDI@;{38gm})C1&aXrYhuN~GWZ2?uy$ekJR^`0f?JO&B3eKdO-bn+e+b-^1 zaIq3lev>{R^NO6Vf@1iBvye>{$siZms^K9g?a z!w?t6SJVFwfsU%24YQS$EzB=PKFIPiFRgcBUt2{Q<%ImXtMccXNd;^6HX85N0HTTu{DX0UU0dkzYBt%GR%6zbjky{2#U_vD8*tQs%VHpkg=p6)Lq; zkgNLH3i1nzowkxHNv?1$lKC)wiqn-n{<8d1x3g-PEu-9xT2fZ-qWWl?J|WXt<*F=S zl)+K;>#R-BsC1(KWM>337YH2kyu$5*cgtMP8W(u!H}!PcE-k5;Q=VT5yPTzHd`im8 zY$&*;HG(lqRHrIuSs|ruisG11?kXu-WOFWbmbnC9L92zZsl0%i4q3!5?p;NNe|&xg zO7ZyevhraSB{j}cs;p@Pl85D^RHH!VSEacs-Oge8ZdZ9RO1iisJ4L;(%&$h>uc}J5 zm6h5Oodw0^w!{*o#aU)cEU0poSIn^uN=;5m0{@gn3+9xUR@o*vks!*cHqmkQm(6C; zTxlWIIGN1qO4(SUR+hV6p2h}6nlhO;T@@&@@+ z=&q1q*~U3*N~nFSENo(%R^^ZQ@WPMXpo1D1c(fpll?vrR6Aplp(#V zhEd(6TUlbs-EA6JBB4~Z!J#om@eFE(OUeserMA56i5XKe^71ZkL!0Y#RaCo6xV^51 z@2T`IDtC|UtW9xb<>Y7?6LY4HpD;zs$)1okMa#>YJU+votL01_Gr>MSOPe|+YqI8; zh!9auCyGk4Yyr^NQ_D>WXs=0Yub zxE5dLE-g*a;6vx!%5ryEp+>cxgJ7#FuWU70wYGEetDJ+AJ8L8VKtaPMN}-G%tv)&) z3UlqDDqlpAbZ(VYZa@jMQPJ#OK!0a;*1lYJ3$EL6-HL0K_T?Qn;ksM~~Y@W5+_J_6Yo3xCa?A%ck?UOUn&x;;Zt3p}MpMy#z8$?lX zPq&An479cnT2(2U2d&y!GPl?ThnJ!GBaE%E+*u`B!v*>1J3C8~WcvdxC?27KN?Bps z=2Js=>dK);3wf;-l$M}TnF^m7mq8>BG_b#A+TAqFKn5?`hz+~f^sP&&yQr5q!Se9XtDpsvpP->qlDxJ>4 z5|Lxp5 z;)J%C-0VzkYToG7SyS>RXHOWT^)7X5QUeI^$yoduFnpi`fG%>)#2N(bfOJ(NJPMF5 z7s3#lNY_GyGXQbTNEtqmJeop(vT#YADWee_hYRW{m`vosl65eJ2g-1ADZ)A8UT8=z zrZ6##$2DlUKn^5DgQP$%1wo%U57&HL;G4Jr7uuYO<+v(vU5%>>mm60#u0;@nAxILy zAPJHsNReQ$1Vbc9m0%#!O#%`mOOPVLU2&G8R3>IJ_IR)vHAVq?~5)6_c8DQch$tFRH1cN0Q zBtbI3#L1FPf)oh`OE5@+WPpiOPY|gDDH05pV2}VJ?*?%B095xP|8Y;*IAg#N`B&!d zfFaB^WVqxS!d$6>E0wuYnJbmKQkiS0;2O$YLz!zRa}AYT1GyoPTmzFN7oaScNz5hM ziGfHU=>(J|aR756&jgP$dpb87w+Eq;cfn0vgtt{yxF;sRw5U0tD6d4DNT~I?4C}*syXx z=yVZ}3I{_a8;n!obAsd*?tg{t#oG_I^u+ZjE-S;U7!t0%%WZ;JyemL=Al-Xma~0tI zxc2bB6xBmN1veur$OD-f7(xD>7l8@nIEbsMgILD!22 z_l68zB&+xp4Az&&JVOAFLoS&1cliHZf3%lhlqV}W&nSF%P`JJPdgm|bO6I8`K9W&a zuf=%wy(AF0gdv5eAsl=W?!Ud3gZHj>_~!t(upK<l7b6_>JkaLws(1n}uWG~x+o`lKK}B}d9KdF{X8`xrdy z@CM*kT+O(C$J>|}BmI+bU4!dKTxY|tV{sMX8U|l?fzKDq7mG{ z^q+(Hg6R|o#k&qy6|Pv6AF_qweHgT1xIYVcB{LR!t^{roY*#pf-{byLwrM=z<=Bl~ zi|cM&TbBm{PvLE5g2t-{@ai(s{=anpEYd*(HtY%eUw)T@d1P|R;326)hYcT*hC87} z4+#ykbm$n~sdJZz$S7;{8E2jq6MObKUAsx$-Fx)xb?$k#^DnqCuJ=V3$0zi;q;F!s z{z(G{4*Fl>{(p=&5Qx^6P*Sj_PF|t=_I|a5#HLbuv!JoI>F|kKt+5H4&PrAU0=MA0 z8rLFROL2K{eTTOiOW*q6uG?Rg{I4@S68gDIp$qiF?f>VfxJy*2-sK*lyG) z>6&KIr|O;ce+EY{ND+FDzCwRP@1%FrJ^EdG2Ys}DkzS;S>SyT}>VxzseS+RkU#UN# zm+5cm-|JoU9{MG^SO1qjT)ziz`bFx!^zr(Yx=+7Pe^dWd@2H=nkI|>;7wbdxxq7v3 z)z8x>>a+Cz`px>2`bfQ8e?Wg*U!s4mZ`FU$PrD;mU#mOxdc9HasxQ@7>7(>GeYSq4 ze!iZhkJZ=dO?r3zL4Bh>U2o9$>67)|dWC+4{)m29j|k}$5*pHh9{=H|>mhoW9qlZOw4C@eP2@4Ag4GRg=!?duD;T^&);bGyS;UVFAcv!Ta9+DoK zu19o?4vPto(PJWGI>mI1=@JtX(;+4*MkxRP@gKcM=dwuiL(Pskg0jvS+X-A&{IC)eca2T*YBM^u{ zd+=N)=4ybWvY;REDBxPauA>8imjH(Y?gRV~@DO06xP{2~au6Su^GoTyrFyIQn@JPguK65%? zKE_Pz03$Iz+6nm4b0I1v~&)1Gr#4@(1tc@ z;6}i1FCd+OrGS3GIza6lO}iVgE8t@88jeuwGLiq%A0v-aq6Yw{{rvRfdTzCgC9~7xD(I=cmVJd zz;6NR2X}~{e$8Y}uZh-sM05;ag$K4Dx!5biL-`QTHlZ4?JiM$t9`hZn2Fk8oXq+X7r= z5DwpF)6N-$xkwN$9k_YG-Oo4&U>bf#?RFusZHYzH9g~c9~ zw5)?YDz&a-X4LG0&QYmA*rVdX07eLmiVC-*{fLM7TIebp5(w-7)a+5w%R=l?UF$+K zqiiADJ4bZ|#x9tV{n{Jgu?)q0fp{~cqC>`X7PM5{`hklchVeYYWRs6CKGl8ggxC<;x_XyCU!20?*ULgLDL^8LkRh(OEbO{+F3W_U1q)=TfwR zBr}%uPKW<@(D&rEV$kwHyO3!35$-zR3V^#;U?gr8a4oC&r+eld+bCu&{HMw3F&aDrm2OCi)EIRUSM@bvhUL9l)2841P9Tg~0Wg z5(rci0(s7ULvt$|Z_;-Yc*?+&Nj&*c(Q}}W@H9@oTj7hNqGxb9q7UQj2EIS>BmU`J zR>+3;K>G-^2%_Ofdi=oc2M)t3!Jp0gX)gY{0Z;XzEAYF3|Gcf=+V8_ELM>q?d5{cU zh0ry9I@X9N?$c#AI1iFS9Bum^viV-fT@Sf)NRH|QY^E~uEb!L>|0>3Qe_2;+^-v!w zyGa)->K4r*ex471{dJkkjLHp}W2UVq%*lnW=Sne-MOgMNAvbf@Xq2}VpxYN#?!(lcJ3*e_V!vfj=LC>x-j87>Z__W>rZ`mKqHI1!~ zLY59dqO&E)e=F6PVs^-7QN{MC3VT$IJ*rl>N8O~4idv-?M$M+71PwMsXBTp%2^Z8+ z{^ViK^iT!*Ak@9T%%8P-=sg|GJPKVe>lf_4hD^U){$W%^K7LM`THWja*sgTA+G1A)L_>l+ibPY+!c8g)R= zL=Su=?m{w5l_x=WU(CHLF~?qrFxeVBR-uNW&@d(>f6ayrtrG{gyYax+0RIH=WOLhb zjH+s4Xd>Efn)@FFFZoO4T}D(m+4BvBmI%LqAx<@k%%XO^zU8?$7Gn44TJEJ?=g2GAY_t<;!z zxuNs*sOk2o*@aQL#la&nsq+xz4&H(1T&+51qSm4+(tR|>NT7X9zCu6J+7}=luLU&# zSHSYo=nD#9DW)~DW4KC~E!GJf(6e|=An*X`oE#NhA*>Fe;fm<5ib4M!^wmUfzbqw% zIR8+TMEkK0vQzKE8Xw6buV-`K#N)ORv=Y$DiFSHyMJ-TvuqUW4ehc0W;8kPWG8jkr zFswjx3-Dmng&LogZm<-o5ee4BVf9#GMe?K8pG8Ebdbh!5lZ zg`96hs{t(wv?)vr$&y6MkJX@E2U_sBkMOiMc&!1S3j9vsuK^z4kKi)g-k2)n*S13` zIp5YWh8({R&n6*CKO3$X;BE!(AA}HfkXln1D2MA}%$N$E*hjGcirTl+V_LKE@x!4e zZGsKr_0Y8vYnY7)tN3z5N?PNy0lgkPeI7ICLDAOU0MBmlp!pL&>MxprYXq+Ee^VYY zR(|YH$|IGzBxJQeE-IYz z-Ua;V4S~Q!!b8U*7NR^}g+Gfx8%VU`sA!s}P}FD!}$!$8CEfr(*OVaJDNV4fnPPJ<64GG7}hbok)fC2%?xj4css+p z7~aS5L57boe2n4a44-279K$UPUuL+C;Vy=+GJKQaeunQe{D>ia$d`U!Fg(Q2&#;A| zVEU6kUFZy=px+(zFc!Z@K9 z@n%@x`)w&R56Jry@3h6QGTbYwuePlvmb9-piSs+1VJ^ej3@aGcGF-)Q zEyIlr8yW6n*u?NKLv4~wN;E?o!z703409RIW>~?nmfNguO$-k+)OhhWnxTzh62o+cxeRABtYBEn za23O~3^y`tWVnxE6T`y{#YPFZAUaR_!NxF&VLHQHhO-$~Fsx;`is4#@8yPk-+{dtq z;bDfFTvFDe8QK^oF-&Ke%WyWs3Wl`|S20}6a3jM;hWi*cF+9vryNvUnp^ae@!*qtZ z3}-W}U|7p=6~na*H!^HwxQ}5I!@~?U@sJDtXJ})X#4w#*l{`ICrr$pG(=9exgE3de^vdZONSY5N|nrpASezADgDw;AxkIzbK_&T0w z=%qE9c01l_qo2Az%=2>j?%p5iKPx&5D0%b~Lep=G&U%e>tp^J-pl|TePsv-DuIw?= zJ1KdVGtwg!ofX_=(0_(OzmXoR=qi6k80hyI?6Vu__cEP_M_Lxs8Lb)V*-TgV zD7w1OHIY~JJQI0k{|u&c^0a#l>7Q*vSMqZW^!p9+rH1&8^eU#S^eejZPt9M@uQSoF z?5i`8SM-|<@(-{fDt@0q-bla2L|*xSjfuRX-)ADP>|19dujuOy^8aM}75#Cht8w(d z4D_cAbf1C#++WZi?I}~jV%p;d`7H+dCk*tL4fH1s^lb+EMgx79f&P?%{;Glgw1KYL zKjoii4D>e*^3NLRs(n-P&l%|Z4f4+$=&Jov@|z6w_YLx!4RqB%DETc0`bP%&$xK(} z?^C9$`mg9}-gL->uI5oKCUjMQj+oHZyzv(kdh0xv%a_qUHLvYxLRaO#lhV)WR_Rmp zNJVFdLZuKY8{gs%KkVnSExH;zwK`V`$bKAB?h zPlZ9hqAUMYo6wbi7MswOe;Q2a%0DYi=*mC0nb4Jg?lz&T^e3wN!|7J(Q}h9f&h^W7 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 WzqHZix5>ZzJJ(ZwPb>Y$^8W(r$6fXS literal 0 HcmV?d00001 diff --git a/st.c b/st.c index 6f40e35..acd52eb 100644 --- a/st.c +++ b/st.c @@ -35,6 +35,7 @@ #define ESC_ARG_SIZ 16 #define STR_BUF_SIZ ESC_BUF_SIZ #define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 /* macros */ #define IS_SET(flag) ((term.mode & (flag)) != 0) @@ -42,6 +43,9 @@ #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) enum term_mode { MODE_WRAP = 1 << 0, @@ -115,6 +119,9 @@ typedef struct { int col; /* nb col */ Line *line; /* screen */ Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ int *dirty; /* dirtyness of lines */ TCursor c; /* cursor */ int ocx; /* old cursor col */ @@ -185,8 +192,8 @@ 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 tscrollup(int, int, int); +static void tscrolldown(int, int, int); static void tsetattr(const int *, int); static void tsetchar(Rune, const Glyph *, int, int); static void tsetdirt(int, int); @@ -409,10 +416,10 @@ tlinelen(int y) { int i = term.col; - if (term.line[y][i - 1].mode & ATTR_WRAP) + if (TLINE(y)[i - 1].mode & ATTR_WRAP) return i; - while (i > 0 && term.line[y][i - 1].u == ' ') + while (i > 0 && TLINE(y)[i - 1].u == ' ') --i; return i; @@ -521,7 +528,7 @@ selsnap(int *x, int *y, int direction) * Snap around if the word wraps around at the end or * beginning of a line. */ - prevgp = &term.line[*y][*x]; + prevgp = &TLINE(*y)[*x]; prevdelim = ISDELIM(prevgp->u); for (;;) { newx = *x + direction; @@ -536,14 +543,14 @@ selsnap(int *x, int *y, int direction) yt = *y, xt = *x; else yt = newy, xt = newx; - if (!(term.line[yt][xt].mode & ATTR_WRAP)) + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) break; } if (newx >= tlinelen(newy)) break; - gp = &term.line[newy][newx]; + gp = &TLINE(newy)[newx]; delim = ISDELIM(gp->u); if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || (delim && gp->u != prevgp->u))) @@ -564,14 +571,14 @@ selsnap(int *x, int *y, int direction) *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { for (; *y > 0; *y += direction) { - if (!(term.line[*y-1][term.col-1].mode + if (!(TLINE(*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 + if (!(TLINE(*y)[term.col-1].mode & ATTR_WRAP)) { break; } @@ -602,13 +609,13 @@ getsel(void) } if (sel.type == SEL_RECTANGULAR) { - gp = &term.line[y][sel.nb.x]; + gp = &TLINE(y)[sel.nb.x]; lastx = sel.ne.x; } else { - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + gp = &TLINE(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)]; + last = &TLINE(y)[MIN(lastx, linelen-1)]; while (last >= gp && last->u == ' ') --last; @@ -844,6 +851,9 @@ void ttywrite(const char *s, size_t n, int may_echo) { const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); if (may_echo && IS_SET(MODE_ECHO)) twrite(s, n, 1); @@ -1058,13 +1068,53 @@ tswapscreen(void) } void -tscrolldown(int orig, int n) +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + tsetdirt(orig, term.bot-n); tclearregion(0, term.bot-n+1, term.col-1, term.bot); @@ -1074,17 +1124,28 @@ tscrolldown(int orig, int n) term.line[i-n] = temp; } - selscroll(orig, n); + if (term.scr == 0) + selscroll(orig, n); } void -tscrollup(int orig, int n) +tscrollup(int orig, int n, int copyhist) { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + tclearregion(0, orig, term.col-1, orig+n-1); tsetdirt(orig+n, term.bot); @@ -1094,7 +1155,8 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1123,7 +1185,7 @@ tnewline(int first_col) int y = term.c.y; if (y == term.bot) { - tscrollup(term.top, 1); + tscrollup(term.top, 1, 1); } else { y++; } @@ -1221,6 +1283,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 @@ -1291,14 +1356,14 @@ void tinsertblankline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrolldown(term.c.y, n); + tscrolldown(term.c.y, n, 0); } void tdeleteline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrollup(term.c.y, n); + tscrollup(term.c.y, n, 0); } int32_t @@ -1742,11 +1807,11 @@ csihandle(void) case 'S': /* SU -- Scroll line up */ if (csiescseq.priv) break; DEFAULT(csiescseq.arg[0], 1); - tscrollup(term.top, csiescseq.arg[0]); + tscrollup(term.top, csiescseq.arg[0], 0); break; case 'T': /* SD -- Scroll line down */ DEFAULT(csiescseq.arg[0], 1); - tscrolldown(term.top, csiescseq.arg[0]); + tscrolldown(term.top, csiescseq.arg[0], 0); break; case 'L': /* IL -- Insert blank lines */ DEFAULT(csiescseq.arg[0], 1); @@ -2335,7 +2400,7 @@ eschandle(uchar ascii) return 0; case 'D': /* IND -- Linefeed */ if (term.c.y == term.bot) { - tscrollup(term.top, 1); + tscrollup(term.top, 1, 1); } else { tmoveto(term.c.x, term.c.y+1); } @@ -2348,7 +2413,7 @@ eschandle(uchar ascii) break; case 'M': /* RI -- Reverse index */ if (term.c.y == term.top) { - tscrolldown(term.top, 1); + tscrolldown(term.top, 1, 1); } else { tmoveto(term.c.x, term.c.y-1); } @@ -2572,7 +2637,7 @@ twrite(const char *buf, int buflen, int show_ctrl) void tresize(int col, int row) { - int i; + int i, j; int minrow = MIN(row, term.row); int mincol = MIN(col, term.col); int *bp; @@ -2609,6 +2674,14 @@ tresize(int col, int row) term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + /* 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)); @@ -2667,7 +2740,7 @@ drawregion(int x1, int y1, int x2, int y2) continue; term.dirty[y] = 0; - xdrawline(term.line[y], x1, y, x2); + xdrawline(TLINE(y), x1, y, x2); } } @@ -2688,8 +2761,9 @@ draw(void) 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]); + if (term.scr == 0) + 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(); diff --git a/st.c.orig b/st.c.orig new file mode 100644 index 0000000..1295c37 --- /dev/null +++ b/st.c.orig @@ -0,0 +1,2776 @@ +/* See LICENSE for license details. */ +#include +#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 +#define HISTSIZE 2000 + +/* 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)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +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 */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + 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, int); +static void tscrolldown(int, 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 (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(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 = &TLINE(*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 (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(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 (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*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 = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(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; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + 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 +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + 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; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-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; + } + + if (term.scr == 0) + 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, 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, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +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], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 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, 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, 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, j; + 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)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* 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(TLINE(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); + if (term.scr == 0) + 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..f6bd3b4 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, }; @@ -81,6 +82,8 @@ void die(const char *, ...); void redraw(void); void draw(void); +void kscrolldown(const Arg *); +void kscrollup(const Arg *); void printscreen(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); @@ -111,6 +114,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 +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/st.h.orig b/st.h.orig new file mode 100644 index 0000000..818a6f8 --- /dev/null +++ b/st.h.orig @@ -0,0 +1,128 @@ +/* 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 kscrolldown(const Arg *); +void kscrollup(const Arg *); +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 new file mode 100644 index 0000000000000000000000000000000000000000..4af489c59b91c621cd2c8a1d029f1335ea77828a GIT binary patch literal 82168 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 z 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 + 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(); @@ -1240,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 @@ -1286,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; @@ -1491,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) { @@ -1535,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 new file mode 100644 index 0000000..2f2180c --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2209 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +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); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + xloadsparefonts(); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + 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) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent, root; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* spare fonts */ + xloadsparefonts(); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/x.o b/x.o new file mode 100644 index 0000000000000000000000000000000000000000..ee2420b66126e4674b325143de0e58558ab99d4d GIT binary patch literal 79552 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 Hi2DBmbA8wF literal 0 HcmV?d00001