aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile6
-rw-r--r--PATCHES12
-rw-r--r--boxdraw.c194
-rw-r--r--boxdraw_data.h214
-rw-r--r--config.def.h107
-rw-r--r--config.mk3
-rw-r--r--st.c1277
-rw-r--r--st.desktop12
-rw-r--r--st.h45
-rw-r--r--st.info5
-rw-r--r--win.h4
-rw-r--r--x.c757
13 files changed, 2247 insertions, 392 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..51447da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+config.h
+st
diff --git a/Makefile b/Makefile
index 15db421..5132a07 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
include config.mk
-SRC = st.c x.c
+SRC = st.c x.c boxdraw.c
OBJ = $(SRC:.c=.o)
all: st
@@ -17,6 +17,7 @@ config.h:
st.o: config.h st.h win.h
x.o: arg.h config.h st.h win.h
+boxdraw.o: config.h st.h boxdraw_data.h
$(OBJ): config.h config.mk
@@ -43,9 +44,12 @@ install: st
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
tic -sx st.info
@echo Please see the README file regarding the terminfo entry of st.
+ mkdir -p $(DESTDIR)$(APPPREFIX)
+ cp -f st.desktop $(DESTDIR)$(APPPREFIX)
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st
+ rm -f $(DESTDIR)$(APPPREFIX)/st.desktop
rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
.PHONY: all clean dist install uninstall
diff --git a/PATCHES b/PATCHES
new file mode 100644
index 0000000..1433cbf
--- /dev/null
+++ b/PATCHES
@@ -0,0 +1,12 @@
+st-boxdraw_v2-0.8.5.diff
+st-clipboard-20180309-c5ba9c0.diff
+st-copyurl-multiline-20230406-211964d.diff (modified to not copy url, just highlight it. func renamed to selurl)
+st-csi_22_23-0.8.5.diff
+st-desktopentry-0.8.5.diff
+st-drag-n-drop-0.9.2.diff
+st-expected-anysize-0.9.diff
+st-open-selected-0.9.2.diff
+st-scrollback-0.9.2.diff
+st-scrollback-reflow-0.9.2.diff
+st-themed_cursor-0.8.1.diff
+st-undercurl-0.9-20240103.diff
diff --git a/boxdraw.c b/boxdraw.c
new file mode 100644
index 0000000..28a92d0
--- /dev/null
+++ b/boxdraw.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
+ * MIT/X Consortium License
+ */
+
+#include <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
new file mode 100644
index 0000000..7890500
--- /dev/null
+++ b/boxdraw_data.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
+ * MIT/X Consortium License
+ */
+
+/*
+ * U+25XX codepoints data
+ *
+ * References:
+ * http://www.unicode.org/charts/PDF/U2500.pdf
+ * http://www.unicode.org/charts/PDF/U2580.pdf
+ *
+ * Test page:
+ * https://github.com/GNOME/vte/blob/master/doc/boxes.txt
+ */
+
+/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */
+/* Categories (mutually exclusive except BDB): */
+/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */
+#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */
+#define BDA (1<<9) /* Box Draw Arc (light) */
+
+#define BBD (1<<10) /* Box Block Down (lower) X/8 */
+#define BBL (2<<10) /* Box Block Left X/8 */
+#define BBU (3<<10) /* Box Block Upper X/8 */
+#define BBR (4<<10) /* Box Block Right X/8 */
+#define BBQ (5<<10) /* Box Block Quadrants */
+#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */
+
+#define BBS (1<<14) /* Box Block Shades */
+#define BDB (1<<15) /* Box Draw is Bold */
+
+/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */
+/* Heavy is light+double (literally drawing light+double align to form heavy) */
+#define LL (1<<0)
+#define LU (1<<1)
+#define LR (1<<2)
+#define LD (1<<3)
+#define LH (LL+LR)
+#define LV (LU+LD)
+
+#define DL (1<<4)
+#define DU (1<<5)
+#define DR (1<<6)
+#define DD (1<<7)
+#define DH (DL+DR)
+#define DV (DU+DD)
+
+#define HL (LL+DL)
+#define HU (LU+DU)
+#define HR (LR+DR)
+#define HD (LD+DD)
+#define HH (HL+HR)
+#define HV (HU+HD)
+
+/* (BBQ) Quadrants Top/Bottom x Left/Right */
+#define TL (1<<0)
+#define TR (1<<1)
+#define BL (1<<2)
+#define BR (1<<3)
+
+/* Data for U+2500 - U+259F except dashes/diagonals */
+static const unsigned short boxdata[256] = {
+ /* light lines */
+ [0x00] = BDL + LH, /* light horizontal */
+ [0x02] = BDL + LV, /* light vertical */
+ [0x0c] = BDL + LD + LR, /* light down and right */
+ [0x10] = BDL + LD + LL, /* light down and left */
+ [0x14] = BDL + LU + LR, /* light up and right */
+ [0x18] = BDL + LU + LL, /* light up and left */
+ [0x1c] = BDL + LV + LR, /* light vertical and right */
+ [0x24] = BDL + LV + LL, /* light vertical and left */
+ [0x2c] = BDL + LH + LD, /* light horizontal and down */
+ [0x34] = BDL + LH + LU, /* light horizontal and up */
+ [0x3c] = BDL + LV + LH, /* light vertical and horizontal */
+ [0x74] = BDL + LL, /* light left */
+ [0x75] = BDL + LU, /* light up */
+ [0x76] = BDL + LR, /* light right */
+ [0x77] = BDL + LD, /* light down */
+
+ /* heavy [+light] lines */
+ [0x01] = BDL + HH,
+ [0x03] = BDL + HV,
+ [0x0d] = BDL + HR + LD,
+ [0x0e] = BDL + HD + LR,
+ [0x0f] = BDL + HD + HR,
+ [0x11] = BDL + HL + LD,
+ [0x12] = BDL + HD + LL,
+ [0x13] = BDL + HD + HL,
+ [0x15] = BDL + HR + LU,
+ [0x16] = BDL + HU + LR,
+ [0x17] = BDL + HU + HR,
+ [0x19] = BDL + HL + LU,
+ [0x1a] = BDL + HU + LL,
+ [0x1b] = BDL + HU + HL,
+ [0x1d] = BDL + HR + LV,
+ [0x1e] = BDL + HU + LD + LR,
+ [0x1f] = BDL + HD + LR + LU,
+ [0x20] = BDL + HV + LR,
+ [0x21] = BDL + HU + HR + LD,
+ [0x22] = BDL + HD + HR + LU,
+ [0x23] = BDL + HV + HR,
+ [0x25] = BDL + HL + LV,
+ [0x26] = BDL + HU + LD + LL,
+ [0x27] = BDL + HD + LU + LL,
+ [0x28] = BDL + HV + LL,
+ [0x29] = BDL + HU + HL + LD,
+ [0x2a] = BDL + HD + HL + LU,
+ [0x2b] = BDL + HV + HL,
+ [0x2d] = BDL + HL + LD + LR,
+ [0x2e] = BDL + HR + LL + LD,
+ [0x2f] = BDL + HH + LD,
+ [0x30] = BDL + HD + LH,
+ [0x31] = BDL + HD + HL + LR,
+ [0x32] = BDL + HR + HD + LL,
+ [0x33] = BDL + HH + HD,
+ [0x35] = BDL + HL + LU + LR,
+ [0x36] = BDL + HR + LU + LL,
+ [0x37] = BDL + HH + LU,
+ [0x38] = BDL + HU + LH,
+ [0x39] = BDL + HU + HL + LR,
+ [0x3a] = BDL + HU + HR + LL,
+ [0x3b] = BDL + HH + HU,
+ [0x3d] = BDL + HL + LV + LR,
+ [0x3e] = BDL + HR + LV + LL,
+ [0x3f] = BDL + HH + LV,
+ [0x40] = BDL + HU + LH + LD,
+ [0x41] = BDL + HD + LH + LU,
+ [0x42] = BDL + HV + LH,
+ [0x43] = BDL + HU + HL + LD + LR,
+ [0x44] = BDL + HU + HR + LD + LL,
+ [0x45] = BDL + HD + HL + LU + LR,
+ [0x46] = BDL + HD + HR + LU + LL,
+ [0x47] = BDL + HH + HU + LD,
+ [0x48] = BDL + HH + HD + LU,
+ [0x49] = BDL + HV + HL + LR,
+ [0x4a] = BDL + HV + HR + LL,
+ [0x4b] = BDL + HV + HH,
+ [0x78] = BDL + HL,
+ [0x79] = BDL + HU,
+ [0x7a] = BDL + HR,
+ [0x7b] = BDL + HD,
+ [0x7c] = BDL + HR + LL,
+ [0x7d] = BDL + HD + LU,
+ [0x7e] = BDL + HL + LR,
+ [0x7f] = BDL + HU + LD,
+
+ /* double [+light] lines */
+ [0x50] = BDL + DH,
+ [0x51] = BDL + DV,
+ [0x52] = BDL + DR + LD,
+ [0x53] = BDL + DD + LR,
+ [0x54] = BDL + DR + DD,
+ [0x55] = BDL + DL + LD,
+ [0x56] = BDL + DD + LL,
+ [0x57] = BDL + DL + DD,
+ [0x58] = BDL + DR + LU,
+ [0x59] = BDL + DU + LR,
+ [0x5a] = BDL + DU + DR,
+ [0x5b] = BDL + DL + LU,
+ [0x5c] = BDL + DU + LL,
+ [0x5d] = BDL + DL + DU,
+ [0x5e] = BDL + DR + LV,
+ [0x5f] = BDL + DV + LR,
+ [0x60] = BDL + DV + DR,
+ [0x61] = BDL + DL + LV,
+ [0x62] = BDL + DV + LL,
+ [0x63] = BDL + DV + DL,
+ [0x64] = BDL + DH + LD,
+ [0x65] = BDL + DD + LH,
+ [0x66] = BDL + DD + DH,
+ [0x67] = BDL + DH + LU,
+ [0x68] = BDL + DU + LH,
+ [0x69] = BDL + DH + DU,
+ [0x6a] = BDL + DH + LV,
+ [0x6b] = BDL + DV + LH,
+ [0x6c] = BDL + DH + DV,
+
+ /* (light) arcs */
+ [0x6d] = BDA + LD + LR,
+ [0x6e] = BDA + LD + LL,
+ [0x6f] = BDA + LU + LL,
+ [0x70] = BDA + LU + LR,
+
+ /* Lower (Down) X/8 block (data is 8 - X) */
+ [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4,
+ [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0,
+
+ /* Left X/8 block (data is X) */
+ [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4,
+ [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1,
+
+ /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */
+ [0x80] = BBU + 4, [0x94] = BBU + 1,
+ [0x90] = BBR + 4, [0x95] = BBR + 7,
+
+ /* Quadrants */
+ [0x96] = BBQ + BL,
+ [0x97] = BBQ + BR,
+ [0x98] = BBQ + TL,
+ [0x99] = BBQ + TL + BL + BR,
+ [0x9a] = BBQ + TL + BR,
+ [0x9b] = BBQ + TL + TR + BL,
+ [0x9c] = BBQ + TL + TR + BR,
+ [0x9d] = BBQ + TR,
+ [0x9e] = BBQ + BL + TR,
+ [0x9f] = BBQ + BL + TR + BR,
+
+ /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */
+ [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3,
+
+ /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */
+ /* U+2571 - U+2573: unsupported (diagonals) */
+};
diff --git a/config.def.h b/config.def.h
index 2cd740a..779056e 100644
--- a/config.def.h
+++ b/config.def.h
@@ -5,8 +5,8 @@
*
* font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
*/
-static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
-static int borderpx = 2;
+static char *font = "monospace:size=12:antialias=true:autohint=true";
+static int borderpx = 0;
/*
* What program is execed by st depends of these precedence rules:
@@ -68,6 +68,18 @@ static unsigned int blinktimeout = 800;
static unsigned int cursorthickness = 2;
/*
+ * 1: render most of the lines/blocks characters without using the font for
+ * perfect alignment between cells (U2500 - U259F except dashes/diagonals).
+ * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored.
+ * 0: disable (render all U25XX glyphs normally from the font).
+ */
+const int boxdraw = 0;
+const int boxdraw_bold = 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
*/
@@ -91,37 +103,42 @@ char *termname = "st-256color";
*
* stty tabs
*/
-unsigned int tabspaces = 8;
+unsigned int tabspaces = 8; // TODO change to 4
+
+/*
+ * drag and drop escape characters
+ *
+ * this will add a '\' before any characters specified in the string.
+ */
+char *xdndescchar = " !\"#$&'()*;<>?[\\]^`{|}~";
/* Terminal colors (16 first used in escape sequence) */
static const char *colorname[] = {
/* 8 normal colors */
- "black",
- "red3",
- "green3",
- "yellow3",
- "blue2",
- "magenta3",
- "cyan3",
- "gray90",
+ "#101010",
+ "#ed1207",
+ "#2bb500",
+ "#fc9700",
+ "#1578c1",
+ "#b14ff7",
+ "#00c4a3",
+ "white",
/* 8 bright colors */
"gray50",
- "red",
- "green",
- "yellow",
- "#5c5cff",
- "magenta",
- "cyan",
+ "#ed1207",
+ "#2bb500",
+ "#fc9700",
+ "#1578c1",
+ "#b14ff7",
+ "#00c4a3",
"white",
[255] = 0,
- /* more colors can be added after 255 to use with DefaultXX */
+ /* more colors can be added after 255 to use with defaultxx */
"#cccccc",
"#555555",
- "gray90", /* default foreground colour */
- "black", /* default background colour */
};
@@ -129,8 +146,8 @@ static const char *colorname[] = {
* Default colors (colorname index)
* foreground, background, cursor, reverse cursor
*/
-unsigned int defaultfg = 258;
-unsigned int defaultbg = 259;
+unsigned int defaultfg = 7;
+unsigned int defaultbg = 0;
unsigned int defaultcs = 256;
static unsigned int defaultrcs = 257;
@@ -151,11 +168,10 @@ static unsigned int cols = 80;
static unsigned int rows = 24;
/*
- * Default colour and shape of the mouse cursor
++ * Default shape of the mouse cursor
*/
-static unsigned int mouseshape = XC_xterm;
-static unsigned int mousefg = 7;
-static unsigned int mousebg = 0;
+
+static char* mouseshape = "xterm";
/*
* Color used to display font attributes when fontconfig selected a font which
@@ -176,7 +192,13 @@ static uint forcemousemod = ShiftMask;
*/
static MouseShortcut mshortcuts[] = {
/* mask button function argument release */
+ { ControlMask, Button4, zoom, {.f = +1}, 0 },
+ { ControlMask, Button5, zoom, {.f = -1}, 0 },
+ { XK_ANY_MOD, Button4, kscrollup, {.i = 3}, 0 },
+ { XK_ANY_MOD, Button5, kscrolldown, {.i = 3}, 0 },
+ { ControlMask, Button2, selopen, {.i = 0}, 1 },
{ XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
+ { XK_ANY_MOD, Button3, 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~"} },
@@ -184,7 +206,7 @@ static MouseShortcut mshortcuts[] = {
};
/* Internal keyboard shortcuts. */
-#define MODKEY Mod1Mask
+#define MODKEY Mod1Mask /* alt key */
#define TERMMOD (ControlMask|ShiftMask)
static Shortcut shortcuts[] = {
@@ -195,12 +217,19 @@ static Shortcut shortcuts[] = {
{ XK_ANY_MOD, XK_Print, printsel, {.i = 0} },
{ TERMMOD, XK_Prior, zoom, {.f = +1} },
{ TERMMOD, XK_Next, zoom, {.f = -1} },
+ { ControlMask, XK_equal, zoom, {.f = +1} },
+ { ControlMask, XK_minus, zoom, {.f = -1} },
{ TERMMOD, XK_Home, zoomreset, {.f = 0} },
{ TERMMOD, XK_C, clipcopy, {.i = 0} },
{ TERMMOD, XK_V, clippaste, {.i = 0} },
{ TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
+ { MODKEY, XK_k, selurl, {.i = 0} },
+ { MODKEY, XK_j, selurl, {.i = 1} },
+ { MODKEY, XK_o, selopen, {.i = 0} },
+ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
+ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
};
/*
@@ -472,3 +501,27 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/**
+ * Undercurl style. Set UNDERCURL_STYLE to one of the available styles.
+ *
+ * Curly: Dunno how to draw it *shrug*
+ * _ _ _ _
+ * ( ) ( ) ( ) ( )
+ * (_) (_) (_) (_)
+ *
+ * Spiky:
+ * /\ /\ /\ /\
+ * \/ \/ \/
+ *
+ * Capped:
+ * _ _ _
+ * / \ / \ / \
+ * \_/ \_/
+ */
+// Available styles
+#define UNDERCURL_CURLY 0
+#define UNDERCURL_SPIKY 1
+#define UNDERCURL_CAPPED 2
+// Active style
+#define UNDERCURL_STYLE UNDERCURL_SPIKY
diff --git a/config.mk b/config.mk
index 2fc854e..12ca220 100644
--- a/config.mk
+++ b/config.mk
@@ -5,6 +5,7 @@ VERSION = 0.9.3
# paths
PREFIX = /usr/local
+APPPREFIX = $(PREFIX)/share/applications
MANPREFIX = $(PREFIX)/share/man
X11INC = /usr/X11R6/include
@@ -16,7 +17,7 @@ PKG_CONFIG = pkg-config
INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2`
-LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
+LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXcursor \
`$(PKG_CONFIG) --libs fontconfig` \
`$(PKG_CONFIG) --libs freetype2`
diff --git a/st.c b/st.c
index 8e57991..2df7f1e 100644
--- a/st.c
+++ b/st.c
@@ -33,8 +33,11 @@
#define UTF_SIZ 4
#define ESC_BUF_SIZ (128*UTF_SIZ)
#define ESC_ARG_SIZ 16
+#define CAR_PER_ARG 4
#define STR_BUF_SIZ ESC_BUF_SIZ
#define STR_ARG_SIZ ESC_ARG_SIZ
+#define HISTSIZE 10000
+#define RESIZEBUFFER 1000
/* macros */
#define IS_SET(flag) ((term.mode & (flag)) != 0)
@@ -42,6 +45,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[(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,
@@ -53,6 +71,12 @@ enum term_mode {
MODE_UTF8 = 1 << 6,
};
+enum scroll_mode {
+ SCROLL_RESIZE = -1,
+ SCROLL_NOSAVEHIST = 0,
+ SCROLL_SAVEHIST = 1
+};
+
enum cursor_movement {
CURSOR_SAVE,
CURSOR_LOAD
@@ -114,7 +138,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 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 */
@@ -139,6 +167,7 @@ typedef struct {
int arg[ESC_ARG_SIZ];
int narg; /* nb of args */
char mode[2];
+ int carg[ESC_ARG_SIZ][CAR_PER_ARG]; /* colon args */
} CSIEscape;
/* STR Escape sequence structs */
@@ -152,6 +181,11 @@ typedef struct {
int narg; /* nb of args */
} STREscape;
+typedef struct {
+ int state;
+ size_t length;
+} URLdfa;
+
static void execsh(char *, char **);
static void stty(char **);
static void sigchld(int);
@@ -159,6 +193,7 @@ static void ttywriteraw(const char *, size_t);
static void csidump(void);
static void csihandle(void);
+static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
static void csiparse(void);
static void csireset(void);
static void osc_color_response(int, int, int);
@@ -172,26 +207,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);
+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);
@@ -201,11 +247,15 @@ static void tdefutf8(char);
static int32_t tdefcolor(const int *, int *, int);
static void tdeftran(char);
static void tstrsequence(uchar);
+static int daddch(URLdfa *, char);
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);
@@ -405,17 +455,46 @@ selinit(void)
}
int
-tlinelen(int y)
+tlinelen(Line line)
{
- int i = term.col;
+ int i = term.col - 1;
- if (term.line[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 && term.line[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
@@ -455,10 +534,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;
@@ -467,61 +547,69 @@ 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:
/*
* Snap around if the word wraps around at the end or
* beginning of a line.
*/
- prevgp = &term.line[*y][*x];
+ prevgp = &TLINE(*y)[*x];
prevdelim = ISDELIM(prevgp->u);
for (;;) {
newx = *x + direction;
@@ -529,24 +617,24 @@ 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)
yt = *y, xt = *x;
else
yt = newy, xt = newx;
- if (!(term.line[yt][xt].mode & ATTR_WRAP))
+ if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
break;
}
- if (newx >= tlinelen(newy))
+ if (newx >= tlinelen(TLINE(newy)))
break;
- gp = &term.line[newy][newx];
+ gp = &TLINE(newy)[newx];
delim = ISDELIM(gp->u);
- if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
- || (delim && gp->u != prevgp->u)))
+ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim ||
+ (delim && !(gp->u == ' ' && prevgp->u == ' '))))
break;
*x = newx;
@@ -561,20 +649,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 (!(term.line[*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 (!(term.line[*y][term.col-1].mode
- & ATTR_WRAP)) {
+ for (; *y < rbot; *y += 1) {
+ if (!tiswrapped(TLINE(*y)))
break;
- }
}
}
break;
@@ -585,39 +669,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 = &term.line[y][sel.nb.x];
+ gp = &line[sel.nb.x];
lastx = sel.ne.x;
} else {
- gp = &term.line[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 = &term.line[y][MIN(lastx, linelen-1)];
- while (last >= gp && last->u == ' ')
- --last;
-
- for ( ; gp <= last; ++gp) {
- if (gp->mode & ATTR_WDUMMY)
- continue;
+ lgp = &line[MIN(lastx, linelen-1)];
- ptr += utf8encode(gp->u, ptr);
- }
+ ptr = tgetglyphs(ptr, gp, lgp);
/*
* Copy and pasting of line endings is inconsistent
@@ -629,10 +708,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;
}
@@ -641,9 +720,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
@@ -845,6 +930,8 @@ ttywrite(const char *s, size_t n, int may_echo)
{
const char *next;
+ kscrolldown(&((Arg){ .i = term.scr }));
+
if (may_echo && IS_SET(MODE_ECHO))
twrite(s, n, 1);
@@ -980,7 +1067,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;
}
}
@@ -990,7 +1077,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
@@ -1008,109 +1096,260 @@ 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
-tscrolldown(int orig, int n)
+tloaddefscreen(int clear, int loadcursor)
{
- int i;
- Line temp;
+ int col, row, alt = IS_SET(MODE_ALTSCREEN);
- LIMIT(n, 0, term.bot-orig+1);
+ 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);
+}
- tsetdirt(orig, term.bot-n);
- tclearregion(0, term.bot-n+1, term.col-1, term.bot);
+void
+tloadaltscreen(int clear, int savecursor)
+{
+ int col, row, def = !IS_SET(MODE_ALTSCREEN);
- 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 (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);
+}
- selscroll(orig, n);
+int
+tisaltscreen(void)
+{
+ return IS_SET(MODE_ALTSCREEN);
}
+
void
-tscrollup(int orig, int n)
+kscrolldown(const Arg* a)
{
- int i;
- Line temp;
+ int n = a->i;
- LIMIT(n, 0, term.bot-orig+1);
+ if (!term.scr || IS_SET(MODE_ALTSCREEN))
+ return;
+
+ 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();
+}
- tclearregion(0, orig, term.col-1, orig+n-1);
- tsetdirt(orig+n, term.bot);
- for (i = orig; i <= term.bot-n; i++) {
- temp = term.line[i];
- term.line[i] = term.line[i+n];
- term.line[i+n] = temp;
- }
- selscroll(orig, -n);
+void
+kscrollup(const Arg* a)
+{
+ int n = a->i;
+
+ if (!term.histf || IS_SET(MODE_ALTSCREEN))
+ return;
+
+ if (n < 0)
+ n = MAX(term.row / -n, 1);
+
+ if (term.scr + n <= term.histf) {
+ term.scr += n;
+ } else {
+ n = term.histf - term.scr;
+ term.scr = term.histf;
+ }
+
+ if (sel.ob.x != -1 && !sel.alt)
+ selmove(n); /* negate change in term.scr */
+ tfulldirt();
+
}
void
-selscroll(int orig, int n)
+tscrolldown(int top, int n)
{
- if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
- return;
+ int i, bot = term.bot;
+ Line temp;
- 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 (n <= 0)
+ return;
+ n = MIN(n, bot-top+1);
+
+ tsetdirt(top, bot-n);
+ tclearregion(0, bot-n+1, term.col-1, bot, 1);
+
+ for (i = bot; i >= top+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 == IS_SET(MODE_ALTSCREEN))
+ selscroll(top, bot, n);
+}
+
+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 (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
+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)
+{
+ /* turn absolute coordinates into relative */
+ top += term.scr, bot += term.scr;
+
+ 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();
}
}
@@ -1120,7 +1359,7 @@ tnewline(int first_col)
int y = term.c.y;
if (y == term.bot) {
- tscrollup(term.top, 1);
+ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
} else {
y++;
}
@@ -1128,6 +1367,29 @@ tnewline(int first_col)
}
void
+readcolonargs(char **p, int cursor, int params[][CAR_PER_ARG])
+{
+ int i = 0;
+ for (; i < CAR_PER_ARG; i++)
+ params[cursor][i] = -1;
+
+ if (**p != ':')
+ return;
+
+ char *np = NULL;
+ i = 0;
+
+ while (**p == ':' && i < CAR_PER_ARG) {
+ while (**p == ':')
+ (*p)++;
+ params[cursor][i] = strtol(*p, &np, 10);
+ *p = np;
+ i++;
+ }
+}
+
+
+void
csiparse(void)
{
char *p = csiescseq.buf, *np;
@@ -1152,6 +1414,7 @@ csiparse(void)
p = np;
if (sep == ';' && *p == ':')
sep = ':'; /* allow override to colon once */
+ readcolonargs(&p, csiescseq.narg-1, csiescseq.carg); // TJK here dont know if to put before or after above
if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
break;
p++;
@@ -1187,101 +1450,115 @@ 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);
- term.dirty[y] = 1;
- term.line[y][x] = *attr;
- term.line[y][x].u = u;
+ 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.line[y][x].mode |= ATTR_SET;
+
+ if (isboxdraw(u))
+ term.line[y][x].mode |= ATTR_BOXDRAW;
}
+
+
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
@@ -1295,7 +1572,7 @@ void
tdeleteline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
- tscrollup(term.c.y, n);
+ tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST);
}
int32_t
@@ -1368,6 +1645,10 @@ tsetattr(const int *attr, int l)
ATTR_STRUCK );
term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg;
+ term.c.attr.ustyle = -1;
+ term.c.attr.ucolor[0] = -1;
+ term.c.attr.ucolor[1] = -1;
+ term.c.attr.ucolor[2] = -1;
break;
case 1:
term.c.attr.mode |= ATTR_BOLD;
@@ -1379,7 +1660,14 @@ tsetattr(const int *attr, int l)
term.c.attr.mode |= ATTR_ITALIC;
break;
case 4:
- term.c.attr.mode |= ATTR_UNDERLINE;
+ term.c.attr.ustyle = csiescseq.carg[i][0];
+
+ if (term.c.attr.ustyle != 0)
+ term.c.attr.mode |= ATTR_UNDERLINE;
+ else
+ term.c.attr.mode &= ~ATTR_UNDERLINE;
+
+ term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
break;
case 5: /* slow blink */
/* FALLTHROUGH */
@@ -1431,10 +1719,16 @@ tsetattr(const int *attr, int l)
term.c.attr.bg = defaultbg;
break;
case 58:
- /* This starts a sequence to change the color of
- * "underline" pixels. We don't support that and
- * instead eat up a following "5;n" or "2;r;g;b". */
- tdefcolor(attr, &i, l);
+ term.c.attr.ucolor[0] = csiescseq.carg[i][1];
+ term.c.attr.ucolor[1] = csiescseq.carg[i][2];
+ term.c.attr.ucolor[2] = csiescseq.carg[i][3];
+ term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
+ break;
+ case 59:
+ term.c.attr.ucolor[0] = -1;
+ term.c.attr.ucolor[1] = -1;
+ term.c.attr.ucolor[2] = -1;
+ term.c.attr.mode ^= ATTR_DIRTYUNDERLINE;
break;
default:
if (BETWEEN(attr[i], 30, 37)) {
@@ -1475,7 +1769,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) {
@@ -1544,15 +1838,11 @@ tsetmode(int priv, int set, const int *args, int narg)
case 1047: /* swap screen buffer */
if (!allowaltscreen)
break;
- alt = IS_SET(MODE_ALTSCREEN);
- if (alt) {
- tclearregion(0, 0, term.col-1,
- term.row-1);
- }
- if (set ^ alt) /* set is always 1 or 0 */
- tswapscreen();
- if (*args != 1049)
- break;
+ if (set)
+ tloadaltscreen(*args == 1049, *args == 1049);
+ else
+ tloaddefscreen(*args == 1047, *args == 1049);
+ break;
/* FALLTHROUGH */
case 1048: /* save/restore cursor (like DECSC/DECRC) */
tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
@@ -1606,7 +1896,7 @@ void
csihandle(void)
{
char buf[40];
- int len;
+ int n, x;
switch (csiescseq.mode[0]) {
default:
@@ -1704,19 +1994,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 > 0)
- 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);
+ 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;
@@ -1725,21 +2025,21 @@ 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]);
+ /* 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);
@@ -1757,9 +2057,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);
@@ -1785,9 +2087,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;
@@ -1823,6 +2125,33 @@ csihandle(void)
goto unknown;
}
break;
+ case 't': /* title stack operations */
+ switch (csiescseq.arg[0]) {
+ case 22: /* pust current title on stack */
+ switch (csiescseq.arg[1]) {
+ case 0:
+ case 1:
+ case 2:
+ xpushtitle();
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ case 23: /* pop last title from stack */
+ switch (csiescseq.arg[1]) {
+ case 0:
+ case 1:
+ case 2:
+ xsettitle(NULL, 1);
+ break;
+ default:
+ goto unknown;
+ }
+ break;
+ default:
+ goto unknown;
+ }
}
}
@@ -1901,7 +2230,7 @@ strhandle(void)
switch (par) {
case 0:
if (narg > 1) {
- xsettitle(strescseq.args[1]);
+ xsettitle(strescseq.args[1], 0);
xseticontitle(strescseq.args[1]);
}
return;
@@ -1911,7 +2240,17 @@ strhandle(void)
return;
case 2:
if (narg > 1)
- xsettitle(strescseq.args[1]);
+ xsettitle(strescseq.args[1], 0);
+ return;
+ case 7:
+ if (strstr(strescseq.args[1], "file://") != strescseq.args[1]) {
+ fprintf(stderr, "erresc: dir %s must have prefix 'file://'\n",
+ strescseq.args[1]);
+ return;
+ }
+ if (chdir(strescseq.args[1] + 7) != 0) /* +7 to remove prefix */
+ fprintf(stderr, "erresc: invalid directory %s\n",
+ strescseq.args[1]);
return;
case 52: /* manipulate selection data */
if (narg > 2 && allowwindowops) {
@@ -1983,7 +2322,7 @@ strhandle(void)
}
break;
case 'k': /* old title set compatibility */
- xsettitle(strescseq.args[0]);
+ xsettitle(strescseq.args[0], 0);
return;
case 'P': /* DCS -- Device Control String */
case '_': /* APC -- Application Program Command */
@@ -2102,16 +2441,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
@@ -2332,7 +2663,7 @@ eschandle(uchar ascii)
return 0;
case 'D': /* IND -- Linefeed */
if (term.c.y == term.bot) {
- tscrollup(term.top, 1);
+ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST);
} else {
tmoveto(term.c.x, term.c.y+1);
}
@@ -2355,6 +2686,7 @@ eschandle(uchar ascii)
break;
case 'c': /* RIS -- Reset to initial state */
treset();
+ xfreetitlestack();
resettitle();
xloadcols();
xsetmode(0, MODE_HIDE);
@@ -2489,7 +2821,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];
@@ -2529,6 +2863,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;
}
}
@@ -2566,91 +2901,290 @@ twrite(const char *buf, int buflen, int show_ctrl)
}
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;
- 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;
+ }
- /* 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));
- }
+ if (IS_SET(MODE_ALTSCREEN))
+ tresizealt(col, row);
+ else
+ tresizedef(col, row);
+}
- /* allocate any new rows */
- for (/* i = minrow */; i < row; i++) {
- term.line[i] = xmalloc(col * sizeof(Glyph));
- term.alt[i] = xmalloc(col * sizeof(Glyph));
- }
- if (col > term.col) {
- bp = term.tabs + term.col;
- memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
- while (--bp > term.tabs && !*bp)
- /* nothing */ ;
- for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
- *bp = 1;
+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) {
+ 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
resettitle(void)
{
- xsettitle(NULL);
+ xsettitle(NULL, 0);
}
void
@@ -2663,7 +3197,7 @@ drawregion(int x1, int y1, int x2, int y2)
continue;
term.dirty[y] = 0;
- xdrawline(term.line[y], x1, y, x2);
+ xdrawline(TLINE(y), x1, y, x2);
}
}
@@ -2699,3 +3233,98 @@ redraw(void)
tfulldirt();
draw();
}
+
+int
+daddch(URLdfa *dfa, char c)
+{
+ /* () and [] can appear in urls, but excluding them here will reduce false
+ * positives when figuring out where a given url ends.
+ */
+ static const char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-._~:/?#@!$&*+,;=%"; // removed apostrophe (') since it causes false positives as well
+ static const char RPFX[] = "//:sptth";
+
+ if (!strchr(URLCHARS, c)) {
+ dfa->length = 0;
+ dfa->state = 0;
+
+ return 0;
+ }
+
+ dfa->length++;
+
+ if (dfa->state == 2 && c == '/') {
+ dfa->state = 0;
+ } else if (dfa->state == 3 && c == 'p') {
+ dfa->state++;
+ } else if (c != RPFX[dfa->state]) {
+ dfa->state = 0;
+ return 0;
+ }
+
+ if (dfa->state++ == 7) {
+ dfa->state = 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+** Select and copy the previous url on screen (do nothing if there's no url).
+*/
+void
+selurl(const Arg *arg) {
+ int row = 0, /* row of current URL */
+ col = 0, /* column of current URL start */
+ colend = 0, /* column of last occurrence */
+ passes = 0; /* how many rows have been scanned */
+
+ const char *c = NULL,
+ *match = NULL;
+ URLdfa dfa = { 0 };
+
+ row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot;
+ LIMIT(row, term.top, term.bot);
+
+ colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col;
+ LIMIT(colend, 0, term.col);
+
+ /*
+ ** Scan from (term.row - 1,term.col - 1) to (0,0) and find
+ ** next occurrance of a URL
+ */
+ for (passes = 0; passes < term.row; passes++) {
+ /* Read in each column of every row until
+ ** we hit previous occurrence of URL
+ */
+ for (col = colend; col--;)
+ if (daddch(&dfa, term.line[row][col].u < 128 ? term.line[row][col].u : ' '))
+ break;
+
+ if (col >= 0)
+ break;
+
+ /* .i = 0 --> botton-up
+ * .i = 1 --> top-down
+ */
+ if (!arg->i) {
+ if (--row < 0)
+ row = term.row - 1;
+ } else {
+ if (++row >= term.row)
+ row = 0;
+ }
+
+ colend = term.col;
+ }
+
+ if (passes < term.row) {
+ selstart(col, row, 0);
+ selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 0);
+ selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 1);
+ xsetsel(getsel());
+ //xclipcopy();
+ }
+}
diff --git a/st.desktop b/st.desktop
new file mode 100644
index 0000000..2d2b76f
--- /dev/null
+++ b/st.desktop
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Type=Application
+Exec=st
+TryExec=st
+Icon=utilities-terminal
+Terminal=false
+Categories=System;TerminalEmulator;
+
+Name=st
+GenericName=Terminal
+Comment=st is a simple terminal implementation for X
+StartupWMClass=st-256color
diff --git a/st.h b/st.h
index fd3b0d8..ee85b73 100644
--- a/st.h
+++ b/st.h
@@ -20,20 +20,28 @@
#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
#define IS_TRUECOL(x) (1 << 24 & (x))
+#define HEX_TO_INT(c) ((c) >= '0' && (c) <= '9' ? (c) - '0' : \
+ (c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \
+ (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : -1)
+
enum glyph_attribute {
ATTR_NULL = 0,
- ATTR_BOLD = 1 << 0,
- ATTR_FAINT = 1 << 1,
- ATTR_ITALIC = 1 << 2,
- ATTR_UNDERLINE = 1 << 3,
- ATTR_BLINK = 1 << 4,
- ATTR_REVERSE = 1 << 5,
- ATTR_INVISIBLE = 1 << 6,
- ATTR_STRUCK = 1 << 7,
- ATTR_WRAP = 1 << 8,
- ATTR_WIDE = 1 << 9,
- ATTR_WDUMMY = 1 << 10,
+ ATTR_SET = 1 << 0,
+ ATTR_BOLD = 1 << 1,
+ ATTR_FAINT = 1 << 2,
+ ATTR_ITALIC = 1 << 3,
+ ATTR_UNDERLINE = 1 << 4,
+ ATTR_BLINK = 1 << 5,
+ ATTR_REVERSE = 1 << 6,
+ ATTR_INVISIBLE = 1 << 7,
+ ATTR_STRUCK = 1 << 8,
+ ATTR_WRAP = 1 << 9,
+ ATTR_WIDE = 1 << 10,
+ ATTR_WDUMMY = 1 << 11,
+ ATTR_SELECTED = 1 << 12,
+ ATTR_BOXDRAW = 1 << 11,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+ ATTR_DIRTYUNDERLINE = 1 << 15,
};
enum selection_mode {
@@ -65,6 +73,8 @@ typedef struct {
ushort mode; /* attribute flags */
uint32_t fg; /* foreground */
uint32_t bg; /* background */
+ int ustyle; /* underline style */
+ int ucolor[3]; /* underline color */
} Glyph;
typedef Glyph *Line;
@@ -81,13 +91,17 @@ void die(const char *, ...);
void redraw(void);
void draw(void);
+void kscrolldown(const Arg *);
+void kscrollup(const Arg *);
void printscreen(const Arg *);
void printsel(const Arg *);
void sendbreak(const Arg *);
void toggleprinter(const Arg *);
+void selurl(const Arg *);
int tattrset(int);
void tnew(int, int);
+int tisaltscreen(void);
void tresize(int, int);
void tsetdirtattr(int);
void ttyhangup(void);
@@ -111,6 +125,14 @@ void *xmalloc(size_t);
void *xrealloc(void *, size_t);
char *xstrdup(const char *);
+int isboxdraw(Rune);
+ushort boxdrawindex(const Glyph *);
+#ifdef XFT_VERSION
+/* only exposed to x.c, otherwise we'll need Xft.h for the types */
+void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *);
+void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int);
+#endif
+
/* config.h globals */
extern char *utmp;
extern char *scroll;
@@ -124,3 +146,4 @@ extern unsigned int tabspaces;
extern unsigned int defaultfg;
extern unsigned int defaultbg;
extern unsigned int defaultcs;
+extern const int boxdraw, boxdraw_bold, boxdraw_braille;
diff --git a/st.info b/st.info
index efab2cf..caef745 100644
--- a/st.info
+++ b/st.info
@@ -1,4 +1,5 @@
st-mono| simpleterm monocolor,
+ Su,
acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
am,
bce,
@@ -161,7 +162,7 @@ st-mono| simpleterm monocolor,
rin=\E[%p1%dT,
ritm=\E[23m,
rmacs=\E(B,
- rmcup=\E[?1049l,
+ rmcup=\E[?1049l\E[23;0;0t,
rmir=\E[4l,
rmkx=\E[?1l\E>,
rmso=\E[27m,
@@ -172,7 +173,7 @@ st-mono| simpleterm monocolor,
sitm=\E[3m,
sgr0=\E[0m,
smacs=\E(0,
- smcup=\E[?1049h,
+ smcup=\E[?1049h\E[22;0;0t,
smir=\E[4h,
smkx=\E[?1h\E=,
smso=\E[7m,
diff --git a/win.h b/win.h
index 6de960d..2a40aa0 100644
--- a/win.h
+++ b/win.h
@@ -32,7 +32,9 @@ void xloadcols(void);
int xsetcolorname(int, const char *);
int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *);
void xseticontitle(char *);
-void xsettitle(char *);
+void xfreetitlestack(void);
+void xsettitle(char *, int);
+void xpushtitle(void);
int xsetcursor(int);
void xsetmode(int, unsigned int);
void xsetpointermotion(int);
diff --git a/x.c b/x.c
index d73152b..7ca1b41 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,7 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -14,6 +15,7 @@
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <X11/XKBlib.h>
+#include <X11/Xcursor/Xcursor.h>
char *argv0;
#include "arg.h"
@@ -45,6 +47,14 @@ typedef struct {
signed char appcursor; /* application cursor */
} Key;
+/* Undercurl slope types */
+enum undercurl_slope_type {
+ UNDERCURL_SLOPE_ASCENDING = 0,
+ UNDERCURL_SLOPE_TOP_CAP = 1,
+ UNDERCURL_SLOPE_DESCENDING = 2,
+ UNDERCURL_SLOPE_BOTTOM_CAP = 3
+};
+
/* X modifiers */
#define XK_ANY_MOD UINT_MAX
#define XK_NO_MOD 0
@@ -55,6 +65,7 @@ static void clipcopy(const Arg *);
static void clippaste(const Arg *);
static void numlock(const Arg *);
static void selpaste(const Arg *);
+static void selopen(const Arg *);
static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
@@ -63,6 +74,9 @@ static void ttysend(const Arg *);
/* config.h for applying patches and the configuration. */
#include "config.h"
+/* size of title stack */
+#define TITLESTACKSIZE 8
+
/* XEMBED messages */
#define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5
@@ -94,6 +108,12 @@ typedef struct {
Drawable buf;
GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
+ Atom XdndTypeList, XdndSelection, XdndEnter, XdndPosition, XdndStatus,
+ XdndLeave, XdndDrop, XdndFinished, XdndActionCopy, XdndActionMove,
+ XdndActionLink, XdndActionAsk, XdndActionPrivate, XtextUriList,
+ XtextPlain, XdndAware;
+ int64_t XdndSourceWin, XdndSourceVersion;
+ int32_t XdndSourceFormat;
struct {
XIM xim;
XIC xic;
@@ -169,6 +189,9 @@ static void visibility(XEvent *);
static void unmap(XEvent *);
static void kpress(XEvent *);
static void cmessage(XEvent *);
+static void xdndenter(XEvent *);
+static void xdndpos(XEvent *);
+static void xdnddrop(XEvent *);
static void resize(XEvent *);
static void focus(XEvent *);
static uint buttonmask(uint);
@@ -178,6 +201,8 @@ static void bpress(XEvent *);
static void bmotion(XEvent *);
static void propnotify(XEvent *);
static void selnotify(XEvent *);
+static void xdndsel(XEvent *);
+static void xdndpastedata(char *);
static void selclear_(XEvent *);
static void selrequest(XEvent *);
static void setsel(char *, Time);
@@ -220,6 +245,9 @@ static DC dc;
static XWindow xw;
static XSelection xsel;
static TermWindow win;
+static int tstki; /* title stack index */
+static char *titlestack[TITLESTACKSIZE]; /* title stack */
+const char XdndVersion = 5;
/* Font Ring Cache */
enum {
@@ -287,6 +315,20 @@ selpaste(const Arg *dummy)
}
void
+selopen(const Arg *dummy)
+{
+ pid_t chpid;
+
+ if ((chpid = fork()) == 0) {
+ if (fork() == 0)
+ execlp("xdg-open", "xdg-open", getsel(), NULL);
+ exit(1);
+ }
+ if (chpid > 0)
+ waitpid(chpid, NULL, 0);
+}
+
+void
numlock(const Arg *dummy)
{
win.mode ^= MODE_NUMLOCK;
@@ -536,6 +578,11 @@ selnotify(XEvent *e)
if (property == None)
return;
+ if (property == xw.XdndSelection) {
+ xdndsel(e);
+ return;
+ }
+
do {
if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
BUFSIZ/4, False, AnyPropertyType,
@@ -605,6 +652,95 @@ selnotify(XEvent *e)
}
void
+xdndsel(XEvent *e)
+{
+ char* data;
+ unsigned long result;
+
+ Atom actualType;
+ int32_t actualFormat;
+ unsigned long bytesAfter;
+ XEvent reply = { ClientMessage };
+
+ reply.xclient.window = xw.XdndSourceWin;
+ reply.xclient.format = 32;
+ reply.xclient.data.l[0] = (long) xw.win;
+ reply.xclient.data.l[2] = 0;
+ reply.xclient.data.l[3] = 0;
+
+ XGetWindowProperty((Display*) xw.dpy, e->xselection.requestor,
+ e->xselection.property, 0, LONG_MAX, False,
+ e->xselection.target, &actualType, &actualFormat, &result,
+ &bytesAfter, (unsigned char**) &data);
+
+ if (result == 0)
+ return;
+
+ if (data) {
+ xdndpastedata(data);
+ XFree(data);
+ }
+
+ if (xw.XdndSourceVersion >= 2) {
+ reply.xclient.message_type = xw.XdndFinished;
+ reply.xclient.data.l[1] = result;
+ reply.xclient.data.l[2] = xw.XdndActionCopy;
+
+ XSendEvent((Display*) xw.dpy, xw.XdndSourceWin, False, NoEventMask,
+ &reply);
+ XFlush((Display*) xw.dpy);
+ }
+}
+
+int
+xdndurldecode(char *src, char *dest)
+{
+ char c;
+ int i = 0;
+
+ while (*src) {
+ if (*src == '%' && HEX_TO_INT(src[1]) != -1 && HEX_TO_INT(src[2]) != -1) {
+ /* handle %xx escape sequences in url e.g. %20 == ' ' */
+ c = (char)((HEX_TO_INT(src[1]) << 4) | HEX_TO_INT(src[2]));
+ src += 3;
+ } else {
+ c = *src++;
+ }
+ if (strchr(xdndescchar, c) != NULL) {
+ *dest++ = '\\';
+ i++;
+ }
+ *dest++ = c;
+ i++;
+ }
+ *dest++ = ' ';
+ *dest = '\0';
+ return i + 1;
+}
+
+void
+xdndpastedata(char *data)
+{
+ char *pastedata, *t;
+ int i = 0;
+
+ pastedata = (char *)malloc(strlen(data) * 2 + 1);
+ *pastedata = '\0';
+
+ t = strtok(data, "\n\r");
+ while(t != NULL) {
+ /* remove 'file://' prefix if it exists */
+ if (strncmp(data, "file://", 7) == 0)
+ t += 7;
+ i += xdndurldecode(t, pastedata + i);
+ t = strtok(NULL, "\n\r");
+ }
+
+ xsetsel(pastedata);
+ selpaste(0);
+}
+
+void
xclipcopy(void)
{
clipcopy(NULL);
@@ -686,6 +822,8 @@ setsel(char *str, Time t)
XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
selclear();
+
+ xclipcopy();
}
void
@@ -869,8 +1007,8 @@ xhints(void)
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->height_inc = 1;
+ sizeh->width_inc = 1;
sizeh->base_height = 2 * borderpx;
sizeh->base_width = 2 * borderpx;
sizeh->min_height = win.ch + 2 * borderpx;
@@ -1200,23 +1338,9 @@ xinit(int cols, int rows)
}
/* white cursor, black outline */
- cursor = XCreateFontCursor(xw.dpy, mouseshape);
+ cursor = XcursorLibraryLoadCursor(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);
@@ -1227,6 +1351,26 @@ xinit(int cols, int rows)
XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
PropModeReplace, (uchar *)&thispid, 1);
+ /* Xdnd setup */
+ xw.XdndTypeList = XInternAtom(xw.dpy, "XdndTypeList", 0);
+ xw.XdndSelection = XInternAtom(xw.dpy, "XdndSelection", 0);
+ xw.XdndEnter = XInternAtom(xw.dpy, "XdndEnter", 0);
+ xw.XdndPosition = XInternAtom(xw.dpy, "XdndPosition", 0);
+ xw.XdndStatus = XInternAtom(xw.dpy, "XdndStatus", 0);
+ xw.XdndLeave = XInternAtom(xw.dpy, "XdndLeave", 0);
+ xw.XdndDrop = XInternAtom(xw.dpy, "XdndDrop", 0);
+ xw.XdndFinished = XInternAtom(xw.dpy, "XdndFinished", 0);
+ xw.XdndActionCopy = XInternAtom(xw.dpy, "XdndActionCopy", 0);
+ xw.XdndActionMove = XInternAtom(xw.dpy, "XdndActionMove", 0);
+ xw.XdndActionLink = XInternAtom(xw.dpy, "XdndActionLink", 0);
+ xw.XdndActionAsk = XInternAtom(xw.dpy, "XdndActionAsk", 0);
+ xw.XdndActionPrivate = XInternAtom(xw.dpy, "XdndActionPrivate", 0);
+ xw.XtextUriList = XInternAtom((Display*) xw.dpy, "text/uri-list", 0);
+ xw.XtextPlain = XInternAtom((Display*) xw.dpy, "text/plain", 0);
+ xw.XdndAware = XInternAtom(xw.dpy, "XdndAware", 0);
+ XChangeProperty(xw.dpy, xw.win, xw.XdndAware, 4, 32, PropModeReplace,
+ &XdndVersion, 1);
+
win.mode = MODE_NUMLOCK;
resettitle();
xhints();
@@ -1240,6 +1384,8 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
+
+ boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
}
int
@@ -1287,7 +1433,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
}
/* Lookup character index with default font. */
- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+ if (mode & ATTR_BOXDRAW) {
+ /* minor shoehorning: boxdraw uses only this ushort */
+ glyphidx = boxdrawindex(&glyphs[i]);
+ } else {
+ /* Lookup character index with default font. */
+ glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+ }
if (glyphidx) {
specs[numspecs].font = font->match;
specs[numspecs].glyph = glyphidx;
@@ -1374,6 +1526,51 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
return numspecs;
}
+static int isSlopeRising (int x, int iPoint, int waveWidth)
+{
+ // . . . .
+ // / \ / \ / \ / \
+ // / \ / \ / \ / \
+ // . . . . .
+
+ // Find absolute `x` of point
+ x += iPoint * (waveWidth/2);
+
+ // Find index of absolute wave
+ int absSlope = x / ((float)waveWidth/2);
+
+ return (absSlope % 2);
+}
+
+static int getSlope (int x, int iPoint, int waveWidth)
+{
+ // Sizes: Caps are half width of slopes
+ // 1_2 1_2 1_2 1_2
+ // / \ / \ / \ / \
+ // / \ / \ / \ / \
+ // 0 3_0 3_0 3_0 3_
+ // <2-> <1> <---6---->
+
+ // Find type of first point
+ int firstType;
+ x -= (x / waveWidth) * waveWidth;
+ if (x < (waveWidth * (2.f/6.f)))
+ firstType = UNDERCURL_SLOPE_ASCENDING;
+ else if (x < (waveWidth * (3.f/6.f)))
+ firstType = UNDERCURL_SLOPE_TOP_CAP;
+ else if (x < (waveWidth * (5.f/6.f)))
+ firstType = UNDERCURL_SLOPE_DESCENDING;
+ else
+ firstType = UNDERCURL_SLOPE_BOTTOM_CAP;
+
+ // Find type of given point
+ int pointType = (iPoint % 4);
+ pointType += firstType;
+ pointType %= 4;
+
+ return pointType;
+}
+
void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
{
@@ -1492,12 +1689,366 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
/* Render the glyphs. */
- XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+ if (base.mode & ATTR_BOXDRAW) {
+ drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
+ } else {
+ /* Render the glyphs. */
+ XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+ }
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) {
- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
- width, 1);
+ // Underline Color
+ const int widthThreshold = 28; // +1 width every widthThreshold px of font
+ int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width
+ int linecolor;
+ if ((base.ucolor[0] >= 0) &&
+ !(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
+ !(base.mode & ATTR_INVISIBLE)
+ ) {
+ // Special color for underline
+ // Index
+ if (base.ucolor[1] < 0) {
+ linecolor = dc.col[base.ucolor[0]].pixel;
+ }
+ // RGB
+ else {
+ XColor lcolor;
+ lcolor.red = base.ucolor[0] * 257;
+ lcolor.green = base.ucolor[1] * 257;
+ lcolor.blue = base.ucolor[2] * 257;
+ lcolor.flags = DoRed | DoGreen | DoBlue;
+ XAllocColor(xw.dpy, xw.cmap, &lcolor);
+ linecolor = lcolor.pixel;
+ }
+ } else {
+ // Foreground color for underline
+ linecolor = fg->pixel;
+ }
+
+ XGCValues ugcv = {
+ .foreground = linecolor,
+ .line_width = wlw,
+ .line_style = LineSolid,
+ .cap_style = CapNotLast
+ };
+
+ GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
+ GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
+ &ugcv);
+
+ // Underline Style
+ if (base.ustyle != 3) {
+ //XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1);
+ XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
+ winy + dc.font.ascent + 1, width, wlw);
+ } else if (base.ustyle == 3) {
+ int ww = win.cw;//width;
+ int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
+ int wx = winx;
+ int wy = winy + win.ch - dc.font.descent;
+
+#if UNDERCURL_STYLE == UNDERCURL_CURLY
+ // Draw waves
+ int narcs = charlen * 2 + 1;
+ XArc *arcs = xmalloc(sizeof(XArc) * narcs);
+
+ int i = 0;
+ for (i = 0; i < charlen-1; i++) {
+ arcs[i*2] = (XArc) {
+ .x = wx + win.cw * i + ww / 4,
+ .y = wy,
+ .width = win.cw / 2,
+ .height = wh,
+ .angle1 = 0,
+ .angle2 = 180 * 64
+ };
+ arcs[i*2+1] = (XArc) {
+ .x = wx + win.cw * i + ww * 0.75,
+ .y = wy,
+ .width = win.cw/2,
+ .height = wh,
+ .angle1 = 180 * 64,
+ .angle2 = 180 * 64
+ };
+ }
+ // Last wave
+ arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
+ 0, 180 * 64 };
+ // Last wave tail
+ arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
+ wh, 180 * 64, 90 * 64};
+ // First wave tail
+ i++;
+ arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
+ 90 * 64 };
+
+ XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
+
+ free(arcs);
+#elif UNDERCURL_STYLE == UNDERCURL_SPIKY
+ // Make the underline corridor larger
+ /*
+ wy -= wh;
+ */
+ wh *= 2;
+
+ // Set the angle of the slope to 45°
+ ww = wh;
+
+ // Position of wave is independent of word, it's absolute
+ wx = (wx / (ww/2)) * (ww/2);
+
+ int marginStart = winx - wx;
+
+ // Calculate number of points with floating precision
+ float n = width; // Width of word in pixels
+ n = (n / ww) * 2; // Number of slopes (/ or \)
+ n += 2; // Add two last points
+ int npoints = n; // Convert to int
+
+ // Total length of underline
+ float waveLength = 0;
+
+ if (npoints >= 3) {
+ // We add an aditional slot in case we use a bonus point
+ XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
+
+ // First point (Starts with the word bounds)
+ points[0] = (XPoint) {
+ .x = wx + marginStart,
+ .y = (isSlopeRising(wx, 0, ww))
+ ? (wy - marginStart + ww/2.f)
+ : (wy + marginStart)
+ };
+
+ // Second point (Goes back to the absolute point coordinates)
+ points[1] = (XPoint) {
+ .x = (ww/2.f) - marginStart,
+ .y = (isSlopeRising(wx, 1, ww))
+ ? (ww/2.f - marginStart)
+ : (-ww/2.f + marginStart)
+ };
+ waveLength += (ww/2.f) - marginStart;
+
+ // The rest of the points
+ for (int i = 2; i < npoints-1; i++) {
+ points[i] = (XPoint) {
+ .x = ww/2,
+ .y = (isSlopeRising(wx, i, ww))
+ ? wh/2
+ : -wh/2
+ };
+ waveLength += ww/2;
+ }
+
+ // Last point
+ points[npoints-1] = (XPoint) {
+ .x = ww/2,
+ .y = (isSlopeRising(wx, npoints-1, ww))
+ ? wh/2
+ : -wh/2
+ };
+ waveLength += ww/2;
+
+ // End
+ if (waveLength < width) { // Add a bonus point?
+ int marginEnd = width - waveLength;
+ points[npoints] = (XPoint) {
+ .x = marginEnd,
+ .y = (isSlopeRising(wx, npoints, ww))
+ ? (marginEnd)
+ : (-marginEnd)
+ };
+
+ npoints++;
+ } else if (waveLength > width) { // Is last point too far?
+ int marginEnd = waveLength - width;
+ points[npoints-1].x -= marginEnd;
+ if (isSlopeRising(wx, npoints-1, ww))
+ points[npoints-1].y -= (marginEnd);
+ else
+ points[npoints-1].y += (marginEnd);
+ }
+
+ // Draw the lines
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
+ CoordModePrevious);
+
+ // Draw a second underline with an offset of 1 pixel
+ if ( ((win.ch / (widthThreshold/2)) % 2)) {
+ points[0].x++;
+
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
+ npoints, CoordModePrevious);
+ }
+
+ // Free resources
+ free(points);
+ }
+#else // UNDERCURL_CAPPED
+ // Cap is half of wave width
+ float capRatio = 0.5f;
+
+ // Make the underline corridor larger
+ wh *= 2;
+
+ // Set the angle of the slope to 45°
+ ww = wh;
+ ww *= 1 + capRatio; // Add a bit of width for the cap
+
+ // Position of wave is independent of word, it's absolute
+ wx = (wx / ww) * ww;
+
+ float marginStart;
+ switch(getSlope(winx, 0, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ marginStart = winx - wx;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ marginStart = winx - (wx + (ww * (2.f/6.f)));
+ break;
+ case UNDERCURL_SLOPE_DESCENDING:
+ marginStart = winx - (wx + (ww * (3.f/6.f)));
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ marginStart = winx - (wx + (ww * (5.f/6.f)));
+ break;
+ }
+
+ // Calculate number of points with floating precision
+ float n = width; // Width of word in pixels
+ // ._.
+ n = (n / ww) * 4; // Number of points (./ \.)
+ n += 2; // Add two last points
+ int npoints = n; // Convert to int
+
+ // Position of the pen to draw the lines
+ float penX = 0;
+ float penY = 0;
+
+ if (npoints >= 3) {
+ XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
+
+ // First point (Starts with the word bounds)
+ penX = winx;
+ switch (getSlope(winx, 0, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ penY = wy + wh/2.f - marginStart;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penY = wy;
+ break;
+ case UNDERCURL_SLOPE_DESCENDING:
+ penY = wy + marginStart;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penY = wy + wh/2.f;
+ break;
+ }
+ points[0].x = penX;
+ points[0].y = penY;
+
+ // Second point (Goes back to the absolute point coordinates)
+ switch (getSlope(winx, 1, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ penX += ww * (1.f/6.f) - marginStart;
+ penY += 0;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penX += ww * (2.f/6.f) - marginStart;
+ penY += -wh/2.f + marginStart;
+ break;
+ case UNDERCURL_SLOPE_DESCENDING:
+ penX += ww * (1.f/6.f) - marginStart;
+ penY += 0;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penX += ww * (2.f/6.f) - marginStart;
+ penY += -marginStart + wh/2.f;
+ break;
+ }
+ points[1].x = penX;
+ points[1].y = penY;
+
+ // The rest of the points
+ for (int i = 2; i < npoints; i++) {
+ switch (getSlope(winx, i, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ case UNDERCURL_SLOPE_DESCENDING:
+ penX += ww * (1.f/6.f);
+ penY += 0;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penX += ww * (2.f/6.f);
+ penY += -wh / 2.f;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penX += ww * (2.f/6.f);
+ penY += wh / 2.f;
+ break;
+ }
+ points[i].x = penX;
+ points[i].y = penY;
+ }
+
+ // End
+ float waveLength = penX - winx;
+ if (waveLength < width) { // Add a bonus point?
+ int marginEnd = width - waveLength;
+ penX += marginEnd;
+ switch(getSlope(winx, npoints, ww)) {
+ case UNDERCURL_SLOPE_ASCENDING:
+ case UNDERCURL_SLOPE_DESCENDING:
+ //penY += 0;
+ break;
+ case UNDERCURL_SLOPE_TOP_CAP:
+ penY += -marginEnd;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ penY += marginEnd;
+ break;
+ }
+
+ points[npoints].x = penX;
+ points[npoints].y = penY;
+
+ npoints++;
+ } else if (waveLength > width) { // Is last point too far?
+ int marginEnd = waveLength - width;
+ points[npoints-1].x -= marginEnd;
+ switch(getSlope(winx, npoints-1, ww)) {
+ case UNDERCURL_SLOPE_TOP_CAP:
+ points[npoints-1].y += marginEnd;
+ break;
+ case UNDERCURL_SLOPE_BOTTOM_CAP:
+ points[npoints-1].y -= marginEnd;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Draw the lines
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
+ CoordModeOrigin);
+
+ // Draw a second underline with an offset of 1 pixel
+ if ( ((win.ch / (widthThreshold/2)) % 2)) {
+ for (int i = 0; i < npoints; i++)
+ points[i].x++;
+
+ XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
+ npoints, CoordModeOrigin);
+ }
+
+ // Free resources
+ free(points);
+ }
+#endif
+ }
+
+ XFreeGC(xw.dpy, ugc);
}
if (base.mode & ATTR_STRUCK) {
@@ -1535,7 +2086,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
/*
* Select the right color for the right mode.
*/
- g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
+ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
if (IS_SET(MODE_REVERSE)) {
g.mode |= ATTR_REVERSE;
@@ -1632,10 +2183,30 @@ xseticontitle(char *p)
}
void
-xsettitle(char *p)
+xfreetitlestack(void)
{
- XTextProperty prop;
- DEFAULT(p, opt_title);
+ for (int i = 0; i < LEN(titlestack); i++) {
+ free(titlestack[i]);
+ titlestack[i] = NULL;
+ }
+}
+
+void
+xsettitle(char *p, int pop)
+{
+ XTextProperty prop;
+
+ free(titlestack[tstki]);
+ if (pop) {
+ titlestack[tstki] = NULL;
+ tstki = (tstki - 1 + TITLESTACKSIZE) % TITLESTACKSIZE;
+ p = titlestack[tstki] ? titlestack[tstki] : opt_title;
+ } else if (p) {
+ titlestack[tstki] = xstrdup(p);
+ } else {
+ titlestack[tstki] = NULL;
+ p = opt_title;
+ }
if (p[0] == '\0')
p = opt_title;
@@ -1648,6 +2219,16 @@ xsettitle(char *p)
XFree(prop.value);
}
+void
+xpushtitle(void)
+{
+ int tstkin = (tstki + 1) % TITLESTACKSIZE;
+
+ free(titlestack[tstkin]);
+ titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL;
+ tstki = tstkin;
+}
+
int
xstartdraw(void)
{
@@ -1908,6 +2489,132 @@ cmessage(XEvent *e)
} else if (e->xclient.data.l[0] == xw.wmdeletewin) {
ttyhangup();
exit(0);
+ } else if (e->xclient.message_type == xw.XdndEnter) {
+ xw.XdndSourceWin = e->xclient.data.l[0];
+ xw.XdndSourceVersion = e->xclient.data.l[1] >> 24;
+ xw.XdndSourceFormat = None;
+ if (xw.XdndSourceVersion > 5)
+ return;
+ xdndenter(e);
+ } else if (e->xclient.message_type == xw.XdndPosition
+ && xw.XdndSourceVersion <= 5) {
+ xdndpos(e);
+ } else if (e->xclient.message_type == xw.XdndDrop
+ && xw.XdndSourceVersion <= 5) {
+ xdnddrop(e);
+ }
+}
+
+void
+xdndenter(XEvent *e)
+{
+ unsigned long count;
+ Atom* formats;
+ Atom real_formats[6];
+ Bool list;
+ Atom actualType;
+ int32_t actualFormat;
+ unsigned long bytesAfter;
+ unsigned long i;
+
+ list = e->xclient.data.l[1] & 1;
+
+ if (list) {
+ XGetWindowProperty((Display*) xw.dpy,
+ xw.XdndSourceWin,
+ xw.XdndTypeList,
+ 0,
+ LONG_MAX,
+ False,
+ 4,
+ &actualType,
+ &actualFormat,
+ &count,
+ &bytesAfter,
+ (unsigned char**) &formats);
+ } else {
+ count = 0;
+
+ if (e->xclient.data.l[2] != None)
+ real_formats[count++] = e->xclient.data.l[2];
+ if (e->xclient.data.l[3] != None)
+ real_formats[count++] = e->xclient.data.l[3];
+ if (e->xclient.data.l[4] != None)
+ real_formats[count++] = e->xclient.data.l[4];
+
+ formats = real_formats;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (formats[i] == xw.XtextUriList || formats[i] == xw.XtextPlain) {
+ xw.XdndSourceFormat = formats[i];
+ break;
+ }
+ }
+
+ if (list)
+ XFree(formats);
+}
+
+void
+xdndpos(XEvent *e)
+{
+ const int32_t xabs = (e->xclient.data.l[2] >> 16) & 0xffff;
+ const int32_t yabs = (e->xclient.data.l[2]) & 0xffff;
+ Window dummy;
+ int32_t xpos, ypos;
+ XEvent reply = { ClientMessage };
+
+ reply.xclient.window = xw.XdndSourceWin;
+ reply.xclient.format = 32;
+ reply.xclient.data.l[0] = (long) xw.win;
+ reply.xclient.data.l[2] = 0;
+ reply.xclient.data.l[3] = 0;
+
+ XTranslateCoordinates((Display*) xw.dpy,
+ XDefaultRootWindow((Display*) xw.dpy),
+ (Window) xw.win,
+ xabs, yabs,
+ &xpos, &ypos,
+ &dummy);
+
+ reply.xclient.message_type = xw.XdndStatus;
+
+ if (xw.XdndSourceFormat) {
+ reply.xclient.data.l[1] = 1;
+ if (xw.XdndSourceVersion >= 2)
+ reply.xclient.data.l[4] = xw.XdndActionCopy;
+ }
+
+ XSendEvent((Display*) xw.dpy, xw.XdndSourceWin, False, NoEventMask,
+ &reply);
+ XFlush((Display*) xw.dpy);
+}
+
+void
+xdnddrop(XEvent *e)
+{
+ Time time = CurrentTime;
+ XEvent reply = { ClientMessage };
+
+ reply.xclient.window = xw.XdndSourceWin;
+ reply.xclient.format = 32;
+ reply.xclient.data.l[0] = (long) xw.win;
+ reply.xclient.data.l[2] = 0;
+ reply.xclient.data.l[3] = 0;
+
+ if (xw.XdndSourceFormat) {
+ if (xw.XdndSourceVersion >= 1)
+ time = e->xclient.data.l[2];
+
+ XConvertSelection((Display*) xw.dpy, xw.XdndSelection,
+ xw.XdndSourceFormat, xw.XdndSelection, (Window) xw.win, time);
+ } else if (xw.XdndSourceVersion >= 2) {
+ reply.xclient.message_type = xw.XdndFinished;
+
+ XSendEvent((Display*) xw.dpy, xw.XdndSourceWin,
+ False, NoEventMask, &reply);
+ XFlush((Display*) xw.dpy);
}
}