From 4f7e452a8c48faa57408f05f141910f74da3b686 Mon Sep 17 00:00:00 2001 From: Hektor Misplon Date: Thu, 7 May 2020 15:51:18 +0000 Subject: [PATCH] Patch ligatures --- .suckless/st/.gitignore | 2 +- .suckless/st/Makefile | 5 +- .suckless/st/config.mk | 6 +- .suckless/st/hb.c | 136 + .suckless/st/hb.h | 7 + .suckless/st/hb.o | Bin 0 -> 4552 bytes .../patches/st-ligatures-20200430-0.8.3.diff | 306 ++ .suckless/st/st | Bin 102208 -> 102984 bytes .suckless/st/st.c | 3 +- .suckless/st/st.c.orig | 2599 +++++++++++++++++ .suckless/st/st.c.rej | 12 + .suckless/st/st.h | 4 +- .suckless/st/st.o | Bin 73184 -> 73248 bytes .suckless/st/win.h | 2 +- .suckless/st/x.c | 16 +- .suckless/st/x.c.orig | 2045 +++++++++++++ .suckless/st/x.o | Bin 73896 -> 73808 bytes 17 files changed, 5132 insertions(+), 11 deletions(-) create mode 100644 .suckless/st/hb.c create mode 100644 .suckless/st/hb.h create mode 100644 .suckless/st/hb.o create mode 100644 .suckless/st/patches/st-ligatures-20200430-0.8.3.diff create mode 100644 .suckless/st/st.c.orig create mode 100644 .suckless/st/st.c.rej create mode 100644 .suckless/st/x.c.orig diff --git a/.suckless/st/.gitignore b/.suckless/st/.gitignore index c190512..645e6a2 100644 --- a/.suckless/st/.gitignore +++ b/.suckless/st/.gitignore @@ -1 +1 @@ -*.backup +*.old diff --git a/.suckless/st/Makefile b/.suckless/st/Makefile index 470ac86..38240da 100644 --- a/.suckless/st/Makefile +++ b/.suckless/st/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c +SRC = st.c x.c hb.c OBJ = $(SRC:.c=.o) all: options st @@ -22,7 +22,8 @@ config.h: $(CC) $(STCFLAGS) -c $< st.o: config.h st.h win.h -x.o: arg.h config.h st.h win.h +x.o: arg.h config.h st.h win.h hb.h +hb.o: st.h $(OBJ): config.h config.mk diff --git a/.suckless/st/config.mk b/.suckless/st/config.mk index 0cbb002..a021b2c 100644 --- a/.suckless/st/config.mk +++ b/.suckless/st/config.mk @@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config # includes and libs INCS = -I$(X11INC) \ `$(PKG_CONFIG) --cflags fontconfig` \ - `$(PKG_CONFIG) --cflags freetype2` + `$(PKG_CONFIG) --cflags freetype2` \ + `$(PKG_CONFIG) --cflags harfbuzz` LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ `$(PKG_CONFIG) --libs fontconfig` \ - `$(PKG_CONFIG) --libs freetype2` + `$(PKG_CONFIG) --libs freetype2` \ + `$(PKG_CONFIG) --libs harfbuzz` # flags STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 diff --git a/.suckless/st/hb.c b/.suckless/st/hb.c new file mode 100644 index 0000000..7df2828 --- /dev/null +++ b/.suckless/st/hb.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include + +#include "st.h" + +void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length); +hb_font_t *hbfindfont(XftFont *match); + +typedef struct { + XftFont *match; + hb_font_t *font; +} HbFontMatch; + +static int hbfontslen = 0; +static HbFontMatch *hbfontcache = NULL; + +void +hbunloadfonts() +{ + for (int i = 0; i < hbfontslen; i++) { + hb_font_destroy(hbfontcache[i].font); + XftUnlockFace(hbfontcache[i].match); + } + + if (hbfontcache != NULL) { + free(hbfontcache); + hbfontcache = NULL; + } + hbfontslen = 0; +} + +hb_font_t * +hbfindfont(XftFont *match) +{ + for (int i = 0; i < hbfontslen; i++) { + if (hbfontcache[i].match == match) + return hbfontcache[i].font; + } + + /* Font not found in cache, caching it now. */ + hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1)); + FT_Face face = XftLockFace(match); + hb_font_t *font = hb_ft_font_create(face, NULL); + if (font == NULL) + die("Failed to load Harfbuzz font."); + + hbfontcache[hbfontslen].match = match; + hbfontcache[hbfontslen].font = font; + hbfontslen += 1; + + return font; +} + +void +hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y) +{ + int start = 0, length = 1, gstart = 0; + hb_codepoint_t *codepoints = calloc(len, sizeof(hb_codepoint_t)); + + for (int idx = 1, specidx = 1; idx < len; idx++) { + if (glyphs[idx].mode & ATTR_WDUMMY) { + length += 1; + continue; + } + + if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) { + hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length); + + /* Reset the sequence. */ + length = 1; + start = specidx; + gstart = idx; + } else { + length += 1; + } + + specidx++; + } + + /* EOL. */ + hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length); + + /* Apply the transformation to glyph specs. */ + for (int i = 0, specidx = 0; i < len; i++) { + if (glyphs[i].mode & ATTR_WDUMMY) + continue; + + if (codepoints[i] != specs[specidx].glyph) + ((Glyph *)glyphs)[i].mode |= ATTR_LIGA; + + specs[specidx++].glyph = codepoints[i]; + } + + free(codepoints); +} + +void +hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length) +{ + hb_font_t *font = hbfindfont(xfont); + if (font == NULL) + return; + + Rune rune; + ushort mode = USHRT_MAX; + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + + /* Fill buffer with codepoints. */ + for (int i = start; i < (start+length); i++) { + rune = string[i].u; + mode = string[i].mode; + if (mode & ATTR_WDUMMY) + rune = 0x0020; + hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1); + } + + /* Shape the segment. */ + hb_shape(font, buffer, NULL, 0); + + /* Get new glyph info. */ + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL); + + /* Write new codepoints. */ + for (int i = 0; i < length; i++) { + hb_codepoint_t gid = info[i].codepoint; + codepoints[start+i] = gid; + } + + /* Cleanup. */ + hb_buffer_destroy(buffer); +} diff --git a/.suckless/st/hb.h b/.suckless/st/hb.h new file mode 100644 index 0000000..b3e02d0 --- /dev/null +++ b/.suckless/st/hb.h @@ -0,0 +1,7 @@ +#include +#include +#include + +void hbunloadfonts(); +void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int); + diff --git a/.suckless/st/hb.o b/.suckless/st/hb.o new file mode 100644 index 0000000000000000000000000000000000000000..4d2292d9efc0707c752526f9a9e535bfe0af08f5 GIT binary patch literal 4552 zcmbW5e{36P8OPs?ukF!fwpX%ZwgKLBOy0DaWCbaPFy$O4c|ESH#FCb=b)IuR*H#@n z*k_bhrJLlr+HrUc@gINi7t(}~koW_(4mRaSyG$sIN`N{|;`fF+1<|rXghYjTp7-82 zd3)rfiMP7E@B4Ya&-1*$?meqdPnpvLib6zD$U|hOCnzD$MR(d0tT{n$Awy*DQQG;d z*7-a`XEQ&=n!3NGEAOh*+rxU^nUr?LZ*ef5^81}NCG#<~JiS3Z7J_#BPTV)HGcj6s zTC{UPd;IvCpOm`)_CAC5?$+76c(UHnK3(~fN_%EJPBRJTE3~r=jW^owcV1VhwMAF{ z0^?$-{4B0NB=`5cyZ#zR zz6k~~qSsWWT4(*qo;q%1E`04+-ug9le??dRteVQE(VOX0L%Ws-LE_7B@53+Z((kQI4G&il+Sl;~)b+6~$%dV;GIhaCdxplh|j*&6y73%|kgD%aCX4aq7+Lu&ckzi;03^7~;lrZcR^$AV%k+LgC40 zuEw5?L0B*jQ};c(@<%oHQf^S)x}dhdXIdpanO}KFJ*@mAZ~X?(B!twwP2MI?Y6Fad7d@(72F7R zl1y0p#O4pM4`sSot=Xld(@54D#ZrC?$_qwb^$nVFpYY%*mutxD3Y*4ssOE`?MEemvrEFa$6f~BN{(9U_=w0NG_Tjs6`V1 zjHs3ZQ;zF?{J*+y?-xR2kWjEMa_tBnuxvE`&4Fw*@q8p3P4?7mH1&-=Ms(!dUL!iz z*_Vqx6*-{nkB$K{qA8Go6kM`V%@{Pn{bxMZ)ejPDF;WSO#l!4cVQ7{7y}z3=vg^%H zNCf`vRw587SoQ)?fNR_!6t{9N^umt)2-&{sg_+e6vhSw8=tQ=o3qlC{jD*I)QQchb zhVB7rzU)i@8-a&i8aK5%3LNu_eC&(?b3*iILilqb{5v81yTCE;eRu$% zu=5J_kKoyDzN;bp?GS!Fgx?6^2Voq$#n7LH6$KRPvAm^HHIQ~@*vG#5&C%V{=F zkuQ~@nj!jBcbaI--daK-MF0||u z3wGVXRP*g}*=`oNZU?k%7(umZTTZos6A{8tw;IoGg^#P6p@gp%KfUPs^@BZX1vK_8W=YgYUe!F+Pq) zD1zUBFA|oNkC_&KS_=Lt&V%|fNgtE+*??Z$H(v;F5r0mKgXd51Cj$C^`NQm&1Dtos zGXXC0dI2ut|0IO7AIxAw{{!#{{c4E*=bWSd!;=28#PJ&_;=IebSWhudd|$B$F7y$2 zZ)7g?w@Z9K2!vke9Cf!z{Fub??ic#1#6KeOQxZ=|{F1~!Dsl1tW8T{(enZmVA@K)s zqEJwOCp=<2MJ%pc^xE$vViOc761Or3CO4cV4 zLcwA2p|L(?e|Na}-r#ozi_nYj!7`&eT->uH-8!}46z8FKntojot)^WorX739A?Z@l zDU$Sjt3}ey26j+BnjR%-tFgfDv2?xR*y))^<{n@d3+T|>t`y46;)2by|Nm7ITyg7z zU-}7PSn_=R{GP8BZLIOEpkx9bxllapP?q?Ea<-!FJpvD^a>axO!}iW|r)IBMl` zKXfo=(EkPQKO~E}Rz{=H!I%%q5Zc0D+{3~w@{4)`4A>qK0$%@+=f5k!g`cSNU>9v! z`pXYE|0lV>e18$ literal 0 HcmV?d00001 diff --git a/.suckless/st/patches/st-ligatures-20200430-0.8.3.diff b/.suckless/st/patches/st-ligatures-20200430-0.8.3.diff new file mode 100644 index 0000000..3f2131e --- /dev/null +++ b/.suckless/st/patches/st-ligatures-20200430-0.8.3.diff @@ -0,0 +1,306 @@ +diff --git a/Makefile b/Makefile +index 470ac86..38240da 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ + + include config.mk + +-SRC = st.c x.c ++SRC = st.c x.c hb.c + OBJ = $(SRC:.c=.o) + + all: options st +@@ -22,7 +22,8 @@ config.h: + $(CC) $(STCFLAGS) -c $< + + st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++x.o: arg.h config.h st.h win.h hb.h ++hb.o: st.h + + $(OBJ): config.h config.mk + +diff --git a/config.mk b/config.mk +index beafc35..3df5c83 100644 +--- a/config.mk ++++ b/config.mk +@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config + # includes and libs + INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ +- `$(PKG_CONFIG) --cflags freetype2` ++ `$(PKG_CONFIG) --cflags freetype2` \ ++ `$(PKG_CONFIG) --cflags harfbuzz` + LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ +- `$(PKG_CONFIG) --libs freetype2` ++ `$(PKG_CONFIG) --libs freetype2` \ ++ `$(PKG_CONFIG) --libs harfbuzz` + + # flags + STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +diff --git a/hb.c b/hb.c +new file mode 100644 +index 0000000..7df2828 +--- /dev/null ++++ b/hb.c +@@ -0,0 +1,136 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "st.h" ++ ++void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length); ++hb_font_t *hbfindfont(XftFont *match); ++ ++typedef struct { ++ XftFont *match; ++ hb_font_t *font; ++} HbFontMatch; ++ ++static int hbfontslen = 0; ++static HbFontMatch *hbfontcache = NULL; ++ ++void ++hbunloadfonts() ++{ ++ for (int i = 0; i < hbfontslen; i++) { ++ hb_font_destroy(hbfontcache[i].font); ++ XftUnlockFace(hbfontcache[i].match); ++ } ++ ++ if (hbfontcache != NULL) { ++ free(hbfontcache); ++ hbfontcache = NULL; ++ } ++ hbfontslen = 0; ++} ++ ++hb_font_t * ++hbfindfont(XftFont *match) ++{ ++ for (int i = 0; i < hbfontslen; i++) { ++ if (hbfontcache[i].match == match) ++ return hbfontcache[i].font; ++ } ++ ++ /* Font not found in cache, caching it now. */ ++ hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1)); ++ FT_Face face = XftLockFace(match); ++ hb_font_t *font = hb_ft_font_create(face, NULL); ++ if (font == NULL) ++ die("Failed to load Harfbuzz font."); ++ ++ hbfontcache[hbfontslen].match = match; ++ hbfontcache[hbfontslen].font = font; ++ hbfontslen += 1; ++ ++ return font; ++} ++ ++void ++hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y) ++{ ++ int start = 0, length = 1, gstart = 0; ++ hb_codepoint_t *codepoints = calloc(len, sizeof(hb_codepoint_t)); ++ ++ for (int idx = 1, specidx = 1; idx < len; idx++) { ++ if (glyphs[idx].mode & ATTR_WDUMMY) { ++ length += 1; ++ continue; ++ } ++ ++ if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) { ++ hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length); ++ ++ /* Reset the sequence. */ ++ length = 1; ++ start = specidx; ++ gstart = idx; ++ } else { ++ length += 1; ++ } ++ ++ specidx++; ++ } ++ ++ /* EOL. */ ++ hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length); ++ ++ /* Apply the transformation to glyph specs. */ ++ for (int i = 0, specidx = 0; i < len; i++) { ++ if (glyphs[i].mode & ATTR_WDUMMY) ++ continue; ++ ++ if (codepoints[i] != specs[specidx].glyph) ++ ((Glyph *)glyphs)[i].mode |= ATTR_LIGA; ++ ++ specs[specidx++].glyph = codepoints[i]; ++ } ++ ++ free(codepoints); ++} ++ ++void ++hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length) ++{ ++ hb_font_t *font = hbfindfont(xfont); ++ if (font == NULL) ++ return; ++ ++ Rune rune; ++ ushort mode = USHRT_MAX; ++ hb_buffer_t *buffer = hb_buffer_create(); ++ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); ++ ++ /* Fill buffer with codepoints. */ ++ for (int i = start; i < (start+length); i++) { ++ rune = string[i].u; ++ mode = string[i].mode; ++ if (mode & ATTR_WDUMMY) ++ rune = 0x0020; ++ hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1); ++ } ++ ++ /* Shape the segment. */ ++ hb_shape(font, buffer, NULL, 0); ++ ++ /* Get new glyph info. */ ++ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL); ++ ++ /* Write new codepoints. */ ++ for (int i = 0; i < length; i++) { ++ hb_codepoint_t gid = info[i].codepoint; ++ codepoints[start+i] = gid; ++ } ++ ++ /* Cleanup. */ ++ hb_buffer_destroy(buffer); ++} +diff --git a/hb.h b/hb.h +new file mode 100644 +index 0000000..b3e02d0 +--- /dev/null ++++ b/hb.h +@@ -0,0 +1,7 @@ ++#include ++#include ++#include ++ ++void hbunloadfonts(); ++void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int); ++ +diff --git a/st.c b/st.c +index 2bf133f..747f7b4 100644 +--- a/st.c ++++ b/st.c +@@ -2599,7 +2599,8 @@ draw(void) + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index d978458..c9b279b 100644 +--- a/st.h ++++ b/st.h +@@ -11,7 +11,8 @@ + #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 || \ ++#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \ ++ (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) +@@ -33,6 +34,7 @@ enum glyph_attribute { + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, ++ ATTR_LIGA = 1 << 11, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +diff --git a/win.h b/win.h +index a6ef1b9..bc0d180 100644 +--- a/win.h ++++ b/win.h +@@ -25,7 +25,7 @@ enum win_mode { + + void xbell(void); + void xclipcopy(void); +-void xdrawcursor(int, int, Glyph, int, int, Glyph); ++void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); + void xdrawline(Line, int, int, int); + void xfinishdraw(void); + void xloadcols(void); +diff --git a/x.c b/x.c +index e5f1737..3334a83 100644 +--- a/x.c ++++ b/x.c +@@ -19,6 +19,7 @@ char *argv0; + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -1031,6 +1032,9 @@ xunloadfont(Font *f) + void + xunloadfonts(void) + { ++ /* Clear Harfbuzz font cache. */ ++ hbunloadfonts(); ++ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); +@@ -1229,7 +1233,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ +- if (mode == ATTR_WDUMMY) ++ if (mode & ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ +@@ -1336,6 +1340,9 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x + numspecs++; + } + ++ /* Harfbuzz transformation for ligatures. */ ++ hbtransform(specs, glyphs, len, x, y); ++ + return numspecs; + } + +@@ -1485,14 +1492,17 @@ xdrawglyph(Glyph g, int x, int y) + } + + void +-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) + { + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; +- xdrawglyph(og, ox, oy); ++ ++ /* Redraw the line where cursor was previously. ++ * It will restore the ligatures broken by the cursor. */ ++ xdrawline(line, 0, oy, len); + + if (IS_SET(MODE_HIDE)) + return; diff --git a/.suckless/st/st b/.suckless/st/st index b05e62681b7d62e6635392257b59a227be37697b..bea976a238f8d30bf73659c7327ed6aa4ceac6f8 100755 GIT binary patch delta 42297 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 delta 39393 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õM2q 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, + MODE_SIXEL = 1 << 7, +}; + +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, /* 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, + ESC_DCS =128, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; +} 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 int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(int *, int); +static void tsetchar(Rune, Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, 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(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 uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static 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(char *s) +{ + if ((s = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return s; +} + +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; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) + *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; + 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]; + else if (utmp) + prog = utmp; + else + prog = sh; + DEFAULT(args, ((char *[]) {prog, 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(char *line, char *cmd, 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); + 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)); + close(s); + close(m); +#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 written; + int ret; + + /* append read bytes to unprocessed bytes */ + if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) + die("couldn't read from shell: %s\n", strerror(errno)); + buflen += ret; + + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any uncomplete utf8 char for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + + return ret; +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* 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; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { + if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { + selclear(); + return; + } + if (sel.type == SEL_RECTANGULAR) { + if (sel.ob.y < term.top) + sel.ob.y = term.top; + if (sel.oe.y > term.bot) + sel.oe.y = term.bot; + } else { + if (sel.ob.y < term.top) { + sel.ob.y = term.top; + sel.ob.x = 0; + } + if (sel.oe.y > term.bot) { + sel.oe.y = term.bot; + sel.oe.x = term.col; + } + } + selnormalize(); + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + 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 (*p != ';' || 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, Glyph *attr, int x, int y) +{ + static char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(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(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: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + 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, int *args, int narg) +{ + int alt, *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: + 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 */ + case 1047: + 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: + 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 '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 > 1) + 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 */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf),"\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + 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) */ + 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 +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + 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: + case 1: + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset, here p = NULL */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + 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 + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + term.mode |= ESC_DCS; + 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]; + 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) +{ + strreset(); + + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + term.esc |= ESC_DCS; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + 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); + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + 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 (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) { + memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ + 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|ESC_DCS); + if (IS_SET(MODE_SIXEL)) { + /* TODO: render sixel */; + term.mode &= ~MODE_SIXEL; + return; + } + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (IS_SET(MODE_SIXEL)) { + /* TODO: implement sixel mode */ + return; + } + if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') + term.mode |= MODE_SIXEL; + + 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) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + 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 (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.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)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + 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) && !IS_SET(MODE_SIXEL)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx, term.ocy = term.c.y; + xfinishdraw(); + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/.suckless/st/st.c.rej b/.suckless/st/st.c.rej new file mode 100644 index 0000000..9a79b8b --- /dev/null +++ b/.suckless/st/st.c.rej @@ -0,0 +1,12 @@ +--- st.c ++++ st.c +@@ -2599,7 +2599,8 @@ draw(void) + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); diff --git a/.suckless/st/st.h b/.suckless/st/st.h index a1928ca..07ba678 100644 --- a/.suckless/st/st.h +++ b/.suckless/st/st.h @@ -11,7 +11,8 @@ #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 || \ +#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \ + (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) @@ -33,6 +34,7 @@ enum glyph_attribute { ATTR_WRAP = 1 << 8, ATTR_WIDE = 1 << 9, ATTR_WDUMMY = 1 << 10, + ATTR_LIGA = 1 << 11, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; diff --git a/.suckless/st/st.o b/.suckless/st/st.o index 90f6a064ac8ec9a2646bf031abf48e33cbf054be..f37cd09ec49fd2cee347cc72b86bd8c23586a099 100644 GIT binary patch delta 12727 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%0AdINY- z4bnQMPkC&lcq7958|R>E+Wlfx&+*)kCJ9F{Sv&WUz6rA9RPmR zOxlV(rb8fzI*Bx&Dc+2tX3&-vGR*{6lVN6&7BNkQT+|%W5~fMuLp@DnE@gT@1W@y- zZ5h)CA&go`TEX-&a7GNXh;$v(1>iv~A+2P(6pB$xNjEZG0ad8h7kitTJ_jMx3TnHZ zDSk2Y5BxHow1(+=aHCd|9%T9ocu_Z!)-nAX_)#~L)-&A#LDcP}4NP}I1hs~=k!dZs zE*a)Q(k7-yAQ!ccw3#X1$e`Af>N^(OHTiQ0pf->?)cuT2PeT~Bku;v^d2s%KH;JSP zOmRk|Hj^eX#TosBksU){cqTKArTPJ^-a+*#tiBV3u&;R1G^SlaTsF)E(hR1F;6_a% z&0^Xcyr{{fIZSazqgr1S=P|_@jhaSn^O@p|M$I5CWQsHTN1V~5MNB6_E@}>G3DbMP zhnh!P$`ofbYCdThQ=HMLg`^csaYp|WXEf1L)l zqfskJw==~V-HbDuw1z3pXw*v5gG_Noqi!UvV~R5xbu(!_Q=HML+esUk;*3VEA#G%e zGx{f-(WFgGaYm!okv22M8I4*`8q?<5Ig2wIwSm;Z6lXN*m2uHF@l0_>{|jd{wM}4( zGa9v-G>IwB=zkg6)(;cOOk?S%fdQ=ELG>xDzBh!huXxflrZ}U2#u-hT!4zjSY7%J{ zQ=HML$)q_gap zlqt?=)Ed$Q2&_w%5++JrF>BpVYzh0EAILAdP3*=S8Q$ zFKy}QCop~dMUPVYaV_cJv>5d@(qyK`U#vp4en3lM`oxPN)NM4XG^VGd5Xh(Km#GZK zRnm=|-IjhB$zt4&noVN$9jQKt)t5@Y>g@*NJjR9$DlVoUp7I(0MMf0&qSl3s`$|_U zleFUu;$E9eMsW;|Yo6pyCX+ZivCA{KcGjl72OOzBpu#CWF6Rosa_#x*l8 zmOjNkY8^vgFSnw5?jjjbypP`a4#uCzu;MUrJmZVf*%qhGFT@Fq1?|m5R^Nr{lUV)D zvKZ5uw-F~Z{#jNb%Z&EI;YnfEntn1H!s^>meHyFpC`CK$sta)j;~S(~u|b^0xV!W! z?n#`(xR>-R?n|7!b@eq~CfLbvtn-tDi56v9-B~cq8K_ zvP$t&#G4r}mm$R~iMKOeB}E71HN-WHACPXv&k!GE{F?MC-cL88b&P+Me$6z;>i>;{ ziu=%+se$pmGNSk>aUeCV_Ex=}~Om&m=K^T^1{TgSIr8ag(f4Y~9hM zFwT@A#nvrN8sj}ubVj!BX)+iekZ#4+O-&Z#q0+0^y13*p{!IE6x1mFr$M|^}RJ@Gd zANh`*WBwmj=05y_Cuw>ZMZtq+W_-dZHN9%Abe-9luFdB#Pwr o>++(R>trwyGiS-4FhUf{^qcXeMlQye_AzGkhp_e1mneGvKasDd*Z=?k delta 12590 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 LUbtI!Q$79#w@ 0) XftFontClose(xw.dpy, frc[--frclen].font); @@ -1219,7 +1223,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x mode = glyphs[i].mode; /* Skip dummy wide-character spacing. */ - if (mode == ATTR_WDUMMY) + if (mode & ATTR_WDUMMY) continue; /* Determine font for glyph if different from previous glyph. */ @@ -1326,6 +1330,9 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x numspecs++; } + /* Harfbuzz transformation for ligatures. */ + hbtransform(specs, glyphs, len, x, y); + return numspecs; } @@ -1475,14 +1482,17 @@ xdrawglyph(Glyph g, int x, int y) } void -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) { Color drawcol; /* remove the old cursor */ if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; - xdrawglyph(og, ox, oy); + + /* Redraw the line where cursor was previously. + * It will restore the ligatures broken by the cursor. */ + xdrawline(line, 0, oy, len); if (IS_SET(MODE_HIDE)) return; diff --git a/.suckless/st/x.c.orig b/.suckless/st/x.c.orig new file mode 100644 index 0000000..78a4044 --- /dev/null +++ b/.suckless/st/x.c.orig @@ -0,0 +1,2045 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static 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) + +/* 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 hborderpx, vborderpx; + 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, 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(char *, double); +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 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 int oldbutton = 3; /* button event on startup: 3 = release */ + +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); + 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 - win.hborderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - win.vborderpx; + 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, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, e->xbutton.state) || /* exact or forced */ + match(ms->mod, e->xbutton.state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + struct timespec now; + int snap; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == 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) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == 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); + + win.hborderpx = (win.w - col * win.cw) / 2; + win.vborderpx = (win.h - row * win.ch) / 2; + + 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 +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + 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 = 1; + sizeh->width_inc = 1; + 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(char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((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); +} + +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, + XNFocusWindow, 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; + 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); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * win.hborderpx + cols * win.cw; + win.h = 2 * win.vborderpx + 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; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, 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); + 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 = win.hborderpx + x * win.cw, winy = win.vborderpx + 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 = win.hborderpx + x * win.cw, winy = win.vborderpx + 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, win.vborderpx, + winy + win.ch + + ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); + } + if (winx + width >= win.hborderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, win.hborderpx); + if (winy + win.ch >= win.vborderpx + 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 + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 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: snowman (U+2603) */ + g.u = 0x2603; + 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, + win.hborderpx + cx * win.cw, + win.vborderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + (cx + 1) * win.cw - 1, + win.vborderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + win.hborderpx + cx * win.cw, + win.vborderpx + (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 +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + 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) +{ + DEFAULT(cursor, 1); + if (!BETWEEN(cursor, 0, 6)) + 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; + 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); + 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), xev, blinkset = 0, dodraw = 0; + int ttyfd; + struct timespec drawtimeout, *tv = NULL, now, last, lastblink; + long deltatime; + + /* 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); + + clock_gettime(CLOCK_MONOTONIC, &last); + lastblink = last; + + for (xev = actionfps;;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(ttyfd, &rfd)) { + ttyread(); + if (blinktimeout) { + blinkset = tattrset(ATTR_BLINK); + if (!blinkset) + MODBIT(win.mode, 0, MODE_BLINK); + } + } + + if (FD_ISSET(xfd, &rfd)) + xev = actionfps; + + clock_gettime(CLOCK_MONOTONIC, &now); + drawtimeout.tv_sec = 0; + drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; + tv = &drawtimeout; + + dodraw = 0; + if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { + tsetdirtattr(ATTR_BLINK); + win.mode ^= MODE_BLINK; + lastblink = now; + dodraw = 1; + } + deltatime = TIMEDIFF(now, last); + if (deltatime > 1000 / (xev ? xfps : actionfps)) { + dodraw = 1; + last = now; + } + + if (dodraw) { + while (XPending(xw.dpy)) { + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + draw(); + XFlush(xw.dpy); + + if (xev && !FD_ISSET(xfd, &rfd)) + xev--; + if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) { + if (blinkset) { + if (TIMEDIFF(now, lastblink) \ + > blinktimeout) { + drawtimeout.tv_nsec = 1000; + } else { + drawtimeout.tv_nsec = (1E6 * \ + (blinktimeout - \ + TIMEDIFF(now, + lastblink))); + } + drawtimeout.tv_sec = \ + drawtimeout.tv_nsec / 1E9; + drawtimeout.tv_nsec %= (long)1E9; + } else { + tv = NULL; + } + } + } + } +} + +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; + win.cursor = 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/.suckless/st/x.o b/.suckless/st/x.o index ce343e4f75995097b5318af633a960f1171f2050..28870e4a01ec1386168be51b6154e881ea896f30 100644 GIT binary patch literal 73808 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