summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafael Marçalo <rafa99@protonmail.com>2020-05-26 16:09:04 +0000
committerRafael Marçalo <rafa99@protonmail.com>2020-05-26 16:09:04 +0000
commit3009d4e230559a9843fd13fdedd8aad8687e9f96 (patch)
treebe5761a032911eddd96dde8fc7b774f05de41592
parentfa8dabacbd6f78ae3c756e6cee5da204de7a70d7 (diff)
parentbeb5860118808d28df64aff7926938efa6bb7dac (diff)
Merge branch 'bump' into 'master'
Bumped to Version 0.8.3 See merge request rafa_99/st!3
-rw-r--r--FAQ33
-rw-r--r--Makefile6
-rw-r--r--boxdraw.c194
-rw-r--r--boxdraw_data.h214
-rw-r--r--colors.h2
-rw-r--r--config.def.h50
-rw-r--r--config.h72
-rw-r--r--config.mk8
-rw-r--r--hb.c136
-rw-r--r--hb.h7
-rw-r--r--st.13
-rw-r--r--st.c141
-rw-r--r--st.h16
-rw-r--r--st.info35
-rw-r--r--win.h2
-rw-r--r--x.c350
16 files changed, 548 insertions, 721 deletions
diff --git a/FAQ b/FAQ
index ecf7af8..85534a4 100644
--- a/FAQ
+++ b/FAQ
@@ -1,6 +1,6 @@
## Why does st not handle utmp entries?
-Use the excellent tool of [utmp](http://git.suckless.org/utmp/) for this task.
+Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task.
## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever!
@@ -15,19 +15,18 @@ you can manually run `tic -sx st.info`.
* Some programs don’t complain about the lacking st description and default to
another terminal. In that case see the question about terminfo.
-## I get some weird glitches/visual bug on _random program_!
-
-Try launching it with a different TERM: $ TERM=xterm myapp. toe(1) will give
-you a list of available terminals, but you’ll most likely switch between xterm,
-st or st-256color. The default value for TERM can be changed in config.h
-(TNAME).
-
## How do I scroll back up?
-Using a terminal multiplexer.
+* Using a terminal multiplexer.
+ * `st -e tmux` using C-b [
+ * `st -e screen` using C-a ESC
+* Using the excellent tool of [scroll](https://git.suckless.org/scroll/).
+* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/).
+
+## I would like to have utmp and/or scroll functionality by default
-* `st -e tmux` using C-b [
-* `st -e screen` using C-a ESC
+You can add the absolute patch of both programs in your config.h
+file. You only have to modify the value of utmp and scroll variables.
## Why doesn't the Del key work in some programs?
@@ -104,7 +103,7 @@ St is emulating the Linux way of handling backspace being delete and delete bein
backspace.
This is an issue that was discussed in suckless mailing list
-<http://lists.suckless.org/dev/1404/20697.html>. Here is why some old grumpy
+<https://lists.suckless.org/dev/1404/20697.html>. Here is why some old grumpy
terminal users wants its backspace to be how he feels it:
Well, I am going to comment why I want to change the behaviour
@@ -163,7 +162,15 @@ terminal users wants its backspace to be how he feels it:
Apply [1].
-[1] http://st.suckless.org/patches/delkey
+[1] https://st.suckless.org/patches/delkey
+
+## Why do images not work in st (in programs such as w3m)?
+
+This is a terrible hack that overdraws an image on top of the terminal emulator
+window. It also relies on a very specific way the terminal draws it's contents.
+
+A more proper (but limited way) would be using sixels. Which st doesn't
+support.
## BadLength X error in Xft when trying to render emoji
diff --git a/Makefile b/Makefile
index 6dfa212..38240da 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
include config.mk
-SRC = st.c x.c boxdraw.c
+SRC = st.c x.c hb.c
OBJ = $(SRC:.c=.o)
all: options st
@@ -22,8 +22,8 @@ config.h:
$(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h
-x.o: arg.h config.h st.h win.h
-boxdraw.o: config.h st.h boxdraw_data.h
+x.o: arg.h config.h st.h win.h hb.h
+hb.o: st.h
$(OBJ): config.h config.mk
diff --git a/boxdraw.c b/boxdraw.c
deleted file mode 100644
index 28a92d0..0000000
--- a/boxdraw.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
- * MIT/X Consortium License
- */
-
-#include <X11/Xft/Xft.h>
-#include "st.h"
-#include "boxdraw_data.h"
-
-/* Rounded non-negative integers division of n / d */
-#define DIV(n, d) (((n) + (d) / 2) / (d))
-
-static Display *xdpy;
-static Colormap xcmap;
-static XftDraw *xd;
-static Visual *xvis;
-
-static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort);
-static void drawboxlines(int, int, int, int, XftColor *, ushort);
-
-/* public API */
-
-void
-boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis)
-{
- xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis;
-}
-
-int
-isboxdraw(Rune u)
-{
- Rune block = u & ~0xff;
- return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) ||
- (boxdraw_braille && block == 0x2800);
-}
-
-/* the "index" is actually the entire shape data encoded as ushort */
-ushort
-boxdrawindex(const Glyph *g)
-{
- if (boxdraw_braille && (g->u & ~0xff) == 0x2800)
- return BRL | (uint8_t)g->u;
- if (boxdraw_bold && (g->mode & ATTR_BOLD))
- return BDB | boxdata[(uint8_t)g->u];
- return boxdata[(uint8_t)g->u];
-}
-
-void
-drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg,
- const XftGlyphFontSpec *specs, int len)
-{
- for ( ; len-- > 0; x += cw, specs++)
- drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph);
-}
-
-/* implementation */
-
-void
-drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd)
-{
- ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */
- if (bd & (BDL | BDA)) {
- /* lines (light/double/heavy/arcs) */
- drawboxlines(x, y, w, h, fg, bd);
-
- } else if (cat == BBD) {
- /* lower (8-X)/8 block */
- int d = DIV((uint8_t)bd * h, 8);
- XftDrawRect(xd, fg, x, y + d, w, h - d);
-
- } else if (cat == BBU) {
- /* upper X/8 block */
- XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8));
-
- } else if (cat == BBL) {
- /* left X/8 block */
- XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h);
-
- } else if (cat == BBR) {
- /* right (8-X)/8 block */
- int d = DIV((uint8_t)bd * w, 8);
- XftDrawRect(xd, fg, x + d, y, w - d, h);
-
- } else if (cat == BBQ) {
- /* Quadrants */
- int w2 = DIV(w, 2), h2 = DIV(h, 2);
- if (bd & TL)
- XftDrawRect(xd, fg, x, y, w2, h2);
- if (bd & TR)
- XftDrawRect(xd, fg, x + w2, y, w - w2, h2);
- if (bd & BL)
- XftDrawRect(xd, fg, x, y + h2, w2, h - h2);
- if (bd & BR)
- XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2);
-
- } else if (bd & BBS) {
- /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */
- int d = (uint8_t)bd;
- XftColor xfc;
- XRenderColor xrc = { .alpha = 0xffff };
-
- xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4);
- xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4);
- xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4);
-
- XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc);
- XftDrawRect(xd, &xfc, x, y, w, h);
- XftColorFree(xdpy, xvis, xcmap, &xfc);
-
- } else if (cat == BRL) {
- /* braille, each data bit corresponds to one dot at 2x4 grid */
- int w1 = DIV(w, 2);
- int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4);
-
- if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1);
- if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1);
- if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2);
- if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1);
- if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1);
- if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2);
- if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3);
- if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3);
-
- }
-}
-
-void
-drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd)
-{
- /* s: stem thickness. width/8 roughly matches underscore thickness. */
- /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */
- /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */
- int mwh = MIN(w, h);
- int base_s = MAX(1, DIV(mwh, 8));
- int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */
- int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s;
- int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2);
- /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */
- /* The base length (per direction till edge) includes this square. */
-
- int light = bd & (LL | LU | LR | LD);
- int double_ = bd & (DL | DU | DR | DD);
-
- if (light) {
- /* d: additional (negative) length to not-draw the center */
- /* texel - at arcs and avoid drawing inside (some) doubles */
- int arc = bd & BDA;
- int multi_light = light & (light - 1);
- int multi_double = double_ & (double_ - 1);
- /* light crosses double only at DH+LV, DV+LH (ref. shapes) */
- int d = arc || (multi_double && !multi_light) ? -s : 0;
-
- if (bd & LL)
- XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s);
- if (bd & LU)
- XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d);
- if (bd & LR)
- XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s);
- if (bd & LD)
- XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d);
- }
-
- /* double lines - also align with light to form heavy when combined */
- if (double_) {
- /*
- * going clockwise, for each double-ray: p is additional length
- * to the single-ray nearer to the previous direction, and n to
- * the next. p and n adjust from the base length to lengths
- * which consider other doubles - shorter to avoid intersections
- * (p, n), or longer to draw the far-corner texel (n).
- */
- int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD;
- if (dl) {
- int p = dd ? -s : 0, n = du ? -s : dd ? s : 0;
- XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s);
- XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s);
- }
- if (du) {
- int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0;
- XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p);
- XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n);
- }
- if (dr) {
- int p = du ? -s : 0, n = dd ? -s : du ? s : 0;
- XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s);
- XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s);
- }
- if (dd) {
- int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0;
- XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p);
- XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n);
- }
- }
-}
diff --git a/boxdraw_data.h b/boxdraw_data.h
deleted file mode 100644
index 7890500..0000000
--- a/boxdraw_data.h
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
- * MIT/X Consortium License
- */
-
-/*
- * U+25XX codepoints data
- *
- * References:
- * http://www.unicode.org/charts/PDF/U2500.pdf
- * http://www.unicode.org/charts/PDF/U2580.pdf
- *
- * Test page:
- * https://github.com/GNOME/vte/blob/master/doc/boxes.txt
- */
-
-/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */
-/* Categories (mutually exclusive except BDB): */
-/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */
-#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */
-#define BDA (1<<9) /* Box Draw Arc (light) */
-
-#define BBD (1<<10) /* Box Block Down (lower) X/8 */
-#define BBL (2<<10) /* Box Block Left X/8 */
-#define BBU (3<<10) /* Box Block Upper X/8 */
-#define BBR (4<<10) /* Box Block Right X/8 */
-#define BBQ (5<<10) /* Box Block Quadrants */
-#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */
-
-#define BBS (1<<14) /* Box Block Shades */
-#define BDB (1<<15) /* Box Draw is Bold */
-
-/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */
-/* Heavy is light+double (literally drawing light+double align to form heavy) */
-#define LL (1<<0)
-#define LU (1<<1)
-#define LR (1<<2)
-#define LD (1<<3)
-#define LH (LL+LR)
-#define LV (LU+LD)
-
-#define DL (1<<4)
-#define DU (1<<5)
-#define DR (1<<6)
-#define DD (1<<7)
-#define DH (DL+DR)
-#define DV (DU+DD)
-
-#define HL (LL+DL)
-#define HU (LU+DU)
-#define HR (LR+DR)
-#define HD (LD+DD)
-#define HH (HL+HR)
-#define HV (HU+HD)
-
-/* (BBQ) Quadrants Top/Bottom x Left/Right */
-#define TL (1<<0)
-#define TR (1<<1)
-#define BL (1<<2)
-#define BR (1<<3)
-
-/* Data for U+2500 - U+259F except dashes/diagonals */
-static const unsigned short boxdata[256] = {
- /* light lines */
- [0x00] = BDL + LH, /* light horizontal */
- [0x02] = BDL + LV, /* light vertical */
- [0x0c] = BDL + LD + LR, /* light down and right */
- [0x10] = BDL + LD + LL, /* light down and left */
- [0x14] = BDL + LU + LR, /* light up and right */
- [0x18] = BDL + LU + LL, /* light up and left */
- [0x1c] = BDL + LV + LR, /* light vertical and right */
- [0x24] = BDL + LV + LL, /* light vertical and left */
- [0x2c] = BDL + LH + LD, /* light horizontal and down */
- [0x34] = BDL + LH + LU, /* light horizontal and up */
- [0x3c] = BDL + LV + LH, /* light vertical and horizontal */
- [0x74] = BDL + LL, /* light left */
- [0x75] = BDL + LU, /* light up */
- [0x76] = BDL + LR, /* light right */
- [0x77] = BDL + LD, /* light down */
-
- /* heavy [+light] lines */
- [0x01] = BDL + HH,
- [0x03] = BDL + HV,
- [0x0d] = BDL + HR + LD,
- [0x0e] = BDL + HD + LR,
- [0x0f] = BDL + HD + HR,
- [0x11] = BDL + HL + LD,
- [0x12] = BDL + HD + LL,
- [0x13] = BDL + HD + HL,
- [0x15] = BDL + HR + LU,
- [0x16] = BDL + HU + LR,
- [0x17] = BDL + HU + HR,
- [0x19] = BDL + HL + LU,
- [0x1a] = BDL + HU + LL,
- [0x1b] = BDL + HU + HL,
- [0x1d] = BDL + HR + LV,
- [0x1e] = BDL + HU + LD + LR,
- [0x1f] = BDL + HD + LR + LU,
- [0x20] = BDL + HV + LR,
- [0x21] = BDL + HU + HR + LD,
- [0x22] = BDL + HD + HR + LU,
- [0x23] = BDL + HV + HR,
- [0x25] = BDL + HL + LV,
- [0x26] = BDL + HU + LD + LL,
- [0x27] = BDL + HD + LU + LL,
- [0x28] = BDL + HV + LL,
- [0x29] = BDL + HU + HL + LD,
- [0x2a] = BDL + HD + HL + LU,
- [0x2b] = BDL + HV + HL,
- [0x2d] = BDL + HL + LD + LR,
- [0x2e] = BDL + HR + LL + LD,
- [0x2f] = BDL + HH + LD,
- [0x30] = BDL + HD + LH,
- [0x31] = BDL + HD + HL + LR,
- [0x32] = BDL + HR + HD + LL,
- [0x33] = BDL + HH + HD,
- [0x35] = BDL + HL + LU + LR,
- [0x36] = BDL + HR + LU + LL,
- [0x37] = BDL + HH + LU,
- [0x38] = BDL + HU + LH,
- [0x39] = BDL + HU + HL + LR,
- [0x3a] = BDL + HU + HR + LL,
- [0x3b] = BDL + HH + HU,
- [0x3d] = BDL + HL + LV + LR,
- [0x3e] = BDL + HR + LV + LL,
- [0x3f] = BDL + HH + LV,
- [0x40] = BDL + HU + LH + LD,
- [0x41] = BDL + HD + LH + LU,
- [0x42] = BDL + HV + LH,
- [0x43] = BDL + HU + HL + LD + LR,
- [0x44] = BDL + HU + HR + LD + LL,
- [0x45] = BDL + HD + HL + LU + LR,
- [0x46] = BDL + HD + HR + LU + LL,
- [0x47] = BDL + HH + HU + LD,
- [0x48] = BDL + HH + HD + LU,
- [0x49] = BDL + HV + HL + LR,
- [0x4a] = BDL + HV + HR + LL,
- [0x4b] = BDL + HV + HH,
- [0x78] = BDL + HL,
- [0x79] = BDL + HU,
- [0x7a] = BDL + HR,
- [0x7b] = BDL + HD,
- [0x7c] = BDL + HR + LL,
- [0x7d] = BDL + HD + LU,
- [0x7e] = BDL + HL + LR,
- [0x7f] = BDL + HU + LD,
-
- /* double [+light] lines */
- [0x50] = BDL + DH,
- [0x51] = BDL + DV,
- [0x52] = BDL + DR + LD,
- [0x53] = BDL + DD + LR,
- [0x54] = BDL + DR + DD,
- [0x55] = BDL + DL + LD,
- [0x56] = BDL + DD + LL,
- [0x57] = BDL + DL + DD,
- [0x58] = BDL + DR + LU,
- [0x59] = BDL + DU + LR,
- [0x5a] = BDL + DU + DR,
- [0x5b] = BDL + DL + LU,
- [0x5c] = BDL + DU + LL,
- [0x5d] = BDL + DL + DU,
- [0x5e] = BDL + DR + LV,
- [0x5f] = BDL + DV + LR,
- [0x60] = BDL + DV + DR,
- [0x61] = BDL + DL + LV,
- [0x62] = BDL + DV + LL,
- [0x63] = BDL + DV + DL,
- [0x64] = BDL + DH + LD,
- [0x65] = BDL + DD + LH,
- [0x66] = BDL + DD + DH,
- [0x67] = BDL + DH + LU,
- [0x68] = BDL + DU + LH,
- [0x69] = BDL + DH + DU,
- [0x6a] = BDL + DH + LV,
- [0x6b] = BDL + DV + LH,
- [0x6c] = BDL + DH + DV,
-
- /* (light) arcs */
- [0x6d] = BDA + LD + LR,
- [0x6e] = BDA + LD + LL,
- [0x6f] = BDA + LU + LL,
- [0x70] = BDA + LU + LR,
-
- /* Lower (Down) X/8 block (data is 8 - X) */
- [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4,
- [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0,
-
- /* Left X/8 block (data is X) */
- [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4,
- [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1,
-
- /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */
- [0x80] = BBU + 4, [0x94] = BBU + 1,
- [0x90] = BBR + 4, [0x95] = BBR + 7,
-
- /* Quadrants */
- [0x96] = BBQ + BL,
- [0x97] = BBQ + BR,
- [0x98] = BBQ + TL,
- [0x99] = BBQ + TL + BL + BR,
- [0x9a] = BBQ + TL + BR,
- [0x9b] = BBQ + TL + TR + BL,
- [0x9c] = BBQ + TL + TR + BR,
- [0x9d] = BBQ + TR,
- [0x9e] = BBQ + BL + TR,
- [0x9f] = BBQ + BL + TR + BR,
-
- /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */
- [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3,
-
- /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */
- /* U+2571 - U+2573: unsupported (diagonals) */
-};
diff --git a/colors.h b/colors.h
index 9b955c2..bc2470b 100644
--- a/colors.h
+++ b/colors.h
@@ -1,3 +1,4 @@
+/* Terminal colors (16 first used in escape sequence) */
static const char *colorname[] = {
/* 8 normal colors */
"black",
@@ -24,4 +25,5 @@ static const char *colorname[] = {
/* more colors can be added after 255 to use with DefaultXX */
"#cccccc",
"#555555",
+ "black",
};
diff --git a/config.def.h b/config.def.h
index 81ade7b..a84dd6b 100644
--- a/config.def.h
+++ b/config.def.h
@@ -17,13 +17,15 @@ static int borderpx = 2;
/*
* What program is execed by st depends of these precedence rules:
* 1: program passed with -e
- * 2: utmp option
+ * 2: scroll and/or utmp
* 3: SHELL environment variable
* 4: value of shell in /etc/passwd
* 5: value of shell in config.h
*/
static char *shell = "/bin/sh";
char *utmp = NULL;
+/* scroll program: to enable use a string like "scroll" */
+char *scroll = NULL;
char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
/* identification sequence returned in DA and DECID */
@@ -47,9 +49,14 @@ static unsigned int tripleclicktimeout = 600;
/* alt screens */
int allowaltscreen = 1;
-/* frames per second st should at maximum draw to the screen */
-static unsigned int xfps = 120;
-static unsigned int actionfps = 30;
+/*
+ * draw latency range in ms - from new content/keypress/etc until drawing.
+ * within this range, st draws when content stops arriving (idle). mostly it's
+ * near minlatency, but it waits longer for slow updates to avoid partial draw.
+ * low minlatency will tear/flicker more, as it can "detect" idle too early.
+ */
+static double minlatency = 8;
+static double maxlatency = 33;
/*
* blinking timeout (set to 0 to disable blinking) for the terminal blinking
@@ -63,18 +70,6 @@ static unsigned int blinktimeout = 800;
static unsigned int cursorthickness = 2;
/*
- * 1: render most of the lines/blocks characters without using the font for
- * perfect alignment between cells (U2500 - U259F except dashes/diagonals).
- * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored.
- * 0: disable (render all U25XX glyphs normally from the font).
- */
-const int boxdraw = 0;
-const int boxdraw_bold = 0;
-
-/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */
-const int boxdraw_braille = 0;
-
-/*
* bell volume. It must be a value between -100 and 100. Use 0 for disabling
* it
*/
@@ -173,13 +168,23 @@ static unsigned int mousebg = 0;
static unsigned int defaultattr = 11;
/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
* Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection.
*/
static MouseShortcut mshortcuts[] = {
- /* button mask string */
- { Button4, XK_ANY_MOD, "\031" },
- { Button5, XK_ANY_MOD, "\005" },
+ /* mask button function argument release */
+ { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
+ { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
+ { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
+ { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} },
+ { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} },
};
/* Internal keyboard shortcuts. */
@@ -238,13 +243,6 @@ static KeySym mappedkeys[] = { -1 };
static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;
/*
- * Override mouse-select while mask is active (when MODE_MOUSE is set).
- * Note that if you want to use ShiftMask with selmasks, set this to an other
- * modifier, set to 0 to not use it.
- */
-static uint forceselmod = ShiftMask;
-
-/*
* This is the huge key array which defines all compatibility to the Linux
* world. Please decide about changes wisely.
*/
diff --git a/config.h b/config.h
index 0aa0a84..f5238ef 100644
--- a/config.h
+++ b/config.h
@@ -1,3 +1,4 @@
+#include "colors.h"
/* See LICENSE file for copyright and license details. */
/*
@@ -5,24 +6,26 @@
*
* font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
*/
-static char *font = "JetBrains Mono Nerd Font:size=12:antialias=true:autohint=true";
+static char *font = "JetBrains Mono Nerd Font:pixelsize=12:antialias=true:autohint=true";
/* Spare fonts */
static char *font2[] = {
- "Symbola:size=12:antialias=true:autohint=true",
+ "Symbola:pixelsize=11:antialias=true:autohint=true",
};
-static int borderpx = 2;
+static int borderpx = 5;
/*
* What program is execed by st depends of these precedence rules:
* 1: program passed with -e
- * 2: utmp option
+ * 2: scroll and/or utmp
* 3: SHELL environment variable
* 4: value of shell in /etc/passwd
* 5: value of shell in config.h
*/
static char *shell = "/bin/sh";
char *utmp = NULL;
+/* scroll program: to enable use a string like "scroll" */
+char *scroll = NULL;
char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
/* identification sequence returned in DA and DECID */
@@ -46,9 +49,14 @@ static unsigned int tripleclicktimeout = 600;
/* alt screens */
int allowaltscreen = 1;
-/* frames per second st should at maximum draw to the screen */
-static unsigned int xfps = 120;
-static unsigned int actionfps = 30;
+/*
+ * draw latency range in ms - from new content/keypress/etc until drawing.
+ * within this range, st draws when content stops arriving (idle). mostly it's
+ * near minlatency, but it waits longer for slow updates to avoid partial draw.
+ * low minlatency will tear/flicker more, as it can "detect" idle too early.
+ */
+static double minlatency = 8;
+static double maxlatency = 33;
/*
* blinking timeout (set to 0 to disable blinking) for the terminal blinking
@@ -62,18 +70,6 @@ static unsigned int blinktimeout = 800;
static unsigned int cursorthickness = 2;
/*
- * 1: render most of the lines/blocks characters without using the font for
- * perfect alignment between cells (U2500 - U259F except dashes/diagonals).
- * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored.
- * 0: disable (render all U25XX glyphs normally from the font).
- */
-const int boxdraw = 1;
-const int boxdraw_bold = 1;
-
-/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */
-const int boxdraw_braille = 1;
-
-/*
* bell volume. It must be a value between -100 and 100. Use 0 for disabling
* it
*/
@@ -102,17 +98,14 @@ unsigned int tabspaces = 8;
/* bg opacity */
float alpha = 0.8;
-/* Terminal colors (16 first used in escape sequence) */
-#include "colors.h"
-
/*
* Default colors (colorname index)
* foreground, background, cursor, reverse cursor
*/
-unsigned int defaultfg = 257;
-unsigned int defaultbg = 0;
-static unsigned int defaultcs = 258;
-static unsigned int defaultrcs = 258;
+unsigned int defaultfg = 7;
+unsigned int defaultbg = 258;
+static unsigned int defaultcs = 256;
+static unsigned int defaultrcs = 257;
/*
* Default shape of cursor
@@ -144,13 +137,23 @@ static unsigned int mousebg = 0;
static unsigned int defaultattr = 11;
/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
* Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection.
*/
static MouseShortcut mshortcuts[] = {
- /* button mask string */
- { Button4, XK_ANY_MOD, "\031" },
- { Button5, XK_ANY_MOD, "\005" },
+ /* mask button function argument release */
+ { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
+ { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
+ { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
+ { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} },
+ { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} },
};
/* Internal keyboard shortcuts. */
@@ -163,8 +166,8 @@ static Shortcut shortcuts[] = {
{ ControlMask, XK_Print, toggleprinter, {.i = 0} },
{ ShiftMask, XK_Print, printscreen, {.i = 0} },
{ XK_ANY_MOD, XK_Print, printsel, {.i = 0} },
- { ControlMask, XK_plus, zoom, {.f = +1} },
- { ControlMask, XK_minus, zoom, {.f = -1} },
+ { ControlMask, XK_plus, zoom, {.f = +2} },
+ { ControlMask, XK_minus, zoom, {.f = -2} },
{ ControlMask, XK_0, zoomreset, {.f = 0} },
{ TERMMOD, XK_C, clipcopy, {.i = 0} },
{ TERMMOD, XK_V, clippaste, {.i = 0} },
@@ -211,13 +214,6 @@ static KeySym mappedkeys[] = { -1 };
static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;
/*
- * Override mouse-select while mask is active (when MODE_MOUSE is set).
- * Note that if you want to use ShiftMask with selmasks, set this to an other
- * modifier, set to 0 to not use it.
- */
-static uint forceselmod = ShiftMask;
-
-/*
* This is the huge key array which defines all compatibility to the Linux
* world. Please decide about changes wisely.
*/
diff --git a/config.mk b/config.mk
index 1d2f0e2..07a3d14 100644
--- a/config.mk
+++ b/config.mk
@@ -1,5 +1,5 @@
# st version
-VERSION = 0.8.2
+VERSION = 0.8.3
# Customize below to fit your system
@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
# includes and libs
INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \
- `$(PKG_CONFIG) --cflags freetype2`
+ `$(PKG_CONFIG) --cflags freetype2` \
+ `$(PKG_CONFIG) --cflags harfbuzz`
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\
`$(PKG_CONFIG) --libs fontconfig` \
- `$(PKG_CONFIG) --libs freetype2`
+ `$(PKG_CONFIG) --libs freetype2` \
+ `$(PKG_CONFIG) --libs harfbuzz`
# flags
STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
diff --git a/hb.c b/hb.c
new file mode 100644
index 0000000..7df2828
--- /dev/null
+++ b/hb.c
@@ -0,0 +1,136 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <X11/Xft/Xft.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+#include "st.h"
+
+void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length);
+hb_font_t *hbfindfont(XftFont *match);
+
+typedef struct {
+ XftFont *match;
+ hb_font_t *font;
+} HbFontMatch;
+
+static int hbfontslen = 0;
+static HbFontMatch *hbfontcache = NULL;
+
+void
+hbunloadfonts()
+{
+ for (int i = 0; i < hbfontslen; i++) {
+ hb_font_destroy(hbfontcache[i].font);
+ XftUnlockFace(hbfontcache[i].match);
+ }
+
+ if (hbfontcache != NULL) {
+ free(hbfontcache);
+ hbfontcache = NULL;
+ }
+ hbfontslen = 0;
+}
+
+hb_font_t *
+hbfindfont(XftFont *match)
+{
+ for (int i = 0; i < hbfontslen; i++) {
+ if (hbfontcache[i].match == match)
+ return hbfontcache[i].font;
+ }
+
+ /* Font not found in cache, caching it now. */
+ hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1));
+ FT_Face face = XftLockFace(match);
+ hb_font_t *font = hb_ft_font_create(face, NULL);
+ if (font == NULL)
+ die("Failed to load Harfbuzz font.");
+
+ hbfontcache[hbfontslen].match = match;
+ hbfontcache[hbfontslen].font = font;
+ hbfontslen += 1;
+
+ return font;
+}
+
+void
+hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y)
+{
+ int start = 0, length = 1, gstart = 0;
+ hb_codepoint_t *codepoints = calloc(len, sizeof(hb_codepoint_t));
+
+ for (int idx = 1, specidx = 1; idx < len; idx++) {
+ if (glyphs[idx].mode & ATTR_WDUMMY) {
+ length += 1;
+ continue;
+ }
+
+ if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) {
+ hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
+
+ /* Reset the sequence. */
+ length = 1;
+ start = specidx;
+ gstart = idx;
+ } else {
+ length += 1;
+ }
+
+ specidx++;
+ }
+
+ /* EOL. */
+ hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
+
+ /* Apply the transformation to glyph specs. */
+ for (int i = 0, specidx = 0; i < len; i++) {
+ if (glyphs[i].mode & ATTR_WDUMMY)
+ continue;
+
+ if (codepoints[i] != specs[specidx].glyph)
+ ((Glyph *)glyphs)[i].mode |= ATTR_LIGA;
+
+ specs[specidx++].glyph = codepoints[i];
+ }
+
+ free(codepoints);
+}
+
+void
+hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length)
+{
+ hb_font_t *font = hbfindfont(xfont);
+ if (font == NULL)
+ return;
+
+ Rune rune;
+ ushort mode = USHRT_MAX;
+ hb_buffer_t *buffer = hb_buffer_create();
+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
+
+ /* Fill buffer with codepoints. */
+ for (int i = start; i < (start+length); i++) {
+ rune = string[i].u;
+ mode = string[i].mode;
+ if (mode & ATTR_WDUMMY)
+ rune = 0x0020;
+ hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1);
+ }
+
+ /* Shape the segment. */
+ hb_shape(font, buffer, NULL, 0);
+
+ /* Get new glyph info. */
+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL);
+
+ /* Write new codepoints. */
+ for (int i = 0; i < length; i++) {
+ hb_codepoint_t gid = info[i].codepoint;
+ codepoints[start+i] = gid;
+ }
+
+ /* Cleanup. */
+ hb_buffer_destroy(buffer);
+}
diff --git a/hb.h b/hb.h
new file mode 100644
index 0000000..b3e02d0
--- /dev/null
+++ b/hb.h
@@ -0,0 +1,7 @@
+#include <X11/Xft/Xft.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+void hbunloadfonts();
+void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int);
+
diff --git a/st.1 b/st.1
index e8d6059..39120b4 100644
--- a/st.1
+++ b/st.1
@@ -170,7 +170,8 @@ See the LICENSE file for the terms of redistribution.
.SH SEE ALSO
.BR tabbed (1),
.BR utmp (1),
-.BR stty (1)
+.BR stty (1),
+.BR scroll (1)
.SH BUGS
See the TODO file in the distribution.
diff --git a/st.c b/st.c
index c3bca25..18cdc95 100644
--- a/st.c
+++ b/st.c
@@ -39,7 +39,7 @@
/* macros */
#define IS_SET(flag) ((term.mode & (flag)) != 0)
-#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177')
+#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
#define ISDELIM(u) (u && wcschr(worddelimiters, u))
@@ -142,7 +142,7 @@ typedef struct {
/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
typedef struct {
char buf[ESC_BUF_SIZ]; /* raw string */
- int len; /* raw string length */
+ size_t len; /* raw string length */
char priv;
int arg[ESC_ARG_SIZ];
int narg; /* nb of args */
@@ -153,8 +153,9 @@ typedef struct {
/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
typedef struct {
char type; /* ESC type ... */
- char buf[STR_BUF_SIZ]; /* raw string */
- int len; /* raw string length */
+ char *buf; /* allocated raw string */
+ size_t siz; /* allocation size */
+ size_t len; /* raw string length */
char *args[STR_ARG_SIZ];
int narg; /* nb of args */
} STREscape;
@@ -372,8 +373,9 @@ static const char base64_digits[] = {
char
base64dec_getc(const char **src)
{
- while (**src && !isprint(**src)) (*src)++;
- return *((*src)++);
+ while (**src && !isprint(**src))
+ (*src)++;
+ return **src ? *((*src)++) : '='; /* emulate padding if string ends */
}
char *
@@ -391,6 +393,10 @@ base64dec(const char *src)
int c = base64_digits[(unsigned char) base64dec_getc(&src)];
int d = base64_digits[(unsigned char) base64dec_getc(&src)];
+ /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
+ if (a == -1 || b == -1)
+ break;
+
*dst++ = (a << 2) | ((b & 0x30) >> 4);
if (c == -1)
break;
@@ -635,7 +641,8 @@ getsel(void)
* st.
* FIXME: Fix the computer world.
*/
- if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
+ if ((y < sel.ne.y || lastx >= linelen) &&
+ (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
*ptr++ = '\n';
}
*ptr = 0;
@@ -666,7 +673,7 @@ die(const char *errstr, ...)
void
execsh(char *cmd, char **args)
{
- char *sh, *prog;
+ char *sh, *prog, *arg;
const struct passwd *pw;
errno = 0;
@@ -680,13 +687,20 @@ execsh(char *cmd, char **args)
if ((sh = getenv("SHELL")) == NULL)
sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
- if (args)
+ if (args) {
prog = args[0];
- else if (utmp)
+ arg = NULL;
+ } else if (scroll) {
+ prog = scroll;
+ arg = utmp ? utmp : sh;
+ } else if (utmp) {
prog = utmp;
- else
+ arg = NULL;
+ } else {
prog = sh;
- DEFAULT(args, ((char *[]) {prog, NULL}));
+ arg = NULL;
+ }
+ DEFAULT(args, ((char *[]) {prog, arg, NULL}));
unsetenv("COLUMNS");
unsetenv("LINES");
@@ -724,7 +738,7 @@ sigchld(int a)
die("child exited with status %d\n", WEXITSTATUS(stat));
else if (WIFSIGNALED(stat))
die("child terminated due to signal %d\n", WTERMSIG(stat));
- exit(0);
+ _exit(0);
}
void
@@ -817,21 +831,26 @@ ttyread(void)
{
static char buf[BUFSIZ];
static int buflen = 0;
- int written;
- int ret;
+ int ret, written;
/* append read bytes to unprocessed bytes */
- if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
- die("couldn't read from shell: %s\n", strerror(errno));
- buflen += ret;
+ ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
- written = twrite(buf, buflen, 0);
- buflen -= written;
- /* keep any uncomplete utf8 char for the next call */
- if (buflen > 0)
- memmove(buf, buf + written, buflen);
+ switch (ret) {
+ case 0:
+ exit(0);
+ case -1:
+ die("couldn't read from shell: %s\n", strerror(errno));
+ default:
+ buflen += ret;
+ written = twrite(buf, buflen, 0);
+ buflen -= written;
+ /* keep any incomplete UTF-8 byte sequence for the next call */
+ if (buflen > 0)
+ memmove(buf, buf + written, buflen);
+ return ret;
- return ret;
+ }
}
void
@@ -1108,7 +1127,8 @@ tscrolldown(int orig, int n, int copyhist)
term.line[i-n] = temp;
}
- selscroll(orig, n);
+ if (term.scr == 0)
+ selscroll(orig, n);
}
void
@@ -1138,7 +1158,8 @@ tscrollup(int orig, int n, int copyhist)
term.line[i+n] = temp;
}
- selscroll(orig, -n);
+ if (term.scr == 0)
+ selscroll(orig, -n);
}
void
@@ -1147,27 +1168,17 @@ selscroll(int orig, int n)
if (sel.ob.x == -1)
return;
- if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
- if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
+ 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();
- return;
- }
- if (sel.type == SEL_RECTANGULAR) {
- if (sel.ob.y < term.top)
- sel.ob.y = term.top;
- if (sel.oe.y > term.bot)
- sel.oe.y = term.bot;
} else {
- if (sel.ob.y < term.top) {
- sel.ob.y = term.top;
- sel.ob.x = 0;
- }
- if (sel.oe.y > term.bot) {
- sel.oe.y = term.bot;
- sel.oe.x = term.col;
- }
+ selnormalize();
}
- selnormalize();
}
}
@@ -1272,9 +1283,6 @@ tsetchar(Rune u, Glyph *attr, int x, int y)
term.dirty[y] = 1;
term.line[y][x] = *attr;
term.line[y][x].u = u;
-
- if (isboxdraw(u))
- term.line[y][x].mode |= ATTR_BOXDRAW;
}
void
@@ -1862,7 +1870,7 @@ csihandle(void)
void
csidump(void)
{
- int i;
+ size_t i;
uint c;
fprintf(stderr, "ESC[");
@@ -1980,7 +1988,7 @@ strparse(void)
void
strdump(void)
{
- int i;
+ size_t i;
uint c;
fprintf(stderr, "ESC%c", strescseq.type);
@@ -2007,7 +2015,10 @@ strdump(void)
void
strreset(void)
{
- memset(&strescseq, 0, sizeof(strescseq));
+ strescseq = (STREscape){
+ .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
+ .siz = STR_BUF_SIZ,
+ };
}
void
@@ -2065,7 +2076,7 @@ tdumpline(int n)
bp = &term.line[n][0];
end = &bp[MIN(tlinelen(n), term.col) - 1];
if (bp != end || bp->u != ' ') {
- for ( ;bp <= end; ++bp)
+ for ( ; bp <= end; ++bp)
tprinter(buf, utf8encode(bp->u, buf));
}
tprinter("\n", 1);
@@ -2195,6 +2206,7 @@ tcontrolcode(uchar ascii)
return;
case '\032': /* SUB */
tsetchar('?', &term.c.attr, term.c.x, term.c.y);
+ /* FALLTHROUGH */
case '\030': /* CAN */
csireset();
break;
@@ -2349,15 +2361,13 @@ tputc(Rune u)
Glyph *gp;
control = ISCONTROL(u);
- if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
+ if (u < 127 || !IS_SET(MODE_UTF8 | MODE_SIXEL)) {
c[0] = u;
width = len = 1;
} else {
len = utf8encode(u, c);
- if (!control && (width = wcwidth(u)) == -1) {
- memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
+ if (!control && (width = wcwidth(u)) == -1)
width = 1;
- }
}
if (IS_SET(MODE_PRINT))
@@ -2389,7 +2399,7 @@ tputc(Rune u)
if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
term.mode |= MODE_SIXEL;
- if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
+ if (strescseq.len+len >= strescseq.siz) {
/*
* Here is a bug in terminals. If the user never sends
* some code to stop the str or esc command, then st
@@ -2403,7 +2413,10 @@ tputc(Rune u)
* term.esc = 0;
* strhandle();
*/
- return;
+ if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
+ return;
+ strescseq.siz *= 2;
+ strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
}
memmove(&strescseq.buf[strescseq.len], c, len);
@@ -2452,7 +2465,7 @@ check_control_code:
*/
return;
}
- if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
+ if (selected(term.c.x, term.c.y))
selclear();
gp = &term.line[term.c.y][term.c.x];
@@ -2618,6 +2631,7 @@ void
drawregion(int x1, int y1, int x2, int y2)
{
int y;
+
for (y = y1; y < y2; y++) {
if (!term.dirty[y])
continue;
@@ -2630,7 +2644,7 @@ drawregion(int x1, int y1, int x2, int y2)
void
draw(void)
{
- int cx = term.c.x;
+ int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
if (!xstartdraw())
return;
@@ -2646,10 +2660,13 @@ draw(void)
drawregion(0, 0, term.col, term.row);
if (term.scr == 0)
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
- term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
- term.ocx = cx, term.ocy = term.c.y;
+ term.ocx, term.ocy, term.line[term.ocy][term.ocx],
+ term.line[term.ocy], term.col);
+ term.ocx = cx;
+ term.ocy = term.c.y;
xfinishdraw();
- xximspot(term.ocx, term.ocy);
+ if (ocx != term.ocx || ocy != term.ocy)
+ xximspot(term.ocx, term.ocy);
}
void
diff --git a/st.h b/st.h
index ab868d2..01bfb05 100644
--- a/st.h
+++ b/st.h
@@ -11,7 +11,8 @@
#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
-#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \
+#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \
+ (a).fg != (b).fg || \
(a).bg != (b).bg)
#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
(t1.tv_nsec-t2.tv_nsec)/1E6)
@@ -33,7 +34,7 @@ enum glyph_attribute {
ATTR_WRAP = 1 << 8,
ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10,
- ATTR_BOXDRAW = 1 << 11,
+ ATTR_LIGA = 1 << 11,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
};
@@ -75,6 +76,7 @@ typedef union {
uint ui;
float f;
const void *v;
+ const char *s;
} Arg;
void die(const char *, ...);
@@ -113,16 +115,9 @@ void *xmalloc(size_t);
void *xrealloc(void *, size_t);
char *xstrdup(char *);
-int isboxdraw(Rune);
-ushort boxdrawindex(const Glyph *);
-#ifdef XFT_VERSION
-/* only exposed to x.c, otherwise we'll need Xft.h for the types */
-void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *);
-void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int);
-#endif
-
/* config.h globals */
extern char *utmp;
+extern char *scroll;
extern char *stty_args;
extern char *vtiden;
extern wchar_t *worddelimiters;
@@ -132,4 +127,3 @@ extern unsigned int tabspaces;
extern unsigned int defaultfg;
extern unsigned int defaultbg;
extern float alpha;
-extern const int boxdraw, boxdraw_bold, boxdraw_braille;
diff --git a/st.info b/st.info
index 52fc617..d0694e2 100644
--- a/st.info
+++ b/st.info
@@ -1,4 +1,4 @@
-st| simpleterm,
+st-mono| simpleterm monocolor,
acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
am,
bce,
@@ -10,7 +10,7 @@ st| simpleterm,
civis=\E[?25l,
clear=\E[H\E[2J,
cnorm=\E[?12l\E[?25h,
- colors#8,
+ colors#2,
cols#80,
cr=^M,
csr=\E[%i%p1%d;%p2%dr,
@@ -158,6 +158,7 @@ st| simpleterm,
rc=\E8,
rev=\E[7m,
ri=\EM,
+ rin=\E[%p1%dT,
ritm=\E[23m,
rmacs=\E(B,
rmcup=\E[?1049l,
@@ -168,13 +169,8 @@ st| simpleterm,
rs1=\Ec,
rs2=\E[4l\E>\E[?1034l,
sc=\E7,
- setab=\E[4%p1%dm,
- setaf=\E[3%p1%dm,
- setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
- setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
- sgr0=\E[0m,
- sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
sitm=\E[3m,
+ sgr0=\E[0m,
smacs=\E(0,
smcup=\E[?1049h,
smir=\E[4h,
@@ -189,10 +185,19 @@ st| simpleterm,
rmxx=\E[29m,
smxx=\E[9m,
# tmux extensions, see TERMINFO EXTENSIONS in tmux(1)
- Se,
- Ss,
Tc,
Ms=\E]52;%p1%s;%p2%s\007,
+ Se=\E[2 q,
+ Ss=\E[%p1%d q,
+
+st| simpleterm,
+ use=st-mono,
+ colors#8,
+ setab=\E[4%p1%dm,
+ setaf=\E[3%p1%dm,
+ setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
+ setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
+ sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
st-256color| simpleterm with 256 colors,
use=st,
@@ -220,3 +225,13 @@ st-meta-256color| simpleterm with meta key and 256 colors,
smm=\E[?1034h,
rs2=\E[4l\E>\E[?1034h,
is2=\E[4l\E>\E[?1034h,
+
+st-bs| simpleterm with backspace as backspace,
+ use=st,
+ kbs=\010,
+ kdch1=\177,
+
+st-bs-256color| simpleterm with backspace as backspace and 256colors,
+ use=st-256color,
+ kbs=\010,
+ kdch1=\177,
diff --git a/win.h b/win.h
index a6ef1b9..bc0d180 100644
--- a/win.h
+++ b/win.h
@@ -25,7 +25,7 @@ enum win_mode {
void xbell(void);
void xclipcopy(void);
-void xdrawcursor(int, int, Glyph, int, int, Glyph);
+void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
void xdrawline(Line, int, int, int);
void xfinishdraw(void);
void xloadcols(void);
diff --git a/x.c b/x.c
index 04bcbe2..c3ff2cd 100644
--- a/x.c
+++ b/x.c
@@ -15,10 +15,11 @@
#include <X11/Xft/Xft.h>
#include <X11/XKBlib.h>
-static char *argv0;
+char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
+#include "hb.h"
/* types used in config.h */
typedef struct {
@@ -29,9 +30,11 @@ typedef struct {
} Shortcut;
typedef struct {
- uint b;
- uint mask;
- char *s;
+ uint mod;
+ uint button;
+ void (*func)(const Arg *);
+ const Arg arg;
+ uint release;
} MouseShortcut;
typedef struct {
@@ -56,6 +59,7 @@ static void selpaste(const Arg *);
static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
+static void ttysend(const Arg *);
/* config.h for applying patches and the configuration. */
#include "config.h"
@@ -80,7 +84,6 @@ typedef struct {
int w, h; /* window width and height */
int ch; /* char height */
int cw; /* char width */
- int cyo; /* char y offset */
int mode; /* window state/mode flags */
int cursor; /* cursor style */
} TermWindow;
@@ -92,8 +95,12 @@ typedef struct {
Drawable buf;
GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
Atom xembed, wmdeletewin, netwmname, netwmpid;
- XIM xim;
- XIC xic;
+ struct {
+ XIM xim;
+ XIC xic;
+ XPoint spot;
+ XVaNestedList spotlist;
+ } ime;
Draw draw;
Visual *vis;
XSetWindowAttributes attrs;
@@ -141,9 +148,10 @@ 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 void ximopen(Display *);
+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);
@@ -167,6 +175,8 @@ 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 *);
@@ -318,6 +328,12 @@ zoomreset(const Arg *arg)
}
}
+void
+ttysend(const Arg *arg)
+{
+ ttywrite(arg->s, strlen(arg->s), 1);
+}
+
int
evcol(XEvent *e)
{
@@ -338,7 +354,7 @@ void
mousesel(XEvent *e, int done)
{
int type, seltype = SEL_REGULAR;
- uint state = e->xbutton.state & ~(Button1Mask | forceselmod);
+ uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
for (type = 1; type < LEN(selmasks); ++type) {
if (match(selmasks[type], state)) {
@@ -414,25 +430,51 @@ mousereport(XEvent *e)
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)
{
struct timespec now;
- MouseShortcut *ms;
int snap;
- if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
return;
}
- for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
- if (e->xbutton.button == ms->b
- && match(ms->mask, e->xbutton.state)) {
- ttywrite(ms->s, strlen(ms->s), 1);
- return;
- }
- }
+ if (mouseaction(e, 0))
+ return;
if (e->xbutton.button == Button1) {
/*
@@ -648,21 +690,21 @@ xsetsel(char *str)
void
brelease(XEvent *e)
{
- if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
return;
}
- if (e->xbutton.button == Button2)
- selpaste(NULL);
- else if (e->xbutton.button == Button1)
+ if (mouseaction(e, 1))
+ return;
+ if (e->xbutton.button == Button1)
mousesel(e, 1);
}
void
bmotion(XEvent *e)
{
- if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) {
+ if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
return;
}
@@ -971,7 +1013,6 @@ xloadfonts(char *fontstr, double fontsize)
/* Setting character width and height. */
win.cw = ceilf(dc.font.width * cwscale);
win.ch = ceilf(dc.font.height * chscale);
- win.cyo = ceilf(dc.font.height * (chscale - 1) / 2);
FcPatternDel(pattern, FC_SLANT);
FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
@@ -1098,6 +1139,9 @@ xunloadfont(Font *f)
void
xunloadfonts(void)
{
+ /* Clear Harfbuzz font cache. */
+ hbunloadfonts();
+
/* Free the loaded fonts in the font cache. */
while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font);
@@ -1108,41 +1152,58 @@ xunloadfonts(void)
xunloadfont(&dc.ibfont);
}
-void
+int
ximopen(Display *dpy)
{
- XIMCallback destroy = { .client_data = NULL, .callback = ximdestroy };
+ XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
+ XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
- if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
- XSetLocaleModifiers("@im=local");
- if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) {
- XSetLocaleModifiers("@im=");
- if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL)
- die("XOpenIM failed. Could not open input device.\n");
- }
+ 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 (XSetIMValues(xw.xim, XNDestroyCallback, &destroy, NULL) != NULL)
- die("XSetIMValues failed. Could not set input method value.\n");
- xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
- XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL);
- if (xw.xic == NULL)
- die("XCreateIC failed. Could not obtain input method.\n");
+ if (xw.ime.xic == NULL)
+ fprintf(stderr, "XCreateIC: Could not create input context.\n");
+
+ return 1;
}
void
ximinstantiate(Display *dpy, XPointer client, XPointer call)
{
- ximopen(dpy);
- XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
- ximinstantiate, NULL);
+ if (ximopen(dpy))
+ XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
}
void
ximdestroy(XIM xim, XPointer client, XPointer call)
{
- xw.xim = NULL;
+ xw.ime.xim = NULL;
XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
- ximinstantiate, NULL);
+ ximinstantiate, NULL);
+ XFree(xw.ime.spotlist);
+}
+
+int
+xicdestroy(XIC xim, XPointer client, XPointer call)
+{
+ xw.ime.xic = NULL;
+ return 1;
}
void
@@ -1221,7 +1282,10 @@ xinit(int cols, int rows)
xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
/* input methods */
- ximopen(xw.dpy);
+ if (!ximopen(xw.dpy)) {
+ XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
+ ximinstantiate, NULL);
+ }
/* white cursor, black outline */
cursor = XCreateFontCursor(xw.dpy, mouseshape);
@@ -1252,8 +1316,8 @@ xinit(int cols, int rows)
win.mode = MODE_NUMLOCK;
resettitle();
- XMapWindow(xw.dpy, xw.win);
xhints();
+ XMapWindow(xw.dpy, xw.win);
XSync(xw.dpy, False);
clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
@@ -1263,8 +1327,6 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
-
- boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
}
int
@@ -1283,13 +1345,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
FcCharSet *fccharset;
int i, f, numspecs = 0;
- for (i = 0, xp = winx, yp = winy + font->ascent + win.cyo; i < len; ++i) {
+ 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)
+ if (mode & ATTR_WDUMMY)
continue;
/* Determine font for glyph if different from previous glyph. */
@@ -1308,16 +1370,11 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
font = &dc.bfont;
frcflags = FRC_BOLD;
}
- yp = winy + font->ascent + win.cyo;
+ yp = winy + font->ascent;
}
- if (mode & ATTR_BOXDRAW) {
- /* minor shoehorning: boxdraw uses only this ushort */
- glyphidx = boxdrawindex(&glyphs[i]);
- } else {
- /* Lookup character index with default font. */
- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
- }
+ /* Lookup character index with default font. */
+ glyphidx = XftCharIndex(xw.dpy, font->match, rune);
if (glyphidx) {
specs[numspecs].font = font->match;
specs[numspecs].glyph = glyphidx;
@@ -1401,6 +1458,9 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
numspecs++;
}
+ /* Harfbuzz transformation for ligatures. */
+ hbtransform(specs, glyphs, len, x, y);
+
return numspecs;
}
@@ -1521,21 +1581,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
r.width = width;
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
- if (base.mode & ATTR_BOXDRAW) {
- drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
- } else {
- /* Render the glyphs. */
- XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
- }
+ /* Render the glyphs. */
+ XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) {
- XftDrawRect(xw.draw, fg, winx, winy + win.cyo + dc.font.ascent + 1,
+ XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
width, 1);
}
if (base.mode & ATTR_STRUCK) {
- XftDrawRect(xw.draw, fg, winx, winy + win.cyo + 2 * dc.font.ascent / 3,
+ XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
width, 1);
}
@@ -1554,14 +1610,17 @@ xdrawglyph(Glyph g, int x, int y)
}
void
-xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
+xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
{
Color drawcol;
/* remove the old cursor */
if (selected(ox, oy))
og.mode ^= ATTR_REVERSE;
- xdrawglyph(og, ox, oy);
+
+ /* Redraw the line where cursor was previously.
+ * It will restore the ligatures broken by the cursor. */
+ xdrawline(line, 0, oy, len);
if (IS_SET(MODE_HIDE))
return;
@@ -1569,7 +1628,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
/*
* Select the right color for the right mode.
*/
- g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
+ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
if (IS_SET(MODE_REVERSE)) {
g.mode |= ATTR_REVERSE;
@@ -1595,8 +1654,9 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
/* draw the new one */
if (IS_SET(MODE_FOCUSED)) {
switch (win.cursor) {
- case 7: /* st extension: snowman (U+2603) */
- g.u = 0x2603;
+ case 7: /* st extension */
+ g.u = 0x2603; /* snowman (U+2603) */
+ /* FALLTHROUGH */
case 0: /* Blinking Block */
case 1: /* Blinking Block (Default) */
case 2: /* Steady Block */
@@ -1710,11 +1770,13 @@ xfinishdraw(void)
void
xximspot(int x, int y)
{
- XPoint spot = { borderpx + x * win.cw, borderpx + (y + 1) * win.ch };
- XVaNestedList attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
+ if (xw.ime.xic == NULL)
+ return;
- XSetICValues(xw.xic, XNPreeditAttributes, attr, NULL);
- XFree(attr);
+ 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
@@ -1756,8 +1818,7 @@ xsetmode(int set, unsigned int flags)
int
xsetcursor(int cursor)
{
- DEFAULT(cursor, 1);
- if (!BETWEEN(cursor, 0, 6))
+ if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
return 1;
win.cursor = cursor;
return 0;
@@ -1791,13 +1852,15 @@ focus(XEvent *ev)
return;
if (ev->type == FocusIn) {
- XSetICFocus(xw.xic);
+ 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 {
- XUnsetICFocus(xw.xic);
+ if (xw.ime.xic)
+ XUnsetICFocus(xw.ime.xic);
win.mode &= ~MODE_FOCUSED;
if (IS_SET(MODE_FOCUS))
ttywrite("\033[O", 3, 0);
@@ -1852,7 +1915,7 @@ kpress(XEvent *ev)
{
XKeyEvent *e = &ev->xkey;
KeySym ksym;
- char buf[32], *customkey;
+ char buf[64], *customkey;
int len;
Rune c;
Status status;
@@ -1861,7 +1924,10 @@ kpress(XEvent *ev)
if (IS_SET(MODE_KBDLOCK))
return;
- len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status);
+ if (xw.ime.xic)
+ len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
+ else
+ len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
/* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state)) {
@@ -1929,10 +1995,9 @@ run(void)
XEvent ev;
int w = win.w, h = win.h;
fd_set rfd;
- int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0;
- int ttyfd;
- struct timespec drawtimeout, *tv = NULL, now, last, lastblink;
- long deltatime;
+ int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
+ struct timespec seltv, *tv, now, lastblink, trigger;
+ double timeout;
/* Waiting for window mapping */
do {
@@ -1953,82 +2018,77 @@ run(void)
ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
cresize(w, h);
- clock_gettime(CLOCK_MONOTONIC, &last);
- lastblink = last;
-
- for (xev = actionfps;;) {
+ 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));
}
- if (FD_ISSET(ttyfd, &rfd)) {
- ttyread();
- if (blinktimeout) {
- blinkset = tattrset(ATTR_BLINK);
- if (!blinkset)
- MODBIT(win.mode, 0, MODE_BLINK);
- }
- }
+ clock_gettime(CLOCK_MONOTONIC, &now);
- if (FD_ISSET(xfd, &rfd))
- xev = actionfps;
+ if (FD_ISSET(ttyfd, &rfd))
+ ttyread();
- clock_gettime(CLOCK_MONOTONIC, &now);
- drawtimeout.tv_sec = 0;
- drawtimeout.tv_nsec = (1000 * 1E6)/ xfps;
- tv = &drawtimeout;
-
- dodraw = 0;
- if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) {
- tsetdirtattr(ATTR_BLINK);
- win.mode ^= MODE_BLINK;
- lastblink = now;
- dodraw = 1;
- }
- deltatime = TIMEDIFF(now, last);
- if (deltatime > 1000 / (xev ? xfps : actionfps)) {
- dodraw = 1;
- last = now;
+ 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);
}
- if (dodraw) {
- while (XPending(xw.dpy)) {
- 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 */
+ }
- draw();
- XFlush(xw.dpy);
-
- if (xev && !FD_ISSET(xfd, &rfd))
- xev--;
- if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) {
- if (blinkset) {
- if (TIMEDIFF(now, lastblink) \
- > blinktimeout) {
- drawtimeout.tv_nsec = 1000;
- } else {
- drawtimeout.tv_nsec = (1E6 * \
- (blinktimeout - \
- TIMEDIFF(now,
- lastblink)));
- }
- drawtimeout.tv_sec = \
- drawtimeout.tv_nsec / 1E9;
- drawtimeout.tv_nsec %= (long)1E9;
- } else {
- tv = NULL;
- }
+ /* 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;
}
}
@@ -2050,7 +2110,7 @@ main(int argc, char *argv[])
{
xw.l = xw.t = 0;
xw.isfixed = False;
- win.cursor = cursorshape;
+ xsetcursor(cursorshape);
ARGBEGIN {
case 'a':