aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Chudnick <sam@chudnick.com>2023-02-16 20:47:04 -0500
committerSam Chudnick <sam@chudnick.com>2023-02-16 20:47:04 -0500
commitd4a7091392747d8f55565e4b2a7d0af7c800a64a (patch)
tree3b65de70743c80b90e0277f79548af2404186549
initial commit
-rw-r--r--LICENSE34
-rw-r--r--Makefile62
-rw-r--r--README.md39
-rw-r--r--Xdefaults128
-rw-r--r--arg.h50
-rw-r--r--boxdraw.c194
-rw-r--r--boxdraw_data.h214
-rw-r--r--config.h557
-rw-r--r--config.mk37
-rw-r--r--hb.c154
-rw-r--r--hb.h7
-rwxr-xr-xst-copyout13
-rwxr-xr-xst-urlhandler19
-rw-r--r--st.1193
-rw-r--r--st.c2816
-rw-r--r--st.h146
-rw-r--r--st.info239
-rw-r--r--win.h42
-rw-r--r--x.c2366
19 files changed, 7310 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3cbf420
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,34 @@
1MIT/X Consortium License
2
3© 2014-2022 Hiltjo Posthuma <hiltjo at codemadness dot org>
4© 2018 Devin J. Pohly <djpohly at gmail dot com>
5© 2014-2017 Quentin Rameau <quinq at fifth dot space>
6© 2009-2012 Aurélien APTEL <aurelien dot aptel at gmail dot com>
7© 2008-2017 Anselm R Garbe <garbeam at gmail dot com>
8© 2012-2017 Roberto E. Vargas Caballero <k0ga at shike2 dot com>
9© 2012-2016 Christoph Lohmann <20h at r-36 dot net>
10© 2013 Eon S. Jeon <esjeon at hyunmu dot am>
11© 2013 Alexander Sedov <alex0player at gmail dot com>
12© 2013 Mark Edgar <medgar123 at gmail dot com>
13© 2013-2014 Eric Pruitt <eric.pruitt at gmail dot com>
14© 2013 Michael Forney <mforney at mforney dot org>
15© 2013-2014 Markus Teich <markus dot teich at stusta dot mhn dot de>
16© 2014-2015 Laslo Hunhold <dev at frign dot de>
17
18Permission is hereby granted, free of charge, to any person obtaining a
19copy of this software and associated documentation files (the "Software"),
20to deal in the Software without restriction, including without limitation
21the rights to use, copy, modify, merge, publish, distribute, sublicense,
22and/or sell copies of the Software, and to permit persons to whom the
23Software is furnished to do so, subject to the following conditions:
24
25The above copyright notice and this permission notice shall be included in
26all copies or substantial portions of the Software.
27
28THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
31THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
33FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
34DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..02045f0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,62 @@
1# st - simple terminal
2# See LICENSE file for copyright and license details.
3.POSIX:
4
5include config.mk
6
7SRC = st.c x.c boxdraw.c hb.c
8OBJ = $(SRC:.c=.o)
9
10all: options st
11
12options:
13 @echo st build options:
14 @echo "CFLAGS = $(STCFLAGS)"
15 @echo "LDFLAGS = $(STLDFLAGS)"
16 @echo "CC = $(CC)"
17
18.c.o:
19 $(CC) $(STCFLAGS) -c $<
20
21st.o: config.h st.h win.h
22x.o: arg.h config.h st.h win.h hb.h
23hb.o: st.h
24boxdraw.o: config.h st.h boxdraw_data.h
25
26$(OBJ): config.h config.mk
27
28st: $(OBJ)
29 $(CC) -o $@ $(OBJ) $(STLDFLAGS)
30
31clean:
32 rm -f st $(OBJ) st-$(VERSION).tar.gz *.rej *.orig *.o
33
34dist: clean
35 mkdir -p st-$(VERSION)
36 cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\
37 config.h st.info st.1 arg.h st.h win.h $(SRC)\
38 st-$(VERSION)
39 tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz
40 rm -rf st-$(VERSION)
41
42install: st
43 mkdir -p $(DESTDIR)$(PREFIX)/bin
44 cp -f st $(DESTDIR)$(PREFIX)/bin
45 cp -f st-copyout $(DESTDIR)$(PREFIX)/bin
46 cp -f st-urlhandler $(DESTDIR)$(PREFIX)/bin
47 chmod 755 $(DESTDIR)$(PREFIX)/bin/st
48 chmod 755 $(DESTDIR)$(PREFIX)/bin/st-copyout
49 chmod 755 $(DESTDIR)$(PREFIX)/bin/st-urlhandler
50 mkdir -p $(DESTDIR)$(MANPREFIX)/man1
51 sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1
52 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
53 tic -sx st.info
54 @echo Please see the README file regarding the terminfo entry of st.
55
56uninstall:
57 rm -f $(DESTDIR)$(PREFIX)/bin/st
58 rm -f $(DESTDIR)$(PREFIX)/bin/st-copyout
59 rm -f $(DESTDIR)$(PREFIX)/bin/st-urlhandler
60 rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
61
62.PHONY: all options clean dist install uninstall
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..72fe8e5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
1# st
2
3Forked from [https://github.com/Lukesmithxyz/st][https://github.com/Lukesmithxyz/st]
4
5## Unique features (using dmenu)
6
7+ **follow urls** by pressing `alt-l`
8+ **copy urls** in the same way with `alt-y`
9+ **copy the output of commands** with `alt-o`
10
11## Bindings for
12
13+ **scrollback** with `alt-↑/↓` or `alt-pageup/down` or `shift` while scrolling the
14 mouse.
15+ OR **vim-bindings**: scroll up/down in history with `alt-k` and `alt-j`.
16 Faster with `alt-u`/`alt-d`.
17+ **zoom/change font size**: same bindings as above, but holding down shift as
18 well. `alt-home` returns to default
19+ **copy text** with `alt-c`, **paste** is `alt-v` or `shift-insert`
20
21
22## st patches
23
24- alpha
25- xresources
26- boxdraw
27- ligatures
28- font2
29- scrollback
30- externalpipe
31
32## Installation
33
34```
35apt install fontconfig libx11-dev libxft-dev libharfbuzz-dev build-essential
36git clone https://gitea.chudnick.com/sam/st
37cd st
38sudo make install
39```
diff --git a/Xdefaults b/Xdefaults
new file mode 100644
index 0000000..040c772
--- /dev/null
+++ b/Xdefaults
@@ -0,0 +1,128 @@
1!! Transparency (0-1):
2st.alpha: 0.92
3st.alphaOffset: 0.3
4
5!! Set a default font and font size as below:
6st.font: Monospace-11;
7
8! st.termname: st-256color
9! st.borderpx: 2
10
11!! Set the background, foreground and cursor colors as below:
12
13!! gruvbox:
14*.color0: #1d2021
15*.color1: #cc241d
16*.color2: #98971a
17*.color3: #d79921
18*.color4: #458588
19*.color5: #b16286
20*.color6: #689d6a
21*.color7: #a89984
22*.color8: #928374
23*.color9: #fb4934
24*.color10: #b8bb26
25*.color11: #fabd2f
26*.color12: #83a598
27*.color13: #d3869b
28*.color14: #8ec07c
29*.color15: #ebdbb2
30*.background: #282828
31*.foreground: white
32*.cursorColor: white
33
34/* /1* !! gruvbox light: *1/ */
35/* *.color0: #fbf1c7 */
36/* *.color1: #cc241d */
37/* *.color2: #98971a */
38/* *.color3: #d79921 */
39/* *.color4: #458588 */
40/* *.color5: #b16286 */
41/* *.color6: #689d6a */
42/* *.color7: #7c6f64 */
43/* *.color8: #928374 */
44/* *.color9: #9d0006 */
45/* *.color10: #79740e */
46/* *.color11: #b57614 */
47/* *.color12: #076678 */
48/* *.color13: #8f3f71 */
49/* *.color14: #427b58 */
50/* *.color15: #3c3836 */
51/* *.background: #fbf1c7 */
52/* *.foreground: #282828 */
53/* *.cursorColor: #282828 */
54
55/* !! brogrammer: */
56/* *.foreground: #d6dbe5 */
57/* *.background: #131313 */
58/* *.color0: #1f1f1f */
59/* *.color8: #d6dbe5 */
60/* *.color1: #f81118 */
61/* *.color9: #de352e */
62/* *.color2: #2dc55e */
63/* *.color10: #1dd361 */
64/* *.color3: #ecba0f */
65/* *.color11: #f3bd09 */
66/* *.color4: #2a84d2 */
67/* *.color12: #1081d6 */
68/* *.color5: #4e5ab7 */
69/* *.color13: #5350b9 */
70/* *.color6: #1081d6 */
71/* *.color14: #0f7ddb */
72/* *.color7: #d6dbe5 */
73/* *.color15: #ffffff */
74/* *.colorBD: #d6dbe5 */
75
76/* ! base16 */
77/* *.color0: #181818 */
78/* *.color1: #ab4642 */
79/* *.color2: #a1b56c */
80/* *.color3: #f7ca88 */
81/* *.color4: #7cafc2 */
82/* *.color5: #ba8baf */
83/* *.color6: #86c1b9 */
84/* *.color7: #d8d8d8 */
85/* *.color8: #585858 */
86/* *.color9: #ab4642 */
87/* *.color10: #a1b56c */
88/* *.color11: #f7ca88 */
89/* *.color12: #7cafc2 */
90/* *.color13: #ba8baf */
91/* *.color14: #86c1b9 */
92/* *.color15: #f8f8f8 */
93
94/* !! solarized */
95/* *.color0: #073642 */
96/* *.color1: #dc322f */
97/* *.color2: #859900 */
98/* *.color3: #b58900 */
99/* *.color4: #268bd2 */
100/* *.color5: #d33682 */
101/* *.color6: #2aa198 */
102/* *.color7: #eee8d5 */
103/* *.color9: #cb4b16 */
104/* *.color8: #fdf6e3 */
105/* *.color10: #586e75 */
106/* *.color11: #657b83 */
107/* *.color12: #839496 */
108/* *.color13: #6c71c4 */
109/* *.color14: #93a1a1 */
110/* *.color15: #fdf6e3 */
111
112/* !! xterm */
113/* *.color0: #000000 */
114/* *.color1: #cd0000 */
115/* *.color2: #00cd00 */
116/* *.color3: #cdcd00 */
117/* *.color4: #0000cd */
118/* *.color5: #cd00cd */
119/* *.color6: #00cdcd */
120/* *.color7: #e5e5e5 */
121/* *.color8: #4d4d4d */
122/* *.color9: #ff0000 */
123/* *.color10: #00ff00 */
124/* *.color11: #ffff00 */
125/* *.color12: #0000ff */
126/* *.color13: #ff00ff */
127/* *.color14: #00ffff */
128/* *.color15: #aabac8 */
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..a22e019
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,50 @@
1/*
2 * Copy me if you can.
3 * by 20h
4 */
5
6#ifndef ARG_H__
7#define ARG_H__
8
9extern char *argv0;
10
11/* use main(int argc, char *argv[]) */
12#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
13 argv[0] && argv[0][0] == '-'\
14 && argv[0][1];\
15 argc--, argv++) {\
16 char argc_;\
17 char **argv_;\
18 int brk_;\
19 if (argv[0][1] == '-' && argv[0][2] == '\0') {\
20 argv++;\
21 argc--;\
22 break;\
23 }\
24 int i_;\
25 for (i_ = 1, brk_ = 0, argv_ = argv;\
26 argv[0][i_] && !brk_;\
27 i_++) {\
28 if (argv_ != argv)\
29 break;\
30 argc_ = argv[0][i_];\
31 switch (argc_)
32
33#define ARGEND }\
34 }
35
36#define ARGC() argc_
37
38#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\
39 ((x), abort(), (char *)0) :\
40 (brk_ = 1, (argv[0][i_+1] != '\0')?\
41 (&argv[0][i_+1]) :\
42 (argc--, argv++, argv[0])))
43
44#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\
45 (char *)0 :\
46 (brk_ = 1, (argv[0][i_+1] != '\0')?\
47 (&argv[0][i_+1]) :\
48 (argc--, argv++, argv[0])))
49
50#endif
diff --git a/boxdraw.c b/boxdraw.c
new file mode 100644
index 0000000..28a92d0
--- /dev/null
+++ b/boxdraw.c
@@ -0,0 +1,194 @@
1/*
2 * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
3 * MIT/X Consortium License
4 */
5
6#include <X11/Xft/Xft.h>
7#include "st.h"
8#include "boxdraw_data.h"
9
10/* Rounded non-negative integers division of n / d */
11#define DIV(n, d) (((n) + (d) / 2) / (d))
12
13static Display *xdpy;
14static Colormap xcmap;
15static XftDraw *xd;
16static Visual *xvis;
17
18static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort);
19static void drawboxlines(int, int, int, int, XftColor *, ushort);
20
21/* public API */
22
23void
24boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis)
25{
26 xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis;
27}
28
29int
30isboxdraw(Rune u)
31{
32 Rune block = u & ~0xff;
33 return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) ||
34 (boxdraw_braille && block == 0x2800);
35}
36
37/* the "index" is actually the entire shape data encoded as ushort */
38ushort
39boxdrawindex(const Glyph *g)
40{
41 if (boxdraw_braille && (g->u & ~0xff) == 0x2800)
42 return BRL | (uint8_t)g->u;
43 if (boxdraw_bold && (g->mode & ATTR_BOLD))
44 return BDB | boxdata[(uint8_t)g->u];
45 return boxdata[(uint8_t)g->u];
46}
47
48void
49drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg,
50 const XftGlyphFontSpec *specs, int len)
51{
52 for ( ; len-- > 0; x += cw, specs++)
53 drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph);
54}
55
56/* implementation */
57
58void
59drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd)
60{
61 ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */
62 if (bd & (BDL | BDA)) {
63 /* lines (light/double/heavy/arcs) */
64 drawboxlines(x, y, w, h, fg, bd);
65
66 } else if (cat == BBD) {
67 /* lower (8-X)/8 block */
68 int d = DIV((uint8_t)bd * h, 8);
69 XftDrawRect(xd, fg, x, y + d, w, h - d);
70
71 } else if (cat == BBU) {
72 /* upper X/8 block */
73 XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8));
74
75 } else if (cat == BBL) {
76 /* left X/8 block */
77 XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h);
78
79 } else if (cat == BBR) {
80 /* right (8-X)/8 block */
81 int d = DIV((uint8_t)bd * w, 8);
82 XftDrawRect(xd, fg, x + d, y, w - d, h);
83
84 } else if (cat == BBQ) {
85 /* Quadrants */
86 int w2 = DIV(w, 2), h2 = DIV(h, 2);
87 if (bd & TL)
88 XftDrawRect(xd, fg, x, y, w2, h2);
89 if (bd & TR)
90 XftDrawRect(xd, fg, x + w2, y, w - w2, h2);
91 if (bd & BL)
92 XftDrawRect(xd, fg, x, y + h2, w2, h - h2);
93 if (bd & BR)
94 XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2);
95
96 } else if (bd & BBS) {
97 /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */
98 int d = (uint8_t)bd;
99 XftColor xfc;
100 XRenderColor xrc = { .alpha = 0xffff };
101
102 xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4);
103 xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4);
104 xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4);
105
106 XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc);
107 XftDrawRect(xd, &xfc, x, y, w, h);
108 XftColorFree(xdpy, xvis, xcmap, &xfc);
109
110 } else if (cat == BRL) {
111 /* braille, each data bit corresponds to one dot at 2x4 grid */
112 int w1 = DIV(w, 2);
113 int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4);
114
115 if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1);
116 if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1);
117 if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2);
118 if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1);
119 if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1);
120 if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2);
121 if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3);
122 if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3);
123
124 }
125}
126
127void
128drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd)
129{
130 /* s: stem thickness. width/8 roughly matches underscore thickness. */
131 /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */
132 /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */
133 int mwh = MIN(w, h);
134 int base_s = MAX(1, DIV(mwh, 8));
135 int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */
136 int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s;
137 int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2);
138 /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */
139 /* The base length (per direction till edge) includes this square. */
140
141 int light = bd & (LL | LU | LR | LD);
142 int double_ = bd & (DL | DU | DR | DD);
143
144 if (light) {
145 /* d: additional (negative) length to not-draw the center */
146 /* texel - at arcs and avoid drawing inside (some) doubles */
147 int arc = bd & BDA;
148 int multi_light = light & (light - 1);
149 int multi_double = double_ & (double_ - 1);
150 /* light crosses double only at DH+LV, DV+LH (ref. shapes) */
151 int d = arc || (multi_double && !multi_light) ? -s : 0;
152
153 if (bd & LL)
154 XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s);
155 if (bd & LU)
156 XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d);
157 if (bd & LR)
158 XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s);
159 if (bd & LD)
160 XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d);
161 }
162
163 /* double lines - also align with light to form heavy when combined */
164 if (double_) {
165 /*
166 * going clockwise, for each double-ray: p is additional length
167 * to the single-ray nearer to the previous direction, and n to
168 * the next. p and n adjust from the base length to lengths
169 * which consider other doubles - shorter to avoid intersections
170 * (p, n), or longer to draw the far-corner texel (n).
171 */
172 int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD;
173 if (dl) {
174 int p = dd ? -s : 0, n = du ? -s : dd ? s : 0;
175 XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s);
176 XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s);
177 }
178 if (du) {
179 int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0;
180 XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p);
181 XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n);
182 }
183 if (dr) {
184 int p = du ? -s : 0, n = dd ? -s : du ? s : 0;
185 XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s);
186 XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s);
187 }
188 if (dd) {
189 int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0;
190 XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p);
191 XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n);
192 }
193 }
194}
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 @@
1/*
2 * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
3 * MIT/X Consortium License
4 */
5
6/*
7 * U+25XX codepoints data
8 *
9 * References:
10 * http://www.unicode.org/charts/PDF/U2500.pdf
11 * http://www.unicode.org/charts/PDF/U2580.pdf
12 *
13 * Test page:
14 * https://github.com/GNOME/vte/blob/master/doc/boxes.txt
15 */
16
17/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */
18/* Categories (mutually exclusive except BDB): */
19/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */
20#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */
21#define BDA (1<<9) /* Box Draw Arc (light) */
22
23#define BBD (1<<10) /* Box Block Down (lower) X/8 */
24#define BBL (2<<10) /* Box Block Left X/8 */
25#define BBU (3<<10) /* Box Block Upper X/8 */
26#define BBR (4<<10) /* Box Block Right X/8 */
27#define BBQ (5<<10) /* Box Block Quadrants */
28#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */
29
30#define BBS (1<<14) /* Box Block Shades */
31#define BDB (1<<15) /* Box Draw is Bold */
32
33/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */
34/* Heavy is light+double (literally drawing light+double align to form heavy) */
35#define LL (1<<0)
36#define LU (1<<1)
37#define LR (1<<2)
38#define LD (1<<3)
39#define LH (LL+LR)
40#define LV (LU+LD)
41
42#define DL (1<<4)
43#define DU (1<<5)
44#define DR (1<<6)
45#define DD (1<<7)
46#define DH (DL+DR)
47#define DV (DU+DD)
48
49#define HL (LL+DL)
50#define HU (LU+DU)
51#define HR (LR+DR)
52#define HD (LD+DD)
53#define HH (HL+HR)
54#define HV (HU+HD)
55
56/* (BBQ) Quadrants Top/Bottom x Left/Right */
57#define TL (1<<0)
58#define TR (1<<1)
59#define BL (1<<2)
60#define BR (1<<3)
61
62/* Data for U+2500 - U+259F except dashes/diagonals */
63static const unsigned short boxdata[256] = {
64 /* light lines */
65 [0x00] = BDL + LH, /* light horizontal */
66 [0x02] = BDL + LV, /* light vertical */
67 [0x0c] = BDL + LD + LR, /* light down and right */
68 [0x10] = BDL + LD + LL, /* light down and left */
69 [0x14] = BDL + LU + LR, /* light up and right */
70 [0x18] = BDL + LU + LL, /* light up and left */
71 [0x1c] = BDL + LV + LR, /* light vertical and right */
72 [0x24] = BDL + LV + LL, /* light vertical and left */
73 [0x2c] = BDL + LH + LD, /* light horizontal and down */
74 [0x34] = BDL + LH + LU, /* light horizontal and up */
75 [0x3c] = BDL + LV + LH, /* light vertical and horizontal */
76 [0x74] = BDL + LL, /* light left */
77 [0x75] = BDL + LU, /* light up */
78 [0x76] = BDL + LR, /* light right */
79 [0x77] = BDL + LD, /* light down */
80
81 /* heavy [+light] lines */
82 [0x01] = BDL + HH,
83 [0x03] = BDL + HV,
84 [0x0d] = BDL + HR + LD,
85 [0x0e] = BDL + HD + LR,
86 [0x0f] = BDL + HD + HR,
87 [0x11] = BDL + HL + LD,
88 [0x12] = BDL + HD + LL,
89 [0x13] = BDL + HD + HL,
90 [0x15] = BDL + HR + LU,
91 [0x16] = BDL + HU + LR,
92 [0x17] = BDL + HU + HR,
93 [0x19] = BDL + HL + LU,
94 [0x1a] = BDL + HU + LL,
95 [0x1b] = BDL + HU + HL,
96 [0x1d] = BDL + HR + LV,
97 [0x1e] = BDL + HU + LD + LR,
98 [0x1f] = BDL + HD + LR + LU,
99 [0x20] = BDL + HV + LR,
100 [0x21] = BDL + HU + HR + LD,
101 [0x22] = BDL + HD + HR + LU,
102 [0x23] = BDL + HV + HR,
103 [0x25] = BDL + HL + LV,
104 [0x26] = BDL + HU + LD + LL,
105 [0x27] = BDL + HD + LU + LL,
106 [0x28] = BDL + HV + LL,
107 [0x29] = BDL + HU + HL + LD,
108 [0x2a] = BDL + HD + HL + LU,
109 [0x2b] = BDL + HV + HL,
110 [0x2d] = BDL + HL + LD + LR,
111 [0x2e] = BDL + HR + LL + LD,
112 [0x2f] = BDL + HH + LD,
113 [0x30] = BDL + HD + LH,
114 [0x31] = BDL + HD + HL + LR,
115 [0x32] = BDL + HR + HD + LL,
116 [0x33] = BDL + HH + HD,
117 [0x35] = BDL + HL + LU + LR,
118 [0x36] = BDL + HR + LU + LL,
119 [0x37] = BDL + HH + LU,
120 [0x38] = BDL + HU + LH,
121 [0x39] = BDL + HU + HL + LR,
122 [0x3a] = BDL + HU + HR + LL,
123 [0x3b] = BDL + HH + HU,
124 [0x3d] = BDL + HL + LV + LR,
125 [0x3e] = BDL + HR + LV + LL,
126 [0x3f] = BDL + HH + LV,
127 [0x40] = BDL + HU + LH + LD,
128 [0x41] = BDL + HD + LH + LU,
129 [0x42] = BDL + HV + LH,
130 [0x43] = BDL + HU + HL + LD + LR,
131 [0x44] = BDL + HU + HR + LD + LL,
132 [0x45] = BDL + HD + HL + LU + LR,
133 [0x46] = BDL + HD + HR + LU + LL,
134 [0x47] = BDL + HH + HU + LD,
135 [0x48] = BDL + HH + HD + LU,
136 [0x49] = BDL + HV + HL + LR,
137 [0x4a] = BDL + HV + HR + LL,
138 [0x4b] = BDL + HV + HH,
139 [0x78] = BDL + HL,
140 [0x79] = BDL + HU,
141 [0x7a] = BDL + HR,
142 [0x7b] = BDL + HD,
143 [0x7c] = BDL + HR + LL,
144 [0x7d] = BDL + HD + LU,
145 [0x7e] = BDL + HL + LR,
146 [0x7f] = BDL + HU + LD,
147
148 /* double [+light] lines */
149 [0x50] = BDL + DH,
150 [0x51] = BDL + DV,
151 [0x52] = BDL + DR + LD,
152 [0x53] = BDL + DD + LR,
153 [0x54] = BDL + DR + DD,
154 [0x55] = BDL + DL + LD,
155 [0x56] = BDL + DD + LL,
156 [0x57] = BDL + DL + DD,
157 [0x58] = BDL + DR + LU,
158 [0x59] = BDL + DU + LR,
159 [0x5a] = BDL + DU + DR,
160 [0x5b] = BDL + DL + LU,
161 [0x5c] = BDL + DU + LL,
162 [0x5d] = BDL + DL + DU,
163 [0x5e] = BDL + DR + LV,
164 [0x5f] = BDL + DV + LR,
165 [0x60] = BDL + DV + DR,
166 [0x61] = BDL + DL + LV,
167 [0x62] = BDL + DV + LL,
168 [0x63] = BDL + DV + DL,
169 [0x64] = BDL + DH + LD,
170 [0x65] = BDL + DD + LH,
171 [0x66] = BDL + DD + DH,
172 [0x67] = BDL + DH + LU,
173 [0x68] = BDL + DU + LH,
174 [0x69] = BDL + DH + DU,
175 [0x6a] = BDL + DH + LV,
176 [0x6b] = BDL + DV + LH,
177 [0x6c] = BDL + DH + DV,
178
179 /* (light) arcs */
180 [0x6d] = BDA + LD + LR,
181 [0x6e] = BDA + LD + LL,
182 [0x6f] = BDA + LU + LL,
183 [0x70] = BDA + LU + LR,
184
185 /* Lower (Down) X/8 block (data is 8 - X) */
186 [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4,
187 [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0,
188
189 /* Left X/8 block (data is X) */
190 [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4,
191 [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1,
192
193 /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */
194 [0x80] = BBU + 4, [0x94] = BBU + 1,
195 [0x90] = BBR + 4, [0x95] = BBR + 7,
196
197 /* Quadrants */
198 [0x96] = BBQ + BL,
199 [0x97] = BBQ + BR,
200 [0x98] = BBQ + TL,
201 [0x99] = BBQ + TL + BL + BR,
202 [0x9a] = BBQ + TL + BR,
203 [0x9b] = BBQ + TL + TR + BL,
204 [0x9c] = BBQ + TL + TR + BR,
205 [0x9d] = BBQ + TR,
206 [0x9e] = BBQ + BL + TR,
207 [0x9f] = BBQ + BL + TR + BR,
208
209 /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */
210 [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3,
211
212 /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */
213 /* U+2571 - U+2573: unsupported (diagonals) */
214};
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..8f79881
--- /dev/null
+++ b/config.h
@@ -0,0 +1,557 @@
1/* See LICENSE file for copyright and license details. */
2
3/*
4 * appearance
5 *
6 * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
7 */
8static char *font = "mono:pixelsize=16:antialias=true:autohint=true";
9static char *font2[] = { "NotoColorEmoji:pixelsize=10:antialias=true:autohint=true" };
10static int borderpx = 2;
11
12/*
13 * What program is execed by st depends of these precedence rules:
14 * 1: program passed with -e
15 * 2: scroll and/or utmp
16 * 3: SHELL environment variable
17 * 4: value of shell in /etc/passwd
18 * 5: value of shell in config.h
19 */
20static char *shell = "/bin/sh";
21char *utmp = NULL;
22/* scroll program: to enable use a string like "scroll" */
23char *scroll = NULL;
24char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
25
26/* identification sequence returned in DA and DECID */
27char *vtiden = "\033[?6c";
28
29/* Kerning / character bounding-box multipliers */
30static float cwscale = 1.0;
31static float chscale = 1.0;
32
33/*
34 * word delimiter string
35 *
36 * More advanced example: L" `'\"()[]{}"
37 */
38wchar_t *worddelimiters = L" ";
39
40/* selection timeouts (in milliseconds) */
41static unsigned int doubleclicktimeout = 300;
42static unsigned int tripleclicktimeout = 600;
43
44/* alt screens */
45int allowaltscreen = 1;
46
47/* allow certain non-interactive (insecure) window operations such as:
48 setting the clipboard text */
49int allowwindowops = 0;
50
51/*
52 * draw latency range in ms - from new content/keypress/etc until drawing.
53 * within this range, st draws when content stops arriving (idle). mostly it's
54 * near minlatency, but it waits longer for slow updates to avoid partial draw.
55 * low minlatency will tear/flicker more, as it can "detect" idle too early.
56 */
57static double minlatency = 8;
58static double maxlatency = 33;
59
60/*
61 * blinking timeout (set to 0 to disable blinking) for the terminal blinking
62 * attribute.
63 */
64static unsigned int blinktimeout = 800;
65
66/*
67 * thickness of underline and bar cursors
68 */
69static unsigned int cursorthickness = 2;
70
71/*
72 * 1: render most of the lines/blocks characters without using the font for
73 * perfect alignment between cells (U2500 - U259F except dashes/diagonals).
74 * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored.
75 * 0: disable (render all U25XX glyphs normally from the font).
76 */
77const int boxdraw = 1;
78const int boxdraw_bold = 0;
79
80/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */
81const int boxdraw_braille = 0;
82
83/*
84 * bell volume. It must be a value between -100 and 100. Use 0 for disabling
85 * it
86 */
87static int bellvolume = 0;
88
89/* default TERM value */
90char *termname = "st-256color";
91
92/*
93 * spaces per tab
94 *
95 * When you are changing this value, don't forget to adapt the »it« value in
96 * the st.info and appropriately install the st.info in the environment where
97 * you use this st version.
98 *
99 * it#$tabspaces,
100 *
101 * Secondly make sure your kernel is not expanding tabs. When running `stty
102 * -a` »tab0« should appear. You can tell the terminal to not expand tabs by
103 * running following command:
104 *
105 * stty tabs
106 */
107unsigned int tabspaces = 8;
108
109/* bg opacity */
110float alpha = 0.8;
111float alphaOffset = 0.0;
112float alphaUnfocus;
113
114/* Terminal colors (16 first used in escape sequence) */
115static const char *colorname[] = {
116 "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */
117 "#cc241d",
118 "#98971a",
119 "#d79921",
120 "#458588",
121 "#b16286",
122 "#689d6a",
123 "#a89984",
124 "#928374",
125 "#fb4934",
126 "#b8bb26",
127 "#fabd2f",
128 "#83a598",
129 "#d3869b",
130 "#8ec07c",
131 "#ebdbb2",
132 [255] = 0,
133 /* more colors can be added after 255 to use with DefaultXX */
134 "#add8e6", /* 256 -> cursor */
135 "#555555", /* 257 -> rev cursor*/
136 "#282828", /* 258 -> bg */
137 "#ebdbb2", /* 259 -> fg */
138};
139
140
141/*
142 * Default colors (colorname index)
143 * foreground, background, cursor, reverse cursor
144 */
145unsigned int defaultfg = 259;
146unsigned int defaultbg = 258;
147unsigned int defaultcs = 256;
148unsigned int defaultrcs = 257;
149unsigned int background = 258;
150
151/*
152 * Default shape of cursor
153 * 2: Block ("█")
154 * 4: Underline ("_")
155 * 6: Bar ("|")
156 * 7: Snowman ("☃")
157 */
158static unsigned int cursorshape = 2;
159
160/*
161 * Default columns and rows numbers
162 */
163
164static unsigned int cols = 80;
165static unsigned int rows = 24;
166
167/*
168 * Default colour and shape of the mouse cursor
169 */
170static unsigned int mouseshape = XC_xterm;
171static unsigned int mousefg = 7;
172static unsigned int mousebg = 0;
173
174/*
175 * Color used to display font attributes when fontconfig selected a font which
176 * doesn't match the ones requested.
177 */
178static unsigned int defaultattr = 11;
179
180/*
181 * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
182 * Note that if you want to use ShiftMask with selmasks, set this to an other
183 * modifier, set to 0 to not use it.
184 */
185static uint forcemousemod = ShiftMask;
186
187/*
188 * Xresources preferences to load at startup
189 */
190ResourcePref resources[] = {
191 { "font", STRING, &font },
192 { "fontalt0", STRING, &font2[0] },
193 { "color0", STRING, &colorname[0] },
194 { "color1", STRING, &colorname[1] },
195 { "color2", STRING, &colorname[2] },
196 { "color3", STRING, &colorname[3] },
197 { "color4", STRING, &colorname[4] },
198 { "color5", STRING, &colorname[5] },
199 { "color6", STRING, &colorname[6] },
200 { "color7", STRING, &colorname[7] },
201 { "color8", STRING, &colorname[8] },
202 { "color9", STRING, &colorname[9] },
203 { "color10", STRING, &colorname[10] },
204 { "color11", STRING, &colorname[11] },
205 { "color12", STRING, &colorname[12] },
206 { "color13", STRING, &colorname[13] },
207 { "color14", STRING, &colorname[14] },
208 { "color15", STRING, &colorname[15] },
209 { "background", STRING, &colorname[258] },
210 { "foreground", STRING, &colorname[259] },
211 { "cursorColor", STRING, &colorname[256] },
212 { "termname", STRING, &termname },
213 { "shell", STRING, &shell },
214 { "minlatency", INTEGER, &minlatency },
215 { "maxlatency", INTEGER, &maxlatency },
216 { "blinktimeout", INTEGER, &blinktimeout },
217 { "bellvolume", INTEGER, &bellvolume },
218 { "tabspaces", INTEGER, &tabspaces },
219 { "borderpx", INTEGER, &borderpx },
220 { "cwscale", FLOAT, &cwscale },
221 { "chscale", FLOAT, &chscale },
222 { "alpha", FLOAT, &alpha },
223 { "alphaOffset", FLOAT, &alphaOffset },
224};
225
226/*
227 * Internal mouse shortcuts.
228 * Beware that overloading Button1 will disable the selection.
229 */
230static MouseShortcut mshortcuts[] = {
231 /* mask button function argument release */
232 { XK_NO_MOD, Button4, kscrollup, {.i = 1} },
233 { XK_NO_MOD, Button5, kscrolldown, {.i = 1} },
234 { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
235 { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
236 { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
237 { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} },
238 { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} },
239};
240
241/* Internal keyboard shortcuts. */
242#define MODKEY Mod1Mask
243#define TERMMOD (Mod1Mask|ShiftMask)
244
245static char *openurlcmd[] = { "/bin/sh", "-c", "st-urlhandler -o", "externalpipe", NULL };
246static char *copyurlcmd[] = { "/bin/sh", "-c", "st-urlhandler -c", "externalpipe", NULL };
247static char *copyoutput[] = { "/bin/sh", "-c", "st-copyout", "externalpipe", NULL };
248
249static Shortcut shortcuts[] = {
250 /* mask keysym function argument */
251 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
252 { ControlMask, XK_Print, toggleprinter, {.i = 0} },
253 { ShiftMask, XK_Print, printscreen, {.i = 0} },
254 { XK_ANY_MOD, XK_Print, printsel, {.i = 0} },
255 { TERMMOD, XK_Prior, zoom, {.f = +1} },
256 { TERMMOD, XK_Next, zoom, {.f = -1} },
257 { TERMMOD, XK_Home, zoomreset, {.f = 0} },
258 { TERMMOD, XK_C, clipcopy, {.i = 0} },
259 { TERMMOD, XK_V, clippaste, {.i = 0} },
260 { MODKEY, XK_c, clipcopy, {.i = 0} },
261 { ShiftMask, XK_Insert, clippaste, {.i = 0} },
262 { MODKEY, XK_v, clippaste, {.i = 0} },
263 { ShiftMask, XK_Insert, selpaste, {.i = 0} },
264 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
265 { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
266 { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
267 { MODKEY, XK_Page_Up, kscrollup, {.i = -1} },
268 { MODKEY, XK_Page_Down, kscrolldown, {.i = -1} },
269 { MODKEY, XK_k, kscrollup, {.i = 1} },
270 { MODKEY, XK_j, kscrolldown, {.i = 1} },
271 { MODKEY, XK_Up, kscrollup, {.i = 1} },
272 { MODKEY, XK_Down, kscrolldown, {.i = 1} },
273 { MODKEY, XK_u, kscrollup, {.i = -1} },
274 { MODKEY, XK_d, kscrolldown, {.i = -1} },
275 { MODKEY, XK_s, changealpha, {.f = -0.05} },
276 { MODKEY, XK_a, changealpha, {.f = +0.05} },
277 { TERMMOD, XK_Up, zoom, {.f = +1} },
278 { TERMMOD, XK_Down, zoom, {.f = -1} },
279 { TERMMOD, XK_K, zoom, {.f = +1} },
280 { TERMMOD, XK_J, zoom, {.f = -1} },
281 { TERMMOD, XK_U, zoom, {.f = +2} },
282 { TERMMOD, XK_D, zoom, {.f = -2} },
283 { MODKEY, XK_l, externalpipe, {.v = openurlcmd } },
284 { MODKEY, XK_y, externalpipe, {.v = copyurlcmd } },
285 { MODKEY, XK_o, externalpipe, {.v = copyoutput } },
286};
287
288/*
289 * Special keys (change & recompile st.info accordingly)
290 *
291 * Mask value:
292 * * Use XK_ANY_MOD to match the key no matter modifiers state
293 * * Use XK_NO_MOD to match the key alone (no modifiers)
294 * appkey value:
295 * * 0: no value
296 * * > 0: keypad application mode enabled
297 * * = 2: term.numlock = 1
298 * * < 0: keypad application mode disabled
299 * appcursor value:
300 * * 0: no value
301 * * > 0: cursor application mode enabled
302 * * < 0: cursor application mode disabled
303 *
304 * Be careful with the order of the definitions because st searches in
305 * this table sequentially, so any XK_ANY_MOD must be in the last
306 * position for a key.
307 */
308
309/*
310 * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF)
311 * to be mapped below, add them to this array.
312 */
313static KeySym mappedkeys[] = { -1 };
314
315/*
316 * State bits to ignore when matching key or button events. By default,
317 * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored.
318 */
319static uint ignoremod = Mod2Mask|XK_SWITCH_MOD;
320
321/*
322 * This is the huge key array which defines all compatibility to the Linux
323 * world. Please decide about changes wisely.
324 */
325static Key key[] = {
326 /* keysym mask string appkey appcursor */
327 { XK_KP_Home, ShiftMask, "\033[2J", 0, -1},
328 { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1},
329 { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1},
330 { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1},
331 { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0},
332 { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1},
333 { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1},
334 { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0},
335 { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1},
336 { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1},
337 { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0},
338 { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1},
339 { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1},
340 { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0},
341 { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1},
342 { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1},
343 { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0},
344 { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
345 { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0},
346 { XK_KP_End, ControlMask, "\033[J", -1, 0},
347 { XK_KP_End, ControlMask, "\033[1;5F", +1, 0},
348 { XK_KP_End, ShiftMask, "\033[K", -1, 0},
349 { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0},
350 { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0},
351 { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0},
352 { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0},
353 { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0},
354 { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0},
355 { XK_KP_Insert, ControlMask, "\033[L", -1, 0},
356 { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0},
357 { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
358 { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
359 { XK_KP_Delete, ControlMask, "\033[M", -1, 0},
360 { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0},
361 { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0},
362 { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0},
363 { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0},
364 { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
365 { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0},
366 { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0},
367 { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0},
368 { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0},
369 { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0},
370 { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0},
371 { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0},
372 { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0},
373 { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0},
374 { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0},
375 { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0},
376 { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0},
377 { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0},
378 { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0},
379 { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0},
380 { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0},
381 { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0},
382 { XK_Up, ShiftMask, "\033[1;2A", 0, 0},
383 { XK_Up, Mod1Mask, "\033[1;3A", 0, 0},
384 { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0},
385 { XK_Up, ControlMask, "\033[1;5A", 0, 0},
386 { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0},
387 { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0},
388 { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0},
389 { XK_Up, XK_ANY_MOD, "\033[A", 0, -1},
390 { XK_Up, XK_ANY_MOD, "\033OA", 0, +1},
391 { XK_Down, ShiftMask, "\033[1;2B", 0, 0},
392 { XK_Down, Mod1Mask, "\033[1;3B", 0, 0},
393 { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0},
394 { XK_Down, ControlMask, "\033[1;5B", 0, 0},
395 { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0},
396 { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0},
397 { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0},
398 { XK_Down, XK_ANY_MOD, "\033[B", 0, -1},
399 { XK_Down, XK_ANY_MOD, "\033OB", 0, +1},
400 { XK_Left, ShiftMask, "\033[1;2D", 0, 0},
401 { XK_Left, Mod1Mask, "\033[1;3D", 0, 0},
402 { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0},
403 { XK_Left, ControlMask, "\033[1;5D", 0, 0},
404 { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0},
405 { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0},
406 { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0},
407 { XK_Left, XK_ANY_MOD, "\033[D", 0, -1},
408 { XK_Left, XK_ANY_MOD, "\033OD", 0, +1},
409 { XK_Right, ShiftMask, "\033[1;2C", 0, 0},
410 { XK_Right, Mod1Mask, "\033[1;3C", 0, 0},
411 { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0},
412 { XK_Right, ControlMask, "\033[1;5C", 0, 0},
413 { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0},
414 { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0},
415 { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0},
416 { XK_Right, XK_ANY_MOD, "\033[C", 0, -1},
417 { XK_Right, XK_ANY_MOD, "\033OC", 0, +1},
418 { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0},
419 { XK_Return, Mod1Mask, "\033\r", 0, 0},
420 { XK_Return, XK_ANY_MOD, "\r", 0, 0},
421 { XK_Insert, ShiftMask, "\033[4l", -1, 0},
422 { XK_Insert, ShiftMask, "\033[2;2~", +1, 0},
423 { XK_Insert, ControlMask, "\033[L", -1, 0},
424 { XK_Insert, ControlMask, "\033[2;5~", +1, 0},
425 { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0},
426 { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0},
427 { XK_Delete, ControlMask, "\033[M", -1, 0},
428 { XK_Delete, ControlMask, "\033[3;5~", +1, 0},
429 { XK_Delete, ShiftMask, "\033[2K", -1, 0},
430 { XK_Delete, ShiftMask, "\033[3;2~", +1, 0},
431 { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0},
432 { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0},
433 { XK_BackSpace, XK_NO_MOD, "\177", 0, 0},
434 { XK_BackSpace, Mod1Mask, "\033\177", 0, 0},
435 { XK_Home, ShiftMask, "\033[2J", 0, -1},
436 { XK_Home, ShiftMask, "\033[1;2H", 0, +1},
437 { XK_Home, XK_ANY_MOD, "\033[H", 0, -1},
438 { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1},
439 { XK_End, ControlMask, "\033[J", -1, 0},
440 { XK_End, ControlMask, "\033[1;5F", +1, 0},
441 { XK_End, ShiftMask, "\033[K", -1, 0},
442 { XK_End, ShiftMask, "\033[1;2F", +1, 0},
443 { XK_End, XK_ANY_MOD, "\033[4~", 0, 0},
444 { XK_Prior, ControlMask, "\033[5;5~", 0, 0},
445 { XK_Prior, ShiftMask, "\033[5;2~", 0, 0},
446 { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0},
447 { XK_Next, ControlMask, "\033[6;5~", 0, 0},
448 { XK_Next, ShiftMask, "\033[6;2~", 0, 0},
449 { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0},
450 { XK_F1, XK_NO_MOD, "\033OP" , 0, 0},
451 { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0},
452 { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0},
453 { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0},
454 { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0},
455 { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0},
456 { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0},
457 { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0},
458 { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0},
459 { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0},
460 { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0},
461 { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0},
462 { XK_F3, XK_NO_MOD, "\033OR" , 0, 0},
463 { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0},
464 { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0},
465 { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0},
466 { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0},
467 { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0},
468 { XK_F4, XK_NO_MOD, "\033OS" , 0, 0},
469 { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0},
470 { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0},
471 { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0},
472 { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0},
473 { XK_F5, XK_NO_MOD, "\033[15~", 0, 0},
474 { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0},
475 { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0},
476 { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0},
477 { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0},
478 { XK_F6, XK_NO_MOD, "\033[17~", 0, 0},
479 { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0},
480 { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0},
481 { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0},
482 { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0},
483 { XK_F7, XK_NO_MOD, "\033[18~", 0, 0},
484 { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0},
485 { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0},
486 { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0},
487 { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0},
488 { XK_F8, XK_NO_MOD, "\033[19~", 0, 0},
489 { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0},
490 { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0},
491 { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0},
492 { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0},
493 { XK_F9, XK_NO_MOD, "\033[20~", 0, 0},
494 { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0},
495 { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0},
496 { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0},
497 { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0},
498 { XK_F10, XK_NO_MOD, "\033[21~", 0, 0},
499 { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0},
500 { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0},
501 { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0},
502 { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0},
503 { XK_F11, XK_NO_MOD, "\033[23~", 0, 0},
504 { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0},
505 { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0},
506 { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0},
507 { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0},
508 { XK_F12, XK_NO_MOD, "\033[24~", 0, 0},
509 { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0},
510 { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0},
511 { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0},
512 { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0},
513 { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0},
514 { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0},
515 { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0},
516 { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0},
517 { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0},
518 { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0},
519 { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0},
520 { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0},
521 { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0},
522 { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0},
523 { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0},
524 { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0},
525 { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0},
526 { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0},
527 { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0},
528 { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0},
529 { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0},
530 { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0},
531 { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0},
532 { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0},
533 { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0},
534 { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0},
535 { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0},
536};
537
538/*
539 * Selection types' masks.
540 * Use the same masks as usual.
541 * Button1Mask is always unset, to make masks match between ButtonPress.
542 * ButtonRelease and MotionNotify.
543 * If no match is found, regular selection is used.
544 */
545static uint selmasks[] = {
546 [SEL_RECTANGULAR] = Mod1Mask,
547};
548
549/*
550 * Printable characters in ASCII, used to estimate the advance width
551 * of single wide characters.
552 */
553static char ascii_printable[] =
554 " !\"#$%&'()*+,-./0123456789:;<=>?"
555 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
556 "`abcdefghijklmnopqrstuvwxyz{|}~";
557
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..ef6de39
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,37 @@
1# st version
2VERSION = 0.8.5
3
4# Customize below to fit your system
5
6# paths
7PREFIX = /usr/local
8MANPREFIX = $(PREFIX)/share/man
9
10X11INC = /usr/X11R6/include
11X11LIB = /usr/X11R6/lib
12
13PKG_CONFIG = pkg-config
14
15# includes and libs
16INCS = -I$(X11INC) \
17 `$(PKG_CONFIG) --cflags fontconfig` \
18 `$(PKG_CONFIG) --cflags freetype2` \
19 `$(PKG_CONFIG) --cflags harfbuzz`
20LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\
21 `$(PKG_CONFIG) --libs fontconfig` \
22 `$(PKG_CONFIG) --libs freetype2` \
23 `$(PKG_CONFIG) --libs harfbuzz`
24
25# flags
26STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
27STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS)
28STLDFLAGS = $(LIBS) $(LDFLAGS)
29
30# OpenBSD:
31#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
32#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
33# `$(PKG_CONFIG) --libs fontconfig` \
34# `$(PKG_CONFIG) --libs freetype2`
35
36# compiler and linker
37# CC = c99
diff --git a/hb.c b/hb.c
new file mode 100644
index 0000000..8000afa
--- /dev/null
+++ b/hb.c
@@ -0,0 +1,154 @@
1#include <stdlib.h>
2#include <stdio.h>
3#include <math.h>
4#include <X11/Xft/Xft.h>
5#include <X11/cursorfont.h>
6#include <hb.h>
7#include <hb-ft.h>
8
9#include "st.h"
10
11#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
12
13/*
14 * Replace 0 with a list of font features, wrapped in FEATURE macro, e.g.
15 * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
16 *
17 * Uncomment either one of the 2 lines below. Uncomment the prior to disable (any) font features. Uncomment the
18 * latter to enable the (selected) font features.
19 */
20
21hb_feature_t features[] = { 0 };
22//hb_feature_t features[] = { FEATURE('s','s','0','1'), FEATURE('s','s','0','2'), FEATURE('s','s','0','3'), FEATURE('s','s','0','5'), FEATURE('s','s','0','6'), FEATURE('s','s','0','7'), FEATURE('s','s','0','8'), FEATURE('z','e','r','o') };
23
24void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length);
25hb_font_t *hbfindfont(XftFont *match);
26
27typedef struct {
28 XftFont *match;
29 hb_font_t *font;
30} HbFontMatch;
31
32static int hbfontslen = 0;
33static HbFontMatch *hbfontcache = NULL;
34
35void
36hbunloadfonts()
37{
38 for (int i = 0; i < hbfontslen; i++) {
39 hb_font_destroy(hbfontcache[i].font);
40 XftUnlockFace(hbfontcache[i].match);
41 }
42
43 if (hbfontcache != NULL) {
44 free(hbfontcache);
45 hbfontcache = NULL;
46 }
47 hbfontslen = 0;
48}
49
50hb_font_t *
51hbfindfont(XftFont *match)
52{
53 for (int i = 0; i < hbfontslen; i++) {
54 if (hbfontcache[i].match == match)
55 return hbfontcache[i].font;
56 }
57
58 /* Font not found in cache, caching it now. */
59 hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1));
60 FT_Face face = XftLockFace(match);
61 hb_font_t *font = hb_ft_font_create(face, NULL);
62 if (font == NULL)
63 die("Failed to load Harfbuzz font.");
64
65 hbfontcache[hbfontslen].match = match;
66 hbfontcache[hbfontslen].font = font;
67 hbfontslen += 1;
68
69 return font;
70}
71
72void
73hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y)
74{
75 int start = 0, length = 1, gstart = 0;
76 hb_codepoint_t *codepoints = calloc((unsigned int)len, sizeof(hb_codepoint_t));
77
78 for (int idx = 1, specidx = 1; idx < len; idx++) {
79 if (glyphs[idx].mode & ATTR_WDUMMY) {
80 length += 1;
81 continue;
82 }
83
84 if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) {
85 hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
86
87 /* Reset the sequence. */
88 length = 1;
89 start = specidx;
90 gstart = idx;
91 } else {
92 length += 1;
93 }
94
95 specidx++;
96 }
97
98 /* EOL. */
99 hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
100
101 /* Apply the transformation to glyph specs. */
102 for (int i = 0, specidx = 0; i < len; i++) {
103 if (glyphs[i].mode & ATTR_WDUMMY)
104 continue;
105 if (glyphs[i].mode & ATTR_BOXDRAW) {
106 specidx++;
107 continue;
108 }
109
110 if (codepoints[i] != specs[specidx].glyph)
111 ((Glyph *)glyphs)[i].mode |= ATTR_LIGA;
112
113 specs[specidx++].glyph = codepoints[i];
114 }
115
116 free(codepoints);
117}
118
119void
120hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length)
121{
122 hb_font_t *font = hbfindfont(xfont);
123 if (font == NULL)
124 return;
125
126 Rune rune;
127 ushort mode = USHRT_MAX;
128 hb_buffer_t *buffer = hb_buffer_create();
129 hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
130
131 /* Fill buffer with codepoints. */
132 for (int i = start; i < (start+length); i++) {
133 rune = string[i].u;
134 mode = string[i].mode;
135 if (mode & ATTR_WDUMMY)
136 rune = 0x0020;
137 hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1);
138 }
139
140 /* Shape the segment. */
141 hb_shape(font, buffer, features, sizeof(features));
142
143 /* Get new glyph info. */
144 hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL);
145
146 /* Write new codepoints. */
147 for (int i = 0; i < length; i++) {
148 hb_codepoint_t gid = info[i].codepoint;
149 codepoints[start+i] = gid;
150 }
151
152 /* Cleanup. */
153 hb_buffer_destroy(buffer);
154}
diff --git a/hb.h b/hb.h
new file mode 100644
index 0000000..b3e02d0
--- /dev/null
+++ b/hb.h
@@ -0,0 +1,7 @@
1#include <X11/Xft/Xft.h>
2#include <hb.h>
3#include <hb-ft.h>
4
5void hbunloadfonts();
6void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int);
7
diff --git a/st-copyout b/st-copyout
new file mode 100755
index 0000000..0d19e5a
--- /dev/null
+++ b/st-copyout
@@ -0,0 +1,13 @@
1#!/bin/sh
2# Using external pipe with st, give a dmenu prompt of recent commands,
3# allowing the user to copy the output of one.
4# xclip required for this script.
5# By Jaywalker and Luke
6tmpfile=$(mktemp /tmp/st-cmd-output.XXXXXX)
7trap 'rm "$tmpfile"' 0 1 15
8sed -n "w $tmpfile"
9sed -i 's/\x0//g' "$tmpfile"
10ps1="$(grep "\S" "$tmpfile" | tail -n 1 | sed 's/^\s*//' | cut -d' ' -f1)"
11chosen="$(grep -F "$ps1" "$tmpfile" | sed '$ d' | tac | dmenu -p "Copy which command's output?" -i -l 10 | sed 's/[^^]/[&]/g; s/\^/\\^/g')"
12eps1="$(echo "$ps1" | sed 's/[^^]/[&]/g; s/\^/\\^/g')"
13awk "/^$chosen$/{p=1;print;next} p&&/$eps1/{p=0};p" "$tmpfile" | xclip -selection clipboard
diff --git a/st-urlhandler b/st-urlhandler
new file mode 100755
index 0000000..0eb4586
--- /dev/null
+++ b/st-urlhandler
@@ -0,0 +1,19 @@
1#!/bin/sh
2
3urlregex="(((http|https|gopher|gemini|ftp|ftps|git)://|www\\.)[a-zA-Z0-9.]*[:;a-zA-Z0-9./+@$&%?$\#=_~-]*)|((magnet:\\?xt=urn:btih:)[a-zA-Z0-9]*)"
4
5urls="$(sed 's/.*│//g' | tr -d '\n' | # First remove linebreaks and mutt sidebars:
6 grep -aEo "$urlregex" | # grep only urls as defined above.
7 uniq | # Ignore neighboring duplicates.
8 sed "s/\(\.\|,\|;\|\!\\|\?\)$//;
9 s/^www./http:\/\/www\./")" # xdg-open will not detect url without http
10
11[ -z "$urls" ] && exit 1
12
13while getopts "hoc" o; do case "${o}" in
14 h) printf "Optional arguments for custom use:\\n -c: copy\\n -o: xdg-open\\n -h: Show this message\\n" && exit 1 ;;
15 o) chosen="$(echo "$urls" | dmenu -i -p 'Follow which url?' -l 10)"
16 setsid xdg-open "$chosen" >/dev/null 2>&1 & ;;
17 c) echo "$urls" | dmenu -i -p 'Copy which url?' -l 10 | tr -d '\n' | xclip -selection clipboard ;;
18 *) printf "Invalid option: -%s\\n" "$OPTARG" && exit 1 ;;
19esac done
diff --git a/st.1 b/st.1
new file mode 100644
index 0000000..37f7e84
--- /dev/null
+++ b/st.1
@@ -0,0 +1,193 @@
1.TH ST 1 st\-VERSION
2.SH NAME
3st \- simple terminal
4.SH SYNOPSIS
5.B st
6.RB [ \-aiv ]
7.RB [ \-c
8.IR class ]
9.RB [ \-f
10.IR font ]
11.RB [ \-g
12.IR geometry ]
13.RB [ \-n
14.IR name ]
15.RB [ \-o
16.IR iofile ]
17.RB [ \-T
18.IR title ]
19.RB [ \-t
20.IR title ]
21.RB [ \-l
22.IR line ]
23.RB [ \-w
24.IR windowid ]
25.RB [[ \-e ]
26.IR command
27.RI [ arguments ...]]
28.PP
29.B st
30.RB [ \-aiv ]
31.RB [ \-c
32.IR class ]
33.RB [ \-f
34.IR font ]
35.RB [ \-g
36.IR geometry ]
37.RB [ \-n
38.IR name ]
39.RB [ \-o
40.IR iofile ]
41.RB [ \-T
42.IR title ]
43.RB [ \-t
44.IR title ]
45.RB [ \-w
46.IR windowid ]
47.RB \-l
48.IR line
49.RI [ stty_args ...]
50.SH DESCRIPTION
51.B st
52is a simple terminal emulator.
53.SH OPTIONS
54.TP
55.B \-a
56disable alternate screens in terminal
57.TP
58.BI \-c " class"
59defines the window class (default $TERM).
60.TP
61.BI \-f " font"
62defines the
63.I font
64to use when st is run.
65.TP
66.BI \-g " geometry"
67defines the X11 geometry string.
68The form is [=][<cols>{xX}<rows>][{+-}<xoffset>{+-}<yoffset>]. See
69.BR XParseGeometry (3)
70for further details.
71.TP
72.B \-i
73will fixate the position given with the -g option.
74.TP
75.BI \-n " name"
76defines the window instance name (default $TERM).
77.TP
78.BI \-o " iofile"
79writes all the I/O to
80.I iofile.
81This feature is useful when recording st sessions. A value of "-" means
82standard output.
83.TP
84.BI \-T " title"
85defines the window title (default 'st').
86.TP
87.BI \-t " title"
88defines the window title (default 'st').
89.TP
90.BI \-w " windowid"
91embeds st within the window identified by
92.I windowid
93.TP
94.BI \-l " line"
95use a tty
96.I line
97instead of a pseudo terminal.
98.I line
99should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port
1000).
101When this flag is given
102remaining arguments are used as flags for
103.BR stty(1).
104By default st initializes the serial line to 8 bits, no parity, 1 stop bit
105and a 38400 baud rate. The speed is set by appending it as last argument
106(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are
107.BR stty(1)
108flags. If you want to set odd parity on 115200 baud use for example 'st -l
109/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for
110example 'st -l /dev/ttyS0 cs7 115200'. See
111.BR stty(1)
112for more arguments and cases.
113.TP
114.B \-v
115prints version information to stderr, then exits.
116.TP
117.BI \-e " command " [ " arguments " "... ]"
118st executes
119.I command
120instead of the shell. If this is used it
121.B must be the last option
122on the command line, as in xterm / rxvt.
123This option is only intended for compatibility,
124and all the remaining arguments are used as a command
125even without it.
126.SH SHORTCUTS
127.TP
128.B Alt-j/k or Alt-Up/Down or Alt-Mouse Wheel
129Scroll up/down one line at a time.
130.TP
131.B Alt-u/d or Alt-Page Up/Page Down
132Scroll up/down one screen at a time.
133.TP
134.B Alt-Shift-k/j or Alt-Shift-Page Up/Page Down or Alt-Shift-Mouse Wheel
135Increase or decrease font size.
136.TP
137.B Alt-Home
138Reset to default font size.
139.TP
140.B Shift-Insert or Alt-v
141Paste from clipboard.
142.TP
143.B Alt-c
144Copy to clipboard.
145.TP
146.B Alt-p
147Paste/input primary selection.
148.TP
149.B Alt-l
150Show dmenu menu of all URLs on screen and choose one to open.
151.TP
152.B Alt-y
153Show dmenu menu of all URLs on screen and choose one to copy.
154.TP
155.B Alt-o
156Show dmenu menu of all recently run commands and copy the output of the chosen command to the clipboard.
157.I xclip
158required.
159.TP
160.B Alt-a/s
161Increase or decrease opacity/alpha value (make window more or less transparent).
162.TP
163.B Break
164Send a break in the serial line.
165Break key is obtained in PC keyboards
166pressing at the same time control and pause.
167.TP
168.B Ctrl-Print Screen
169Toggle if st should print to the
170.I iofile.
171.TP
172.B Shift-Print Screen
173Print the full screen to the
174.I iofile.
175.TP
176.B Print Screen
177Print the selection to the
178.I iofile.
179.SH CUSTOMIZATION
180.B st
181can be customized by creating a custom config.h and (re)compiling the source
182code. This keeps it fast, secure and simple.
183.SH AUTHORS
184See the LICENSE file for the authors.
185.SH LICENSE
186See the LICENSE file for the terms of redistribution.
187.SH SEE ALSO
188.BR tabbed (1),
189.BR utmp (1),
190.BR stty (1),
191.BR scroll (1)
192.SH BUGS
193See the TODO file in the distribution.
diff --git a/st.c b/st.c
new file mode 100644
index 0000000..0f4593e
--- /dev/null
+++ b/st.c
@@ -0,0 +1,2816 @@
1/* See LICENSE for license details. */
2#include <ctype.h>
3#include <errno.h>
4#include <fcntl.h>
5#include <limits.h>
6#include <pwd.h>
7#include <stdarg.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <signal.h>
12#include <sys/ioctl.h>
13#include <sys/select.h>
14#include <sys/types.h>
15#include <sys/wait.h>
16#include <termios.h>
17#include <unistd.h>
18#include <wchar.h>
19
20#include "st.h"
21#include "win.h"
22
23#if defined(__linux)
24 #include <pty.h>
25#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
26 #include <util.h>
27#elif defined(__FreeBSD__) || defined(__DragonFly__)
28 #include <libutil.h>
29#endif
30
31/* Arbitrary sizes */
32#define UTF_INVALID 0xFFFD
33#define UTF_SIZ 4
34#define ESC_BUF_SIZ (128*UTF_SIZ)
35#define ESC_ARG_SIZ 16
36#define STR_BUF_SIZ ESC_BUF_SIZ
37#define STR_ARG_SIZ ESC_ARG_SIZ
38#define HISTSIZE 2000
39
40/* macros */
41#define IS_SET(flag) ((term.mode & (flag)) != 0)
42#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
43#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
44#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
45#define ISDELIM(u) (u && wcschr(worddelimiters, u))
46#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
47 term.scr + HISTSIZE + 1) % HISTSIZE] : \
48 term.line[(y) - term.scr])
49#define TLINE_HIST(y) ((y) <= HISTSIZE-term.row+2 ? term.hist[(y)] : term.line[(y-HISTSIZE+term.row-3)])
50
51enum term_mode {
52 MODE_WRAP = 1 << 0,
53 MODE_INSERT = 1 << 1,
54 MODE_ALTSCREEN = 1 << 2,
55 MODE_CRLF = 1 << 3,
56 MODE_ECHO = 1 << 4,
57 MODE_PRINT = 1 << 5,
58 MODE_UTF8 = 1 << 6,
59};
60
61enum cursor_movement {
62 CURSOR_SAVE,
63 CURSOR_LOAD
64};
65
66enum cursor_state {
67 CURSOR_DEFAULT = 0,
68 CURSOR_WRAPNEXT = 1,
69 CURSOR_ORIGIN = 2
70};
71
72enum charset {
73 CS_GRAPHIC0,
74 CS_GRAPHIC1,
75 CS_UK,
76 CS_USA,
77 CS_MULTI,
78 CS_GER,
79 CS_FIN
80};
81
82enum escape_state {
83 ESC_START = 1,
84 ESC_CSI = 2,
85 ESC_STR = 4, /* DCS, OSC, PM, APC */
86 ESC_ALTCHARSET = 8,
87 ESC_STR_END = 16, /* a final string was encountered */
88 ESC_TEST = 32, /* Enter in test mode */
89 ESC_UTF8 = 64,
90};
91
92typedef struct {
93 Glyph attr; /* current char attributes */
94 int x;
95 int y;
96 char state;
97} TCursor;
98
99typedef struct {
100 int mode;
101 int type;
102 int snap;
103 /*
104 * Selection variables:
105 * nb – normalized coordinates of the beginning of the selection
106 * ne – normalized coordinates of the end of the selection
107 * ob – original coordinates of the beginning of the selection
108 * oe – original coordinates of the end of the selection
109 */
110 struct {
111 int x, y;
112 } nb, ne, ob, oe;
113
114 int alt;
115} Selection;
116
117/* Internal representation of the screen */
118typedef struct {
119 int row; /* nb row */
120 int col; /* nb col */
121 int maxcol;
122 Line *line; /* screen */
123 Line *alt; /* alternate screen */
124 Line hist[HISTSIZE]; /* history buffer */
125 int histi; /* history index */
126 int scr; /* scroll back */
127 int *dirty; /* dirtyness of lines */
128 TCursor c; /* cursor */
129 int ocx; /* old cursor col */
130 int ocy; /* old cursor row */
131 int top; /* top scroll limit */
132 int bot; /* bottom scroll limit */
133 int mode; /* terminal mode flags */
134 int esc; /* escape state flags */
135 char trantbl[4]; /* charset table translation */
136 int charset; /* current charset */
137 int icharset; /* selected charset for sequence */
138 int *tabs;
139 Rune lastc; /* last printed char outside of sequence, 0 if control */
140} Term;
141
142/* CSI Escape sequence structs */
143/* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
144typedef struct {
145 char buf[ESC_BUF_SIZ]; /* raw string */
146 size_t len; /* raw string length */
147 char priv;
148 int arg[ESC_ARG_SIZ];
149 int narg; /* nb of args */
150 char mode[2];
151} CSIEscape;
152
153/* STR Escape sequence structs */
154/* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
155typedef struct {
156 char type; /* ESC type ... */
157 char *buf; /* allocated raw string */
158 size_t siz; /* allocation size */
159 size_t len; /* raw string length */
160 char *args[STR_ARG_SIZ];
161 int narg; /* nb of args */
162} STREscape;
163
164static void execsh(char *, char **);
165static void stty(char **);
166static void sigchld(int);
167static void ttywriteraw(const char *, size_t);
168
169static void csidump(void);
170static void csihandle(void);
171static void csiparse(void);
172static void csireset(void);
173static void osc_color_response(int, int, int);
174static int eschandle(uchar);
175static void strdump(void);
176static void strhandle(void);
177static void strparse(void);
178static void strreset(void);
179
180static void tprinter(char *, size_t);
181static void tdumpsel(void);
182static void tdumpline(int);
183static void tdump(void);
184static void tclearregion(int, int, int, int);
185static void tcursor(int);
186static void tdeletechar(int);
187static void tdeleteline(int);
188static void tinsertblank(int);
189static void tinsertblankline(int);
190static int tlinelen(int);
191static void tmoveto(int, int);
192static void tmoveato(int, int);
193static void tnewline(int);
194static void tputtab(int);
195static void tputc(Rune);
196static void treset(void);
197static void tscrollup(int, int, int);
198static void tscrolldown(int, int, int);
199static void tsetattr(const int *, int);
200static void tsetchar(Rune, const Glyph *, int, int);
201static void tsetdirt(int, int);
202static void tsetscroll(int, int);
203static void tswapscreen(void);
204static void tsetmode(int, int, const int *, int);
205static int twrite(const char *, int, int);
206static void tcontrolcode(uchar );
207static void tdectest(char );
208static void tdefutf8(char);
209static int32_t tdefcolor(const int *, int *, int);
210static void tdeftran(char);
211static void tstrsequence(uchar);
212
213static void drawregion(int, int, int, int);
214
215static void selnormalize(void);
216static void selscroll(int, int);
217static void selsnap(int *, int *, int);
218
219static size_t utf8decode(const char *, Rune *, size_t);
220static Rune utf8decodebyte(char, size_t *);
221static char utf8encodebyte(Rune, size_t);
222static size_t utf8validate(Rune *, size_t);
223
224static char *base64dec(const char *);
225static char base64dec_getc(const char **);
226
227static ssize_t xwrite(int, const char *, size_t);
228
229/* Globals */
230static Term term;
231static Selection sel;
232static CSIEscape csiescseq;
233static STREscape strescseq;
234static int iofd = 1;
235static int cmdfd;
236static pid_t pid;
237
238static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
239static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
240static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
241static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
242
243ssize_t
244xwrite(int fd, const char *s, size_t len)
245{
246 size_t aux = len;
247 ssize_t r;
248
249 while (len > 0) {
250 r = write(fd, s, len);
251 if (r < 0)
252 return r;
253 len -= r;
254 s += r;
255 }
256
257 return aux;
258}
259
260void *
261xmalloc(size_t len)
262{
263 void *p;
264
265 if (!(p = malloc(len)))
266 die("malloc: %s\n", strerror(errno));
267
268 return p;
269}
270
271void *
272xrealloc(void *p, size_t len)
273{
274 if ((p = realloc(p, len)) == NULL)
275 die("realloc: %s\n", strerror(errno));
276
277 return p;
278}
279
280char *
281xstrdup(const char *s)
282{
283 if ((s = strdup(s)) == NULL)
284 die("strdup: %s\n", strerror(errno));
285 char *p;
286
287 if ((p = strdup(s)) == NULL)
288 die("strdup: %s\n", strerror(errno));
289
290 return p;
291}
292
293size_t
294utf8decode(const char *c, Rune *u, size_t clen)
295{
296 size_t i, j, len, type;
297 Rune udecoded;
298
299 *u = UTF_INVALID;
300 if (!clen)
301 return 0;
302 udecoded = utf8decodebyte(c[0], &len);
303 if (!BETWEEN(len, 1, UTF_SIZ))
304 return 1;
305 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
306 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
307 if (type != 0)
308 return j;
309 }
310 if (j < len)
311 return 0;
312 *u = udecoded;
313 utf8validate(u, len);
314
315 return len;
316}
317
318Rune
319utf8decodebyte(char c, size_t *i)
320{
321 for (*i = 0; *i < LEN(utfmask); ++(*i))
322 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
323 return (uchar)c & ~utfmask[*i];
324
325 return 0;
326}
327
328size_t
329utf8encode(Rune u, char *c)
330{
331 size_t len, i;
332
333 len = utf8validate(&u, 0);
334 if (len > UTF_SIZ)
335 return 0;
336
337 for (i = len - 1; i != 0; --i) {
338 c[i] = utf8encodebyte(u, 0);
339 u >>= 6;
340 }
341 c[0] = utf8encodebyte(u, len);
342
343 return len;
344}
345
346char
347utf8encodebyte(Rune u, size_t i)
348{
349 return utfbyte[i] | (u & ~utfmask[i]);
350}
351
352size_t
353utf8validate(Rune *u, size_t i)
354{
355 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
356 *u = UTF_INVALID;
357 for (i = 1; *u > utfmax[i]; ++i)
358 ;
359
360 return i;
361}
362
363char
364base64dec_getc(const char **src)
365{
366 while (**src && !isprint((unsigned char)**src))
367 (*src)++;
368 return **src ? *((*src)++) : '='; /* emulate padding if string ends */
369}
370
371char *
372base64dec(const char *src)
373{
374 size_t in_len = strlen(src);
375 char *result, *dst;
376 static const char base64_digits[256] = {
377 [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
378 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
379 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
380 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
381 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
382 };
383
384 if (in_len % 4)
385 in_len += 4 - (in_len % 4);
386 result = dst = xmalloc(in_len / 4 * 3 + 1);
387 while (*src) {
388 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
389 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
390 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
391 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
392
393 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
394 if (a == -1 || b == -1)
395 break;
396
397 *dst++ = (a << 2) | ((b & 0x30) >> 4);
398 if (c == -1)
399 break;
400 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
401 if (d == -1)
402 break;
403 *dst++ = ((c & 0x03) << 6) | d;
404 }
405 *dst = '\0';
406 return result;
407}
408
409void
410selinit(void)
411{
412 sel.mode = SEL_IDLE;
413 sel.snap = 0;
414 sel.ob.x = -1;
415}
416
417int
418tlinelen(int y)
419{
420 int i = term.col;
421
422 if (TLINE(y)[i - 1].mode & ATTR_WRAP)
423 return i;
424
425 while (i > 0 && TLINE(y)[i - 1].u == ' ')
426 --i;
427
428 return i;
429}
430
431int
432tlinehistlen(int y)
433{
434 int i = term.col;
435
436 if (TLINE_HIST(y)[i - 1].mode & ATTR_WRAP)
437 return i;
438
439 while (i > 0 && TLINE_HIST(y)[i - 1].u == ' ')
440 --i;
441
442 return i;
443}
444
445void
446selstart(int col, int row, int snap)
447{
448 selclear();
449 sel.mode = SEL_EMPTY;
450 sel.type = SEL_REGULAR;
451 sel.alt = IS_SET(MODE_ALTSCREEN);
452 sel.snap = snap;
453 sel.oe.x = sel.ob.x = col;
454 sel.oe.y = sel.ob.y = row;
455 selnormalize();
456
457 if (sel.snap != 0)
458 sel.mode = SEL_READY;
459 tsetdirt(sel.nb.y, sel.ne.y);
460}
461
462void
463selextend(int col, int row, int type, int done)
464{
465 int oldey, oldex, oldsby, oldsey, oldtype;
466
467 if (sel.mode == SEL_IDLE)
468 return;
469 if (done && sel.mode == SEL_EMPTY) {
470 selclear();
471 return;
472 }
473
474 oldey = sel.oe.y;
475 oldex = sel.oe.x;
476 oldsby = sel.nb.y;
477 oldsey = sel.ne.y;
478 oldtype = sel.type;
479
480 sel.oe.x = col;
481 sel.oe.y = row;
482 selnormalize();
483 sel.type = type;
484
485 if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
486 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
487
488 sel.mode = done ? SEL_IDLE : SEL_READY;
489}
490
491
492void
493selnormalize(void)
494{
495 int i;
496
497 if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
498 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
499 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
500 } else {
501 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
502 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
503 }
504 sel.nb.y = MIN(sel.ob.y, sel.oe.y);
505 sel.ne.y = MAX(sel.ob.y, sel.oe.y);
506
507 selsnap(&sel.nb.x, &sel.nb.y, -1);
508 selsnap(&sel.ne.x, &sel.ne.y, +1);
509
510 /* expand selection over line breaks */
511 if (sel.type == SEL_RECTANGULAR)
512 return;
513 i = tlinelen(sel.nb.y);
514 if (i < sel.nb.x)
515 sel.nb.x = i;
516 if (tlinelen(sel.ne.y) <= sel.ne.x)
517 sel.ne.x = term.col - 1;
518}
519
520int
521selected(int x, int y)
522{
523 if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
524 sel.alt != IS_SET(MODE_ALTSCREEN))
525 return 0;
526
527 if (sel.type == SEL_RECTANGULAR)
528 return BETWEEN(y, sel.nb.y, sel.ne.y)
529 && BETWEEN(x, sel.nb.x, sel.ne.x);
530
531 return BETWEEN(y, sel.nb.y, sel.ne.y)
532 && (y != sel.nb.y || x >= sel.nb.x)
533 && (y != sel.ne.y || x <= sel.ne.x);
534}
535
536void
537selsnap(int *x, int *y, int direction)
538{
539 int newx, newy, xt, yt;
540 int delim, prevdelim;
541 const Glyph *gp, *prevgp;
542
543 switch (sel.snap) {
544 case SNAP_WORD:
545 /*
546 * Snap around if the word wraps around at the end or
547 * beginning of a line.
548 */
549 prevgp = &TLINE(*y)[*x];
550 prevdelim = ISDELIM(prevgp->u);
551 for (;;) {
552 newx = *x + direction;
553 newy = *y;
554 if (!BETWEEN(newx, 0, term.col - 1)) {
555 newy += direction;
556 newx = (newx + term.col) % term.col;
557 if (!BETWEEN(newy, 0, term.row - 1))
558 break;
559
560 if (direction > 0)
561 yt = *y, xt = *x;
562 else
563 yt = newy, xt = newx;
564 if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
565 break;
566 }
567
568 if (newx >= tlinelen(newy))
569 break;
570
571 gp = &TLINE(newy)[newx];
572 delim = ISDELIM(gp->u);
573 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
574 || (delim && gp->u != prevgp->u)))
575 break;
576
577 *x = newx;
578 *y = newy;
579 prevgp = gp;
580 prevdelim = delim;
581 }
582 break;
583 case SNAP_LINE:
584 /*
585 * Snap around if the the previous line or the current one
586 * has set ATTR_WRAP at its end. Then the whole next or
587 * previous line will be selected.
588 */
589 *x = (direction < 0) ? 0 : term.col - 1;
590 if (direction < 0) {
591 for (; *y > 0; *y += direction) {
592 if (!(TLINE(*y-1)[term.col-1].mode
593 & ATTR_WRAP)) {
594 break;
595 }
596 }
597 } else if (direction > 0) {
598 for (; *y < term.row-1; *y += direction) {
599 if (!(TLINE(*y)[term.col-1].mode
600 & ATTR_WRAP)) {
601 break;
602 }
603 }
604 }
605 break;
606 }
607}
608
609char *
610getsel(void)
611{
612 char *str, *ptr;
613 int y, bufsize, lastx, linelen;
614 const Glyph *gp, *last;
615
616 if (sel.ob.x == -1)
617 return NULL;
618
619 bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
620 ptr = str = xmalloc(bufsize);
621
622 /* append every set & selected glyph to the selection */
623 for (y = sel.nb.y; y <= sel.ne.y; y++) {
624 if ((linelen = tlinelen(y)) == 0) {
625 *ptr++ = '\n';
626 continue;
627 }
628
629 if (sel.type == SEL_RECTANGULAR) {
630 gp = &TLINE(y)[sel.nb.x];
631 lastx = sel.ne.x;
632 } else {
633 gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
634 lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
635 }
636 last = &TLINE(y)[MIN(lastx, linelen-1)];
637 while (last >= gp && last->u == ' ')
638 --last;
639
640 for ( ; gp <= last; ++gp) {
641 if (gp->mode & ATTR_WDUMMY)
642 continue;
643
644 ptr += utf8encode(gp->u, ptr);
645 }
646
647 /*
648 * Copy and pasting of line endings is inconsistent
649 * in the inconsistent terminal and GUI world.
650 * The best solution seems like to produce '\n' when
651 * something is copied from st and convert '\n' to
652 * '\r', when something to be pasted is received by
653 * st.
654 * FIXME: Fix the computer world.
655 */
656 if ((y < sel.ne.y || lastx >= linelen) &&
657 (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
658 *ptr++ = '\n';
659 }
660 *ptr = 0;
661 return str;
662}
663
664void
665selclear(void)
666{
667 if (sel.ob.x == -1)
668 return;
669 sel.mode = SEL_IDLE;
670 sel.ob.x = -1;
671 tsetdirt(sel.nb.y, sel.ne.y);
672}
673
674void
675die(const char *errstr, ...)
676{
677 va_list ap;
678
679 va_start(ap, errstr);
680 vfprintf(stderr, errstr, ap);
681 va_end(ap);
682 exit(1);
683}
684
685void
686execsh(char *cmd, char **args)
687{
688 char *sh, *prog, *arg;
689 const struct passwd *pw;
690
691 errno = 0;
692 if ((pw = getpwuid(getuid())) == NULL) {
693 if (errno)
694 die("getpwuid: %s\n", strerror(errno));
695 else
696 die("who are you?\n");
697 }
698
699 if ((sh = getenv("SHELL")) == NULL)
700 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
701
702 if (args) {
703 prog = args[0];
704 arg = NULL;
705 } else if (scroll) {
706 prog = scroll;
707 arg = utmp ? utmp : sh;
708 } else if (utmp) {
709 prog = utmp;
710 arg = NULL;
711 } else {
712 prog = sh;
713 arg = NULL;
714 }
715 DEFAULT(args, ((char *[]) {prog, arg, NULL}));
716
717 unsetenv("COLUMNS");
718 unsetenv("LINES");
719 unsetenv("TERMCAP");
720 setenv("LOGNAME", pw->pw_name, 1);
721 setenv("USER", pw->pw_name, 1);
722 setenv("SHELL", sh, 1);
723 setenv("HOME", pw->pw_dir, 1);
724 setenv("TERM", termname, 1);
725
726 signal(SIGCHLD, SIG_DFL);
727 signal(SIGHUP, SIG_DFL);
728 signal(SIGINT, SIG_DFL);
729 signal(SIGQUIT, SIG_DFL);
730 signal(SIGTERM, SIG_DFL);
731 signal(SIGALRM, SIG_DFL);
732
733 execvp(prog, args);
734 _exit(1);
735}
736
737void
738sigchld(int a)
739{
740 int stat;
741 pid_t p;
742
743 if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
744 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
745
746 if (pid != p)
747 return;
748
749 if (WIFEXITED(stat) && WEXITSTATUS(stat))
750 die("child exited with status %d\n", WEXITSTATUS(stat));
751 else if (WIFSIGNALED(stat))
752 die("child terminated due to signal %d\n", WTERMSIG(stat));
753 _exit(0);
754}
755
756void
757stty(char **args)
758{
759 char cmd[_POSIX_ARG_MAX], **p, *q, *s;
760 size_t n, siz;
761
762 if ((n = strlen(stty_args)) > sizeof(cmd)-1)
763 die("incorrect stty parameters\n");
764 memcpy(cmd, stty_args, n);
765 q = cmd + n;
766 siz = sizeof(cmd) - n;
767 for (p = args; p && (s = *p); ++p) {
768 if ((n = strlen(s)) > siz-1)
769 die("stty parameter length too long\n");
770 *q++ = ' ';
771 memcpy(q, s, n);
772 q += n;
773 siz -= n + 1;
774 }
775 *q = '\0';
776 if (system(cmd) != 0)
777 perror("Couldn't call stty");
778}
779
780int
781ttynew(const char *line, char *cmd, const char *out, char **args)
782{
783 int m, s;
784
785 if (out) {
786 term.mode |= MODE_PRINT;
787 iofd = (!strcmp(out, "-")) ?
788 1 : open(out, O_WRONLY | O_CREAT, 0666);
789 if (iofd < 0) {
790 fprintf(stderr, "Error opening %s:%s\n",
791 out, strerror(errno));
792 }
793 }
794
795 if (line) {
796 if ((cmdfd = open(line, O_RDWR)) < 0)
797 die("open line '%s' failed: %s\n",
798 line, strerror(errno));
799 dup2(cmdfd, 0);
800 stty(args);
801 return cmdfd;
802 }
803
804 /* seems to work fine on linux, openbsd and freebsd */
805 if (openpty(&m, &s, NULL, NULL, NULL) < 0)
806 die("openpty failed: %s\n", strerror(errno));
807
808 switch (pid = fork()) {
809 case -1:
810 die("fork failed: %s\n", strerror(errno));
811 break;
812 case 0:
813 close(iofd);
814 close(m);
815 setsid(); /* create a new process group */
816 dup2(s, 0);
817 dup2(s, 1);
818 dup2(s, 2);
819 if (ioctl(s, TIOCSCTTY, NULL) < 0)
820 die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
821 if (s > 2)
822 close(s);
823#ifdef __OpenBSD__
824 if (pledge("stdio getpw proc exec", NULL) == -1)
825 die("pledge\n");
826#endif
827 execsh(cmd, args);
828 break;
829 default:
830#ifdef __OpenBSD__
831 if (pledge("stdio rpath tty proc", NULL) == -1)
832 die("pledge\n");
833#endif
834 close(s);
835 cmdfd = m;
836 signal(SIGCHLD, sigchld);
837 break;
838 }
839 return cmdfd;
840}
841
842size_t
843ttyread(void)
844{
845 static char buf[BUFSIZ];
846 static int buflen = 0;
847 int ret, written;
848
849 /* append read bytes to unprocessed bytes */
850 ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
851
852 switch (ret) {
853 case 0:
854 exit(0);
855 case -1:
856 die("couldn't read from shell: %s\n", strerror(errno));
857 default:
858 buflen += ret;
859 written = twrite(buf, buflen, 0);
860 buflen -= written;
861 /* keep any incomplete UTF-8 byte sequence for the next call */
862 if (buflen > 0)
863 memmove(buf, buf + written, buflen);
864 return ret;
865 }
866}
867
868void
869ttywrite(const char *s, size_t n, int may_echo)
870{
871 const char *next;
872 Arg arg = (Arg) { .i = term.scr };
873
874 kscrolldown(&arg);
875
876 if (may_echo && IS_SET(MODE_ECHO))
877 twrite(s, n, 1);
878
879 if (!IS_SET(MODE_CRLF)) {
880 ttywriteraw(s, n);
881 return;
882 }
883
884 /* This is similar to how the kernel handles ONLCR for ttys */
885 while (n > 0) {
886 if (*s == '\r') {
887 next = s + 1;
888 ttywriteraw("\r\n", 2);
889 } else {
890 next = memchr(s, '\r', n);
891 DEFAULT(next, s + n);
892 ttywriteraw(s, next - s);
893 }
894 n -= next - s;
895 s = next;
896 }
897}
898
899void
900ttywriteraw(const char *s, size_t n)
901{
902 fd_set wfd, rfd;
903 ssize_t r;
904 size_t lim = 256;
905
906 /*
907 * Remember that we are using a pty, which might be a modem line.
908 * Writing too much will clog the line. That's why we are doing this
909 * dance.
910 * FIXME: Migrate the world to Plan 9.
911 */
912 while (n > 0) {
913 FD_ZERO(&wfd);
914 FD_ZERO(&rfd);
915 FD_SET(cmdfd, &wfd);
916 FD_SET(cmdfd, &rfd);
917
918 /* Check if we can write. */
919 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
920 if (errno == EINTR)
921 continue;
922 die("select failed: %s\n", strerror(errno));
923 }
924 if (FD_ISSET(cmdfd, &wfd)) {
925 /*
926 * Only write the bytes written by ttywrite() or the
927 * default of 256. This seems to be a reasonable value
928 * for a serial line. Bigger values might clog the I/O.
929 */
930 if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
931 goto write_error;
932 if (r < n) {
933 /*
934 * We weren't able to write out everything.
935 * This means the buffer is getting full
936 * again. Empty it.
937 */
938 if (n < lim)
939 lim = ttyread();
940 n -= r;
941 s += r;
942 } else {
943 /* All bytes have been written. */
944 break;
945 }
946 }
947 if (FD_ISSET(cmdfd, &rfd))
948 lim = ttyread();
949 }
950 return;
951
952write_error:
953 die("write error on tty: %s\n", strerror(errno));
954}
955
956void
957ttyresize(int tw, int th)
958{
959 struct winsize w;
960
961 w.ws_row = term.row;
962 w.ws_col = term.col;
963 w.ws_xpixel = tw;
964 w.ws_ypixel = th;
965 if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
966 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
967}
968
969void
970ttyhangup(void)
971{
972 /* Send SIGHUP to shell */
973 kill(pid, SIGHUP);
974}
975
976int
977tattrset(int attr)
978{
979 int i, j;
980
981 for (i = 0; i < term.row-1; i++) {
982 for (j = 0; j < term.col-1; j++) {
983 if (term.line[i][j].mode & attr)
984 return 1;
985 }
986 }
987
988 return 0;
989}
990
991void
992tsetdirt(int top, int bot)
993{
994 int i;
995
996 LIMIT(top, 0, term.row-1);
997 LIMIT(bot, 0, term.row-1);
998
999 for (i = top; i <= bot; i++)
1000 term.dirty[i] = 1;
1001}
1002
1003void
1004tsetdirtattr(int attr)
1005{
1006 int i, j;
1007
1008 for (i = 0; i < term.row-1; i++) {
1009 for (j = 0; j < term.col-1; j++) {
1010 if (term.line[i][j].mode & attr) {
1011 tsetdirt(i, i);
1012 break;
1013 }
1014 }
1015 }
1016}
1017
1018void
1019tfulldirt(void)
1020{
1021 tsetdirt(0, term.row-1);
1022}
1023
1024void
1025tcursor(int mode)
1026{
1027 static TCursor c[2];
1028 int alt = IS_SET(MODE_ALTSCREEN);
1029
1030 if (mode == CURSOR_SAVE) {
1031 c[alt] = term.c;
1032 } else if (mode == CURSOR_LOAD) {
1033 term.c = c[alt];
1034 tmoveto(c[alt].x, c[alt].y);
1035 }
1036}
1037
1038void
1039treset(void)
1040{
1041 uint i;
1042
1043 term.c = (TCursor){{
1044 .mode = ATTR_NULL,
1045 .fg = defaultfg,
1046 .bg = defaultbg
1047 }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1048
1049 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1050 for (i = tabspaces; i < term.col; i += tabspaces)
1051 term.tabs[i] = 1;
1052 term.top = 0;
1053 term.bot = term.row - 1;
1054 term.mode = MODE_WRAP|MODE_UTF8;
1055 memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1056 term.charset = 0;
1057
1058 for (i = 0; i < 2; i++) {
1059 tmoveto(0, 0);
1060 tcursor(CURSOR_SAVE);
1061 tclearregion(0, 0, term.col-1, term.row-1);
1062 tswapscreen();
1063 }
1064}
1065
1066void
1067tnew(int col, int row)
1068{
1069 term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1070 tresize(col, row);
1071 treset();
1072}
1073
1074void
1075tswapscreen(void)
1076{
1077 Line *tmp = term.line;
1078
1079 term.line = term.alt;
1080 term.alt = tmp;
1081 term.mode ^= MODE_ALTSCREEN;
1082 tfulldirt();
1083}
1084
1085void
1086kscrolldown(const Arg* a)
1087{
1088 int n = a->i;
1089
1090 if (n < 0)
1091 n = term.row + n;
1092
1093 if (n > term.scr)
1094 n = term.scr;
1095
1096 if (term.scr > 0) {
1097 term.scr -= n;
1098 selscroll(0, -n);
1099 tfulldirt();
1100 }
1101}
1102
1103void
1104kscrollup(const Arg* a)
1105{
1106 int n = a->i;
1107
1108 if (n < 0)
1109 n = term.row + n;
1110
1111 if (term.scr <= HISTSIZE-n) {
1112 term.scr += n;
1113 selscroll(0, n);
1114 tfulldirt();
1115 }
1116}
1117
1118void
1119tscrolldown(int orig, int n, int copyhist)
1120{
1121 int i;
1122 Line temp;
1123
1124 LIMIT(n, 0, term.bot-orig+1);
1125
1126 if (copyhist) {
1127 term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
1128 temp = term.hist[term.histi];
1129 term.hist[term.histi] = term.line[term.bot];
1130 term.line[term.bot] = temp;
1131 }
1132
1133 tsetdirt(orig, term.bot-n);
1134 tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1135
1136 for (i = term.bot; i >= orig+n; i--) {
1137 temp = term.line[i];
1138 term.line[i] = term.line[i-n];
1139 term.line[i-n] = temp;
1140 }
1141
1142 if (term.scr == 0)
1143 selscroll(orig, n);
1144}
1145
1146void
1147tscrollup(int orig, int n, int copyhist)
1148{
1149 int i;
1150 Line temp;
1151
1152 LIMIT(n, 0, term.bot-orig+1);
1153
1154 if (copyhist) {
1155 term.histi = (term.histi + 1) % HISTSIZE;
1156 temp = term.hist[term.histi];
1157 term.hist[term.histi] = term.line[orig];
1158 term.line[orig] = temp;
1159 }
1160
1161 if (term.scr > 0 && term.scr < HISTSIZE)
1162 term.scr = MIN(term.scr + n, HISTSIZE-1);
1163
1164 tclearregion(0, orig, term.col-1, orig+n-1);
1165 tsetdirt(orig+n, term.bot);
1166
1167 for (i = orig; i <= term.bot-n; i++) {
1168 temp = term.line[i];
1169 term.line[i] = term.line[i+n];
1170 term.line[i+n] = temp;
1171 }
1172
1173 if (term.scr == 0)
1174 selscroll(orig, -n);
1175}
1176
1177void
1178selscroll(int orig, int n)
1179{
1180 if (sel.ob.x == -1)
1181 return;
1182
1183 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
1184 selclear();
1185 } else if (BETWEEN(sel.nb.y, orig, term.bot)) {
1186 sel.ob.y += n;
1187 sel.oe.y += n;
1188 if (sel.ob.y < term.top || sel.ob.y > term.bot ||
1189 sel.oe.y < term.top || sel.oe.y > term.bot) {
1190 selclear();
1191 } else {
1192 selnormalize();
1193 }
1194 }
1195}
1196
1197void
1198tnewline(int first_col)
1199{
1200 int y = term.c.y;
1201
1202 if (y == term.bot) {
1203 tscrollup(term.top, 1, 1);
1204 } else {
1205 y++;
1206 }
1207 tmoveto(first_col ? 0 : term.c.x, y);
1208}
1209
1210void
1211csiparse(void)
1212{
1213 char *p = csiescseq.buf, *np;
1214 long int v;
1215
1216 csiescseq.narg = 0;
1217 if (*p == '?') {
1218 csiescseq.priv = 1;
1219 p++;
1220 }
1221
1222 csiescseq.buf[csiescseq.len] = '\0';
1223 while (p < csiescseq.buf+csiescseq.len) {
1224 np = NULL;
1225 v = strtol(p, &np, 10);
1226 if (np == p)
1227 v = 0;
1228 if (v == LONG_MAX || v == LONG_MIN)
1229 v = -1;
1230 csiescseq.arg[csiescseq.narg++] = v;
1231 p = np;
1232 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1233 break;
1234 p++;
1235 }
1236 csiescseq.mode[0] = *p++;
1237 csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1238}
1239
1240/* for absolute user moves, when decom is set */
1241void
1242tmoveato(int x, int y)
1243{
1244 tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1245}
1246
1247void
1248tmoveto(int x, int y)
1249{
1250 int miny, maxy;
1251
1252 if (term.c.state & CURSOR_ORIGIN) {
1253 miny = term.top;
1254 maxy = term.bot;
1255 } else {
1256 miny = 0;
1257 maxy = term.row - 1;
1258 }
1259 term.c.state &= ~CURSOR_WRAPNEXT;
1260 term.c.x = LIMIT(x, 0, term.col-1);
1261 term.c.y = LIMIT(y, miny, maxy);
1262}
1263
1264void
1265tsetchar(Rune u, const Glyph *attr, int x, int y)
1266{
1267 static const char *vt100_0[62] = { /* 0x41 - 0x7e */
1268 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1269 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1270 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1271 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1272 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1273 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1274 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1275 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1276 };
1277
1278 /*
1279 * The table is proudly stolen from rxvt.
1280 */
1281 if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1282 BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1283 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1284
1285 if (term.line[y][x].mode & ATTR_WIDE) {
1286 if (x+1 < term.col) {
1287 term.line[y][x+1].u = ' ';
1288 term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1289 }
1290 } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1291 term.line[y][x-1].u = ' ';
1292 term.line[y][x-1].mode &= ~ATTR_WIDE;
1293 }
1294
1295 term.dirty[y] = 1;
1296 term.line[y][x] = *attr;
1297 term.line[y][x].u = u;
1298
1299 if (isboxdraw(u))
1300 term.line[y][x].mode |= ATTR_BOXDRAW;
1301}
1302
1303void
1304tclearregion(int x1, int y1, int x2, int y2)
1305{
1306 int x, y, temp;
1307 Glyph *gp;
1308
1309 if (x1 > x2)
1310 temp = x1, x1 = x2, x2 = temp;
1311 if (y1 > y2)
1312 temp = y1, y1 = y2, y2 = temp;
1313
1314 LIMIT(x1, 0, term.maxcol-1);
1315 LIMIT(x2, 0, term.maxcol-1);
1316 LIMIT(y1, 0, term.row-1);
1317 LIMIT(y2, 0, term.row-1);
1318
1319 for (y = y1; y <= y2; y++) {
1320 term.dirty[y] = 1;
1321 for (x = x1; x <= x2; x++) {
1322 gp = &term.line[y][x];
1323 if (selected(x, y))
1324 selclear();
1325 gp->fg = term.c.attr.fg;
1326 gp->bg = term.c.attr.bg;
1327 gp->mode = 0;
1328 gp->u = ' ';
1329 }
1330 }
1331}
1332
1333void
1334tdeletechar(int n)
1335{
1336 int dst, src, size;
1337 Glyph *line;
1338
1339 LIMIT(n, 0, term.col - term.c.x);
1340
1341 dst = term.c.x;
1342 src = term.c.x + n;
1343 size = term.col - src;
1344 line = term.line[term.c.y];
1345
1346 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1347 tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1348}
1349
1350void
1351tinsertblank(int n)
1352{
1353 int dst, src, size;
1354 Glyph *line;
1355
1356 LIMIT(n, 0, term.col - term.c.x);
1357
1358 dst = term.c.x + n;
1359 src = term.c.x;
1360 size = term.col - dst;
1361 line = term.line[term.c.y];
1362
1363 memmove(&line[dst], &line[src], size * sizeof(Glyph));
1364 tclearregion(src, term.c.y, dst - 1, term.c.y);
1365}
1366
1367void
1368tinsertblankline(int n)
1369{
1370 if (BETWEEN(term.c.y, term.top, term.bot))
1371 tscrolldown(term.c.y, n, 0);
1372}
1373
1374void
1375tdeleteline(int n)
1376{
1377 if (BETWEEN(term.c.y, term.top, term.bot))
1378 tscrollup(term.c.y, n, 0);
1379}
1380
1381int32_t
1382tdefcolor(const int *attr, int *npar, int l)
1383{
1384 int32_t idx = -1;
1385 uint r, g, b;
1386
1387 switch (attr[*npar + 1]) {
1388 case 2: /* direct color in RGB space */
1389 if (*npar + 4 >= l) {
1390 fprintf(stderr,
1391 "erresc(38): Incorrect number of parameters (%d)\n",
1392 *npar);
1393 break;
1394 }
1395 r = attr[*npar + 2];
1396 g = attr[*npar + 3];
1397 b = attr[*npar + 4];
1398 *npar += 4;
1399 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1400 fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1401 r, g, b);
1402 else
1403 idx = TRUECOLOR(r, g, b);
1404 break;
1405 case 5: /* indexed color */
1406 if (*npar + 2 >= l) {
1407 fprintf(stderr,
1408 "erresc(38): Incorrect number of parameters (%d)\n",
1409 *npar);
1410 break;
1411 }
1412 *npar += 2;
1413 if (!BETWEEN(attr[*npar], 0, 255))
1414 fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1415 else
1416 idx = attr[*npar];
1417 break;
1418 case 0: /* implemented defined (only foreground) */
1419 case 1: /* transparent */
1420 case 3: /* direct color in CMY space */
1421 case 4: /* direct color in CMYK space */
1422 default:
1423 fprintf(stderr,
1424 "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1425 break;
1426 }
1427
1428 return idx;
1429}
1430
1431void
1432tsetattr(const int *attr, int l)
1433{
1434 int i;
1435 int32_t idx;
1436
1437 for (i = 0; i < l; i++) {
1438 switch (attr[i]) {
1439 case 0:
1440 term.c.attr.mode &= ~(
1441 ATTR_BOLD |
1442 ATTR_FAINT |
1443 ATTR_ITALIC |
1444 ATTR_UNDERLINE |
1445 ATTR_BLINK |
1446 ATTR_REVERSE |
1447 ATTR_INVISIBLE |
1448 ATTR_STRUCK );
1449 term.c.attr.fg = defaultfg;
1450 term.c.attr.bg = defaultbg;
1451 break;
1452 case 1:
1453 term.c.attr.mode |= ATTR_BOLD;
1454 break;
1455 case 2:
1456 term.c.attr.mode |= ATTR_FAINT;
1457 break;
1458 case 3:
1459 term.c.attr.mode |= ATTR_ITALIC;
1460 break;
1461 case 4:
1462 term.c.attr.mode |= ATTR_UNDERLINE;
1463 break;
1464 case 5: /* slow blink */
1465 /* FALLTHROUGH */
1466 case 6: /* rapid blink */
1467 term.c.attr.mode |= ATTR_BLINK;
1468 break;
1469 case 7:
1470 term.c.attr.mode |= ATTR_REVERSE;
1471 break;
1472 case 8:
1473 term.c.attr.mode |= ATTR_INVISIBLE;
1474 break;
1475 case 9:
1476 term.c.attr.mode |= ATTR_STRUCK;
1477 break;
1478 case 22:
1479 term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1480 break;
1481 case 23:
1482 term.c.attr.mode &= ~ATTR_ITALIC;
1483 break;
1484 case 24:
1485 term.c.attr.mode &= ~ATTR_UNDERLINE;
1486 break;
1487 case 25:
1488 term.c.attr.mode &= ~ATTR_BLINK;
1489 break;
1490 case 27:
1491 term.c.attr.mode &= ~ATTR_REVERSE;
1492 break;
1493 case 28:
1494 term.c.attr.mode &= ~ATTR_INVISIBLE;
1495 break;
1496 case 29:
1497 term.c.attr.mode &= ~ATTR_STRUCK;
1498 break;
1499 case 38:
1500 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1501 term.c.attr.fg = idx;
1502 break;
1503 case 39:
1504 term.c.attr.fg = defaultfg;
1505 break;
1506 case 48:
1507 if ((idx = tdefcolor(attr, &i, l)) >= 0)
1508 term.c.attr.bg = idx;
1509 break;
1510 case 49:
1511 term.c.attr.bg = defaultbg;
1512 break;
1513 default:
1514 if (BETWEEN(attr[i], 30, 37)) {
1515 term.c.attr.fg = attr[i] - 30;
1516 } else if (BETWEEN(attr[i], 40, 47)) {
1517 term.c.attr.bg = attr[i] - 40;
1518 } else if (BETWEEN(attr[i], 90, 97)) {
1519 term.c.attr.fg = attr[i] - 90 + 8;
1520 } else if (BETWEEN(attr[i], 100, 107)) {
1521 term.c.attr.bg = attr[i] - 100 + 8;
1522 } else {
1523 fprintf(stderr,
1524 "erresc(default): gfx attr %d unknown\n",
1525 attr[i]);
1526 csidump();
1527 }
1528 break;
1529 }
1530 }
1531}
1532
1533void
1534tsetscroll(int t, int b)
1535{
1536 int temp;
1537
1538 LIMIT(t, 0, term.row-1);
1539 LIMIT(b, 0, term.row-1);
1540 if (t > b) {
1541 temp = t;
1542 t = b;
1543 b = temp;
1544 }
1545 term.top = t;
1546 term.bot = b;
1547}
1548
1549void
1550tsetmode(int priv, int set, const int *args, int narg)
1551{
1552 int alt; const int *lim;
1553
1554 for (lim = args + narg; args < lim; ++args) {
1555 if (priv) {
1556 switch (*args) {
1557 case 1: /* DECCKM -- Cursor key */
1558 xsetmode(set, MODE_APPCURSOR);
1559 break;
1560 case 5: /* DECSCNM -- Reverse video */
1561 xsetmode(set, MODE_REVERSE);
1562 break;
1563 case 6: /* DECOM -- Origin */
1564 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1565 tmoveato(0, 0);
1566 break;
1567 case 7: /* DECAWM -- Auto wrap */
1568 MODBIT(term.mode, set, MODE_WRAP);
1569 break;
1570 case 0: /* Error (IGNORED) */
1571 case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
1572 case 3: /* DECCOLM -- Column (IGNORED) */
1573 case 4: /* DECSCLM -- Scroll (IGNORED) */
1574 case 8: /* DECARM -- Auto repeat (IGNORED) */
1575 case 18: /* DECPFF -- Printer feed (IGNORED) */
1576 case 19: /* DECPEX -- Printer extent (IGNORED) */
1577 case 42: /* DECNRCM -- National characters (IGNORED) */
1578 case 12: /* att610 -- Start blinking cursor (IGNORED) */
1579 break;
1580 case 25: /* DECTCEM -- Text Cursor Enable Mode */
1581 xsetmode(!set, MODE_HIDE);
1582 break;
1583 case 9: /* X10 mouse compatibility mode */
1584 xsetpointermotion(0);
1585 xsetmode(0, MODE_MOUSE);
1586 xsetmode(set, MODE_MOUSEX10);
1587 break;
1588 case 1000: /* 1000: report button press */
1589 xsetpointermotion(0);
1590 xsetmode(0, MODE_MOUSE);
1591 xsetmode(set, MODE_MOUSEBTN);
1592 break;
1593 case 1002: /* 1002: report motion on button press */
1594 xsetpointermotion(0);
1595 xsetmode(0, MODE_MOUSE);
1596 xsetmode(set, MODE_MOUSEMOTION);
1597 break;
1598 case 1003: /* 1003: enable all mouse motions */
1599 xsetpointermotion(set);
1600 xsetmode(0, MODE_MOUSE);
1601 xsetmode(set, MODE_MOUSEMANY);
1602 break;
1603 case 1004: /* 1004: send focus events to tty */
1604 xsetmode(set, MODE_FOCUS);
1605 break;
1606 case 1006: /* 1006: extended reporting mode */
1607 xsetmode(set, MODE_MOUSESGR);
1608 break;
1609 case 1034:
1610 xsetmode(set, MODE_8BIT);
1611 break;
1612 case 1049: /* swap screen & set/restore cursor as xterm */
1613 if (!allowaltscreen)
1614 break;
1615 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1616 /* FALLTHROUGH */
1617 case 47: /* swap screen */
1618 case 1047:
1619 if (!allowaltscreen)
1620 break;
1621 alt = IS_SET(MODE_ALTSCREEN);
1622 if (alt) {
1623 tclearregion(0, 0, term.col-1,
1624 term.row-1);
1625 }
1626 if (set ^ alt) /* set is always 1 or 0 */
1627 tswapscreen();
1628 if (*args != 1049)
1629 break;
1630 /* FALLTHROUGH */
1631 case 1048:
1632 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1633 break;
1634 case 2004: /* 2004: bracketed paste mode */
1635 xsetmode(set, MODE_BRCKTPASTE);
1636 break;
1637 /* Not implemented mouse modes. See comments there. */
1638 case 1001: /* mouse highlight mode; can hang the
1639 terminal by design when implemented. */
1640 case 1005: /* UTF-8 mouse mode; will confuse
1641 applications not supporting UTF-8
1642 and luit. */
1643 case 1015: /* urxvt mangled mouse mode; incompatible
1644 and can be mistaken for other control
1645 codes. */
1646 break;
1647 default:
1648 fprintf(stderr,
1649 "erresc: unknown private set/reset mode %d\n",
1650 *args);
1651 break;
1652 }
1653 } else {
1654 switch (*args) {
1655 case 0: /* Error (IGNORED) */
1656 break;
1657 case 2:
1658 xsetmode(set, MODE_KBDLOCK);
1659 break;
1660 case 4: /* IRM -- Insertion-replacement */
1661 MODBIT(term.mode, set, MODE_INSERT);
1662 break;
1663 case 12: /* SRM -- Send/Receive */
1664 MODBIT(term.mode, !set, MODE_ECHO);
1665 break;
1666 case 20: /* LNM -- Linefeed/new line */
1667 MODBIT(term.mode, set, MODE_CRLF);
1668 break;
1669 default:
1670 fprintf(stderr,
1671 "erresc: unknown set/reset mode %d\n",
1672 *args);
1673 break;
1674 }
1675 }
1676 }
1677}
1678
1679void
1680csihandle(void)
1681{
1682 char buf[40];
1683 int len;
1684
1685 switch (csiescseq.mode[0]) {
1686 default:
1687 unknown:
1688 fprintf(stderr, "erresc: unknown csi ");
1689 csidump();
1690 /* die(""); */
1691 break;
1692 case '@': /* ICH -- Insert <n> blank char */
1693 DEFAULT(csiescseq.arg[0], 1);
1694 tinsertblank(csiescseq.arg[0]);
1695 break;
1696 case 'A': /* CUU -- Cursor <n> Up */
1697 DEFAULT(csiescseq.arg[0], 1);
1698 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1699 break;
1700 case 'B': /* CUD -- Cursor <n> Down */
1701 case 'e': /* VPR --Cursor <n> Down */
1702 DEFAULT(csiescseq.arg[0], 1);
1703 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1704 break;
1705 case 'i': /* MC -- Media Copy */
1706 switch (csiescseq.arg[0]) {
1707 case 0:
1708 tdump();
1709 break;
1710 case 1:
1711 tdumpline(term.c.y);
1712 break;
1713 case 2:
1714 tdumpsel();
1715 break;
1716 case 4:
1717 term.mode &= ~MODE_PRINT;
1718 break;
1719 case 5:
1720 term.mode |= MODE_PRINT;
1721 break;
1722 }
1723 break;
1724 case 'c': /* DA -- Device Attributes */
1725 if (csiescseq.arg[0] == 0)
1726 ttywrite(vtiden, strlen(vtiden), 0);
1727 break;
1728 case 'b': /* REP -- if last char is printable print it <n> more times */
1729 DEFAULT(csiescseq.arg[0], 1);
1730 if (term.lastc)
1731 while (csiescseq.arg[0]-- > 0)
1732 tputc(term.lastc);
1733 break;
1734 case 'C': /* CUF -- Cursor <n> Forward */
1735 case 'a': /* HPR -- Cursor <n> Forward */
1736 DEFAULT(csiescseq.arg[0], 1);
1737 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1738 break;
1739 case 'D': /* CUB -- Cursor <n> Backward */
1740 DEFAULT(csiescseq.arg[0], 1);
1741 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1742 break;
1743 case 'E': /* CNL -- Cursor <n> Down and first col */
1744 DEFAULT(csiescseq.arg[0], 1);
1745 tmoveto(0, term.c.y+csiescseq.arg[0]);
1746 break;
1747 case 'F': /* CPL -- Cursor <n> Up and first col */
1748 DEFAULT(csiescseq.arg[0], 1);
1749 tmoveto(0, term.c.y-csiescseq.arg[0]);
1750 break;
1751 case 'g': /* TBC -- Tabulation clear */
1752 switch (csiescseq.arg[0]) {
1753 case 0: /* clear current tab stop */
1754 term.tabs[term.c.x] = 0;
1755 break;
1756 case 3: /* clear all the tabs */
1757 memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1758 break;
1759 default:
1760 goto unknown;
1761 }
1762 break;
1763 case 'G': /* CHA -- Move to <col> */
1764 case '`': /* HPA */
1765 DEFAULT(csiescseq.arg[0], 1);
1766 tmoveto(csiescseq.arg[0]-1, term.c.y);
1767 break;
1768 case 'H': /* CUP -- Move to <row> <col> */
1769 case 'f': /* HVP */
1770 DEFAULT(csiescseq.arg[0], 1);
1771 DEFAULT(csiescseq.arg[1], 1);
1772 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1773 break;
1774 case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1775 DEFAULT(csiescseq.arg[0], 1);
1776 tputtab(csiescseq.arg[0]);
1777 break;
1778 case 'J': /* ED -- Clear screen */
1779 switch (csiescseq.arg[0]) {
1780 case 0: /* below */
1781 tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1782 if (term.c.y < term.row-1) {
1783 tclearregion(0, term.c.y+1, term.col-1,
1784 term.row-1);
1785 }
1786 break;
1787 case 1: /* above */
1788 if (term.c.y > 1)
1789 tclearregion(0, 0, term.col-1, term.c.y-1);
1790 tclearregion(0, term.c.y, term.c.x, term.c.y);
1791 break;
1792 case 2: /* all */
1793 tclearregion(0, 0, term.col-1, term.row-1);
1794 break;
1795 default:
1796 goto unknown;
1797 }
1798 break;
1799 case 'K': /* EL -- Clear line */
1800 switch (csiescseq.arg[0]) {
1801 case 0: /* right */
1802 tclearregion(term.c.x, term.c.y, term.col-1,
1803 term.c.y);
1804 break;
1805 case 1: /* left */
1806 tclearregion(0, term.c.y, term.c.x, term.c.y);
1807 break;
1808 case 2: /* all */
1809 tclearregion(0, term.c.y, term.col-1, term.c.y);
1810 break;
1811 }
1812 break;
1813 case 'S': /* SU -- Scroll <n> line up */
1814 DEFAULT(csiescseq.arg[0], 1);
1815 tscrollup(term.top, csiescseq.arg[0], 0);
1816 break;
1817 case 'T': /* SD -- Scroll <n> line down */
1818 DEFAULT(csiescseq.arg[0], 1);
1819 tscrolldown(term.top, csiescseq.arg[0], 0);
1820 break;
1821 case 'L': /* IL -- Insert <n> blank lines */
1822 DEFAULT(csiescseq.arg[0], 1);
1823 tinsertblankline(csiescseq.arg[0]);
1824 break;
1825 case 'l': /* RM -- Reset Mode */
1826 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1827 break;
1828 case 'M': /* DL -- Delete <n> lines */
1829 DEFAULT(csiescseq.arg[0], 1);
1830 tdeleteline(csiescseq.arg[0]);
1831 break;
1832 case 'X': /* ECH -- Erase <n> char */
1833 DEFAULT(csiescseq.arg[0], 1);
1834 tclearregion(term.c.x, term.c.y,
1835 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1836 break;
1837 case 'P': /* DCH -- Delete <n> char */
1838 DEFAULT(csiescseq.arg[0], 1);
1839 tdeletechar(csiescseq.arg[0]);
1840 break;
1841 case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1842 DEFAULT(csiescseq.arg[0], 1);
1843 tputtab(-csiescseq.arg[0]);
1844 break;
1845 case 'd': /* VPA -- Move to <row> */
1846 DEFAULT(csiescseq.arg[0], 1);
1847 tmoveato(term.c.x, csiescseq.arg[0]-1);
1848 break;
1849 case 'h': /* SM -- Set terminal mode */
1850 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1851 break;
1852 case 'm': /* SGR -- Terminal attribute (color) */
1853 tsetattr(csiescseq.arg, csiescseq.narg);
1854 break;
1855 case 'n': /* DSR – Device Status Report (cursor position) */
1856 if (csiescseq.arg[0] == 6) {
1857 len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
1858 term.c.y+1, term.c.x+1);
1859 ttywrite(buf, len, 0);
1860 }
1861 break;
1862 case 'r': /* DECSTBM -- Set Scrolling Region */
1863 if (csiescseq.priv) {
1864 goto unknown;
1865 } else {
1866 DEFAULT(csiescseq.arg[0], 1);
1867 DEFAULT(csiescseq.arg[1], term.row);
1868 tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1869 tmoveato(0, 0);
1870 }
1871 break;
1872 case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1873 tcursor(CURSOR_SAVE);
1874 break;
1875 case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1876 tcursor(CURSOR_LOAD);
1877 break;
1878 case ' ':
1879 switch (csiescseq.mode[1]) {
1880 case 'q': /* DECSCUSR -- Set Cursor Style */
1881 if (xsetcursor(csiescseq.arg[0]))
1882 goto unknown;
1883 break;
1884 default:
1885 goto unknown;
1886 }
1887 break;
1888 }
1889}
1890
1891void
1892csidump(void)
1893{
1894 size_t i;
1895 uint c;
1896
1897 fprintf(stderr, "ESC[");
1898 for (i = 0; i < csiescseq.len; i++) {
1899 c = csiescseq.buf[i] & 0xff;
1900 if (isprint(c)) {
1901 putc(c, stderr);
1902 } else if (c == '\n') {
1903 fprintf(stderr, "(\\n)");
1904 } else if (c == '\r') {
1905 fprintf(stderr, "(\\r)");
1906 } else if (c == 0x1b) {
1907 fprintf(stderr, "(\\e)");
1908 } else {
1909 fprintf(stderr, "(%02x)", c);
1910 }
1911 }
1912 putc('\n', stderr);
1913}
1914
1915void
1916csireset(void)
1917{
1918 memset(&csiescseq, 0, sizeof(csiescseq));
1919}
1920
1921void
1922osc_color_response(int num, int index, int is_osc4)
1923{
1924 int n;
1925 char buf[32];
1926 unsigned char r, g, b;
1927
1928 if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
1929 fprintf(stderr, "erresc: failed to fetch %s color %d\n",
1930 is_osc4 ? "osc4" : "osc",
1931 is_osc4 ? num : index);
1932 return;
1933 }
1934
1935 n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
1936 is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
1937 if (n < 0 || n >= sizeof(buf)) {
1938 fprintf(stderr, "error: %s while printing %s response\n",
1939 n < 0 ? "snprintf failed" : "truncation occurred",
1940 is_osc4 ? "osc4" : "osc");
1941 } else {
1942 ttywrite(buf, n, 1);
1943 }
1944}
1945
1946void
1947strhandle(void)
1948{
1949 char *p = NULL, *dec;
1950 int j, narg, par;
1951 const struct { int idx; char *str; } osc_table[] = {
1952 { defaultfg, "foreground" },
1953 { defaultbg, "background" },
1954 { defaultcs, "cursor" }
1955 };
1956
1957 term.esc &= ~(ESC_STR_END|ESC_STR);
1958 strparse();
1959 par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1960
1961 switch (strescseq.type) {
1962 case ']': /* OSC -- Operating System Command */
1963 switch (par) {
1964 case 0:
1965 if (narg > 1) {
1966 xsettitle(strescseq.args[1]);
1967 xseticontitle(strescseq.args[1]);
1968 }
1969 return;
1970 case 1:
1971 if (narg > 1)
1972 xseticontitle(strescseq.args[1]);
1973 return;
1974 case 2:
1975 if (narg > 1)
1976 xsettitle(strescseq.args[1]);
1977 return;
1978 case 52:
1979 if (narg > 2 && allowwindowops) {
1980 dec = base64dec(strescseq.args[2]);
1981 if (dec) {
1982 xsetsel(dec);
1983 xclipcopy();
1984 } else {
1985 fprintf(stderr, "erresc: invalid base64\n");
1986 }
1987 }
1988 return;
1989 case 10:
1990 case 11:
1991 case 12:
1992 if (narg < 2)
1993 break;
1994 p = strescseq.args[1];
1995 if ((j = par - 10) < 0 || j >= LEN(osc_table))
1996 break; /* shouldn't be possible */
1997
1998 if (!strcmp(p, "?")) {
1999 osc_color_response(par, osc_table[j].idx, 0);
2000 } else if (xsetcolorname(osc_table[j].idx, p)) {
2001 fprintf(stderr, "erresc: invalid %s color: %s\n",
2002 osc_table[j].str, p);
2003 } else {
2004 tfulldirt();
2005 }
2006 return;
2007 case 4: /* color set */
2008 if (narg < 3)
2009 break;
2010 p = strescseq.args[2];
2011 /* FALLTHROUGH */
2012 case 104: /* color reset */
2013 j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
2014
2015 if (p && !strcmp(p, "?")) {
2016 osc_color_response(j, 0, 1);
2017 } else if (xsetcolorname(j, p)) {
2018 if (par == 104 && narg <= 1)
2019 return; /* color reset without parameter */
2020 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
2021 j, p ? p : "(null)");
2022 } else {
2023 /*
2024 * TODO if defaultbg color is changed, borders
2025 * are dirty
2026 */
2027 tfulldirt();
2028 }
2029 return;
2030 }
2031 break;
2032 case 'k': /* old title set compatibility */
2033 xsettitle(strescseq.args[0]);
2034 return;
2035 case 'P': /* DCS -- Device Control String */
2036 case '_': /* APC -- Application Program Command */
2037 case '^': /* PM -- Privacy Message */
2038 return;
2039 }
2040
2041 fprintf(stderr, "erresc: unknown str ");
2042 strdump();
2043}
2044
2045void
2046strparse(void)
2047{
2048 int c;
2049 char *p = strescseq.buf;
2050
2051 strescseq.narg = 0;
2052 strescseq.buf[strescseq.len] = '\0';
2053
2054 if (*p == '\0')
2055 return;
2056
2057 while (strescseq.narg < STR_ARG_SIZ) {
2058 strescseq.args[strescseq.narg++] = p;
2059 while ((c = *p) != ';' && c != '\0')
2060 ++p;
2061 if (c == '\0')
2062 return;
2063 *p++ = '\0';
2064 }
2065}
2066
2067void
2068externalpipe(const Arg *arg)
2069{
2070 int to[2];
2071 char buf[UTF_SIZ];
2072 void (*oldsigpipe)(int);
2073 Glyph *bp, *end;
2074 int lastpos, n, newline;
2075
2076 if (pipe(to) == -1)
2077 return;
2078
2079 switch (fork()) {
2080 case -1:
2081 close(to[0]);
2082 close(to[1]);
2083 return;
2084 case 0:
2085 dup2(to[0], STDIN_FILENO);
2086 close(to[0]);
2087 close(to[1]);
2088 execvp(((char **)arg->v)[0], (char **)arg->v);
2089 fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]);
2090 perror("failed");
2091 exit(0);
2092 }
2093
2094 close(to[0]);
2095 /* ignore sigpipe for now, in case child exists early */
2096 oldsigpipe = signal(SIGPIPE, SIG_IGN);
2097 newline = 0;
2098 for (n = 0; n <= HISTSIZE + 2; n++) {
2099 bp = TLINE_HIST(n);
2100 lastpos = MIN(tlinehistlen(n) + 1, term.col) - 1;
2101 if (lastpos < 0)
2102 break;
2103 if (lastpos == 0)
2104 continue;
2105 end = &bp[lastpos + 1];
2106 for (; bp < end; ++bp)
2107 if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
2108 break;
2109 if ((newline = TLINE_HIST(n)[lastpos].mode & ATTR_WRAP))
2110 continue;
2111 if (xwrite(to[1], "\n", 1) < 0)
2112 break;
2113 newline = 0;
2114 }
2115 if (newline)
2116 (void)xwrite(to[1], "\n", 1);
2117 close(to[1]);
2118 /* restore */
2119 signal(SIGPIPE, oldsigpipe);
2120}
2121
2122void
2123strdump(void)
2124{
2125 size_t i;
2126 uint c;
2127
2128 fprintf(stderr, "ESC%c", strescseq.type);
2129 for (i = 0; i < strescseq.len; i++) {
2130 c = strescseq.buf[i] & 0xff;
2131 if (c == '\0') {
2132 putc('\n', stderr);
2133 return;
2134 } else if (isprint(c)) {
2135 putc(c, stderr);
2136 } else if (c == '\n') {
2137 fprintf(stderr, "(\\n)");
2138 } else if (c == '\r') {
2139 fprintf(stderr, "(\\r)");
2140 } else if (c == 0x1b) {
2141 fprintf(stderr, "(\\e)");
2142 } else {
2143 fprintf(stderr, "(%02x)", c);
2144 }
2145 }
2146 fprintf(stderr, "ESC\\\n");
2147}
2148
2149void
2150strreset(void)
2151{
2152 strescseq = (STREscape){
2153 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
2154 .siz = STR_BUF_SIZ,
2155 };
2156}
2157
2158void
2159sendbreak(const Arg *arg)
2160{
2161 if (tcsendbreak(cmdfd, 0))
2162 perror("Error sending break");
2163}
2164
2165void
2166tprinter(char *s, size_t len)
2167{
2168 if (iofd != -1 && xwrite(iofd, s, len) < 0) {
2169 perror("Error writing to output file");
2170 close(iofd);
2171 iofd = -1;
2172 }
2173}
2174
2175void
2176toggleprinter(const Arg *arg)
2177{
2178 term.mode ^= MODE_PRINT;
2179}
2180
2181void
2182printscreen(const Arg *arg)
2183{
2184 tdump();
2185}
2186
2187void
2188printsel(const Arg *arg)
2189{
2190 tdumpsel();
2191}
2192
2193void
2194tdumpsel(void)
2195{
2196 char *ptr;
2197
2198 if ((ptr = getsel())) {
2199 tprinter(ptr, strlen(ptr));
2200 free(ptr);
2201 }
2202}
2203
2204void
2205tdumpline(int n)
2206{
2207 char buf[UTF_SIZ];
2208 const Glyph *bp, *end;
2209
2210 bp = &term.line[n][0];
2211 end = &bp[MIN(tlinelen(n), term.col) - 1];
2212 if (bp != end || bp->u != ' ') {
2213 for ( ; bp <= end; ++bp)
2214 tprinter(buf, utf8encode(bp->u, buf));
2215 }
2216 tprinter("\n", 1);
2217}
2218
2219void
2220tdump(void)
2221{
2222 int i;
2223
2224 for (i = 0; i < term.row; ++i)
2225 tdumpline(i);
2226}
2227
2228void
2229tputtab(int n)
2230{
2231 uint x = term.c.x;
2232
2233 if (n > 0) {
2234 while (x < term.col && n--)
2235 for (++x; x < term.col && !term.tabs[x]; ++x)
2236 /* nothing */ ;
2237 } else if (n < 0) {
2238 while (x > 0 && n++)
2239 for (--x; x > 0 && !term.tabs[x]; --x)
2240 /* nothing */ ;
2241 }
2242 term.c.x = LIMIT(x, 0, term.col-1);
2243}
2244
2245void
2246tdefutf8(char ascii)
2247{
2248 if (ascii == 'G')
2249 term.mode |= MODE_UTF8;
2250 else if (ascii == '@')
2251 term.mode &= ~MODE_UTF8;
2252}
2253
2254void
2255tdeftran(char ascii)
2256{
2257 static char cs[] = "0B";
2258 static int vcs[] = {CS_GRAPHIC0, CS_USA};
2259 char *p;
2260
2261 if ((p = strchr(cs, ascii)) == NULL) {
2262 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2263 } else {
2264 term.trantbl[term.icharset] = vcs[p - cs];
2265 }
2266}
2267
2268void
2269tdectest(char c)
2270{
2271 int x, y;
2272
2273 if (c == '8') { /* DEC screen alignment test. */
2274 for (x = 0; x < term.col; ++x) {
2275 for (y = 0; y < term.row; ++y)
2276 tsetchar('E', &term.c.attr, x, y);
2277 }
2278 }
2279}
2280
2281void
2282tstrsequence(uchar c)
2283{
2284 switch (c) {
2285 case 0x90: /* DCS -- Device Control String */
2286 c = 'P';
2287 break;
2288 case 0x9f: /* APC -- Application Program Command */
2289 c = '_';
2290 break;
2291 case 0x9e: /* PM -- Privacy Message */
2292 c = '^';
2293 break;
2294 case 0x9d: /* OSC -- Operating System Command */
2295 c = ']';
2296 break;
2297 }
2298 strreset();
2299 strescseq.type = c;
2300 term.esc |= ESC_STR;
2301}
2302
2303void
2304tcontrolcode(uchar ascii)
2305{
2306 switch (ascii) {
2307 case '\t': /* HT */
2308 tputtab(1);
2309 return;
2310 case '\b': /* BS */
2311 tmoveto(term.c.x-1, term.c.y);
2312 return;
2313 case '\r': /* CR */
2314 tmoveto(0, term.c.y);
2315 return;
2316 case '\f': /* LF */
2317 case '\v': /* VT */
2318 case '\n': /* LF */
2319 /* go to first col if the mode is set */
2320 tnewline(IS_SET(MODE_CRLF));
2321 return;
2322 case '\a': /* BEL */
2323 if (term.esc & ESC_STR_END) {
2324 /* backwards compatibility to xterm */
2325 strhandle();
2326 } else {
2327 xbell();
2328 }
2329 break;
2330 case '\033': /* ESC */
2331 csireset();
2332 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2333 term.esc |= ESC_START;
2334 return;
2335 case '\016': /* SO (LS1 -- Locking shift 1) */
2336 case '\017': /* SI (LS0 -- Locking shift 0) */
2337 term.charset = 1 - (ascii - '\016');
2338 return;
2339 case '\032': /* SUB */
2340 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2341 /* FALLTHROUGH */
2342 case '\030': /* CAN */
2343 csireset();
2344 break;
2345 case '\005': /* ENQ (IGNORED) */
2346 case '\000': /* NUL (IGNORED) */
2347 case '\021': /* XON (IGNORED) */
2348 case '\023': /* XOFF (IGNORED) */
2349 case 0177: /* DEL (IGNORED) */
2350 return;
2351 case 0x80: /* TODO: PAD */
2352 case 0x81: /* TODO: HOP */
2353 case 0x82: /* TODO: BPH */
2354 case 0x83: /* TODO: NBH */
2355 case 0x84: /* TODO: IND */
2356 break;
2357 case 0x85: /* NEL -- Next line */
2358 tnewline(1); /* always go to first col */
2359 break;
2360 case 0x86: /* TODO: SSA */
2361 case 0x87: /* TODO: ESA */
2362 break;
2363 case 0x88: /* HTS -- Horizontal tab stop */
2364 term.tabs[term.c.x] = 1;
2365 break;
2366 case 0x89: /* TODO: HTJ */
2367 case 0x8a: /* TODO: VTS */
2368 case 0x8b: /* TODO: PLD */
2369 case 0x8c: /* TODO: PLU */
2370 case 0x8d: /* TODO: RI */
2371 case 0x8e: /* TODO: SS2 */
2372 case 0x8f: /* TODO: SS3 */
2373 case 0x91: /* TODO: PU1 */
2374 case 0x92: /* TODO: PU2 */
2375 case 0x93: /* TODO: STS */
2376 case 0x94: /* TODO: CCH */
2377 case 0x95: /* TODO: MW */
2378 case 0x96: /* TODO: SPA */
2379 case 0x97: /* TODO: EPA */
2380 case 0x98: /* TODO: SOS */
2381 case 0x99: /* TODO: SGCI */
2382 break;
2383 case 0x9a: /* DECID -- Identify Terminal */
2384 ttywrite(vtiden, strlen(vtiden), 0);
2385 break;
2386 case 0x9b: /* TODO: CSI */
2387 case 0x9c: /* TODO: ST */
2388 break;
2389 case 0x90: /* DCS -- Device Control String */
2390 case 0x9d: /* OSC -- Operating System Command */
2391 case 0x9e: /* PM -- Privacy Message */
2392 case 0x9f: /* APC -- Application Program Command */
2393 tstrsequence(ascii);
2394 return;
2395 }
2396 /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2397 term.esc &= ~(ESC_STR_END|ESC_STR);
2398}
2399
2400/*
2401 * returns 1 when the sequence is finished and it hasn't to read
2402 * more characters for this sequence, otherwise 0
2403 */
2404int
2405eschandle(uchar ascii)
2406{
2407 switch (ascii) {
2408 case '[':
2409 term.esc |= ESC_CSI;
2410 return 0;
2411 case '#':
2412 term.esc |= ESC_TEST;
2413 return 0;
2414 case '%':
2415 term.esc |= ESC_UTF8;
2416 return 0;
2417 case 'P': /* DCS -- Device Control String */
2418 case '_': /* APC -- Application Program Command */
2419 case '^': /* PM -- Privacy Message */
2420 case ']': /* OSC -- Operating System Command */
2421 case 'k': /* old title set compatibility */
2422 tstrsequence(ascii);
2423 return 0;
2424 case 'n': /* LS2 -- Locking shift 2 */
2425 case 'o': /* LS3 -- Locking shift 3 */
2426 term.charset = 2 + (ascii - 'n');
2427 break;
2428 case '(': /* GZD4 -- set primary charset G0 */
2429 case ')': /* G1D4 -- set secondary charset G1 */
2430 case '*': /* G2D4 -- set tertiary charset G2 */
2431 case '+': /* G3D4 -- set quaternary charset G3 */
2432 term.icharset = ascii - '(';
2433 term.esc |= ESC_ALTCHARSET;
2434 return 0;
2435 case 'D': /* IND -- Linefeed */
2436 if (term.c.y == term.bot) {
2437 tscrollup(term.top, 1, 1);
2438 } else {
2439 tmoveto(term.c.x, term.c.y+1);
2440 }
2441 break;
2442 case 'E': /* NEL -- Next line */
2443 tnewline(1); /* always go to first col */
2444 break;
2445 case 'H': /* HTS -- Horizontal tab stop */
2446 term.tabs[term.c.x] = 1;
2447 break;
2448 case 'M': /* RI -- Reverse index */
2449 if (term.c.y == term.top) {
2450 tscrolldown(term.top, 1, 1);
2451 } else {
2452 tmoveto(term.c.x, term.c.y-1);
2453 }
2454 break;
2455 case 'Z': /* DECID -- Identify Terminal */
2456 ttywrite(vtiden, strlen(vtiden), 0);
2457 break;
2458 case 'c': /* RIS -- Reset to initial state */
2459 treset();
2460 resettitle();
2461 xloadcols();
2462 break;
2463 case '=': /* DECPAM -- Application keypad */
2464 xsetmode(1, MODE_APPKEYPAD);
2465 break;
2466 case '>': /* DECPNM -- Normal keypad */
2467 xsetmode(0, MODE_APPKEYPAD);
2468 break;
2469 case '7': /* DECSC -- Save Cursor */
2470 tcursor(CURSOR_SAVE);
2471 break;
2472 case '8': /* DECRC -- Restore Cursor */
2473 tcursor(CURSOR_LOAD);
2474 break;
2475 case '\\': /* ST -- String Terminator */
2476 if (term.esc & ESC_STR_END)
2477 strhandle();
2478 break;
2479 default:
2480 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2481 (uchar) ascii, isprint(ascii)? ascii:'.');
2482 break;
2483 }
2484 return 1;
2485}
2486
2487void
2488tputc(Rune u)
2489{
2490 char c[UTF_SIZ];
2491 int control;
2492 int width, len;
2493 Glyph *gp;
2494
2495 control = ISCONTROL(u);
2496 if (u < 127 || !IS_SET(MODE_UTF8)) {
2497 c[0] = u;
2498 width = len = 1;
2499 } else {
2500 len = utf8encode(u, c);
2501 if (!control && (width = wcwidth(u)) == -1)
2502 width = 1;
2503 }
2504
2505 if (IS_SET(MODE_PRINT))
2506 tprinter(c, len);
2507
2508 /*
2509 * STR sequence must be checked before anything else
2510 * because it uses all following characters until it
2511 * receives a ESC, a SUB, a ST or any other C1 control
2512 * character.
2513 */
2514 if (term.esc & ESC_STR) {
2515 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2516 ISCONTROLC1(u)) {
2517 term.esc &= ~(ESC_START|ESC_STR);
2518 term.esc |= ESC_STR_END;
2519 goto check_control_code;
2520 }
2521
2522 if (strescseq.len+len >= strescseq.siz) {
2523 /*
2524 * Here is a bug in terminals. If the user never sends
2525 * some code to stop the str or esc command, then st
2526 * will stop responding. But this is better than
2527 * silently failing with unknown characters. At least
2528 * then users will report back.
2529 *
2530 * In the case users ever get fixed, here is the code:
2531 */
2532 /*
2533 * term.esc = 0;
2534 * strhandle();
2535 */
2536 if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2537 return;
2538 strescseq.siz *= 2;
2539 strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2540 }
2541
2542 memmove(&strescseq.buf[strescseq.len], c, len);
2543 strescseq.len += len;
2544 return;
2545 }
2546
2547check_control_code:
2548 /*
2549 * Actions of control codes must be performed as soon they arrive
2550 * because they can be embedded inside a control sequence, and
2551 * they must not cause conflicts with sequences.
2552 */
2553 if (control) {
2554 tcontrolcode(u);
2555 /*
2556 * control codes are not shown ever
2557 */
2558 if (!term.esc)
2559 term.lastc = 0;
2560 return;
2561 } else if (term.esc & ESC_START) {
2562 if (term.esc & ESC_CSI) {
2563 csiescseq.buf[csiescseq.len++] = u;
2564 if (BETWEEN(u, 0x40, 0x7E)
2565 || csiescseq.len >= \
2566 sizeof(csiescseq.buf)-1) {
2567 term.esc = 0;
2568 csiparse();
2569 csihandle();
2570 }
2571 return;
2572 } else if (term.esc & ESC_UTF8) {
2573 tdefutf8(u);
2574 } else if (term.esc & ESC_ALTCHARSET) {
2575 tdeftran(u);
2576 } else if (term.esc & ESC_TEST) {
2577 tdectest(u);
2578 } else {
2579 if (!eschandle(u))
2580 return;
2581 /* sequence already finished */
2582 }
2583 term.esc = 0;
2584 /*
2585 * All characters which form part of a sequence are not
2586 * printed
2587 */
2588 return;
2589 }
2590 if (selected(term.c.x, term.c.y))
2591 selclear();
2592
2593 gp = &term.line[term.c.y][term.c.x];
2594 if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2595 gp->mode |= ATTR_WRAP;
2596 tnewline(1);
2597 gp = &term.line[term.c.y][term.c.x];
2598 }
2599
2600 if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2601 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2602
2603 if (term.c.x+width > term.col) {
2604 tnewline(1);
2605 gp = &term.line[term.c.y][term.c.x];
2606 }
2607
2608 tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2609 term.lastc = u;
2610
2611 if (width == 2) {
2612 gp->mode |= ATTR_WIDE;
2613 if (term.c.x+1 < term.col) {
2614 if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
2615 gp[2].u = ' ';
2616 gp[2].mode &= ~ATTR_WDUMMY;
2617 }
2618 gp[1].u = '\0';
2619 gp[1].mode = ATTR_WDUMMY;
2620 }
2621 }
2622 if (term.c.x+width < term.col) {
2623 tmoveto(term.c.x+width, term.c.y);
2624 } else {
2625 term.c.state |= CURSOR_WRAPNEXT;
2626 }
2627}
2628
2629int
2630twrite(const char *buf, int buflen, int show_ctrl)
2631{
2632 int charsize;
2633 Rune u;
2634 int n;
2635
2636 for (n = 0; n < buflen; n += charsize) {
2637 if (IS_SET(MODE_UTF8)) {
2638 /* process a complete utf8 char */
2639 charsize = utf8decode(buf + n, &u, buflen - n);
2640 if (charsize == 0)
2641 break;
2642 } else {
2643 u = buf[n] & 0xFF;
2644 charsize = 1;
2645 }
2646 if (show_ctrl && ISCONTROL(u)) {
2647 if (u & 0x80) {
2648 u &= 0x7f;
2649 tputc('^');
2650 tputc('[');
2651 } else if (u != '\n' && u != '\r' && u != '\t') {
2652 u ^= 0x40;
2653 tputc('^');
2654 }
2655 }
2656 tputc(u);
2657 }
2658 return n;
2659}
2660
2661void
2662tresize(int col, int row)
2663{
2664 int i, j;
2665 int tmp;
2666 int minrow, mincol;
2667 int *bp;
2668 TCursor c;
2669
2670 tmp = col;
2671 if (!term.maxcol)
2672 term.maxcol = term.col;
2673 col = MAX(col, term.maxcol);
2674 minrow = MIN(row, term.row);
2675 mincol = MIN(col, term.maxcol);
2676
2677 if (col < 1 || row < 1) {
2678 fprintf(stderr,
2679 "tresize: error resizing to %dx%d\n", col, row);
2680 return;
2681 }
2682
2683 /*
2684 * slide screen to keep cursor where we expect it -
2685 * tscrollup would work here, but we can optimize to
2686 * memmove because we're freeing the earlier lines
2687 */
2688 for (i = 0; i <= term.c.y - row; i++) {
2689 free(term.line[i]);
2690 free(term.alt[i]);
2691 }
2692 /* ensure that both src and dst are not NULL */
2693 if (i > 0) {
2694 memmove(term.line, term.line + i, row * sizeof(Line));
2695 memmove(term.alt, term.alt + i, row * sizeof(Line));
2696 }
2697 for (i += row; i < term.row; i++) {
2698 free(term.line[i]);
2699 free(term.alt[i]);
2700 }
2701
2702 /* resize to new height */
2703 term.line = xrealloc(term.line, row * sizeof(Line));
2704 term.alt = xrealloc(term.alt, row * sizeof(Line));
2705 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2706 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2707
2708 for (i = 0; i < HISTSIZE; i++) {
2709 term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
2710 for (j = mincol; j < col; j++) {
2711 term.hist[i][j] = term.c.attr;
2712 term.hist[i][j].u = ' ';
2713 }
2714 }
2715
2716 /* resize each row to new width, zero-pad if needed */
2717 for (i = 0; i < minrow; i++) {
2718 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2719 term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
2720 }
2721
2722 /* allocate any new rows */
2723 for (/* i = minrow */; i < row; i++) {
2724 term.line[i] = xmalloc(col * sizeof(Glyph));
2725 term.alt[i] = xmalloc(col * sizeof(Glyph));
2726 }
2727 if (col > term.maxcol) {
2728 bp = term.tabs + term.maxcol;
2729
2730 memset(bp, 0, sizeof(*term.tabs) * (col - term.maxcol));
2731 while (--bp > term.tabs && !*bp)
2732 /* nothing */ ;
2733 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2734 *bp = 1;
2735 }
2736 /* update terminal size */
2737 term.col = tmp;
2738 term.maxcol = col;
2739 term.row = row;
2740 /* reset scrolling region */
2741 tsetscroll(0, row-1);
2742 /* make use of the LIMIT in tmoveto */
2743 tmoveto(term.c.x, term.c.y);
2744 /* Clearing both screens (it makes dirty all lines) */
2745 c = term.c;
2746 for (i = 0; i < 2; i++) {
2747 if (mincol < col && 0 < minrow) {
2748 tclearregion(mincol, 0, col - 1, minrow - 1);
2749 }
2750 if (0 < col && minrow < row) {
2751 tclearregion(0, minrow, col - 1, row - 1);
2752 }
2753 tswapscreen();
2754 tcursor(CURSOR_LOAD);
2755 }
2756 term.c = c;
2757}
2758
2759void
2760resettitle(void)
2761{
2762 xsettitle(NULL);
2763}
2764
2765void
2766drawregion(int x1, int y1, int x2, int y2)
2767{
2768 int y;
2769
2770 for (y = y1; y < y2; y++) {
2771 if (!term.dirty[y])
2772 continue;
2773
2774 term.dirty[y] = 0;
2775 xdrawline(TLINE(y), x1, y, x2);
2776 }
2777}
2778
2779void
2780draw(void)
2781{
2782 int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
2783
2784 if (!xstartdraw())
2785 return;
2786
2787 /* adjust cursor position */
2788 LIMIT(term.ocx, 0, term.col-1);
2789 LIMIT(term.ocy, 0, term.row-1);
2790 if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2791 term.ocx--;
2792 if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2793 cx--;
2794
2795 drawregion(0, 0, term.col, term.row);
2796 if (term.scr == 0)
2797 xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2798 term.ocx, term.ocy, term.line[term.ocy][term.ocx],
2799 term.line[term.ocy], term.col);
2800 /* xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], */
2801 /* term.ocx, term.ocy, term.line[term.ocy][term.ocx], */
2802 /* term.line[term.ocy], term.col); */
2803 term.ocx = cx;
2804 term.ocy = term.c.y;
2805 xfinishdraw();
2806 if (ocx != term.ocx || ocy != term.ocy)
2807 xximspot(term.ocx, term.ocy);
2808}
2809
2810void
2811redraw(void)
2812{
2813 tfulldirt();
2814 draw();
2815}
2816
diff --git a/st.h b/st.h
new file mode 100644
index 0000000..60d973a
--- /dev/null
+++ b/st.h
@@ -0,0 +1,146 @@
1/* See LICENSE for license details. */
2
3#include <stdint.h>
4#include <sys/types.h>
5
6/* macros */
7#define MIN(a, b) ((a) < (b) ? (a) : (b))
8#define MAX(a, b) ((a) < (b) ? (b) : (a))
9#define LEN(a) (sizeof(a) / sizeof(a)[0])
10#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
11#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
12#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
13#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
14#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \
15 (a).fg != (b).fg || \
16 (a).bg != (b).bg)
17#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
18 (t1.tv_nsec-t2.tv_nsec)/1E6)
19#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
20
21#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
22#define IS_TRUECOL(x) (1 << 24 & (x))
23
24enum glyph_attribute {
25 ATTR_NULL = 0,
26 ATTR_BOLD = 1 << 0,
27 ATTR_FAINT = 1 << 1,
28 ATTR_ITALIC = 1 << 2,
29 ATTR_UNDERLINE = 1 << 3,
30 ATTR_BLINK = 1 << 4,
31 ATTR_REVERSE = 1 << 5,
32 ATTR_INVISIBLE = 1 << 6,
33 ATTR_STRUCK = 1 << 7,
34 ATTR_WRAP = 1 << 8,
35 ATTR_WIDE = 1 << 9,
36 ATTR_WDUMMY = 1 << 10,
37 ATTR_BOXDRAW = 1 << 11,
38 ATTR_LIGA = 1 << 12,
39 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
40};
41
42enum selection_mode {
43 SEL_IDLE = 0,
44 SEL_EMPTY = 1,
45 SEL_READY = 2
46};
47
48enum selection_type {
49 SEL_REGULAR = 1,
50 SEL_RECTANGULAR = 2
51};
52
53enum selection_snap {
54 SNAP_WORD = 1,
55 SNAP_LINE = 2
56};
57
58typedef unsigned char uchar;
59typedef unsigned int uint;
60typedef unsigned long ulong;
61typedef unsigned short ushort;
62
63typedef uint_least32_t Rune;
64
65#define Glyph Glyph_
66typedef struct {
67 Rune u; /* character code */
68 ushort mode; /* attribute flags */
69 uint32_t fg; /* foreground */
70 uint32_t bg; /* background */
71} Glyph;
72
73typedef Glyph *Line;
74
75typedef union {
76 int i;
77 uint ui;
78 float f;
79 const void *v;
80 const char *s;
81} Arg;
82
83void die(const char *, ...);
84void redraw(void);
85void tfulldirt(void);
86void draw(void);
87
88void externalpipe(const Arg *);
89void kscrolldown(const Arg *);
90void kscrollup(const Arg *);
91
92void printscreen(const Arg *);
93void printsel(const Arg *);
94void sendbreak(const Arg *);
95void toggleprinter(const Arg *);
96
97int tattrset(int);
98void tnew(int, int);
99void tresize(int, int);
100void tsetdirtattr(int);
101void ttyhangup(void);
102int ttynew(const char *, char *, const char *, char **);
103size_t ttyread(void);
104void ttyresize(int, int);
105void ttywrite(const char *, size_t, int);
106
107void resettitle(void);
108
109void selclear(void);
110void selinit(void);
111void selstart(int, int, int);
112void selextend(int, int, int, int);
113int selected(int, int);
114char *getsel(void);
115
116size_t utf8encode(Rune, char *);
117
118void *xmalloc(size_t);
119void *xrealloc(void *, size_t);
120char *xstrdup(const char *);
121
122int isboxdraw(Rune);
123ushort boxdrawindex(const Glyph *);
124#ifdef XFT_VERSION
125/* only exposed to x.c, otherwise we'll need Xft.h for the types */
126void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *);
127void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int);
128#endif
129
130/* config.h globals */
131extern char *utmp;
132extern char *scroll;
133extern char *stty_args;
134extern char *vtiden;
135extern wchar_t *worddelimiters;
136extern int allowaltscreen;
137extern int allowwindowops;
138extern char *termname;
139extern unsigned int tabspaces;
140extern unsigned int defaultfg;
141extern unsigned int defaultbg;
142extern float alpha;
143extern float alphaUnfocus;
144extern const int boxdraw, boxdraw_bold, boxdraw_braille;
145extern unsigned int defaultcs;
146
diff --git a/st.info b/st.info
new file mode 100644
index 0000000..8201ad6
--- /dev/null
+++ b/st.info
@@ -0,0 +1,239 @@
1st-mono| simpleterm monocolor,
2 acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
3 am,
4 bce,
5 bel=^G,
6 blink=\E[5m,
7 bold=\E[1m,
8 cbt=\E[Z,
9 cvvis=\E[?25h,
10 civis=\E[?25l,
11 clear=\E[H\E[2J,
12 cnorm=\E[?12l\E[?25h,
13 colors#2,
14 cols#80,
15 cr=^M,
16 csr=\E[%i%p1%d;%p2%dr,
17 cub=\E[%p1%dD,
18 cub1=^H,
19 cud1=^J,
20 cud=\E[%p1%dB,
21 cuf1=\E[C,
22 cuf=\E[%p1%dC,
23 cup=\E[%i%p1%d;%p2%dH,
24 cuu1=\E[A,
25 cuu=\E[%p1%dA,
26 dch=\E[%p1%dP,
27 dch1=\E[P,
28 dim=\E[2m,
29 dl=\E[%p1%dM,
30 dl1=\E[M,
31 ech=\E[%p1%dX,
32 ed=\E[J,
33 el=\E[K,
34 el1=\E[1K,
35 enacs=\E)0,
36 flash=\E[?5h$<80/>\E[?5l,
37 fsl=^G,
38 home=\E[H,
39 hpa=\E[%i%p1%dG,
40 hs,
41 ht=^I,
42 hts=\EH,
43 ich=\E[%p1%d@,
44 il1=\E[L,
45 il=\E[%p1%dL,
46 ind=^J,
47 indn=\E[%p1%dS,
48 invis=\E[8m,
49 is2=\E[4l\E>\E[?1034l,
50 it#8,
51 kel=\E[1;2F,
52 ked=\E[1;5F,
53 ka1=\E[1~,
54 ka3=\E[5~,
55 kc1=\E[4~,
56 kc3=\E[6~,
57 kbs=\177,
58 kcbt=\E[Z,
59 kb2=\EOu,
60 kcub1=\EOD,
61 kcud1=\EOB,
62 kcuf1=\EOC,
63 kcuu1=\EOA,
64 kDC=\E[3;2~,
65 kent=\EOM,
66 kEND=\E[1;2F,
67 kIC=\E[2;2~,
68 kNXT=\E[6;2~,
69 kPRV=\E[5;2~,
70 kHOM=\E[1;2H,
71 kLFT=\E[1;2D,
72 kRIT=\E[1;2C,
73 kind=\E[1;2B,
74 kri=\E[1;2A,
75 kclr=\E[3;5~,
76 kdl1=\E[3;2~,
77 kdch1=\E[3~,
78 kich1=\E[2~,
79 kend=\E[4~,
80 kf1=\EOP,
81 kf2=\EOQ,
82 kf3=\EOR,
83 kf4=\EOS,
84 kf5=\E[15~,
85 kf6=\E[17~,
86 kf7=\E[18~,
87 kf8=\E[19~,
88 kf9=\E[20~,
89 kf10=\E[21~,
90 kf11=\E[23~,
91 kf12=\E[24~,
92 kf13=\E[1;2P,
93 kf14=\E[1;2Q,
94 kf15=\E[1;2R,
95 kf16=\E[1;2S,
96 kf17=\E[15;2~,
97 kf18=\E[17;2~,
98 kf19=\E[18;2~,
99 kf20=\E[19;2~,
100 kf21=\E[20;2~,
101 kf22=\E[21;2~,
102 kf23=\E[23;2~,
103 kf24=\E[24;2~,
104 kf25=\E[1;5P,
105 kf26=\E[1;5Q,
106 kf27=\E[1;5R,
107 kf28=\E[1;5S,
108 kf29=\E[15;5~,
109 kf30=\E[17;5~,
110 kf31=\E[18;5~,
111 kf32=\E[19;5~,
112 kf33=\E[20;5~,
113 kf34=\E[21;5~,
114 kf35=\E[23;5~,
115 kf36=\E[24;5~,
116 kf37=\E[1;6P,
117 kf38=\E[1;6Q,
118 kf39=\E[1;6R,
119 kf40=\E[1;6S,
120 kf41=\E[15;6~,
121 kf42=\E[17;6~,
122 kf43=\E[18;6~,
123 kf44=\E[19;6~,
124 kf45=\E[20;6~,
125 kf46=\E[21;6~,
126 kf47=\E[23;6~,
127 kf48=\E[24;6~,
128 kf49=\E[1;3P,
129 kf50=\E[1;3Q,
130 kf51=\E[1;3R,
131 kf52=\E[1;3S,
132 kf53=\E[15;3~,
133 kf54=\E[17;3~,
134 kf55=\E[18;3~,
135 kf56=\E[19;3~,
136 kf57=\E[20;3~,
137 kf58=\E[21;3~,
138 kf59=\E[23;3~,
139 kf60=\E[24;3~,
140 kf61=\E[1;4P,
141 kf62=\E[1;4Q,
142 kf63=\E[1;4R,
143 khome=\E[1~,
144 kil1=\E[2;5~,
145 krmir=\E[2;2~,
146 knp=\E[6~,
147 kmous=\E[M,
148 kpp=\E[5~,
149 lines#24,
150 mir,
151 msgr,
152 npc,
153 op=\E[39;49m,
154 pairs#64,
155 mc0=\E[i,
156 mc4=\E[4i,
157 mc5=\E[5i,
158 rc=\E8,
159 rev=\E[7m,
160 ri=\EM,
161 rin=\E[%p1%dT,
162 ritm=\E[23m,
163 rmacs=\E(B,
164 rmcup=\E[?1049l,
165 rmir=\E[4l,
166 rmkx=\E[?1l\E>,
167 rmso=\E[27m,
168 rmul=\E[24m,
169 rs1=\Ec,
170 rs2=\E[4l\E>\E[?1034l,
171 sc=\E7,
172 sitm=\E[3m,
173 sgr0=\E[0m,
174 smacs=\E(0,
175 smcup=\E[?1049h,
176 smir=\E[4h,
177 smkx=\E[?1h\E=,
178 smso=\E[7m,
179 smul=\E[4m,
180 tbc=\E[3g,
181 tsl=\E]0;,
182 xenl,
183 vpa=\E[%i%p1%dd,
184# XTerm extensions
185 rmxx=\E[29m,
186 smxx=\E[9m,
187# disabled rep for now: causes some issues with older ncurses versions.
188# rep=%p1%c\E[%p2%{1}%-%db,
189# tmux extensions, see TERMINFO EXTENSIONS in tmux(1)
190 Tc,
191 Ms=\E]52;%p1%s;%p2%s\007,
192 Se=\E[2 q,
193 Ss=\E[%p1%d q,
194
195st| simpleterm,
196 use=st-mono,
197 colors#8,
198 setab=\E[4%p1%dm,
199 setaf=\E[3%p1%dm,
200 setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
201 setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m,
202 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,
203
204st-256color| simpleterm with 256 colors,
205 use=st,
206 ccc,
207 colors#256,
208 oc=\E]104\007,
209 pairs#32767,
210# Nicked from xterm-256color
211 initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\,
212 setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
213 setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
214
215st-meta| simpleterm with meta key,
216 use=st,
217 km,
218 rmm=\E[?1034l,
219 smm=\E[?1034h,
220 rs2=\E[4l\E>\E[?1034h,
221 is2=\E[4l\E>\E[?1034h,
222
223st-meta-256color| simpleterm with meta key and 256 colors,
224 use=st-256color,
225 km,
226 rmm=\E[?1034l,
227 smm=\E[?1034h,
228 rs2=\E[4l\E>\E[?1034h,
229 is2=\E[4l\E>\E[?1034h,
230
231st-bs| simpleterm with backspace as backspace,
232 use=st,
233 kbs=\010,
234 kdch1=\177,
235
236st-bs-256color| simpleterm with backspace as backspace and 256colors,
237 use=st-256color,
238 kbs=\010,
239 kdch1=\177,
diff --git a/win.h b/win.h
new file mode 100644
index 0000000..8839e31
--- /dev/null
+++ b/win.h
@@ -0,0 +1,42 @@
1/* See LICENSE for license details. */
2
3enum win_mode {
4 MODE_VISIBLE = 1 << 0,
5 MODE_FOCUSED = 1 << 1,
6 MODE_APPKEYPAD = 1 << 2,
7 MODE_MOUSEBTN = 1 << 3,
8 MODE_MOUSEMOTION = 1 << 4,
9 MODE_REVERSE = 1 << 5,
10 MODE_KBDLOCK = 1 << 6,
11 MODE_HIDE = 1 << 7,
12 MODE_APPCURSOR = 1 << 8,
13 MODE_MOUSESGR = 1 << 9,
14 MODE_8BIT = 1 << 10,
15 MODE_BLINK = 1 << 11,
16 MODE_FBLINK = 1 << 12,
17 MODE_FOCUS = 1 << 13,
18 MODE_MOUSEX10 = 1 << 14,
19 MODE_MOUSEMANY = 1 << 15,
20 MODE_BRCKTPASTE = 1 << 16,
21 MODE_NUMLOCK = 1 << 17,
22 MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\
23 |MODE_MOUSEMANY,
24};
25
26void xbell(void);
27void xclipcopy(void);
28void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
29void xdrawline(Line, int, int, int);
30void xfinishdraw(void);
31void xloadcols(void);
32int xsetcolorname(int, const char *);
33int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *);
34void xseticontitle(char *);
35void xsettitle(char *);
36int xsetcursor(int);
37void xsetmode(int, unsigned int);
38void xsetpointermotion(int);
39void xsetsel(char *);
40int xstartdraw(void);
41void xximspot(int, int);
42
diff --git a/x.c b/x.c
new file mode 100644
index 0000000..e2ec9db
--- /dev/null
+++ b/x.c
@@ -0,0 +1,2366 @@
1/* See LICENSE for license details. */
2#include <errno.h>
3#include <math.h>
4#include <limits.h>
5#include <locale.h>
6#include <signal.h>
7#include <sys/select.h>
8#include <time.h>
9#include <unistd.h>
10#include <libgen.h>
11#include <X11/Xatom.h>
12#include <X11/Xlib.h>
13#include <X11/cursorfont.h>
14#include <X11/keysym.h>
15#include <X11/Xft/Xft.h>
16#include <X11/XKBlib.h>
17#include <X11/Xresource.h>
18
19char *argv0;
20#include "arg.h"
21#include "st.h"
22#include "win.h"
23#include "hb.h"
24
25/* types used in config.h */
26typedef struct {
27 uint mod;
28 KeySym keysym;
29 void (*func)(const Arg *);
30 const Arg arg;
31} Shortcut;
32
33typedef struct {
34 uint mod;
35 uint button;
36 void (*func)(const Arg *);
37 const Arg arg;
38 uint release;
39} MouseShortcut;
40
41typedef struct {
42 KeySym k;
43 uint mask;
44 char *s;
45 /* three-valued logic variables: 0 indifferent, 1 on, -1 off */
46 signed char appkey; /* application keypad */
47 signed char appcursor; /* application cursor */
48} Key;
49
50/* Xresources preferences */
51enum resource_type {
52 STRING = 0,
53 INTEGER = 1,
54 FLOAT = 2
55};
56
57typedef struct {
58 char *name;
59 enum resource_type type;
60 void *dst;
61} ResourcePref;
62
63/* X modifiers */
64#define XK_ANY_MOD UINT_MAX
65#define XK_NO_MOD 0
66#define XK_SWITCH_MOD (1<<13|1<<14)
67
68/* function definitions used in config.h */
69static void clipcopy(const Arg *);
70static void clippaste(const Arg *);
71static void numlock(const Arg *);
72static void selpaste(const Arg *);
73static void changealpha(const Arg *);
74static void zoom(const Arg *);
75static void zoomabs(const Arg *);
76static void zoomreset(const Arg *);
77static void ttysend(const Arg *);
78
79/* config.h for applying patches and the configuration. */
80#include "config.h"
81
82/* XEMBED messages */
83#define XEMBED_FOCUS_IN 4
84#define XEMBED_FOCUS_OUT 5
85
86/* macros */
87#define IS_SET(flag) ((win.mode & (flag)) != 0)
88#define TRUERED(x) (((x) & 0xff0000) >> 8)
89#define TRUEGREEN(x) (((x) & 0xff00))
90#define TRUEBLUE(x) (((x) & 0xff) << 8)
91
92typedef XftDraw *Draw;
93typedef XftColor Color;
94typedef XftGlyphFontSpec GlyphFontSpec;
95
96/* Purely graphic info */
97typedef struct {
98 int tw, th; /* tty width and height */
99 int w, h; /* window width and height */
100 int ch; /* char height */
101 int cw; /* char width */
102 int mode; /* window state/mode flags */
103 int cursor; /* cursor style */
104} TermWindow;
105
106typedef struct {
107 Display *dpy;
108 Colormap cmap;
109 Window win;
110 Drawable buf;
111 GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
112 Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
113 struct {
114 XIM xim;
115 XIC xic;
116 XPoint spot;
117 XVaNestedList spotlist;
118 } ime;
119 Draw draw;
120 Visual *vis;
121 XSetWindowAttributes attrs;
122 int scr;
123 int isfixed; /* is fixed geometry? */
124 int depth; /* bit depth */
125 int l, t; /* left and top offset */
126 int gm; /* geometry mask */
127} XWindow;
128
129typedef struct {
130 Atom xtarget;
131 char *primary, *clipboard;
132 struct timespec tclick1;
133 struct timespec tclick2;
134} XSelection;
135
136/* Font structure */
137#define Font Font_
138typedef struct {
139 int height;
140 int width;
141 int ascent;
142 int descent;
143 int badslant;
144 int badweight;
145 short lbearing;
146 short rbearing;
147 XftFont *match;
148 FcFontSet *set;
149 FcPattern *pattern;
150} Font;
151
152/* Drawing Context */
153typedef struct {
154 Color *col;
155 size_t collen;
156 Font font, bfont, ifont, ibfont;
157 GC gc;
158} DC;
159
160static inline ushort sixd_to_16bit(int);
161static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
162static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
163static void xdrawglyph(Glyph, int, int);
164static void xclear(int, int, int, int);
165static int xgeommasktogravity(int);
166static int ximopen(Display *);
167static void ximinstantiate(Display *, XPointer, XPointer);
168static void ximdestroy(XIM, XPointer, XPointer);
169static int xicdestroy(XIC, XPointer, XPointer);
170static void xinit(int, int);
171static void cresize(int, int);
172static void xresize(int, int);
173static void xhints(void);
174static int xloadcolor(int, const char *, Color *);
175static int xloadfont(Font *, FcPattern *);
176static int xloadsparefont(FcPattern *, int);
177static void xloadsparefonts(void);
178static void xloadfonts(const char *, double);
179static void xunloadfont(Font *);
180static void xunloadfonts(void);
181static void xsetenv(void);
182static void xseturgency(int);
183static int evcol(XEvent *);
184static int evrow(XEvent *);
185static float clamp(float, float, float);
186
187static void expose(XEvent *);
188static void visibility(XEvent *);
189static void unmap(XEvent *);
190static void kpress(XEvent *);
191static void cmessage(XEvent *);
192static void resize(XEvent *);
193static void focus(XEvent *);
194static uint buttonmask(uint);
195static int mouseaction(XEvent *, uint);
196static void brelease(XEvent *);
197static void bpress(XEvent *);
198static void bmotion(XEvent *);
199static void propnotify(XEvent *);
200static void selnotify(XEvent *);
201static void selclear_(XEvent *);
202static void selrequest(XEvent *);
203static void setsel(char *, Time);
204static void mousesel(XEvent *, int);
205static void mousereport(XEvent *);
206static char *kmap(KeySym, uint);
207static int match(uint, uint);
208
209static void run(void);
210static void usage(void);
211
212static void (*handler[LASTEvent])(XEvent *) = {
213 [KeyPress] = kpress,
214 [ClientMessage] = cmessage,
215 [ConfigureNotify] = resize,
216 [VisibilityNotify] = visibility,
217 [UnmapNotify] = unmap,
218 [Expose] = expose,
219 [FocusIn] = focus,
220 [FocusOut] = focus,
221 [MotionNotify] = bmotion,
222 [ButtonPress] = bpress,
223 [ButtonRelease] = brelease,
224/*
225 * Uncomment if you want the selection to disappear when you select something
226 * different in another window.
227 */
228/* [SelectionClear] = selclear_, */
229 [SelectionNotify] = selnotify,
230/*
231 * PropertyNotify is only turned on when there is some INCR transfer happening
232 * for the selection retrieval.
233 */
234 [PropertyNotify] = propnotify,
235 [SelectionRequest] = selrequest,
236};
237
238/* Globals */
239static DC dc;
240static XWindow xw;
241static XSelection xsel;
242static TermWindow win;
243
244/* Font Ring Cache */
245enum {
246 FRC_NORMAL,
247 FRC_ITALIC,
248 FRC_BOLD,
249 FRC_ITALICBOLD
250};
251
252typedef struct {
253 XftFont *font;
254 int flags;
255 Rune unicodep;
256} Fontcache;
257
258/* Fontcache is an array now. A new font will be appended to the array. */
259static Fontcache *frc = NULL;
260static int frclen = 0;
261static int frccap = 0;
262static char *usedfont = NULL;
263static double usedfontsize = 0;
264static double defaultfontsize = 0;
265
266static char *opt_alpha = NULL;
267static char *opt_class = NULL;
268static char **opt_cmd = NULL;
269static char *opt_embed = NULL;
270static char *opt_font = NULL;
271static char *opt_io = NULL;
272static char *opt_line = NULL;
273static char *opt_name = NULL;
274static char *opt_title = NULL;
275
276static int focused = 0;
277
278static int oldbutton = 3; /* button event on startup: 3 = release */
279static uint buttons; /* bit field of pressed buttons */
280
281void
282clipcopy(const Arg *dummy)
283{
284 Atom clipboard;
285
286 free(xsel.clipboard);
287 xsel.clipboard = NULL;
288
289 if (xsel.primary != NULL) {
290 xsel.clipboard = xstrdup(xsel.primary);
291 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
292 XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
293 }
294}
295
296void
297clippaste(const Arg *dummy)
298{
299 Atom clipboard;
300
301 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
302 XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
303 xw.win, CurrentTime);
304}
305
306void
307selpaste(const Arg *dummy)
308{
309 XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
310 xw.win, CurrentTime);
311}
312
313void
314numlock(const Arg *dummy)
315{
316 win.mode ^= MODE_NUMLOCK;
317}
318
319void
320changealpha(const Arg *arg)
321{
322 if((alpha > 0 && arg->f < 0) || (alpha < 1 && arg->f > 0))
323 alpha += arg->f;
324 alpha = clamp(alpha, 0.0, 1.0);
325 alphaUnfocus = clamp(alpha-alphaOffset, 0.0, 1.0);
326
327 xloadcols();
328 redraw();
329}
330
331void
332zoom(const Arg *arg)
333{
334 Arg larg;
335
336 larg.f = usedfontsize + arg->f;
337 zoomabs(&larg);
338}
339
340void
341zoomabs(const Arg *arg)
342{
343 xunloadfonts();
344 xloadfonts(usedfont, arg->f);
345 xloadsparefonts();
346 cresize(0, 0);
347 redraw();
348 xhints();
349}
350
351void
352zoomreset(const Arg *arg)
353{
354 Arg larg;
355
356 if (defaultfontsize > 0) {
357 larg.f = defaultfontsize;
358 zoomabs(&larg);
359 }
360}
361
362void
363ttysend(const Arg *arg)
364{
365 ttywrite(arg->s, strlen(arg->s), 1);
366}
367
368int
369evcol(XEvent *e)
370{
371 int x = e->xbutton.x - borderpx;
372 LIMIT(x, 0, win.tw - 1);
373 return x / win.cw;
374}
375
376int
377evrow(XEvent *e)
378{
379 int y = e->xbutton.y - borderpx;
380 LIMIT(y, 0, win.th - 1);
381 return y / win.ch;
382}
383
384float
385clamp(float value, float lower, float upper) {
386 if(value < lower)
387 return lower;
388 if(value > upper)
389 return upper;
390 return value;
391}
392
393void
394mousesel(XEvent *e, int done)
395{
396 int type, seltype = SEL_REGULAR;
397 uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
398
399 for (type = 1; type < LEN(selmasks); ++type) {
400 if (match(selmasks[type], state)) {
401 seltype = type;
402 break;
403 }
404 }
405 selextend(evcol(e), evrow(e), seltype, done);
406 if (done)
407 setsel(getsel(), e->xbutton.time);
408}
409
410void
411mousereport(XEvent *e)
412{
413 int len, x = evcol(e), y = evrow(e),
414 button = e->xbutton.button, state = e->xbutton.state;
415 char buf[40];
416 static int ox, oy;
417
418 /* from urxvt */
419 if (e->xbutton.type == MotionNotify) {
420 if (x == ox && y == oy)
421 return;
422 if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
423 return;
424 /* MOUSE_MOTION: no reporting if no button is pressed */
425 if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
426 return;
427
428 button = oldbutton + 32;
429 ox = x;
430 oy = y;
431 } else {
432 if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
433 button = 3;
434 } else {
435 button -= Button1;
436 if (button >= 3)
437 button += 64 - 3;
438 }
439 if (e->xbutton.type == ButtonPress) {
440 oldbutton = button;
441 ox = x;
442 oy = y;
443 } else if (e->xbutton.type == ButtonRelease) {
444 oldbutton = 3;
445 /* MODE_MOUSEX10: no button release reporting */
446 if (IS_SET(MODE_MOUSEX10))
447 return;
448 if (button == 64 || button == 65)
449 return;
450 }
451 }
452
453 if (!IS_SET(MODE_MOUSEX10)) {
454 button += ((state & ShiftMask ) ? 4 : 0)
455 + ((state & Mod4Mask ) ? 8 : 0)
456 + ((state & ControlMask) ? 16 : 0);
457 }
458
459 if (IS_SET(MODE_MOUSESGR)) {
460 len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
461 button, x+1, y+1,
462 e->type == ButtonRelease ? 'm' : 'M');
463 } else if (x < 223 && y < 223) {
464 len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
465 32+button, 32+x+1, 32+y+1);
466 } else {
467 return;
468 }
469
470 ttywrite(buf, len, 0);
471}
472
473uint
474buttonmask(uint button)
475{
476 return button == Button1 ? Button1Mask
477 : button == Button2 ? Button2Mask
478 : button == Button3 ? Button3Mask
479 : button == Button4 ? Button4Mask
480 : button == Button5 ? Button5Mask
481 : 0;
482}
483
484int
485mouseaction(XEvent *e, uint release)
486{
487 MouseShortcut *ms;
488
489 /* ignore Button<N>mask for Button<N> - it's set on release */
490 uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
491
492 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
493 if (ms->release == release &&
494 ms->button == e->xbutton.button &&
495 (match(ms->mod, state) || /* exact or forced */
496 match(ms->mod, state & ~forcemousemod))) {
497 ms->func(&(ms->arg));
498 return 1;
499 }
500 }
501
502 return 0;
503}
504
505void
506bpress(XEvent *e)
507{
508 int btn = e->xbutton.button;
509 struct timespec now;
510 int snap;
511
512 if (1 <= btn && btn <= 11)
513 buttons |= 1 << (btn-1);
514
515 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
516 mousereport(e);
517 return;
518 }
519
520 if (mouseaction(e, 0))
521 return;
522
523 if (btn == Button1) {
524 /*
525 * If the user clicks below predefined timeouts specific
526 * snapping behaviour is exposed.
527 */
528 clock_gettime(CLOCK_MONOTONIC, &now);
529 if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {
530 snap = SNAP_LINE;
531 } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {
532 snap = SNAP_WORD;
533 } else {
534 snap = 0;
535 }
536 xsel.tclick2 = xsel.tclick1;
537 xsel.tclick1 = now;
538
539 selstart(evcol(e), evrow(e), snap);
540 }
541}
542
543void
544propnotify(XEvent *e)
545{
546 XPropertyEvent *xpev;
547 Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
548
549 xpev = &e->xproperty;
550 if (xpev->state == PropertyNewValue &&
551 (xpev->atom == XA_PRIMARY ||
552 xpev->atom == clipboard)) {
553 selnotify(e);
554 }
555}
556
557void
558selnotify(XEvent *e)
559{
560 ulong nitems, ofs, rem;
561 int format;
562 uchar *data, *last, *repl;
563 Atom type, incratom, property = None;
564
565 incratom = XInternAtom(xw.dpy, "INCR", 0);
566
567 ofs = 0;
568 if (e->type == SelectionNotify)
569 property = e->xselection.property;
570 else if (e->type == PropertyNotify)
571 property = e->xproperty.atom;
572
573 if (property == None)
574 return;
575
576 do {
577 if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
578 BUFSIZ/4, False, AnyPropertyType,
579 &type, &format, &nitems, &rem,
580 &data)) {
581 fprintf(stderr, "Clipboard allocation failed\n");
582 return;
583 }
584
585 if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
586 /*
587 * If there is some PropertyNotify with no data, then
588 * this is the signal of the selection owner that all
589 * data has been transferred. We won't need to receive
590 * PropertyNotify events anymore.
591 */
592 MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
593 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
594 &xw.attrs);
595 }
596
597 if (type == incratom) {
598 /*
599 * Activate the PropertyNotify events so we receive
600 * when the selection owner does send us the next
601 * chunk of data.
602 */
603 MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
604 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
605 &xw.attrs);
606
607 /*
608 * Deleting the property is the transfer start signal.
609 */
610 XDeleteProperty(xw.dpy, xw.win, (int)property);
611 continue;
612 }
613
614 /*
615 * As seen in getsel:
616 * Line endings are inconsistent in the terminal and GUI world
617 * copy and pasting. When receiving some selection data,
618 * replace all '\n' with '\r'.
619 * FIXME: Fix the computer world.
620 */
621 repl = data;
622 last = data + nitems * format / 8;
623 while ((repl = memchr(repl, '\n', last - repl))) {
624 *repl++ = '\r';
625 }
626
627 if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)
628 ttywrite("\033[200~", 6, 0);
629 ttywrite((char *)data, nitems * format / 8, 1);
630 if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
631 ttywrite("\033[201~", 6, 0);
632 XFree(data);
633 /* number of 32-bit chunks returned */
634 ofs += nitems * format / 32;
635 } while (rem > 0);
636
637 /*
638 * Deleting the property again tells the selection owner to send the
639 * next data chunk in the property.
640 */
641 XDeleteProperty(xw.dpy, xw.win, (int)property);
642}
643
644void
645xclipcopy(void)
646{
647 clipcopy(NULL);
648}
649
650void
651selclear_(XEvent *e)
652{
653 selclear();
654}
655
656void
657selrequest(XEvent *e)
658{
659 XSelectionRequestEvent *xsre;
660 XSelectionEvent xev;
661 Atom xa_targets, string, clipboard;
662 char *seltext;
663
664 xsre = (XSelectionRequestEvent *) e;
665 xev.type = SelectionNotify;
666 xev.requestor = xsre->requestor;
667 xev.selection = xsre->selection;
668 xev.target = xsre->target;
669 xev.time = xsre->time;
670 if (xsre->property == None)
671 xsre->property = xsre->target;
672
673 /* reject */
674 xev.property = None;
675
676 xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
677 if (xsre->target == xa_targets) {
678 /* respond with the supported type */
679 string = xsel.xtarget;
680 XChangeProperty(xsre->display, xsre->requestor, xsre->property,
681 XA_ATOM, 32, PropModeReplace,
682 (uchar *) &string, 1);
683 xev.property = xsre->property;
684 } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {
685 /*
686 * xith XA_STRING non ascii characters may be incorrect in the
687 * requestor. It is not our problem, use utf8.
688 */
689 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
690 if (xsre->selection == XA_PRIMARY) {
691 seltext = xsel.primary;
692 } else if (xsre->selection == clipboard) {
693 seltext = xsel.clipboard;
694 } else {
695 fprintf(stderr,
696 "Unhandled clipboard selection 0x%lx\n",
697 xsre->selection);
698 return;
699 }
700 if (seltext != NULL) {
701 XChangeProperty(xsre->display, xsre->requestor,
702 xsre->property, xsre->target,
703 8, PropModeReplace,
704 (uchar *)seltext, strlen(seltext));
705 xev.property = xsre->property;
706 }
707 }
708
709 /* all done, send a notification to the listener */
710 if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
711 fprintf(stderr, "Error sending SelectionNotify event\n");
712}
713
714void
715setsel(char *str, Time t)
716{
717 if (!str)
718 return;
719
720 free(xsel.primary);
721 xsel.primary = str;
722
723 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
724 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
725 selclear();
726}
727
728void
729xsetsel(char *str)
730{
731 setsel(str, CurrentTime);
732}
733
734void
735brelease(XEvent *e)
736{
737 int btn = e->xbutton.button;
738
739 if (1 <= btn && btn <= 11)
740 buttons &= ~(1 << (btn-1));
741
742 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
743 mousereport(e);
744 return;
745 }
746
747 if (mouseaction(e, 1))
748 return;
749 if (btn == Button1)
750 mousesel(e, 1);
751}
752
753void
754bmotion(XEvent *e)
755{
756 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
757 mousereport(e);
758 return;
759 }
760
761 mousesel(e, 0);
762}
763
764void
765cresize(int width, int height)
766{
767 int col, row;
768
769 if (width != 0)
770 win.w = width;
771 if (height != 0)
772 win.h = height;
773
774 col = (win.w - 2 * borderpx) / win.cw;
775 row = (win.h - 2 * borderpx) / win.ch;
776 col = MAX(1, col);
777 row = MAX(1, row);
778
779 tresize(col, row);
780 xresize(col, row);
781 ttyresize(win.tw, win.th);
782}
783
784void
785xresize(int col, int row)
786{
787 win.tw = col * win.cw;
788 win.th = row * win.ch;
789
790 XFreePixmap(xw.dpy, xw.buf);
791 xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
792 xw.depth);
793 XftDrawChange(xw.draw, xw.buf);
794 xclear(0, 0, win.w, win.h);
795
796 /* resize to new width */
797 xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
798}
799
800ushort
801sixd_to_16bit(int x)
802{
803 return x == 0 ? 0 : 0x3737 + 0x2828 * x;
804}
805
806int
807xloadcolor(int i, const char *name, Color *ncolor)
808{
809 XRenderColor color = { .alpha = 0xffff };
810
811 if (!name) {
812 if (BETWEEN(i, 16, 255)) { /* 256 color */
813 if (i < 6*6*6+16) { /* same colors as xterm */
814 color.red = sixd_to_16bit( ((i-16)/36)%6 );
815 color.green = sixd_to_16bit( ((i-16)/6) %6 );
816 color.blue = sixd_to_16bit( ((i-16)/1) %6 );
817 } else { /* greyscale */
818 color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
819 color.green = color.blue = color.red;
820 }
821 return XftColorAllocValue(xw.dpy, xw.vis,
822 xw.cmap, &color, ncolor);
823 } else
824 name = colorname[i];
825 }
826
827 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
828}
829
830void
831xloadalpha(void)
832{
833 float const usedAlpha = focused ? alpha : alphaUnfocus;
834 if (opt_alpha) alpha = strtof(opt_alpha, NULL);
835 dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * usedAlpha);
836 dc.col[defaultbg].pixel &= 0x00FFFFFF;
837 dc.col[defaultbg].pixel |= (unsigned char)(0xff * usedAlpha) << 24;
838}
839
840void
841xloadcols(void)
842{
843 int i;
844 static int loaded;
845 Color *cp;
846
847 if (!loaded) {
848 dc.collen = 1 + (defaultbg = MAX(LEN(colorname), 256));
849 dc.col = xmalloc(dc.collen * sizeof(Color));
850 }
851
852 for (i = 0; i+1 < dc.collen; i++)
853 if (!xloadcolor(i, NULL, &dc.col[i])) {
854 if (colorname[i])
855 die("could not allocate color '%s'\n", colorname[i]);
856 else
857 die("could not allocate color %d\n", i);
858 }
859
860 if (dc.collen) // cannot die, as the color is already loaded.
861 xloadcolor(background, NULL, &dc.col[defaultbg]);
862
863 xloadalpha();
864 loaded = 1;
865}
866
867int
868xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
869{
870 if (!BETWEEN(x, 0, dc.collen))
871 return 1;
872
873 *r = dc.col[x].color.red >> 8;
874 *g = dc.col[x].color.green >> 8;
875 *b = dc.col[x].color.blue >> 8;
876
877 return 0;
878}
879
880int
881xsetcolorname(int x, const char *name)
882{
883 Color ncolor;
884
885 if (!BETWEEN(x, 0, dc.collen))
886 return 1;
887
888 if (!xloadcolor(x, name, &ncolor))
889 return 1;
890
891 XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
892 dc.col[x] = ncolor;
893
894 return 0;
895}
896
897/*
898 * Absolute coordinates.
899 */
900void
901xclear(int x1, int y1, int x2, int y2)
902{
903 XftDrawRect(xw.draw,
904 &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
905 x1, y1, x2-x1, y2-y1);
906}
907
908void
909xhints(void)
910{
911 XClassHint class = {opt_name ? opt_name : "st",
912 opt_class ? opt_class : "St"};
913 XWMHints wm = {.flags = InputHint, .input = 1};
914 XSizeHints *sizeh;
915
916 sizeh = XAllocSizeHints();
917
918 sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
919 sizeh->height = win.h;
920 sizeh->width = win.w;
921 sizeh->height_inc = win.ch;
922 sizeh->width_inc = win.cw;
923 sizeh->base_height = 2 * borderpx;
924 sizeh->base_width = 2 * borderpx;
925 sizeh->min_height = win.ch + 2 * borderpx;
926 sizeh->min_width = win.cw + 2 * borderpx;
927 if (xw.isfixed) {
928 sizeh->flags |= PMaxSize;
929 sizeh->min_width = sizeh->max_width = win.w;
930 sizeh->min_height = sizeh->max_height = win.h;
931 }
932 if (xw.gm & (XValue|YValue)) {
933 sizeh->flags |= USPosition | PWinGravity;
934 sizeh->x = xw.l;
935 sizeh->y = xw.t;
936 sizeh->win_gravity = xgeommasktogravity(xw.gm);
937 }
938
939 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,
940 &class);
941 XFree(sizeh);
942}
943
944int
945xgeommasktogravity(int mask)
946{
947 switch (mask & (XNegative|YNegative)) {
948 case 0:
949 return NorthWestGravity;
950 case XNegative:
951 return NorthEastGravity;
952 case YNegative:
953 return SouthWestGravity;
954 }
955
956 return SouthEastGravity;
957}
958
959int
960xloadfont(Font *f, FcPattern *pattern)
961{
962 FcPattern *configured;
963 FcPattern *match;
964 FcResult result;
965 XGlyphInfo extents;
966 int wantattr, haveattr;
967
968 /*
969 * Manually configure instead of calling XftMatchFont
970 * so that we can use the configured pattern for
971 * "missing glyph" lookups.
972 */
973 configured = FcPatternDuplicate(pattern);
974 if (!configured)
975 return 1;
976
977 FcConfigSubstitute(NULL, configured, FcMatchPattern);
978 XftDefaultSubstitute(xw.dpy, xw.scr, configured);
979
980 match = FcFontMatch(NULL, configured, &result);
981 if (!match) {
982 FcPatternDestroy(configured);
983 return 1;
984 }
985
986 if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
987 FcPatternDestroy(configured);
988 FcPatternDestroy(match);
989 return 1;
990 }
991
992 if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
993 XftResultMatch)) {
994 /*
995 * Check if xft was unable to find a font with the appropriate
996 * slant but gave us one anyway. Try to mitigate.
997 */
998 if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
999 &haveattr) != XftResultMatch) || haveattr < wantattr) {
1000 f->badslant = 1;
1001 fputs("font slant does not match\n", stderr);
1002 }
1003 }
1004
1005 if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
1006 XftResultMatch)) {
1007 if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
1008 &haveattr) != XftResultMatch) || haveattr != wantattr) {
1009 f->badweight = 1;
1010 fputs("font weight does not match\n", stderr);
1011 }
1012 }
1013
1014 XftTextExtentsUtf8(xw.dpy, f->match,
1015 (const FcChar8 *) ascii_printable,
1016 strlen(ascii_printable), &extents);
1017
1018 f->set = NULL;
1019 f->pattern = configured;
1020
1021 f->ascent = f->match->ascent;
1022 f->descent = f->match->descent;
1023 f->lbearing = 0;
1024 f->rbearing = f->match->max_advance_width;
1025
1026 f->height = f->ascent + f->descent;
1027 f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
1028
1029 return 0;
1030}
1031
1032void
1033xloadfonts(const char *fontstr, double fontsize)
1034{
1035 FcPattern *pattern;
1036 double fontval;
1037
1038 if (fontstr[0] == '-')
1039 pattern = XftXlfdParse(fontstr, False, False);
1040 else
1041 pattern = FcNameParse((const FcChar8 *)fontstr);
1042
1043 if (!pattern)
1044 die("can't open font %s\n", fontstr);
1045
1046 if (fontsize > 1) {
1047 FcPatternDel(pattern, FC_PIXEL_SIZE);
1048 FcPatternDel(pattern, FC_SIZE);
1049 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
1050 usedfontsize = fontsize;
1051 } else {
1052 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
1053 FcResultMatch) {
1054 usedfontsize = fontval;
1055 } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
1056 FcResultMatch) {
1057 usedfontsize = -1;
1058 } else {
1059 /*
1060 * Default font size is 12, if none given. This is to
1061 * have a known usedfontsize value.
1062 */
1063 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
1064 usedfontsize = 12;
1065 }
1066 defaultfontsize = usedfontsize;
1067 }
1068
1069 if (xloadfont(&dc.font, pattern))
1070 die("can't open font %s\n", fontstr);
1071
1072 if (usedfontsize < 0) {
1073 FcPatternGetDouble(dc.font.match->pattern,
1074 FC_PIXEL_SIZE, 0, &fontval);
1075 usedfontsize = fontval;
1076 if (fontsize == 0)
1077 defaultfontsize = fontval;
1078 }
1079
1080 /* Setting character width and height. */
1081 win.cw = ceilf(dc.font.width * cwscale);
1082 win.ch = ceilf(dc.font.height * chscale);
1083
1084 FcPatternDel(pattern, FC_SLANT);
1085 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
1086 if (xloadfont(&dc.ifont, pattern))
1087 die("can't open font %s\n", fontstr);
1088
1089 FcPatternDel(pattern, FC_WEIGHT);
1090 FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
1091 if (xloadfont(&dc.ibfont, pattern))
1092 die("can't open font %s\n", fontstr);
1093
1094 FcPatternDel(pattern, FC_SLANT);
1095 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
1096 if (xloadfont(&dc.bfont, pattern))
1097 die("can't open font %s\n", fontstr);
1098
1099 FcPatternDestroy(pattern);
1100}
1101
1102int
1103xloadsparefont(FcPattern *pattern, int flags)
1104{
1105 FcPattern *match;
1106 FcResult result;
1107
1108 match = FcFontMatch(NULL, pattern, &result);
1109 if (!match) {
1110 return 1;
1111 }
1112
1113 if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) {
1114 FcPatternDestroy(match);
1115 return 1;
1116 }
1117
1118 frc[frclen].flags = flags;
1119 /* Believe U+0000 glyph will present in each default font */
1120 frc[frclen].unicodep = 0;
1121 frclen++;
1122
1123 return 0;
1124}
1125
1126void
1127xloadsparefonts(void)
1128{
1129 FcPattern *pattern;
1130 double sizeshift, fontval;
1131 int fc;
1132 char **fp;
1133
1134 if (frclen != 0)
1135 die("can't embed spare fonts. cache isn't empty");
1136
1137 /* Calculate count of spare fonts */
1138 fc = sizeof(font2) / sizeof(*font2);
1139 if (fc == 0)
1140 return;
1141
1142 /* Allocate memory for cache entries. */
1143 if (frccap < 4 * fc) {
1144 frccap += 4 * fc - frccap;
1145 frc = xrealloc(frc, frccap * sizeof(Fontcache));
1146 }
1147
1148 for (fp = font2; fp - font2 < fc; ++fp) {
1149
1150 if (**fp == '-')
1151 pattern = XftXlfdParse(*fp, False, False);
1152 else
1153 pattern = FcNameParse((FcChar8 *)*fp);
1154
1155 if (!pattern)
1156 die("can't open spare font %s\n", *fp);
1157
1158 if (defaultfontsize > 0) {
1159 sizeshift = usedfontsize - defaultfontsize;
1160 if (sizeshift != 0 &&
1161 FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
1162 FcResultMatch) {
1163 fontval += sizeshift;
1164 FcPatternDel(pattern, FC_PIXEL_SIZE);
1165 FcPatternDel(pattern, FC_SIZE);
1166 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval);
1167 }
1168 }
1169
1170 FcPatternAddBool(pattern, FC_SCALABLE, 1);
1171
1172 FcConfigSubstitute(NULL, pattern, FcMatchPattern);
1173 XftDefaultSubstitute(xw.dpy, xw.scr, pattern);
1174
1175 if (xloadsparefont(pattern, FRC_NORMAL))
1176 die("can't open spare font %s\n", *fp);
1177
1178 FcPatternDel(pattern, FC_SLANT);
1179 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
1180 if (xloadsparefont(pattern, FRC_ITALIC))
1181 die("can't open spare font %s\n", *fp);
1182
1183 FcPatternDel(pattern, FC_WEIGHT);
1184 FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
1185 if (xloadsparefont(pattern, FRC_ITALICBOLD))
1186 die("can't open spare font %s\n", *fp);
1187
1188 FcPatternDel(pattern, FC_SLANT);
1189 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
1190 if (xloadsparefont(pattern, FRC_BOLD))
1191 die("can't open spare font %s\n", *fp);
1192
1193 FcPatternDestroy(pattern);
1194 }
1195}
1196
1197void
1198xunloadfont(Font *f)
1199{
1200 XftFontClose(xw.dpy, f->match);
1201 FcPatternDestroy(f->pattern);
1202 if (f->set)
1203 FcFontSetDestroy(f->set);
1204}
1205
1206void
1207xunloadfonts(void)
1208{
1209 /* Clear Harfbuzz font cache. */
1210 hbunloadfonts();
1211
1212 /* Free the loaded fonts in the font cache. */
1213 while (frclen > 0)
1214 XftFontClose(xw.dpy, frc[--frclen].font);
1215
1216 xunloadfont(&dc.font);
1217 xunloadfont(&dc.bfont);
1218 xunloadfont(&dc.ifont);
1219 xunloadfont(&dc.ibfont);
1220}
1221
1222int
1223ximopen(Display *dpy)
1224{
1225 XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
1226 XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
1227
1228 xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
1229 if (xw.ime.xim == NULL)
1230 return 0;
1231
1232 if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
1233 fprintf(stderr, "XSetIMValues: "
1234 "Could not set XNDestroyCallback.\n");
1235
1236 xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
1237 NULL);
1238
1239 if (xw.ime.xic == NULL) {
1240 xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
1241 XIMPreeditNothing | XIMStatusNothing,
1242 XNClientWindow, xw.win,
1243 XNDestroyCallback, &icdestroy,
1244 NULL);
1245 }
1246 if (xw.ime.xic == NULL)
1247 fprintf(stderr, "XCreateIC: Could not create input context.\n");
1248
1249 return 1;
1250}
1251
1252void
1253ximinstantiate(Display *dpy, XPointer client, XPointer call)
1254{
1255 if (ximopen(dpy))
1256 XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
1257 ximinstantiate, NULL);
1258}
1259
1260void
1261ximdestroy(XIM xim, XPointer client, XPointer call)
1262{
1263 xw.ime.xim = NULL;
1264 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
1265 ximinstantiate, NULL);
1266 XFree(xw.ime.spotlist);
1267}
1268
1269int
1270xicdestroy(XIC xim, XPointer client, XPointer call)
1271{
1272 xw.ime.xic = NULL;
1273 return 1;
1274}
1275
1276void
1277xinit(int cols, int rows)
1278{
1279 XGCValues gcvalues;
1280 Cursor cursor;
1281 Window parent;
1282 pid_t thispid = getpid();
1283 XColor xmousefg, xmousebg;
1284 XWindowAttributes attr;
1285 XVisualInfo vis;
1286
1287 xw.scr = XDefaultScreen(xw.dpy);
1288
1289 if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) {
1290 parent = XRootWindow(xw.dpy, xw.scr);
1291 xw.depth = 32;
1292 } else {
1293 XGetWindowAttributes(xw.dpy, parent, &attr);
1294 xw.depth = attr.depth;
1295 }
1296
1297 XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis);
1298 xw.vis = vis.visual;
1299
1300 /* font */
1301 if (!FcInit())
1302 die("could not init fontconfig.\n");
1303
1304 usedfont = (opt_font == NULL)? font : opt_font;
1305 xloadfonts(usedfont, 0);
1306
1307 /* spare fonts */
1308 xloadsparefonts();
1309
1310 /* colors */
1311 xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
1312 xloadcols();
1313
1314 /* adjust fixed window geometry */
1315 win.w = 2 * borderpx + cols * win.cw;
1316 win.h = 2 * borderpx + rows * win.ch;
1317 if (xw.gm & XNegative)
1318 xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
1319 if (xw.gm & YNegative)
1320 xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
1321
1322 /* Events */
1323 xw.attrs.background_pixel = dc.col[defaultbg].pixel;
1324 xw.attrs.border_pixel = dc.col[defaultbg].pixel;
1325 xw.attrs.bit_gravity = NorthWestGravity;
1326 xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
1327 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
1328 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
1329 xw.attrs.colormap = xw.cmap;
1330
1331 xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
1332 win.w, win.h, 0, xw.depth, InputOutput,
1333 xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
1334 | CWEventMask | CWColormap, &xw.attrs);
1335
1336 memset(&gcvalues, 0, sizeof(gcvalues));
1337 gcvalues.graphics_exposures = False;
1338 xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
1339 dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues);
1340 XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
1341 XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
1342
1343 /* font spec buffer */
1344 xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
1345
1346 /* Xft rendering context */
1347 xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
1348
1349 /* input methods */
1350 if (!ximopen(xw.dpy)) {
1351 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
1352 ximinstantiate, NULL);
1353 }
1354
1355 /* white cursor, black outline */
1356 cursor = XCreateFontCursor(xw.dpy, mouseshape);
1357 XDefineCursor(xw.dpy, xw.win, cursor);
1358
1359 if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
1360 xmousefg.red = 0xffff;
1361 xmousefg.green = 0xffff;
1362 xmousefg.blue = 0xffff;
1363 }
1364
1365 if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
1366 xmousebg.red = 0x0000;
1367 xmousebg.green = 0x0000;
1368 xmousebg.blue = 0x0000;
1369 }
1370
1371 XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
1372
1373 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
1374 xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
1375 xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
1376 xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
1377 XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
1378
1379 xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
1380 XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
1381 PropModeReplace, (uchar *)&thispid, 1);
1382
1383 win.mode = MODE_NUMLOCK;
1384 resettitle();
1385 xhints();
1386 XMapWindow(xw.dpy, xw.win);
1387 XSync(xw.dpy, False);
1388
1389 clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
1390 clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
1391 xsel.primary = NULL;
1392 xsel.clipboard = NULL;
1393 xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
1394 if (xsel.xtarget == None)
1395 xsel.xtarget = XA_STRING;
1396
1397 boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
1398}
1399
1400int
1401xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
1402{
1403 float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
1404 ushort mode, prevmode = USHRT_MAX;
1405 Font *font = &dc.font;
1406 int frcflags = FRC_NORMAL;
1407 float runewidth = win.cw;
1408 Rune rune;
1409 FT_UInt glyphidx;
1410 FcResult fcres;
1411 FcPattern *fcpattern, *fontpattern;
1412 FcFontSet *fcsets[] = { NULL };
1413 FcCharSet *fccharset;
1414 int i, f, numspecs = 0;
1415
1416 for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
1417 /* Fetch rune and mode for current glyph. */
1418 rune = glyphs[i].u;
1419 mode = glyphs[i].mode;
1420
1421 /* Skip dummy wide-character spacing. */
1422 if (mode & ATTR_WDUMMY)
1423 continue;
1424
1425 /* Determine font for glyph if different from previous glyph. */
1426 if (prevmode != mode) {
1427 prevmode = mode;
1428 font = &dc.font;
1429 frcflags = FRC_NORMAL;
1430 runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
1431 if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
1432 font = &dc.ibfont;
1433 frcflags = FRC_ITALICBOLD;
1434 } else if (mode & ATTR_ITALIC) {
1435 font = &dc.ifont;
1436 frcflags = FRC_ITALIC;
1437 } else if (mode & ATTR_BOLD) {
1438 font = &dc.bfont;
1439 frcflags = FRC_BOLD;
1440 }
1441 yp = winy + font->ascent;
1442 }
1443
1444 if (mode & ATTR_BOXDRAW) {
1445 /* minor shoehorning: boxdraw uses only this ushort */
1446 glyphidx = boxdrawindex(&glyphs[i]);
1447 } else {
1448 /* Lookup character index with default font. */
1449 glyphidx = XftCharIndex(xw.dpy, font->match, rune);
1450 }
1451 if (glyphidx) {
1452 specs[numspecs].font = font->match;
1453 specs[numspecs].glyph = glyphidx;
1454 specs[numspecs].x = (short)xp;
1455 specs[numspecs].y = (short)yp;
1456 xp += runewidth;
1457 numspecs++;
1458 continue;
1459 }
1460
1461 /* Fallback on font cache, search the font cache for match. */
1462 for (f = 0; f < frclen; f++) {
1463 glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
1464 /* Everything correct. */
1465 if (glyphidx && frc[f].flags == frcflags)
1466 break;
1467 /* We got a default font for a not found glyph. */
1468 if (!glyphidx && frc[f].flags == frcflags
1469 && frc[f].unicodep == rune) {
1470 break;
1471 }
1472 }
1473
1474 /* Nothing was found. Use fontconfig to find matching font. */
1475 if (f >= frclen) {
1476 if (!font->set)
1477 font->set = FcFontSort(0, font->pattern,
1478 1, 0, &fcres);
1479 fcsets[0] = font->set;
1480
1481 /*
1482 * Nothing was found in the cache. Now use
1483 * some dozen of Fontconfig calls to get the
1484 * font for one single character.
1485 *
1486 * Xft and fontconfig are design failures.
1487 */
1488 fcpattern = FcPatternDuplicate(font->pattern);
1489 fccharset = FcCharSetCreate();
1490
1491 FcCharSetAddChar(fccharset, rune);
1492 FcPatternAddCharSet(fcpattern, FC_CHARSET,
1493 fccharset);
1494 FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
1495
1496 FcConfigSubstitute(0, fcpattern,
1497 FcMatchPattern);
1498 FcDefaultSubstitute(fcpattern);
1499
1500 fontpattern = FcFontSetMatch(0, fcsets, 1,
1501 fcpattern, &fcres);
1502
1503 /* Allocate memory for the new cache entry. */
1504 if (frclen >= frccap) {
1505 frccap += 16;
1506 frc = xrealloc(frc, frccap * sizeof(Fontcache));
1507 }
1508
1509 frc[frclen].font = XftFontOpenPattern(xw.dpy,
1510 fontpattern);
1511 if (!frc[frclen].font)
1512 die("XftFontOpenPattern failed seeking fallback font: %s\n",
1513 strerror(errno));
1514 frc[frclen].flags = frcflags;
1515 frc[frclen].unicodep = rune;
1516
1517 glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
1518
1519 f = frclen;
1520 frclen++;
1521
1522 FcPatternDestroy(fcpattern);
1523 FcCharSetDestroy(fccharset);
1524 }
1525
1526 specs[numspecs].font = frc[f].font;
1527 specs[numspecs].glyph = glyphidx;
1528 specs[numspecs].x = (short)xp;
1529 specs[numspecs].y = (short)yp;
1530 xp += runewidth;
1531 numspecs++;
1532 }
1533
1534 /* Harfbuzz transformation for ligatures. */
1535 hbtransform(specs, glyphs, len, x, y);
1536
1537 return numspecs;
1538}
1539
1540void
1541xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
1542{
1543 int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
1544 int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
1545 width = charlen * win.cw;
1546 Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
1547 XRenderColor colfg, colbg;
1548 XRectangle r;
1549
1550 /* Fallback on color display for attributes not supported by the font */
1551 if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
1552 if (dc.ibfont.badslant || dc.ibfont.badweight)
1553 base.fg = defaultattr;
1554 } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
1555 (base.mode & ATTR_BOLD && dc.bfont.badweight)) {
1556 base.fg = defaultattr;
1557 }
1558
1559 if (IS_TRUECOL(base.fg)) {
1560 colfg.alpha = 0xffff;
1561 colfg.red = TRUERED(base.fg);
1562 colfg.green = TRUEGREEN(base.fg);
1563 colfg.blue = TRUEBLUE(base.fg);
1564 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
1565 fg = &truefg;
1566 } else {
1567 fg = &dc.col[base.fg];
1568 }
1569
1570 if (IS_TRUECOL(base.bg)) {
1571 colbg.alpha = 0xffff;
1572 colbg.green = TRUEGREEN(base.bg);
1573 colbg.red = TRUERED(base.bg);
1574 colbg.blue = TRUEBLUE(base.bg);
1575 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
1576 bg = &truebg;
1577 } else {
1578 bg = &dc.col[base.bg];
1579 }
1580
1581 /* Change basic system colors [0-7] to bright system colors [8-15] */
1582 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
1583 fg = &dc.col[base.fg + 8];
1584
1585 if (IS_SET(MODE_REVERSE)) {
1586 if (fg == &dc.col[defaultfg]) {
1587 fg = &dc.col[defaultbg];
1588 } else {
1589 colfg.red = ~fg->color.red;
1590 colfg.green = ~fg->color.green;
1591 colfg.blue = ~fg->color.blue;
1592 colfg.alpha = fg->color.alpha;
1593 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg,
1594 &revfg);
1595 fg = &revfg;
1596 }
1597
1598 if (bg == &dc.col[defaultbg]) {
1599 bg = &dc.col[defaultfg];
1600 } else {
1601 colbg.red = ~bg->color.red;
1602 colbg.green = ~bg->color.green;
1603 colbg.blue = ~bg->color.blue;
1604 colbg.alpha = bg->color.alpha;
1605 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg,
1606 &revbg);
1607 bg = &revbg;
1608 }
1609 }
1610
1611 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
1612 colfg.red = fg->color.red / 2;
1613 colfg.green = fg->color.green / 2;
1614 colfg.blue = fg->color.blue / 2;
1615 colfg.alpha = fg->color.alpha;
1616 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
1617 fg = &revfg;
1618 }
1619
1620 if (base.mode & ATTR_REVERSE) {
1621 temp = fg;
1622 fg = bg;
1623 bg = temp;
1624 }
1625
1626 if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
1627 fg = bg;
1628
1629 if (base.mode & ATTR_INVISIBLE)
1630 fg = bg;
1631
1632 /* Intelligent cleaning up of the borders. */
1633 if (x == 0) {
1634 xclear(0, (y == 0)? 0 : winy, borderpx,
1635 winy + win.ch +
1636 ((winy + win.ch >= borderpx + win.th)? win.h : 0));
1637 }
1638 if (winx + width >= borderpx + win.tw) {
1639 xclear(winx + width, (y == 0)? 0 : winy, win.w,
1640 ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
1641 }
1642 if (y == 0)
1643 xclear(winx, 0, winx + width, borderpx);
1644 if (winy + win.ch >= borderpx + win.th)
1645 xclear(winx, winy + win.ch, winx + width, win.h);
1646
1647 /* Clean up the region we want to draw to. */
1648 XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
1649
1650 /* Set the clip region because Xft is sometimes dirty. */
1651 r.x = 0;
1652 r.y = 0;
1653 r.height = win.ch;
1654 r.width = width;
1655 XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
1656
1657 if (base.mode & ATTR_BOXDRAW) {
1658 drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
1659 } else {
1660 /* Render the glyphs. */
1661 XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
1662 }
1663
1664 /* Render underline and strikethrough. */
1665 if (base.mode & ATTR_UNDERLINE) {
1666 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
1667 width, 1);
1668 }
1669
1670 if (base.mode & ATTR_STRUCK) {
1671 XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
1672 width, 1);
1673 }
1674
1675 /* Reset clip to none. */
1676 XftDrawSetClip(xw.draw, 0);
1677}
1678
1679void
1680xdrawglyph(Glyph g, int x, int y)
1681{
1682 int numspecs;
1683 XftGlyphFontSpec spec;
1684
1685 numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
1686 xdrawglyphfontspecs(&spec, g, numspecs, x, y);
1687}
1688
1689void
1690xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
1691{
1692 Color drawcol;
1693
1694 /* remove the old cursor */
1695 if (selected(ox, oy))
1696 og.mode ^= ATTR_REVERSE;
1697
1698 /* Redraw the line where cursor was previously.
1699 * It will restore the ligatures broken by the cursor. */
1700 xdrawline(line, 0, oy, len);
1701
1702 if (IS_SET(MODE_HIDE))
1703 return;
1704
1705 /*
1706 * Select the right color for the right mode.
1707 */
1708 g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
1709
1710 if (IS_SET(MODE_REVERSE)) {
1711 g.mode |= ATTR_REVERSE;
1712 g.bg = defaultfg;
1713 if (selected(cx, cy)) {
1714 drawcol = dc.col[defaultcs];
1715 g.fg = defaultrcs;
1716 } else {
1717 drawcol = dc.col[defaultrcs];
1718 g.fg = defaultcs;
1719 }
1720 } else {
1721 if (selected(cx, cy)) {
1722 g.fg = defaultfg;
1723 g.bg = defaultrcs;
1724 } else {
1725 g.fg = defaultbg;
1726 g.bg = defaultcs;
1727 }
1728 drawcol = dc.col[g.bg];
1729 }
1730
1731 /* draw the new one */
1732 if (IS_SET(MODE_FOCUSED)) {
1733 switch (win.cursor) {
1734 case 7: /* st extension */
1735 g.u = 0x2603; /* snowman (U+2603) */
1736 /* FALLTHROUGH */
1737 case 0: /* Blinking Block */
1738 case 1: /* Blinking Block (Default) */
1739 case 2: /* Steady Block */
1740 xdrawglyph(g, cx, cy);
1741 break;
1742 case 3: /* Blinking Underline */
1743 case 4: /* Steady Underline */
1744 XftDrawRect(xw.draw, &drawcol,
1745 borderpx + cx * win.cw,
1746 borderpx + (cy + 1) * win.ch - \
1747 cursorthickness,
1748 win.cw, cursorthickness);
1749 break;
1750 case 5: /* Blinking bar */
1751 case 6: /* Steady bar */
1752 XftDrawRect(xw.draw, &drawcol,
1753 borderpx + cx * win.cw,
1754 borderpx + cy * win.ch,
1755 cursorthickness, win.ch);
1756 break;
1757 }
1758 } else {
1759 XftDrawRect(xw.draw, &drawcol,
1760 borderpx + cx * win.cw,
1761 borderpx + cy * win.ch,
1762 win.cw - 1, 1);
1763 XftDrawRect(xw.draw, &drawcol,
1764 borderpx + cx * win.cw,
1765 borderpx + cy * win.ch,
1766 1, win.ch - 1);
1767 XftDrawRect(xw.draw, &drawcol,
1768 borderpx + (cx + 1) * win.cw - 1,
1769 borderpx + cy * win.ch,
1770 1, win.ch - 1);
1771 XftDrawRect(xw.draw, &drawcol,
1772 borderpx + cx * win.cw,
1773 borderpx + (cy + 1) * win.ch - 1,
1774 win.cw, 1);
1775 }
1776}
1777
1778void
1779xsetenv(void)
1780{
1781 char buf[sizeof(long) * 8 + 1];
1782
1783 snprintf(buf, sizeof(buf), "%lu", xw.win);
1784 setenv("WINDOWID", buf, 1);
1785}
1786
1787void
1788xseticontitle(char *p)
1789{
1790 XTextProperty prop;
1791 DEFAULT(p, opt_title);
1792
1793 if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
1794 &prop) != Success)
1795 return;
1796 XSetWMIconName(xw.dpy, xw.win, &prop);
1797 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
1798 XFree(prop.value);
1799}
1800
1801void
1802xsettitle(char *p)
1803{
1804 XTextProperty prop;
1805 DEFAULT(p, opt_title);
1806
1807 if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
1808 &prop) != Success)
1809 return;
1810 XSetWMName(xw.dpy, xw.win, &prop);
1811 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
1812 XFree(prop.value);
1813}
1814
1815int
1816xstartdraw(void)
1817{
1818 return IS_SET(MODE_VISIBLE);
1819}
1820
1821void
1822xdrawline(Line line, int x1, int y1, int x2)
1823{
1824 int i, x, ox, numspecs;
1825 Glyph base, new;
1826 XftGlyphFontSpec *specs = xw.specbuf;
1827
1828 numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
1829 i = ox = 0;
1830 for (x = x1; x < x2 && i < numspecs; x++) {
1831 new = line[x];
1832 if (new.mode == ATTR_WDUMMY)
1833 continue;
1834 if (selected(x, y1))
1835 new.mode ^= ATTR_REVERSE;
1836 if (i > 0 && ATTRCMP(base, new)) {
1837 xdrawglyphfontspecs(specs, base, i, ox, y1);
1838 specs += i;
1839 numspecs -= i;
1840 i = 0;
1841 }
1842 if (i == 0) {
1843 ox = x;
1844 base = new;
1845 }
1846 i++;
1847 }
1848 if (i > 0)
1849 xdrawglyphfontspecs(specs, base, i, ox, y1);
1850}
1851
1852void
1853xfinishdraw(void)
1854{
1855 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w,
1856 win.h, 0, 0);
1857 XSetForeground(xw.dpy, dc.gc,
1858 dc.col[IS_SET(MODE_REVERSE)?
1859 defaultfg : defaultbg].pixel);
1860}
1861
1862void
1863xximspot(int x, int y)
1864{
1865 if (xw.ime.xic == NULL)
1866 return;
1867
1868 xw.ime.spot.x = borderpx + x * win.cw;
1869 xw.ime.spot.y = borderpx + (y + 1) * win.ch;
1870
1871 XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL);
1872}
1873
1874void
1875expose(XEvent *ev)
1876{
1877 redraw();
1878}
1879
1880void
1881visibility(XEvent *ev)
1882{
1883 XVisibilityEvent *e = &ev->xvisibility;
1884
1885 MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE);
1886}
1887
1888void
1889unmap(XEvent *ev)
1890{
1891 win.mode &= ~MODE_VISIBLE;
1892}
1893
1894void
1895xsetpointermotion(int set)
1896{
1897 MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
1898 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
1899}
1900
1901void
1902xsetmode(int set, unsigned int flags)
1903{
1904 int mode = win.mode;
1905 MODBIT(win.mode, set, flags);
1906 if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))
1907 redraw();
1908}
1909
1910int
1911xsetcursor(int cursor)
1912{
1913 if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
1914 return 1;
1915 win.cursor = cursor;
1916 return 0;
1917}
1918
1919void
1920xseturgency(int add)
1921{
1922 XWMHints *h = XGetWMHints(xw.dpy, xw.win);
1923
1924 MODBIT(h->flags, add, XUrgencyHint);
1925 XSetWMHints(xw.dpy, xw.win, h);
1926 XFree(h);
1927}
1928
1929void
1930xbell(void)
1931{
1932 if (!(IS_SET(MODE_FOCUSED)))
1933 xseturgency(1);
1934 if (bellvolume)
1935 XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL);
1936}
1937
1938void
1939focus(XEvent *ev)
1940{
1941 XFocusChangeEvent *e = &ev->xfocus;
1942
1943 if (e->mode == NotifyGrab)
1944 return;
1945
1946 if (ev->type == FocusIn) {
1947 if (xw.ime.xic)
1948 XSetICFocus(xw.ime.xic);
1949 win.mode |= MODE_FOCUSED;
1950 xseturgency(0);
1951 if (IS_SET(MODE_FOCUS))
1952 ttywrite("\033[I", 3, 0);
1953 if (!focused) {
1954 focused = 1;
1955 xloadcols();
1956 tfulldirt();
1957 }
1958 } else {
1959 if (xw.ime.xic)
1960 XUnsetICFocus(xw.ime.xic);
1961 win.mode &= ~MODE_FOCUSED;
1962 if (IS_SET(MODE_FOCUS))
1963 ttywrite("\033[O", 3, 0);
1964 if (focused) {
1965 focused = 0;
1966 xloadcols();
1967 tfulldirt();
1968 }
1969 }
1970}
1971
1972int
1973match(uint mask, uint state)
1974{
1975 return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
1976}
1977
1978char*
1979kmap(KeySym k, uint state)
1980{
1981 Key *kp;
1982 int i;
1983
1984 /* Check for mapped keys out of X11 function keys. */
1985 for (i = 0; i < LEN(mappedkeys); i++) {
1986 if (mappedkeys[i] == k)
1987 break;
1988 }
1989 if (i == LEN(mappedkeys)) {
1990 if ((k & 0xFFFF) < 0xFD00)
1991 return NULL;
1992 }
1993
1994 for (kp = key; kp < key + LEN(key); kp++) {
1995 if (kp->k != k)
1996 continue;
1997
1998 if (!match(kp->mask, state))
1999 continue;
2000
2001 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
2002 continue;
2003 if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2)
2004 continue;
2005
2006 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
2007 continue;
2008
2009 return kp->s;
2010 }
2011
2012 return NULL;
2013}
2014
2015void
2016kpress(XEvent *ev)
2017{
2018 XKeyEvent *e = &ev->xkey;
2019 KeySym ksym;
2020 char *buf = NULL, *customkey;
2021 int len = 0;
2022 int buf_size = 64;
2023 int critical = - 1;
2024 Rune c;
2025 Status status;
2026 Shortcut *bp;
2027
2028 if (IS_SET(MODE_KBDLOCK))
2029 return;
2030
2031reallocbuf:
2032 if (critical > 0)
2033 goto cleanup;
2034 if (buf)
2035 free(buf);
2036
2037 buf = xmalloc((buf_size) * sizeof(char));
2038 critical += 1;
2039
2040 if (xw.ime.xic) {
2041 len = XmbLookupString(xw.ime.xic, e, buf, buf_size, &ksym, &status);
2042 if (status == XBufferOverflow) {
2043 buf_size = len;
2044 goto reallocbuf;
2045 }
2046 } else {
2047 // Not sure how to fix this and if it is fixable
2048 // but at least it does write something into the buffer
2049 // so it is not as critical
2050 len = XLookupString(e, buf, buf_size, &ksym, NULL);
2051 }
2052 /* 1. shortcuts */
2053 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
2054 if (ksym == bp->keysym && match(bp->mod, e->state)) {
2055 bp->func(&(bp->arg));
2056 goto cleanup;
2057 }
2058 }
2059
2060 /* 2. custom keys from config.h */
2061 if ((customkey = kmap(ksym, e->state))) {
2062 ttywrite(customkey, strlen(customkey), 1);
2063 goto cleanup;
2064 }
2065
2066 /* 3. composed string from input method */
2067 if (len == 0)
2068 goto cleanup;
2069 if (len == 1 && e->state & Mod1Mask) {
2070 if (IS_SET(MODE_8BIT)) {
2071 if (*buf < 0177) {
2072 c = *buf | 0x80;
2073 len = utf8encode(c, buf);
2074 }
2075 } else {
2076 buf[1] = buf[0];
2077 buf[0] = '\033';
2078 len = 2;
2079 }
2080 }
2081 if (len <= buf_size)
2082 ttywrite(buf, len, 1);
2083cleanup:
2084 if (buf)
2085 free(buf);
2086}
2087
2088void
2089cmessage(XEvent *e)
2090{
2091 /*
2092 * See xembed specs
2093 * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
2094 */
2095 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
2096 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
2097 win.mode |= MODE_FOCUSED;
2098 xseturgency(0);
2099 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
2100 win.mode &= ~MODE_FOCUSED;
2101 }
2102 } else if (e->xclient.data.l[0] == xw.wmdeletewin) {
2103 ttyhangup();
2104 exit(0);
2105 }
2106}
2107
2108void
2109resize(XEvent *e)
2110{
2111 if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
2112 return;
2113
2114 cresize(e->xconfigure.width, e->xconfigure.height);
2115}
2116
2117void
2118run(void)
2119{
2120 XEvent ev;
2121 int w = win.w, h = win.h;
2122 fd_set rfd;
2123 int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
2124 struct timespec seltv, *tv, now, lastblink, trigger;
2125 double timeout;
2126
2127 /* Waiting for window mapping */
2128 do {
2129 XNextEvent(xw.dpy, &ev);
2130 /*
2131 * This XFilterEvent call is required because of XOpenIM. It
2132 * does filter out the key event and some client message for
2133 * the input method too.
2134 */
2135 if (XFilterEvent(&ev, None))
2136 continue;
2137 if (ev.type == ConfigureNotify) {
2138 w = ev.xconfigure.width;
2139 h = ev.xconfigure.height;
2140 }
2141 } while (ev.type != MapNotify);
2142
2143 ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
2144 cresize(w, h);
2145
2146 for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
2147 FD_ZERO(&rfd);
2148 FD_SET(ttyfd, &rfd);
2149 FD_SET(xfd, &rfd);
2150
2151 if (XPending(xw.dpy))
2152 timeout = 0; /* existing events might not set xfd */
2153
2154 seltv.tv_sec = timeout / 1E3;
2155 seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
2156 tv = timeout >= 0 ? &seltv : NULL;
2157
2158 if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
2159 if (errno == EINTR)
2160 continue;
2161 die("select failed: %s\n", strerror(errno));
2162 }
2163 clock_gettime(CLOCK_MONOTONIC, &now);
2164
2165 if (FD_ISSET(ttyfd, &rfd))
2166 ttyread();
2167
2168 xev = 0;
2169 while (XPending(xw.dpy)) {
2170 xev = 1;
2171 XNextEvent(xw.dpy, &ev);
2172 if (XFilterEvent(&ev, None))
2173 continue;
2174 if (handler[ev.type])
2175 (handler[ev.type])(&ev);
2176 }
2177
2178 /*
2179 * To reduce flicker and tearing, when new content or event
2180 * triggers drawing, we first wait a bit to ensure we got
2181 * everything, and if nothing new arrives - we draw.
2182 * We start with trying to wait minlatency ms. If more content
2183 * arrives sooner, we retry with shorter and shorter periods,
2184 * and eventually draw even without idle after maxlatency ms.
2185 * Typically this results in low latency while interacting,
2186 * maximum latency intervals during `cat huge.txt`, and perfect
2187 * sync with periodic updates from animations/key-repeats/etc.
2188 */
2189 if (FD_ISSET(ttyfd, &rfd) || xev) {
2190 if (!drawing) {
2191 trigger = now;
2192 drawing = 1;
2193 }
2194 timeout = (maxlatency - TIMEDIFF(now, trigger)) \
2195 / maxlatency * minlatency;
2196 if (timeout > 0)
2197 continue; /* we have time, try to find idle */
2198 }
2199
2200 /* idle detected or maxlatency exhausted -> draw */
2201 timeout = -1;
2202 if (blinktimeout && tattrset(ATTR_BLINK)) {
2203 timeout = blinktimeout - TIMEDIFF(now, lastblink);
2204 if (timeout <= 0) {
2205 if (-timeout > blinktimeout) /* start visible */
2206 win.mode |= MODE_BLINK;
2207 win.mode ^= MODE_BLINK;
2208 tsetdirtattr(ATTR_BLINK);
2209 lastblink = now;
2210 timeout = blinktimeout;
2211 }
2212 }
2213
2214 draw();
2215 XFlush(xw.dpy);
2216 drawing = 0;
2217 }
2218}
2219
2220int
2221resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
2222{
2223 char **sdst = dst;
2224 int *idst = dst;
2225 float *fdst = dst;
2226
2227 char fullname[256];
2228 char fullclass[256];
2229 char *type;
2230 XrmValue ret;
2231
2232 snprintf(fullname, sizeof(fullname), "%s.%s",
2233 opt_name ? opt_name : "st", name);
2234 snprintf(fullclass, sizeof(fullclass), "%s.%s",
2235 opt_class ? opt_class : "St", name);
2236 fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
2237
2238 XrmGetResource(db, fullname, fullclass, &type, &ret);
2239 if (ret.addr == NULL || strncmp("String", type, 64))
2240 return 1;
2241
2242 switch (rtype) {
2243 case STRING:
2244 *sdst = ret.addr;
2245 break;
2246 case INTEGER:
2247 *idst = strtoul(ret.addr, NULL, 10);
2248 break;
2249 case FLOAT:
2250 *fdst = strtof(ret.addr, NULL);
2251 break;
2252 }
2253 return 0;
2254}
2255
2256void
2257config_init(void)
2258{
2259 char *resm;
2260 XrmDatabase db;
2261 ResourcePref *p;
2262
2263 XrmInitialize();
2264 resm = XResourceManagerString(xw.dpy);
2265 if (!resm)
2266 return;
2267
2268 db = XrmGetStringDatabase(resm);
2269 for (p = resources; p < resources + LEN(resources); p++)
2270 resource_load(db, p->name, p->type, p->dst);
2271}
2272
2273void
2274usage(void)
2275{
2276 die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]"
2277 " [-n name] [-o file]\n"
2278 " [-T title] [-t title] [-w windowid]"
2279 " [[-e] command [args ...]]\n"
2280 " %s [-aiv] [-c class] [-f font] [-g geometry]"
2281 " [-n name] [-o file]\n"
2282 " [-T title] [-t title] [-w windowid] -l line"
2283 " [stty_args ...]\n", argv0, argv0);
2284}
2285
2286int
2287main(int argc, char *argv[])
2288{
2289 xw.l = xw.t = 0;
2290 xw.isfixed = False;
2291 xsetcursor(cursorshape);
2292
2293 ARGBEGIN {
2294 case 'a':
2295 allowaltscreen = 0;
2296 break;
2297 case 'A':
2298 opt_alpha = EARGF(usage());
2299 break;
2300 case 'c':
2301 opt_class = EARGF(usage());
2302 break;
2303 case 'e':
2304 if (argc > 0)
2305 --argc, ++argv;
2306 goto run;
2307 case 'f':
2308 opt_font = EARGF(usage());
2309 break;
2310 case 'g':
2311 xw.gm = XParseGeometry(EARGF(usage()),
2312 &xw.l, &xw.t, &cols, &rows);
2313 break;
2314 case 'i':
2315 xw.isfixed = 1;
2316 break;
2317 case 'o':
2318 opt_io = EARGF(usage());
2319 break;
2320 case 'l':
2321 opt_line = EARGF(usage());
2322 break;
2323 case 'n':
2324 opt_name = EARGF(usage());
2325 break;
2326 case 't':
2327 case 'T':
2328 opt_title = EARGF(usage());
2329 break;
2330 case 'w':
2331 opt_embed = EARGF(usage());
2332 break;
2333 case 'v':
2334 die("%s " VERSION "\n", argv0);
2335 break;
2336 default:
2337 usage();
2338 } ARGEND;
2339
2340run:
2341 if (argc > 0) /* eat all remaining arguments */
2342 opt_cmd = argv;
2343
2344 if (!opt_title)
2345 opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];
2346
2347 setlocale(LC_CTYPE, "");
2348 XSetLocaleModifiers("");
2349
2350 if(!(xw.dpy = XOpenDisplay(NULL)))
2351 die("Can't open display\n");
2352
2353 config_init();
2354 cols = MAX(cols, 1);
2355 rows = MAX(rows, 1);
2356 defaultbg = MAX(LEN(colorname), 256);
2357 alphaUnfocus = alpha-alphaOffset;
2358 tnew(cols, rows);
2359 xinit(cols, rows);
2360 xsetenv();
2361 selinit();
2362 run();
2363
2364 return 0;
2365}
2366