diff --git a/home/natto/config/emacs/config.org b/home/natto/config/emacs/config.org index a26fb1e..5dd4522 100644 --- a/home/natto/config/emacs/config.org +++ b/home/natto/config/emacs/config.org @@ -486,7 +486,7 @@ This returns the command to run in the terminal ("java" (concat "java" full)) ("py" (concat "python" full)) ("cpp" (concat "g++ " full " -o " file " && " file " && rm " file)) - ("hs" (concat "ghc -dynamic " full " && " file " && rm " file " " file ".o")) + ("hs" (concat "runhaskell " full)) ("sh" (concat "sh" full)) ("js" (concat "node" full)) ("ts" (concat "tsc" full " && node " file ".js && rm " file ".js" )) diff --git a/home/natto/config/xmonad/xmobar.hs b/home/natto/config/xmonad/xmobar.hs index 935dc2d..6692332 100644 --- a/home/natto/config/xmonad/xmobar.hs +++ b/home/natto/config/xmonad/xmobar.hs @@ -37,13 +37,13 @@ config = Run $ Date "\xf073 %-d/%-m/%-y/%w" "date" 10000, Run $ Date "%H:%M:%S" "time" 10, Run $ MPD ["-t", "<fn=" ++ magenta ++ "> \xf001</fn>", "--", "-P", ">> ", "-Z", "|| ", "-S", "Stopped", "-h", "127.0.0.1", "-p", "6600"] 10, - Run $ Com "/home/natto/.xmonad/lib/padding-icon.sh" ["stalonetray"] "tray" 10, + Run $ Com "~/.xmonad/lib/padding-icon.sh" ["stalonetray"] "tray" 10, Run $ Com "pamixer" ["--get-volume"] "" 100, Run UnsafeStdinReader ], sepChar = "*", alignSep = "--", - template = "<action=`dmenu_run` button=1><icon=/home/natto/.xmonad/lib/nixos.xpm/></action> *UnsafeStdinReader* " ++ sep ++ " <fn=1></fn>*mpd* -- *multicpu* " ++ sep ++ " *memory* " ++ sep ++ " *dynnetwork* " ++ sep ++ " *battery* " ++ sep ++ " <fn=1>\xf028</fn> <fc=" ++ green ++ ">*pamixer*%</fc> " ++ sep ++ " *date* - *time* " ++ sep ++ "*tray*" + template = "<action=`dmenu_run` button=1><icon=~/.xmonad/lib/nixos.xpm/></action> *UnsafeStdinReader* " ++ sep ++ " <fn=1></fn>*mpd* -- *multicpu* " ++ sep ++ " *memory* " ++ sep ++ " *dynnetwork* " ++ sep ++ " *battery* " ++ sep ++ " <fn=1>\xf028</fn> <fc=" ++ green ++ ">*pamixer*%</fc> " ++ sep ++ " *date* - *time* " ++ sep ++ "*tray*" } main :: IO () diff --git a/home/natto/config/xmonad/xmonad.hs b/home/natto/config/xmonad/xmonad.hs index bb21b82..afc1f0d 100644 --- a/home/natto/config/xmonad/xmonad.hs +++ b/home/natto/config/xmonad/xmonad.hs @@ -56,6 +56,12 @@ myKeys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $ , ((modMask, xK_d), spawn "dmenu_run -l 20") + + , ((shiftMask .|. mod1Mask, xK_v), + spawn "clipmenu") + + , ((modMask, xK_s), + spawn "dmenu_websearch") , ((shiftMask, xK_Print), spawn "flameshot gui") diff --git a/home/natto/default.nix b/home/natto/default.nix index b453131..69e256a 100644 --- a/home/natto/default.nix +++ b/home/natto/default.nix @@ -1,6 +1,4 @@ { config, pkgs, lib, ... }: -let -in { imports = [ ./email.nix diff --git a/home/natto/patches/st.patch b/home/natto/patches/st.patch old mode 100755 new mode 100644 index 2f879a8..7061f7c --- a/home/natto/patches/st.patch +++ b/home/natto/patches/st.patch @@ -1,6 +1,6 @@ -diff --color -ru st-0.9/config.def.h st-0.9-a/config.def.h +diff --color -urN st-0.9/config.def.h st-0.9-d/config.def.h --- st-0.9/config.def.h 2022-10-04 23:11:26.000000000 +0530 -+++ st-0.9-a/config.def.h 2022-11-25 02:12:19.384259019 +0530 ++++ st-0.9-d/config.def.h 2022-11-25 20:37:13.107583248 +0530 @@ -3,9 +3,10 @@ /* * appearance @@ -148,9 +148,208 @@ diff --color -ru st-0.9/config.def.h st-0.9-a/config.def.h }; /* -diff --color -ru st-0.9/st.c st-0.9-a/st.c +diff --color -urN st-0.9/config.mk st-0.9-d/config.mk +--- st-0.9/config.mk 2022-10-04 23:11:26.000000000 +0530 ++++ st-0.9-d/config.mk 2022-11-25 20:37:17.096658798 +0530 +@@ -15,10 +15,12 @@ + # 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 --color -urN st-0.9/hb.c st-0.9-d/hb.c +--- st-0.9/hb.c 1970-01-01 05:30:00.000000000 +0530 ++++ st-0.9-d/hb.c 2022-11-25 20:37:17.096658798 +0530 +@@ -0,0 +1,145 @@ ++#include <stdlib.h> ++#include <stdio.h> ++#include <math.h> ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++#include "st.h" ++ ++#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } ++ ++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; ++ ++/* ++ * Poplulate the array with a list of font features, wrapped in FEATURE macro, ++ * e. g. ++ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') ++ */ ++hb_feature_t features[] = { }; ++ ++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((unsigned int)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, features, sizeof(features)/sizeof(hb_feature_t)); ++ ++ /* 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 --color -urN st-0.9/hb.h st-0.9-d/hb.h +--- st-0.9/hb.h 1970-01-01 05:30:00.000000000 +0530 ++++ st-0.9-d/hb.h 2022-11-25 20:37:17.096658798 +0530 +@@ -0,0 +1,6 @@ ++#include <X11/Xft/Xft.h> ++#include <hb.h> ++#include <hb-ft.h> ++ ++void hbunloadfonts(); ++void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int); +diff --color -urN st-0.9/Makefile st-0.9-d/Makefile +--- st-0.9/Makefile 2022-10-04 23:11:26.000000000 +0530 ++++ st-0.9-d/Makefile 2022-11-25 20:37:17.096658798 +0530 +@@ -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 @@ + $(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 --color -urN st-0.9/st.c st-0.9-d/st.c --- st-0.9/st.c 2022-10-04 23:11:26.000000000 +0530 -+++ st-0.9-a/st.c 2022-11-25 02:12:19.384259019 +0530 ++++ st-0.9-d/st.c 2022-11-25 20:37:17.096658798 +0530 @@ -35,6 +35,7 @@ #define ESC_ARG_SIZ 16 #define STR_BUF_SIZ ESC_BUF_SIZ @@ -460,7 +659,7 @@ diff --color -ru st-0.9/st.c st-0.9-a/st.c } } -@@ -2639,8 +2709,9 @@ +@@ -2639,8 +2709,10 @@ cx--; drawregion(0, 0, term.col, term.row); @@ -468,14 +667,2764 @@ diff --color -ru st-0.9/st.c st-0.9-a/st.c - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], -+ term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx], ++ term.line[term.ocy], term.col); term.ocx = cx; term.ocy = term.c.y; xfinishdraw(); -diff --color -ru st-0.9/st.h st-0.9-a/st.h +diff --color -urN st-0.9/st.c.orig st-0.9-d/st.c.orig +--- st-0.9/st.c.orig 1970-01-01 05:30:00.000000000 +0530 ++++ st-0.9-d/st.c.orig 2022-11-25 20:37:13.107583248 +0530 +@@ -0,0 +1,2727 @@ ++/* See LICENSE for license details. */ ++#include <ctype.h> ++#include <errno.h> ++#include <fcntl.h> ++#include <limits.h> ++#include <pwd.h> ++#include <stdarg.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++#include <signal.h> ++#include <sys/ioctl.h> ++#include <sys/select.h> ++#include <sys/types.h> ++#include <sys/wait.h> ++#include <termios.h> ++#include <unistd.h> ++#include <wchar.h> ++ ++#include "st.h" ++#include "win.h" ++ ++#if defined(__linux) ++ #include <pty.h> ++#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) ++ #include <util.h> ++#elif defined(__FreeBSD__) || defined(__DragonFly__) ++ #include <libutil.h> ++#endif ++ ++/* Arbitrary sizes */ ++#define UTF_INVALID 0xFFFD ++#define UTF_SIZ 4 ++#define ESC_BUF_SIZ (128*UTF_SIZ) ++#define ESC_ARG_SIZ 16 ++#define STR_BUF_SIZ ESC_BUF_SIZ ++#define STR_ARG_SIZ ESC_ARG_SIZ ++#define HISTSIZE 2000 ++ ++/* macros */ ++#define IS_SET(flag) ((term.mode & (flag)) != 0) ++#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) ++#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) ++#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) ++#define ISDELIM(u) (u && wcschr(worddelimiters, u)) ++#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ ++ term.scr + HISTSIZE + 1) % HISTSIZE] : \ ++ term.line[(y) - term.scr]) ++ ++enum term_mode { ++ MODE_WRAP = 1 << 0, ++ MODE_INSERT = 1 << 1, ++ MODE_ALTSCREEN = 1 << 2, ++ MODE_CRLF = 1 << 3, ++ MODE_ECHO = 1 << 4, ++ MODE_PRINT = 1 << 5, ++ MODE_UTF8 = 1 << 6, ++}; ++ ++enum cursor_movement { ++ CURSOR_SAVE, ++ CURSOR_LOAD ++}; ++ ++enum cursor_state { ++ CURSOR_DEFAULT = 0, ++ CURSOR_WRAPNEXT = 1, ++ CURSOR_ORIGIN = 2 ++}; ++ ++enum charset { ++ CS_GRAPHIC0, ++ CS_GRAPHIC1, ++ CS_UK, ++ CS_USA, ++ CS_MULTI, ++ CS_GER, ++ CS_FIN ++}; ++ ++enum escape_state { ++ ESC_START = 1, ++ ESC_CSI = 2, ++ ESC_STR = 4, /* DCS, OSC, PM, APC */ ++ ESC_ALTCHARSET = 8, ++ ESC_STR_END = 16, /* a final string was encountered */ ++ ESC_TEST = 32, /* Enter in test mode */ ++ ESC_UTF8 = 64, ++}; ++ ++typedef struct { ++ Glyph attr; /* current char attributes */ ++ int x; ++ int y; ++ char state; ++} TCursor; ++ ++typedef struct { ++ int mode; ++ int type; ++ int snap; ++ /* ++ * Selection variables: ++ * nb – normalized coordinates of the beginning of the selection ++ * ne – normalized coordinates of the end of the selection ++ * ob – original coordinates of the beginning of the selection ++ * oe – original coordinates of the end of the selection ++ */ ++ struct { ++ int x, y; ++ } nb, ne, ob, oe; ++ ++ int alt; ++} Selection; ++ ++/* Internal representation of the screen */ ++typedef struct { ++ int row; /* nb row */ ++ int col; /* nb col */ ++ Line *line; /* screen */ ++ Line *alt; /* alternate screen */ ++ Line hist[HISTSIZE]; /* history buffer */ ++ int histi; /* history index */ ++ int scr; /* scroll back */ ++ int *dirty; /* dirtyness of lines */ ++ TCursor c; /* cursor */ ++ int ocx; /* old cursor col */ ++ int ocy; /* old cursor row */ ++ int top; /* top scroll limit */ ++ int bot; /* bottom scroll limit */ ++ int mode; /* terminal mode flags */ ++ int esc; /* escape state flags */ ++ char trantbl[4]; /* charset table translation */ ++ int charset; /* current charset */ ++ int icharset; /* selected charset for sequence */ ++ int *tabs; ++ Rune lastc; /* last printed char outside of sequence, 0 if control */ ++} Term; ++ ++/* CSI Escape sequence structs */ ++/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ ++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 [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ ++typedef struct { ++ char type; /* ESC type ... */ ++ char *buf; /* allocated raw string */ ++ size_t siz; /* allocation size */ ++ size_t len; /* raw string length */ ++ char *args[STR_ARG_SIZ]; ++ int narg; /* nb of args */ ++} STREscape; ++ ++static void execsh(char *, char **); ++static void stty(char **); ++static void sigchld(int); ++static void ttywriteraw(const char *, size_t); ++ ++static void csidump(void); ++static void csihandle(void); ++static void csiparse(void); ++static void csireset(void); ++static void osc_color_response(int, int, int); ++static int eschandle(uchar); ++static void strdump(void); ++static void strhandle(void); ++static void strparse(void); ++static void strreset(void); ++ ++static void tprinter(char *, size_t); ++static void tdumpsel(void); ++static void tdumpline(int); ++static void tdump(void); ++static void tclearregion(int, int, int, int); ++static void tcursor(int); ++static void tdeletechar(int); ++static void tdeleteline(int); ++static void tinsertblank(int); ++static void tinsertblankline(int); ++static int tlinelen(int); ++static void tmoveto(int, int); ++static void tmoveato(int, int); ++static void tnewline(int); ++static void tputtab(int); ++static void tputc(Rune); ++static void treset(void); ++static void tscrollup(int, int, int); ++static void tscrolldown(int, int, int); ++static void tsetattr(const int *, int); ++static void tsetchar(Rune, const Glyph *, int, int); ++static void tsetdirt(int, int); ++static void tsetscroll(int, int); ++static void tswapscreen(void); ++static void tsetmode(int, int, const int *, int); ++static int twrite(const char *, int, int); ++static void tfulldirt(void); ++static void tcontrolcode(uchar ); ++static void tdectest(char ); ++static void tdefutf8(char); ++static int32_t tdefcolor(const int *, int *, int); ++static void tdeftran(char); ++static void tstrsequence(uchar); ++ ++static void drawregion(int, int, int, int); ++ ++static void selnormalize(void); ++static void selscroll(int, int); ++static void selsnap(int *, int *, int); ++ ++static size_t utf8decode(const char *, Rune *, size_t); ++static Rune utf8decodebyte(char, size_t *); ++static char utf8encodebyte(Rune, size_t); ++static size_t utf8validate(Rune *, size_t); ++ ++static char *base64dec(const char *); ++static char base64dec_getc(const char **); ++ ++static ssize_t xwrite(int, const char *, size_t); ++ ++/* Globals */ ++static Term term; ++static Selection sel; ++static CSIEscape csiescseq; ++static STREscape strescseq; ++static int iofd = 1; ++static int cmdfd; ++static pid_t pid; ++ ++static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; ++static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; ++static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; ++static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; ++ ++ssize_t ++xwrite(int fd, const char *s, size_t len) ++{ ++ size_t aux = len; ++ ssize_t r; ++ ++ while (len > 0) { ++ r = write(fd, s, len); ++ if (r < 0) ++ return r; ++ len -= r; ++ s += r; ++ } ++ ++ return aux; ++} ++ ++void * ++xmalloc(size_t len) ++{ ++ void *p; ++ ++ if (!(p = malloc(len))) ++ die("malloc: %s\n", strerror(errno)); ++ ++ return p; ++} ++ ++void * ++xrealloc(void *p, size_t len) ++{ ++ if ((p = realloc(p, len)) == NULL) ++ die("realloc: %s\n", strerror(errno)); ++ ++ return p; ++} ++ ++char * ++xstrdup(const char *s) ++{ ++ char *p; ++ ++ if ((p = strdup(s)) == NULL) ++ die("strdup: %s\n", strerror(errno)); ++ ++ return p; ++} ++ ++size_t ++utf8decode(const char *c, Rune *u, size_t clen) ++{ ++ size_t i, j, len, type; ++ Rune udecoded; ++ ++ *u = UTF_INVALID; ++ if (!clen) ++ return 0; ++ udecoded = utf8decodebyte(c[0], &len); ++ if (!BETWEEN(len, 1, UTF_SIZ)) ++ return 1; ++ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { ++ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); ++ if (type != 0) ++ return j; ++ } ++ if (j < len) ++ return 0; ++ *u = udecoded; ++ utf8validate(u, len); ++ ++ return len; ++} ++ ++Rune ++utf8decodebyte(char c, size_t *i) ++{ ++ for (*i = 0; *i < LEN(utfmask); ++(*i)) ++ if (((uchar)c & utfmask[*i]) == utfbyte[*i]) ++ return (uchar)c & ~utfmask[*i]; ++ ++ return 0; ++} ++ ++size_t ++utf8encode(Rune u, char *c) ++{ ++ size_t len, i; ++ ++ len = utf8validate(&u, 0); ++ if (len > UTF_SIZ) ++ return 0; ++ ++ for (i = len - 1; i != 0; --i) { ++ c[i] = utf8encodebyte(u, 0); ++ u >>= 6; ++ } ++ c[0] = utf8encodebyte(u, len); ++ ++ return len; ++} ++ ++char ++utf8encodebyte(Rune u, size_t i) ++{ ++ return utfbyte[i] | (u & ~utfmask[i]); ++} ++ ++size_t ++utf8validate(Rune *u, size_t i) ++{ ++ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) ++ *u = UTF_INVALID; ++ for (i = 1; *u > utfmax[i]; ++i) ++ ; ++ ++ return i; ++} ++ ++char ++base64dec_getc(const char **src) ++{ ++ while (**src && !isprint((unsigned char)**src)) ++ (*src)++; ++ return **src ? *((*src)++) : '='; /* emulate padding if string ends */ ++} ++ ++char * ++base64dec(const char *src) ++{ ++ size_t in_len = strlen(src); ++ char *result, *dst; ++ static const char base64_digits[256] = { ++ [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, ++ 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ++ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, ++ 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, ++ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 ++ }; ++ ++ if (in_len % 4) ++ in_len += 4 - (in_len % 4); ++ result = dst = xmalloc(in_len / 4 * 3 + 1); ++ while (*src) { ++ int a = base64_digits[(unsigned char) base64dec_getc(&src)]; ++ int b = base64_digits[(unsigned char) base64dec_getc(&src)]; ++ int c = base64_digits[(unsigned char) base64dec_getc(&src)]; ++ int d = base64_digits[(unsigned char) base64dec_getc(&src)]; ++ ++ /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ ++ if (a == -1 || b == -1) ++ break; ++ ++ *dst++ = (a << 2) | ((b & 0x30) >> 4); ++ if (c == -1) ++ break; ++ *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); ++ if (d == -1) ++ break; ++ *dst++ = ((c & 0x03) << 6) | d; ++ } ++ *dst = '\0'; ++ return result; ++} ++ ++void ++selinit(void) ++{ ++ sel.mode = SEL_IDLE; ++ sel.snap = 0; ++ sel.ob.x = -1; ++} ++ ++int ++tlinelen(int y) ++{ ++ int i = term.col; ++ ++ if (TLINE(y)[i - 1].mode & ATTR_WRAP) ++ return i; ++ ++ while (i > 0 && TLINE(y)[i - 1].u == ' ') ++ --i; ++ ++ return i; ++} ++ ++void ++selstart(int col, int row, int snap) ++{ ++ selclear(); ++ sel.mode = SEL_EMPTY; ++ sel.type = SEL_REGULAR; ++ sel.alt = IS_SET(MODE_ALTSCREEN); ++ sel.snap = snap; ++ sel.oe.x = sel.ob.x = col; ++ sel.oe.y = sel.ob.y = row; ++ selnormalize(); ++ ++ if (sel.snap != 0) ++ sel.mode = SEL_READY; ++ tsetdirt(sel.nb.y, sel.ne.y); ++} ++ ++void ++selextend(int col, int row, int type, int done) ++{ ++ int oldey, oldex, oldsby, oldsey, oldtype; ++ ++ if (sel.mode == SEL_IDLE) ++ return; ++ if (done && sel.mode == SEL_EMPTY) { ++ selclear(); ++ return; ++ } ++ ++ oldey = sel.oe.y; ++ oldex = sel.oe.x; ++ oldsby = sel.nb.y; ++ oldsey = sel.ne.y; ++ oldtype = sel.type; ++ ++ sel.oe.x = col; ++ sel.oe.y = row; ++ selnormalize(); ++ sel.type = type; ++ ++ if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) ++ tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); ++ ++ sel.mode = done ? SEL_IDLE : SEL_READY; ++} ++ ++void ++selnormalize(void) ++{ ++ int i; ++ ++ if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { ++ sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; ++ sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; ++ } else { ++ sel.nb.x = MIN(sel.ob.x, sel.oe.x); ++ sel.ne.x = MAX(sel.ob.x, sel.oe.x); ++ } ++ sel.nb.y = MIN(sel.ob.y, sel.oe.y); ++ sel.ne.y = MAX(sel.ob.y, sel.oe.y); ++ ++ selsnap(&sel.nb.x, &sel.nb.y, -1); ++ selsnap(&sel.ne.x, &sel.ne.y, +1); ++ ++ /* expand selection over line breaks */ ++ if (sel.type == SEL_RECTANGULAR) ++ return; ++ i = tlinelen(sel.nb.y); ++ if (i < sel.nb.x) ++ sel.nb.x = i; ++ if (tlinelen(sel.ne.y) <= sel.ne.x) ++ sel.ne.x = term.col - 1; ++} ++ ++int ++selected(int x, int y) ++{ ++ if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || ++ sel.alt != IS_SET(MODE_ALTSCREEN)) ++ return 0; ++ ++ if (sel.type == SEL_RECTANGULAR) ++ return BETWEEN(y, sel.nb.y, sel.ne.y) ++ && BETWEEN(x, sel.nb.x, sel.ne.x); ++ ++ return BETWEEN(y, sel.nb.y, sel.ne.y) ++ && (y != sel.nb.y || x >= sel.nb.x) ++ && (y != sel.ne.y || x <= sel.ne.x); ++} ++ ++void ++selsnap(int *x, int *y, int direction) ++{ ++ int newx, newy, xt, yt; ++ int delim, prevdelim; ++ const Glyph *gp, *prevgp; ++ ++ switch (sel.snap) { ++ case SNAP_WORD: ++ /* ++ * Snap around if the word wraps around at the end or ++ * beginning of a line. ++ */ ++ prevgp = &TLINE(*y)[*x]; ++ prevdelim = ISDELIM(prevgp->u); ++ for (;;) { ++ newx = *x + direction; ++ newy = *y; ++ if (!BETWEEN(newx, 0, term.col - 1)) { ++ newy += direction; ++ newx = (newx + term.col) % term.col; ++ if (!BETWEEN(newy, 0, term.row - 1)) ++ break; ++ ++ if (direction > 0) ++ yt = *y, xt = *x; ++ else ++ yt = newy, xt = newx; ++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) ++ break; ++ } ++ ++ if (newx >= tlinelen(newy)) ++ break; ++ ++ gp = &TLINE(newy)[newx]; ++ delim = ISDELIM(gp->u); ++ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim ++ || (delim && gp->u != prevgp->u))) ++ break; ++ ++ *x = newx; ++ *y = newy; ++ prevgp = gp; ++ prevdelim = delim; ++ } ++ break; ++ case SNAP_LINE: ++ /* ++ * Snap around if the the previous line or the current one ++ * has set ATTR_WRAP at its end. Then the whole next or ++ * previous line will be selected. ++ */ ++ *x = (direction < 0) ? 0 : term.col - 1; ++ if (direction < 0) { ++ for (; *y > 0; *y += direction) { ++ if (!(TLINE(*y-1)[term.col-1].mode ++ & ATTR_WRAP)) { ++ break; ++ } ++ } ++ } else if (direction > 0) { ++ for (; *y < term.row-1; *y += direction) { ++ if (!(TLINE(*y)[term.col-1].mode ++ & ATTR_WRAP)) { ++ break; ++ } ++ } ++ } ++ break; ++ } ++} ++ ++char * ++getsel(void) ++{ ++ char *str, *ptr; ++ int y, bufsize, lastx, linelen; ++ const Glyph *gp, *last; ++ ++ if (sel.ob.x == -1) ++ return NULL; ++ ++ bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; ++ ptr = str = xmalloc(bufsize); ++ ++ /* append every set & selected glyph to the selection */ ++ for (y = sel.nb.y; y <= sel.ne.y; y++) { ++ if ((linelen = tlinelen(y)) == 0) { ++ *ptr++ = '\n'; ++ continue; ++ } ++ ++ if (sel.type == SEL_RECTANGULAR) { ++ gp = &TLINE(y)[sel.nb.x]; ++ lastx = sel.ne.x; ++ } else { ++ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; ++ lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; ++ } ++ last = &TLINE(y)[MIN(lastx, linelen-1)]; ++ while (last >= gp && last->u == ' ') ++ --last; ++ ++ for ( ; gp <= last; ++gp) { ++ if (gp->mode & ATTR_WDUMMY) ++ continue; ++ ++ ptr += utf8encode(gp->u, ptr); ++ } ++ ++ /* ++ * Copy and pasting of line endings is inconsistent ++ * in the inconsistent terminal and GUI world. ++ * The best solution seems like to produce '\n' when ++ * something is copied from st and convert '\n' to ++ * '\r', when something to be pasted is received by ++ * st. ++ * FIXME: Fix the computer world. ++ */ ++ if ((y < sel.ne.y || lastx >= linelen) && ++ (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) ++ *ptr++ = '\n'; ++ } ++ *ptr = 0; ++ return str; ++} ++ ++void ++selclear(void) ++{ ++ if (sel.ob.x == -1) ++ return; ++ sel.mode = SEL_IDLE; ++ sel.ob.x = -1; ++ tsetdirt(sel.nb.y, sel.ne.y); ++} ++ ++void ++die(const char *errstr, ...) ++{ ++ va_list ap; ++ ++ va_start(ap, errstr); ++ vfprintf(stderr, errstr, ap); ++ va_end(ap); ++ exit(1); ++} ++ ++void ++execsh(char *cmd, char **args) ++{ ++ char *sh, *prog, *arg; ++ const struct passwd *pw; ++ ++ errno = 0; ++ if ((pw = getpwuid(getuid())) == NULL) { ++ if (errno) ++ die("getpwuid: %s\n", strerror(errno)); ++ else ++ die("who are you?\n"); ++ } ++ ++ if ((sh = getenv("SHELL")) == NULL) ++ sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; ++ ++ if (args) { ++ prog = args[0]; ++ arg = NULL; ++ } else if (scroll) { ++ prog = scroll; ++ arg = utmp ? utmp : sh; ++ } else if (utmp) { ++ prog = utmp; ++ arg = NULL; ++ } else { ++ prog = sh; ++ arg = NULL; ++ } ++ DEFAULT(args, ((char *[]) {prog, arg, NULL})); ++ ++ unsetenv("COLUMNS"); ++ unsetenv("LINES"); ++ unsetenv("TERMCAP"); ++ setenv("LOGNAME", pw->pw_name, 1); ++ setenv("USER", pw->pw_name, 1); ++ setenv("SHELL", sh, 1); ++ setenv("HOME", pw->pw_dir, 1); ++ setenv("TERM", termname, 1); ++ ++ signal(SIGCHLD, SIG_DFL); ++ signal(SIGHUP, SIG_DFL); ++ signal(SIGINT, SIG_DFL); ++ signal(SIGQUIT, SIG_DFL); ++ signal(SIGTERM, SIG_DFL); ++ signal(SIGALRM, SIG_DFL); ++ ++ execvp(prog, args); ++ _exit(1); ++} ++ ++void ++sigchld(int a) ++{ ++ int stat; ++ pid_t p; ++ ++ if ((p = waitpid(pid, &stat, WNOHANG)) < 0) ++ die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); ++ ++ if (pid != p) ++ return; ++ ++ if (WIFEXITED(stat) && WEXITSTATUS(stat)) ++ die("child exited with status %d\n", WEXITSTATUS(stat)); ++ else if (WIFSIGNALED(stat)) ++ die("child terminated due to signal %d\n", WTERMSIG(stat)); ++ _exit(0); ++} ++ ++void ++stty(char **args) ++{ ++ char cmd[_POSIX_ARG_MAX], **p, *q, *s; ++ size_t n, siz; ++ ++ if ((n = strlen(stty_args)) > sizeof(cmd)-1) ++ die("incorrect stty parameters\n"); ++ memcpy(cmd, stty_args, n); ++ q = cmd + n; ++ siz = sizeof(cmd) - n; ++ for (p = args; p && (s = *p); ++p) { ++ if ((n = strlen(s)) > siz-1) ++ die("stty parameter length too long\n"); ++ *q++ = ' '; ++ memcpy(q, s, n); ++ q += n; ++ siz -= n + 1; ++ } ++ *q = '\0'; ++ if (system(cmd) != 0) ++ perror("Couldn't call stty"); ++} ++ ++int ++ttynew(const char *line, char *cmd, const char *out, char **args) ++{ ++ int m, s; ++ ++ if (out) { ++ term.mode |= MODE_PRINT; ++ iofd = (!strcmp(out, "-")) ? ++ 1 : open(out, O_WRONLY | O_CREAT, 0666); ++ if (iofd < 0) { ++ fprintf(stderr, "Error opening %s:%s\n", ++ out, strerror(errno)); ++ } ++ } ++ ++ if (line) { ++ if ((cmdfd = open(line, O_RDWR)) < 0) ++ die("open line '%s' failed: %s\n", ++ line, strerror(errno)); ++ dup2(cmdfd, 0); ++ stty(args); ++ return cmdfd; ++ } ++ ++ /* seems to work fine on linux, openbsd and freebsd */ ++ if (openpty(&m, &s, NULL, NULL, NULL) < 0) ++ die("openpty failed: %s\n", strerror(errno)); ++ ++ switch (pid = fork()) { ++ case -1: ++ die("fork failed: %s\n", strerror(errno)); ++ break; ++ case 0: ++ close(iofd); ++ close(m); ++ setsid(); /* create a new process group */ ++ dup2(s, 0); ++ dup2(s, 1); ++ dup2(s, 2); ++ if (ioctl(s, TIOCSCTTY, NULL) < 0) ++ die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); ++ if (s > 2) ++ close(s); ++#ifdef __OpenBSD__ ++ if (pledge("stdio getpw proc exec", NULL) == -1) ++ die("pledge\n"); ++#endif ++ execsh(cmd, args); ++ break; ++ default: ++#ifdef __OpenBSD__ ++ if (pledge("stdio rpath tty proc", NULL) == -1) ++ die("pledge\n"); ++#endif ++ close(s); ++ cmdfd = m; ++ signal(SIGCHLD, sigchld); ++ break; ++ } ++ return cmdfd; ++} ++ ++size_t ++ttyread(void) ++{ ++ static char buf[BUFSIZ]; ++ static int buflen = 0; ++ int ret, written; ++ ++ /* append read bytes to unprocessed bytes */ ++ ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); ++ ++ switch (ret) { ++ case 0: ++ exit(0); ++ case -1: ++ die("couldn't read from shell: %s\n", strerror(errno)); ++ default: ++ buflen += ret; ++ written = twrite(buf, buflen, 0); ++ buflen -= written; ++ /* keep any incomplete UTF-8 byte sequence for the next call */ ++ if (buflen > 0) ++ memmove(buf, buf + written, buflen); ++ return ret; ++ } ++} ++ ++void ++ttywrite(const char *s, size_t n, int may_echo) ++{ ++ const char *next; ++ Arg arg = (Arg) { .i = term.scr }; ++ ++ kscrolldown(&arg); ++ ++ if (may_echo && IS_SET(MODE_ECHO)) ++ twrite(s, n, 1); ++ ++ if (!IS_SET(MODE_CRLF)) { ++ ttywriteraw(s, n); ++ return; ++ } ++ ++ /* This is similar to how the kernel handles ONLCR for ttys */ ++ while (n > 0) { ++ if (*s == '\r') { ++ next = s + 1; ++ ttywriteraw("\r\n", 2); ++ } else { ++ next = memchr(s, '\r', n); ++ DEFAULT(next, s + n); ++ ttywriteraw(s, next - s); ++ } ++ n -= next - s; ++ s = next; ++ } ++} ++ ++void ++ttywriteraw(const char *s, size_t n) ++{ ++ fd_set wfd, rfd; ++ ssize_t r; ++ size_t lim = 256; ++ ++ /* ++ * Remember that we are using a pty, which might be a modem line. ++ * Writing too much will clog the line. That's why we are doing this ++ * dance. ++ * FIXME: Migrate the world to Plan 9. ++ */ ++ while (n > 0) { ++ FD_ZERO(&wfd); ++ FD_ZERO(&rfd); ++ FD_SET(cmdfd, &wfd); ++ FD_SET(cmdfd, &rfd); ++ ++ /* Check if we can write. */ ++ if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { ++ if (errno == EINTR) ++ continue; ++ die("select failed: %s\n", strerror(errno)); ++ } ++ if (FD_ISSET(cmdfd, &wfd)) { ++ /* ++ * Only write the bytes written by ttywrite() or the ++ * default of 256. This seems to be a reasonable value ++ * for a serial line. Bigger values might clog the I/O. ++ */ ++ if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) ++ goto write_error; ++ if (r < n) { ++ /* ++ * We weren't able to write out everything. ++ * This means the buffer is getting full ++ * again. Empty it. ++ */ ++ if (n < lim) ++ lim = ttyread(); ++ n -= r; ++ s += r; ++ } else { ++ /* All bytes have been written. */ ++ break; ++ } ++ } ++ if (FD_ISSET(cmdfd, &rfd)) ++ lim = ttyread(); ++ } ++ return; ++ ++write_error: ++ die("write error on tty: %s\n", strerror(errno)); ++} ++ ++void ++ttyresize(int tw, int th) ++{ ++ struct winsize w; ++ ++ w.ws_row = term.row; ++ w.ws_col = term.col; ++ w.ws_xpixel = tw; ++ w.ws_ypixel = th; ++ if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) ++ fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); ++} ++ ++void ++ttyhangup(void) ++{ ++ /* Send SIGHUP to shell */ ++ kill(pid, SIGHUP); ++} ++ ++int ++tattrset(int attr) ++{ ++ int i, j; ++ ++ for (i = 0; i < term.row-1; i++) { ++ for (j = 0; j < term.col-1; j++) { ++ if (term.line[i][j].mode & attr) ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++void ++tsetdirt(int top, int bot) ++{ ++ int i; ++ ++ LIMIT(top, 0, term.row-1); ++ LIMIT(bot, 0, term.row-1); ++ ++ for (i = top; i <= bot; i++) ++ term.dirty[i] = 1; ++} ++ ++void ++tsetdirtattr(int attr) ++{ ++ int i, j; ++ ++ for (i = 0; i < term.row-1; i++) { ++ for (j = 0; j < term.col-1; j++) { ++ if (term.line[i][j].mode & attr) { ++ tsetdirt(i, i); ++ break; ++ } ++ } ++ } ++} ++ ++void ++tfulldirt(void) ++{ ++ tsetdirt(0, term.row-1); ++} ++ ++void ++tcursor(int mode) ++{ ++ static TCursor c[2]; ++ int alt = IS_SET(MODE_ALTSCREEN); ++ ++ if (mode == CURSOR_SAVE) { ++ c[alt] = term.c; ++ } else if (mode == CURSOR_LOAD) { ++ term.c = c[alt]; ++ tmoveto(c[alt].x, c[alt].y); ++ } ++} ++ ++void ++treset(void) ++{ ++ uint i; ++ ++ term.c = (TCursor){{ ++ .mode = ATTR_NULL, ++ .fg = defaultfg, ++ .bg = defaultbg ++ }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; ++ ++ memset(term.tabs, 0, term.col * sizeof(*term.tabs)); ++ for (i = tabspaces; i < term.col; i += tabspaces) ++ term.tabs[i] = 1; ++ term.top = 0; ++ term.bot = term.row - 1; ++ term.mode = MODE_WRAP|MODE_UTF8; ++ memset(term.trantbl, CS_USA, sizeof(term.trantbl)); ++ term.charset = 0; ++ ++ for (i = 0; i < 2; i++) { ++ tmoveto(0, 0); ++ tcursor(CURSOR_SAVE); ++ tclearregion(0, 0, term.col-1, term.row-1); ++ tswapscreen(); ++ } ++} ++ ++void ++tnew(int col, int row) ++{ ++ term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; ++ tresize(col, row); ++ treset(); ++} ++ ++void ++tswapscreen(void) ++{ ++ Line *tmp = term.line; ++ ++ term.line = term.alt; ++ term.alt = tmp; ++ term.mode ^= MODE_ALTSCREEN; ++ tfulldirt(); ++} ++ ++void ++kscrolldown(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (n > term.scr) ++ n = term.scr; ++ ++ if (term.scr > 0) { ++ term.scr -= n; ++ selscroll(0, -n); ++ tfulldirt(); ++ } ++} ++ ++void ++kscrollup(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (term.scr <= HISTSIZE-n) { ++ term.scr += n; ++ selscroll(0, n); ++ tfulldirt(); ++ } ++} ++ ++void ++tscrolldown(int orig, int n, int copyhist) ++{ ++ int i; ++ Line temp; ++ ++ LIMIT(n, 0, term.bot-orig+1); ++ ++ if (copyhist) { ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[term.bot]; ++ term.line[term.bot] = temp; ++ } ++ ++ tsetdirt(orig, term.bot-n); ++ tclearregion(0, term.bot-n+1, term.col-1, term.bot); ++ ++ for (i = term.bot; i >= orig+n; i--) { ++ temp = term.line[i]; ++ term.line[i] = term.line[i-n]; ++ term.line[i-n] = temp; ++ } ++ ++ if (term.scr == 0) ++ selscroll(orig, n); ++} ++ ++void ++tscrollup(int orig, int n, int copyhist) ++{ ++ int i; ++ Line temp; ++ ++ LIMIT(n, 0, term.bot-orig+1); ++ ++ if (copyhist) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[orig]; ++ term.line[orig] = temp; ++ } ++ ++ if (term.scr > 0 && term.scr < HISTSIZE) ++ term.scr = MIN(term.scr + n, HISTSIZE-1); ++ ++ tclearregion(0, orig, term.col-1, orig+n-1); ++ tsetdirt(orig+n, term.bot); ++ ++ for (i = orig; i <= term.bot-n; i++) { ++ temp = term.line[i]; ++ term.line[i] = term.line[i+n]; ++ term.line[i+n] = temp; ++ } ++ ++ if (term.scr == 0) ++ selscroll(orig, -n); ++} ++ ++void ++selscroll(int orig, int n) ++{ ++ if (sel.ob.x == -1) ++ return; ++ ++ if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { ++ selclear(); ++ } else if (BETWEEN(sel.nb.y, orig, term.bot)) { ++ sel.ob.y += n; ++ sel.oe.y += n; ++ if (sel.ob.y < term.top || sel.ob.y > term.bot || ++ sel.oe.y < term.top || sel.oe.y > term.bot) { ++ selclear(); ++ } else { ++ selnormalize(); ++ } ++ } ++} ++ ++void ++tnewline(int first_col) ++{ ++ int y = term.c.y; ++ ++ if (y == term.bot) { ++ tscrollup(term.top, 1, 1); ++ } else { ++ y++; ++ } ++ tmoveto(first_col ? 0 : term.c.x, y); ++} ++ ++void ++csiparse(void) ++{ ++ char *p = csiescseq.buf, *np; ++ long int v; ++ ++ 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, const Glyph *attr, int x, int y) ++{ ++ static const char *vt100_0[62] = { /* 0x41 - 0x7e */ ++ "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ ++ 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ ++ 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ ++ 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ ++ "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ ++ "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ ++ "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ ++ "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ ++ }; ++ ++ /* ++ * The table is proudly stolen from rxvt. ++ */ ++ if (term.trantbl[term.charset] == CS_GRAPHIC0 && ++ BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) ++ utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); ++ ++ if (term.line[y][x].mode & ATTR_WIDE) { ++ if (x+1 < term.col) { ++ term.line[y][x+1].u = ' '; ++ term.line[y][x+1].mode &= ~ATTR_WDUMMY; ++ } ++ } else if (term.line[y][x].mode & ATTR_WDUMMY) { ++ term.line[y][x-1].u = ' '; ++ term.line[y][x-1].mode &= ~ATTR_WIDE; ++ } ++ ++ term.dirty[y] = 1; ++ term.line[y][x] = *attr; ++ term.line[y][x].u = u; ++} ++ ++void ++tclearregion(int x1, int y1, int x2, int y2) ++{ ++ int x, y, temp; ++ Glyph *gp; ++ ++ if (x1 > x2) ++ temp = x1, x1 = x2, x2 = temp; ++ if (y1 > y2) ++ temp = y1, y1 = y2, y2 = temp; ++ ++ LIMIT(x1, 0, term.col-1); ++ LIMIT(x2, 0, term.col-1); ++ LIMIT(y1, 0, term.row-1); ++ LIMIT(y2, 0, term.row-1); ++ ++ for (y = y1; y <= y2; y++) { ++ term.dirty[y] = 1; ++ for (x = x1; x <= x2; x++) { ++ gp = &term.line[y][x]; ++ if (selected(x, y)) ++ selclear(); ++ gp->fg = term.c.attr.fg; ++ gp->bg = term.c.attr.bg; ++ gp->mode = 0; ++ gp->u = ' '; ++ } ++ } ++} ++ ++void ++tdeletechar(int n) ++{ ++ int dst, src, size; ++ Glyph *line; ++ ++ LIMIT(n, 0, term.col - term.c.x); ++ ++ dst = term.c.x; ++ src = term.c.x + n; ++ size = term.col - src; ++ line = term.line[term.c.y]; ++ ++ memmove(&line[dst], &line[src], size * sizeof(Glyph)); ++ tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); ++} ++ ++void ++tinsertblank(int n) ++{ ++ int dst, src, size; ++ Glyph *line; ++ ++ LIMIT(n, 0, term.col - term.c.x); ++ ++ dst = term.c.x + n; ++ src = term.c.x; ++ size = term.col - dst; ++ line = term.line[term.c.y]; ++ ++ memmove(&line[dst], &line[src], size * sizeof(Glyph)); ++ tclearregion(src, term.c.y, dst - 1, term.c.y); ++} ++ ++void ++tinsertblankline(int n) ++{ ++ if (BETWEEN(term.c.y, term.top, term.bot)) ++ tscrolldown(term.c.y, n, 0); ++} ++ ++void ++tdeleteline(int n) ++{ ++ if (BETWEEN(term.c.y, term.top, term.bot)) ++ tscrollup(term.c.y, n, 0); ++} ++ ++int32_t ++tdefcolor(const int *attr, int *npar, int l) ++{ ++ int32_t idx = -1; ++ uint r, g, b; ++ ++ switch (attr[*npar + 1]) { ++ case 2: /* direct color in RGB space */ ++ if (*npar + 4 >= l) { ++ fprintf(stderr, ++ "erresc(38): Incorrect number of parameters (%d)\n", ++ *npar); ++ break; ++ } ++ r = attr[*npar + 2]; ++ g = attr[*npar + 3]; ++ b = attr[*npar + 4]; ++ *npar += 4; ++ if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) ++ fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", ++ r, g, b); ++ else ++ idx = TRUECOLOR(r, g, b); ++ break; ++ case 5: /* indexed color */ ++ if (*npar + 2 >= l) { ++ fprintf(stderr, ++ "erresc(38): Incorrect number of parameters (%d)\n", ++ *npar); ++ break; ++ } ++ *npar += 2; ++ if (!BETWEEN(attr[*npar], 0, 255)) ++ fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); ++ else ++ idx = attr[*npar]; ++ break; ++ case 0: /* implemented defined (only foreground) */ ++ case 1: /* transparent */ ++ case 3: /* direct color in CMY space */ ++ case 4: /* direct color in CMYK space */ ++ default: ++ fprintf(stderr, ++ "erresc(38): gfx attr %d unknown\n", attr[*npar]); ++ break; ++ } ++ ++ return idx; ++} ++ ++void ++tsetattr(const int *attr, int l) ++{ ++ int i; ++ int32_t idx; ++ ++ for (i = 0; i < l; i++) { ++ switch (attr[i]) { ++ case 0: ++ term.c.attr.mode &= ~( ++ ATTR_BOLD | ++ ATTR_FAINT | ++ ATTR_ITALIC | ++ ATTR_UNDERLINE | ++ ATTR_BLINK | ++ ATTR_REVERSE | ++ ATTR_INVISIBLE | ++ ATTR_STRUCK ); ++ term.c.attr.fg = defaultfg; ++ term.c.attr.bg = defaultbg; ++ break; ++ case 1: ++ term.c.attr.mode |= ATTR_BOLD; ++ break; ++ case 2: ++ term.c.attr.mode |= ATTR_FAINT; ++ break; ++ case 3: ++ term.c.attr.mode |= ATTR_ITALIC; ++ break; ++ case 4: ++ term.c.attr.mode |= ATTR_UNDERLINE; ++ break; ++ case 5: /* slow blink */ ++ /* FALLTHROUGH */ ++ case 6: /* rapid blink */ ++ term.c.attr.mode |= ATTR_BLINK; ++ break; ++ case 7: ++ term.c.attr.mode |= ATTR_REVERSE; ++ break; ++ case 8: ++ term.c.attr.mode |= ATTR_INVISIBLE; ++ break; ++ case 9: ++ term.c.attr.mode |= ATTR_STRUCK; ++ break; ++ case 22: ++ term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); ++ break; ++ case 23: ++ term.c.attr.mode &= ~ATTR_ITALIC; ++ break; ++ case 24: ++ term.c.attr.mode &= ~ATTR_UNDERLINE; ++ break; ++ case 25: ++ term.c.attr.mode &= ~ATTR_BLINK; ++ break; ++ case 27: ++ term.c.attr.mode &= ~ATTR_REVERSE; ++ break; ++ case 28: ++ term.c.attr.mode &= ~ATTR_INVISIBLE; ++ break; ++ case 29: ++ term.c.attr.mode &= ~ATTR_STRUCK; ++ break; ++ case 38: ++ if ((idx = tdefcolor(attr, &i, l)) >= 0) ++ term.c.attr.fg = idx; ++ break; ++ case 39: ++ 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, const int *args, int narg) ++{ ++ int alt; const int *lim; ++ ++ for (lim = args + narg; args < lim; ++args) { ++ if (priv) { ++ switch (*args) { ++ case 1: /* DECCKM -- Cursor key */ ++ xsetmode(set, MODE_APPCURSOR); ++ break; ++ case 5: /* DECSCNM -- Reverse video */ ++ xsetmode(set, MODE_REVERSE); ++ break; ++ case 6: /* DECOM -- Origin */ ++ MODBIT(term.c.state, set, CURSOR_ORIGIN); ++ tmoveato(0, 0); ++ break; ++ case 7: /* DECAWM -- Auto wrap */ ++ MODBIT(term.mode, set, MODE_WRAP); ++ break; ++ case 0: /* Error (IGNORED) */ ++ case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ ++ case 3: /* DECCOLM -- Column (IGNORED) */ ++ case 4: /* DECSCLM -- Scroll (IGNORED) */ ++ case 8: /* DECARM -- Auto repeat (IGNORED) */ ++ case 18: /* DECPFF -- Printer feed (IGNORED) */ ++ case 19: /* DECPEX -- Printer extent (IGNORED) */ ++ case 42: /* DECNRCM -- National characters (IGNORED) */ ++ case 12: /* att610 -- Start blinking cursor (IGNORED) */ ++ break; ++ case 25: /* DECTCEM -- Text Cursor Enable Mode */ ++ xsetmode(!set, MODE_HIDE); ++ break; ++ case 9: /* X10 mouse compatibility mode */ ++ xsetpointermotion(0); ++ xsetmode(0, MODE_MOUSE); ++ xsetmode(set, MODE_MOUSEX10); ++ break; ++ case 1000: /* 1000: report button press */ ++ xsetpointermotion(0); ++ xsetmode(0, MODE_MOUSE); ++ xsetmode(set, MODE_MOUSEBTN); ++ break; ++ case 1002: /* 1002: report motion on button press */ ++ xsetpointermotion(0); ++ xsetmode(0, MODE_MOUSE); ++ xsetmode(set, MODE_MOUSEMOTION); ++ break; ++ case 1003: /* 1003: enable all mouse motions */ ++ xsetpointermotion(set); ++ xsetmode(0, MODE_MOUSE); ++ xsetmode(set, MODE_MOUSEMANY); ++ break; ++ case 1004: /* 1004: send focus events to tty */ ++ xsetmode(set, MODE_FOCUS); ++ break; ++ case 1006: /* 1006: extended reporting mode */ ++ xsetmode(set, MODE_MOUSESGR); ++ break; ++ case 1034: ++ 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 <n> blank char */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tinsertblank(csiescseq.arg[0]); ++ break; ++ case 'A': /* CUU -- Cursor <n> Up */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); ++ break; ++ case 'B': /* CUD -- Cursor <n> Down */ ++ case 'e': /* VPR --Cursor <n> Down */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); ++ break; ++ case 'i': /* MC -- Media Copy */ ++ switch (csiescseq.arg[0]) { ++ case 0: ++ tdump(); ++ break; ++ case 1: ++ tdumpline(term.c.y); ++ break; ++ case 2: ++ tdumpsel(); ++ break; ++ case 4: ++ term.mode &= ~MODE_PRINT; ++ break; ++ case 5: ++ term.mode |= MODE_PRINT; ++ break; ++ } ++ break; ++ case 'c': /* DA -- Device Attributes */ ++ if (csiescseq.arg[0] == 0) ++ ttywrite(vtiden, strlen(vtiden), 0); ++ break; ++ case 'b': /* REP -- if last char is printable print it <n> more times */ ++ DEFAULT(csiescseq.arg[0], 1); ++ if (term.lastc) ++ while (csiescseq.arg[0]-- > 0) ++ tputc(term.lastc); ++ break; ++ case 'C': /* CUF -- Cursor <n> Forward */ ++ case 'a': /* HPR -- Cursor <n> Forward */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tmoveto(term.c.x+csiescseq.arg[0], term.c.y); ++ break; ++ case 'D': /* CUB -- Cursor <n> Backward */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tmoveto(term.c.x-csiescseq.arg[0], term.c.y); ++ break; ++ case 'E': /* CNL -- Cursor <n> Down and first col */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tmoveto(0, term.c.y+csiescseq.arg[0]); ++ break; ++ case 'F': /* CPL -- Cursor <n> 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 <col> */ ++ case '`': /* HPA */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tmoveto(csiescseq.arg[0]-1, term.c.y); ++ break; ++ case 'H': /* CUP -- Move to <row> <col> */ ++ 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 <n> 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 <n> line up */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tscrollup(term.top, csiescseq.arg[0], 0); ++ break; ++ case 'T': /* SD -- Scroll <n> line down */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tscrolldown(term.top, csiescseq.arg[0], 0); ++ break; ++ case 'L': /* IL -- Insert <n> 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 <n> lines */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tdeleteline(csiescseq.arg[0]); ++ break; ++ case 'X': /* ECH -- Erase <n> 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 <n> char */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tdeletechar(csiescseq.arg[0]); ++ break; ++ case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */ ++ DEFAULT(csiescseq.arg[0], 1); ++ tputtab(-csiescseq.arg[0]); ++ break; ++ case 'd': /* VPA -- Move to <row> */ ++ 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 ++osc_color_response(int num, int index, int is_osc4) ++{ ++ int n; ++ char buf[32]; ++ unsigned char r, g, b; ++ ++ if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { ++ fprintf(stderr, "erresc: failed to fetch %s color %d\n", ++ is_osc4 ? "osc4" : "osc", ++ is_osc4 ? num : index); ++ return; ++ } ++ ++ n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", ++ is_osc4 ? "4;" : "", num, r, r, g, g, b, b); ++ if (n < 0 || n >= sizeof(buf)) { ++ fprintf(stderr, "error: %s while printing %s response\n", ++ n < 0 ? "snprintf failed" : "truncation occurred", ++ is_osc4 ? "osc4" : "osc"); ++ } else { ++ ttywrite(buf, n, 1); ++ } ++} ++ ++void ++strhandle(void) ++{ ++ char *p = NULL, *dec; ++ int j, narg, par; ++ const struct { int idx; char *str; } osc_table[] = { ++ { defaultfg, "foreground" }, ++ { defaultbg, "background" }, ++ { defaultcs, "cursor" } ++ }; ++ ++ term.esc &= ~(ESC_STR_END|ESC_STR); ++ strparse(); ++ par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; ++ ++ switch (strescseq.type) { ++ case ']': /* OSC -- Operating System Command */ ++ switch (par) { ++ case 0: ++ if (narg > 1) { ++ xsettitle(strescseq.args[1]); ++ xseticontitle(strescseq.args[1]); ++ } ++ return; ++ case 1: ++ if (narg > 1) ++ xseticontitle(strescseq.args[1]); ++ return; ++ case 2: ++ if (narg > 1) ++ xsettitle(strescseq.args[1]); ++ return; ++ case 52: ++ if (narg > 2 && allowwindowops) { ++ dec = base64dec(strescseq.args[2]); ++ if (dec) { ++ xsetsel(dec); ++ xclipcopy(); ++ } else { ++ fprintf(stderr, "erresc: invalid base64\n"); ++ } ++ } ++ return; ++ case 10: ++ case 11: ++ case 12: ++ if (narg < 2) ++ break; ++ p = strescseq.args[1]; ++ if ((j = par - 10) < 0 || j >= LEN(osc_table)) ++ break; /* shouldn't be possible */ ++ ++ if (!strcmp(p, "?")) { ++ osc_color_response(par, osc_table[j].idx, 0); ++ } else if (xsetcolorname(osc_table[j].idx, p)) { ++ fprintf(stderr, "erresc: invalid %s color: %s\n", ++ osc_table[j].str, p); ++ } else { ++ tfulldirt(); ++ } ++ return; ++ case 4: /* color set */ ++ if (narg < 3) ++ break; ++ p = strescseq.args[2]; ++ /* FALLTHROUGH */ ++ case 104: /* color reset */ ++ j = (narg > 1) ? atoi(strescseq.args[1]) : -1; ++ ++ if (p && !strcmp(p, "?")) { ++ osc_color_response(j, 0, 1); ++ } else if (xsetcolorname(j, p)) { ++ if (par == 104 && narg <= 1) ++ return; /* color reset without parameter */ ++ fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", ++ j, p ? p : "(null)"); ++ } else { ++ /* ++ * TODO if defaultbg color is changed, borders ++ * are dirty ++ */ ++ tfulldirt(); ++ } ++ return; ++ } ++ break; ++ case 'k': /* old title set compatibility */ ++ xsettitle(strescseq.args[0]); ++ return; ++ case 'P': /* DCS -- Device Control String */ ++ case '_': /* APC -- Application Program Command */ ++ case '^': /* PM -- Privacy Message */ ++ return; ++ } ++ ++ fprintf(stderr, "erresc: unknown str "); ++ strdump(); ++} ++ ++void ++strparse(void) ++{ ++ int c; ++ char *p = strescseq.buf; ++ ++ strescseq.narg = 0; ++ strescseq.buf[strescseq.len] = '\0'; ++ ++ if (*p == '\0') ++ return; ++ ++ while (strescseq.narg < STR_ARG_SIZ) { ++ strescseq.args[strescseq.narg++] = p; ++ while ((c = *p) != ';' && c != '\0') ++ ++p; ++ if (c == '\0') ++ return; ++ *p++ = '\0'; ++ } ++} ++ ++void ++strdump(void) ++{ ++ size_t i; ++ uint c; ++ ++ fprintf(stderr, "ESC%c", strescseq.type); ++ for (i = 0; i < strescseq.len; i++) { ++ c = strescseq.buf[i] & 0xff; ++ if (c == '\0') { ++ putc('\n', stderr); ++ return; ++ } else if (isprint(c)) { ++ putc(c, stderr); ++ } else if (c == '\n') { ++ fprintf(stderr, "(\\n)"); ++ } else if (c == '\r') { ++ fprintf(stderr, "(\\r)"); ++ } else if (c == 0x1b) { ++ fprintf(stderr, "(\\e)"); ++ } else { ++ fprintf(stderr, "(%02x)", c); ++ } ++ } ++ fprintf(stderr, "ESC\\\n"); ++} ++ ++void ++strreset(void) ++{ ++ strescseq = (STREscape){ ++ .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), ++ .siz = STR_BUF_SIZ, ++ }; ++} ++ ++void ++sendbreak(const Arg *arg) ++{ ++ if (tcsendbreak(cmdfd, 0)) ++ perror("Error sending break"); ++} ++ ++void ++tprinter(char *s, size_t len) ++{ ++ if (iofd != -1 && xwrite(iofd, s, len) < 0) { ++ perror("Error writing to output file"); ++ close(iofd); ++ iofd = -1; ++ } ++} ++ ++void ++toggleprinter(const Arg *arg) ++{ ++ term.mode ^= MODE_PRINT; ++} ++ ++void ++printscreen(const Arg *arg) ++{ ++ tdump(); ++} ++ ++void ++printsel(const Arg *arg) ++{ ++ tdumpsel(); ++} ++ ++void ++tdumpsel(void) ++{ ++ char *ptr; ++ ++ if ((ptr = getsel())) { ++ tprinter(ptr, strlen(ptr)); ++ free(ptr); ++ } ++} ++ ++void ++tdumpline(int n) ++{ ++ char buf[UTF_SIZ]; ++ const Glyph *bp, *end; ++ ++ bp = &term.line[n][0]; ++ end = &bp[MIN(tlinelen(n), term.col) - 1]; ++ if (bp != end || bp->u != ' ') { ++ for ( ; bp <= end; ++bp) ++ tprinter(buf, utf8encode(bp->u, buf)); ++ } ++ tprinter("\n", 1); ++} ++ ++void ++tdump(void) ++{ ++ int i; ++ ++ for (i = 0; i < term.row; ++i) ++ tdumpline(i); ++} ++ ++void ++tputtab(int n) ++{ ++ uint x = term.c.x; ++ ++ if (n > 0) { ++ while (x < term.col && n--) ++ for (++x; x < term.col && !term.tabs[x]; ++x) ++ /* nothing */ ; ++ } else if (n < 0) { ++ while (x > 0 && n++) ++ for (--x; x > 0 && !term.tabs[x]; --x) ++ /* nothing */ ; ++ } ++ term.c.x = LIMIT(x, 0, term.col-1); ++} ++ ++void ++tdefutf8(char ascii) ++{ ++ if (ascii == 'G') ++ term.mode |= MODE_UTF8; ++ else if (ascii == '@') ++ term.mode &= ~MODE_UTF8; ++} ++ ++void ++tdeftran(char ascii) ++{ ++ static char cs[] = "0B"; ++ static int vcs[] = {CS_GRAPHIC0, CS_USA}; ++ char *p; ++ ++ if ((p = strchr(cs, ascii)) == NULL) { ++ fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); ++ } else { ++ term.trantbl[term.icharset] = vcs[p - cs]; ++ } ++} ++ ++void ++tdectest(char c) ++{ ++ int x, y; ++ ++ if (c == '8') { /* DEC screen alignment test. */ ++ for (x = 0; x < term.col; ++x) { ++ for (y = 0; y < term.row; ++y) ++ tsetchar('E', &term.c.attr, x, y); ++ } ++ } ++} ++ ++void ++tstrsequence(uchar c) ++{ ++ switch (c) { ++ case 0x90: /* DCS -- Device Control String */ ++ c = 'P'; ++ break; ++ case 0x9f: /* APC -- Application Program Command */ ++ c = '_'; ++ break; ++ case 0x9e: /* PM -- Privacy Message */ ++ c = '^'; ++ break; ++ case 0x9d: /* OSC -- Operating System Command */ ++ c = ']'; ++ break; ++ } ++ strreset(); ++ strescseq.type = c; ++ term.esc |= ESC_STR; ++} ++ ++void ++tcontrolcode(uchar ascii) ++{ ++ switch (ascii) { ++ case '\t': /* HT */ ++ tputtab(1); ++ return; ++ case '\b': /* BS */ ++ tmoveto(term.c.x-1, term.c.y); ++ return; ++ case '\r': /* CR */ ++ tmoveto(0, term.c.y); ++ return; ++ case '\f': /* LF */ ++ case '\v': /* VT */ ++ case '\n': /* LF */ ++ /* go to first col if the mode is set */ ++ tnewline(IS_SET(MODE_CRLF)); ++ return; ++ case '\a': /* BEL */ ++ if (term.esc & ESC_STR_END) { ++ /* backwards compatibility to xterm */ ++ strhandle(); ++ } else { ++ xbell(); ++ } ++ break; ++ case '\033': /* ESC */ ++ csireset(); ++ term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); ++ term.esc |= ESC_START; ++ return; ++ case '\016': /* SO (LS1 -- Locking shift 1) */ ++ case '\017': /* SI (LS0 -- Locking shift 0) */ ++ term.charset = 1 - (ascii - '\016'); ++ return; ++ case '\032': /* SUB */ ++ tsetchar('?', &term.c.attr, term.c.x, term.c.y); ++ /* FALLTHROUGH */ ++ case '\030': /* CAN */ ++ csireset(); ++ break; ++ case '\005': /* ENQ (IGNORED) */ ++ case '\000': /* NUL (IGNORED) */ ++ case '\021': /* XON (IGNORED) */ ++ case '\023': /* XOFF (IGNORED) */ ++ case 0177: /* DEL (IGNORED) */ ++ return; ++ case 0x80: /* TODO: PAD */ ++ case 0x81: /* TODO: HOP */ ++ case 0x82: /* TODO: BPH */ ++ case 0x83: /* TODO: NBH */ ++ case 0x84: /* TODO: IND */ ++ break; ++ case 0x85: /* NEL -- Next line */ ++ tnewline(1); /* always go to first col */ ++ break; ++ case 0x86: /* TODO: SSA */ ++ case 0x87: /* TODO: ESA */ ++ break; ++ case 0x88: /* HTS -- Horizontal tab stop */ ++ term.tabs[term.c.x] = 1; ++ break; ++ case 0x89: /* TODO: HTJ */ ++ case 0x8a: /* TODO: VTS */ ++ case 0x8b: /* TODO: PLD */ ++ case 0x8c: /* TODO: PLU */ ++ case 0x8d: /* TODO: RI */ ++ case 0x8e: /* TODO: SS2 */ ++ case 0x8f: /* TODO: SS3 */ ++ case 0x91: /* TODO: PU1 */ ++ case 0x92: /* TODO: PU2 */ ++ case 0x93: /* TODO: STS */ ++ case 0x94: /* TODO: CCH */ ++ case 0x95: /* TODO: MW */ ++ case 0x96: /* TODO: SPA */ ++ case 0x97: /* TODO: EPA */ ++ case 0x98: /* TODO: SOS */ ++ case 0x99: /* TODO: SGCI */ ++ break; ++ case 0x9a: /* DECID -- Identify Terminal */ ++ ttywrite(vtiden, strlen(vtiden), 0); ++ break; ++ case 0x9b: /* TODO: CSI */ ++ case 0x9c: /* TODO: ST */ ++ break; ++ case 0x90: /* DCS -- Device Control String */ ++ case 0x9d: /* OSC -- Operating System Command */ ++ case 0x9e: /* PM -- Privacy Message */ ++ case 0x9f: /* APC -- Application Program Command */ ++ tstrsequence(ascii); ++ return; ++ } ++ /* only CAN, SUB, \a and C1 chars interrupt a sequence */ ++ term.esc &= ~(ESC_STR_END|ESC_STR); ++} ++ ++/* ++ * returns 1 when the sequence is finished and it hasn't to read ++ * more characters for this sequence, otherwise 0 ++ */ ++int ++eschandle(uchar ascii) ++{ ++ switch (ascii) { ++ case '[': ++ term.esc |= ESC_CSI; ++ return 0; ++ case '#': ++ term.esc |= ESC_TEST; ++ return 0; ++ case '%': ++ term.esc |= ESC_UTF8; ++ return 0; ++ case 'P': /* DCS -- Device Control String */ ++ case '_': /* APC -- Application Program Command */ ++ case '^': /* PM -- Privacy Message */ ++ case ']': /* OSC -- Operating System Command */ ++ case 'k': /* old title set compatibility */ ++ tstrsequence(ascii); ++ return 0; ++ case 'n': /* LS2 -- Locking shift 2 */ ++ case 'o': /* LS3 -- Locking shift 3 */ ++ term.charset = 2 + (ascii - 'n'); ++ break; ++ case '(': /* GZD4 -- set primary charset G0 */ ++ case ')': /* G1D4 -- set secondary charset G1 */ ++ case '*': /* G2D4 -- set tertiary charset G2 */ ++ case '+': /* G3D4 -- set quaternary charset G3 */ ++ term.icharset = ascii - '('; ++ term.esc |= ESC_ALTCHARSET; ++ return 0; ++ case 'D': /* IND -- Linefeed */ ++ if (term.c.y == term.bot) { ++ tscrollup(term.top, 1, 1); ++ } else { ++ tmoveto(term.c.x, term.c.y+1); ++ } ++ break; ++ case 'E': /* NEL -- Next line */ ++ tnewline(1); /* always go to first col */ ++ break; ++ case 'H': /* HTS -- Horizontal tab stop */ ++ term.tabs[term.c.x] = 1; ++ break; ++ case 'M': /* RI -- Reverse index */ ++ if (term.c.y == term.top) { ++ tscrolldown(term.top, 1, 1); ++ } else { ++ tmoveto(term.c.x, term.c.y-1); ++ } ++ break; ++ case 'Z': /* DECID -- Identify Terminal */ ++ ttywrite(vtiden, strlen(vtiden), 0); ++ break; ++ case 'c': /* RIS -- Reset to initial state */ ++ treset(); ++ resettitle(); ++ xloadcols(); ++ break; ++ case '=': /* DECPAM -- Application keypad */ ++ xsetmode(1, MODE_APPKEYPAD); ++ break; ++ case '>': /* DECPNM -- Normal keypad */ ++ xsetmode(0, MODE_APPKEYPAD); ++ break; ++ case '7': /* DECSC -- Save Cursor */ ++ tcursor(CURSOR_SAVE); ++ break; ++ case '8': /* DECRC -- Restore Cursor */ ++ tcursor(CURSOR_LOAD); ++ break; ++ case '\\': /* ST -- String Terminator */ ++ if (term.esc & ESC_STR_END) ++ strhandle(); ++ break; ++ default: ++ fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", ++ (uchar) ascii, isprint(ascii)? ascii:'.'); ++ break; ++ } ++ return 1; ++} ++ ++void ++tputc(Rune u) ++{ ++ char c[UTF_SIZ]; ++ int control; ++ int width, len; ++ Glyph *gp; ++ ++ control = ISCONTROL(u); ++ if (u < 127 || !IS_SET(MODE_UTF8)) { ++ c[0] = u; ++ width = len = 1; ++ } else { ++ len = utf8encode(u, c); ++ if (!control && (width = wcwidth(u)) == -1) ++ width = 1; ++ } ++ ++ if (IS_SET(MODE_PRINT)) ++ tprinter(c, len); ++ ++ /* ++ * STR sequence must be checked before anything else ++ * because it uses all following characters until it ++ * receives a ESC, a SUB, a ST or any other C1 control ++ * character. ++ */ ++ if (term.esc & ESC_STR) { ++ if (u == '\a' || u == 030 || u == 032 || u == 033 || ++ ISCONTROLC1(u)) { ++ term.esc &= ~(ESC_START|ESC_STR); ++ term.esc |= ESC_STR_END; ++ goto check_control_code; ++ } ++ ++ if (strescseq.len+len >= strescseq.siz) { ++ /* ++ * Here is a bug in terminals. If the user never sends ++ * some code to stop the str or esc command, then st ++ * will stop responding. But this is better than ++ * silently failing with unknown characters. At least ++ * then users will report back. ++ * ++ * In the case users ever get fixed, here is the code: ++ */ ++ /* ++ * term.esc = 0; ++ * strhandle(); ++ */ ++ if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) ++ return; ++ strescseq.siz *= 2; ++ strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); ++ } ++ ++ memmove(&strescseq.buf[strescseq.len], c, len); ++ strescseq.len += len; ++ return; ++ } ++ ++check_control_code: ++ /* ++ * Actions of control codes must be performed as soon they arrive ++ * because they can be embedded inside a control sequence, and ++ * they must not cause conflicts with sequences. ++ */ ++ if (control) { ++ tcontrolcode(u); ++ /* ++ * control codes are not shown ever ++ */ ++ if (!term.esc) ++ term.lastc = 0; ++ return; ++ } else if (term.esc & ESC_START) { ++ if (term.esc & ESC_CSI) { ++ csiescseq.buf[csiescseq.len++] = u; ++ if (BETWEEN(u, 0x40, 0x7E) ++ || csiescseq.len >= \ ++ sizeof(csiescseq.buf)-1) { ++ term.esc = 0; ++ csiparse(); ++ csihandle(); ++ } ++ return; ++ } else if (term.esc & ESC_UTF8) { ++ tdefutf8(u); ++ } else if (term.esc & ESC_ALTCHARSET) { ++ tdeftran(u); ++ } else if (term.esc & ESC_TEST) { ++ tdectest(u); ++ } else { ++ if (!eschandle(u)) ++ return; ++ /* sequence already finished */ ++ } ++ term.esc = 0; ++ /* ++ * All characters which form part of a sequence are not ++ * printed ++ */ ++ return; ++ } ++ if (selected(term.c.x, term.c.y)) ++ selclear(); ++ ++ gp = &term.line[term.c.y][term.c.x]; ++ if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { ++ gp->mode |= ATTR_WRAP; ++ tnewline(1); ++ gp = &term.line[term.c.y][term.c.x]; ++ } ++ ++ if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) ++ memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); ++ ++ 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); ++ term.lastc = u; ++ ++ if (width == 2) { ++ gp->mode |= ATTR_WIDE; ++ if (term.c.x+1 < term.col) { ++ if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { ++ gp[2].u = ' '; ++ gp[2].mode &= ~ATTR_WDUMMY; ++ } ++ gp[1].u = '\0'; ++ gp[1].mode = ATTR_WDUMMY; ++ } ++ } ++ if (term.c.x+width < term.col) { ++ tmoveto(term.c.x+width, term.c.y); ++ } else { ++ term.c.state |= CURSOR_WRAPNEXT; ++ } ++} ++ ++int ++twrite(const char *buf, int buflen, int show_ctrl) ++{ ++ int charsize; ++ Rune u; ++ int n; ++ ++ for (n = 0; n < buflen; n += charsize) { ++ if (IS_SET(MODE_UTF8)) { ++ /* process a complete utf8 char */ ++ charsize = utf8decode(buf + n, &u, buflen - n); ++ if (charsize == 0) ++ break; ++ } else { ++ u = buf[n] & 0xFF; ++ charsize = 1; ++ } ++ if (show_ctrl && ISCONTROL(u)) { ++ if (u & 0x80) { ++ u &= 0x7f; ++ tputc('^'); ++ tputc('['); ++ } else if (u != '\n' && u != '\r' && u != '\t') { ++ u ^= 0x40; ++ tputc('^'); ++ } ++ } ++ tputc(u); ++ } ++ return n; ++} ++ ++void ++tresize(int col, int row) ++{ ++ int i, j; ++ int minrow = MIN(row, term.row); ++ int mincol = MIN(col, term.col); ++ int *bp; ++ TCursor c; ++ ++ if (col < 1 || row < 1) { ++ fprintf(stderr, ++ "tresize: error resizing to %dx%d\n", col, row); ++ return; ++ } ++ ++ /* ++ * slide screen to keep cursor where we expect it - ++ * tscrollup would work here, but we can optimize to ++ * memmove because we're freeing the earlier lines ++ */ ++ for (i = 0; i <= term.c.y - row; i++) { ++ free(term.line[i]); ++ free(term.alt[i]); ++ } ++ /* ensure that both src and dst are not NULL */ ++ if (i > 0) { ++ memmove(term.line, term.line + i, row * sizeof(Line)); ++ memmove(term.alt, term.alt + i, row * sizeof(Line)); ++ } ++ for (i += row; i < term.row; i++) { ++ free(term.line[i]); ++ free(term.alt[i]); ++ } ++ ++ /* resize to new height */ ++ term.line = xrealloc(term.line, row * sizeof(Line)); ++ term.alt = xrealloc(term.alt, row * sizeof(Line)); ++ term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); ++ term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); ++ ++ for (i = 0; i < HISTSIZE; i++) { ++ term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); ++ for (j = mincol; j < col; j++) { ++ term.hist[i][j] = term.c.attr; ++ term.hist[i][j].u = ' '; ++ } ++ } ++ ++ /* resize each row to new width, zero-pad if needed */ ++ for (i = 0; i < minrow; i++) { ++ term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); ++ term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); ++ } ++ ++ /* allocate any new rows */ ++ for (/* i = minrow */; i < row; i++) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ term.alt[i] = xmalloc(col * sizeof(Glyph)); ++ } ++ if (col > term.col) { ++ bp = term.tabs + term.col; ++ ++ memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); ++ while (--bp > term.tabs && !*bp) ++ /* nothing */ ; ++ for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) ++ *bp = 1; ++ } ++ /* update terminal size */ ++ term.col = col; ++ term.row = row; ++ /* reset scrolling region */ ++ tsetscroll(0, row-1); ++ /* make use of the LIMIT in tmoveto */ ++ tmoveto(term.c.x, term.c.y); ++ /* Clearing both screens (it makes dirty all lines) */ ++ c = term.c; ++ for (i = 0; i < 2; i++) { ++ if (mincol < col && 0 < minrow) { ++ tclearregion(mincol, 0, col - 1, minrow - 1); ++ } ++ if (0 < col && minrow < row) { ++ tclearregion(0, minrow, col - 1, row - 1); ++ } ++ tswapscreen(); ++ tcursor(CURSOR_LOAD); ++ } ++ term.c = c; ++} ++ ++void ++resettitle(void) ++{ ++ xsettitle(NULL); ++} ++ ++void ++drawregion(int x1, int y1, int x2, int y2) ++{ ++ int y; ++ ++ for (y = y1; y < y2; y++) { ++ if (!term.dirty[y]) ++ continue; ++ ++ term.dirty[y] = 0; ++ xdrawline(TLINE(y), x1, y, x2); ++ } ++} ++ ++void ++draw(void) ++{ ++ int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; ++ ++ if (!xstartdraw()) ++ return; ++ ++ /* adjust cursor position */ ++ LIMIT(term.ocx, 0, term.col-1); ++ LIMIT(term.ocy, 0, term.row-1); ++ if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) ++ term.ocx--; ++ if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) ++ cx--; ++ ++ drawregion(0, 0, term.col, term.row); ++ if (term.scr == 0) ++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ term.ocx = cx; ++ term.ocy = term.c.y; ++ xfinishdraw(); ++ if (ocx != term.ocx || ocy != term.ocy) ++ xximspot(term.ocx, term.ocy); ++} ++ ++void ++redraw(void) ++{ ++ tfulldirt(); ++ draw(); ++} +diff --color -urN st-0.9/st.h st-0.9-d/st.h --- st-0.9/st.h 2022-10-04 23:11:26.000000000 +0530 -+++ st-0.9-a/st.h 2022-11-25 02:12:19.385259029 +0530 -@@ -81,6 +81,8 @@ ++++ st-0.9-d/st.h 2022-11-25 20:37:17.096658798 +0530 +@@ -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 @@ + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, ++ ATTR_LIGA = 1 << 11, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +@@ -81,6 +83,8 @@ void redraw(void); void draw(void); @@ -484,7 +3433,7 @@ diff --color -ru st-0.9/st.h st-0.9-a/st.h void printscreen(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); -@@ -120,6 +122,7 @@ +@@ -120,6 +124,7 @@ extern int allowaltscreen; extern int allowwindowops; extern char *termname; @@ -492,10 +3441,30 @@ diff --color -ru st-0.9/st.h st-0.9-a/st.h extern unsigned int tabspaces; extern unsigned int defaultfg; extern unsigned int defaultbg; -diff --color -ru st-0.9/x.c st-0.9-a/x.c +diff --color -urN st-0.9/win.h st-0.9-d/win.h +--- st-0.9/win.h 2022-10-04 23:11:26.000000000 +0530 ++++ st-0.9-d/win.h 2022-11-25 20:37:17.096658798 +0530 +@@ -25,7 +25,7 @@ + + 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 --color -urN st-0.9/x.c st-0.9-d/x.c --- st-0.9/x.c 2022-10-04 23:11:26.000000000 +0530 -+++ st-0.9-a/x.c 2022-11-25 02:12:19.385259029 +0530 -@@ -55,6 +55,7 @@ ++++ st-0.9-d/x.c 2022-11-25 20:37:17.097658817 +0530 +@@ -19,6 +19,7 @@ + #include "arg.h" + #include "st.h" + #include "win.h" ++#include "hb.h" + + /* types used in config.h */ + typedef struct { +@@ -55,6 +56,7 @@ static void clippaste(const Arg *); static void numlock(const Arg *); static void selpaste(const Arg *); @@ -503,7 +3472,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c static void zoom(const Arg *); static void zoomabs(const Arg *); static void zoomreset(const Arg *); -@@ -157,6 +158,7 @@ +@@ -157,6 +159,7 @@ static int xloadcolor(int, const char *, Color *); static int xloadfont(Font *, FcPattern *); static void xloadfonts(const char *, double); @@ -511,7 +3480,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c static void xunloadfont(Font *); static void xunloadfonts(void); static void xsetenv(void); -@@ -254,6 +256,8 @@ +@@ -254,6 +257,8 @@ static uint buttons; /* bit field of pressed buttons */ @@ -520,7 +3489,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c void clipcopy(const Arg *dummy) { -@@ -293,6 +297,14 @@ +@@ -293,6 +298,14 @@ } void @@ -535,7 +3504,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c zoom(const Arg *arg) { Arg larg; -@@ -306,6 +318,7 @@ +@@ -306,6 +319,7 @@ { xunloadfonts(); xloadfonts(usedfont, arg->f); @@ -543,7 +3512,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c cresize(0, 0); redraw(); xhints(); -@@ -766,6 +779,11 @@ +@@ -766,6 +780,11 @@ return x == 0 ? 0 : 0x3737 + 0x2828 * x; } @@ -555,7 +3524,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c int xloadcolor(int i, const char *name, Color *ncolor) { -@@ -784,7 +802,7 @@ +@@ -784,7 +803,7 @@ return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, ncolor); } else @@ -564,7 +3533,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c } return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); -@@ -807,8 +825,8 @@ +@@ -807,8 +826,8 @@ for (i = 0; i < dc.collen; i++) if (!xloadcolor(i, NULL, &dc.col[i])) { @@ -575,7 +3544,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c else die("could not allocate color %d\n", i); } -@@ -1050,6 +1068,67 @@ +@@ -1050,6 +1069,67 @@ FcPatternDestroy(pattern); } @@ -643,7 +3612,17 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c void xunloadfont(Font *f) { -@@ -1147,6 +1226,10 @@ +@@ -1062,6 +1142,9 @@ + 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); +@@ -1147,6 +1230,10 @@ usedfont = (opt_font == NULL)? font : opt_font; xloadfonts(usedfont, 0); @@ -654,7 +3633,7 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xloadcols(); -@@ -1200,13 +1283,13 @@ +@@ -1200,13 +1287,13 @@ cursor = XCreateFontCursor(xw.dpy, mouseshape); XDefineCursor(xw.dpy, xw.win, cursor); @@ -670,7 +3649,26 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c xmousebg.red = 0x0000; xmousebg.green = 0x0000; xmousebg.blue = 0x0000; -@@ -1414,7 +1497,7 @@ +@@ -1261,7 +1348,7 @@ + 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. */ +@@ -1368,6 +1455,9 @@ + numspecs++; + } + ++ /* Harfbuzz transformation for ligatures. */ ++ hbtransform(specs, glyphs, len, x, y); ++ + return numspecs; + } + +@@ -1414,7 +1504,7 @@ /* 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)) @@ -679,7 +3677,27 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c if (IS_SET(MODE_REVERSE)) { if (fg == &dc.col[defaultfg]) { -@@ -1642,6 +1725,8 @@ +@@ -1517,14 +1607,17 @@ + } + + 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; +@@ -1642,6 +1735,8 @@ int xstartdraw(void) { @@ -688,3 +3706,2188 @@ diff --color -ru st-0.9/x.c st-0.9-a/x.c return IS_SET(MODE_VISIBLE); } +diff --color -urN st-0.9/x.c.orig st-0.9-d/x.c.orig +--- st-0.9/x.c.orig 1970-01-01 05:30:00.000000000 +0530 ++++ st-0.9-d/x.c.orig 2022-11-25 20:37:13.108583267 +0530 +@@ -0,0 +1,2181 @@ ++/* See LICENSE for license details. */ ++#include <errno.h> ++#include <math.h> ++#include <limits.h> ++#include <locale.h> ++#include <signal.h> ++#include <sys/select.h> ++#include <time.h> ++#include <unistd.h> ++#include <libgen.h> ++#include <X11/Xatom.h> ++#include <X11/Xlib.h> ++#include <X11/cursorfont.h> ++#include <X11/keysym.h> ++#include <X11/Xft/Xft.h> ++#include <X11/XKBlib.h> ++ ++char *argv0; ++#include "arg.h" ++#include "st.h" ++#include "win.h" ++ ++/* types used in config.h */ ++typedef struct { ++ uint mod; ++ KeySym keysym; ++ void (*func)(const Arg *); ++ const Arg arg; ++} Shortcut; ++ ++typedef struct { ++ uint mod; ++ uint button; ++ void (*func)(const Arg *); ++ const Arg arg; ++ uint release; ++} MouseShortcut; ++ ++typedef struct { ++ KeySym k; ++ uint mask; ++ char *s; ++ /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ ++ signed char appkey; /* application keypad */ ++ signed char appcursor; /* application cursor */ ++} Key; ++ ++/* X modifiers */ ++#define XK_ANY_MOD UINT_MAX ++#define XK_NO_MOD 0 ++#define XK_SWITCH_MOD (1<<13|1<<14) ++ ++/* function definitions used in config.h */ ++static void clipcopy(const Arg *); ++static void clippaste(const Arg *); ++static void numlock(const Arg *); ++static void selpaste(const Arg *); ++static void swapcolors(const Arg *); ++static void zoom(const Arg *); ++static void zoomabs(const Arg *); ++static void zoomreset(const Arg *); ++static void ttysend(const Arg *); ++ ++/* config.h for applying patches and the configuration. */ ++#include "config.h" ++ ++/* XEMBED messages */ ++#define XEMBED_FOCUS_IN 4 ++#define XEMBED_FOCUS_OUT 5 ++ ++/* macros */ ++#define IS_SET(flag) ((win.mode & (flag)) != 0) ++#define TRUERED(x) (((x) & 0xff0000) >> 8) ++#define TRUEGREEN(x) (((x) & 0xff00)) ++#define TRUEBLUE(x) (((x) & 0xff) << 8) ++ ++typedef XftDraw *Draw; ++typedef XftColor Color; ++typedef XftGlyphFontSpec GlyphFontSpec; ++ ++/* Purely graphic info */ ++typedef struct { ++ int tw, th; /* tty width and height */ ++ int w, h; /* window width and height */ ++ int ch; /* char height */ ++ int cw; /* char width */ ++ int mode; /* window state/mode flags */ ++ int cursor; /* cursor style */ ++} TermWindow; ++ ++typedef struct { ++ Display *dpy; ++ Colormap cmap; ++ Window win; ++ Drawable buf; ++ GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ ++ Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; ++ struct { ++ XIM xim; ++ XIC xic; ++ XPoint spot; ++ XVaNestedList spotlist; ++ } ime; ++ Draw draw; ++ Visual *vis; ++ XSetWindowAttributes attrs; ++ int scr; ++ int isfixed; /* is fixed geometry? */ ++ int l, t; /* left and top offset */ ++ int gm; /* geometry mask */ ++} XWindow; ++ ++typedef struct { ++ Atom xtarget; ++ char *primary, *clipboard; ++ struct timespec tclick1; ++ struct timespec tclick2; ++} XSelection; ++ ++/* Font structure */ ++#define Font Font_ ++typedef struct { ++ int height; ++ int width; ++ int ascent; ++ int descent; ++ int badslant; ++ int badweight; ++ short lbearing; ++ short rbearing; ++ XftFont *match; ++ FcFontSet *set; ++ FcPattern *pattern; ++} Font; ++ ++/* Drawing Context */ ++typedef struct { ++ Color *col; ++ size_t collen; ++ Font font, bfont, ifont, ibfont; ++ GC gc; ++} DC; ++ ++static inline ushort sixd_to_16bit(int); ++static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); ++static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); ++static void xdrawglyph(Glyph, int, int); ++static void xclear(int, int, int, int); ++static int xgeommasktogravity(int); ++static int ximopen(Display *); ++static void ximinstantiate(Display *, XPointer, XPointer); ++static void ximdestroy(XIM, XPointer, XPointer); ++static int xicdestroy(XIC, XPointer, XPointer); ++static void xinit(int, int); ++static void cresize(int, int); ++static void xresize(int, int); ++static void xhints(void); ++static int xloadcolor(int, const char *, Color *); ++static int xloadfont(Font *, FcPattern *); ++static void xloadfonts(const char *, double); ++static void xloadsparefont(); ++static void xunloadfont(Font *); ++static void xunloadfonts(void); ++static void xsetenv(void); ++static void xseturgency(int); ++static int evcol(XEvent *); ++static int evrow(XEvent *); ++ ++static void expose(XEvent *); ++static void visibility(XEvent *); ++static void unmap(XEvent *); ++static void kpress(XEvent *); ++static void cmessage(XEvent *); ++static void resize(XEvent *); ++static void focus(XEvent *); ++static uint buttonmask(uint); ++static int mouseaction(XEvent *, uint); ++static void brelease(XEvent *); ++static void bpress(XEvent *); ++static void bmotion(XEvent *); ++static void propnotify(XEvent *); ++static void selnotify(XEvent *); ++static void selclear_(XEvent *); ++static void selrequest(XEvent *); ++static void setsel(char *, Time); ++static void mousesel(XEvent *, int); ++static void mousereport(XEvent *); ++static char *kmap(KeySym, uint); ++static int match(uint, uint); ++ ++static void run(void); ++static void usage(void); ++ ++static void (*handler[LASTEvent])(XEvent *) = { ++ [KeyPress] = kpress, ++ [ClientMessage] = cmessage, ++ [ConfigureNotify] = resize, ++ [VisibilityNotify] = visibility, ++ [UnmapNotify] = unmap, ++ [Expose] = expose, ++ [FocusIn] = focus, ++ [FocusOut] = focus, ++ [MotionNotify] = bmotion, ++ [ButtonPress] = bpress, ++ [ButtonRelease] = brelease, ++/* ++ * Uncomment if you want the selection to disappear when you select something ++ * different in another window. ++ */ ++/* [SelectionClear] = selclear_, */ ++ [SelectionNotify] = selnotify, ++/* ++ * PropertyNotify is only turned on when there is some INCR transfer happening ++ * for the selection retrieval. ++ */ ++ [PropertyNotify] = propnotify, ++ [SelectionRequest] = selrequest, ++}; ++ ++/* Globals */ ++static DC dc; ++static XWindow xw; ++static XSelection xsel; ++static TermWindow win; ++ ++/* Font Ring Cache */ ++enum { ++ FRC_NORMAL, ++ FRC_ITALIC, ++ FRC_BOLD, ++ FRC_ITALICBOLD ++}; ++ ++typedef struct { ++ XftFont *font; ++ int flags; ++ Rune unicodep; ++} Fontcache; ++ ++/* Fontcache is an array now. A new font will be appended to the array. */ ++static Fontcache *frc = NULL; ++static int frclen = 0; ++static int frccap = 0; ++static char *usedfont = NULL; ++static double usedfontsize = 0; ++static double defaultfontsize = 0; ++ ++static char *opt_class = NULL; ++static char **opt_cmd = NULL; ++static char *opt_embed = NULL; ++static char *opt_font = NULL; ++static char *opt_io = NULL; ++static char *opt_line = NULL; ++static char *opt_name = NULL; ++static char *opt_title = NULL; ++ ++static uint buttons; /* bit field of pressed buttons */ ++ ++int usealtcolors = 0; /* 1 to use alternate palette */ ++ ++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 ++swapcolors(const Arg *dummy) ++{ ++ usealtcolors = !usealtcolors; ++ xloadcols(); ++ redraw(); ++} ++ ++void ++zoom(const Arg *arg) ++{ ++ Arg larg; ++ ++ larg.f = usedfontsize + arg->f; ++ zoomabs(&larg); ++} ++ ++void ++zoomabs(const Arg *arg) ++{ ++ xunloadfonts(); ++ xloadfonts(usedfont, arg->f); ++ xloadsparefont(); ++ cresize(0, 0); ++ redraw(); ++ xhints(); ++} ++ ++void ++zoomreset(const Arg *arg) ++{ ++ Arg larg; ++ ++ if (defaultfontsize > 0) { ++ larg.f = defaultfontsize; ++ zoomabs(&larg); ++ } ++} ++ ++void ++ttysend(const Arg *arg) ++{ ++ ttywrite(arg->s, strlen(arg->s), 1); ++} ++ ++int ++evcol(XEvent *e) ++{ ++ int x = e->xbutton.x - borderpx; ++ LIMIT(x, 0, win.tw - 1); ++ return x / win.cw; ++} ++ ++int ++evrow(XEvent *e) ++{ ++ int y = e->xbutton.y - borderpx; ++ LIMIT(y, 0, win.th - 1); ++ return y / win.ch; ++} ++ ++void ++mousesel(XEvent *e, int done) ++{ ++ int type, seltype = SEL_REGULAR; ++ uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); ++ ++ for (type = 1; type < LEN(selmasks); ++type) { ++ if (match(selmasks[type], state)) { ++ seltype = type; ++ break; ++ } ++ } ++ selextend(evcol(e), evrow(e), seltype, done); ++ if (done) ++ setsel(getsel(), e->xbutton.time); ++} ++ ++void ++mousereport(XEvent *e) ++{ ++ int len, btn, code; ++ int x = evcol(e), y = evrow(e); ++ int state = e->xbutton.state; ++ char buf[40]; ++ static int ox, oy; ++ ++ if (e->type == MotionNotify) { ++ if (x == ox && y == oy) ++ return; ++ if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) ++ return; ++ /* MODE_MOUSEMOTION: no reporting if no button is pressed */ ++ if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) ++ return; ++ /* Set btn to lowest-numbered pressed button, or 12 if no ++ * buttons are pressed. */ ++ for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) ++ ; ++ code = 32; ++ } else { ++ btn = e->xbutton.button; ++ /* Only buttons 1 through 11 can be encoded */ ++ if (btn < 1 || btn > 11) ++ return; ++ if (e->type == ButtonRelease) { ++ /* MODE_MOUSEX10: no button release reporting */ ++ if (IS_SET(MODE_MOUSEX10)) ++ return; ++ /* Don't send release events for the scroll wheel */ ++ if (btn == 4 || btn == 5) ++ return; ++ } ++ code = 0; ++ } ++ ++ ox = x; ++ oy = y; ++ ++ /* Encode btn into code. If no button is pressed for a motion event in ++ * MODE_MOUSEMANY, then encode it as a release. */ ++ if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) ++ code += 3; ++ else if (btn >= 8) ++ code += 128 + btn - 8; ++ else if (btn >= 4) ++ code += 64 + btn - 4; ++ else ++ code += btn - 1; ++ ++ if (!IS_SET(MODE_MOUSEX10)) { ++ code += ((state & ShiftMask ) ? 4 : 0) ++ + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ ++ + ((state & ControlMask) ? 16 : 0); ++ } ++ ++ if (IS_SET(MODE_MOUSESGR)) { ++ len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", ++ code, x+1, y+1, ++ e->type == ButtonRelease ? 'm' : 'M'); ++ } else if (x < 223 && y < 223) { ++ len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", ++ 32+code, 32+x+1, 32+y+1); ++ } else { ++ return; ++ } ++ ++ ttywrite(buf, len, 0); ++} ++ ++uint ++buttonmask(uint button) ++{ ++ return button == Button1 ? Button1Mask ++ : button == Button2 ? Button2Mask ++ : button == Button3 ? Button3Mask ++ : button == Button4 ? Button4Mask ++ : button == Button5 ? Button5Mask ++ : 0; ++} ++ ++int ++mouseaction(XEvent *e, uint release) ++{ ++ MouseShortcut *ms; ++ ++ /* ignore Button<N>mask for Button<N> - it's set on release */ ++ uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); ++ ++ for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { ++ if (ms->release == release && ++ ms->button == e->xbutton.button && ++ (match(ms->mod, state) || /* exact or forced */ ++ match(ms->mod, state & ~forcemousemod))) { ++ ms->func(&(ms->arg)); ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++void ++bpress(XEvent *e) ++{ ++ int btn = e->xbutton.button; ++ struct timespec now; ++ int snap; ++ ++ if (1 <= btn && btn <= 11) ++ buttons |= 1 << (btn-1); ++ ++ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { ++ mousereport(e); ++ return; ++ } ++ ++ if (mouseaction(e, 0)) ++ return; ++ ++ if (btn == Button1) { ++ /* ++ * If the user clicks below predefined timeouts specific ++ * snapping behaviour is exposed. ++ */ ++ clock_gettime(CLOCK_MONOTONIC, &now); ++ if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { ++ snap = SNAP_LINE; ++ } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { ++ snap = SNAP_WORD; ++ } else { ++ snap = 0; ++ } ++ xsel.tclick2 = xsel.tclick1; ++ xsel.tclick1 = now; ++ ++ selstart(evcol(e), evrow(e), snap); ++ } ++} ++ ++void ++propnotify(XEvent *e) ++{ ++ XPropertyEvent *xpev; ++ Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); ++ ++ xpev = &e->xproperty; ++ if (xpev->state == PropertyNewValue && ++ (xpev->atom == XA_PRIMARY || ++ xpev->atom == clipboard)) { ++ selnotify(e); ++ } ++} ++ ++void ++selnotify(XEvent *e) ++{ ++ ulong nitems, ofs, rem; ++ int format; ++ uchar *data, *last, *repl; ++ Atom type, incratom, property = None; ++ ++ incratom = XInternAtom(xw.dpy, "INCR", 0); ++ ++ ofs = 0; ++ if (e->type == SelectionNotify) ++ property = e->xselection.property; ++ else if (e->type == PropertyNotify) ++ property = e->xproperty.atom; ++ ++ if (property == None) ++ return; ++ ++ do { ++ if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, ++ BUFSIZ/4, False, AnyPropertyType, ++ &type, &format, &nitems, &rem, ++ &data)) { ++ fprintf(stderr, "Clipboard allocation failed\n"); ++ return; ++ } ++ ++ if (e->type == PropertyNotify && nitems == 0 && rem == 0) { ++ /* ++ * If there is some PropertyNotify with no data, then ++ * this is the signal of the selection owner that all ++ * data has been transferred. We won't need to receive ++ * PropertyNotify events anymore. ++ */ ++ MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); ++ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, ++ &xw.attrs); ++ } ++ ++ if (type == incratom) { ++ /* ++ * Activate the PropertyNotify events so we receive ++ * when the selection owner does send us the next ++ * chunk of data. ++ */ ++ MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); ++ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, ++ &xw.attrs); ++ ++ /* ++ * Deleting the property is the transfer start signal. ++ */ ++ XDeleteProperty(xw.dpy, xw.win, (int)property); ++ continue; ++ } ++ ++ /* ++ * As seen in getsel: ++ * Line endings are inconsistent in the terminal and GUI world ++ * copy and pasting. When receiving some selection data, ++ * replace all '\n' with '\r'. ++ * FIXME: Fix the computer world. ++ */ ++ repl = data; ++ last = data + nitems * format / 8; ++ while ((repl = memchr(repl, '\n', last - repl))) { ++ *repl++ = '\r'; ++ } ++ ++ if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) ++ ttywrite("\033[200~", 6, 0); ++ ttywrite((char *)data, nitems * format / 8, 1); ++ if (IS_SET(MODE_BRCKTPASTE) && rem == 0) ++ ttywrite("\033[201~", 6, 0); ++ XFree(data); ++ /* number of 32-bit chunks returned */ ++ ofs += nitems * format / 32; ++ } while (rem > 0); ++ ++ /* ++ * Deleting the property again tells the selection owner to send the ++ * next data chunk in the property. ++ */ ++ XDeleteProperty(xw.dpy, xw.win, (int)property); ++} ++ ++void ++xclipcopy(void) ++{ ++ clipcopy(NULL); ++} ++ ++void ++selclear_(XEvent *e) ++{ ++ selclear(); ++} ++ ++void ++selrequest(XEvent *e) ++{ ++ XSelectionRequestEvent *xsre; ++ XSelectionEvent xev; ++ Atom xa_targets, string, clipboard; ++ char *seltext; ++ ++ xsre = (XSelectionRequestEvent *) e; ++ xev.type = SelectionNotify; ++ xev.requestor = xsre->requestor; ++ xev.selection = xsre->selection; ++ xev.target = xsre->target; ++ xev.time = xsre->time; ++ if (xsre->property == None) ++ xsre->property = xsre->target; ++ ++ /* reject */ ++ xev.property = None; ++ ++ xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); ++ if (xsre->target == xa_targets) { ++ /* respond with the supported type */ ++ string = xsel.xtarget; ++ XChangeProperty(xsre->display, xsre->requestor, xsre->property, ++ XA_ATOM, 32, PropModeReplace, ++ (uchar *) &string, 1); ++ xev.property = xsre->property; ++ } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { ++ /* ++ * xith XA_STRING non ascii characters may be incorrect in the ++ * requestor. It is not our problem, use utf8. ++ */ ++ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); ++ if (xsre->selection == XA_PRIMARY) { ++ seltext = xsel.primary; ++ } else if (xsre->selection == clipboard) { ++ seltext = xsel.clipboard; ++ } else { ++ fprintf(stderr, ++ "Unhandled clipboard selection 0x%lx\n", ++ xsre->selection); ++ return; ++ } ++ if (seltext != NULL) { ++ XChangeProperty(xsre->display, xsre->requestor, ++ xsre->property, xsre->target, ++ 8, PropModeReplace, ++ (uchar *)seltext, strlen(seltext)); ++ xev.property = xsre->property; ++ } ++ } ++ ++ /* all done, send a notification to the listener */ ++ if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) ++ fprintf(stderr, "Error sending SelectionNotify event\n"); ++} ++ ++void ++setsel(char *str, Time t) ++{ ++ if (!str) ++ return; ++ ++ free(xsel.primary); ++ xsel.primary = str; ++ ++ XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); ++ if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) ++ selclear(); ++} ++ ++void ++xsetsel(char *str) ++{ ++ setsel(str, CurrentTime); ++} ++ ++void ++brelease(XEvent *e) ++{ ++ int btn = e->xbutton.button; ++ ++ if (1 <= btn && btn <= 11) ++ buttons &= ~(1 << (btn-1)); ++ ++ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { ++ mousereport(e); ++ return; ++ } ++ ++ if (mouseaction(e, 1)) ++ return; ++ if (btn == Button1) ++ mousesel(e, 1); ++} ++ ++void ++bmotion(XEvent *e) ++{ ++ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { ++ mousereport(e); ++ return; ++ } ++ ++ mousesel(e, 0); ++} ++ ++void ++cresize(int width, int height) ++{ ++ int col, row; ++ ++ if (width != 0) ++ win.w = width; ++ if (height != 0) ++ win.h = height; ++ ++ col = (win.w - 2 * borderpx) / win.cw; ++ row = (win.h - 2 * borderpx) / win.ch; ++ col = MAX(1, col); ++ row = MAX(1, row); ++ ++ tresize(col, row); ++ xresize(col, row); ++ ttyresize(win.tw, win.th); ++} ++ ++void ++xresize(int col, int row) ++{ ++ win.tw = col * win.cw; ++ win.th = row * win.ch; ++ ++ XFreePixmap(xw.dpy, xw.buf); ++ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, ++ DefaultDepth(xw.dpy, xw.scr)); ++ XftDrawChange(xw.draw, xw.buf); ++ xclear(0, 0, win.w, win.h); ++ ++ /* resize to new width */ ++ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); ++} ++ ++ushort ++sixd_to_16bit(int x) ++{ ++ return x == 0 ? 0 : 0x3737 + 0x2828 * x; ++} ++ ++const char* getcolorname(int i) ++{ ++ return (usealtcolors) ? altcolorname[i] : colorname[i]; ++} ++ ++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 = getcolorname(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 (getcolorname(i)) ++ die("could not allocate color '%s'\n", getcolorname(i)); ++ else ++ die("could not allocate color %d\n", i); ++ } ++ loaded = 1; ++} ++ ++int ++xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) ++{ ++ if (!BETWEEN(x, 0, dc.collen)) ++ return 1; ++ ++ *r = dc.col[x].color.red >> 8; ++ *g = dc.col[x].color.green >> 8; ++ *b = dc.col[x].color.blue >> 8; ++ ++ return 0; ++} ++ ++int ++xsetcolorname(int x, const char *name) ++{ ++ Color ncolor; ++ ++ if (!BETWEEN(x, 0, dc.collen)) ++ return 1; ++ ++ if (!xloadcolor(x, name, &ncolor)) ++ return 1; ++ ++ XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); ++ dc.col[x] = ncolor; ++ ++ return 0; ++} ++ ++/* ++ * Absolute coordinates. ++ */ ++void ++xclear(int x1, int y1, int x2, int y2) ++{ ++ XftDrawRect(xw.draw, ++ &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], ++ x1, y1, x2-x1, y2-y1); ++} ++ ++void ++xhints(void) ++{ ++ XClassHint class = {opt_name ? opt_name : termname, ++ opt_class ? opt_class : termname}; ++ XWMHints wm = {.flags = InputHint, .input = 1}; ++ XSizeHints *sizeh; ++ ++ sizeh = XAllocSizeHints(); ++ ++ sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; ++ sizeh->height = win.h; ++ sizeh->width = win.w; ++ sizeh->height_inc = win.ch; ++ sizeh->width_inc = win.cw; ++ sizeh->base_height = 2 * borderpx; ++ sizeh->base_width = 2 * borderpx; ++ sizeh->min_height = win.ch + 2 * borderpx; ++ sizeh->min_width = win.cw + 2 * borderpx; ++ if (xw.isfixed) { ++ sizeh->flags |= PMaxSize; ++ sizeh->min_width = sizeh->max_width = win.w; ++ sizeh->min_height = sizeh->max_height = win.h; ++ } ++ if (xw.gm & (XValue|YValue)) { ++ sizeh->flags |= USPosition | PWinGravity; ++ sizeh->x = xw.l; ++ sizeh->y = xw.t; ++ sizeh->win_gravity = xgeommasktogravity(xw.gm); ++ } ++ ++ XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, ++ &class); ++ XFree(sizeh); ++} ++ ++int ++xgeommasktogravity(int mask) ++{ ++ switch (mask & (XNegative|YNegative)) { ++ case 0: ++ return NorthWestGravity; ++ case XNegative: ++ return NorthEastGravity; ++ case YNegative: ++ return SouthWestGravity; ++ } ++ ++ return SouthEastGravity; ++} ++ ++int ++xloadfont(Font *f, FcPattern *pattern) ++{ ++ FcPattern *configured; ++ FcPattern *match; ++ FcResult result; ++ XGlyphInfo extents; ++ int wantattr, haveattr; ++ ++ /* ++ * Manually configure instead of calling XftMatchFont ++ * so that we can use the configured pattern for ++ * "missing glyph" lookups. ++ */ ++ configured = FcPatternDuplicate(pattern); ++ if (!configured) ++ return 1; ++ ++ FcConfigSubstitute(NULL, configured, FcMatchPattern); ++ XftDefaultSubstitute(xw.dpy, xw.scr, configured); ++ ++ match = FcFontMatch(NULL, configured, &result); ++ if (!match) { ++ FcPatternDestroy(configured); ++ return 1; ++ } ++ ++ if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { ++ FcPatternDestroy(configured); ++ FcPatternDestroy(match); ++ return 1; ++ } ++ ++ if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == ++ XftResultMatch)) { ++ /* ++ * Check if xft was unable to find a font with the appropriate ++ * slant but gave us one anyway. Try to mitigate. ++ */ ++ if ((XftPatternGetInteger(f->match->pattern, "slant", 0, ++ &haveattr) != XftResultMatch) || haveattr < wantattr) { ++ f->badslant = 1; ++ fputs("font slant does not match\n", stderr); ++ } ++ } ++ ++ if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == ++ XftResultMatch)) { ++ if ((XftPatternGetInteger(f->match->pattern, "weight", 0, ++ &haveattr) != XftResultMatch) || haveattr != wantattr) { ++ f->badweight = 1; ++ fputs("font weight does not match\n", stderr); ++ } ++ } ++ ++ XftTextExtentsUtf8(xw.dpy, f->match, ++ (const FcChar8 *) ascii_printable, ++ strlen(ascii_printable), &extents); ++ ++ f->set = NULL; ++ f->pattern = configured; ++ ++ f->ascent = f->match->ascent; ++ f->descent = f->match->descent; ++ f->lbearing = 0; ++ f->rbearing = f->match->max_advance_width; ++ ++ f->height = f->ascent + f->descent; ++ f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); ++ ++ return 0; ++} ++ ++void ++xloadfonts(const char *fontstr, double fontsize) ++{ ++ FcPattern *pattern; ++ double fontval; ++ ++ if (fontstr[0] == '-') ++ pattern = XftXlfdParse(fontstr, False, False); ++ else ++ pattern = FcNameParse((const FcChar8 *)fontstr); ++ ++ if (!pattern) ++ die("can't open font %s\n", fontstr); ++ ++ if (fontsize > 1) { ++ FcPatternDel(pattern, FC_PIXEL_SIZE); ++ FcPatternDel(pattern, FC_SIZE); ++ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); ++ usedfontsize = fontsize; ++ } else { ++ if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == ++ FcResultMatch) { ++ usedfontsize = fontval; ++ } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == ++ FcResultMatch) { ++ usedfontsize = -1; ++ } else { ++ /* ++ * Default font size is 12, if none given. This is to ++ * have a known usedfontsize value. ++ */ ++ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); ++ usedfontsize = 12; ++ } ++ defaultfontsize = usedfontsize; ++ } ++ ++ if (xloadfont(&dc.font, pattern)) ++ die("can't open font %s\n", fontstr); ++ ++ if (usedfontsize < 0) { ++ FcPatternGetDouble(dc.font.match->pattern, ++ FC_PIXEL_SIZE, 0, &fontval); ++ usedfontsize = fontval; ++ if (fontsize == 0) ++ defaultfontsize = fontval; ++ } ++ ++ /* Setting character width and height. */ ++ win.cw = ceilf(dc.font.width * cwscale); ++ win.ch = ceilf(dc.font.height * chscale); ++ ++ FcPatternDel(pattern, FC_SLANT); ++ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); ++ if (xloadfont(&dc.ifont, pattern)) ++ die("can't open font %s\n", fontstr); ++ ++ FcPatternDel(pattern, FC_WEIGHT); ++ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); ++ if (xloadfont(&dc.ibfont, pattern)) ++ die("can't open font %s\n", fontstr); ++ ++ FcPatternDel(pattern, FC_SLANT); ++ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); ++ if (xloadfont(&dc.bfont, pattern)) ++ die("can't open font %s\n", fontstr); ++ ++ FcPatternDestroy(pattern); ++} ++ ++ void ++xloadsparefont() ++{ ++FcPattern *fontpattern, *match; ++FcResult result; ++ ++/* add font2 to font cache as first 4 entries */ ++if ( font2[0] == '-' ) ++ fontpattern = XftXlfdParse(font2, False, False); ++else ++ fontpattern = FcNameParse((FcChar8 *)font2); ++if ( fontpattern ) { ++ /* Allocate memory for the new cache entries. */ ++ frccap += 4; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* add Normal */ ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_NORMAL; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ /* add Italic */ ++ FcPatternDel(fontpattern, FC_SLANT); ++ FcPatternAddInteger(fontpattern, FC_SLANT, FC_SLANT_ITALIC); ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_ITALIC; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ /* add Italic Bold */ ++ FcPatternDel(fontpattern, FC_WEIGHT); ++ FcPatternAddInteger(fontpattern, FC_WEIGHT, FC_WEIGHT_BOLD); ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_ITALICBOLD; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ /* add Bold */ ++ FcPatternDel(fontpattern, FC_SLANT); ++ FcPatternAddInteger(fontpattern, FC_SLANT, FC_SLANT_ROMAN); ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_BOLD; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ FcPatternDestroy(fontpattern); ++ } ++} ++ ++void ++xunloadfont(Font *f) ++{ ++ XftFontClose(xw.dpy, f->match); ++ FcPatternDestroy(f->pattern); ++ if (f->set) ++ FcFontSetDestroy(f->set); ++} ++ ++void ++xunloadfonts(void) ++{ ++ /* Free the loaded fonts in the font cache. */ ++ while (frclen > 0) ++ XftFontClose(xw.dpy, frc[--frclen].font); ++ ++ xunloadfont(&dc.font); ++ xunloadfont(&dc.bfont); ++ xunloadfont(&dc.ifont); ++ xunloadfont(&dc.ibfont); ++} ++ ++int ++ximopen(Display *dpy) ++{ ++ XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; ++ XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; ++ ++ xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); ++ if (xw.ime.xim == NULL) ++ return 0; ++ ++ if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) ++ fprintf(stderr, "XSetIMValues: " ++ "Could not set XNDestroyCallback.\n"); ++ ++ xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, ++ NULL); ++ ++ if (xw.ime.xic == NULL) { ++ xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, ++ XIMPreeditNothing | XIMStatusNothing, ++ XNClientWindow, xw.win, ++ XNDestroyCallback, &icdestroy, ++ NULL); ++ } ++ if (xw.ime.xic == NULL) ++ fprintf(stderr, "XCreateIC: Could not create input context.\n"); ++ ++ return 1; ++} ++ ++void ++ximinstantiate(Display *dpy, XPointer client, XPointer call) ++{ ++ if (ximopen(dpy)) ++ XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ++ ximinstantiate, NULL); ++} ++ ++void ++ximdestroy(XIM xim, XPointer client, XPointer call) ++{ ++ xw.ime.xim = NULL; ++ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, ++ ximinstantiate, NULL); ++ XFree(xw.ime.spotlist); ++} ++ ++int ++xicdestroy(XIC xim, XPointer client, XPointer call) ++{ ++ xw.ime.xic = NULL; ++ return 1; ++} ++ ++void ++xinit(int cols, int rows) ++{ ++ XGCValues gcvalues; ++ Cursor cursor; ++ Window parent; ++ pid_t thispid = getpid(); ++ XColor xmousefg, xmousebg; ++ ++ if (!(xw.dpy = XOpenDisplay(NULL))) ++ die("can't open display\n"); ++ xw.scr = XDefaultScreen(xw.dpy); ++ xw.vis = XDefaultVisual(xw.dpy, xw.scr); ++ ++ /* font */ ++ if (!FcInit()) ++ die("could not init fontconfig.\n"); ++ ++ usedfont = (opt_font == NULL)? font : opt_font; ++ xloadfonts(usedfont, 0); ++ ++ /* spare font (font2) */ ++ xloadsparefont(); ++ ++ ++ /* colors */ ++ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); ++ xloadcols(); ++ ++ /* adjust fixed window geometry */ ++ win.w = 2 * borderpx + cols * win.cw; ++ win.h = 2 * borderpx + rows * win.ch; ++ if (xw.gm & XNegative) ++ xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; ++ if (xw.gm & YNegative) ++ xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; ++ ++ /* Events */ ++ xw.attrs.background_pixel = dc.col[defaultbg].pixel; ++ xw.attrs.border_pixel = dc.col[defaultbg].pixel; ++ xw.attrs.bit_gravity = NorthWestGravity; ++ xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask ++ | ExposureMask | VisibilityChangeMask | StructureNotifyMask ++ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; ++ xw.attrs.colormap = xw.cmap; ++ ++ 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, getcolorname(mousefg), &xmousefg) == 0) { ++ xmousefg.red = 0xffff; ++ xmousefg.green = 0xffff; ++ xmousefg.blue = 0xffff; ++ } ++ ++ if (XParseColor(xw.dpy, xw.cmap, getcolorname(mousebg), &xmousebg) == 0) { ++ xmousebg.red = 0x0000; ++ xmousebg.green = 0x0000; ++ xmousebg.blue = 0x0000; ++ } ++ ++ XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); ++ ++ xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); ++ xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); ++ xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); ++ xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); ++ XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); ++ ++ xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); ++ XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, ++ PropModeReplace, (uchar *)&thispid, 1); ++ ++ win.mode = MODE_NUMLOCK; ++ resettitle(); ++ xhints(); ++ XMapWindow(xw.dpy, xw.win); ++ XSync(xw.dpy, False); ++ ++ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); ++ clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); ++ xsel.primary = NULL; ++ xsel.clipboard = NULL; ++ xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); ++ if (xsel.xtarget == None) ++ xsel.xtarget = XA_STRING; ++} ++ ++int ++xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) ++{ ++ float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; ++ ushort mode, prevmode = USHRT_MAX; ++ Font *font = &dc.font; ++ int frcflags = FRC_NORMAL; ++ float runewidth = win.cw; ++ Rune rune; ++ FT_UInt glyphidx; ++ FcResult fcres; ++ FcPattern *fcpattern, *fontpattern; ++ FcFontSet *fcsets[] = { NULL }; ++ FcCharSet *fccharset; ++ int i, f, numspecs = 0; ++ ++ for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { ++ /* Fetch rune and mode for current glyph. */ ++ rune = glyphs[i].u; ++ mode = glyphs[i].mode; ++ ++ /* Skip dummy wide-character spacing. */ ++ if (mode == ATTR_WDUMMY) ++ continue; ++ ++ /* Determine font for glyph if different from previous glyph. */ ++ if (prevmode != mode) { ++ prevmode = mode; ++ font = &dc.font; ++ frcflags = FRC_NORMAL; ++ runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); ++ if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { ++ font = &dc.ibfont; ++ frcflags = FRC_ITALICBOLD; ++ } else if (mode & ATTR_ITALIC) { ++ font = &dc.ifont; ++ frcflags = FRC_ITALIC; ++ } else if (mode & ATTR_BOLD) { ++ font = &dc.bfont; ++ frcflags = FRC_BOLD; ++ } ++ yp = winy + font->ascent; ++ } ++ ++ /* Lookup character index with default font. */ ++ glyphidx = XftCharIndex(xw.dpy, font->match, rune); ++ if (glyphidx) { ++ specs[numspecs].font = font->match; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ xp += runewidth; ++ numspecs++; ++ continue; ++ } ++ ++ /* Fallback on font cache, search the font cache for match. */ ++ for (f = 0; f < frclen; f++) { ++ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); ++ /* Everything correct. */ ++ if (glyphidx && frc[f].flags == frcflags) ++ break; ++ /* We got a default font for a not found glyph. */ ++ if (!glyphidx && frc[f].flags == frcflags ++ && frc[f].unicodep == rune) { ++ break; ++ } ++ } ++ ++ /* Nothing was found. Use fontconfig to find matching font. */ ++ if (f >= frclen) { ++ if (!font->set) ++ font->set = FcFontSort(0, font->pattern, ++ 1, 0, &fcres); ++ fcsets[0] = font->set; ++ ++ /* ++ * Nothing was found in the cache. Now use ++ * some dozen of Fontconfig calls to get the ++ * font for one single character. ++ * ++ * Xft and fontconfig are design failures. ++ */ ++ fcpattern = FcPatternDuplicate(font->pattern); ++ fccharset = FcCharSetCreate(); ++ ++ FcCharSetAddChar(fccharset, rune); ++ FcPatternAddCharSet(fcpattern, FC_CHARSET, ++ fccharset); ++ FcPatternAddBool(fcpattern, FC_SCALABLE, 1); ++ ++ FcConfigSubstitute(0, fcpattern, ++ FcMatchPattern); ++ FcDefaultSubstitute(fcpattern); ++ ++ fontpattern = FcFontSetMatch(0, fcsets, 1, ++ fcpattern, &fcres); ++ ++ /* Allocate memory for the new cache entry. */ ++ if (frclen >= frccap) { ++ frccap += 16; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ } ++ ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, ++ fontpattern); ++ if (!frc[frclen].font) ++ die("XftFontOpenPattern failed seeking fallback font: %s\n", ++ strerror(errno)); ++ frc[frclen].flags = frcflags; ++ frc[frclen].unicodep = rune; ++ ++ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); ++ ++ f = frclen; ++ frclen++; ++ ++ FcPatternDestroy(fcpattern); ++ FcCharSetDestroy(fccharset); ++ } ++ ++ specs[numspecs].font = frc[f].font; ++ specs[numspecs].glyph = glyphidx; ++ specs[numspecs].x = (short)xp; ++ specs[numspecs].y = (short)yp; ++ xp += runewidth; ++ numspecs++; ++ } ++ ++ return numspecs; ++} ++ ++void ++xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) ++{ ++ int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); ++ int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, ++ width = charlen * win.cw; ++ Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; ++ XRenderColor colfg, colbg; ++ XRectangle r; ++ ++ /* Fallback on color display for attributes not supported by the font */ ++ if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { ++ if (dc.ibfont.badslant || dc.ibfont.badweight) ++ base.fg = defaultattr; ++ } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || ++ (base.mode & ATTR_BOLD && dc.bfont.badweight)) { ++ base.fg = defaultattr; ++ } ++ ++ if (IS_TRUECOL(base.fg)) { ++ colfg.alpha = 0xffff; ++ colfg.red = TRUERED(base.fg); ++ colfg.green = TRUEGREEN(base.fg); ++ colfg.blue = TRUEBLUE(base.fg); ++ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); ++ fg = &truefg; ++ } else { ++ fg = &dc.col[base.fg]; ++ } ++ ++ if (IS_TRUECOL(base.bg)) { ++ colbg.alpha = 0xffff; ++ colbg.green = TRUEGREEN(base.bg); ++ colbg.red = TRUERED(base.bg); ++ colbg.blue = TRUEBLUE(base.bg); ++ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); ++ bg = &truebg; ++ } else { ++ bg = &dc.col[base.bg]; ++ } ++ ++ /* Change basic system colors [0-7] to bright system colors [8-15] */ ++ if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) ++ fg = &dc.col[base.fg]; ++ ++ if (IS_SET(MODE_REVERSE)) { ++ if (fg == &dc.col[defaultfg]) { ++ fg = &dc.col[defaultbg]; ++ } else { ++ colfg.red = ~fg->color.red; ++ colfg.green = ~fg->color.green; ++ colfg.blue = ~fg->color.blue; ++ colfg.alpha = fg->color.alpha; ++ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, ++ &revfg); ++ fg = &revfg; ++ } ++ ++ if (bg == &dc.col[defaultbg]) { ++ bg = &dc.col[defaultfg]; ++ } else { ++ colbg.red = ~bg->color.red; ++ colbg.green = ~bg->color.green; ++ colbg.blue = ~bg->color.blue; ++ colbg.alpha = bg->color.alpha; ++ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, ++ &revbg); ++ bg = &revbg; ++ } ++ } ++ ++ if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { ++ colfg.red = fg->color.red / 2; ++ colfg.green = fg->color.green / 2; ++ colfg.blue = fg->color.blue / 2; ++ colfg.alpha = fg->color.alpha; ++ XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); ++ fg = &revfg; ++ } ++ ++ if (base.mode & ATTR_REVERSE) { ++ temp = fg; ++ fg = bg; ++ bg = temp; ++ } ++ ++ if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) ++ fg = bg; ++ ++ if (base.mode & ATTR_INVISIBLE) ++ fg = bg; ++ ++ /* Intelligent cleaning up of the borders. */ ++ if (x == 0) { ++ xclear(0, (y == 0)? 0 : winy, borderpx, ++ winy + win.ch + ++ ((winy + win.ch >= borderpx + win.th)? win.h : 0)); ++ } ++ if (winx + width >= borderpx + win.tw) { ++ xclear(winx + width, (y == 0)? 0 : winy, win.w, ++ ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); ++ } ++ if (y == 0) ++ xclear(winx, 0, winx + width, borderpx); ++ if (winy + win.ch >= borderpx + win.th) ++ xclear(winx, winy + win.ch, winx + width, win.h); ++ ++ /* Clean up the region we want to draw to. */ ++ XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); ++ ++ /* Set the clip region because Xft is sometimes dirty. */ ++ r.x = 0; ++ r.y = 0; ++ r.height = win.ch; ++ r.width = width; ++ XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); ++ ++ /* Render the glyphs. */ ++ XftDrawGlyphFontSpec(xw.draw, fg, specs, len); ++ ++ /* Render underline and strikethrough. */ ++ if (base.mode & ATTR_UNDERLINE) { ++ XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, ++ width, 1); ++ } ++ ++ if (base.mode & ATTR_STRUCK) { ++ XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, ++ width, 1); ++ } ++ ++ /* Reset clip to none. */ ++ XftDrawSetClip(xw.draw, 0); ++} ++ ++void ++xdrawglyph(Glyph g, int x, int y) ++{ ++ int numspecs; ++ XftGlyphFontSpec spec; ++ ++ numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); ++ xdrawglyphfontspecs(&spec, g, numspecs, x, y); ++} ++ ++void ++xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) ++{ ++ Color drawcol; ++ ++ /* remove the old cursor */ ++ if (selected(ox, oy)) ++ og.mode ^= ATTR_REVERSE; ++ xdrawglyph(og, ox, oy); ++ ++ if (IS_SET(MODE_HIDE)) ++ return; ++ ++ /* ++ * Select the right color for the right mode. ++ */ ++ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; ++ ++ if (IS_SET(MODE_REVERSE)) { ++ g.mode |= ATTR_REVERSE; ++ g.bg = defaultfg; ++ if (selected(cx, cy)) { ++ drawcol = dc.col[defaultcs]; ++ g.fg = defaultrcs; ++ } else { ++ drawcol = dc.col[defaultrcs]; ++ g.fg = defaultcs; ++ } ++ } else { ++ if (selected(cx, cy)) { ++ g.fg = defaultfg; ++ g.bg = defaultrcs; ++ } else { ++ g.fg = defaultbg; ++ g.bg = defaultcs; ++ } ++ drawcol = dc.col[g.bg]; ++ } ++ ++ /* draw the new one */ ++ if (IS_SET(MODE_FOCUSED)) { ++ switch (win.cursor) { ++ case 7: /* st extension */ ++ g.u = 0x2603; /* snowman (U+2603) */ ++ /* FALLTHROUGH */ ++ case 0: /* Blinking Block */ ++ case 1: /* Blinking Block (Default) */ ++ case 2: /* Steady Block */ ++ xdrawglyph(g, cx, cy); ++ break; ++ case 3: /* Blinking Underline */ ++ case 4: /* Steady Underline */ ++ XftDrawRect(xw.draw, &drawcol, ++ borderpx + cx * win.cw, ++ borderpx + (cy + 1) * win.ch - \ ++ cursorthickness, ++ win.cw, cursorthickness); ++ break; ++ case 5: /* Blinking bar */ ++ case 6: /* Steady bar */ ++ XftDrawRect(xw.draw, &drawcol, ++ borderpx + cx * win.cw, ++ borderpx + cy * win.ch, ++ cursorthickness, win.ch); ++ break; ++ } ++ } else { ++ XftDrawRect(xw.draw, &drawcol, ++ borderpx + cx * win.cw, ++ borderpx + cy * win.ch, ++ win.cw - 1, 1); ++ XftDrawRect(xw.draw, &drawcol, ++ borderpx + cx * win.cw, ++ borderpx + cy * win.ch, ++ 1, win.ch - 1); ++ XftDrawRect(xw.draw, &drawcol, ++ borderpx + (cx + 1) * win.cw - 1, ++ borderpx + cy * win.ch, ++ 1, win.ch - 1); ++ XftDrawRect(xw.draw, &drawcol, ++ borderpx + cx * win.cw, ++ borderpx + (cy + 1) * win.ch - 1, ++ win.cw, 1); ++ } ++} ++ ++void ++xsetenv(void) ++{ ++ char buf[sizeof(long) * 8 + 1]; ++ ++ snprintf(buf, sizeof(buf), "%lu", xw.win); ++ setenv("WINDOWID", buf, 1); ++} ++ ++void ++xseticontitle(char *p) ++{ ++ XTextProperty prop; ++ DEFAULT(p, opt_title); ++ ++ if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, ++ &prop) != Success) ++ return; ++ XSetWMIconName(xw.dpy, xw.win, &prop); ++ XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); ++ XFree(prop.value); ++} ++ ++void ++xsettitle(char *p) ++{ ++ XTextProperty prop; ++ DEFAULT(p, opt_title); ++ ++ if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, ++ &prop) != Success) ++ return; ++ XSetWMName(xw.dpy, xw.win, &prop); ++ XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); ++ XFree(prop.value); ++} ++ ++int ++xstartdraw(void) ++{ ++ if (IS_SET(MODE_VISIBLE)) ++ XCopyArea(xw.dpy, xw.win, xw.buf, dc.gc, 0, 0, win.w, win.h, 0, 0); ++ return IS_SET(MODE_VISIBLE); ++} ++ ++void ++xdrawline(Line line, int x1, int y1, int x2) ++{ ++ int i, x, ox, numspecs; ++ Glyph base, new; ++ XftGlyphFontSpec *specs = xw.specbuf; ++ ++ numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); ++ i = ox = 0; ++ for (x = x1; x < x2 && i < numspecs; x++) { ++ new = line[x]; ++ if (new.mode == ATTR_WDUMMY) ++ continue; ++ if (selected(x, y1)) ++ new.mode ^= ATTR_REVERSE; ++ if (i > 0 && ATTRCMP(base, new)) { ++ xdrawglyphfontspecs(specs, base, i, ox, y1); ++ specs += i; ++ numspecs -= i; ++ i = 0; ++ } ++ if (i == 0) { ++ ox = x; ++ base = new; ++ } ++ i++; ++ } ++ if (i > 0) ++ xdrawglyphfontspecs(specs, base, i, ox, y1); ++} ++ ++void ++xfinishdraw(void) ++{ ++ XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, ++ win.h, 0, 0); ++ XSetForeground(xw.dpy, dc.gc, ++ dc.col[IS_SET(MODE_REVERSE)? ++ defaultfg : defaultbg].pixel); ++} ++ ++void ++xximspot(int x, int y) ++{ ++ if (xw.ime.xic == NULL) ++ return; ++ ++ xw.ime.spot.x = borderpx + x * win.cw; ++ xw.ime.spot.y = borderpx + (y + 1) * win.ch; ++ ++ XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); ++} ++ ++void ++expose(XEvent *ev) ++{ ++ redraw(); ++} ++ ++void ++visibility(XEvent *ev) ++{ ++ XVisibilityEvent *e = &ev->xvisibility; ++ ++ MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); ++} ++ ++void ++unmap(XEvent *ev) ++{ ++ win.mode &= ~MODE_VISIBLE; ++} ++ ++void ++xsetpointermotion(int set) ++{ ++ MODBIT(xw.attrs.event_mask, set, PointerMotionMask); ++ XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); ++} ++ ++void ++xsetmode(int set, unsigned int flags) ++{ ++ int mode = win.mode; ++ MODBIT(win.mode, set, flags); ++ if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) ++ redraw(); ++} ++ ++int ++xsetcursor(int cursor) ++{ ++ if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ ++ return 1; ++ win.cursor = cursor; ++ return 0; ++} ++ ++void ++xseturgency(int add) ++{ ++ XWMHints *h = XGetWMHints(xw.dpy, xw.win); ++ ++ MODBIT(h->flags, add, XUrgencyHint); ++ XSetWMHints(xw.dpy, xw.win, h); ++ XFree(h); ++} ++ ++void ++xbell(void) ++{ ++ if (!(IS_SET(MODE_FOCUSED))) ++ xseturgency(1); ++ if (bellvolume) ++ XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); ++} ++ ++void ++focus(XEvent *ev) ++{ ++ XFocusChangeEvent *e = &ev->xfocus; ++ ++ if (e->mode == NotifyGrab) ++ return; ++ ++ if (ev->type == FocusIn) { ++ if (xw.ime.xic) ++ XSetICFocus(xw.ime.xic); ++ win.mode |= MODE_FOCUSED; ++ xseturgency(0); ++ if (IS_SET(MODE_FOCUS)) ++ ttywrite("\033[I", 3, 0); ++ } else { ++ if (xw.ime.xic) ++ XUnsetICFocus(xw.ime.xic); ++ win.mode &= ~MODE_FOCUSED; ++ if (IS_SET(MODE_FOCUS)) ++ ttywrite("\033[O", 3, 0); ++ } ++} ++ ++int ++match(uint mask, uint state) ++{ ++ return mask == XK_ANY_MOD || mask == (state & ~ignoremod); ++} ++ ++char* ++kmap(KeySym k, uint state) ++{ ++ Key *kp; ++ int i; ++ ++ /* Check for mapped keys out of X11 function keys. */ ++ for (i = 0; i < LEN(mappedkeys); i++) { ++ if (mappedkeys[i] == k) ++ break; ++ } ++ if (i == LEN(mappedkeys)) { ++ if ((k & 0xFFFF) < 0xFD00) ++ return NULL; ++ } ++ ++ for (kp = key; kp < key + LEN(key); kp++) { ++ if (kp->k != k) ++ continue; ++ ++ if (!match(kp->mask, state)) ++ continue; ++ ++ if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) ++ continue; ++ if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) ++ continue; ++ ++ if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) ++ continue; ++ ++ return kp->s; ++ } ++ ++ return NULL; ++} ++ ++void ++kpress(XEvent *ev) ++{ ++ XKeyEvent *e = &ev->xkey; ++ KeySym ksym; ++ 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), ttyfd, xev, drawing; ++ struct timespec seltv, *tv, now, lastblink, trigger; ++ double timeout; ++ ++ /* Waiting for window mapping */ ++ do { ++ XNextEvent(xw.dpy, &ev); ++ /* ++ * This XFilterEvent call is required because of XOpenIM. It ++ * does filter out the key event and some client message for ++ * the input method too. ++ */ ++ if (XFilterEvent(&ev, None)) ++ continue; ++ if (ev.type == ConfigureNotify) { ++ w = ev.xconfigure.width; ++ h = ev.xconfigure.height; ++ } ++ } while (ev.type != MapNotify); ++ ++ ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); ++ cresize(w, h); ++ ++ for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { ++ FD_ZERO(&rfd); ++ FD_SET(ttyfd, &rfd); ++ FD_SET(xfd, &rfd); ++ ++ if (XPending(xw.dpy)) ++ timeout = 0; /* existing events might not set xfd */ ++ ++ seltv.tv_sec = timeout / 1E3; ++ seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); ++ tv = timeout >= 0 ? &seltv : NULL; ++ ++ if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { ++ if (errno == EINTR) ++ continue; ++ die("select failed: %s\n", strerror(errno)); ++ } ++ clock_gettime(CLOCK_MONOTONIC, &now); ++ ++ if (FD_ISSET(ttyfd, &rfd)) ++ ttyread(); ++ ++ xev = 0; ++ while (XPending(xw.dpy)) { ++ xev = 1; ++ XNextEvent(xw.dpy, &ev); ++ if (XFilterEvent(&ev, None)) ++ continue; ++ if (handler[ev.type]) ++ (handler[ev.type])(&ev); ++ } ++ ++ /* ++ * To reduce flicker and tearing, when new content or event ++ * triggers drawing, we first wait a bit to ensure we got ++ * everything, and if nothing new arrives - we draw. ++ * We start with trying to wait minlatency ms. If more content ++ * arrives sooner, we retry with shorter and shorter periods, ++ * and eventually draw even without idle after maxlatency ms. ++ * Typically this results in low latency while interacting, ++ * maximum latency intervals during `cat huge.txt`, and perfect ++ * sync with periodic updates from animations/key-repeats/etc. ++ */ ++ if (FD_ISSET(ttyfd, &rfd) || xev) { ++ if (!drawing) { ++ trigger = now; ++ drawing = 1; ++ } ++ timeout = (maxlatency - TIMEDIFF(now, trigger)) \ ++ / maxlatency * minlatency; ++ if (timeout > 0) ++ continue; /* we have time, try to find idle */ ++ } ++ ++ /* idle detected or maxlatency exhausted -> draw */ ++ timeout = -1; ++ if (blinktimeout && tattrset(ATTR_BLINK)) { ++ timeout = blinktimeout - TIMEDIFF(now, lastblink); ++ if (timeout <= 0) { ++ if (-timeout > blinktimeout) /* start visible */ ++ win.mode |= MODE_BLINK; ++ win.mode ^= MODE_BLINK; ++ tsetdirtattr(ATTR_BLINK); ++ lastblink = now; ++ timeout = blinktimeout; ++ } ++ } ++ ++ draw(); ++ XFlush(xw.dpy); ++ drawing = 0; ++ } ++} ++ ++void ++usage(void) ++{ ++ die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" ++ " [-n name] [-o file]\n" ++ " [-T title] [-t title] [-w windowid]" ++ " [[-e] command [args ...]]\n" ++ " %s [-aiv] [-c class] [-f font] [-g geometry]" ++ " [-n name] [-o file]\n" ++ " [-T title] [-t title] [-w windowid] -l line" ++ " [stty_args ...]\n", argv0, argv0); ++} ++ ++int ++main(int argc, char *argv[]) ++{ ++ xw.l = xw.t = 0; ++ xw.isfixed = False; ++ xsetcursor(cursorshape); ++ ++ ARGBEGIN { ++ case 'a': ++ allowaltscreen = 0; ++ break; ++ case 'c': ++ opt_class = EARGF(usage()); ++ break; ++ case 'e': ++ if (argc > 0) ++ --argc, ++argv; ++ goto run; ++ case 'f': ++ opt_font = EARGF(usage()); ++ break; ++ case 'g': ++ xw.gm = XParseGeometry(EARGF(usage()), ++ &xw.l, &xw.t, &cols, &rows); ++ break; ++ case 'i': ++ xw.isfixed = 1; ++ break; ++ case 'o': ++ opt_io = EARGF(usage()); ++ break; ++ case 'l': ++ opt_line = EARGF(usage()); ++ break; ++ case 'n': ++ opt_name = EARGF(usage()); ++ break; ++ case 't': ++ case 'T': ++ opt_title = EARGF(usage()); ++ break; ++ case 'w': ++ opt_embed = EARGF(usage()); ++ break; ++ case 'v': ++ die("%s " VERSION "\n", argv0); ++ break; ++ default: ++ usage(); ++ } ARGEND; ++ ++run: ++ if (argc > 0) /* eat all remaining arguments */ ++ opt_cmd = argv; ++ ++ if (!opt_title) ++ opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; ++ ++ setlocale(LC_CTYPE, ""); ++ XSetLocaleModifiers(""); ++ cols = MAX(cols, 1); ++ rows = MAX(rows, 1); ++ tnew(cols, rows); ++ xinit(cols, rows); ++ xsetenv(); ++ selinit(); ++ run(); ++ ++ return 0; ++} diff --git a/home/natto/pkgs.nix b/home/natto/pkgs.nix index 4884399..8e27fc4 100644 --- a/home/natto/pkgs.nix +++ b/home/natto/pkgs.nix @@ -18,8 +18,11 @@ # Utils rage curl - dmenu - st + (dmenu.override { patches = [ ./patches/dmenu.patch ]; }) + (st.override { + patches = [ ./patches/st.patch ]; + extraLibs = [ harfbuzz ]; + }) yt-dlp xclip xorg.xkbcomp @@ -67,18 +70,8 @@ wineWowPackages.stable master.winetricks tlauncher - lutris citra - (yuzu.overrideAttrs (_: rec { - version = "1245"; - src = fetchFromGitHub { - owner = "yuzu-emu"; - repo = "yuzu-mainline"; - rev = "mainline-0-${version}"; - sha256 = "sha256-lWXlY1KQC067MvCRUFhmr0c7KDrHDuwJOhIWMKw1f+A="; - fetchSubmodules = true; - }; - })) + yuzu ryujinx # Dev shit @@ -103,7 +96,6 @@ }) python3Packages.pygments inform7 - gnome-inform7 # Misc teams @@ -114,5 +106,6 @@ visualizerSupport = true; clockSupport = true; }) + libsForQt5.qtstyleplugins ]; } diff --git a/home/natto/services.nix b/home/natto/services.nix index 64918db..74edbf1 100644 --- a/home/natto/services.nix +++ b/home/natto/services.nix @@ -81,6 +81,7 @@ in }; }; }; + }; systemd.user.services = { diff --git a/home/natto/stuff.nix b/home/natto/stuff.nix index 18f4880..4fe6371 100644 --- a/home/natto/stuff.nix +++ b/home/natto/stuff.nix @@ -8,6 +8,7 @@ sessionVariables = { LV2_PATH = lib.makeSearchPath "lib/lv2" (with pkgs; [ calf ]); TERM = "st-24bits"; + QT_QPA_PLATFORMTHEME = "gtk2"; }; file = with config; { stalonetray = { @@ -22,6 +23,14 @@ source = ./config/mpv/mpv.conf; target = "${xdg.configHome}/mpv/mpv.conf"; }; + packages = { + text = let + packages = builtins.map (p: "${p.name}") config.home.packages; + sortedUnique = builtins.sort builtins.lessThan (lib.unique packages); + formatted = builtins.concatStringsSep "\n" sortedUnique; + in formatted; + target = "${xdg.configHome}/${config.home.username}-packages"; + }; }; }; }