diff options
-rw-r--r-- | LICENSE | 34 | ||||
-rw-r--r-- | Makefile | 62 | ||||
-rw-r--r-- | README.md | 39 | ||||
-rw-r--r-- | Xdefaults | 128 | ||||
-rw-r--r-- | arg.h | 50 | ||||
-rw-r--r-- | boxdraw.c | 194 | ||||
-rw-r--r-- | boxdraw_data.h | 214 | ||||
-rw-r--r-- | config.h | 557 | ||||
-rw-r--r-- | config.mk | 37 | ||||
-rw-r--r-- | hb.c | 154 | ||||
-rw-r--r-- | hb.h | 7 | ||||
-rwxr-xr-x | st-copyout | 13 | ||||
-rwxr-xr-x | st-urlhandler | 19 | ||||
-rw-r--r-- | st.1 | 193 | ||||
-rw-r--r-- | st.c | 2816 | ||||
-rw-r--r-- | st.h | 146 | ||||
-rw-r--r-- | st.info | 239 | ||||
-rw-r--r-- | win.h | 42 | ||||
-rw-r--r-- | x.c | 2366 |
19 files changed, 7310 insertions, 0 deletions
@@ -0,0 +1,34 @@ | |||
1 | MIT/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 | |||
18 | Permission is hereby granted, free of charge, to any person obtaining a | ||
19 | copy of this software and associated documentation files (the "Software"), | ||
20 | to deal in the Software without restriction, including without limitation | ||
21 | the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
22 | and/or sell copies of the Software, and to permit persons to whom the | ||
23 | Software is furnished to do so, subject to the following conditions: | ||
24 | |||
25 | The above copyright notice and this permission notice shall be included in | ||
26 | all copies or substantial portions of the Software. | ||
27 | |||
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||
31 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
34 | DEALINGS 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 | |||
5 | include config.mk | ||
6 | |||
7 | SRC = st.c x.c boxdraw.c hb.c | ||
8 | OBJ = $(SRC:.c=.o) | ||
9 | |||
10 | all: options st | ||
11 | |||
12 | options: | ||
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 | |||
21 | st.o: config.h st.h win.h | ||
22 | x.o: arg.h config.h st.h win.h hb.h | ||
23 | hb.o: st.h | ||
24 | boxdraw.o: config.h st.h boxdraw_data.h | ||
25 | |||
26 | $(OBJ): config.h config.mk | ||
27 | |||
28 | st: $(OBJ) | ||
29 | $(CC) -o $@ $(OBJ) $(STLDFLAGS) | ||
30 | |||
31 | clean: | ||
32 | rm -f st $(OBJ) st-$(VERSION).tar.gz *.rej *.orig *.o | ||
33 | |||
34 | dist: 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 | |||
42 | install: 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 | |||
56 | uninstall: | ||
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 | |||
3 | Forked 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 | ``` | ||
35 | apt install fontconfig libx11-dev libxft-dev libharfbuzz-dev build-essential | ||
36 | git clone https://gitea.chudnick.com/sam/st | ||
37 | cd st | ||
38 | sudo 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): | ||
2 | st.alpha: 0.92 | ||
3 | st.alphaOffset: 0.3 | ||
4 | |||
5 | !! Set a default font and font size as below: | ||
6 | st.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 */ | ||
@@ -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 | |||
9 | extern 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 | |||
13 | static Display *xdpy; | ||
14 | static Colormap xcmap; | ||
15 | static XftDraw *xd; | ||
16 | static Visual *xvis; | ||
17 | |||
18 | static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort); | ||
19 | static void drawboxlines(int, int, int, int, XftColor *, ushort); | ||
20 | |||
21 | /* public API */ | ||
22 | |||
23 | void | ||
24 | boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis) | ||
25 | { | ||
26 | xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis; | ||
27 | } | ||
28 | |||
29 | int | ||
30 | isboxdraw(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 */ | ||
38 | ushort | ||
39 | boxdrawindex(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 | |||
48 | void | ||
49 | drawboxes(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 | |||
58 | void | ||
59 | drawbox(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 | |||
127 | void | ||
128 | drawboxlines(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 */ | ||
63 | static 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 | */ | ||
8 | static char *font = "mono:pixelsize=16:antialias=true:autohint=true"; | ||
9 | static char *font2[] = { "NotoColorEmoji:pixelsize=10:antialias=true:autohint=true" }; | ||
10 | static 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 | */ | ||
20 | static char *shell = "/bin/sh"; | ||
21 | char *utmp = NULL; | ||
22 | /* scroll program: to enable use a string like "scroll" */ | ||
23 | char *scroll = NULL; | ||
24 | char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; | ||
25 | |||
26 | /* identification sequence returned in DA and DECID */ | ||
27 | char *vtiden = "\033[?6c"; | ||
28 | |||
29 | /* Kerning / character bounding-box multipliers */ | ||
30 | static float cwscale = 1.0; | ||
31 | static float chscale = 1.0; | ||
32 | |||
33 | /* | ||
34 | * word delimiter string | ||
35 | * | ||
36 | * More advanced example: L" `'\"()[]{}" | ||
37 | */ | ||
38 | wchar_t *worddelimiters = L" "; | ||
39 | |||
40 | /* selection timeouts (in milliseconds) */ | ||
41 | static unsigned int doubleclicktimeout = 300; | ||
42 | static unsigned int tripleclicktimeout = 600; | ||
43 | |||
44 | /* alt screens */ | ||
45 | int allowaltscreen = 1; | ||
46 | |||
47 | /* allow certain non-interactive (insecure) window operations such as: | ||
48 | setting the clipboard text */ | ||
49 | int 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 | */ | ||
57 | static double minlatency = 8; | ||
58 | static double maxlatency = 33; | ||
59 | |||
60 | /* | ||
61 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking | ||
62 | * attribute. | ||
63 | */ | ||
64 | static unsigned int blinktimeout = 800; | ||
65 | |||
66 | /* | ||
67 | * thickness of underline and bar cursors | ||
68 | */ | ||
69 | static 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 | */ | ||
77 | const int boxdraw = 1; | ||
78 | const int boxdraw_bold = 0; | ||
79 | |||
80 | /* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ | ||
81 | const 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 | */ | ||
87 | static int bellvolume = 0; | ||
88 | |||
89 | /* default TERM value */ | ||
90 | char *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 | */ | ||
107 | unsigned int tabspaces = 8; | ||
108 | |||
109 | /* bg opacity */ | ||
110 | float alpha = 0.8; | ||
111 | float alphaOffset = 0.0; | ||
112 | float alphaUnfocus; | ||
113 | |||
114 | /* Terminal colors (16 first used in escape sequence) */ | ||
115 | static 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 | */ | ||
145 | unsigned int defaultfg = 259; | ||
146 | unsigned int defaultbg = 258; | ||
147 | unsigned int defaultcs = 256; | ||
148 | unsigned int defaultrcs = 257; | ||
149 | unsigned int background = 258; | ||
150 | |||
151 | /* | ||
152 | * Default shape of cursor | ||
153 | * 2: Block ("█") | ||
154 | * 4: Underline ("_") | ||
155 | * 6: Bar ("|") | ||
156 | * 7: Snowman ("☃") | ||
157 | */ | ||
158 | static unsigned int cursorshape = 2; | ||
159 | |||
160 | /* | ||
161 | * Default columns and rows numbers | ||
162 | */ | ||
163 | |||
164 | static unsigned int cols = 80; | ||
165 | static unsigned int rows = 24; | ||
166 | |||
167 | /* | ||
168 | * Default colour and shape of the mouse cursor | ||
169 | */ | ||
170 | static unsigned int mouseshape = XC_xterm; | ||
171 | static unsigned int mousefg = 7; | ||
172 | static 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 | */ | ||
178 | static 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 | */ | ||
185 | static uint forcemousemod = ShiftMask; | ||
186 | |||
187 | /* | ||
188 | * Xresources preferences to load at startup | ||
189 | */ | ||
190 | ResourcePref 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 | */ | ||
230 | static 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 | |||
245 | static char *openurlcmd[] = { "/bin/sh", "-c", "st-urlhandler -o", "externalpipe", NULL }; | ||
246 | static char *copyurlcmd[] = { "/bin/sh", "-c", "st-urlhandler -c", "externalpipe", NULL }; | ||
247 | static char *copyoutput[] = { "/bin/sh", "-c", "st-copyout", "externalpipe", NULL }; | ||
248 | |||
249 | static 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 | */ | ||
313 | static 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 | */ | ||
319 | static 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 | */ | ||
325 | static 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 | */ | ||
545 | static 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 | */ | ||
553 | static 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 | ||
2 | VERSION = 0.8.5 | ||
3 | |||
4 | # Customize below to fit your system | ||
5 | |||
6 | # paths | ||
7 | PREFIX = /usr/local | ||
8 | MANPREFIX = $(PREFIX)/share/man | ||
9 | |||
10 | X11INC = /usr/X11R6/include | ||
11 | X11LIB = /usr/X11R6/lib | ||
12 | |||
13 | PKG_CONFIG = pkg-config | ||
14 | |||
15 | # includes and libs | ||
16 | INCS = -I$(X11INC) \ | ||
17 | `$(PKG_CONFIG) --cflags fontconfig` \ | ||
18 | `$(PKG_CONFIG) --cflags freetype2` \ | ||
19 | `$(PKG_CONFIG) --cflags harfbuzz` | ||
20 | LIBS = -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 | ||
26 | STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 | ||
27 | STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) | ||
28 | STLDFLAGS = $(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 | ||
@@ -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 | |||
21 | hb_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 | |||
24 | void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length); | ||
25 | hb_font_t *hbfindfont(XftFont *match); | ||
26 | |||
27 | typedef struct { | ||
28 | XftFont *match; | ||
29 | hb_font_t *font; | ||
30 | } HbFontMatch; | ||
31 | |||
32 | static int hbfontslen = 0; | ||
33 | static HbFontMatch *hbfontcache = NULL; | ||
34 | |||
35 | void | ||
36 | hbunloadfonts() | ||
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 | |||
50 | hb_font_t * | ||
51 | hbfindfont(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 | |||
72 | void | ||
73 | hbtransform(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 | |||
119 | void | ||
120 | hbtransformsegment(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 | } | ||
@@ -0,0 +1,7 @@ | |||
1 | #include <X11/Xft/Xft.h> | ||
2 | #include <hb.h> | ||
3 | #include <hb-ft.h> | ||
4 | |||
5 | void hbunloadfonts(); | ||
6 | void 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 | ||
6 | tmpfile=$(mktemp /tmp/st-cmd-output.XXXXXX) | ||
7 | trap 'rm "$tmpfile"' 0 1 15 | ||
8 | sed -n "w $tmpfile" | ||
9 | sed -i 's/\x0//g' "$tmpfile" | ||
10 | ps1="$(grep "\S" "$tmpfile" | tail -n 1 | sed 's/^\s*//' | cut -d' ' -f1)" | ||
11 | chosen="$(grep -F "$ps1" "$tmpfile" | sed '$ d' | tac | dmenu -p "Copy which command's output?" -i -l 10 | sed 's/[^^]/[&]/g; s/\^/\\^/g')" | ||
12 | eps1="$(echo "$ps1" | sed 's/[^^]/[&]/g; s/\^/\\^/g')" | ||
13 | awk "/^$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 | |||
3 | urlregex="(((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 | |||
5 | urls="$(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 | |||
13 | while 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 ;; | ||
19 | esac done | ||
@@ -0,0 +1,193 @@ | |||
1 | .TH ST 1 st\-VERSION | ||
2 | .SH NAME | ||
3 | st \- 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 | ||
52 | is a simple terminal emulator. | ||
53 | .SH OPTIONS | ||
54 | .TP | ||
55 | .B \-a | ||
56 | disable alternate screens in terminal | ||
57 | .TP | ||
58 | .BI \-c " class" | ||
59 | defines the window class (default $TERM). | ||
60 | .TP | ||
61 | .BI \-f " font" | ||
62 | defines the | ||
63 | .I font | ||
64 | to use when st is run. | ||
65 | .TP | ||
66 | .BI \-g " geometry" | ||
67 | defines the X11 geometry string. | ||
68 | The form is [=][<cols>{xX}<rows>][{+-}<xoffset>{+-}<yoffset>]. See | ||
69 | .BR XParseGeometry (3) | ||
70 | for further details. | ||
71 | .TP | ||
72 | .B \-i | ||
73 | will fixate the position given with the -g option. | ||
74 | .TP | ||
75 | .BI \-n " name" | ||
76 | defines the window instance name (default $TERM). | ||
77 | .TP | ||
78 | .BI \-o " iofile" | ||
79 | writes all the I/O to | ||
80 | .I iofile. | ||
81 | This feature is useful when recording st sessions. A value of "-" means | ||
82 | standard output. | ||
83 | .TP | ||
84 | .BI \-T " title" | ||
85 | defines the window title (default 'st'). | ||
86 | .TP | ||
87 | .BI \-t " title" | ||
88 | defines the window title (default 'st'). | ||
89 | .TP | ||
90 | .BI \-w " windowid" | ||
91 | embeds st within the window identified by | ||
92 | .I windowid | ||
93 | .TP | ||
94 | .BI \-l " line" | ||
95 | use a tty | ||
96 | .I line | ||
97 | instead of a pseudo terminal. | ||
98 | .I line | ||
99 | should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port | ||
100 | 0). | ||
101 | When this flag is given | ||
102 | remaining arguments are used as flags for | ||
103 | .BR stty(1). | ||
104 | By default st initializes the serial line to 8 bits, no parity, 1 stop bit | ||
105 | and 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) | ||
108 | flags. 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 | ||
110 | example 'st -l /dev/ttyS0 cs7 115200'. See | ||
111 | .BR stty(1) | ||
112 | for more arguments and cases. | ||
113 | .TP | ||
114 | .B \-v | ||
115 | prints version information to stderr, then exits. | ||
116 | .TP | ||
117 | .BI \-e " command " [ " arguments " "... ]" | ||
118 | st executes | ||
119 | .I command | ||
120 | instead of the shell. If this is used it | ||
121 | .B must be the last option | ||
122 | on the command line, as in xterm / rxvt. | ||
123 | This option is only intended for compatibility, | ||
124 | and all the remaining arguments are used as a command | ||
125 | even without it. | ||
126 | .SH SHORTCUTS | ||
127 | .TP | ||
128 | .B Alt-j/k or Alt-Up/Down or Alt-Mouse Wheel | ||
129 | Scroll up/down one line at a time. | ||
130 | .TP | ||
131 | .B Alt-u/d or Alt-Page Up/Page Down | ||
132 | Scroll 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 | ||
135 | Increase or decrease font size. | ||
136 | .TP | ||
137 | .B Alt-Home | ||
138 | Reset to default font size. | ||
139 | .TP | ||
140 | .B Shift-Insert or Alt-v | ||
141 | Paste from clipboard. | ||
142 | .TP | ||
143 | .B Alt-c | ||
144 | Copy to clipboard. | ||
145 | .TP | ||
146 | .B Alt-p | ||
147 | Paste/input primary selection. | ||
148 | .TP | ||
149 | .B Alt-l | ||
150 | Show dmenu menu of all URLs on screen and choose one to open. | ||
151 | .TP | ||
152 | .B Alt-y | ||
153 | Show dmenu menu of all URLs on screen and choose one to copy. | ||
154 | .TP | ||
155 | .B Alt-o | ||
156 | Show dmenu menu of all recently run commands and copy the output of the chosen command to the clipboard. | ||
157 | .I xclip | ||
158 | required. | ||
159 | .TP | ||
160 | .B Alt-a/s | ||
161 | Increase or decrease opacity/alpha value (make window more or less transparent). | ||
162 | .TP | ||
163 | .B Break | ||
164 | Send a break in the serial line. | ||
165 | Break key is obtained in PC keyboards | ||
166 | pressing at the same time control and pause. | ||
167 | .TP | ||
168 | .B Ctrl-Print Screen | ||
169 | Toggle if st should print to the | ||
170 | .I iofile. | ||
171 | .TP | ||
172 | .B Shift-Print Screen | ||
173 | Print the full screen to the | ||
174 | .I iofile. | ||
175 | .TP | ||
176 | .B Print Screen | ||
177 | Print the selection to the | ||
178 | .I iofile. | ||
179 | .SH CUSTOMIZATION | ||
180 | .B st | ||
181 | can be customized by creating a custom config.h and (re)compiling the source | ||
182 | code. This keeps it fast, secure and simple. | ||
183 | .SH AUTHORS | ||
184 | See the LICENSE file for the authors. | ||
185 | .SH LICENSE | ||
186 | See 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 | ||
193 | See the TODO file in the distribution. | ||
@@ -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 | |||
51 | enum 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 | |||
61 | enum cursor_movement { | ||
62 | CURSOR_SAVE, | ||
63 | CURSOR_LOAD | ||
64 | }; | ||
65 | |||
66 | enum cursor_state { | ||
67 | CURSOR_DEFAULT = 0, | ||
68 | CURSOR_WRAPNEXT = 1, | ||
69 | CURSOR_ORIGIN = 2 | ||
70 | }; | ||
71 | |||
72 | enum charset { | ||
73 | CS_GRAPHIC0, | ||
74 | CS_GRAPHIC1, | ||
75 | CS_UK, | ||
76 | CS_USA, | ||
77 | CS_MULTI, | ||
78 | CS_GER, | ||
79 | CS_FIN | ||
80 | }; | ||
81 | |||
82 | enum 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 | |||
92 | typedef struct { | ||
93 | Glyph attr; /* current char attributes */ | ||
94 | int x; | ||
95 | int y; | ||
96 | char state; | ||
97 | } TCursor; | ||
98 | |||
99 | typedef 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 */ | ||
118 | typedef 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>]] */ | ||
144 | typedef 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 '\' */ | ||
155 | typedef 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 | |||
164 | static void execsh(char *, char **); | ||
165 | static void stty(char **); | ||
166 | static void sigchld(int); | ||
167 | static void ttywriteraw(const char *, size_t); | ||
168 | |||
169 | static void csidump(void); | ||
170 | static void csihandle(void); | ||
171 | static void csiparse(void); | ||
172 | static void csireset(void); | ||
173 | static void osc_color_response(int, int, int); | ||
174 | static int eschandle(uchar); | ||
175 | static void strdump(void); | ||
176 | static void strhandle(void); | ||
177 | static void strparse(void); | ||
178 | static void strreset(void); | ||
179 | |||
180 | static void tprinter(char *, size_t); | ||
181 | static void tdumpsel(void); | ||
182 | static void tdumpline(int); | ||
183 | static void tdump(void); | ||
184 | static void tclearregion(int, int, int, int); | ||
185 | static void tcursor(int); | ||
186 | static void tdeletechar(int); | ||
187 | static void tdeleteline(int); | ||
188 | static void tinsertblank(int); | ||
189 | static void tinsertblankline(int); | ||
190 | static int tlinelen(int); | ||
191 | static void tmoveto(int, int); | ||
192 | static void tmoveato(int, int); | ||
193 | static void tnewline(int); | ||
194 | static void tputtab(int); | ||
195 | static void tputc(Rune); | ||
196 | static void treset(void); | ||
197 | static void tscrollup(int, int, int); | ||
198 | static void tscrolldown(int, int, int); | ||
199 | static void tsetattr(const int *, int); | ||
200 | static void tsetchar(Rune, const Glyph *, int, int); | ||
201 | static void tsetdirt(int, int); | ||
202 | static void tsetscroll(int, int); | ||
203 | static void tswapscreen(void); | ||
204 | static void tsetmode(int, int, const int *, int); | ||
205 | static int twrite(const char *, int, int); | ||
206 | static void tcontrolcode(uchar ); | ||
207 | static void tdectest(char ); | ||
208 | static void tdefutf8(char); | ||
209 | static int32_t tdefcolor(const int *, int *, int); | ||
210 | static void tdeftran(char); | ||
211 | static void tstrsequence(uchar); | ||
212 | |||
213 | static void drawregion(int, int, int, int); | ||
214 | |||
215 | static void selnormalize(void); | ||
216 | static void selscroll(int, int); | ||
217 | static void selsnap(int *, int *, int); | ||
218 | |||
219 | static size_t utf8decode(const char *, Rune *, size_t); | ||
220 | static Rune utf8decodebyte(char, size_t *); | ||
221 | static char utf8encodebyte(Rune, size_t); | ||
222 | static size_t utf8validate(Rune *, size_t); | ||
223 | |||
224 | static char *base64dec(const char *); | ||
225 | static char base64dec_getc(const char **); | ||
226 | |||
227 | static ssize_t xwrite(int, const char *, size_t); | ||
228 | |||
229 | /* Globals */ | ||
230 | static Term term; | ||
231 | static Selection sel; | ||
232 | static CSIEscape csiescseq; | ||
233 | static STREscape strescseq; | ||
234 | static int iofd = 1; | ||
235 | static int cmdfd; | ||
236 | static pid_t pid; | ||
237 | |||
238 | static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; | ||
239 | static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; | ||
240 | static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; | ||
241 | static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; | ||
242 | |||
243 | ssize_t | ||
244 | xwrite(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 | |||
260 | void * | ||
261 | xmalloc(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 | |||
271 | void * | ||
272 | xrealloc(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 | |||
280 | char * | ||
281 | xstrdup(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 | |||
293 | size_t | ||
294 | utf8decode(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 | |||
318 | Rune | ||
319 | utf8decodebyte(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 | |||
328 | size_t | ||
329 | utf8encode(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 | |||
346 | char | ||
347 | utf8encodebyte(Rune u, size_t i) | ||
348 | { | ||
349 | return utfbyte[i] | (u & ~utfmask[i]); | ||
350 | } | ||
351 | |||
352 | size_t | ||
353 | utf8validate(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 | |||
363 | char | ||
364 | base64dec_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 | |||
371 | char * | ||
372 | base64dec(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 | |||
409 | void | ||
410 | selinit(void) | ||
411 | { | ||
412 | sel.mode = SEL_IDLE; | ||
413 | sel.snap = 0; | ||
414 | sel.ob.x = -1; | ||
415 | } | ||
416 | |||
417 | int | ||
418 | tlinelen(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 | |||
431 | int | ||
432 | tlinehistlen(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 | |||
445 | void | ||
446 | selstart(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 | |||
462 | void | ||
463 | selextend(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 | |||
492 | void | ||
493 | selnormalize(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 | |||
520 | int | ||
521 | selected(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 | |||
536 | void | ||
537 | selsnap(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 | |||
609 | char * | ||
610 | getsel(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 | |||
664 | void | ||
665 | selclear(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 | |||
674 | void | ||
675 | die(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 | |||
685 | void | ||
686 | execsh(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 | |||
737 | void | ||
738 | sigchld(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 | |||
756 | void | ||
757 | stty(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 | |||
780 | int | ||
781 | ttynew(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 | |||
842 | size_t | ||
843 | ttyread(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 | |||
868 | void | ||
869 | ttywrite(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 | |||
899 | void | ||
900 | ttywriteraw(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 | |||
952 | write_error: | ||
953 | die("write error on tty: %s\n", strerror(errno)); | ||
954 | } | ||
955 | |||
956 | void | ||
957 | ttyresize(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 | |||
969 | void | ||
970 | ttyhangup(void) | ||
971 | { | ||
972 | /* Send SIGHUP to shell */ | ||
973 | kill(pid, SIGHUP); | ||
974 | } | ||
975 | |||
976 | int | ||
977 | tattrset(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 | |||
991 | void | ||
992 | tsetdirt(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 | |||
1003 | void | ||
1004 | tsetdirtattr(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 | |||
1018 | void | ||
1019 | tfulldirt(void) | ||
1020 | { | ||
1021 | tsetdirt(0, term.row-1); | ||
1022 | } | ||
1023 | |||
1024 | void | ||
1025 | tcursor(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 | |||
1038 | void | ||
1039 | treset(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 | |||
1066 | void | ||
1067 | tnew(int col, int row) | ||
1068 | { | ||
1069 | term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; | ||
1070 | tresize(col, row); | ||
1071 | treset(); | ||
1072 | } | ||
1073 | |||
1074 | void | ||
1075 | tswapscreen(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 | |||
1085 | void | ||
1086 | kscrolldown(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 | |||
1103 | void | ||
1104 | kscrollup(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 | |||
1118 | void | ||
1119 | tscrolldown(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 | |||
1146 | void | ||
1147 | tscrollup(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 | |||
1177 | void | ||
1178 | selscroll(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 | |||
1197 | void | ||
1198 | tnewline(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 | |||
1210 | void | ||
1211 | csiparse(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 */ | ||
1241 | void | ||
1242 | tmoveato(int x, int y) | ||
1243 | { | ||
1244 | tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); | ||
1245 | } | ||
1246 | |||
1247 | void | ||
1248 | tmoveto(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 | |||
1264 | void | ||
1265 | tsetchar(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 | |||
1303 | void | ||
1304 | tclearregion(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 | |||
1333 | void | ||
1334 | tdeletechar(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 | |||
1350 | void | ||
1351 | tinsertblank(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 | |||
1367 | void | ||
1368 | tinsertblankline(int n) | ||
1369 | { | ||
1370 | if (BETWEEN(term.c.y, term.top, term.bot)) | ||
1371 | tscrolldown(term.c.y, n, 0); | ||
1372 | } | ||
1373 | |||
1374 | void | ||
1375 | tdeleteline(int n) | ||
1376 | { | ||
1377 | if (BETWEEN(term.c.y, term.top, term.bot)) | ||
1378 | tscrollup(term.c.y, n, 0); | ||
1379 | } | ||
1380 | |||
1381 | int32_t | ||
1382 | tdefcolor(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 | |||
1431 | void | ||
1432 | tsetattr(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 | |||
1533 | void | ||
1534 | tsetscroll(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 | |||
1549 | void | ||
1550 | tsetmode(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 | |||
1679 | void | ||
1680 | csihandle(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 | |||
1891 | void | ||
1892 | csidump(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 | |||
1915 | void | ||
1916 | csireset(void) | ||
1917 | { | ||
1918 | memset(&csiescseq, 0, sizeof(csiescseq)); | ||
1919 | } | ||
1920 | |||
1921 | void | ||
1922 | osc_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 | |||
1946 | void | ||
1947 | strhandle(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 | |||
2045 | void | ||
2046 | strparse(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 | |||
2067 | void | ||
2068 | externalpipe(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 | |||
2122 | void | ||
2123 | strdump(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 | |||
2149 | void | ||
2150 | strreset(void) | ||
2151 | { | ||
2152 | strescseq = (STREscape){ | ||
2153 | .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), | ||
2154 | .siz = STR_BUF_SIZ, | ||
2155 | }; | ||
2156 | } | ||
2157 | |||
2158 | void | ||
2159 | sendbreak(const Arg *arg) | ||
2160 | { | ||
2161 | if (tcsendbreak(cmdfd, 0)) | ||
2162 | perror("Error sending break"); | ||
2163 | } | ||
2164 | |||
2165 | void | ||
2166 | tprinter(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 | |||
2175 | void | ||
2176 | toggleprinter(const Arg *arg) | ||
2177 | { | ||
2178 | term.mode ^= MODE_PRINT; | ||
2179 | } | ||
2180 | |||
2181 | void | ||
2182 | printscreen(const Arg *arg) | ||
2183 | { | ||
2184 | tdump(); | ||
2185 | } | ||
2186 | |||
2187 | void | ||
2188 | printsel(const Arg *arg) | ||
2189 | { | ||
2190 | tdumpsel(); | ||
2191 | } | ||
2192 | |||
2193 | void | ||
2194 | tdumpsel(void) | ||
2195 | { | ||
2196 | char *ptr; | ||
2197 | |||
2198 | if ((ptr = getsel())) { | ||
2199 | tprinter(ptr, strlen(ptr)); | ||
2200 | free(ptr); | ||
2201 | } | ||
2202 | } | ||
2203 | |||
2204 | void | ||
2205 | tdumpline(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 | |||
2219 | void | ||
2220 | tdump(void) | ||
2221 | { | ||
2222 | int i; | ||
2223 | |||
2224 | for (i = 0; i < term.row; ++i) | ||
2225 | tdumpline(i); | ||
2226 | } | ||
2227 | |||
2228 | void | ||
2229 | tputtab(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 | |||
2245 | void | ||
2246 | tdefutf8(char ascii) | ||
2247 | { | ||
2248 | if (ascii == 'G') | ||
2249 | term.mode |= MODE_UTF8; | ||
2250 | else if (ascii == '@') | ||
2251 | term.mode &= ~MODE_UTF8; | ||
2252 | } | ||
2253 | |||
2254 | void | ||
2255 | tdeftran(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 | |||
2268 | void | ||
2269 | tdectest(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 | |||
2281 | void | ||
2282 | tstrsequence(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 | |||
2303 | void | ||
2304 | tcontrolcode(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 | */ | ||
2404 | int | ||
2405 | eschandle(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 | |||
2487 | void | ||
2488 | tputc(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 | |||
2547 | check_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 | |||
2629 | int | ||
2630 | twrite(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 | |||
2661 | void | ||
2662 | tresize(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 | |||
2759 | void | ||
2760 | resettitle(void) | ||
2761 | { | ||
2762 | xsettitle(NULL); | ||
2763 | } | ||
2764 | |||
2765 | void | ||
2766 | drawregion(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 | |||
2779 | void | ||
2780 | draw(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 | |||
2810 | void | ||
2811 | redraw(void) | ||
2812 | { | ||
2813 | tfulldirt(); | ||
2814 | draw(); | ||
2815 | } | ||
2816 | |||
@@ -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 | |||
24 | enum 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 | |||
42 | enum selection_mode { | ||
43 | SEL_IDLE = 0, | ||
44 | SEL_EMPTY = 1, | ||
45 | SEL_READY = 2 | ||
46 | }; | ||
47 | |||
48 | enum selection_type { | ||
49 | SEL_REGULAR = 1, | ||
50 | SEL_RECTANGULAR = 2 | ||
51 | }; | ||
52 | |||
53 | enum selection_snap { | ||
54 | SNAP_WORD = 1, | ||
55 | SNAP_LINE = 2 | ||
56 | }; | ||
57 | |||
58 | typedef unsigned char uchar; | ||
59 | typedef unsigned int uint; | ||
60 | typedef unsigned long ulong; | ||
61 | typedef unsigned short ushort; | ||
62 | |||
63 | typedef uint_least32_t Rune; | ||
64 | |||
65 | #define Glyph Glyph_ | ||
66 | typedef struct { | ||
67 | Rune u; /* character code */ | ||
68 | ushort mode; /* attribute flags */ | ||
69 | uint32_t fg; /* foreground */ | ||
70 | uint32_t bg; /* background */ | ||
71 | } Glyph; | ||
72 | |||
73 | typedef Glyph *Line; | ||
74 | |||
75 | typedef union { | ||
76 | int i; | ||
77 | uint ui; | ||
78 | float f; | ||
79 | const void *v; | ||
80 | const char *s; | ||
81 | } Arg; | ||
82 | |||
83 | void die(const char *, ...); | ||
84 | void redraw(void); | ||
85 | void tfulldirt(void); | ||
86 | void draw(void); | ||
87 | |||
88 | void externalpipe(const Arg *); | ||
89 | void kscrolldown(const Arg *); | ||
90 | void kscrollup(const Arg *); | ||
91 | |||
92 | void printscreen(const Arg *); | ||
93 | void printsel(const Arg *); | ||
94 | void sendbreak(const Arg *); | ||
95 | void toggleprinter(const Arg *); | ||
96 | |||
97 | int tattrset(int); | ||
98 | void tnew(int, int); | ||
99 | void tresize(int, int); | ||
100 | void tsetdirtattr(int); | ||
101 | void ttyhangup(void); | ||
102 | int ttynew(const char *, char *, const char *, char **); | ||
103 | size_t ttyread(void); | ||
104 | void ttyresize(int, int); | ||
105 | void ttywrite(const char *, size_t, int); | ||
106 | |||
107 | void resettitle(void); | ||
108 | |||
109 | void selclear(void); | ||
110 | void selinit(void); | ||
111 | void selstart(int, int, int); | ||
112 | void selextend(int, int, int, int); | ||
113 | int selected(int, int); | ||
114 | char *getsel(void); | ||
115 | |||
116 | size_t utf8encode(Rune, char *); | ||
117 | |||
118 | void *xmalloc(size_t); | ||
119 | void *xrealloc(void *, size_t); | ||
120 | char *xstrdup(const char *); | ||
121 | |||
122 | int isboxdraw(Rune); | ||
123 | ushort boxdrawindex(const Glyph *); | ||
124 | #ifdef XFT_VERSION | ||
125 | /* only exposed to x.c, otherwise we'll need Xft.h for the types */ | ||
126 | void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); | ||
127 | void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); | ||
128 | #endif | ||
129 | |||
130 | /* config.h globals */ | ||
131 | extern char *utmp; | ||
132 | extern char *scroll; | ||
133 | extern char *stty_args; | ||
134 | extern char *vtiden; | ||
135 | extern wchar_t *worddelimiters; | ||
136 | extern int allowaltscreen; | ||
137 | extern int allowwindowops; | ||
138 | extern char *termname; | ||
139 | extern unsigned int tabspaces; | ||
140 | extern unsigned int defaultfg; | ||
141 | extern unsigned int defaultbg; | ||
142 | extern float alpha; | ||
143 | extern float alphaUnfocus; | ||
144 | extern const int boxdraw, boxdraw_bold, boxdraw_braille; | ||
145 | extern unsigned int defaultcs; | ||
146 | |||
@@ -0,0 +1,239 @@ | |||
1 | st-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 | |||
195 | st| 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 | |||
204 | st-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 | |||
215 | st-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 | |||
223 | st-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 | |||
231 | st-bs| simpleterm with backspace as backspace, | ||
232 | use=st, | ||
233 | kbs=\010, | ||
234 | kdch1=\177, | ||
235 | |||
236 | st-bs-256color| simpleterm with backspace as backspace and 256colors, | ||
237 | use=st-256color, | ||
238 | kbs=\010, | ||
239 | kdch1=\177, | ||
@@ -0,0 +1,42 @@ | |||
1 | /* See LICENSE for license details. */ | ||
2 | |||
3 | enum 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 | |||
26 | void xbell(void); | ||
27 | void xclipcopy(void); | ||
28 | void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); | ||
29 | void xdrawline(Line, int, int, int); | ||
30 | void xfinishdraw(void); | ||
31 | void xloadcols(void); | ||
32 | int xsetcolorname(int, const char *); | ||
33 | int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); | ||
34 | void xseticontitle(char *); | ||
35 | void xsettitle(char *); | ||
36 | int xsetcursor(int); | ||
37 | void xsetmode(int, unsigned int); | ||
38 | void xsetpointermotion(int); | ||
39 | void xsetsel(char *); | ||
40 | int xstartdraw(void); | ||
41 | void xximspot(int, int); | ||
42 | |||
@@ -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 | |||
19 | char *argv0; | ||
20 | #include "arg.h" | ||
21 | #include "st.h" | ||
22 | #include "win.h" | ||
23 | #include "hb.h" | ||
24 | |||
25 | /* types used in config.h */ | ||
26 | typedef struct { | ||
27 | uint mod; | ||
28 | KeySym keysym; | ||
29 | void (*func)(const Arg *); | ||
30 | const Arg arg; | ||
31 | } Shortcut; | ||
32 | |||
33 | typedef struct { | ||
34 | uint mod; | ||
35 | uint button; | ||
36 | void (*func)(const Arg *); | ||
37 | const Arg arg; | ||
38 | uint release; | ||
39 | } MouseShortcut; | ||
40 | |||
41 | typedef 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 */ | ||
51 | enum resource_type { | ||
52 | STRING = 0, | ||
53 | INTEGER = 1, | ||
54 | FLOAT = 2 | ||
55 | }; | ||
56 | |||
57 | typedef 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 */ | ||
69 | static void clipcopy(const Arg *); | ||
70 | static void clippaste(const Arg *); | ||
71 | static void numlock(const Arg *); | ||
72 | static void selpaste(const Arg *); | ||
73 | static void changealpha(const Arg *); | ||
74 | static void zoom(const Arg *); | ||
75 | static void zoomabs(const Arg *); | ||
76 | static void zoomreset(const Arg *); | ||
77 | static 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 | |||
92 | typedef XftDraw *Draw; | ||
93 | typedef XftColor Color; | ||
94 | typedef XftGlyphFontSpec GlyphFontSpec; | ||
95 | |||
96 | /* Purely graphic info */ | ||
97 | typedef 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 | |||
106 | typedef 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 | |||
129 | typedef 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_ | ||
138 | typedef 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 */ | ||
153 | typedef struct { | ||
154 | Color *col; | ||
155 | size_t collen; | ||
156 | Font font, bfont, ifont, ibfont; | ||
157 | GC gc; | ||
158 | } DC; | ||
159 | |||
160 | static inline ushort sixd_to_16bit(int); | ||
161 | static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); | ||
162 | static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); | ||
163 | static void xdrawglyph(Glyph, int, int); | ||
164 | static void xclear(int, int, int, int); | ||
165 | static int xgeommasktogravity(int); | ||
166 | static int ximopen(Display *); | ||
167 | static void ximinstantiate(Display *, XPointer, XPointer); | ||
168 | static void ximdestroy(XIM, XPointer, XPointer); | ||
169 | static int xicdestroy(XIC, XPointer, XPointer); | ||
170 | static void xinit(int, int); | ||
171 | static void cresize(int, int); | ||
172 | static void xresize(int, int); | ||
173 | static void xhints(void); | ||
174 | static int xloadcolor(int, const char *, Color *); | ||
175 | static int xloadfont(Font *, FcPattern *); | ||
176 | static int xloadsparefont(FcPattern *, int); | ||
177 | static void xloadsparefonts(void); | ||
178 | static void xloadfonts(const char *, double); | ||
179 | static void xunloadfont(Font *); | ||
180 | static void xunloadfonts(void); | ||
181 | static void xsetenv(void); | ||
182 | static void xseturgency(int); | ||
183 | static int evcol(XEvent *); | ||
184 | static int evrow(XEvent *); | ||
185 | static float clamp(float, float, float); | ||
186 | |||
187 | static void expose(XEvent *); | ||
188 | static void visibility(XEvent *); | ||
189 | static void unmap(XEvent *); | ||
190 | static void kpress(XEvent *); | ||
191 | static void cmessage(XEvent *); | ||
192 | static void resize(XEvent *); | ||
193 | static void focus(XEvent *); | ||
194 | static uint buttonmask(uint); | ||
195 | static int mouseaction(XEvent *, uint); | ||
196 | static void brelease(XEvent *); | ||
197 | static void bpress(XEvent *); | ||
198 | static void bmotion(XEvent *); | ||
199 | static void propnotify(XEvent *); | ||
200 | static void selnotify(XEvent *); | ||
201 | static void selclear_(XEvent *); | ||
202 | static void selrequest(XEvent *); | ||
203 | static void setsel(char *, Time); | ||
204 | static void mousesel(XEvent *, int); | ||
205 | static void mousereport(XEvent *); | ||
206 | static char *kmap(KeySym, uint); | ||
207 | static int match(uint, uint); | ||
208 | |||
209 | static void run(void); | ||
210 | static void usage(void); | ||
211 | |||
212 | static 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 */ | ||
239 | static DC dc; | ||
240 | static XWindow xw; | ||
241 | static XSelection xsel; | ||
242 | static TermWindow win; | ||
243 | |||
244 | /* Font Ring Cache */ | ||
245 | enum { | ||
246 | FRC_NORMAL, | ||
247 | FRC_ITALIC, | ||
248 | FRC_BOLD, | ||
249 | FRC_ITALICBOLD | ||
250 | }; | ||
251 | |||
252 | typedef 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. */ | ||
259 | static Fontcache *frc = NULL; | ||
260 | static int frclen = 0; | ||
261 | static int frccap = 0; | ||
262 | static char *usedfont = NULL; | ||
263 | static double usedfontsize = 0; | ||
264 | static double defaultfontsize = 0; | ||
265 | |||
266 | static char *opt_alpha = NULL; | ||
267 | static char *opt_class = NULL; | ||
268 | static char **opt_cmd = NULL; | ||
269 | static char *opt_embed = NULL; | ||
270 | static char *opt_font = NULL; | ||
271 | static char *opt_io = NULL; | ||
272 | static char *opt_line = NULL; | ||
273 | static char *opt_name = NULL; | ||
274 | static char *opt_title = NULL; | ||
275 | |||
276 | static int focused = 0; | ||
277 | |||
278 | static int oldbutton = 3; /* button event on startup: 3 = release */ | ||
279 | static uint buttons; /* bit field of pressed buttons */ | ||
280 | |||
281 | void | ||
282 | clipcopy(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 | |||
296 | void | ||
297 | clippaste(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 | |||
306 | void | ||
307 | selpaste(const Arg *dummy) | ||
308 | { | ||
309 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, | ||
310 | xw.win, CurrentTime); | ||
311 | } | ||
312 | |||
313 | void | ||
314 | numlock(const Arg *dummy) | ||
315 | { | ||
316 | win.mode ^= MODE_NUMLOCK; | ||
317 | } | ||
318 | |||
319 | void | ||
320 | changealpha(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 | |||
331 | void | ||
332 | zoom(const Arg *arg) | ||
333 | { | ||
334 | Arg larg; | ||
335 | |||
336 | larg.f = usedfontsize + arg->f; | ||
337 | zoomabs(&larg); | ||
338 | } | ||
339 | |||
340 | void | ||
341 | zoomabs(const Arg *arg) | ||
342 | { | ||
343 | xunloadfonts(); | ||
344 | xloadfonts(usedfont, arg->f); | ||
345 | xloadsparefonts(); | ||
346 | cresize(0, 0); | ||
347 | redraw(); | ||
348 | xhints(); | ||
349 | } | ||
350 | |||
351 | void | ||
352 | zoomreset(const Arg *arg) | ||
353 | { | ||
354 | Arg larg; | ||
355 | |||
356 | if (defaultfontsize > 0) { | ||
357 | larg.f = defaultfontsize; | ||
358 | zoomabs(&larg); | ||
359 | } | ||
360 | } | ||
361 | |||
362 | void | ||
363 | ttysend(const Arg *arg) | ||
364 | { | ||
365 | ttywrite(arg->s, strlen(arg->s), 1); | ||
366 | } | ||
367 | |||
368 | int | ||
369 | evcol(XEvent *e) | ||
370 | { | ||
371 | int x = e->xbutton.x - borderpx; | ||
372 | LIMIT(x, 0, win.tw - 1); | ||
373 | return x / win.cw; | ||
374 | } | ||
375 | |||
376 | int | ||
377 | evrow(XEvent *e) | ||
378 | { | ||
379 | int y = e->xbutton.y - borderpx; | ||
380 | LIMIT(y, 0, win.th - 1); | ||
381 | return y / win.ch; | ||
382 | } | ||
383 | |||
384 | float | ||
385 | clamp(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 | |||
393 | void | ||
394 | mousesel(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 | |||
410 | void | ||
411 | mousereport(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 | |||
473 | uint | ||
474 | buttonmask(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 | |||
484 | int | ||
485 | mouseaction(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 | |||
505 | void | ||
506 | bpress(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 | |||
543 | void | ||
544 | propnotify(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 | |||
557 | void | ||
558 | selnotify(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 | |||
644 | void | ||
645 | xclipcopy(void) | ||
646 | { | ||
647 | clipcopy(NULL); | ||
648 | } | ||
649 | |||
650 | void | ||
651 | selclear_(XEvent *e) | ||
652 | { | ||
653 | selclear(); | ||
654 | } | ||
655 | |||
656 | void | ||
657 | selrequest(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 | |||
714 | void | ||
715 | setsel(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 | |||
728 | void | ||
729 | xsetsel(char *str) | ||
730 | { | ||
731 | setsel(str, CurrentTime); | ||
732 | } | ||
733 | |||
734 | void | ||
735 | brelease(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 | |||
753 | void | ||
754 | bmotion(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 | |||
764 | void | ||
765 | cresize(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 | |||
784 | void | ||
785 | xresize(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 | |||
800 | ushort | ||
801 | sixd_to_16bit(int x) | ||
802 | { | ||
803 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; | ||
804 | } | ||
805 | |||
806 | int | ||
807 | xloadcolor(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 | |||
830 | void | ||
831 | xloadalpha(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 | |||
840 | void | ||
841 | xloadcols(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 | |||
867 | int | ||
868 | xgetcolor(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 | |||
880 | int | ||
881 | xsetcolorname(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 | */ | ||
900 | void | ||
901 | xclear(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 | |||
908 | void | ||
909 | xhints(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 | |||
944 | int | ||
945 | xgeommasktogravity(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 | |||
959 | int | ||
960 | xloadfont(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 | |||
1032 | void | ||
1033 | xloadfonts(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 | |||
1102 | int | ||
1103 | xloadsparefont(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 | |||
1126 | void | ||
1127 | xloadsparefonts(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 | |||
1197 | void | ||
1198 | xunloadfont(Font *f) | ||
1199 | { | ||
1200 | XftFontClose(xw.dpy, f->match); | ||
1201 | FcPatternDestroy(f->pattern); | ||
1202 | if (f->set) | ||
1203 | FcFontSetDestroy(f->set); | ||
1204 | } | ||
1205 | |||
1206 | void | ||
1207 | xunloadfonts(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 | |||
1222 | int | ||
1223 | ximopen(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 | |||
1252 | void | ||
1253 | ximinstantiate(Display *dpy, XPointer client, XPointer call) | ||
1254 | { | ||
1255 | if (ximopen(dpy)) | ||
1256 | XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, | ||
1257 | ximinstantiate, NULL); | ||
1258 | } | ||
1259 | |||
1260 | void | ||
1261 | ximdestroy(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 | |||
1269 | int | ||
1270 | xicdestroy(XIC xim, XPointer client, XPointer call) | ||
1271 | { | ||
1272 | xw.ime.xic = NULL; | ||
1273 | return 1; | ||
1274 | } | ||
1275 | |||
1276 | void | ||
1277 | xinit(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 | |||
1400 | int | ||
1401 | xmakeglyphfontspecs(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 | |||
1540 | void | ||
1541 | xdrawglyphfontspecs(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 | |||
1679 | void | ||
1680 | xdrawglyph(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 | |||
1689 | void | ||
1690 | xdrawcursor(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 | |||
1778 | void | ||
1779 | xsetenv(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 | |||
1787 | void | ||
1788 | xseticontitle(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 | |||
1801 | void | ||
1802 | xsettitle(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 | |||
1815 | int | ||
1816 | xstartdraw(void) | ||
1817 | { | ||
1818 | return IS_SET(MODE_VISIBLE); | ||
1819 | } | ||
1820 | |||
1821 | void | ||
1822 | xdrawline(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 | |||
1852 | void | ||
1853 | xfinishdraw(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 | |||
1862 | void | ||
1863 | xximspot(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 | |||
1874 | void | ||
1875 | expose(XEvent *ev) | ||
1876 | { | ||
1877 | redraw(); | ||
1878 | } | ||
1879 | |||
1880 | void | ||
1881 | visibility(XEvent *ev) | ||
1882 | { | ||
1883 | XVisibilityEvent *e = &ev->xvisibility; | ||
1884 | |||
1885 | MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); | ||
1886 | } | ||
1887 | |||
1888 | void | ||
1889 | unmap(XEvent *ev) | ||
1890 | { | ||
1891 | win.mode &= ~MODE_VISIBLE; | ||
1892 | } | ||
1893 | |||
1894 | void | ||
1895 | xsetpointermotion(int set) | ||
1896 | { | ||
1897 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); | ||
1898 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); | ||
1899 | } | ||
1900 | |||
1901 | void | ||
1902 | xsetmode(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 | |||
1910 | int | ||
1911 | xsetcursor(int cursor) | ||
1912 | { | ||
1913 | if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ | ||
1914 | return 1; | ||
1915 | win.cursor = cursor; | ||
1916 | return 0; | ||
1917 | } | ||
1918 | |||
1919 | void | ||
1920 | xseturgency(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 | |||
1929 | void | ||
1930 | xbell(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 | |||
1938 | void | ||
1939 | focus(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 | |||
1972 | int | ||
1973 | match(uint mask, uint state) | ||
1974 | { | ||
1975 | return mask == XK_ANY_MOD || mask == (state & ~ignoremod); | ||
1976 | } | ||
1977 | |||
1978 | char* | ||
1979 | kmap(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 | |||
2015 | void | ||
2016 | kpress(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 | |||
2031 | reallocbuf: | ||
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); | ||
2083 | cleanup: | ||
2084 | if (buf) | ||
2085 | free(buf); | ||
2086 | } | ||
2087 | |||
2088 | void | ||
2089 | cmessage(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 | |||
2108 | void | ||
2109 | resize(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 | |||
2117 | void | ||
2118 | run(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 | |||
2220 | int | ||
2221 | resource_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 | |||
2256 | void | ||
2257 | config_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 | |||
2273 | void | ||
2274 | usage(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 | |||
2286 | int | ||
2287 | main(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 | |||
2340 | run: | ||
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 | |||