diff options
| author | Marcel Screm <marcel@marcelscrem.com> | 2025-12-28 20:13:38 +0100 |
|---|---|---|
| committer | Marcel Screm <marcel@marcelscrem.com> | 2025-12-28 20:13:38 +0100 |
| commit | 87bfa57605671e60fab925ae6e81ad08bd4a1369 (patch) | |
| tree | 73a2724250ec2a15785016cf14194d4bceb9661a | |
| parent | d7d617f3d463b82d64dff809c395e94b64bdf15f (diff) | |
added alpha patch
| -rw-r--r-- | config.def.h | 3 | ||||
| -rw-r--r-- | config.h | 3 | ||||
| -rwxr-xr-x | st | bin | 109768 -> 109880 bytes | |||
| -rw-r--r-- | st.c.rej | 1568 | ||||
| -rw-r--r-- | x.c | 44 | ||||
| -rw-r--r-- | x.c.orig | 2110 | ||||
| -rw-r--r-- | x.o | bin | 75728 -> 76952 bytes |
7 files changed, 2151 insertions, 1577 deletions
diff --git a/config.def.h b/config.def.h index 6769f99..89ef72d 100644 --- a/config.def.h +++ b/config.def.h @@ -93,6 +93,9 @@ char *termname = "st-256color"; */ unsigned int tabspaces = 8; +/* bg opacity */ +float alpha = 0.8; + /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { /* 8 normal colors */ @@ -93,6 +93,9 @@ char *termname = "st-256color"; */ unsigned int tabspaces = 8; +/* bg opacity */ +float alpha = 0.9; + /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ Binary files differdiff --git a/st.c.rej b/st.c.rej deleted file mode 100644 index 62ec366..0000000 --- a/st.c.rej +++ /dev/null @@ -1,1568 +0,0 @@ ---- st.c -+++ st.c -@@ -36,6 +36,7 @@ - #define STR_BUF_SIZ ESC_BUF_SIZ - #define STR_ARG_SIZ ESC_ARG_SIZ - #define HISTSIZE 2000 -+#define RESIZEBUFFER 1000 - - /* macros */ - #define IS_SET(flag) ((term.mode & (flag)) != 0) -@@ -43,9 +44,21 @@ - #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]) -+#define TLINE(y) ( \ -+ (y) < term.scr ? term.hist[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \ -+ : term.line[(y) - term.scr] \ -+) -+ -+#define TLINEABS(y) ( \ -+ (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \ -+) -+ -+#define UPDATEWRAPNEXT(alt, col) do { \ -+ if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \ -+ term.c.x += term.wrapcwidth[alt]; \ -+ term.c.state &= ~CURSOR_WRAPNEXT; \ -+ } \ -+} while (0); - - enum term_mode { - MODE_WRAP = 1 << 0, -@@ -124,10 +143,11 @@ 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 histi; /* history index */ -+ int histf; /* nb history available */ -+ int scr; /* scroll back */ -+ int wrapcwidth[2]; /* used in updating WRAPNEXT when resizing */ - int *dirty; /* dirtyness of lines */ - TCursor c; /* cursor */ - int ocx; /* old cursor col */ -@@ -185,26 +205,37 @@ 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 tclearregion(int, int, int, int, int); - static void tcursor(int); -+static void tclearglyph(Glyph *, int); -+static void tresetcursor(void); - static void tdeletechar(int); - static void tdeleteline(int); - static void tinsertblank(int); - static void tinsertblankline(int); --static int tlinelen(int); -+static int tlinelen(Line len); -+static int tiswrapped(Line line); -+static char *tgetglyphs(char *, const Glyph *, const Glyph *); -+static size_t tgetline(char *, const Glyph *); - 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 tscrollup(int, int, int, int); -+static void tscrolldown(int, int); -+static void treflow(int, int); -+static void rscrolldown(int); -+static void tresizedef(int, int); -+static void tresizealt(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 tloaddefscreen(int, int); -+static void tloadaltscreen(int, int); - static void tsetmode(int, int, const int *, int); - static int twrite(const char *, int, int); - static void tfulldirt(void); -@@ -218,7 +249,10 @@ static void tstrsequence(uchar); - static void drawregion(int, int, int, int); - - static void selnormalize(void); --static void selscroll(int, int); -+static void selscroll(int, int, int); -+static void selmove(int); -+static void selremove(void); -+static int regionselected(int, int, int, int); - static void selsnap(int *, int *, int); - - static size_t utf8decode(const char *, Rune *, size_t); -@@ -418,17 +452,46 @@ selinit(void) - } - - int --tlinelen(int y) -+tlinelen(Line line) - { -- int i = term.col; -+ int i = term.col - 1; - -- if (TLINE(y)[i - 1].mode & ATTR_WRAP) -- return i; -+ for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); -+ return i + 1; -+} - -- while (i > 0 && TLINE(y)[i - 1].u == ' ') -- --i; -+int -+tiswrapped(Line line) -+{ -+ int len = tlinelen(line); - -- return i; -+ return len > 0 && (line[len - 1].mode & ATTR_WRAP); -+} -+ -+char * -+tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp) -+{ -+ while (gp <= lgp) -+ if (gp->mode & ATTR_WDUMMY) { -+ gp++; -+ } else { -+ buf += utf8encode((gp++)->u, buf); -+ } -+ return buf; -+} -+ -+size_t -+tgetline(char *buf, const Glyph *fgp) -+{ -+ char *ptr; -+ const Glyph *lgp = &fgp[term.col - 1]; -+ -+ while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP))) -+ lgp--; -+ ptr = tgetglyphs(buf, fgp, lgp); -+ if (!(lgp->mode & ATTR_WRAP)) -+ *(ptr++) = '\n'; -+ return ptr - buf; - } - - void -@@ -468,10 +531,11 @@ selextend(int col, int row, int type, int done) - - sel.oe.x = col; - sel.oe.y = row; -- selnormalize(); - sel.type = type; -+ selnormalize(); - -- if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) -+ 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; -@@ -480,54 +544,62 @@ selextend(int col, int row, int type, int done) - void - selnormalize(void) - { -- int i; -+ 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); -+ 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); -+ 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; -+ /* expand selection over line breaks */ -+ if (sel.type == SEL_RECTANGULAR) -+ return; -+ -+ i = tlinelen(TLINE(sel.nb.y)); -+ if (sel.nb.x > i) -+ sel.nb.x = i; -+ if (sel.ne.x >= tlinelen(TLINE(sel.ne.y))) -+ sel.ne.x = term.col - 1; - } - --int --selected(int x, int y) -+ int -+regionselected(int x1, int y1, int x2, int y2) - { -- if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || -- sel.alt != IS_SET(MODE_ALTSCREEN)) -- return 0; -+ if (sel.ob.x == -1 || sel.mode == SEL_EMPTY || -+ sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1) -+ 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 (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1 -+ : (sel.nb.y != y2 || sel.nb.x <= x2) && -+ (sel.ne.y != y1 || sel.ne.x >= x1); -+} - -- 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); -+ int -+selected(int x, int y) -+{ -+ return regionselected(x, y, x, y); - } - -+ - void - selsnap(int *x, int *y, int direction) - { - int newx, newy, xt, yt; -+ int rtop = 0, rbot = term.row - 1; - int delim, prevdelim; - const Glyph *gp, *prevgp; - -+ if (!IS_SET(MODE_ALTSCREEN)) -+ rtop += -term.histf + term.scr, rbot += term.scr; -+ - switch (sel.snap) { - case SNAP_WORD: - /* -@@ -542,7 +614,7 @@ selsnap(int *x, int *y, int direction) - if (!BETWEEN(newx, 0, term.col - 1)) { - newy += direction; - newx = (newx + term.col) % term.col; -- if (!BETWEEN(newy, 0, term.row - 1)) -+ if (!BETWEEN(newy, rtop, rbot)) - break; - - if (direction > 0) -@@ -553,13 +625,13 @@ selsnap(int *x, int *y, int direction) - break; - } - -- if (newx >= tlinelen(newy)) -+ if (newx >= tlinelen(TLINE(newy))) - break; - - gp = &TLINE(newy)[newx]; - delim = ISDELIM(gp->u); -- if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim -- || (delim && gp->u != prevgp->u))) -+ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || -+ (delim && !(gp->u == ' ' && prevgp->u == ' ')))) - break; - - *x = newx; -@@ -574,20 +646,16 @@ selsnap(int *x, int *y, int direction) - * 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; -- } -+ *x = (direction < 0) ? 0 : term.col - 1; -+ if (direction < 0) { -+ for (; *y > rtop; *y -= 1) { -+ if (!tiswrapped(TLINE(*y-1))) -+ break; - } - } else if (direction > 0) { -- for (; *y < term.row-1; *y += direction) { -- if (!(TLINE(*y)[term.col-1].mode -- & ATTR_WRAP)) { -+ for (; *y < rbot; *y += 1) { -+ if (!tiswrapped(TLINE(*y))) - break; -- } - } - } - break; -@@ -598,39 +666,34 @@ char * - getsel(void) - { - char *str, *ptr; -- int y, bufsize, lastx, linelen; -- const Glyph *gp, *last; -+ int y, lastx, linelen; -+ const Glyph *gp, *lgp; - -- if (sel.ob.x == -1) -+ if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) - return NULL; - -- bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; -- ptr = str = xmalloc(bufsize); -+ str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ); -+ ptr = str; - - /* append every set & selected glyph to the selection */ - for (y = sel.nb.y; y <= sel.ne.y; y++) { -- if ((linelen = tlinelen(y)) == 0) { -+ Line line = TLINE(y); -+ -+ if ((linelen = tlinelen(line)) == 0) { - *ptr++ = '\n'; - continue; - } - - if (sel.type == SEL_RECTANGULAR) { -- gp = &TLINE(y)[sel.nb.x]; -+ gp = &line[sel.nb.x]; - lastx = sel.ne.x; - } else { -- gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; -+ gp = &line[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; -+ lgp = &line[MIN(lastx, linelen-1)]; - -- for ( ; gp <= last; ++gp) { -- if (gp->mode & ATTR_WDUMMY) -- continue; -- -- ptr += utf8encode(gp->u, ptr); -- } -+ ptr = tgetglyphs(ptr, gp, lgp); - - /* - * Copy and pasting of line endings is inconsistent -@@ -642,10 +705,10 @@ getsel(void) - * FIXME: Fix the computer world. - */ - if ((y < sel.ne.y || lastx >= linelen) && -- (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) -+ (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) - *ptr++ = '\n'; - } -- *ptr = 0; -+ *ptr = '\0'; - return str; - } - -@@ -654,9 +717,15 @@ selclear(void) - { - if (sel.ob.x == -1) - return; -+ selremove(); -+ tsetdirt(sel.nb.y, sel.ne.y); -+} -+ -+void -+selremove(void) -+{ - sel.mode = SEL_IDLE; - sel.ob.x = -1; -- tsetdirt(sel.nb.y, sel.ne.y); - } - - void -@@ -857,9 +926,8 @@ void - ttywrite(const char *s, size_t n, int may_echo) - { - const char *next; -- Arg arg = (Arg) { .i = term.scr }; - -- kscrolldown(&arg); -+ kscrolldown(&((Arg){ .i = term.scr })); - - if (may_echo && IS_SET(MODE_ECHO)) - twrite(s, n, 1); -@@ -996,7 +1064,7 @@ tsetdirtattr(int attr) - 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); -+ term.dirty[i] = 1; - break; - } - } -@@ -1006,7 +1074,8 @@ tsetdirtattr(int attr) - void - tfulldirt(void) - { -- tsetdirt(0, term.row-1); -+ for (int i = 0; i < term.row; i++) -+ term.dirty[i] = 1; - } - - void -@@ -1023,162 +1092,261 @@ tcursor(int mode) - } - } - -+void -+tresetcursor(void) -+{ -+ term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, -+ .x = 0, .y = 0, .state = CURSOR_DEFAULT }; -+} -+ - void - treset(void) - { - uint i; -+ int x, y; - -- term.c = (TCursor){{ -- .mode = ATTR_NULL, -- .fg = defaultfg, -- .bg = defaultbg -- }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; -+ tresetcursor(); - - 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.histf = 0; -+ term.scr = 0; - term.bot = term.row - 1; - term.mode = MODE_WRAP|MODE_UTF8; - memset(term.trantbl, CS_USA, sizeof(term.trantbl)); - term.charset = 0; - -+ selremove(); - for (i = 0; i < 2; i++) { -- tmoveto(0, 0); -- tcursor(CURSOR_SAVE); -- tclearregion(0, 0, term.col-1, term.row-1); -- tswapscreen(); -+ tcursor(CURSOR_SAVE); /* reset saved cursor */ -+ for (y = 0; y < term.row; y++) -+ for (x = 0; x < term.col; x++) -+ tclearglyph(&term.line[y][x], 0); -+ tswapscreen(); - } -+ tfulldirt(); - } - - void - tnew(int col, int row) - { -- term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; -- tresize(col, row); -- treset(); -+ int i, j; -+ -+ for (i = 0; i < 2; i++) { -+ term.line = xmalloc(row * sizeof(Line)); -+ for (j = 0; j < row; j++) -+ term.line[j] = xmalloc(col * sizeof(Glyph)); -+ term.col = col, term.row = row; -+ tswapscreen(); -+ } -+ term.dirty = xmalloc(row * sizeof(*term.dirty)); -+ term.tabs = xmalloc(col * sizeof(*term.tabs)); -+ for (i = 0; i < HISTSIZE; i++) -+ term.hist[i] = xmalloc(col * sizeof(Glyph)); -+ treset(); - } - -+/* handle it with care */ - void - tswapscreen(void) - { -- Line *tmp = term.line; -+ static Line *altline; -+ static int altcol, altrow; -+ Line *tmpline = term.line; -+ int tmpcol = term.col, tmprow = term.row; - -- term.line = term.alt; -- term.alt = tmp; -+ term.line = altline; -+ term.col = altcol, term.row = altrow; -+ altline = tmpline; -+ altcol = tmpcol, altrow = tmprow; - term.mode ^= MODE_ALTSCREEN; -- tfulldirt(); - } - - void --kscrolldown(const Arg* a) -+tloaddefscreen(int clear, int loadcursor) - { -- int n = a->i; -+ int col, row, alt = IS_SET(MODE_ALTSCREEN); - -- if (n < 0) -- n = term.row + n; -+ if (alt) { -+ if (clear) -+ tclearregion(0, 0, term.col-1, term.row-1, 1); -+ col = term.col, row = term.row; -+ tswapscreen(); -+ } -+ if (loadcursor) -+ tcursor(CURSOR_LOAD); -+ if (alt) -+ tresizedef(col, row); -+} - -- if (n > term.scr) -- n = term.scr; -+void -+tloadaltscreen(int clear, int savecursor) -+{ -+ int col, row, def = !IS_SET(MODE_ALTSCREEN); - -- if (term.scr > 0) { -- term.scr -= n; -- selscroll(0, -n); -- tfulldirt(); -+ if (savecursor) -+ tcursor(CURSOR_SAVE); -+ if (def) { -+ col = term.col, row = term.row; -+ tswapscreen(); -+ term.scr = 0; -+ tresizealt(col, row); - } -+ if (clear) -+ tclearregion(0, 0, term.col-1, term.row-1, 1); - } - -+int -+tisaltscreen(void) -+{ -+ return IS_SET(MODE_ALTSCREEN); -+} -+ -+ - void --kscrollup(const Arg* a) -+kscrolldown(const Arg* a) - { -- int n = a->i; -+ int n = a->i; - -- if (n < 0) -- n = term.row + n; -+ if (!term.scr || IS_SET(MODE_ALTSCREEN)) -+ return; - -- if (term.scr <= HISTSIZE-n) { -- term.scr += n; -- selscroll(0, n); -- tfulldirt(); -- } -+ if (n < 0) -+ n = MAX(term.row / -n, 1); -+ -+ if (n <= term.scr) { -+ term.scr -= n; -+ } else { -+ n = term.scr; -+ term.scr = 0; -+ } -+ if (sel.ob.x != -1 && !sel.alt) -+ selmove(-n); /* negate change in term.scr */ -+ tfulldirt(); - } - -+ -+ - void --tscrolldown(int orig, int n, int copyhist) -+kscrollup(const Arg* a) - { -- int i; -- Line temp; -+ int n = a->i; - -- LIMIT(n, 0, term.bot-orig+1); -+ if (!term.histf || IS_SET(MODE_ALTSCREEN)) -+ return; - -- 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; -- } -+ if (n < 0) -+ n = MAX(term.row / -n, 1); - -- tsetdirt(orig, term.bot-n); -- tclearregion(0, term.bot-n+1, term.col-1, term.bot); -+ if (term.scr + n <= term.histf) { -+ term.scr += n; -+ } else { -+ n = term.histf - term.scr; -+ term.scr = term.histf; -+ } - -- 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 (sel.ob.x != -1 && !sel.alt) -+ selmove(n); /* negate change in term.scr */ -+ tfulldirt(); - -- if (term.scr == 0) -- selscroll(orig, n); - } - - void --tscrollup(int orig, int n, int copyhist) -+tscrolldown(int top, int n) - { -- int i; -- Line temp; -+ int i, bot = term.bot; -+ Line temp; - -- LIMIT(n, 0, term.bot-orig+1); -+ if (n <= 0) -+ return; -+ n = MIN(n, bot-top+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; -- } -+ tsetdirt(top, bot-n); -+ tclearregion(0, bot-n+1, term.col-1, bot, 1); - -- if (term.scr > 0 && term.scr < HISTSIZE) -- term.scr = MIN(term.scr + n, HISTSIZE-1); -+ for (i = bot; i >= top+n; i--) { -+ temp = term.line[i]; -+ term.line[i] = term.line[i-n]; -+ term.line[i-n] = temp; -+ } - -- tclearregion(0, orig, term.col-1, orig+n-1); -- tsetdirt(orig+n, term.bot); -+ if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) -+ selscroll(top, bot, n); -+} - -- for (i = orig; i <= term.bot-n; i++) { -- temp = term.line[i]; -- term.line[i] = term.line[i+n]; -- term.line[i+n] = temp; -- } -+void -+tscrollup(int top, int bot, int n, int mode) -+{ -+ int i, j, s; -+ int alt = IS_SET(MODE_ALTSCREEN); -+ int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; -+ Line temp; -+ -+ if (n <= 0) -+ return; -+ n = MIN(n, bot-top+1); - -- if (term.scr == 0) -- selscroll(orig, -n); -+ if (savehist) { -+ for (i = 0; i < n; i++) { -+ term.histi = (term.histi + 1) % HISTSIZE; -+ temp = term.hist[term.histi]; -+ for (j = 0; j < term.col; j++) -+ tclearglyph(&temp[j], 1); -+ term.hist[term.histi] = term.line[i]; -+ term.line[i] = temp; -+ } -+ term.histf = MIN(term.histf + n, HISTSIZE); -+ s = n; -+ if (term.scr) { -+ j = term.scr; -+ term.scr = MIN(j + n, HISTSIZE); -+ s = j + n - term.scr; -+ } -+ if (mode != SCROLL_RESIZE) -+ tfulldirt(); -+ } else { -+ tclearregion(0, top, term.col-1, top+n-1, 1); -+ tsetdirt(top+n, bot); -+ } -+ -+ for (i = top; i <= bot-n; i++) { -+ temp = term.line[i]; -+ term.line[i] = term.line[i+n]; -+ term.line[i+n] = temp; -+ } -+ -+ if (sel.ob.x != -1 && sel.alt == alt) { -+ if (!savehist) { -+ selscroll(top, bot, -n); -+ } else if (s > 0) { -+ selmove(-s); -+ if (-term.scr + sel.nb.y < -term.histf) -+ selremove(); -+ } -+ } - } - - void --selscroll(int orig, int n) -+selmove(int n) -+ { -+ sel.ob.y += n, sel.nb.y += n; -+ sel.oe.y += n, sel.ne.y += n; -+} -+ -+void -+selscroll(int top, int bot, int n) - { -- if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) -- return; -+ /* turn absolute coordinates into relative */ -+ top += term.scr, bot += term.scr; - -- 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(); -- } -+ if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) { -+ selclear(); -+ } else if (BETWEEN(sel.nb.y, top, bot)) { -+ selmove(n); -+ if (sel.nb.y < top || sel.ne.y > bot) -+ selclear(); - } - } - -@@ -1188,7 +1356,7 @@ tnewline(int first_col) - int y = term.c.y; - - if (y == term.bot) { -- tscrollup(term.top, 1, 1); -+ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); - } else { - y++; - } -@@ -1252,115 +1420,126 @@ tmoveto(int x, int y) - 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 - ~ */ -- }; -+ 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; -- } -+ /* -+ * 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; -+ term.dirty[y] = 1; -+ term.line[y][x] = *attr; -+ term.line[y][x].u = u; -+ term.line[y][x].mode |= ATTR_SET; - } - -+ -+ - void --tclearregion(int x1, int y1, int x2, int y2) -+tclearglyph(Glyph *gp, int usecurattr) - { -- int x, y, temp; -- Glyph *gp; -+ if (usecurattr) { -+ gp->fg = term.c.attr.fg; -+ gp->bg = term.c.attr.bg; -+ } else { -+ gp->fg = defaultfg; -+ gp->bg = defaultbg; -+ } -+ gp->mode = ATTR_NULL; -+ gp->u = ' '; -+} - -- 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++) { -+void -+tclearregion(int x1, int y1, int x2, int y2, int usecurattr) -+{ -+ int x, y; -+ /* regionselected() takes relative coordinates */ -+ if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr)) -+ selremove(); -+ -+ 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 = ' '; -- } -+ for (x = x1; x <= x2; x++) -+ tclearglyph(&term.line[y][x], usecurattr); - } - } - - void - tdeletechar(int n) - { -- int dst, src, size; -- Glyph *line; -- -- LIMIT(n, 0, term.col - term.c.x); -+ int src, dst, size; -+ Line line; - -- dst = term.c.x; -- src = term.c.x + n; -- size = term.col - src; -- line = term.line[term.c.y]; -+ if (n <= 0) -+ return; - -- memmove(&line[dst], &line[src], size * sizeof(Glyph)); -- tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); -+ dst = term.c.x; -+ src = MIN(term.c.x + n, term.col); -+ size = term.col - src; -+ if (size > 0) { -+ /* -+ * otherwise src would point beyond the array -+ * https://stackoverflow.com/questions/29844298 -+ */ -+ line = term.line[term.c.y]; -+ memmove(&line[dst], &line[src], size * sizeof(Glyph)); -+ } -+ tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1); - } - - 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]; -+ int src, dst, size; -+ Line line; - -- memmove(&line[dst], &line[src], size * sizeof(Glyph)); -- tclearregion(src, term.c.y, dst - 1, term.c.y); -+ if (n <= 0) -+ return; -+ dst = MIN(term.c.x + n, term.col); -+ src = term.c.x; -+ size = term.col - dst; -+ if (size > 0) { /* otherwise dst would point beyond the array */ -+ line = term.line[term.c.y]; -+ memmove(&line[dst], &line[src], size * sizeof(Glyph)); -+ } -+ tclearregion(src, term.c.y, dst - 1, term.c.y, 1); - } - - void - tinsertblankline(int n) - { - if (BETWEEN(term.c.y, term.top, term.bot)) -- tscrolldown(term.c.y, n, 0); -+ tscrolldown(term.c.y, n); - } - - void - tdeleteline(int n) - { - if (BETWEEN(term.c.y, term.top, term.bot)) -- tscrollup(term.c.y, n, 0); -+ tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST); - } - - int32_t -@@ -1534,7 +1713,7 @@ tsetscroll(int t, int b) - void - tsetmode(int priv, int set, const int *args, int narg) - { -- int alt; const int *lim; -+ const int *lim; - - for (lim = args + narg; args < lim; ++args) { - if (priv) { -@@ -1595,26 +1774,20 @@ tsetmode(int priv, int set, const int *args, int narg) - 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: -+ case 1047: /*swap screen, clearing alternate screen */ - 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; -+ if (set) -+ tloadaltscreen(*args == 1049, *args == 1049); -+ else -+ tloaddefscreen(*args == 1047, *args == 1049); -+ break; - /* FALLTHROUGH */ -- case 1048: -- tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); -+ case 1048: -+ if (!allowaltscreen) -+ break; -+ tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); - break; - case 2004: /* 2004: bracketed paste mode */ - xsetmode(set, MODE_BRCKTPASTE); -@@ -1665,7 +1838,7 @@ void - csihandle(void) - { - char buf[40]; -- int len; -+ int n, x; - - switch (csiescseq.mode[0]) { - default: -@@ -1763,19 +1936,29 @@ csihandle(void) - 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); -+ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); - if (term.c.y < term.row-1) { -- tclearregion(0, term.c.y+1, term.col-1, -- term.row-1); -+ tclearregion(0, term.c.y+1, term.col-1, term.row-1, 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); -+ if (term.c.y >= 1) -+ tclearregion(0, 0, term.col-1, term.c.y-1, 1); -+ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); - break; -- case 2: /* all */ -- tclearregion(0, 0, term.col-1, term.row-1); -+ case 2: /* all */ -+ if (IS_SET(MODE_ALTSCREEN)) { -+ tclearregion(0, 0, term.col-1, term.row-1, 1); -+ break; -+ } -+ /* vte does this: -+ tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ -+ -+ /* alacritty does this: */ -+ for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--); -+ if (n >= 0) -+ tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); -+ tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); - break; - default: - goto unknown; -@@ -1784,25 +1967,25 @@ csihandle(void) - 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); -+ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); - break; - case 1: /* left */ -- tclearregion(0, term.c.y, term.c.x, term.c.y); -+ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); - break; - case 2: /* all */ -- tclearregion(0, term.c.y, term.col-1, term.c.y); -+ tclearregion(0, term.c.y, term.col-1, term.c.y, 1); - break; - } - break; - case 'S': /* SU -- Scroll <n> line up */ - if (csiescseq.priv) break; - DEFAULT(csiescseq.arg[0], 1); -- tscrollup(term.top, csiescseq.arg[0], 0); -+ /* xterm, urxvt, alacritty save this in history */ -+ tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); - break; - case 'T': /* SD -- Scroll <n> line down */ - DEFAULT(csiescseq.arg[0], 1); -- tscrolldown(term.top, csiescseq.arg[0], 0); -+ tscrolldown(term.top, csiescseq.arg[0]); - break; - case 'L': /* IL -- Insert <n> blank lines */ - DEFAULT(csiescseq.arg[0], 1); -@@ -1816,9 +1999,11 @@ csihandle(void) - tdeleteline(csiescseq.arg[0]); - break; - case 'X': /* ECH -- Erase <n> char */ -+ if (csiescseq.arg[0] < 0) -+ return; - DEFAULT(csiescseq.arg[0], 1); -- tclearregion(term.c.x, term.c.y, -- term.c.x + csiescseq.arg[0] - 1, term.c.y); -+ x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; -+ tclearregion(term.c.x, term.c.y, x, term.c.y, 1); - break; - case 'P': /* DCH -- Delete <n> char */ - DEFAULT(csiescseq.arg[0], 1); -@@ -1844,9 +2029,9 @@ csihandle(void) - ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); - break; - case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */ -- len = snprintf(buf, sizeof(buf), "\033[%i;%iR", -+ n = snprintf(buf, sizeof(buf), "\033[%i;%iR", - term.c.y+1, term.c.x+1); -- ttywrite(buf, len, 0); -+ ttywrite(buf, n, 0); - break; - default: - goto unknown; -@@ -2144,16 +2329,8 @@ tdumpsel(void) - 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); -+ char str[(term.col + 1) * UTF_SIZ]; -+ tprinter(str, tgetline(str, &term.line[n][0])); - } - - void -@@ -2374,7 +2551,7 @@ eschandle(uchar ascii) - return 0; - case 'D': /* IND -- Linefeed */ - if (term.c.y == term.bot) { -- tscrollup(term.top, 1, 1); -+ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); - } else { - tmoveto(term.c.x, term.c.y+1); - } -@@ -2387,7 +2564,7 @@ eschandle(uchar ascii) - break; - case 'M': /* RI -- Reverse index */ - if (term.c.y == term.top) { -- tscrolldown(term.top, 1, 1); -+ tscrolldown(term.top, 1); - } else { - tmoveto(term.c.x, term.c.y-1); - } -@@ -2531,7 +2708,9 @@ check_control_code: - */ - return; - } -- if (selected(term.c.x, term.c.y)) -+ -+ /* selected() takes relative coordinates */ -+ if (selected(term.c.x + term.scr, term.c.y + term.scr)) - selclear(); - - gp = &term.line[term.c.y][term.c.x]; -@@ -2571,6 +2750,7 @@ check_control_code: - if (term.c.x+width < term.col) { - tmoveto(term.c.x+width, term.c.y); - } else { -+ term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; - term.c.state |= CURSOR_WRAPNEXT; - } - } -@@ -2607,94 +2787,285 @@ twrite(const char *buf, int buflen, int show_ctrl) - return n; - } - -+void -+rscrolldown(int n) -+{ -+ int i; -+ Line temp; -+ -+ /* can never be true as of now -+ if (IS_SET(MODE_ALTSCREEN)) -+ return; */ -+ -+ if ((n = MIN(n, term.histf)) <= 0) -+ return; -+ -+ for (i = term.c.y + n; i >= n; i--) { -+ temp = term.line[i]; -+ term.line[i] = term.line[i-n]; -+ term.line[i-n] = temp; -+ } -+ for (/*i = n - 1 */; i >= 0; i--) { -+ temp = term.line[i]; -+ term.line[i] = term.hist[term.histi]; -+ term.hist[term.histi] = temp; -+ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; -+ } -+ term.c.y += n; -+ term.histf -= n; -+ if ((i = term.scr - n) >= 0) { -+ term.scr = i; -+ } else { -+ term.scr = 0; -+ if (sel.ob.x != -1 && !sel.alt) -+ selmove(-i); -+ } -+} -+ -+ -+ - 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; - -+ /* col and row are always MAX(_, 1) - if (col < 1 || row < 1) { -- fprintf(stderr, -- "tresize: error resizing to %dx%d\n", col, row); -+ 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)); -+ 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; -+ } - -- 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 = ' '; -- } -- } -+ if (IS_SET(MODE_ALTSCREEN)) -+ tresizealt(col, row); -+ else -+ tresizedef(col, row); -+} - -- /* 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)); -+void -+tresizedef(int col, int row) -+{ -+ int i, j; -+ -+ /* return if dimensions haven't changed */ -+ if (term.col == col && term.row == row) { -+ tfulldirt(); -+ return; - } -- 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; -+ if (col != term.col) { -+ if (!sel.alt) -+ selremove(); -+ treflow(col, row); -+ } else { -+ /* slide screen up if otherwise cursor would get out of the screen */ -+ if (term.c.y >= row) { -+ tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE); -+ term.c.y = row - 1; -+ } -+ for (i = row; i < term.row; i++) -+ free(term.line[i]); -+ -+ /* resize to new height */ -+ term.line = xrealloc(term.line, row * sizeof(Line)); -+ /* allocate any new rows */ -+ for (i = term.row; i < row; i++) { -+ term.line[i] = xmalloc(col * sizeof(Glyph)); -+ for (j = 0; j < col; j++) -+ tclearglyph(&term.line[i][j], 0); -+ } -+ /* scroll down as much as height has increased */ -+ rscrolldown(row - term.row); - } - /* update terminal size */ -- term.col = col; -- term.row = row; -+ 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; -+ term.top = 0, term.bot = row - 1; -+ /* dirty all lines */ -+ tfulldirt(); -+} -+ -+ -+ -+void -+tresizealt(int col, int row) -+{ -+ int i, j; -+ -+ /* return if dimensions haven't changed */ -+ if (term.col == col && term.row == row) { -+ tfulldirt(); -+ return; -+ } -+ if (sel.alt) -+ selremove(); -+ /* slide screen up if otherwise cursor would get out of the screen */ -+ for (i = 0; i <= term.c.y - row; i++) -+ free(term.line[i]); -+ if (i > 0) { -+ /* ensure that both src and dst are not NULL */ -+ memmove(term.line, term.line + i, row * sizeof(Line)); -+ term.c.y = row - 1; -+ } -+ for (i += row; i < term.row; i++) -+ free(term.line[i]); -+ /* resize to new height */ -+ term.line = xrealloc(term.line, row * sizeof(Line)); -+ /* resize to new width */ -+ for (i = 0; i < MIN(row, term.row); i++) { -+ term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); -+ for (j = term.col; j < col; j++) -+ tclearglyph(&term.line[i][j], 0); -+ } -+ /* allocate any new rows */ -+ for (/*i = MIN(row, term.row) */; i < row; i++) { -+ term.line[i] = xmalloc(col * sizeof(Glyph)); -+ for (j = 0; j < col; j++) -+ tclearglyph(&term.line[i][j], 0); -+ } -+ /* update cursor */ -+ if (term.c.x >= col) { -+ term.c.state &= ~CURSOR_WRAPNEXT; -+ term.c.x = col - 1; -+ } else { -+ UPDATEWRAPNEXT(1, col); -+ } -+ /* update terminal size */ -+ term.col = col, term.row = row; -+ /* reset scrolling region */ -+ term.top = 0, term.bot = row - 1; -+ /* dirty all lines */ -+ tfulldirt(); -+ } -+ -+ -+ -+ -+ -+void -+treflow(int col, int row) -+{ -+ int i, j; -+ int oce, nce, bot, scr; -+ int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; -+ int cy = -1; /* proxy for new y coordinate of cursor */ -+ int nlines; -+ Line *buf, line; -+ -+ /* y coordinate of cursor line end */ -+ for (oce = term.c.y; oce < term.row - 1 && -+ tiswrapped(term.line[oce]); oce++); -+ -+ nlines = term.histf + oce + 1; -+ if (col < term.col) { -+ /* each line can take this many lines after reflow */ -+ j = (term.col + col - 1) / col; -+ nlines = j * nlines; -+ if (nlines > HISTSIZE + RESIZEBUFFER + row) { -+ nlines = HISTSIZE + RESIZEBUFFER + row; -+ oy = -(nlines / j - oce - 1); -+ } -+ } -+ buf = xmalloc(nlines * sizeof(Line)); -+ do { -+ if (!nx) -+ buf[++ny] = xmalloc(col * sizeof(Glyph)); -+ if (!ox) { -+ line = TLINEABS(oy); -+ len = tlinelen(line); -+ } -+ if (oy == term.c.y) { -+ if (!ox) -+ len = MAX(len, term.c.x + 1); -+ /* update cursor */ -+ if (cy < 0 && term.c.x - ox < col - nx) { -+ term.c.x = nx + term.c.x - ox, cy = ny; -+ UPDATEWRAPNEXT(0, col); -+ } -+ } -+ /* get reflowed lines in buf */ -+ if (col - nx > len - ox) { -+ memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph)); -+ nx += len - ox; -+ if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { -+ for (j = nx; j < col; j++) -+ tclearglyph(&buf[ny][j], 0); -+ nx = 0; -+ } else if (nx > 0) { -+ buf[ny][nx - 1].mode &= ~ATTR_WRAP; -+ } -+ ox = 0, oy++; -+ } else if (col - nx == len - ox) { -+ memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); -+ ox = 0, oy++, nx = 0; -+ } else/* if (col - nx < len - ox) */ { -+ memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); -+ ox += col - nx; -+ buf[ny][col - 1].mode |= ATTR_WRAP; -+ nx = 0; -+ } -+ } while (oy <= oce); -+ if (nx) -+ for (j = nx; j < col; j++) -+ tclearglyph(&buf[ny][j], 0); -+ -+ /* free extra lines */ -+ for (i = row; i < term.row; i++) -+ free(term.line[i]); -+ /* resize to new height */ -+ term.line = xrealloc(term.line, row * sizeof(Line)); -+ -+ bot = MIN(ny, row - 1); -+ scr = MAX(row - term.row, 0); -+ /* update y coordinate of cursor line end */ -+ nce = MIN(oce + scr, bot); -+ /* update cursor y coordinate */ -+ term.c.y = nce - (ny - cy); -+ if (term.c.y < 0) { -+ j = nce, nce = MIN(nce + -term.c.y, bot); -+ term.c.y += nce - j; -+ while (term.c.y < 0) { -+ free(buf[ny--]); -+ term.c.y++; -+ } -+ } -+ /* allocate new rows */ -+ for (i = row - 1; i > nce; i--) { -+ term.line[i] = xmalloc(col * sizeof(Glyph)); -+ for (j = 0; j < col; j++) -+ tclearglyph(&term.line[i][j], 0); -+ } -+ /* fill visible area */ -+ for (/*i = nce */; i >= term.row; i--, ny--) -+ term.line[i] = buf[ny]; -+ for (/*i = term.row - 1 */; i >= 0; i--, ny--) { -+ free(term.line[i]); -+ term.line[i] = buf[ny]; -+ } -+ /* fill lines in history buffer and update term.histf */ -+ for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) { -+ j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; -+ free(term.hist[j]); -+ term.hist[j] = buf[ny]; -+ } -+ term.histf = -i - 1; -+ term.scr = MIN(term.scr, term.histf); -+ /* resize rest of the history lines */ -+ for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) { -+ j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; -+ term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); -+ } -+ free(buf); - } - - void -@@ -2734,9 +3105,8 @@ draw(void) - 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]); -+ 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(); @@ -107,6 +107,7 @@ typedef struct { XSetWindowAttributes attrs; int scr; int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ int l, t; /* left and top offset */ int gm; /* geometry mask */ } XWindow; @@ -754,7 +755,7 @@ xresize(int col, int row) XFreePixmap(xw.dpy, xw.buf); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); + xw.depth); XftDrawChange(xw.draw, xw.buf); xclear(0, 0, win.w, win.h); @@ -814,6 +815,10 @@ xloadcols(void) else die("could not allocate color %d\n", i); } + + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; loaded = 1; } @@ -844,6 +849,12 @@ xsetcolorname(int x, const char *name) XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); dc.col[x] = ncolor; + if (x == defaultbg) { + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + return 0; } @@ -1136,11 +1147,25 @@ xinit(int cols, int rows) Window parent, root; pid_t thispid = getpid(); XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display\n"); xw.scr = XDefaultScreen(xw.dpy); - xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + + if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { + xw.vis = vis.visual; + xw.depth = vis.depth; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.vis = attr.visual; + xw.depth = attr.depth; + } /* font */ if (!FcInit()) @@ -1150,7 +1175,7 @@ xinit(int cols, int rows) xloadfonts(usedfont, 0); /* colors */ - xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); xloadcols(); /* adjust fixed window geometry */ @@ -1170,11 +1195,8 @@ xinit(int cols, int rows) | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; xw.attrs.colormap = xw.cmap; - root = XRootWindow(xw.dpy, xw.scr); - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) - parent = root; - xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, - win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); if (parent != root) @@ -1185,7 +1207,7 @@ xinit(int cols, int rows) dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); + xw.depth); XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); @@ -2049,6 +2071,10 @@ main(int argc, char *argv[]) case 'a': allowaltscreen = 0; break; + case 'A': + alpha = strtof(EARGF(usage()), NULL); + LIMIT(alpha, 0.0, 1.0); + break; case 'c': opt_class = EARGF(usage()); break; diff --git a/x.c.orig b/x.c.orig new file mode 100644 index 0000000..1e4bdf5 --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2110 @@ +/* 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 zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); +void kscrollup(const Arg *); +void kscrolldown(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 xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + 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; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent, root; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (p[0] == '\0') + p = opt_title; + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} Binary files differ |
