├── .gitignore ├── TODO ├── patches ├── st-scrollback-mouse-20191024-a2c479c.diff ├── st-w3m-0.8.3.diff ├── st-openclipboard-20190202-3be4cf1.diff ├── st-scrollback-mouse-altscreen-20200416-5703aa0.diff ├── st-alpha-20220206-0.8.5.diff ├── st-copyurl-0.8.4.diff ├── st-font2-20190416-ba72400.diff ├── st-xresources-20200604-9ba7ecf.diff ├── st-appsync-20200618-b27a383.diff ├── st-keyboard_select-20200617-9ba7ecf.diff ├── st-scrollback-20210507-4536f46.diff └── st-boxdraw_v2-0.8.3.diff ├── LEGACY ├── config.mk ├── arg.h ├── Makefile ├── win.h ├── LICENSE ├── README ├── st.h ├── st.1 ├── st.info ├── boxdraw.c ├── boxdraw_data.h ├── FAQ ├── config.def.h └── x.c /.gitignore: -------------------------------------------------------------------------------- 1 | # path: /home/klassiker/.local/share/repos/st/.gitignore 2 | # author: klassiker [mrdotx] 3 | # url: https://github.com/mrdotx/st 4 | # date: 2025-08-10T04:41:50+0200 5 | 6 | /boxdraw.o 7 | /config.h 8 | /st 9 | /st.o 10 | /x.o 11 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | vt emulation 2 | ------------ 3 | 4 | * double-height support 5 | 6 | code & interface 7 | ---------------- 8 | 9 | * add a simple way to do multiplexing 10 | 11 | drawing 12 | ------- 13 | * add diacritics support to xdraws() 14 | * switch to a suckless font drawing library 15 | * make the font cache simpler 16 | * add better support for brightening of the upper colors 17 | 18 | bugs 19 | ---- 20 | 21 | * fix shift up/down (shift selection in emacs) 22 | * remove DEC test sequence when appropriate 23 | 24 | misc 25 | ---- 26 | 27 | $ grep -nE 'XXX|TODO' st.c 28 | 29 | -------------------------------------------------------------------------------- /patches/st-scrollback-mouse-20191024-a2c479c.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index ec1b576..4b3bf15 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -163,6 +163,8 @@ static uint forcemousemod = ShiftMask; 6 | */ 7 | static MouseShortcut mshortcuts[] = { 8 | /* mask button function argument release */ 9 | + { ShiftMask, Button4, kscrollup, {.i = 1} }, 10 | + { ShiftMask, Button5, kscrolldown, {.i = 1} }, 11 | { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 12 | { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 13 | { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 14 | -------------------------------------------------------------------------------- /LEGACY: -------------------------------------------------------------------------------- 1 | A STATEMENT ON LEGACY SUPPORT 2 | 3 | In the terminal world there is much cruft that comes from old and unsup‐ 4 | ported terminals that inherit incompatible modes and escape sequences 5 | which noone is able to know, except when he/she comes from that time and 6 | developed a graphical vt100 emulator at that time. 7 | 8 | One goal of st is to only support what is really needed. When you en‐ 9 | counter a sequence which you really need, implement it. But while you 10 | are at it, do not add the other cruft you might encounter while sneek‐ 11 | ing at other terminal emulators. History has bloated them and there is 12 | no real evidence that most of the sequences are used today. 13 | 14 | 15 | Christoph Lohmann <20h@r-36.net> 16 | 2012-09-13T07:00:36.081271045+02:00 17 | 18 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # st version 2 | VERSION = 0.9.3 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 | LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ 20 | `$(PKG_CONFIG) --libs fontconfig` \ 21 | `$(PKG_CONFIG) --libs freetype2` 22 | 23 | # flags 24 | STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 25 | STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) 26 | STLDFLAGS = $(LIBS) $(LDFLAGS) 27 | 28 | # OpenBSD: 29 | #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE 30 | #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ 31 | # `$(PKG_CONFIG) --libs fontconfig` \ 32 | # `$(PKG_CONFIG) --libs freetype2` 33 | #MANPREFIX = ${PREFIX}/man 34 | 35 | # compiler and linker 36 | # CC = c99 37 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 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 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 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 8 | OBJ = $(SRC:.c=.o) 9 | 10 | all: st 11 | 12 | config.h: 13 | cp config.def.h config.h 14 | 15 | .c.o: 16 | $(CC) $(STCFLAGS) -c $< 17 | 18 | st.o: config.h st.h win.h 19 | x.o: arg.h config.h st.h win.h 20 | boxdraw.o: config.h st.h boxdraw_data.h 21 | 22 | $(OBJ): config.h config.mk 23 | 24 | st: $(OBJ) 25 | $(CC) -o $@ $(OBJ) $(STLDFLAGS) 26 | 27 | clean: 28 | rm -f st $(OBJ) st-$(VERSION).tar.gz 29 | 30 | dist: clean 31 | mkdir -p st-$(VERSION) 32 | cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ 33 | config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ 34 | st-$(VERSION) 35 | tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz 36 | rm -rf st-$(VERSION) 37 | 38 | install: st 39 | mkdir -p $(DESTDIR)$(PREFIX)/bin 40 | cp -f st $(DESTDIR)$(PREFIX)/bin 41 | chmod 755 $(DESTDIR)$(PREFIX)/bin/st 42 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 43 | sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 44 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 45 | tic -sx st.info 46 | @echo Please see the README file regarding the terminfo entry of st. 47 | 48 | uninstall: 49 | rm -f $(DESTDIR)$(PREFIX)/bin/st 50 | rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 51 | 52 | .PHONY: all clean dist install uninstall 53 | -------------------------------------------------------------------------------- /win.h: -------------------------------------------------------------------------------- 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 | MODE_KBDSELECT = 1 << 18, 25 | }; 26 | 27 | void xbell(void); 28 | void xclipcopy(void); 29 | void xdrawcursor(int, int, Glyph, int, int, Glyph); 30 | void xdrawline(Line, int, int, int); 31 | void xfinishdraw(void); 32 | void xloadcols(void); 33 | int xsetcolorname(int, const char *); 34 | int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); 35 | void xseticontitle(char *); 36 | void xsettitle(char *); 37 | int xsetcursor(int); 38 | void xsetmode(int, unsigned int); 39 | void xsetpointermotion(int); 40 | void xsetsel(char *); 41 | int xstartdraw(void); 42 | void toggle_winmode(int); 43 | void keyboard_select(const Arg *); 44 | void xximspot(int, int); 45 | -------------------------------------------------------------------------------- /patches/st-w3m-0.8.3.diff: -------------------------------------------------------------------------------- 1 | From 69cffc587b54b0a9cd81adb87abad8e526d5b25b Mon Sep 17 00:00:00 2001 2 | From: "Avi Halachmi (:avih)" 3 | Date: Thu, 4 Jun 2020 17:35:08 +0300 4 | Subject: [PATCH] support w3m images 5 | 6 | w3m images are a hack which renders on top of the terminal's drawable, 7 | which didn't work in st because when using double buffering, the front 8 | buffer (on which w3m draws its images) is ignored, and st draws only 9 | on the back buffer, which is then copied to the front buffer. 10 | 11 | There's a patch to make it work at the FAQ already, but that patch 12 | canceles double-buffering, which can have negative side effects on 13 | some cases such as flickering. 14 | 15 | This patch achieves the same goal but instead of canceling the double 16 | buffer it first copies the front buffer to the back buffer. 17 | 18 | This has the same issues as the FAQ patch in that the cursor line is 19 | deleted at the image (because st renders always full lines), but 20 | otherwise it's simpler and does keeps double buffering. 21 | --- 22 | x.c | 2 ++ 23 | 1 file changed, 2 insertions(+) 24 | 25 | diff --git a/x.c b/x.c 26 | index e5f1737..b6ae162 100644 27 | --- a/x.c 28 | +++ b/x.c 29 | @@ -1594,6 +1594,8 @@ xsettitle(char *p) 30 | int 31 | xstartdraw(void) 32 | { 33 | + if (IS_SET(MODE_VISIBLE)) 34 | + XCopyArea(xw.dpy, xw.win, xw.buf, dc.gc, 0, 0, win.w, win.h, 0, 0); 35 | return IS_SET(MODE_VISIBLE); 36 | } 37 | 38 | 39 | base-commit: 43a395ae91f7d67ce694e65edeaa7bbc720dd027 40 | -- 41 | 2.17.1 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | © 2014-2022 Hiltjo Posthuma 4 | © 2018 Devin J. Pohly 5 | © 2014-2017 Quentin Rameau 6 | © 2009-2012 Aurélien APTEL 7 | © 2008-2017 Anselm R Garbe 8 | © 2012-2017 Roberto E. Vargas Caballero 9 | © 2012-2016 Christoph Lohmann <20h at r-36 dot net> 10 | © 2013 Eon S. Jeon 11 | © 2013 Alexander Sedov 12 | © 2013 Mark Edgar 13 | © 2013-2014 Eric Pruitt 14 | © 2013 Michael Forney 15 | © 2013-2014 Markus Teich 16 | © 2014-2015 Laslo Hunhold 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. 35 | -------------------------------------------------------------------------------- /patches/st-openclipboard-20190202-3be4cf1.diff: -------------------------------------------------------------------------------- 1 | From 8a3e3d9f0de95c55321761e2763cf43666da312d Mon Sep 17 00:00:00 2001 2 | From: Michael Buch 3 | Date: Sat, 2 Feb 2019 14:52:56 +0000 4 | Subject: [PATCH] Add ability to open clipboard with configured program 5 | 6 | --- 7 | config.def.h | 1 + 8 | st.h | 1 + 9 | x.c | 19 +++++++++++++++++++ 10 | 3 files changed, 21 insertions(+) 11 | 12 | diff --git a/config.def.h b/config.def.h 13 | index 0e01717..c273252 100644 14 | --- a/config.def.h 15 | +++ b/config.def.h 16 | @@ -178,6 +178,7 @@ static Shortcut shortcuts[] = { 17 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 18 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 19 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 20 | + { MODKEY, XK_o, opencopied, {.v = "xdg-open"} }, 21 | }; 22 | 23 | /* 24 | diff --git a/st.h b/st.h 25 | index 38c61c4..a0d7a83 100644 26 | --- a/st.h 27 | +++ b/st.h 28 | @@ -80,6 +80,7 @@ void die(const char *, ...); 29 | void redraw(void); 30 | void draw(void); 31 | 32 | +void opencopied(const Arg *); 33 | void printscreen(const Arg *); 34 | void printsel(const Arg *); 35 | void sendbreak(const Arg *); 36 | diff --git a/x.c b/x.c 37 | index 0422421..af75e85 100644 38 | --- a/x.c 39 | +++ b/x.c 40 | @@ -1953,3 +1953,22 @@ run: 41 | 42 | return 0; 43 | } 44 | + 45 | +void 46 | +opencopied(const Arg *arg) 47 | +{ 48 | + size_t const max_cmd = 2048; 49 | + char * const clip = xsel.clipboard; 50 | + if(!clip) { 51 | + fprintf(stderr, "Warning: nothing copied to clipboard\n"); 52 | + return; 53 | + } 54 | + 55 | + /* account for space/quote (3) and \0 (1) and & (1) */ 56 | + /* e.g.: xdg-open "https://st.suckless.org"& */ 57 | + size_t const cmd_size = max_cmd + strlen(clip) + 5; 58 | + char cmd[cmd_size]; 59 | + 60 | + snprintf(cmd, cmd_size, "%s \"%s\"&", (char *)arg->v, clip); 61 | + system(cmd); 62 | +} 63 | -- 64 | 2.20.1 65 | 66 | -------------------------------------------------------------------------------- /patches/st-scrollback-mouse-altscreen-20200416-5703aa0.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index 4b3bf15..1986316 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -163,8 +163,8 @@ static uint forcemousemod = ShiftMask; 6 | */ 7 | static MouseShortcut mshortcuts[] = { 8 | /* mask button function argument release */ 9 | - { ShiftMask, Button4, kscrollup, {.i = 1} }, 10 | - { ShiftMask, Button5, kscrolldown, {.i = 1} }, 11 | + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, 12 | + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, 13 | { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 14 | { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 15 | { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 16 | diff --git a/st.c b/st.c 17 | index f8b6f67..dd4cb31 100644 18 | --- st.c 19 | +++ st.c 20 | @@ -1045,6 +1045,11 @@ tnew(int col, int row) 21 | treset(); 22 | } 23 | 24 | +int tisaltscr(void) 25 | +{ 26 | + return IS_SET(MODE_ALTSCREEN); 27 | +} 28 | + 29 | void 30 | tswapscreen(void) 31 | { 32 | diff --git a/st.h b/st.h 33 | index 1332cf1..f9ad815 100644 34 | --- st.h 35 | +++ st.h 36 | @@ -89,6 +89,7 @@ void sendbreak(const Arg *); 37 | void toggleprinter(const Arg *); 38 | 39 | int tattrset(int); 40 | +int tisaltscr(void); 41 | void tnew(int, int); 42 | void tresize(int, int); 43 | void tsetdirtattr(int); 44 | diff --git a/x.c b/x.c 45 | index e5f1737..b8fbd7b 100644 46 | --- x.c 47 | +++ x.c 48 | @@ -34,6 +34,7 @@ typedef struct { 49 | void (*func)(const Arg *); 50 | const Arg arg; 51 | uint release; 52 | + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ 53 | } MouseShortcut; 54 | 55 | typedef struct { 56 | @@ -446,6 +447,7 @@ mouseaction(XEvent *e, uint release) 57 | for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 58 | if (ms->release == release && 59 | ms->button == e->xbutton.button && 60 | + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && 61 | (match(ms->mod, state) || /* exact or forced */ 62 | match(ms->mod, state & ~forcemousemod))) { 63 | ms->func(&(ms->arg)); 64 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | st - simple terminal 2 | -------------------- 3 | st is a simple terminal emulator for X which sucks less. 4 | 5 | 6 | Requirements 7 | ------------ 8 | In order to build st you need the Xlib header files. 9 | 10 | 11 | Installation 12 | ------------ 13 | Edit config.mk to match your local setup (st is installed into 14 | the /usr/local namespace by default). 15 | 16 | Afterwards enter the following command to build and install st (if 17 | necessary as root): 18 | 19 | make clean install 20 | 21 | 22 | Running st 23 | ---------- 24 | If you did not install st with make clean install, you must compile 25 | the st terminfo entry with the following command: 26 | 27 | tic -sx st.info 28 | 29 | See the man page for additional details. 30 | 31 | Credits 32 | ------- 33 | Based on Aurélien APTEL bt source code. 34 | 35 | Version 36 | ------- 37 | 0.9.3 38 | 39 | Patches 40 | ------- 41 | st-alpha-0.8.5.diff 42 | st-font2-20190416-ba72400.diff 43 | st-copyurl-0.8.4.diff 44 | st-openclipboard-20190202-3be4cf1.diff 45 | st-appsync-20200618-b27a383.diff 46 | st-xresources-20200604-9ba7ecf.diff 47 | st-scrollback-20210507-4536f46.diff 48 | st-scrollback-mouse-20191024-a2c479c.diff 49 | st-scrollback-mouse-altscreen-20200416-5703aa0.diff 50 | st-w3m-0.8.3.diff 51 | st-keyboard_select-20200617-9ba7ecf.diff 52 | st-boxdraw_v2-0.8.3.diff 53 | 54 | Key - Bindings 55 | -------------- 56 | Zoom - ctrl + plus, minus, equel (shift + mouse scroll) 57 | Scollback - shift + page_up, page_down (mouse scroll) 58 | Copy URL - alt + y 59 | Open Copied URL - alt + o 60 | Keyboard Select - ctrl + shift + esc 61 | 62 | Keyboard Select - Navigation 63 | ---------------------------- 64 | h, j, k, l: move cursor left/down/up/right (also with arrow keys) 65 | !, _, *: move cursor to the middle of the line/column/screen 66 | Backspace, $: move cursor to the beginning/end of the line 67 | PgUp, PgDown : move cursor to the beginning/end of the column 68 | Home, End: move cursor to the top/bottom left corner of the screen 69 | /, ?: activate input mode and search up/down 70 | n, N: repeat last search, up/down 71 | s: toggle move/selection mode 72 | t: toggle regular/rectangular selection type 73 | Return: quit keyboard_select, keeping the highlight of the selection 74 | Escape: quit keyboard_select 75 | 76 | Last implemented commit 77 | ----------------------- 78 | 2025-11-28 15:31 Disable bracked paste in reset Roberto E. Vargas Caballero 79 | -------------------------------------------------------------------------------- /st.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | 3 | #include 4 | #include 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 != (b).mode || (a).fg != (b).fg || \ 15 | (a).bg != (b).bg) 16 | #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ 17 | (t1.tv_nsec-t2.tv_nsec)/1E6) 18 | #define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) 19 | 20 | #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) 21 | #define IS_TRUECOL(x) (1 << 24 & (x)) 22 | 23 | enum glyph_attribute { 24 | ATTR_NULL = 0, 25 | ATTR_BOLD = 1 << 0, 26 | ATTR_FAINT = 1 << 1, 27 | ATTR_ITALIC = 1 << 2, 28 | ATTR_UNDERLINE = 1 << 3, 29 | ATTR_BLINK = 1 << 4, 30 | ATTR_REVERSE = 1 << 5, 31 | ATTR_INVISIBLE = 1 << 6, 32 | ATTR_STRUCK = 1 << 7, 33 | ATTR_WRAP = 1 << 8, 34 | ATTR_WIDE = 1 << 9, 35 | ATTR_WDUMMY = 1 << 10, 36 | ATTR_BOXDRAW = 1 << 11, 37 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 38 | }; 39 | 40 | enum selection_mode { 41 | SEL_IDLE = 0, 42 | SEL_EMPTY = 1, 43 | SEL_READY = 2 44 | }; 45 | 46 | enum selection_type { 47 | SEL_REGULAR = 1, 48 | SEL_RECTANGULAR = 2 49 | }; 50 | 51 | enum selection_snap { 52 | SNAP_WORD = 1, 53 | SNAP_LINE = 2 54 | }; 55 | 56 | typedef unsigned char uchar; 57 | typedef unsigned int uint; 58 | typedef unsigned long ulong; 59 | typedef unsigned short ushort; 60 | 61 | typedef uint_least32_t Rune; 62 | 63 | #define Glyph Glyph_ 64 | typedef struct { 65 | Rune u; /* character code */ 66 | ushort mode; /* attribute flags */ 67 | uint32_t fg; /* foreground */ 68 | uint32_t bg; /* background */ 69 | } Glyph; 70 | 71 | typedef Glyph *Line; 72 | 73 | typedef union { 74 | int i; 75 | uint ui; 76 | float f; 77 | const void *v; 78 | const char *s; 79 | } Arg; 80 | 81 | void die(const char *, ...); 82 | void redraw(void); 83 | void draw(void); 84 | 85 | void opencopied(const Arg *); 86 | void kscrolldown(const Arg *); 87 | void kscrollup(const Arg *); 88 | void printscreen(const Arg *); 89 | void printsel(const Arg *); 90 | void sendbreak(const Arg *); 91 | void toggleprinter(const Arg *); 92 | void copyurl(const Arg *); 93 | 94 | int tattrset(int); 95 | int tisaltscr(void); 96 | void tnew(int, int); 97 | void tresize(int, int); 98 | void tsetdirtattr(int); 99 | void ttyhangup(void); 100 | int ttynew(const char *, char *, const char *, char **); 101 | size_t ttyread(void); 102 | void ttyresize(int, int); 103 | void ttywrite(const char *, size_t, int); 104 | 105 | void resettitle(void); 106 | 107 | void selclear(void); 108 | void selinit(void); 109 | void selstart(int, int, int); 110 | void selextend(int, int, int, int); 111 | int selected(int, int); 112 | char *getsel(void); 113 | 114 | size_t utf8encode(Rune, char *); 115 | 116 | void *xmalloc(size_t); 117 | void *xrealloc(void *, size_t); 118 | char *xstrdup(const char *); 119 | 120 | int trt_kbdselect(KeySym, char *, int); 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 unsigned int defaultcs; 143 | extern float alpha; 144 | extern const int boxdraw, boxdraw_bold, boxdraw_braille; 145 | -------------------------------------------------------------------------------- /st.1: -------------------------------------------------------------------------------- 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 [=][{xX}][{+-}{+-}]. 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 Break 129 | Send a break in the serial line. 130 | Break key is obtained in PC keyboards 131 | pressing at the same time control and pause. 132 | .TP 133 | .B Ctrl-Print Screen 134 | Toggle if st should print to the 135 | .I iofile. 136 | .TP 137 | .B Shift-Print Screen 138 | Print the full screen to the 139 | .I iofile. 140 | .TP 141 | .B Print Screen 142 | Print the selection to the 143 | .I iofile. 144 | .TP 145 | .B Ctrl-Shift-Page Up 146 | Increase font size. 147 | .TP 148 | .B Ctrl-Shift-Page Down 149 | Decrease font size. 150 | .TP 151 | .B Ctrl-Shift-Home 152 | Reset to default font size. 153 | .TP 154 | .B Ctrl-Shift-y 155 | Paste from primary selection (middle mouse button). 156 | .TP 157 | .B Ctrl-Shift-c 158 | Copy the selected text to the clipboard selection. 159 | .TP 160 | .B Ctrl-Shift-v 161 | Paste from the clipboard selection. 162 | .SH CUSTOMIZATION 163 | .B st 164 | can be customized by creating a custom config.h and (re)compiling the source 165 | code. This keeps it fast, secure and simple. 166 | .SH AUTHORS 167 | See the LICENSE file for the authors. 168 | .SH LICENSE 169 | See the LICENSE file for the terms of redistribution. 170 | .SH SEE ALSO 171 | .BR tabbed (1), 172 | .BR utmp (1), 173 | .BR stty (1), 174 | .BR scroll (1) 175 | .SH BUGS 176 | See the TODO file in the distribution. 177 | 178 | -------------------------------------------------------------------------------- /patches/st-alpha-20220206-0.8.5.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index 91ab8ca..6af616e 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -93,6 +93,9 @@ char *termname = "st-256color"; 6 | */ 7 | unsigned int tabspaces = 8; 8 | 9 | +/* bg opacity */ 10 | +float alpha = 0.8; 11 | + 12 | /* Terminal colors (16 first used in escape sequence) */ 13 | static const char *colorname[] = { 14 | /* 8 normal colors */ 15 | diff --git a/config.mk b/config.mk 16 | index 4c4c5d5..0114bad 100644 17 | --- a/config.mk 18 | +++ b/config.mk 19 | @@ -16,7 +16,7 @@ PKG_CONFIG = pkg-config 20 | INCS = -I$(X11INC) \ 21 | `$(PKG_CONFIG) --cflags fontconfig` \ 22 | `$(PKG_CONFIG) --cflags freetype2` 23 | -LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ 24 | +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ 25 | `$(PKG_CONFIG) --libs fontconfig` \ 26 | `$(PKG_CONFIG) --libs freetype2` 27 | 28 | diff --git a/st.h b/st.h 29 | index 519b9bd..8bb533d 100644 30 | --- a/st.h 31 | +++ b/st.h 32 | @@ -126,3 +126,4 @@ extern unsigned int tabspaces; 33 | extern unsigned int defaultfg; 34 | extern unsigned int defaultbg; 35 | extern unsigned int defaultcs; 36 | +extern float alpha; 37 | diff --git a/x.c b/x.c 38 | index 8a16faa..ddf4178 100644 39 | --- a/x.c 40 | +++ b/x.c 41 | @@ -105,6 +105,7 @@ typedef struct { 42 | XSetWindowAttributes attrs; 43 | int scr; 44 | int isfixed; /* is fixed geometry? */ 45 | + int depth; /* bit depth */ 46 | int l, t; /* left and top offset */ 47 | int gm; /* geometry mask */ 48 | } XWindow; 49 | @@ -243,6 +244,7 @@ static char *usedfont = NULL; 50 | static double usedfontsize = 0; 51 | static double defaultfontsize = 0; 52 | 53 | +static char *opt_alpha = NULL; 54 | static char *opt_class = NULL; 55 | static char **opt_cmd = NULL; 56 | static char *opt_embed = NULL; 57 | @@ -736,7 +738,7 @@ xresize(int col, int row) 58 | 59 | XFreePixmap(xw.dpy, xw.buf); 60 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 61 | - DefaultDepth(xw.dpy, xw.scr)); 62 | + xw.depth); 63 | XftDrawChange(xw.draw, xw.buf); 64 | xclear(0, 0, win.w, win.h); 65 | 66 | @@ -796,6 +798,13 @@ xloadcols(void) 67 | else 68 | die("could not allocate color %d\n", i); 69 | } 70 | + 71 | + /* set alpha value of bg color */ 72 | + if (opt_alpha) 73 | + alpha = strtof(opt_alpha, NULL); 74 | + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); 75 | + dc.col[defaultbg].pixel &= 0x00FFFFFF; 76 | + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; 77 | loaded = 1; 78 | } 79 | 80 | @@ -1118,11 +1127,23 @@ xinit(int cols, int rows) 81 | Window parent; 82 | pid_t thispid = getpid(); 83 | XColor xmousefg, xmousebg; 84 | + XWindowAttributes attr; 85 | + XVisualInfo vis; 86 | 87 | if (!(xw.dpy = XOpenDisplay(NULL))) 88 | die("can't open display\n"); 89 | xw.scr = XDefaultScreen(xw.dpy); 90 | - xw.vis = XDefaultVisual(xw.dpy, xw.scr); 91 | + 92 | + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { 93 | + parent = XRootWindow(xw.dpy, xw.scr); 94 | + xw.depth = 32; 95 | + } else { 96 | + XGetWindowAttributes(xw.dpy, parent, &attr); 97 | + xw.depth = attr.depth; 98 | + } 99 | + 100 | + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); 101 | + xw.vis = vis.visual; 102 | 103 | /* font */ 104 | if (!FcInit()) 105 | @@ -1132,7 +1153,7 @@ xinit(int cols, int rows) 106 | xloadfonts(usedfont, 0); 107 | 108 | /* colors */ 109 | - xw.cmap = XDefaultColormap(xw.dpy, xw.scr); 110 | + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); 111 | xloadcols(); 112 | 113 | /* adjust fixed window geometry */ 114 | @@ -1152,19 +1173,15 @@ xinit(int cols, int rows) 115 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 116 | xw.attrs.colormap = xw.cmap; 117 | 118 | - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) 119 | - parent = XRootWindow(xw.dpy, xw.scr); 120 | xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, 121 | - win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, 122 | + win.w, win.h, 0, xw.depth, InputOutput, 123 | xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 124 | | CWEventMask | CWColormap, &xw.attrs); 125 | 126 | memset(&gcvalues, 0, sizeof(gcvalues)); 127 | gcvalues.graphics_exposures = False; 128 | - dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, 129 | - &gcvalues); 130 | - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 131 | - DefaultDepth(xw.dpy, xw.scr)); 132 | + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); 133 | + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); 134 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 135 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 136 | 137 | @@ -2019,6 +2036,9 @@ main(int argc, char *argv[]) 138 | case 'a': 139 | allowaltscreen = 0; 140 | break; 141 | + case 'A': 142 | + opt_alpha = EARGF(usage()); 143 | + break; 144 | case 'c': 145 | opt_class = EARGF(usage()); 146 | break; 147 | -------------------------------------------------------------------------------- /patches/st-copyurl-0.8.4.diff: -------------------------------------------------------------------------------- 1 | diff -up ../st-0.8.4/config.def.h ./config.def.h 2 | --- ../st-0.8.4/config.def.h 2020-06-19 10:29:45.000000000 +0100 3 | +++ ./config.def.h 2021-01-29 22:40:56.451916768 +0000 4 | @@ -199,6 +199,7 @@ static Shortcut shortcuts[] = { 5 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 6 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 7 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 8 | + { MODKEY, XK_l, copyurl, {.i = 0} }, 9 | }; 10 | 11 | /* 12 | diff -up ../st-0.8.4/st.c ./st.c 13 | --- ../st-0.8.4/st.c 2020-06-19 10:29:45.000000000 +0100 14 | +++ ./st.c 2021-01-29 22:41:18.031954197 +0000 15 | @@ -200,6 +200,8 @@ static void tdefutf8(char); 16 | static int32_t tdefcolor(int *, int *, int); 17 | static void tdeftran(char); 18 | static void tstrsequence(uchar); 19 | +static void tsetcolor(int, int, int, uint32_t, uint32_t); 20 | +static char * findlastany(char *, const char**, size_t); 21 | 22 | static void drawregion(int, int, int, int); 23 | 24 | @@ -2595,3 +2597,122 @@ redraw(void) 25 | tfulldirt(); 26 | draw(); 27 | } 28 | + 29 | +void 30 | +tsetcolor( int row, int start, int end, uint32_t fg, uint32_t bg ) 31 | +{ 32 | + int i = start; 33 | + for( ; i < end; ++i ) 34 | + { 35 | + term.line[row][i].fg = fg; 36 | + term.line[row][i].bg = bg; 37 | + } 38 | +} 39 | + 40 | +char * 41 | +findlastany(char *str, const char** find, size_t len) 42 | +{ 43 | + char* found = NULL; 44 | + int i = 0; 45 | + for(found = str + strlen(str) - 1; found >= str; --found) { 46 | + for(i = 0; i < len; i++) { 47 | + if(strncmp(found, find[i], strlen(find[i])) == 0) { 48 | + return found; 49 | + } 50 | + } 51 | + } 52 | + 53 | + return NULL; 54 | +} 55 | + 56 | +/* 57 | +** Select and copy the previous url on screen (do nothing if there's no url). 58 | +** 59 | +** FIXME: doesn't handle urls that span multiple lines; will need to add support 60 | +** for multiline "getsel()" first 61 | +*/ 62 | +void 63 | +copyurl(const Arg *arg) { 64 | + /* () and [] can appear in urls, but excluding them here will reduce false 65 | + * positives when figuring out where a given url ends. 66 | + */ 67 | + static char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 68 | + "abcdefghijklmnopqrstuvwxyz" 69 | + "0123456789-._~:/?#@!$&'*+,;=%"; 70 | + 71 | + static const char* URLSTRINGS[] = {"http://", "https://"}; 72 | + 73 | + /* remove highlighting from previous selection if any */ 74 | + if(sel.ob.x >= 0 && sel.oe.x >= 0) 75 | + tsetcolor(sel.nb.y, sel.ob.x, sel.oe.x + 1, defaultfg, defaultbg); 76 | + 77 | + int i = 0, 78 | + row = 0, /* row of current URL */ 79 | + col = 0, /* column of current URL start */ 80 | + startrow = 0, /* row of last occurrence */ 81 | + colend = 0, /* column of last occurrence */ 82 | + passes = 0; /* how many rows have been scanned */ 83 | + 84 | + char *linestr = calloc(term.col+1, sizeof(Rune)); 85 | + char *c = NULL, 86 | + *match = NULL; 87 | + 88 | + row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot; 89 | + LIMIT(row, term.top, term.bot); 90 | + startrow = row; 91 | + 92 | + colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col; 93 | + LIMIT(colend, 0, term.col); 94 | + 95 | + /* 96 | + ** Scan from (term.bot,term.col) to (0,0) and find 97 | + ** next occurrance of a URL 98 | + */ 99 | + while(passes !=term.bot + 2) { 100 | + /* Read in each column of every row until 101 | + ** we hit previous occurrence of URL 102 | + */ 103 | + for (col = 0, i = 0; col < colend; ++col,++i) { 104 | + linestr[i] = term.line[row][col].u; 105 | + } 106 | + linestr[term.col] = '\0'; 107 | + 108 | + if ((match = findlastany(linestr, URLSTRINGS, 109 | + sizeof(URLSTRINGS)/sizeof(URLSTRINGS[0])))) 110 | + break; 111 | + 112 | + if (--row < term.top) 113 | + row = term.bot; 114 | + 115 | + colend = term.col; 116 | + passes++; 117 | + }; 118 | + 119 | + if (match) { 120 | + /* must happen before trim */ 121 | + selclear(); 122 | + sel.ob.x = strlen(linestr) - strlen(match); 123 | + 124 | + /* trim the rest of the line from the url match */ 125 | + for (c = match; *c != '\0'; ++c) 126 | + if (!strchr(URLCHARS, *c)) { 127 | + *c = '\0'; 128 | + break; 129 | + } 130 | + 131 | + /* highlight selection by inverting terminal colors */ 132 | + tsetcolor(row, sel.ob.x, sel.ob.x + strlen( match ), defaultbg, defaultfg); 133 | + 134 | + /* select and copy */ 135 | + sel.mode = 1; 136 | + sel.type = SEL_REGULAR; 137 | + sel.oe.x = sel.ob.x + strlen(match)-1; 138 | + sel.ob.y = sel.oe.y = row; 139 | + selnormalize(); 140 | + tsetdirt(sel.nb.y, sel.ne.y); 141 | + xsetsel(getsel()); 142 | + xclipcopy(); 143 | + } 144 | + 145 | + free(linestr); 146 | +} 147 | Only in .: st-copyurl-0.8.4.diff 148 | Only in .: st-copyurl-20190202-3be4cf1.diff 149 | diff -up ../st-0.8.4/st.h ./st.h 150 | --- ../st-0.8.4/st.h 2020-06-19 10:29:45.000000000 +0100 151 | +++ ./st.h 2021-01-29 22:40:56.451916768 +0000 152 | @@ -85,6 +85,7 @@ void printscreen(const Arg *); 153 | void printsel(const Arg *); 154 | void sendbreak(const Arg *); 155 | void toggleprinter(const Arg *); 156 | +void copyurl(const Arg *); 157 | 158 | int tattrset(int); 159 | void tnew(int, int); 160 | -------------------------------------------------------------------------------- /patches/st-font2-20190416-ba72400.diff: -------------------------------------------------------------------------------- 1 | From ba724004c6a368e452114f7dc147a9978fe0f3b4 Mon Sep 17 00:00:00 2001 2 | From: Kirill Bugaev 3 | Date: Tue, 16 Apr 2019 04:31:30 +0800 4 | Subject: [PATCH] This patch allows to add spare font besides default. Some 5 | glyphs can be not present in default font. For this glyphs st uses 6 | font-config and try to find them in font cache first. This patch append fonts 7 | defined in font2 variable to the beginning of font cache. So they will be 8 | used first for glyphs that absent in default font. 9 | 10 | --- 11 | config.def.h | 6 +++ 12 | x.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ 13 | 2 files changed, 107 insertions(+) 14 | 15 | diff --git a/config.def.h b/config.def.h 16 | index 482901e..676719e 100644 17 | --- a/config.def.h 18 | +++ b/config.def.h 19 | @@ -6,6 +6,12 @@ 20 | * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html 21 | */ 22 | static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; 23 | +/* Spare fonts */ 24 | +static char *font2[] = { 25 | +/* "Inconsolata for Powerline:pixelsize=12:antialias=true:autohint=true", */ 26 | +/* "Hack Nerd Font Mono:pixelsize=11:antialias=true:autohint=true", */ 27 | +}; 28 | + 29 | static int borderpx = 2; 30 | 31 | /* 32 | diff --git a/x.c b/x.c 33 | index 5828a3b..d37e59d 100644 34 | --- a/x.c 35 | +++ b/x.c 36 | @@ -149,6 +149,8 @@ static void xhints(void); 37 | static int xloadcolor(int, const char *, Color *); 38 | static int xloadfont(Font *, FcPattern *); 39 | static void xloadfonts(char *, double); 40 | +static int xloadsparefont(FcPattern *, int); 41 | +static void xloadsparefonts(void); 42 | static void xunloadfont(Font *); 43 | static void xunloadfonts(void); 44 | static void xsetenv(void); 45 | @@ -296,6 +298,7 @@ zoomabs(const Arg *arg) 46 | { 47 | xunloadfonts(); 48 | xloadfonts(usedfont, arg->f); 49 | + xloadsparefonts(); 50 | cresize(0, 0); 51 | redraw(); 52 | xhints(); 53 | @@ -977,6 +980,101 @@ xloadfonts(char *fontstr, double fontsize) 54 | FcPatternDestroy(pattern); 55 | } 56 | 57 | +int 58 | +xloadsparefont(FcPattern *pattern, int flags) 59 | +{ 60 | + FcPattern *match; 61 | + FcResult result; 62 | + 63 | + match = FcFontMatch(NULL, pattern, &result); 64 | + if (!match) { 65 | + return 1; 66 | + } 67 | + 68 | + if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) { 69 | + FcPatternDestroy(match); 70 | + return 1; 71 | + } 72 | + 73 | + frc[frclen].flags = flags; 74 | + /* Believe U+0000 glyph will present in each default font */ 75 | + frc[frclen].unicodep = 0; 76 | + frclen++; 77 | + 78 | + return 0; 79 | +} 80 | + 81 | +void 82 | +xloadsparefonts(void) 83 | +{ 84 | + FcPattern *pattern; 85 | + double sizeshift, fontval; 86 | + int fc; 87 | + char **fp; 88 | + 89 | + if (frclen != 0) 90 | + die("can't embed spare fonts. cache isn't empty"); 91 | + 92 | + /* Calculate count of spare fonts */ 93 | + fc = sizeof(font2) / sizeof(*font2); 94 | + if (fc == 0) 95 | + return; 96 | + 97 | + /* Allocate memory for cache entries. */ 98 | + if (frccap < 4 * fc) { 99 | + frccap += 4 * fc - frccap; 100 | + frc = xrealloc(frc, frccap * sizeof(Fontcache)); 101 | + } 102 | + 103 | + for (fp = font2; fp - font2 < fc; ++fp) { 104 | + 105 | + if (**fp == '-') 106 | + pattern = XftXlfdParse(*fp, False, False); 107 | + else 108 | + pattern = FcNameParse((FcChar8 *)*fp); 109 | + 110 | + if (!pattern) 111 | + die("can't open spare font %s\n", *fp); 112 | + 113 | + if (defaultfontsize > 0) { 114 | + sizeshift = usedfontsize - defaultfontsize; 115 | + if (sizeshift != 0 && 116 | + FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 117 | + FcResultMatch) { 118 | + fontval += sizeshift; 119 | + FcPatternDel(pattern, FC_PIXEL_SIZE); 120 | + FcPatternDel(pattern, FC_SIZE); 121 | + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval); 122 | + } 123 | + } 124 | + 125 | + FcPatternAddBool(pattern, FC_SCALABLE, 1); 126 | + 127 | + FcConfigSubstitute(NULL, pattern, FcMatchPattern); 128 | + XftDefaultSubstitute(xw.dpy, xw.scr, pattern); 129 | + 130 | + if (xloadsparefont(pattern, FRC_NORMAL)) 131 | + die("can't open spare font %s\n", *fp); 132 | + 133 | + FcPatternDel(pattern, FC_SLANT); 134 | + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 135 | + if (xloadsparefont(pattern, FRC_ITALIC)) 136 | + die("can't open spare font %s\n", *fp); 137 | + 138 | + FcPatternDel(pattern, FC_WEIGHT); 139 | + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 140 | + if (xloadsparefont(pattern, FRC_ITALICBOLD)) 141 | + die("can't open spare font %s\n", *fp); 142 | + 143 | + FcPatternDel(pattern, FC_SLANT); 144 | + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 145 | + if (xloadsparefont(pattern, FRC_BOLD)) 146 | + die("can't open spare font %s\n", *fp); 147 | + 148 | + FcPatternDestroy(pattern); 149 | + } 150 | +} 151 | + 152 | void 153 | xunloadfont(Font *f) 154 | { 155 | @@ -1057,6 +1155,9 @@ xinit(int cols, int rows) 156 | usedfont = (opt_font == NULL)? font : opt_font; 157 | xloadfonts(usedfont, 0); 158 | 159 | + /* spare fonts */ 160 | + xloadsparefonts(); 161 | + 162 | /* colors */ 163 | xw.cmap = XDefaultColormap(xw.dpy, xw.scr); 164 | xloadcols(); 165 | -- 166 | 2.21.0 167 | 168 | -------------------------------------------------------------------------------- /st.info: -------------------------------------------------------------------------------- 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 | BE=\E[?2004h, 188 | BD=\E[?2004l, 189 | PS=\E[200~, 190 | PE=\E[201~, 191 | # disabled rep for now: causes some issues with older ncurses versions. 192 | # rep=%p1%c\E[%p2%{1}%-%db, 193 | # tmux extensions, see TERMINFO EXTENSIONS in tmux(1) 194 | Tc, 195 | Ms=\E]52;%p1%s;%p2%s\007, 196 | Se=\E[2 q, 197 | Ss=\E[%p1%d q, 198 | Sync=\EP=%p1%ds\E\\, 199 | 200 | st| simpleterm, 201 | use=st-mono, 202 | colors#8, 203 | setab=\E[4%p1%dm, 204 | setaf=\E[3%p1%dm, 205 | setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 206 | setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, 207 | 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, 208 | 209 | st-256color| simpleterm with 256 colors, 210 | use=st, 211 | ccc, 212 | colors#256, 213 | oc=\E]104\007, 214 | pairs#32767, 215 | # Nicked from xterm-256color 216 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 217 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 218 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 219 | 220 | st-meta| simpleterm with meta key, 221 | use=st, 222 | km, 223 | rmm=\E[?1034l, 224 | smm=\E[?1034h, 225 | rs2=\E[4l\E>\E[?1034h, 226 | is2=\E[4l\E>\E[?1034h, 227 | 228 | st-meta-256color| simpleterm with meta key and 256 colors, 229 | use=st-256color, 230 | km, 231 | rmm=\E[?1034l, 232 | smm=\E[?1034h, 233 | rs2=\E[4l\E>\E[?1034h, 234 | is2=\E[4l\E>\E[?1034h, 235 | 236 | st-bs| simpleterm with backspace as backspace, 237 | use=st, 238 | kbs=\010, 239 | kdch1=\177, 240 | 241 | st-bs-256color| simpleterm with backspace as backspace and 256colors, 242 | use=st-256color, 243 | kbs=\010, 244 | kdch1=\177, 245 | -------------------------------------------------------------------------------- /patches/st-xresources-20200604-9ba7ecf.diff: -------------------------------------------------------------------------------- 1 | From 2752a599ee01305a435729bfacf43b1dde7cf0ef Mon Sep 17 00:00:00 2001 2 | From: Benji Encalada Mora 3 | Date: Thu, 4 Jun 2020 00:41:10 -0500 4 | Subject: [PATCH] fix: replace xfps and actionfps variables 5 | 6 | --- 7 | config.def.h | 36 ++++++++++++++++++++++++ 8 | x.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++--- 9 | 2 files changed, 110 insertions(+), 4 deletions(-) 10 | 11 | diff --git a/config.def.h b/config.def.h 12 | index 6f05dce..9b99782 100644 13 | --- a/config.def.h 14 | +++ b/config.def.h 15 | @@ -168,6 +168,42 @@ static unsigned int defaultattr = 11; 16 | */ 17 | static uint forcemousemod = ShiftMask; 18 | 19 | +/* 20 | + * Xresources preferences to load at startup 21 | + */ 22 | +ResourcePref resources[] = { 23 | + { "font", STRING, &font }, 24 | + { "color0", STRING, &colorname[0] }, 25 | + { "color1", STRING, &colorname[1] }, 26 | + { "color2", STRING, &colorname[2] }, 27 | + { "color3", STRING, &colorname[3] }, 28 | + { "color4", STRING, &colorname[4] }, 29 | + { "color5", STRING, &colorname[5] }, 30 | + { "color6", STRING, &colorname[6] }, 31 | + { "color7", STRING, &colorname[7] }, 32 | + { "color8", STRING, &colorname[8] }, 33 | + { "color9", STRING, &colorname[9] }, 34 | + { "color10", STRING, &colorname[10] }, 35 | + { "color11", STRING, &colorname[11] }, 36 | + { "color12", STRING, &colorname[12] }, 37 | + { "color13", STRING, &colorname[13] }, 38 | + { "color14", STRING, &colorname[14] }, 39 | + { "color15", STRING, &colorname[15] }, 40 | + { "background", STRING, &colorname[256] }, 41 | + { "foreground", STRING, &colorname[257] }, 42 | + { "cursorColor", STRING, &colorname[258] }, 43 | + { "termname", STRING, &termname }, 44 | + { "shell", STRING, &shell }, 45 | + { "minlatency", INTEGER, &minlatency }, 46 | + { "maxlatency", INTEGER, &maxlatency }, 47 | + { "blinktimeout", INTEGER, &blinktimeout }, 48 | + { "bellvolume", INTEGER, &bellvolume }, 49 | + { "tabspaces", INTEGER, &tabspaces }, 50 | + { "borderpx", INTEGER, &borderpx }, 51 | + { "cwscale", FLOAT, &cwscale }, 52 | + { "chscale", FLOAT, &chscale }, 53 | +}; 54 | + 55 | /* 56 | * Internal mouse shortcuts. 57 | * Beware that overloading Button1 will disable the selection. 58 | diff --git a/x.c b/x.c 59 | index 210f184..76f167f 100644 60 | --- a/x.c 61 | +++ b/x.c 62 | @@ -14,6 +14,7 @@ 63 | #include 64 | #include 65 | #include 66 | +#include 67 | 68 | char *argv0; 69 | #include "arg.h" 70 | @@ -45,6 +46,19 @@ typedef struct { 71 | signed char appcursor; /* application cursor */ 72 | } Key; 73 | 74 | +/* Xresources preferences */ 75 | +enum resource_type { 76 | + STRING = 0, 77 | + INTEGER = 1, 78 | + FLOAT = 2 79 | +}; 80 | + 81 | +typedef struct { 82 | + char *name; 83 | + enum resource_type type; 84 | + void *dst; 85 | +} ResourcePref; 86 | + 87 | /* X modifiers */ 88 | #define XK_ANY_MOD UINT_MAX 89 | #define XK_NO_MOD 0 90 | @@ -828,8 +842,8 @@ xclear(int x1, int y1, int x2, int y2) 91 | void 92 | xhints(void) 93 | { 94 | - XClassHint class = {opt_name ? opt_name : termname, 95 | - opt_class ? opt_class : termname}; 96 | + XClassHint class = {opt_name ? opt_name : "st", 97 | + opt_class ? opt_class : "St"}; 98 | XWMHints wm = {.flags = InputHint, .input = 1}; 99 | XSizeHints *sizeh; 100 | 101 | @@ -1104,8 +1118,6 @@ xinit(int cols, int rows) 102 | pid_t thispid = getpid(); 103 | XColor xmousefg, xmousebg; 104 | 105 | - if (!(xw.dpy = XOpenDisplay(NULL))) 106 | - die("can't open display\n"); 107 | xw.scr = XDefaultScreen(xw.dpy); 108 | xw.vis = XDefaultVisual(xw.dpy, xw.scr); 109 | 110 | @@ -1964,6 +1976,59 @@ run(void) 111 | } 112 | } 113 | 114 | +int 115 | +resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) 116 | +{ 117 | + char **sdst = dst; 118 | + int *idst = dst; 119 | + float *fdst = dst; 120 | + 121 | + char fullname[256]; 122 | + char fullclass[256]; 123 | + char *type; 124 | + XrmValue ret; 125 | + 126 | + snprintf(fullname, sizeof(fullname), "%s.%s", 127 | + opt_name ? opt_name : "st", name); 128 | + snprintf(fullclass, sizeof(fullclass), "%s.%s", 129 | + opt_class ? opt_class : "St", name); 130 | + fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; 131 | + 132 | + XrmGetResource(db, fullname, fullclass, &type, &ret); 133 | + if (ret.addr == NULL || strncmp("String", type, 64)) 134 | + return 1; 135 | + 136 | + switch (rtype) { 137 | + case STRING: 138 | + *sdst = ret.addr; 139 | + break; 140 | + case INTEGER: 141 | + *idst = strtoul(ret.addr, NULL, 10); 142 | + break; 143 | + case FLOAT: 144 | + *fdst = strtof(ret.addr, NULL); 145 | + break; 146 | + } 147 | + return 0; 148 | +} 149 | + 150 | +void 151 | +config_init(void) 152 | +{ 153 | + char *resm; 154 | + XrmDatabase db; 155 | + ResourcePref *p; 156 | + 157 | + XrmInitialize(); 158 | + resm = XResourceManagerString(xw.dpy); 159 | + if (!resm) 160 | + return; 161 | + 162 | + db = XrmGetStringDatabase(resm); 163 | + for (p = resources; p < resources + LEN(resources); p++) 164 | + resource_load(db, p->name, p->type, p->dst); 165 | +} 166 | + 167 | void 168 | usage(void) 169 | { 170 | @@ -2037,6 +2102,11 @@ run: 171 | 172 | setlocale(LC_CTYPE, ""); 173 | XSetLocaleModifiers(""); 174 | + 175 | + if(!(xw.dpy = XOpenDisplay(NULL))) 176 | + die("Can't open display\n"); 177 | + 178 | + config_init(); 179 | cols = MAX(cols, 1); 180 | rows = MAX(rows, 1); 181 | tnew(cols, rows); 182 | -- 183 | 2.26.2 184 | 185 | -------------------------------------------------------------------------------- /boxdraw.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih 3 | * MIT/X Consortium License 4 | */ 5 | 6 | #include 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 | } 195 | -------------------------------------------------------------------------------- /boxdraw_data.h: -------------------------------------------------------------------------------- 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 | }; 215 | -------------------------------------------------------------------------------- /patches/st-appsync-20200618-b27a383.diff: -------------------------------------------------------------------------------- 1 | From 8c9c920325fa10440a96736ba58ec647a0365e22 Mon Sep 17 00:00:00 2001 2 | From: "Avi Halachmi (:avih)" 3 | Date: Sat, 18 Apr 2020 13:56:11 +0300 4 | Subject: [PATCH] application-sync: support Synchronized-Updates 5 | 6 | See https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec 7 | 8 | In a nutshell: allow an application to suspend drawing until it has 9 | completed some output - so that the terminal will not flicker/tear by 10 | rendering partial content. If the end-of-suspension sequence doesn't 11 | arrive, the terminal bails out after a timeout (default: 200 ms). 12 | 13 | The feature is supported and pioneered by iTerm2. There are probably 14 | very few other terminals or applications which support this feature 15 | currently. 16 | 17 | One notable application which does support it is tmux (master as of 18 | 2020-04-18) - where cursor flicker is completely avoided when a pane 19 | has new content. E.g. run in one pane: `while :; do cat x.c; done' 20 | while the cursor is at another pane. 21 | 22 | The terminfo string `Sync' added to `st.info' is also a tmux extension 23 | which tmux detects automatically when `st.info` is installed. 24 | 25 | Notes: 26 | 27 | - Draw-suspension begins on BSU sequence (Begin-Synchronized-Update), 28 | and ends on ESU sequence (End-Synchronized-Update). 29 | 30 | - BSU, ESU are "\033P=1s\033\\", "\033P=2s\033\\" respectively (DCS). 31 | 32 | - SU doesn't support nesting - BSU begins or extends, ESU always ends. 33 | 34 | - ESU without BSU is ignored. 35 | 36 | - BSU after BSU extends (resets the timeout), so an application could 37 | send BSU in a loop and keep drawing suspended - exactly like it can 38 | not-draw anything in a loop. But as soon as it exits/aborted then 39 | drawing is resumed according to the timeout even without ESU. 40 | 41 | - This implementation focuses on ESU and doesn't really care about BSU 42 | in the sense that it tries hard to draw exactly once ESU arrives (if 43 | it's not too soon after the last draw - according to minlatency), 44 | and doesn't try to draw the content just up-to BSU. These two sides 45 | complement eachother - not-drawing on BSU increases the chance that 46 | ESU is not too soon after the last draw. This approach was chosen 47 | because the application's main focus is that ESU indicates to the 48 | terminal that the content is now ready - and that's when we try to 49 | draw. 50 | --- 51 | config.def.h | 6 ++++++ 52 | st.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 53 | st.info | 1 + 54 | x.c | 22 +++++++++++++++++++--- 55 | 4 files changed, 72 insertions(+), 5 deletions(-) 56 | 57 | diff --git a/config.def.h b/config.def.h 58 | index 6f05dce..80d768e 100644 59 | --- a/config.def.h 60 | +++ b/config.def.h 61 | @@ -56,6 +56,12 @@ int allowwindowops = 0; 62 | static double minlatency = 8; 63 | static double maxlatency = 33; 64 | 65 | +/* 66 | + * Synchronized-Update timeout in ms 67 | + * https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec 68 | + */ 69 | +static uint su_timeout = 200; 70 | + 71 | /* 72 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking 73 | * attribute. 74 | diff --git a/st.c b/st.c 75 | index 76b7e0d..0582e77 100644 76 | --- a/st.c 77 | +++ b/st.c 78 | @@ -231,6 +231,33 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 79 | static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 80 | static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 81 | 82 | +#include 83 | +static int su = 0; 84 | +struct timespec sutv; 85 | + 86 | +static void 87 | +tsync_begin() 88 | +{ 89 | + clock_gettime(CLOCK_MONOTONIC, &sutv); 90 | + su = 1; 91 | +} 92 | + 93 | +static void 94 | +tsync_end() 95 | +{ 96 | + su = 0; 97 | +} 98 | + 99 | +int 100 | +tinsync(uint timeout) 101 | +{ 102 | + struct timespec now; 103 | + if (su && !clock_gettime(CLOCK_MONOTONIC, &now) 104 | + && TIMEDIFF(now, sutv) >= timeout) 105 | + su = 0; 106 | + return su; 107 | +} 108 | + 109 | ssize_t 110 | xwrite(int fd, const char *s, size_t len) 111 | { 112 | @@ -818,6 +845,9 @@ ttynew(char *line, char *cmd, char *out, char **args) 113 | return cmdfd; 114 | } 115 | 116 | +static int twrite_aborted = 0; 117 | +int ttyread_pending() { return twrite_aborted; } 118 | + 119 | size_t 120 | ttyread(void) 121 | { 122 | @@ -826,7 +856,7 @@ ttyread(void) 123 | int ret, written; 124 | 125 | /* append read bytes to unprocessed bytes */ 126 | - ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); 127 | + ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen); 128 | 129 | switch (ret) { 130 | case 0: 131 | @@ -834,7 +864,7 @@ ttyread(void) 132 | case -1: 133 | die("couldn't read from shell: %s\n", strerror(errno)); 134 | default: 135 | - buflen += ret; 136 | + buflen += twrite_aborted ? 0 : ret; 137 | written = twrite(buf, buflen, 0); 138 | buflen -= written; 139 | /* keep any incomplete UTF-8 byte sequence for the next call */ 140 | @@ -994,6 +1024,7 @@ tsetdirtattr(int attr) 141 | void 142 | tfulldirt(void) 143 | { 144 | + tsync_end(); 145 | tsetdirt(0, term.row-1); 146 | } 147 | 148 | @@ -1895,6 +1926,12 @@ strhandle(void) 149 | xsettitle(strescseq.args[0]); 150 | return; 151 | case 'P': /* DCS -- Device Control String */ 152 | + /* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */ 153 | + if (strstr(strescseq.buf, "=1s") == strescseq.buf) 154 | + tsync_begin(); /* BSU */ 155 | + else if (strstr(strescseq.buf, "=2s") == strescseq.buf) 156 | + tsync_end(); /* ESU */ 157 | + return; 158 | case '_': /* APC -- Application Program Command */ 159 | case '^': /* PM -- Privacy Message */ 160 | return; 161 | @@ -2436,6 +2473,9 @@ twrite(const char *buf, int buflen, int show_ctrl) 162 | Rune u; 163 | int n; 164 | 165 | + int su0 = su; 166 | + twrite_aborted = 0; 167 | + 168 | for (n = 0; n < buflen; n += charsize) { 169 | if (IS_SET(MODE_UTF8)) { 170 | /* process a complete utf8 char */ 171 | @@ -2446,6 +2486,10 @@ twrite(const char *buf, int buflen, int show_ctrl) 172 | u = buf[n] & 0xFF; 173 | charsize = 1; 174 | } 175 | + if (su0 && !su) { 176 | + twrite_aborted = 1; 177 | + break; // ESU - allow rendering before a new BSU 178 | + } 179 | if (show_ctrl && ISCONTROL(u)) { 180 | if (u & 0x80) { 181 | u &= 0x7f; 182 | diff --git a/st.info b/st.info 183 | index 8201ad6..b32b446 100644 184 | --- a/st.info 185 | +++ b/st.info 186 | @@ -191,6 +191,7 @@ st-mono| simpleterm monocolor, 187 | Ms=\E]52;%p1%s;%p2%s\007, 188 | Se=\E[2 q, 189 | Ss=\E[%p1%d q, 190 | + Sync=\EP=%p1%ds\E\\, 191 | 192 | st| simpleterm, 193 | use=st-mono, 194 | diff --git a/x.c b/x.c 195 | index 210f184..27ff4e2 100644 196 | --- a/x.c 197 | +++ b/x.c 198 | @@ -1861,6 +1861,9 @@ resize(XEvent *e) 199 | cresize(e->xconfigure.width, e->xconfigure.height); 200 | } 201 | 202 | +int tinsync(uint); 203 | +int ttyread_pending(); 204 | + 205 | void 206 | run(void) 207 | { 208 | @@ -1895,7 +1898,7 @@ run(void) 209 | FD_SET(ttyfd, &rfd); 210 | FD_SET(xfd, &rfd); 211 | 212 | - if (XPending(xw.dpy)) 213 | + if (XPending(xw.dpy) || ttyread_pending()) 214 | timeout = 0; /* existing events might not set xfd */ 215 | 216 | seltv.tv_sec = timeout / 1E3; 217 | @@ -1909,7 +1912,8 @@ run(void) 218 | } 219 | clock_gettime(CLOCK_MONOTONIC, &now); 220 | 221 | - if (FD_ISSET(ttyfd, &rfd)) 222 | + int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending(); 223 | + if (ttyin) 224 | ttyread(); 225 | 226 | xev = 0; 227 | @@ -1933,7 +1937,7 @@ run(void) 228 | * maximum latency intervals during `cat huge.txt`, and perfect 229 | * sync with periodic updates from animations/key-repeats/etc. 230 | */ 231 | - if (FD_ISSET(ttyfd, &rfd) || xev) { 232 | + if (ttyin || xev) { 233 | if (!drawing) { 234 | trigger = now; 235 | drawing = 1; 236 | @@ -1944,6 +1948,18 @@ run(void) 237 | continue; /* we have time, try to find idle */ 238 | } 239 | 240 | + if (tinsync(su_timeout)) { 241 | + /* 242 | + * on synchronized-update draw-suspension: don't reset 243 | + * drawing so that we draw ASAP once we can (just after 244 | + * ESU). it won't be too soon because we already can 245 | + * draw now but we skip. we set timeout > 0 to draw on 246 | + * SU-timeout even without new content. 247 | + */ 248 | + timeout = minlatency; 249 | + continue; 250 | + } 251 | + 252 | /* idle detected or maxlatency exhausted -> draw */ 253 | timeout = -1; 254 | if (blinktimeout && tattrset(ATTR_BLINK)) { 255 | 256 | base-commit: b27a383a3acc7decf00e6e889fca265430b5d329 257 | -- 258 | 2.17.1 259 | 260 | -------------------------------------------------------------------------------- /patches/st-keyboard_select-20200617-9ba7ecf.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index 6f05dce..54612d1 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -199,6 +199,7 @@ static Shortcut shortcuts[] = { 6 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 7 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 8 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 9 | + { TERMMOD, XK_Escape, keyboard_select,{.i = 0} }, 10 | }; 11 | 12 | /* 13 | diff --git a/st.c b/st.c 14 | index ef8abd5..0c6c6ca 100644 15 | --- a/st.c 16 | +++ b/st.c 17 | @@ -16,6 +16,8 @@ 18 | #include 19 | #include 20 | #include 21 | +#include 22 | +#include 23 | 24 | #include "st.h" 25 | #include "win.h" 26 | @@ -2487,6 +2489,9 @@ tresize(int col, int row) 27 | int *bp; 28 | TCursor c; 29 | 30 | + if ( row < term.row || col < term.col ) 31 | + toggle_winmode(trt_kbdselect(XK_Escape, NULL, 0)); 32 | + 33 | if (col < 1 || row < 1) { 34 | fprintf(stderr, 35 | "tresize: error resizing to %dx%d\n", col, row); 36 | @@ -2612,3 +2617,220 @@ redraw(void) 37 | tfulldirt(); 38 | draw(); 39 | } 40 | + 41 | +void set_notifmode(int type, KeySym ksym) { 42 | + static char *lib[] = { " MOVE ", " SEL "}; 43 | + static Glyph *g, *deb, *fin; 44 | + static int col, bot; 45 | + 46 | + if ( ksym == -1 ) { 47 | + free(g); 48 | + col = term.col, bot = term.bot; 49 | + g = xmalloc(col * sizeof(Glyph)); 50 | + memcpy(g, term.line[bot], col * sizeof(Glyph)); 51 | + 52 | + } 53 | + else if ( ksym == -2 ) 54 | + memcpy(term.line[bot], g, col * sizeof(Glyph)); 55 | + 56 | + if ( type < 2 ) { 57 | + char *z = lib[type]; 58 | + for (deb = &term.line[bot][col - 6], fin = &term.line[bot][col]; deb < fin; z++, deb++) 59 | + deb->mode = ATTR_REVERSE, 60 | + deb->u = *z, 61 | + deb->fg = defaultfg, deb->bg = defaultbg; 62 | + } 63 | + else if ( type < 5 ) 64 | + memcpy(term.line[bot], g, col * sizeof(Glyph)); 65 | + else { 66 | + for (deb = &term.line[bot][0], fin = &term.line[bot][col]; deb < fin; deb++) 67 | + deb->mode = ATTR_REVERSE, 68 | + deb->u = ' ', 69 | + deb->fg = defaultfg, deb->bg = defaultbg; 70 | + term.line[bot][0].u = ksym; 71 | + } 72 | + 73 | + term.dirty[bot] = 1; 74 | + drawregion(0, bot, col, bot + 1); 75 | +} 76 | + 77 | +void select_or_drawcursor(int selectsearch_mode, int type) { 78 | + int done = 0; 79 | + 80 | + if ( selectsearch_mode & 1 ) { 81 | + selextend(term.c.x, term.c.y, type, done); 82 | + xsetsel(getsel()); 83 | + } 84 | + else 85 | + xdrawcursor(term.c.x, term.c.y, term.line[term.c.y][term.c.x], 86 | + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 87 | +} 88 | + 89 | +void search(int selectsearch_mode, Rune *target, int ptarget, int incr, int type, TCursor *cu) { 90 | + Rune *r; 91 | + int i, bound = (term.col * cu->y + cu->x) * (incr > 0) + incr; 92 | + 93 | + for (i = term.col * term.c.y + term.c.x + incr; i != bound; i += incr) { 94 | + for (r = target; r - target < ptarget; r++) { 95 | + if ( *r == term.line[(i + r - target) / term.col][(i + r - target) % term.col].u ) { 96 | + if ( r - target == ptarget - 1 ) break; 97 | + } else { 98 | + r = NULL; 99 | + break; 100 | + } 101 | + } 102 | + if ( r != NULL ) break; 103 | + } 104 | + 105 | + if ( i != bound ) { 106 | + term.c.y = i / term.col, term.c.x = i % term.col; 107 | + select_or_drawcursor(selectsearch_mode, type); 108 | + } 109 | +} 110 | + 111 | +int trt_kbdselect(KeySym ksym, char *buf, int len) { 112 | + static TCursor cu; 113 | + static Rune target[64]; 114 | + static int type = 1, ptarget, in_use; 115 | + static int sens, quant; 116 | + static char selectsearch_mode; 117 | + int i, bound, *xy; 118 | + 119 | + 120 | + if ( selectsearch_mode & 2 ) { 121 | + if ( ksym == XK_Return ) { 122 | + selectsearch_mode ^= 2; 123 | + set_notifmode(selectsearch_mode, -2); 124 | + if ( ksym == XK_Escape ) ptarget = 0; 125 | + return 0; 126 | + } 127 | + else if ( ksym == XK_BackSpace ) { 128 | + if ( !ptarget ) return 0; 129 | + term.line[term.bot][ptarget--].u = ' '; 130 | + } 131 | + else if ( len < 1 ) { 132 | + return 0; 133 | + } 134 | + else if ( ptarget == term.col || ksym == XK_Escape ) { 135 | + return 0; 136 | + } 137 | + else { 138 | + utf8decode(buf, &target[ptarget++], len); 139 | + term.line[term.bot][ptarget].u = target[ptarget - 1]; 140 | + } 141 | + 142 | + if ( ksym != XK_BackSpace ) 143 | + search(selectsearch_mode, &target[0], ptarget, sens, type, &cu); 144 | + 145 | + term.dirty[term.bot] = 1; 146 | + drawregion(0, term.bot, term.col, term.bot + 1); 147 | + return 0; 148 | + } 149 | + 150 | + switch ( ksym ) { 151 | + case -1 : 152 | + in_use = 1; 153 | + cu.x = term.c.x, cu.y = term.c.y; 154 | + set_notifmode(0, ksym); 155 | + return MODE_KBDSELECT; 156 | + case XK_s : 157 | + if ( selectsearch_mode & 1 ) 158 | + selclear(); 159 | + else 160 | + selstart(term.c.x, term.c.y, 0); 161 | + set_notifmode(selectsearch_mode ^= 1, ksym); 162 | + break; 163 | + case XK_t : 164 | + selextend(term.c.x, term.c.y, type ^= 3, i = 0); /* 2 fois */ 165 | + selextend(term.c.x, term.c.y, type, i = 0); 166 | + break; 167 | + case XK_slash : 168 | + case XK_KP_Divide : 169 | + case XK_question : 170 | + ksym &= XK_question; /* Divide to slash */ 171 | + sens = (ksym == XK_slash) ? -1 : 1; 172 | + ptarget = 0; 173 | + set_notifmode(15, ksym); 174 | + selectsearch_mode ^= 2; 175 | + break; 176 | + case XK_Escape : 177 | + if ( !in_use ) break; 178 | + selclear(); 179 | + case XK_Return : 180 | + set_notifmode(4, ksym); 181 | + term.c.x = cu.x, term.c.y = cu.y; 182 | + select_or_drawcursor(selectsearch_mode = 0, type); 183 | + in_use = quant = 0; 184 | + return MODE_KBDSELECT; 185 | + case XK_n : 186 | + case XK_N : 187 | + if ( ptarget ) 188 | + search(selectsearch_mode, &target[0], ptarget, (ksym == XK_n) ? -1 : 1, type, &cu); 189 | + break; 190 | + case XK_BackSpace : 191 | + term.c.x = 0; 192 | + select_or_drawcursor(selectsearch_mode, type); 193 | + break; 194 | + case XK_dollar : 195 | + term.c.x = term.col - 1; 196 | + select_or_drawcursor(selectsearch_mode, type); 197 | + break; 198 | + case XK_Home : 199 | + term.c.x = 0, term.c.y = 0; 200 | + select_or_drawcursor(selectsearch_mode, type); 201 | + break; 202 | + case XK_End : 203 | + term.c.x = cu.x, term.c.y = cu.y; 204 | + select_or_drawcursor(selectsearch_mode, type); 205 | + break; 206 | + case XK_Page_Up : 207 | + case XK_Page_Down : 208 | + term.c.y = (ksym == XK_Prior ) ? 0 : cu.y; 209 | + select_or_drawcursor(selectsearch_mode, type); 210 | + break; 211 | + case XK_exclam : 212 | + term.c.x = term.col >> 1; 213 | + select_or_drawcursor(selectsearch_mode, type); 214 | + break; 215 | + case XK_asterisk : 216 | + case XK_KP_Multiply : 217 | + term.c.x = term.col >> 1; 218 | + case XK_underscore : 219 | + term.c.y = cu.y >> 1; 220 | + select_or_drawcursor(selectsearch_mode, type); 221 | + break; 222 | + default : 223 | + if ( ksym >= XK_0 && ksym <= XK_9 ) { /* 0-9 keyboard */ 224 | + quant = (quant * 10) + (ksym ^ XK_0); 225 | + return 0; 226 | + } 227 | + else if ( ksym >= XK_KP_0 && ksym <= XK_KP_9 ) { /* 0-9 numpad */ 228 | + quant = (quant * 10) + (ksym ^ XK_KP_0); 229 | + return 0; 230 | + } 231 | + else if ( ksym == XK_k || ksym == XK_h ) 232 | + i = ksym & 1; 233 | + else if ( ksym == XK_l || ksym == XK_j ) 234 | + i = ((ksym & 6) | 4) >> 1; 235 | + else if ( (XK_Home & ksym) != XK_Home || (i = (ksym ^ XK_Home) - 1) > 3 ) 236 | + break; 237 | + 238 | + xy = (i & 1) ? &term.c.y : &term.c.x; 239 | + sens = (i & 2) ? 1 : -1; 240 | + bound = (i >> 1 ^ 1) ? 0 : (i ^ 3) ? term.col - 1 : term.bot; 241 | + 242 | + if ( quant == 0 ) 243 | + quant++; 244 | + 245 | + if ( *xy == bound && ((sens < 0 && bound == 0) || (sens > 0 && bound > 0)) ) 246 | + break; 247 | + 248 | + *xy += quant * sens; 249 | + if ( *xy < 0 || ( bound > 0 && *xy > bound) ) 250 | + *xy = bound; 251 | + 252 | + select_or_drawcursor(selectsearch_mode, type); 253 | + } 254 | + quant = 0; 255 | + return 0; 256 | +} 257 | diff --git a/st.h b/st.h 258 | index 3d351b6..2d1a11b 100644 259 | --- a/st.h 260 | +++ b/st.h 261 | @@ -110,6 +110,7 @@ size_t utf8encode(Rune, char *); 262 | void *xmalloc(size_t); 263 | void *xrealloc(void *, size_t); 264 | char *xstrdup(char *); 265 | +int trt_kbdselect(KeySym, char *, int); 266 | 267 | /* config.h globals */ 268 | extern char *utmp; 269 | diff --git a/win.h b/win.h 270 | index a6ef1b9..9a47fbb 100644 271 | --- a/win.h 272 | +++ b/win.h 273 | @@ -21,6 +21,7 @@ enum win_mode { 274 | MODE_NUMLOCK = 1 << 17, 275 | MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 276 | |MODE_MOUSEMANY, 277 | + MODE_KBDSELECT = 1 << 18, 278 | }; 279 | 280 | void xbell(void); 281 | @@ -36,4 +37,6 @@ void xsetmode(int, unsigned int); 282 | void xsetpointermotion(int); 283 | void xsetsel(char *); 284 | int xstartdraw(void); 285 | +void toggle_winmode(int); 286 | +void keyboard_select(const Arg *); 287 | void xximspot(int, int); 288 | diff --git a/x.c b/x.c 289 | index 210f184..b77ce54 100644 290 | --- a/x.c 291 | +++ b/x.c 292 | @@ -1800,6 +1800,12 @@ kpress(XEvent *ev) 293 | len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); 294 | else 295 | len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 296 | + if ( IS_SET(MODE_KBDSELECT) ) { 297 | + if ( match(XK_NO_MOD, e->state) || 298 | + (XK_Shift_L | XK_Shift_R) & e->state ) 299 | + win.mode ^= trt_kbdselect(ksym, buf, len); 300 | + return; 301 | + } 302 | /* 1. shortcuts */ 303 | for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 304 | if (ksym == bp->keysym && match(bp->mod, e->state)) { 305 | @@ -1977,6 +1983,14 @@ usage(void) 306 | " [stty_args ...]\n", argv0, argv0); 307 | } 308 | 309 | +void toggle_winmode(int flag) { 310 | + win.mode ^= flag; 311 | +} 312 | + 313 | +void keyboard_select(const Arg *dummy) { 314 | + win.mode ^= trt_kbdselect(-1, NULL, 0); 315 | +} 316 | + 317 | int 318 | main(int argc, char *argv[]) 319 | { 320 | -------------------------------------------------------------------------------- /patches/st-scrollback-20210507-4536f46.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config.def.h b/config.def.h 2 | index 6f05dce..93cbcc0 100644 3 | --- a/config.def.h 4 | +++ b/config.def.h 5 | @@ -199,6 +199,8 @@ static Shortcut shortcuts[] = { 6 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 7 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 8 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 9 | + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 10 | + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 11 | }; 12 | 13 | /* 14 | diff --git a/st.c b/st.c 15 | index ebdf360..817cc47 100644 16 | --- a/st.c 17 | +++ b/st.c 18 | @@ -35,6 +35,7 @@ 19 | #define ESC_ARG_SIZ 16 20 | #define STR_BUF_SIZ ESC_BUF_SIZ 21 | #define STR_ARG_SIZ ESC_ARG_SIZ 22 | +#define HISTSIZE 2000 23 | 24 | /* macros */ 25 | #define IS_SET(flag) ((term.mode & (flag)) != 0) 26 | @@ -42,6 +43,9 @@ 27 | #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 28 | #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 29 | #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 30 | +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 31 | + term.scr + HISTSIZE + 1) % HISTSIZE] : \ 32 | + term.line[(y) - term.scr]) 33 | 34 | enum term_mode { 35 | MODE_WRAP = 1 << 0, 36 | @@ -115,6 +119,9 @@ typedef struct { 37 | int col; /* nb col */ 38 | Line *line; /* screen */ 39 | Line *alt; /* alternate screen */ 40 | + Line hist[HISTSIZE]; /* history buffer */ 41 | + int histi; /* history index */ 42 | + int scr; /* scroll back */ 43 | int *dirty; /* dirtyness of lines */ 44 | TCursor c; /* cursor */ 45 | int ocx; /* old cursor col */ 46 | @@ -184,8 +191,8 @@ static void tnewline(int); 47 | static void tputtab(int); 48 | static void tputc(Rune); 49 | static void treset(void); 50 | -static void tscrollup(int, int); 51 | -static void tscrolldown(int, int); 52 | +static void tscrollup(int, int, int); 53 | +static void tscrolldown(int, int, int); 54 | static void tsetattr(const int *, int); 55 | static void tsetchar(Rune, const Glyph *, int, int); 56 | static void tsetdirt(int, int); 57 | @@ -416,10 +423,10 @@ tlinelen(int y) 58 | { 59 | int i = term.col; 60 | 61 | - if (term.line[y][i - 1].mode & ATTR_WRAP) 62 | + if (TLINE(y)[i - 1].mode & ATTR_WRAP) 63 | return i; 64 | 65 | - while (i > 0 && term.line[y][i - 1].u == ' ') 66 | + while (i > 0 && TLINE(y)[i - 1].u == ' ') 67 | --i; 68 | 69 | return i; 70 | @@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction) 71 | * Snap around if the word wraps around at the end or 72 | * beginning of a line. 73 | */ 74 | - prevgp = &term.line[*y][*x]; 75 | + prevgp = &TLINE(*y)[*x]; 76 | prevdelim = ISDELIM(prevgp->u); 77 | for (;;) { 78 | newx = *x + direction; 79 | @@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction) 80 | yt = *y, xt = *x; 81 | else 82 | yt = newy, xt = newx; 83 | - if (!(term.line[yt][xt].mode & ATTR_WRAP)) 84 | + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 85 | break; 86 | } 87 | 88 | if (newx >= tlinelen(newy)) 89 | break; 90 | 91 | - gp = &term.line[newy][newx]; 92 | + gp = &TLINE(newy)[newx]; 93 | delim = ISDELIM(gp->u); 94 | if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 95 | || (delim && gp->u != prevgp->u))) 96 | @@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction) 97 | *x = (direction < 0) ? 0 : term.col - 1; 98 | if (direction < 0) { 99 | for (; *y > 0; *y += direction) { 100 | - if (!(term.line[*y-1][term.col-1].mode 101 | + if (!(TLINE(*y-1)[term.col-1].mode 102 | & ATTR_WRAP)) { 103 | break; 104 | } 105 | } 106 | } else if (direction > 0) { 107 | for (; *y < term.row-1; *y += direction) { 108 | - if (!(term.line[*y][term.col-1].mode 109 | + if (!(TLINE(*y)[term.col-1].mode 110 | & ATTR_WRAP)) { 111 | break; 112 | } 113 | @@ -609,13 +616,13 @@ getsel(void) 114 | } 115 | 116 | if (sel.type == SEL_RECTANGULAR) { 117 | - gp = &term.line[y][sel.nb.x]; 118 | + gp = &TLINE(y)[sel.nb.x]; 119 | lastx = sel.ne.x; 120 | } else { 121 | - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 122 | + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; 123 | lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 124 | } 125 | - last = &term.line[y][MIN(lastx, linelen-1)]; 126 | + last = &TLINE(y)[MIN(lastx, linelen-1)]; 127 | while (last >= gp && last->u == ' ') 128 | --last; 129 | 130 | @@ -850,6 +857,9 @@ void 131 | ttywrite(const char *s, size_t n, int may_echo) 132 | { 133 | const char *next; 134 | + Arg arg = (Arg) { .i = term.scr }; 135 | + 136 | + kscrolldown(&arg); 137 | 138 | if (may_echo && IS_SET(MODE_ECHO)) 139 | twrite(s, n, 1); 140 | @@ -1061,13 +1071,53 @@ tswapscreen(void) 141 | } 142 | 143 | void 144 | -tscrolldown(int orig, int n) 145 | +kscrolldown(const Arg* a) 146 | +{ 147 | + int n = a->i; 148 | + 149 | + if (n < 0) 150 | + n = term.row + n; 151 | + 152 | + if (n > term.scr) 153 | + n = term.scr; 154 | + 155 | + if (term.scr > 0) { 156 | + term.scr -= n; 157 | + selscroll(0, -n); 158 | + tfulldirt(); 159 | + } 160 | +} 161 | + 162 | +void 163 | +kscrollup(const Arg* a) 164 | +{ 165 | + int n = a->i; 166 | + 167 | + if (n < 0) 168 | + n = term.row + n; 169 | + 170 | + if (term.scr <= HISTSIZE-n) { 171 | + term.scr += n; 172 | + selscroll(0, n); 173 | + tfulldirt(); 174 | + } 175 | +} 176 | + 177 | +void 178 | +tscrolldown(int orig, int n, int copyhist) 179 | { 180 | int i; 181 | Line temp; 182 | 183 | LIMIT(n, 0, term.bot-orig+1); 184 | 185 | + if (copyhist) { 186 | + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 187 | + temp = term.hist[term.histi]; 188 | + term.hist[term.histi] = term.line[term.bot]; 189 | + term.line[term.bot] = temp; 190 | + } 191 | + 192 | tsetdirt(orig, term.bot-n); 193 | tclearregion(0, term.bot-n+1, term.col-1, term.bot); 194 | 195 | @@ -1077,17 +1127,28 @@ tscrolldown(int orig, int n) 196 | term.line[i-n] = temp; 197 | } 198 | 199 | - selscroll(orig, n); 200 | + if (term.scr == 0) 201 | + selscroll(orig, n); 202 | } 203 | 204 | void 205 | -tscrollup(int orig, int n) 206 | +tscrollup(int orig, int n, int copyhist) 207 | { 208 | int i; 209 | Line temp; 210 | 211 | LIMIT(n, 0, term.bot-orig+1); 212 | 213 | + if (copyhist) { 214 | + term.histi = (term.histi + 1) % HISTSIZE; 215 | + temp = term.hist[term.histi]; 216 | + term.hist[term.histi] = term.line[orig]; 217 | + term.line[orig] = temp; 218 | + } 219 | + 220 | + if (term.scr > 0 && term.scr < HISTSIZE) 221 | + term.scr = MIN(term.scr + n, HISTSIZE-1); 222 | + 223 | tclearregion(0, orig, term.col-1, orig+n-1); 224 | tsetdirt(orig+n, term.bot); 225 | 226 | @@ -1097,7 +1158,8 @@ tscrollup(int orig, int n) 227 | term.line[i+n] = temp; 228 | } 229 | 230 | - selscroll(orig, -n); 231 | + if (term.scr == 0) 232 | + selscroll(orig, -n); 233 | } 234 | 235 | void 236 | @@ -1126,7 +1188,7 @@ tnewline(int first_col) 237 | int y = term.c.y; 238 | 239 | if (y == term.bot) { 240 | - tscrollup(term.top, 1); 241 | + tscrollup(term.top, 1, 1); 242 | } else { 243 | y++; 244 | } 245 | @@ -1291,14 +1353,14 @@ void 246 | tinsertblankline(int n) 247 | { 248 | if (BETWEEN(term.c.y, term.top, term.bot)) 249 | - tscrolldown(term.c.y, n); 250 | + tscrolldown(term.c.y, n, 0); 251 | } 252 | 253 | void 254 | tdeleteline(int n) 255 | { 256 | if (BETWEEN(term.c.y, term.top, term.bot)) 257 | - tscrollup(term.c.y, n); 258 | + tscrollup(term.c.y, n, 0); 259 | } 260 | 261 | int32_t 262 | @@ -1735,11 +1797,11 @@ csihandle(void) 263 | break; 264 | case 'S': /* SU -- Scroll line up */ 265 | DEFAULT(csiescseq.arg[0], 1); 266 | - tscrollup(term.top, csiescseq.arg[0]); 267 | + tscrollup(term.top, csiescseq.arg[0], 0); 268 | break; 269 | case 'T': /* SD -- Scroll line down */ 270 | DEFAULT(csiescseq.arg[0], 1); 271 | - tscrolldown(term.top, csiescseq.arg[0]); 272 | + tscrolldown(term.top, csiescseq.arg[0], 0); 273 | break; 274 | case 'L': /* IL -- Insert blank lines */ 275 | DEFAULT(csiescseq.arg[0], 1); 276 | @@ -2251,7 +2313,7 @@ eschandle(uchar ascii) 277 | return 0; 278 | case 'D': /* IND -- Linefeed */ 279 | if (term.c.y == term.bot) { 280 | - tscrollup(term.top, 1); 281 | + tscrollup(term.top, 1, 1); 282 | } else { 283 | tmoveto(term.c.x, term.c.y+1); 284 | } 285 | @@ -2264,7 +2326,7 @@ eschandle(uchar ascii) 286 | break; 287 | case 'M': /* RI -- Reverse index */ 288 | if (term.c.y == term.top) { 289 | - tscrolldown(term.top, 1); 290 | + tscrolldown(term.top, 1, 1); 291 | } else { 292 | tmoveto(term.c.x, term.c.y-1); 293 | } 294 | @@ -2474,7 +2536,7 @@ twrite(const char *buf, int buflen, int show_ctrl) 295 | void 296 | tresize(int col, int row) 297 | { 298 | - int i; 299 | + int i, j; 300 | int minrow = MIN(row, term.row); 301 | int mincol = MIN(col, term.col); 302 | int *bp; 303 | @@ -2511,6 +2573,14 @@ tresize(int col, int row) 304 | term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 305 | term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 306 | 307 | + for (i = 0; i < HISTSIZE; i++) { 308 | + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 309 | + for (j = mincol; j < col; j++) { 310 | + term.hist[i][j] = term.c.attr; 311 | + term.hist[i][j].u = ' '; 312 | + } 313 | + } 314 | + 315 | /* resize each row to new width, zero-pad if needed */ 316 | for (i = 0; i < minrow; i++) { 317 | term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 318 | @@ -2569,7 +2639,7 @@ drawregion(int x1, int y1, int x2, int y2) 319 | continue; 320 | 321 | term.dirty[y] = 0; 322 | - xdrawline(term.line[y], x1, y, x2); 323 | + xdrawline(TLINE(y), x1, y, x2); 324 | } 325 | } 326 | 327 | @@ -2590,8 +2660,9 @@ draw(void) 328 | cx--; 329 | 330 | drawregion(0, 0, term.col, term.row); 331 | - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 332 | - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 333 | + if (term.scr == 0) 334 | + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 335 | + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 336 | term.ocx = cx; 337 | term.ocy = term.c.y; 338 | xfinishdraw(); 339 | diff --git a/st.h b/st.h 340 | index fa2eddf..adda2db 100644 341 | --- a/st.h 342 | +++ b/st.h 343 | @@ -81,6 +81,8 @@ void die(const char *, ...); 344 | void redraw(void); 345 | void draw(void); 346 | 347 | +void kscrolldown(const Arg *); 348 | +void kscrollup(const Arg *); 349 | void printscreen(const Arg *); 350 | void printsel(const Arg *); 351 | void sendbreak(const Arg *); 352 | -------------------------------------------------------------------------------- /FAQ: -------------------------------------------------------------------------------- 1 | ## Why does st not handle utmp entries? 2 | 3 | Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. 4 | 5 | 6 | ## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! 7 | 8 | It means that st doesn’t have any terminfo entry on your system. Chances are 9 | you did not `make install`. If you just want to test it without installing it, 10 | you can manually run `tic -sx st.info`. 11 | 12 | 13 | ## Nothing works, and nothing is said about an unknown terminal! 14 | 15 | * Some programs just assume they’re running in xterm i.e. they don’t rely on 16 | terminfo. What you see is the current state of the “xterm compliance”. 17 | * Some programs don’t complain about the lacking st description and default to 18 | another terminal. In that case see the question about terminfo. 19 | 20 | 21 | ## How do I scroll back up? 22 | 23 | * Using a terminal multiplexer. 24 | * `st -e tmux` using C-b [ 25 | * `st -e screen` using C-a ESC 26 | * Using the excellent tool of [scroll](https://git.suckless.org/scroll/). 27 | * Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). 28 | 29 | 30 | ## I would like to have utmp and/or scroll functionality by default 31 | 32 | You can add the absolute path of both programs in your config.h file. You only 33 | have to modify the value of utmp and scroll variables. 34 | 35 | 36 | ## Why doesn't the Del key work in some programs? 37 | 38 | Taken from the terminfo manpage: 39 | 40 | If the terminal has a keypad that transmits codes when the keys 41 | are pressed, this information can be given. Note that it is not 42 | possible to handle terminals where the keypad only works in 43 | local (this applies, for example, to the unshifted HP 2621 keys). 44 | If the keypad can be set to transmit or not transmit, give these 45 | codes as smkx and rmkx. Otherwise the keypad is assumed to 46 | always transmit. 47 | 48 | In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that 49 | applications which want to test against keypad keys send these 50 | sequences. 51 | 52 | But buggy applications (like bash and irssi, for example) don't do this. A fast 53 | solution for them is to use the following command: 54 | 55 | $ printf '\033[?1h\033=' >/dev/tty 56 | 57 | or 58 | $ tput smkx 59 | 60 | In the case of bash, readline is used. Readline has a different note in its 61 | manpage about this issue: 62 | 63 | enable-keypad (Off) 64 | When set to On, readline will try to enable the 65 | application keypad when it is called. Some systems 66 | need this to enable arrow keys. 67 | 68 | Adding this option to your .inputrc will fix the keypad problem for all 69 | applications using readline. 70 | 71 | If you are using zsh, then read the zsh FAQ 72 | : 73 | 74 | It should be noted that the O / [ confusion can occur with other keys 75 | such as Home and End. Some systems let you query the key sequences 76 | sent by these keys from the system's terminal database, terminfo. 77 | Unfortunately, the key sequences given there typically apply to the 78 | mode that is not the one zsh uses by default (it's the "application" 79 | mode rather than the "raw" mode). Explaining the use of terminfo is 80 | outside of the scope of this FAQ, but if you wish to use the key 81 | sequences given there you can tell the line editor to turn on 82 | "application" mode when it starts and turn it off when it stops: 83 | 84 | function zle-line-init () { echoti smkx } 85 | function zle-line-finish () { echoti rmkx } 86 | zle -N zle-line-init 87 | zle -N zle-line-finish 88 | 89 | Putting these lines into your .zshrc will fix the problems. 90 | 91 | 92 | ## How can I use meta in 8bit mode? 93 | 94 | St supports meta in 8bit mode, but the default terminfo entry doesn't 95 | use this capability. If you want it, you have to use the 'st-meta' value 96 | in TERM. 97 | 98 | 99 | ## I cannot compile st in OpenBSD 100 | 101 | OpenBSD lacks librt, despite it being mandatory in POSIX 102 | . 103 | If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and 104 | st will compile without any loss of functionality, because all the functions are 105 | included in libc on this platform. 106 | 107 | 108 | ## The Backspace Case 109 | 110 | St is emulating the Linux way of handling backspace being delete and delete being 111 | backspace. 112 | 113 | This is an issue that was discussed in suckless mailing list 114 | . Here is why some old grumpy 115 | terminal users wants its backspace to be how he feels it: 116 | 117 | Well, I am going to comment why I want to change the behaviour 118 | of this key. When ASCII was defined in 1968, communication 119 | with computers was done using punched cards, or hardcopy 120 | terminals (basically a typewriter machine connected with the 121 | computer using a serial port). ASCII defines DELETE as 7F, 122 | because, in punched-card terms, it means all the holes of the 123 | card punched; it is thus a kind of 'physical delete'. In the 124 | same way, the BACKSPACE key was a non-destructive backspace, 125 | as on a typewriter. So, if you wanted to delete a character, 126 | you had to BACKSPACE and then DELETE. Another use of BACKSPACE 127 | was to type accented characters, for example 'a BACKSPACE `'. 128 | The VT100 had no BACKSPACE key; it was generated using the 129 | CONTROL key as another control character (CONTROL key sets to 130 | 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code 131 | 0x08)), but it had a DELETE key in a similar position where 132 | the BACKSPACE key is located today on common PC keyboards. 133 | All the terminal emulators emulated the difference between 134 | these keys correctly: the backspace key generated a BACKSPACE 135 | (^H) and delete key generated a DELETE (^?). 136 | 137 | But a problem arose when Linus Torvalds wrote Linux. Unlike 138 | earlier terminals, the Linux virtual terminal (the terminal 139 | emulator integrated in the kernel) returned a DELETE when 140 | backspace was pressed, due to the VT100 having a DELETE key in 141 | the same position. This created a lot of problems (see [1] 142 | and [2]). Since Linux has become the king, a lot of terminal 143 | emulators today generate a DELETE when the backspace key is 144 | pressed in order to avoid problems with Linux. The result is 145 | that the only way of generating a BACKSPACE on these systems 146 | is by using CONTROL + H. (I also think that emacs had an 147 | important point here because the CONTROL + H prefix is used 148 | in emacs in some commands (help commands).) 149 | 150 | From point of view of the kernel, you can change the key 151 | for deleting a previous character with stty erase. When you 152 | connect a real terminal into a machine you describe the type 153 | of terminal, so getty configures the correct value of stty 154 | erase for this terminal. In the case of terminal emulators, 155 | however, you don't have any getty that can set the correct 156 | value of stty erase, so you always get the default value. 157 | For this reason, it is necessary to add 'stty erase ^H' to your 158 | profile if you have changed the value of the backspace key. 159 | Of course, another solution is for st itself to modify the 160 | value of stty erase. I usually have the inverse problem: 161 | when I connect to non-Unix machines, I have to press CONTROL + 162 | h to get a BACKSPACE. The inverse problem occurs when a user 163 | connects to my Unix machines from a different system with a 164 | correct backspace key. 165 | 166 | [1] http://www.ibb.net/~anne/keyboard.html 167 | [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html 168 | 169 | 170 | ## But I really want the old grumpy behaviour of my terminal 171 | 172 | Apply [1]. 173 | 174 | [1] https://st.suckless.org/patches/delkey 175 | 176 | 177 | ## Why do images not work in st using the w3m image hack? 178 | 179 | w3mimg uses a hack that draws an image on top of the terminal emulator Drawable 180 | window. The hack relies on the terminal to use a single buffer to draw its 181 | contents directly. 182 | 183 | st uses double-buffered drawing so the image is quickly replaced and may show a 184 | short flicker effect. 185 | 186 | Below is a patch example to change st double-buffering to a single Drawable 187 | buffer. 188 | 189 | diff --git a/x.c b/x.c 190 | --- a/x.c 191 | +++ b/x.c 192 | @@ -732,10 +732,6 @@ xresize(int col, int row) 193 | win.tw = col * win.cw; 194 | win.th = row * win.ch; 195 | 196 | - XFreePixmap(xw.dpy, xw.buf); 197 | - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 198 | - DefaultDepth(xw.dpy, xw.scr)); 199 | - XftDrawChange(xw.draw, xw.buf); 200 | xclear(0, 0, win.w, win.h); 201 | 202 | /* resize to new width */ 203 | @@ -1148,8 +1144,7 @@ xinit(int cols, int rows) 204 | gcvalues.graphics_exposures = False; 205 | dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, 206 | &gcvalues); 207 | - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 208 | - DefaultDepth(xw.dpy, xw.scr)); 209 | + xw.buf = xw.win; 210 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 211 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 212 | 213 | @@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) 214 | void 215 | xfinishdraw(void) 216 | { 217 | - XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 218 | - win.h, 0, 0); 219 | XSetForeground(xw.dpy, dc.gc, 220 | dc.col[IS_SET(MODE_REVERSE)? 221 | defaultfg : defaultbg].pixel); 222 | 223 | 224 | ## BadLength X error in Xft when trying to render emoji 225 | 226 | Xft makes st crash when rendering color emojis with the following error: 227 | 228 | "X Error of failed request: BadLength (poly request too large or internal Xlib length error)" 229 | Major opcode of failed request: 139 (RENDER) 230 | Minor opcode of failed request: 20 (RenderAddGlyphs) 231 | Serial number of failed request: 1595 232 | Current serial number in output stream: 1818" 233 | 234 | This is a known bug in Xft (not st) which happens on some platforms and 235 | combination of particular fonts and fontconfig settings. 236 | 237 | See also: 238 | https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 239 | https://bugs.freedesktop.org/show_bug.cgi?id=107534 240 | https://bugzilla.redhat.com/show_bug.cgi?id=1498269 241 | 242 | The solution is to remove color emoji fonts or disable this in the fontconfig 243 | XML configuration. As an ugly workaround (which may work only on newer 244 | fontconfig versions (FC_COLOR)), the following code can be used to mask color 245 | fonts: 246 | 247 | FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); 248 | 249 | Please don't bother reporting this bug to st, but notify the upstream Xft 250 | developers about fixing this bug. 251 | 252 | As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5: 253 | https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS 254 | -------------------------------------------------------------------------------- /patches/st-boxdraw_v2-0.8.3.diff: -------------------------------------------------------------------------------- 1 | From 3f3b80b9966c60086f4ed80ce4de0cbf03468d36 Mon Sep 17 00:00:00 2001 2 | From: "Avi Halachmi (:avih)" 3 | Date: Wed, 26 Dec 2018 14:51:45 +0200 4 | Subject: [PATCH] boxdraw_v2: custom render lines/blocks/braille for perfect 5 | alignment 6 | 7 | It seems impossible to ensure that blocks and line drawing glyphs 8 | align without visible gaps for all combinations of arbitrary font, 9 | size and width/height scale factor. 10 | 11 | This commit adds options to render most of the lines/blocks and 12 | braille codepoints without using the font such that they align 13 | perfectly regardless of font, size or other configuration values. 14 | 15 | Supported codepoints are U+2500 - U+259F except dashes/diagonals, 16 | and U28XX. 17 | 18 | The lines/blocks data is stored as 16-bit values at boxdraw_data.h 19 | 20 | boxdraw/braille are independent, disabled by default at config[.def].h 21 | --- 22 | Makefile | 3 +- 23 | boxdraw.c | 194 ++++++++++++++++++++++++++++++++++++++++++++ 24 | boxdraw_data.h | 214 +++++++++++++++++++++++++++++++++++++++++++++++++ 25 | config.def.h | 12 +++ 26 | st.c | 3 + 27 | st.h | 10 +++ 28 | x.c | 21 +++-- 29 | 7 files changed, 451 insertions(+), 6 deletions(-) 30 | create mode 100644 boxdraw.c 31 | create mode 100644 boxdraw_data.h 32 | 33 | diff --git a/Makefile b/Makefile 34 | index 470ac86..6dfa212 100644 35 | --- a/Makefile 36 | +++ b/Makefile 37 | @@ -4,7 +4,7 @@ 38 | 39 | include config.mk 40 | 41 | -SRC = st.c x.c 42 | +SRC = st.c x.c boxdraw.c 43 | OBJ = $(SRC:.c=.o) 44 | 45 | all: options st 46 | @@ -23,6 +23,7 @@ config.h: 47 | 48 | st.o: config.h st.h win.h 49 | x.o: arg.h config.h st.h win.h 50 | +boxdraw.o: config.h st.h boxdraw_data.h 51 | 52 | $(OBJ): config.h config.mk 53 | 54 | diff --git a/boxdraw.c b/boxdraw.c 55 | new file mode 100644 56 | index 0000000..28a92d0 57 | --- /dev/null 58 | +++ b/boxdraw.c 59 | @@ -0,0 +1,194 @@ 60 | +/* 61 | + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih 62 | + * MIT/X Consortium License 63 | + */ 64 | + 65 | +#include 66 | +#include "st.h" 67 | +#include "boxdraw_data.h" 68 | + 69 | +/* Rounded non-negative integers division of n / d */ 70 | +#define DIV(n, d) (((n) + (d) / 2) / (d)) 71 | + 72 | +static Display *xdpy; 73 | +static Colormap xcmap; 74 | +static XftDraw *xd; 75 | +static Visual *xvis; 76 | + 77 | +static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort); 78 | +static void drawboxlines(int, int, int, int, XftColor *, ushort); 79 | + 80 | +/* public API */ 81 | + 82 | +void 83 | +boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis) 84 | +{ 85 | + xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis; 86 | +} 87 | + 88 | +int 89 | +isboxdraw(Rune u) 90 | +{ 91 | + Rune block = u & ~0xff; 92 | + return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) || 93 | + (boxdraw_braille && block == 0x2800); 94 | +} 95 | + 96 | +/* the "index" is actually the entire shape data encoded as ushort */ 97 | +ushort 98 | +boxdrawindex(const Glyph *g) 99 | +{ 100 | + if (boxdraw_braille && (g->u & ~0xff) == 0x2800) 101 | + return BRL | (uint8_t)g->u; 102 | + if (boxdraw_bold && (g->mode & ATTR_BOLD)) 103 | + return BDB | boxdata[(uint8_t)g->u]; 104 | + return boxdata[(uint8_t)g->u]; 105 | +} 106 | + 107 | +void 108 | +drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg, 109 | + const XftGlyphFontSpec *specs, int len) 110 | +{ 111 | + for ( ; len-- > 0; x += cw, specs++) 112 | + drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph); 113 | +} 114 | + 115 | +/* implementation */ 116 | + 117 | +void 118 | +drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd) 119 | +{ 120 | + ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */ 121 | + if (bd & (BDL | BDA)) { 122 | + /* lines (light/double/heavy/arcs) */ 123 | + drawboxlines(x, y, w, h, fg, bd); 124 | + 125 | + } else if (cat == BBD) { 126 | + /* lower (8-X)/8 block */ 127 | + int d = DIV((uint8_t)bd * h, 8); 128 | + XftDrawRect(xd, fg, x, y + d, w, h - d); 129 | + 130 | + } else if (cat == BBU) { 131 | + /* upper X/8 block */ 132 | + XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8)); 133 | + 134 | + } else if (cat == BBL) { 135 | + /* left X/8 block */ 136 | + XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h); 137 | + 138 | + } else if (cat == BBR) { 139 | + /* right (8-X)/8 block */ 140 | + int d = DIV((uint8_t)bd * w, 8); 141 | + XftDrawRect(xd, fg, x + d, y, w - d, h); 142 | + 143 | + } else if (cat == BBQ) { 144 | + /* Quadrants */ 145 | + int w2 = DIV(w, 2), h2 = DIV(h, 2); 146 | + if (bd & TL) 147 | + XftDrawRect(xd, fg, x, y, w2, h2); 148 | + if (bd & TR) 149 | + XftDrawRect(xd, fg, x + w2, y, w - w2, h2); 150 | + if (bd & BL) 151 | + XftDrawRect(xd, fg, x, y + h2, w2, h - h2); 152 | + if (bd & BR) 153 | + XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2); 154 | + 155 | + } else if (bd & BBS) { 156 | + /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */ 157 | + int d = (uint8_t)bd; 158 | + XftColor xfc; 159 | + XRenderColor xrc = { .alpha = 0xffff }; 160 | + 161 | + xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4); 162 | + xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4); 163 | + xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4); 164 | + 165 | + XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc); 166 | + XftDrawRect(xd, &xfc, x, y, w, h); 167 | + XftColorFree(xdpy, xvis, xcmap, &xfc); 168 | + 169 | + } else if (cat == BRL) { 170 | + /* braille, each data bit corresponds to one dot at 2x4 grid */ 171 | + int w1 = DIV(w, 2); 172 | + int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4); 173 | + 174 | + if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1); 175 | + if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1); 176 | + if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2); 177 | + if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1); 178 | + if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1); 179 | + if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2); 180 | + if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3); 181 | + if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3); 182 | + 183 | + } 184 | +} 185 | + 186 | +void 187 | +drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd) 188 | +{ 189 | + /* s: stem thickness. width/8 roughly matches underscore thickness. */ 190 | + /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */ 191 | + /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */ 192 | + int mwh = MIN(w, h); 193 | + int base_s = MAX(1, DIV(mwh, 8)); 194 | + int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */ 195 | + int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s; 196 | + int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2); 197 | + /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */ 198 | + /* The base length (per direction till edge) includes this square. */ 199 | + 200 | + int light = bd & (LL | LU | LR | LD); 201 | + int double_ = bd & (DL | DU | DR | DD); 202 | + 203 | + if (light) { 204 | + /* d: additional (negative) length to not-draw the center */ 205 | + /* texel - at arcs and avoid drawing inside (some) doubles */ 206 | + int arc = bd & BDA; 207 | + int multi_light = light & (light - 1); 208 | + int multi_double = double_ & (double_ - 1); 209 | + /* light crosses double only at DH+LV, DV+LH (ref. shapes) */ 210 | + int d = arc || (multi_double && !multi_light) ? -s : 0; 211 | + 212 | + if (bd & LL) 213 | + XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s); 214 | + if (bd & LU) 215 | + XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d); 216 | + if (bd & LR) 217 | + XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s); 218 | + if (bd & LD) 219 | + XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d); 220 | + } 221 | + 222 | + /* double lines - also align with light to form heavy when combined */ 223 | + if (double_) { 224 | + /* 225 | + * going clockwise, for each double-ray: p is additional length 226 | + * to the single-ray nearer to the previous direction, and n to 227 | + * the next. p and n adjust from the base length to lengths 228 | + * which consider other doubles - shorter to avoid intersections 229 | + * (p, n), or longer to draw the far-corner texel (n). 230 | + */ 231 | + int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD; 232 | + if (dl) { 233 | + int p = dd ? -s : 0, n = du ? -s : dd ? s : 0; 234 | + XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s); 235 | + XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s); 236 | + } 237 | + if (du) { 238 | + int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0; 239 | + XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p); 240 | + XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n); 241 | + } 242 | + if (dr) { 243 | + int p = du ? -s : 0, n = dd ? -s : du ? s : 0; 244 | + XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s); 245 | + XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s); 246 | + } 247 | + if (dd) { 248 | + int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0; 249 | + XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p); 250 | + XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n); 251 | + } 252 | + } 253 | +} 254 | diff --git a/boxdraw_data.h b/boxdraw_data.h 255 | new file mode 100644 256 | index 0000000..7890500 257 | --- /dev/null 258 | +++ b/boxdraw_data.h 259 | @@ -0,0 +1,214 @@ 260 | +/* 261 | + * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih 262 | + * MIT/X Consortium License 263 | + */ 264 | + 265 | +/* 266 | + * U+25XX codepoints data 267 | + * 268 | + * References: 269 | + * http://www.unicode.org/charts/PDF/U2500.pdf 270 | + * http://www.unicode.org/charts/PDF/U2580.pdf 271 | + * 272 | + * Test page: 273 | + * https://github.com/GNOME/vte/blob/master/doc/boxes.txt 274 | + */ 275 | + 276 | +/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */ 277 | +/* Categories (mutually exclusive except BDB): */ 278 | +/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */ 279 | +#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */ 280 | +#define BDA (1<<9) /* Box Draw Arc (light) */ 281 | + 282 | +#define BBD (1<<10) /* Box Block Down (lower) X/8 */ 283 | +#define BBL (2<<10) /* Box Block Left X/8 */ 284 | +#define BBU (3<<10) /* Box Block Upper X/8 */ 285 | +#define BBR (4<<10) /* Box Block Right X/8 */ 286 | +#define BBQ (5<<10) /* Box Block Quadrants */ 287 | +#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */ 288 | + 289 | +#define BBS (1<<14) /* Box Block Shades */ 290 | +#define BDB (1<<15) /* Box Draw is Bold */ 291 | + 292 | +/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */ 293 | +/* Heavy is light+double (literally drawing light+double align to form heavy) */ 294 | +#define LL (1<<0) 295 | +#define LU (1<<1) 296 | +#define LR (1<<2) 297 | +#define LD (1<<3) 298 | +#define LH (LL+LR) 299 | +#define LV (LU+LD) 300 | + 301 | +#define DL (1<<4) 302 | +#define DU (1<<5) 303 | +#define DR (1<<6) 304 | +#define DD (1<<7) 305 | +#define DH (DL+DR) 306 | +#define DV (DU+DD) 307 | + 308 | +#define HL (LL+DL) 309 | +#define HU (LU+DU) 310 | +#define HR (LR+DR) 311 | +#define HD (LD+DD) 312 | +#define HH (HL+HR) 313 | +#define HV (HU+HD) 314 | + 315 | +/* (BBQ) Quadrants Top/Bottom x Left/Right */ 316 | +#define TL (1<<0) 317 | +#define TR (1<<1) 318 | +#define BL (1<<2) 319 | +#define BR (1<<3) 320 | + 321 | +/* Data for U+2500 - U+259F except dashes/diagonals */ 322 | +static const unsigned short boxdata[256] = { 323 | + /* light lines */ 324 | + [0x00] = BDL + LH, /* light horizontal */ 325 | + [0x02] = BDL + LV, /* light vertical */ 326 | + [0x0c] = BDL + LD + LR, /* light down and right */ 327 | + [0x10] = BDL + LD + LL, /* light down and left */ 328 | + [0x14] = BDL + LU + LR, /* light up and right */ 329 | + [0x18] = BDL + LU + LL, /* light up and left */ 330 | + [0x1c] = BDL + LV + LR, /* light vertical and right */ 331 | + [0x24] = BDL + LV + LL, /* light vertical and left */ 332 | + [0x2c] = BDL + LH + LD, /* light horizontal and down */ 333 | + [0x34] = BDL + LH + LU, /* light horizontal and up */ 334 | + [0x3c] = BDL + LV + LH, /* light vertical and horizontal */ 335 | + [0x74] = BDL + LL, /* light left */ 336 | + [0x75] = BDL + LU, /* light up */ 337 | + [0x76] = BDL + LR, /* light right */ 338 | + [0x77] = BDL + LD, /* light down */ 339 | + 340 | + /* heavy [+light] lines */ 341 | + [0x01] = BDL + HH, 342 | + [0x03] = BDL + HV, 343 | + [0x0d] = BDL + HR + LD, 344 | + [0x0e] = BDL + HD + LR, 345 | + [0x0f] = BDL + HD + HR, 346 | + [0x11] = BDL + HL + LD, 347 | + [0x12] = BDL + HD + LL, 348 | + [0x13] = BDL + HD + HL, 349 | + [0x15] = BDL + HR + LU, 350 | + [0x16] = BDL + HU + LR, 351 | + [0x17] = BDL + HU + HR, 352 | + [0x19] = BDL + HL + LU, 353 | + [0x1a] = BDL + HU + LL, 354 | + [0x1b] = BDL + HU + HL, 355 | + [0x1d] = BDL + HR + LV, 356 | + [0x1e] = BDL + HU + LD + LR, 357 | + [0x1f] = BDL + HD + LR + LU, 358 | + [0x20] = BDL + HV + LR, 359 | + [0x21] = BDL + HU + HR + LD, 360 | + [0x22] = BDL + HD + HR + LU, 361 | + [0x23] = BDL + HV + HR, 362 | + [0x25] = BDL + HL + LV, 363 | + [0x26] = BDL + HU + LD + LL, 364 | + [0x27] = BDL + HD + LU + LL, 365 | + [0x28] = BDL + HV + LL, 366 | + [0x29] = BDL + HU + HL + LD, 367 | + [0x2a] = BDL + HD + HL + LU, 368 | + [0x2b] = BDL + HV + HL, 369 | + [0x2d] = BDL + HL + LD + LR, 370 | + [0x2e] = BDL + HR + LL + LD, 371 | + [0x2f] = BDL + HH + LD, 372 | + [0x30] = BDL + HD + LH, 373 | + [0x31] = BDL + HD + HL + LR, 374 | + [0x32] = BDL + HR + HD + LL, 375 | + [0x33] = BDL + HH + HD, 376 | + [0x35] = BDL + HL + LU + LR, 377 | + [0x36] = BDL + HR + LU + LL, 378 | + [0x37] = BDL + HH + LU, 379 | + [0x38] = BDL + HU + LH, 380 | + [0x39] = BDL + HU + HL + LR, 381 | + [0x3a] = BDL + HU + HR + LL, 382 | + [0x3b] = BDL + HH + HU, 383 | + [0x3d] = BDL + HL + LV + LR, 384 | + [0x3e] = BDL + HR + LV + LL, 385 | + [0x3f] = BDL + HH + LV, 386 | + [0x40] = BDL + HU + LH + LD, 387 | + [0x41] = BDL + HD + LH + LU, 388 | + [0x42] = BDL + HV + LH, 389 | + [0x43] = BDL + HU + HL + LD + LR, 390 | + [0x44] = BDL + HU + HR + LD + LL, 391 | + [0x45] = BDL + HD + HL + LU + LR, 392 | + [0x46] = BDL + HD + HR + LU + LL, 393 | + [0x47] = BDL + HH + HU + LD, 394 | + [0x48] = BDL + HH + HD + LU, 395 | + [0x49] = BDL + HV + HL + LR, 396 | + [0x4a] = BDL + HV + HR + LL, 397 | + [0x4b] = BDL + HV + HH, 398 | + [0x78] = BDL + HL, 399 | + [0x79] = BDL + HU, 400 | + [0x7a] = BDL + HR, 401 | + [0x7b] = BDL + HD, 402 | + [0x7c] = BDL + HR + LL, 403 | + [0x7d] = BDL + HD + LU, 404 | + [0x7e] = BDL + HL + LR, 405 | + [0x7f] = BDL + HU + LD, 406 | + 407 | + /* double [+light] lines */ 408 | + [0x50] = BDL + DH, 409 | + [0x51] = BDL + DV, 410 | + [0x52] = BDL + DR + LD, 411 | + [0x53] = BDL + DD + LR, 412 | + [0x54] = BDL + DR + DD, 413 | + [0x55] = BDL + DL + LD, 414 | + [0x56] = BDL + DD + LL, 415 | + [0x57] = BDL + DL + DD, 416 | + [0x58] = BDL + DR + LU, 417 | + [0x59] = BDL + DU + LR, 418 | + [0x5a] = BDL + DU + DR, 419 | + [0x5b] = BDL + DL + LU, 420 | + [0x5c] = BDL + DU + LL, 421 | + [0x5d] = BDL + DL + DU, 422 | + [0x5e] = BDL + DR + LV, 423 | + [0x5f] = BDL + DV + LR, 424 | + [0x60] = BDL + DV + DR, 425 | + [0x61] = BDL + DL + LV, 426 | + [0x62] = BDL + DV + LL, 427 | + [0x63] = BDL + DV + DL, 428 | + [0x64] = BDL + DH + LD, 429 | + [0x65] = BDL + DD + LH, 430 | + [0x66] = BDL + DD + DH, 431 | + [0x67] = BDL + DH + LU, 432 | + [0x68] = BDL + DU + LH, 433 | + [0x69] = BDL + DH + DU, 434 | + [0x6a] = BDL + DH + LV, 435 | + [0x6b] = BDL + DV + LH, 436 | + [0x6c] = BDL + DH + DV, 437 | + 438 | + /* (light) arcs */ 439 | + [0x6d] = BDA + LD + LR, 440 | + [0x6e] = BDA + LD + LL, 441 | + [0x6f] = BDA + LU + LL, 442 | + [0x70] = BDA + LU + LR, 443 | + 444 | + /* Lower (Down) X/8 block (data is 8 - X) */ 445 | + [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4, 446 | + [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0, 447 | + 448 | + /* Left X/8 block (data is X) */ 449 | + [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4, 450 | + [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1, 451 | + 452 | + /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */ 453 | + [0x80] = BBU + 4, [0x94] = BBU + 1, 454 | + [0x90] = BBR + 4, [0x95] = BBR + 7, 455 | + 456 | + /* Quadrants */ 457 | + [0x96] = BBQ + BL, 458 | + [0x97] = BBQ + BR, 459 | + [0x98] = BBQ + TL, 460 | + [0x99] = BBQ + TL + BL + BR, 461 | + [0x9a] = BBQ + TL + BR, 462 | + [0x9b] = BBQ + TL + TR + BL, 463 | + [0x9c] = BBQ + TL + TR + BR, 464 | + [0x9d] = BBQ + TR, 465 | + [0x9e] = BBQ + BL + TR, 466 | + [0x9f] = BBQ + BL + TR + BR, 467 | + 468 | + /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */ 469 | + [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3, 470 | + 471 | + /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */ 472 | + /* U+2571 - U+2573: unsupported (diagonals) */ 473 | +}; 474 | diff --git a/config.def.h b/config.def.h 475 | index 0895a1f..bf6718b 100644 476 | --- a/config.def.h 477 | +++ b/config.def.h 478 | @@ -58,6 +58,18 @@ static unsigned int blinktimeout = 800; 479 | */ 480 | static unsigned int cursorthickness = 2; 481 | 482 | +/* 483 | + * 1: render most of the lines/blocks characters without using the font for 484 | + * perfect alignment between cells (U2500 - U259F except dashes/diagonals). 485 | + * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. 486 | + * 0: disable (render all U25XX glyphs normally from the font). 487 | + */ 488 | +const int boxdraw = 0; 489 | +const int boxdraw_bold = 0; 490 | + 491 | +/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ 492 | +const int boxdraw_braille = 0; 493 | + 494 | /* 495 | * bell volume. It must be a value between -100 and 100. Use 0 for disabling 496 | * it 497 | diff --git a/st.c b/st.c 498 | index 0ce6ac2..c035e19 100644 499 | --- a/st.c 500 | +++ b/st.c 501 | @@ -1230,6 +1230,9 @@ tsetchar(Rune u, Glyph *attr, int x, int y) 502 | term.dirty[y] = 1; 503 | term.line[y][x] = *attr; 504 | term.line[y][x].u = u; 505 | + 506 | + if (isboxdraw(u)) 507 | + term.line[y][x].mode |= ATTR_BOXDRAW; 508 | } 509 | 510 | void 511 | diff --git a/st.h b/st.h 512 | index d978458..a6c382a 100644 513 | --- a/st.h 514 | +++ b/st.h 515 | @@ -33,6 +33,7 @@ enum glyph_attribute { 516 | ATTR_WRAP = 1 << 8, 517 | ATTR_WIDE = 1 << 9, 518 | ATTR_WDUMMY = 1 << 10, 519 | + ATTR_BOXDRAW = 1 << 11, 520 | ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 521 | }; 522 | 523 | @@ -111,6 +112,14 @@ void *xmalloc(size_t); 524 | void *xrealloc(void *, size_t); 525 | char *xstrdup(char *); 526 | 527 | +int isboxdraw(Rune); 528 | +ushort boxdrawindex(const Glyph *); 529 | +#ifdef XFT_VERSION 530 | +/* only exposed to x.c, otherwise we'll need Xft.h for the types */ 531 | +void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *); 532 | +void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int); 533 | +#endif 534 | + 535 | /* config.h globals */ 536 | extern char *utmp; 537 | extern char *scroll; 538 | @@ -122,3 +131,4 @@ extern char *termname; 539 | extern unsigned int tabspaces; 540 | extern unsigned int defaultfg; 541 | extern unsigned int defaultbg; 542 | +extern const int boxdraw, boxdraw_bold, boxdraw_braille; 543 | diff --git a/x.c b/x.c 544 | index e5f1737..6f7ea2c 100644 545 | --- a/x.c 546 | +++ b/x.c 547 | @@ -1205,6 +1205,8 @@ xinit(int cols, int rows) 548 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 549 | if (xsel.xtarget == None) 550 | xsel.xtarget = XA_STRING; 551 | + 552 | + boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); 553 | } 554 | 555 | int 556 | @@ -1251,8 +1253,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x 557 | yp = winy + font->ascent; 558 | } 559 | 560 | - /* Lookup character index with default font. */ 561 | - glyphidx = XftCharIndex(xw.dpy, font->match, rune); 562 | + if (mode & ATTR_BOXDRAW) { 563 | + /* minor shoehorning: boxdraw uses only this ushort */ 564 | + glyphidx = boxdrawindex(&glyphs[i]); 565 | + } else { 566 | + /* Lookup character index with default font. */ 567 | + glyphidx = XftCharIndex(xw.dpy, font->match, rune); 568 | + } 569 | if (glyphidx) { 570 | specs[numspecs].font = font->match; 571 | specs[numspecs].glyph = glyphidx; 572 | @@ -1456,8 +1463,12 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 573 | r.width = width; 574 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 575 | 576 | - /* Render the glyphs. */ 577 | - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 578 | + if (base.mode & ATTR_BOXDRAW) { 579 | + drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); 580 | + } else { 581 | + /* Render the glyphs. */ 582 | + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 583 | + } 584 | 585 | /* Render underline and strikethrough. */ 586 | if (base.mode & ATTR_UNDERLINE) { 587 | @@ -1500,7 +1511,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 588 | /* 589 | * Select the right color for the right mode. 590 | */ 591 | - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; 592 | + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; 593 | 594 | if (IS_SET(MODE_REVERSE)) { 595 | g.mode |= ATTR_REVERSE; 596 | 597 | base-commit: 43a395ae91f7d67ce694e65edeaa7bbc720dd027 598 | -- 599 | 2.20.1 600 | 601 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 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 = "DejaVu Sans Mono:pixelsize=16"; 9 | /* Spare fonts */ 10 | static char *font2[] = { 11 | "DejaVu Sans:pixelsize=16", 12 | "DejaVuSansM Nerd Font:pixelsize=16", 13 | "Noto Color Emoji:pixelsize=14" 14 | }; 15 | 16 | static int borderpx = 2; 17 | 18 | /* 19 | * What program is execed by st depends of these precedence rules: 20 | * 1: program passed with -e 21 | * 2: scroll and/or utmp 22 | * 3: SHELL environment variable 23 | * 4: value of shell in /etc/passwd 24 | * 5: value of shell in config.h 25 | */ 26 | static char *shell = "/bin/sh"; 27 | char *utmp = NULL; 28 | /* scroll program: to enable use a string like "scroll" */ 29 | char *scroll = NULL; 30 | char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; 31 | 32 | /* identification sequence returned in DA and DECID */ 33 | char *vtiden = "\033[?6c"; 34 | 35 | /* Kerning / character bounding-box multipliers */ 36 | static float cwscale = 1.0; 37 | static float chscale = 1.0; 38 | 39 | /* 40 | * word delimiter string 41 | * 42 | * More advanced example: L" `'\"()[]{}" 43 | */ 44 | wchar_t *worddelimiters = L" "; 45 | 46 | /* selection timeouts (in milliseconds) */ 47 | static unsigned int doubleclicktimeout = 300; 48 | static unsigned int tripleclicktimeout = 600; 49 | 50 | /* alt screens */ 51 | int allowaltscreen = 1; 52 | 53 | /* allow certain non-interactive (insecure) window operations such as: 54 | setting the clipboard text */ 55 | int allowwindowops = 0; 56 | 57 | /* 58 | * draw latency range in ms - from new content/keypress/etc until drawing. 59 | * within this range, st draws when content stops arriving (idle). mostly it's 60 | * near minlatency, but it waits longer for slow updates to avoid partial draw. 61 | * low minlatency will tear/flicker more, as it can "detect" idle too early. 62 | */ 63 | static double minlatency = 2; 64 | static double maxlatency = 33; 65 | 66 | /* 67 | * Synchronized-Update timeout in ms 68 | * https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec 69 | */ 70 | static uint su_timeout = 200; 71 | 72 | /* 73 | * blinking timeout (set to 0 to disable blinking) for the terminal blinking 74 | * attribute. 75 | */ 76 | static unsigned int blinktimeout = 800; 77 | 78 | /* 79 | * thickness of underline and bar cursors 80 | */ 81 | static unsigned int cursorthickness = 2; 82 | 83 | /* 84 | * 1: render most of the lines/blocks characters without using the font for 85 | * perfect alignment between cells (U2500 - U259F except dashes/diagonals). 86 | * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored. 87 | * 0: disable (render all U25XX glyphs normally from the font). 88 | */ 89 | const int boxdraw = 1; 90 | const int boxdraw_bold = 0; 91 | 92 | /* braille (U28XX): 1: render as adjacent "pixels", 0: use font */ 93 | const int boxdraw_braille = 0; 94 | 95 | /* 96 | * bell volume. It must be a value between -100 and 100. Use 0 for disabling 97 | * it 98 | */ 99 | static int bellvolume = 0; 100 | 101 | /* default TERM value */ 102 | char *termname = "st-256color"; 103 | 104 | /* 105 | * spaces per tab 106 | * 107 | * When you are changing this value, don't forget to adapt the »it« value in 108 | * the st.info and appropriately install the st.info in the environment where 109 | * you use this st version. 110 | * 111 | * it#$tabspaces, 112 | * 113 | * Secondly make sure your kernel is not expanding tabs. When running `stty 114 | * -a` »tab0« should appear. You can tell the terminal to not expand tabs by 115 | * running following command: 116 | * 117 | * stty tabs 118 | */ 119 | unsigned int tabspaces = 8; 120 | 121 | /* bg opacity */ 122 | float alpha = 0.9; 123 | 124 | /* Terminal colors (16 first used in escape sequence) */ 125 | static const char *colorname[] = { 126 | /* 8 normal colors */ 127 | "#000000", /* 0 -> black */ 128 | "#df2800", /* 1 -> red */ 129 | "#00b200", /* 2 -> green */ 130 | "#b0b200", /* 3 -> yellow */ 131 | "#1f5393", /* 4 -> blue */ 132 | "#980096", /* 5 -> magenta */ 133 | "#008081", /* 6 -> cyan */ 134 | "#cccccc", /* 7 -> white */ 135 | 136 | /* 8 bright colors */ 137 | "#333333", /* 8 -> black */ 138 | "#ff5555", /* 9 -> red */ 139 | "#39ff14", /* 10 -> green */ 140 | "#effd5f", /* 11 -> yellow */ 141 | "#4185d7", /* 12 -> blue */ 142 | "#ff6fff", /* 13 -> magenta */ 143 | "#7ef9ff", /* 14 -> cyan */ 144 | "#ffffff", /* 15 -> white */ 145 | 146 | [255] = 0, 147 | 148 | /* more colors can be added after 255 to use with DefaultXX */ 149 | "#000000", /* 256 -> bg */ 150 | "#ffffff", /* 257 -> fg */ 151 | "#4185d7", /* 258 -> cursor */ 152 | }; 153 | 154 | /* 155 | * Default colors (colorname index) 156 | * foreground, background, cursor, reverse cursor 157 | */ 158 | unsigned int defaultfg = 257; 159 | unsigned int defaultbg = 256; 160 | unsigned int defaultcs = 258; 161 | static unsigned int defaultrcs = 256; 162 | 163 | /* 164 | * Default shape of cursor 165 | * 2: Block ("█") 166 | * 4: Underline ("_") 167 | * 6: Bar ("|") 168 | * 7: Snowman ("☃") 169 | */ 170 | static unsigned int cursorshape = 2; 171 | 172 | /* 173 | * Default columns and rows numbers 174 | */ 175 | 176 | static unsigned int cols = 80; 177 | static unsigned int rows = 24; 178 | 179 | /* 180 | * Default colour and shape of the mouse cursor 181 | */ 182 | static unsigned int mouseshape = XC_xterm; 183 | static unsigned int mousefg = 7; 184 | static unsigned int mousebg = 0; 185 | 186 | /* 187 | * Color used to display font attributes when fontconfig selected a font which 188 | * doesn't match the ones requested. 189 | */ 190 | static unsigned int defaultattr = 11; 191 | 192 | /* 193 | * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 194 | * Note that if you want to use ShiftMask with selmasks, set this to an other 195 | * modifier, set to 0 to not use it. 196 | */ 197 | static uint forcemousemod = ShiftMask; 198 | 199 | /* 200 | * Xresources preferences to load at startup 201 | */ 202 | ResourcePref resources[] = { 203 | { "font", STRING, &font }, 204 | { "font0", STRING, &font2[0] }, 205 | { "font1", STRING, &font2[1] }, 206 | { "font2", STRING, &font2[2] }, 207 | { "color0", STRING, &colorname[0] }, 208 | { "color1", STRING, &colorname[1] }, 209 | { "color2", STRING, &colorname[2] }, 210 | { "color3", STRING, &colorname[3] }, 211 | { "color4", STRING, &colorname[4] }, 212 | { "color5", STRING, &colorname[5] }, 213 | { "color6", STRING, &colorname[6] }, 214 | { "color7", STRING, &colorname[7] }, 215 | { "color8", STRING, &colorname[8] }, 216 | { "color9", STRING, &colorname[9] }, 217 | { "color10", STRING, &colorname[10] }, 218 | { "color11", STRING, &colorname[11] }, 219 | { "color12", STRING, &colorname[12] }, 220 | { "color13", STRING, &colorname[13] }, 221 | { "color14", STRING, &colorname[14] }, 222 | { "color15", STRING, &colorname[15] }, 223 | { "background", STRING, &colorname[256] }, 224 | { "foreground", STRING, &colorname[257] }, 225 | { "cursorColor", STRING, &colorname[258] }, 226 | { "termname", STRING, &termname }, 227 | { "shell", STRING, &shell }, 228 | { "minlatency", FLOAT, &minlatency }, 229 | { "maxlatency", FLOAT, &maxlatency }, 230 | { "blinktimeout", INTEGER, &blinktimeout }, 231 | { "bellvolume", INTEGER, &bellvolume }, 232 | { "tabspaces", INTEGER, &tabspaces }, 233 | { "borderpx", INTEGER, &borderpx }, 234 | { "cwscale", FLOAT, &cwscale }, 235 | { "chscale", FLOAT, &chscale }, 236 | { "alpha", FLOAT, &alpha }, 237 | }; 238 | 239 | /* 240 | * Internal mouse shortcuts. 241 | * Beware that overloading Button1 will disable the selection. 242 | */ 243 | static MouseShortcut mshortcuts[] = { 244 | /* mask button function argument release */ 245 | { XK_ANY_MOD, Button4, kscrollup, {.i = 5}, 0, /* !alt */ -1 }, 246 | { XK_ANY_MOD, Button5, kscrolldown, {.i = 5}, 0, /* !alt */ -1 }, 247 | { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 248 | { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, 249 | { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 250 | { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, 251 | { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 252 | }; 253 | 254 | /* Internal keyboard shortcuts. */ 255 | #define MODKEY Mod1Mask 256 | #define TERMMOD (ControlMask|ShiftMask) 257 | 258 | static Shortcut shortcuts[] = { 259 | /* mask keysym function argument */ 260 | { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 261 | { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 262 | { ShiftMask, XK_Print, printscreen, {.i = 0} }, 263 | { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, 264 | { TERMMOD, XK_plus, zoom, {.f = +1} }, 265 | { ControlMask, XK_minus, zoom, {.f = -1} }, 266 | { ControlMask, XK_equal, zoomreset, {.f = 0} }, 267 | { TERMMOD, XK_C, clipcopy, {.i = 0} }, 268 | { TERMMOD, XK_V, clippaste, {.i = 0} }, 269 | { TERMMOD, XK_Y, selpaste, {.i = 0} }, 270 | { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 271 | { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 272 | { MODKEY, XK_y, copyurl, {.i = 0} }, 273 | { MODKEY, XK_o, opencopied, {.v = "link_handler.sh"} }, 274 | { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 275 | { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 276 | { TERMMOD, XK_Escape, keyboard_select,{.i = 0} }, 277 | }; 278 | 279 | /* 280 | * Special keys (change & recompile st.info accordingly) 281 | * 282 | * Mask value: 283 | * * Use XK_ANY_MOD to match the key no matter modifiers state 284 | * * Use XK_NO_MOD to match the key alone (no modifiers) 285 | * appkey value: 286 | * * 0: no value 287 | * * > 0: keypad application mode enabled 288 | * * = 2: term.numlock = 1 289 | * * < 0: keypad application mode disabled 290 | * appcursor value: 291 | * * 0: no value 292 | * * > 0: cursor application mode enabled 293 | * * < 0: cursor application mode disabled 294 | * 295 | * Be careful with the order of the definitions because st searches in 296 | * this table sequentially, so any XK_ANY_MOD must be in the last 297 | * position for a key. 298 | */ 299 | 300 | /* 301 | * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) 302 | * to be mapped below, add them to this array. 303 | */ 304 | static KeySym mappedkeys[] = { -1 }; 305 | 306 | /* 307 | * State bits to ignore when matching key or button events. By default, 308 | * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. 309 | */ 310 | static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; 311 | 312 | /* 313 | * This is the huge key array which defines all compatibility to the Linux 314 | * world. Please decide about changes wisely. 315 | */ 316 | static Key key[] = { 317 | /* keysym mask string appkey appcursor */ 318 | { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, 319 | { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, 320 | { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, 321 | { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 322 | { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, 323 | { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, 324 | { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, 325 | { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, 326 | { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, 327 | { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, 328 | { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, 329 | { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, 330 | { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, 331 | { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, 332 | { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, 333 | { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, 334 | { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, 335 | { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 336 | { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, 337 | { XK_KP_End, ControlMask, "\033[J", -1, 0}, 338 | { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, 339 | { XK_KP_End, ShiftMask, "\033[K", -1, 0}, 340 | { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, 341 | { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, 342 | { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, 343 | { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 344 | { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, 345 | { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, 346 | { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, 347 | { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, 348 | { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 349 | { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 350 | { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, 351 | { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, 352 | { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, 353 | { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, 354 | { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 355 | { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 356 | { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, 357 | { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, 358 | { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, 359 | { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, 360 | { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, 361 | { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, 362 | { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, 363 | { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, 364 | { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, 365 | { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, 366 | { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, 367 | { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, 368 | { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, 369 | { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, 370 | { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, 371 | { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, 372 | { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, 373 | { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, 374 | { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, 375 | { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, 376 | { XK_Up, ControlMask, "\033[1;5A", 0, 0}, 377 | { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, 378 | { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, 379 | { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, 380 | { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, 381 | { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, 382 | { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, 383 | { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, 384 | { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, 385 | { XK_Down, ControlMask, "\033[1;5B", 0, 0}, 386 | { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, 387 | { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, 388 | { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, 389 | { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, 390 | { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, 391 | { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, 392 | { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, 393 | { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, 394 | { XK_Left, ControlMask, "\033[1;5D", 0, 0}, 395 | { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, 396 | { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, 397 | { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, 398 | { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, 399 | { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, 400 | { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, 401 | { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, 402 | { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, 403 | { XK_Right, ControlMask, "\033[1;5C", 0, 0}, 404 | { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, 405 | { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, 406 | { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, 407 | { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, 408 | { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, 409 | { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, 410 | { XK_Return, Mod1Mask, "\033\r", 0, 0}, 411 | { XK_Return, XK_ANY_MOD, "\r", 0, 0}, 412 | { XK_Insert, ShiftMask, "\033[4l", -1, 0}, 413 | { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, 414 | { XK_Insert, ControlMask, "\033[L", -1, 0}, 415 | { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, 416 | { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 417 | { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 418 | { XK_Delete, ControlMask, "\033[M", -1, 0}, 419 | { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, 420 | { XK_Delete, ShiftMask, "\033[2K", -1, 0}, 421 | { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, 422 | { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 423 | { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 424 | { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, 425 | { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, 426 | { XK_Home, ShiftMask, "\033[2J", 0, -1}, 427 | { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, 428 | { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, 429 | { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 430 | { XK_End, ControlMask, "\033[J", -1, 0}, 431 | { XK_End, ControlMask, "\033[1;5F", +1, 0}, 432 | { XK_End, ShiftMask, "\033[K", -1, 0}, 433 | { XK_End, ShiftMask, "\033[1;2F", +1, 0}, 434 | { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, 435 | { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, 436 | { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, 437 | { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 438 | { XK_Next, ControlMask, "\033[6;5~", 0, 0}, 439 | { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, 440 | { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 441 | { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, 442 | { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, 443 | { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, 444 | { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, 445 | { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, 446 | { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, 447 | { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, 448 | { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, 449 | { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, 450 | { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, 451 | { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, 452 | { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, 453 | { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, 454 | { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, 455 | { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, 456 | { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, 457 | { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, 458 | { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, 459 | { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, 460 | { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, 461 | { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, 462 | { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, 463 | { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, 464 | { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, 465 | { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, 466 | { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, 467 | { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, 468 | { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, 469 | { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, 470 | { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, 471 | { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, 472 | { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, 473 | { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, 474 | { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, 475 | { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, 476 | { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, 477 | { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, 478 | { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, 479 | { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, 480 | { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, 481 | { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, 482 | { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, 483 | { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, 484 | { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, 485 | { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, 486 | { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, 487 | { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, 488 | { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, 489 | { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, 490 | { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, 491 | { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, 492 | { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, 493 | { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, 494 | { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, 495 | { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, 496 | { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, 497 | { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, 498 | { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, 499 | { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, 500 | { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, 501 | { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, 502 | { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, 503 | { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, 504 | { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, 505 | { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, 506 | { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, 507 | { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, 508 | { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, 509 | { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, 510 | { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, 511 | { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, 512 | { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, 513 | { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, 514 | { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, 515 | { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, 516 | { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, 517 | { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, 518 | { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, 519 | { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, 520 | { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, 521 | { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, 522 | { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, 523 | { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, 524 | { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, 525 | { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, 526 | { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, 527 | }; 528 | 529 | /* 530 | * Selection types' masks. 531 | * Use the same masks as usual. 532 | * Button1Mask is always unset, to make masks match between ButtonPress. 533 | * ButtonRelease and MotionNotify. 534 | * If no match is found, regular selection is used. 535 | */ 536 | static uint selmasks[] = { 537 | [SEL_RECTANGULAR] = Mod1Mask, 538 | }; 539 | 540 | /* 541 | * Printable characters in ASCII, used to estimate the advance width 542 | * of single wide characters. 543 | */ 544 | static char ascii_printable[] = 545 | " !\"#$%&'()*+,-./0123456789:;<=>?" 546 | "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 547 | "`abcdefghijklmnopqrstuvwxyz{|}~"; 548 | -------------------------------------------------------------------------------- /x.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE for license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | char *argv0; 20 | #include "arg.h" 21 | #include "st.h" 22 | #include "win.h" 23 | 24 | /* types used in config.h */ 25 | typedef struct { 26 | uint mod; 27 | KeySym keysym; 28 | void (*func)(const Arg *); 29 | const Arg arg; 30 | } Shortcut; 31 | 32 | typedef struct { 33 | uint mod; 34 | uint button; 35 | void (*func)(const Arg *); 36 | const Arg arg; 37 | uint release; 38 | int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ 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 zoom(const Arg *); 74 | static void zoomabs(const Arg *); 75 | static void zoomreset(const Arg *); 76 | static void ttysend(const Arg *); 77 | 78 | /* config.h for applying patches and the configuration. */ 79 | #include "config.h" 80 | 81 | /* XEMBED messages */ 82 | #define XEMBED_FOCUS_IN 4 83 | #define XEMBED_FOCUS_OUT 5 84 | 85 | /* macros */ 86 | #define IS_SET(flag) ((win.mode & (flag)) != 0) 87 | #define TRUERED(x) (((x) & 0xff0000) >> 8) 88 | #define TRUEGREEN(x) (((x) & 0xff00)) 89 | #define TRUEBLUE(x) (((x) & 0xff) << 8) 90 | 91 | typedef XftDraw *Draw; 92 | typedef XftColor Color; 93 | typedef XftGlyphFontSpec GlyphFontSpec; 94 | 95 | /* Purely graphic info */ 96 | typedef struct { 97 | int tw, th; /* tty width and height */ 98 | int w, h; /* window width and height */ 99 | int ch; /* char height */ 100 | int cw; /* char width */ 101 | int mode; /* window state/mode flags */ 102 | int cursor; /* cursor style */ 103 | } TermWindow; 104 | 105 | typedef struct { 106 | Display *dpy; 107 | Colormap cmap; 108 | Window win; 109 | Drawable buf; 110 | GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ 111 | Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; 112 | struct { 113 | XIM xim; 114 | XIC xic; 115 | XPoint spot; 116 | XVaNestedList spotlist; 117 | } ime; 118 | Draw draw; 119 | Visual *vis; 120 | XSetWindowAttributes attrs; 121 | int scr; 122 | int isfixed; /* is fixed geometry? */ 123 | int depth; /* bit depth */ 124 | int l, t; /* left and top offset */ 125 | int gm; /* geometry mask */ 126 | } XWindow; 127 | 128 | typedef struct { 129 | Atom xtarget; 130 | char *primary, *clipboard; 131 | struct timespec tclick1; 132 | struct timespec tclick2; 133 | } XSelection; 134 | 135 | /* Font structure */ 136 | #define Font Font_ 137 | typedef struct { 138 | int height; 139 | int width; 140 | int ascent; 141 | int descent; 142 | int badslant; 143 | int badweight; 144 | short lbearing; 145 | short rbearing; 146 | XftFont *match; 147 | FcFontSet *set; 148 | FcPattern *pattern; 149 | } Font; 150 | 151 | /* Drawing Context */ 152 | typedef struct { 153 | Color *col; 154 | size_t collen; 155 | Font font, bfont, ifont, ibfont; 156 | GC gc; 157 | } DC; 158 | 159 | static inline ushort sixd_to_16bit(int); 160 | static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 161 | static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 162 | static void xdrawglyph(Glyph, int, int); 163 | static void xclear(int, int, int, int); 164 | static int xgeommasktogravity(int); 165 | static int ximopen(Display *); 166 | static void ximinstantiate(Display *, XPointer, XPointer); 167 | static void ximdestroy(XIM, XPointer, XPointer); 168 | static int xicdestroy(XIC, XPointer, XPointer); 169 | static void xinit(int, int); 170 | static void cresize(int, int); 171 | static void xresize(int, int); 172 | static void xhints(void); 173 | static int xloadcolor(int, const char *, Color *); 174 | static int xloadfont(Font *, FcPattern *); 175 | static void xloadfonts(const char *, double); 176 | static int xloadsparefont(FcPattern *, int); 177 | static void xloadsparefonts(void); 178 | static void xunloadfont(Font *); 179 | static void xunloadfonts(void); 180 | static void xsetenv(void); 181 | static void xseturgency(int); 182 | static int evcol(XEvent *); 183 | static int evrow(XEvent *); 184 | 185 | static void expose(XEvent *); 186 | static void visibility(XEvent *); 187 | static void unmap(XEvent *); 188 | static void kpress(XEvent *); 189 | static void cmessage(XEvent *); 190 | static void resize(XEvent *); 191 | static void focus(XEvent *); 192 | static uint buttonmask(uint); 193 | static int mouseaction(XEvent *, uint); 194 | static void brelease(XEvent *); 195 | static void bpress(XEvent *); 196 | static void bmotion(XEvent *); 197 | static void propnotify(XEvent *); 198 | static void selnotify(XEvent *); 199 | static void selclear_(XEvent *); 200 | static void selrequest(XEvent *); 201 | static void setsel(char *, Time); 202 | static void mousesel(XEvent *, int); 203 | static void mousereport(XEvent *); 204 | static char *kmap(KeySym, uint); 205 | static int match(uint, uint); 206 | 207 | static void run(void); 208 | static void usage(void); 209 | 210 | static void (*handler[LASTEvent])(XEvent *) = { 211 | [KeyPress] = kpress, 212 | [ClientMessage] = cmessage, 213 | [ConfigureNotify] = resize, 214 | [VisibilityNotify] = visibility, 215 | [UnmapNotify] = unmap, 216 | [Expose] = expose, 217 | [FocusIn] = focus, 218 | [FocusOut] = focus, 219 | [MotionNotify] = bmotion, 220 | [ButtonPress] = bpress, 221 | [ButtonRelease] = brelease, 222 | /* 223 | * Uncomment if you want the selection to disappear when you select something 224 | * different in another window. 225 | */ 226 | /* [SelectionClear] = selclear_, */ 227 | [SelectionNotify] = selnotify, 228 | /* 229 | * PropertyNotify is only turned on when there is some INCR transfer happening 230 | * for the selection retrieval. 231 | */ 232 | [PropertyNotify] = propnotify, 233 | [SelectionRequest] = selrequest, 234 | }; 235 | 236 | /* Globals */ 237 | static DC dc; 238 | static XWindow xw; 239 | static XSelection xsel; 240 | static TermWindow win; 241 | 242 | /* Font Ring Cache */ 243 | enum { 244 | FRC_NORMAL, 245 | FRC_ITALIC, 246 | FRC_BOLD, 247 | FRC_ITALICBOLD 248 | }; 249 | 250 | typedef struct { 251 | XftFont *font; 252 | int flags; 253 | Rune unicodep; 254 | } Fontcache; 255 | 256 | /* Fontcache is an array now. A new font will be appended to the array. */ 257 | static Fontcache *frc = NULL; 258 | static int frclen = 0; 259 | static int frccap = 0; 260 | static char *usedfont = NULL; 261 | static double usedfontsize = 0; 262 | static double defaultfontsize = 0; 263 | 264 | static char *opt_alpha = NULL; 265 | static char *opt_class = NULL; 266 | static char **opt_cmd = NULL; 267 | static char *opt_embed = NULL; 268 | static char *opt_font = NULL; 269 | static char *opt_io = NULL; 270 | static char *opt_line = NULL; 271 | static char *opt_name = NULL; 272 | static char *opt_title = NULL; 273 | 274 | static uint buttons; /* bit field of pressed buttons */ 275 | 276 | void 277 | clipcopy(const Arg *dummy) 278 | { 279 | Atom clipboard; 280 | 281 | free(xsel.clipboard); 282 | xsel.clipboard = NULL; 283 | 284 | if (xsel.primary != NULL) { 285 | xsel.clipboard = xstrdup(xsel.primary); 286 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 287 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); 288 | } 289 | } 290 | 291 | void 292 | clippaste(const Arg *dummy) 293 | { 294 | Atom clipboard; 295 | 296 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 297 | XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, 298 | xw.win, CurrentTime); 299 | } 300 | 301 | void 302 | selpaste(const Arg *dummy) 303 | { 304 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, 305 | xw.win, CurrentTime); 306 | } 307 | 308 | void 309 | numlock(const Arg *dummy) 310 | { 311 | win.mode ^= MODE_NUMLOCK; 312 | } 313 | 314 | void 315 | zoom(const Arg *arg) 316 | { 317 | Arg larg; 318 | 319 | larg.f = usedfontsize + arg->f; 320 | zoomabs(&larg); 321 | } 322 | 323 | void 324 | zoomabs(const Arg *arg) 325 | { 326 | xunloadfonts(); 327 | xloadfonts(usedfont, arg->f); 328 | xloadsparefonts(); 329 | cresize(0, 0); 330 | redraw(); 331 | xhints(); 332 | } 333 | 334 | void 335 | zoomreset(const Arg *arg) 336 | { 337 | Arg larg; 338 | 339 | if (defaultfontsize > 0) { 340 | larg.f = defaultfontsize; 341 | zoomabs(&larg); 342 | } 343 | } 344 | 345 | void 346 | ttysend(const Arg *arg) 347 | { 348 | ttywrite(arg->s, strlen(arg->s), 1); 349 | } 350 | 351 | int 352 | evcol(XEvent *e) 353 | { 354 | int x = e->xbutton.x - borderpx; 355 | LIMIT(x, 0, win.tw - 1); 356 | return x / win.cw; 357 | } 358 | 359 | int 360 | evrow(XEvent *e) 361 | { 362 | int y = e->xbutton.y - borderpx; 363 | LIMIT(y, 0, win.th - 1); 364 | return y / win.ch; 365 | } 366 | 367 | void 368 | mousesel(XEvent *e, int done) 369 | { 370 | int type, seltype = SEL_REGULAR; 371 | uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); 372 | 373 | for (type = 1; type < LEN(selmasks); ++type) { 374 | if (match(selmasks[type], state)) { 375 | seltype = type; 376 | break; 377 | } 378 | } 379 | selextend(evcol(e), evrow(e), seltype, done); 380 | if (done) 381 | setsel(getsel(), e->xbutton.time); 382 | } 383 | 384 | void 385 | mousereport(XEvent *e) 386 | { 387 | int len, btn, code; 388 | int x = evcol(e), y = evrow(e); 389 | int state = e->xbutton.state; 390 | char buf[40]; 391 | static int ox, oy; 392 | 393 | if (e->type == MotionNotify) { 394 | if (x == ox && y == oy) 395 | return; 396 | if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) 397 | return; 398 | /* MODE_MOUSEMOTION: no reporting if no button is pressed */ 399 | if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) 400 | return; 401 | 402 | /* Set btn to lowest-numbered pressed button, or 12 if no 403 | * buttons are pressed. */ 404 | for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) 405 | ; 406 | code = 32; 407 | } else { 408 | btn = e->xbutton.button; 409 | /* Only buttons 1 through 11 can be encoded */ 410 | if (btn < 1 || btn > 11) 411 | return; 412 | if (e->type == ButtonRelease) { 413 | /* MODE_MOUSEX10: no button release reporting */ 414 | if (IS_SET(MODE_MOUSEX10)) 415 | return; 416 | /* Don't send release events for the scroll wheel */ 417 | if (btn == 4 || btn == 5) 418 | return; 419 | } 420 | code = 0; 421 | } 422 | 423 | ox = x; 424 | oy = y; 425 | 426 | /* Encode btn into code. If no button is pressed for a motion event in 427 | * MODE_MOUSEMANY, then encode it as a release. */ 428 | if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) 429 | code += 3; 430 | else if (btn >= 8) 431 | code += 128 + btn - 8; 432 | else if (btn >= 4) 433 | code += 64 + btn - 4; 434 | else 435 | code += btn - 1; 436 | 437 | if (!IS_SET(MODE_MOUSEX10)) { 438 | code += ((state & ShiftMask ) ? 4 : 0) 439 | + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ 440 | + ((state & ControlMask) ? 16 : 0); 441 | } 442 | 443 | if (IS_SET(MODE_MOUSESGR)) { 444 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", 445 | code, x+1, y+1, 446 | e->type == ButtonRelease ? 'm' : 'M'); 447 | } else if (x < 223 && y < 223) { 448 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", 449 | 32+code, 32+x+1, 32+y+1); 450 | } else { 451 | return; 452 | } 453 | 454 | ttywrite(buf, len, 0); 455 | } 456 | 457 | uint 458 | buttonmask(uint button) 459 | { 460 | return button == Button1 ? Button1Mask 461 | : button == Button2 ? Button2Mask 462 | : button == Button3 ? Button3Mask 463 | : button == Button4 ? Button4Mask 464 | : button == Button5 ? Button5Mask 465 | : 0; 466 | } 467 | 468 | int 469 | mouseaction(XEvent *e, uint release) 470 | { 471 | MouseShortcut *ms; 472 | 473 | /* ignore Buttonmask for Button - it's set on release */ 474 | uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 475 | 476 | for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 477 | if (ms->release == release && 478 | ms->button == e->xbutton.button && 479 | (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && 480 | (match(ms->mod, state) || /* exact or forced */ 481 | match(ms->mod, state & ~forcemousemod))) { 482 | ms->func(&(ms->arg)); 483 | return 1; 484 | } 485 | } 486 | 487 | return 0; 488 | } 489 | 490 | void 491 | bpress(XEvent *e) 492 | { 493 | int btn = e->xbutton.button; 494 | struct timespec now; 495 | int snap; 496 | 497 | if (1 <= btn && btn <= 11) 498 | buttons |= 1 << (btn-1); 499 | 500 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 501 | mousereport(e); 502 | return; 503 | } 504 | 505 | if (mouseaction(e, 0)) 506 | return; 507 | 508 | if (btn == Button1) { 509 | /* 510 | * If the user clicks below predefined timeouts specific 511 | * snapping behaviour is exposed. 512 | */ 513 | clock_gettime(CLOCK_MONOTONIC, &now); 514 | if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { 515 | snap = SNAP_LINE; 516 | } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { 517 | snap = SNAP_WORD; 518 | } else { 519 | snap = 0; 520 | } 521 | xsel.tclick2 = xsel.tclick1; 522 | xsel.tclick1 = now; 523 | 524 | selstart(evcol(e), evrow(e), snap); 525 | } 526 | } 527 | 528 | void 529 | propnotify(XEvent *e) 530 | { 531 | XPropertyEvent *xpev; 532 | Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 533 | 534 | xpev = &e->xproperty; 535 | if (xpev->state == PropertyNewValue && 536 | (xpev->atom == XA_PRIMARY || 537 | xpev->atom == clipboard)) { 538 | selnotify(e); 539 | } 540 | } 541 | 542 | void 543 | selnotify(XEvent *e) 544 | { 545 | ulong nitems, ofs, rem; 546 | int format; 547 | uchar *data, *last, *repl; 548 | Atom type, incratom, property = None; 549 | 550 | incratom = XInternAtom(xw.dpy, "INCR", 0); 551 | 552 | ofs = 0; 553 | if (e->type == SelectionNotify) 554 | property = e->xselection.property; 555 | else if (e->type == PropertyNotify) 556 | property = e->xproperty.atom; 557 | 558 | if (property == None) 559 | return; 560 | 561 | do { 562 | if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, 563 | BUFSIZ/4, False, AnyPropertyType, 564 | &type, &format, &nitems, &rem, 565 | &data)) { 566 | fprintf(stderr, "Clipboard allocation failed\n"); 567 | return; 568 | } 569 | 570 | if (e->type == PropertyNotify && nitems == 0 && rem == 0) { 571 | /* 572 | * If there is some PropertyNotify with no data, then 573 | * this is the signal of the selection owner that all 574 | * data has been transferred. We won't need to receive 575 | * PropertyNotify events anymore. 576 | */ 577 | MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); 578 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 579 | &xw.attrs); 580 | } 581 | 582 | if (type == incratom) { 583 | /* 584 | * Activate the PropertyNotify events so we receive 585 | * when the selection owner does send us the next 586 | * chunk of data. 587 | */ 588 | MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); 589 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, 590 | &xw.attrs); 591 | 592 | /* 593 | * Deleting the property is the transfer start signal. 594 | */ 595 | XDeleteProperty(xw.dpy, xw.win, (int)property); 596 | continue; 597 | } 598 | 599 | /* 600 | * As seen in getsel: 601 | * Line endings are inconsistent in the terminal and GUI world 602 | * copy and pasting. When receiving some selection data, 603 | * replace all '\n' with '\r'. 604 | * FIXME: Fix the computer world. 605 | */ 606 | repl = data; 607 | last = data + nitems * format / 8; 608 | while ((repl = memchr(repl, '\n', last - repl))) { 609 | *repl++ = '\r'; 610 | } 611 | 612 | if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) 613 | ttywrite("\033[200~", 6, 0); 614 | ttywrite((char *)data, nitems * format / 8, 1); 615 | if (IS_SET(MODE_BRCKTPASTE) && rem == 0) 616 | ttywrite("\033[201~", 6, 0); 617 | XFree(data); 618 | /* number of 32-bit chunks returned */ 619 | ofs += nitems * format / 32; 620 | } while (rem > 0); 621 | 622 | /* 623 | * Deleting the property again tells the selection owner to send the 624 | * next data chunk in the property. 625 | */ 626 | XDeleteProperty(xw.dpy, xw.win, (int)property); 627 | } 628 | 629 | void 630 | xclipcopy(void) 631 | { 632 | clipcopy(NULL); 633 | } 634 | 635 | void 636 | selclear_(XEvent *e) 637 | { 638 | selclear(); 639 | } 640 | 641 | void 642 | selrequest(XEvent *e) 643 | { 644 | XSelectionRequestEvent *xsre; 645 | XSelectionEvent xev; 646 | Atom xa_targets, string, clipboard; 647 | char *seltext; 648 | 649 | xsre = (XSelectionRequestEvent *) e; 650 | xev.type = SelectionNotify; 651 | xev.requestor = xsre->requestor; 652 | xev.selection = xsre->selection; 653 | xev.target = xsre->target; 654 | xev.time = xsre->time; 655 | if (xsre->property == None) 656 | xsre->property = xsre->target; 657 | 658 | /* reject */ 659 | xev.property = None; 660 | 661 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); 662 | if (xsre->target == xa_targets) { 663 | /* respond with the supported type */ 664 | string = xsel.xtarget; 665 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, 666 | XA_ATOM, 32, PropModeReplace, 667 | (uchar *) &string, 1); 668 | xev.property = xsre->property; 669 | } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { 670 | /* 671 | * xith XA_STRING non ascii characters may be incorrect in the 672 | * requestor. It is not our problem, use utf8. 673 | */ 674 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); 675 | if (xsre->selection == XA_PRIMARY) { 676 | seltext = xsel.primary; 677 | } else if (xsre->selection == clipboard) { 678 | seltext = xsel.clipboard; 679 | } else { 680 | fprintf(stderr, 681 | "Unhandled clipboard selection 0x%lx\n", 682 | xsre->selection); 683 | return; 684 | } 685 | if (seltext != NULL) { 686 | XChangeProperty(xsre->display, xsre->requestor, 687 | xsre->property, xsre->target, 688 | 8, PropModeReplace, 689 | (uchar *)seltext, strlen(seltext)); 690 | xev.property = xsre->property; 691 | } 692 | } 693 | 694 | /* all done, send a notification to the listener */ 695 | if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) 696 | fprintf(stderr, "Error sending SelectionNotify event\n"); 697 | } 698 | 699 | void 700 | setsel(char *str, Time t) 701 | { 702 | if (!str) 703 | return; 704 | 705 | free(xsel.primary); 706 | xsel.primary = str; 707 | 708 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); 709 | if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) 710 | selclear(); 711 | } 712 | 713 | void 714 | xsetsel(char *str) 715 | { 716 | setsel(str, CurrentTime); 717 | } 718 | 719 | void 720 | brelease(XEvent *e) 721 | { 722 | int btn = e->xbutton.button; 723 | 724 | if (1 <= btn && btn <= 11) 725 | buttons &= ~(1 << (btn-1)); 726 | 727 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 728 | mousereport(e); 729 | return; 730 | } 731 | 732 | if (mouseaction(e, 1)) 733 | return; 734 | if (btn == Button1) 735 | mousesel(e, 1); 736 | } 737 | 738 | void 739 | bmotion(XEvent *e) 740 | { 741 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { 742 | mousereport(e); 743 | return; 744 | } 745 | 746 | mousesel(e, 0); 747 | } 748 | 749 | void 750 | cresize(int width, int height) 751 | { 752 | int col, row; 753 | 754 | if (width != 0) 755 | win.w = width; 756 | if (height != 0) 757 | win.h = height; 758 | 759 | col = (win.w - 2 * borderpx) / win.cw; 760 | row = (win.h - 2 * borderpx) / win.ch; 761 | col = MAX(1, col); 762 | row = MAX(1, row); 763 | 764 | tresize(col, row); 765 | xresize(col, row); 766 | ttyresize(win.tw, win.th); 767 | } 768 | 769 | void 770 | xresize(int col, int row) 771 | { 772 | win.tw = col * win.cw; 773 | win.th = row * win.ch; 774 | 775 | XFreePixmap(xw.dpy, xw.buf); 776 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, 777 | xw.depth); 778 | XftDrawChange(xw.draw, xw.buf); 779 | xclear(0, 0, win.w, win.h); 780 | 781 | /* resize to new width */ 782 | xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); 783 | } 784 | 785 | ushort 786 | sixd_to_16bit(int x) 787 | { 788 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; 789 | } 790 | 791 | int 792 | xloadcolor(int i, const char *name, Color *ncolor) 793 | { 794 | XRenderColor color = { .alpha = 0xffff }; 795 | 796 | if (!name) { 797 | if (BETWEEN(i, 16, 255)) { /* 256 color */ 798 | if (i < 6*6*6+16) { /* same colors as xterm */ 799 | color.red = sixd_to_16bit( ((i-16)/36)%6 ); 800 | color.green = sixd_to_16bit( ((i-16)/6) %6 ); 801 | color.blue = sixd_to_16bit( ((i-16)/1) %6 ); 802 | } else { /* greyscale */ 803 | color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); 804 | color.green = color.blue = color.red; 805 | } 806 | return XftColorAllocValue(xw.dpy, xw.vis, 807 | xw.cmap, &color, ncolor); 808 | } else 809 | name = colorname[i]; 810 | } 811 | 812 | return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 813 | } 814 | 815 | void 816 | xloadcols(void) 817 | { 818 | int i; 819 | static int loaded; 820 | Color *cp; 821 | 822 | if (loaded) { 823 | for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) 824 | XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); 825 | } else { 826 | dc.collen = MAX(LEN(colorname), 256); 827 | dc.col = xmalloc(dc.collen * sizeof(Color)); 828 | } 829 | 830 | for (i = 0; i < dc.collen; i++) 831 | if (!xloadcolor(i, NULL, &dc.col[i])) { 832 | if (colorname[i]) 833 | die("could not allocate color '%s'\n", colorname[i]); 834 | else 835 | die("could not allocate color %d\n", i); 836 | } 837 | 838 | /* set alpha value of bg color */ 839 | if (opt_alpha) 840 | alpha = strtof(opt_alpha, NULL); 841 | dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); 842 | dc.col[defaultbg].pixel &= 0x00FFFFFF; 843 | dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; 844 | loaded = 1; 845 | } 846 | 847 | int 848 | xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) 849 | { 850 | if (!BETWEEN(x, 0, dc.collen - 1)) 851 | return 1; 852 | 853 | *r = dc.col[x].color.red >> 8; 854 | *g = dc.col[x].color.green >> 8; 855 | *b = dc.col[x].color.blue >> 8; 856 | 857 | return 0; 858 | } 859 | 860 | int 861 | xsetcolorname(int x, const char *name) 862 | { 863 | Color ncolor; 864 | 865 | if (!BETWEEN(x, 0, dc.collen - 1)) 866 | return 1; 867 | 868 | if (!xloadcolor(x, name, &ncolor)) 869 | return 1; 870 | 871 | XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); 872 | dc.col[x] = ncolor; 873 | 874 | return 0; 875 | } 876 | 877 | /* 878 | * Absolute coordinates. 879 | */ 880 | void 881 | xclear(int x1, int y1, int x2, int y2) 882 | { 883 | XftDrawRect(xw.draw, 884 | &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], 885 | x1, y1, x2-x1, y2-y1); 886 | } 887 | 888 | void 889 | xhints(void) 890 | { 891 | XClassHint class = {opt_name ? opt_name : "st", 892 | opt_class ? opt_class : "St"}; 893 | XWMHints wm = {.flags = InputHint, .input = 1}; 894 | XSizeHints *sizeh; 895 | 896 | sizeh = XAllocSizeHints(); 897 | 898 | sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; 899 | sizeh->height = win.h; 900 | sizeh->width = win.w; 901 | sizeh->height_inc = win.ch; 902 | sizeh->width_inc = win.cw; 903 | sizeh->base_height = 2 * borderpx; 904 | sizeh->base_width = 2 * borderpx; 905 | sizeh->min_height = win.ch + 2 * borderpx; 906 | sizeh->min_width = win.cw + 2 * borderpx; 907 | if (xw.isfixed) { 908 | sizeh->flags |= PMaxSize; 909 | sizeh->min_width = sizeh->max_width = win.w; 910 | sizeh->min_height = sizeh->max_height = win.h; 911 | } 912 | if (xw.gm & (XValue|YValue)) { 913 | sizeh->flags |= USPosition | PWinGravity; 914 | sizeh->x = xw.l; 915 | sizeh->y = xw.t; 916 | sizeh->win_gravity = xgeommasktogravity(xw.gm); 917 | } 918 | 919 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, 920 | &class); 921 | XFree(sizeh); 922 | } 923 | 924 | int 925 | xgeommasktogravity(int mask) 926 | { 927 | switch (mask & (XNegative|YNegative)) { 928 | case 0: 929 | return NorthWestGravity; 930 | case XNegative: 931 | return NorthEastGravity; 932 | case YNegative: 933 | return SouthWestGravity; 934 | } 935 | 936 | return SouthEastGravity; 937 | } 938 | 939 | int 940 | xloadfont(Font *f, FcPattern *pattern) 941 | { 942 | FcPattern *configured; 943 | FcPattern *match; 944 | FcResult result; 945 | XGlyphInfo extents; 946 | int wantattr, haveattr; 947 | 948 | /* 949 | * Manually configure instead of calling XftMatchFont 950 | * so that we can use the configured pattern for 951 | * "missing glyph" lookups. 952 | */ 953 | configured = FcPatternDuplicate(pattern); 954 | if (!configured) 955 | return 1; 956 | 957 | FcConfigSubstitute(NULL, configured, FcMatchPattern); 958 | XftDefaultSubstitute(xw.dpy, xw.scr, configured); 959 | 960 | match = FcFontMatch(NULL, configured, &result); 961 | if (!match) { 962 | FcPatternDestroy(configured); 963 | return 1; 964 | } 965 | 966 | if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { 967 | FcPatternDestroy(configured); 968 | FcPatternDestroy(match); 969 | return 1; 970 | } 971 | 972 | if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == 973 | XftResultMatch)) { 974 | /* 975 | * Check if xft was unable to find a font with the appropriate 976 | * slant but gave us one anyway. Try to mitigate. 977 | */ 978 | if ((XftPatternGetInteger(f->match->pattern, "slant", 0, 979 | &haveattr) != XftResultMatch) || haveattr < wantattr) { 980 | f->badslant = 1; 981 | fputs("font slant does not match\n", stderr); 982 | } 983 | } 984 | 985 | if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == 986 | XftResultMatch)) { 987 | if ((XftPatternGetInteger(f->match->pattern, "weight", 0, 988 | &haveattr) != XftResultMatch) || haveattr != wantattr) { 989 | f->badweight = 1; 990 | fputs("font weight does not match\n", stderr); 991 | } 992 | } 993 | 994 | XftTextExtentsUtf8(xw.dpy, f->match, 995 | (const FcChar8 *) ascii_printable, 996 | strlen(ascii_printable), &extents); 997 | 998 | f->set = NULL; 999 | f->pattern = configured; 1000 | 1001 | f->ascent = f->match->ascent; 1002 | f->descent = f->match->descent; 1003 | f->lbearing = 0; 1004 | f->rbearing = f->match->max_advance_width; 1005 | 1006 | f->height = f->ascent + f->descent; 1007 | f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); 1008 | 1009 | return 0; 1010 | } 1011 | 1012 | void 1013 | xloadfonts(const char *fontstr, double fontsize) 1014 | { 1015 | FcPattern *pattern; 1016 | double fontval; 1017 | 1018 | if (fontstr[0] == '-') 1019 | pattern = XftXlfdParse(fontstr, False, False); 1020 | else 1021 | pattern = FcNameParse((const FcChar8 *)fontstr); 1022 | 1023 | if (!pattern) 1024 | die("can't open font %s\n", fontstr); 1025 | 1026 | if (fontsize > 1) { 1027 | FcPatternDel(pattern, FC_PIXEL_SIZE); 1028 | FcPatternDel(pattern, FC_SIZE); 1029 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); 1030 | usedfontsize = fontsize; 1031 | } else { 1032 | if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 1033 | FcResultMatch) { 1034 | usedfontsize = fontval; 1035 | } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == 1036 | FcResultMatch) { 1037 | usedfontsize = -1; 1038 | } else { 1039 | /* 1040 | * Default font size is 12, if none given. This is to 1041 | * have a known usedfontsize value. 1042 | */ 1043 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); 1044 | usedfontsize = 12; 1045 | } 1046 | defaultfontsize = usedfontsize; 1047 | } 1048 | 1049 | if (xloadfont(&dc.font, pattern)) 1050 | die("can't open font %s\n", fontstr); 1051 | 1052 | if (usedfontsize < 0) { 1053 | FcPatternGetDouble(dc.font.match->pattern, 1054 | FC_PIXEL_SIZE, 0, &fontval); 1055 | usedfontsize = fontval; 1056 | if (fontsize == 0) 1057 | defaultfontsize = fontval; 1058 | } 1059 | 1060 | /* Setting character width and height. */ 1061 | win.cw = ceilf(dc.font.width * cwscale); 1062 | win.ch = ceilf(dc.font.height * chscale); 1063 | 1064 | FcPatternDel(pattern, FC_SLANT); 1065 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1066 | if (xloadfont(&dc.ifont, pattern)) 1067 | die("can't open font %s\n", fontstr); 1068 | 1069 | FcPatternDel(pattern, FC_WEIGHT); 1070 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1071 | if (xloadfont(&dc.ibfont, pattern)) 1072 | die("can't open font %s\n", fontstr); 1073 | 1074 | FcPatternDel(pattern, FC_SLANT); 1075 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 1076 | if (xloadfont(&dc.bfont, pattern)) 1077 | die("can't open font %s\n", fontstr); 1078 | 1079 | FcPatternDestroy(pattern); 1080 | } 1081 | 1082 | int 1083 | xloadsparefont(FcPattern *pattern, int flags) 1084 | { 1085 | FcPattern *match; 1086 | FcResult result; 1087 | 1088 | match = FcFontMatch(NULL, pattern, &result); 1089 | if (!match) { 1090 | return 1; 1091 | } 1092 | 1093 | if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) { 1094 | FcPatternDestroy(match); 1095 | return 1; 1096 | } 1097 | 1098 | frc[frclen].flags = flags; 1099 | /* Believe U+0000 glyph will present in each default font */ 1100 | frc[frclen].unicodep = 0; 1101 | frclen++; 1102 | 1103 | return 0; 1104 | } 1105 | 1106 | void 1107 | xloadsparefonts(void) 1108 | { 1109 | FcPattern *pattern; 1110 | double sizeshift, fontval; 1111 | int fc; 1112 | char **fp; 1113 | 1114 | if (frclen != 0) 1115 | die("can't embed spare fonts. cache isn't empty"); 1116 | 1117 | /* Calculate count of spare fonts */ 1118 | fc = sizeof(font2) / sizeof(*font2); 1119 | if (fc == 0) 1120 | return; 1121 | 1122 | /* Allocate memory for cache entries. */ 1123 | if (frccap < 4 * fc) { 1124 | frccap += 4 * fc - frccap; 1125 | frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1126 | } 1127 | 1128 | for (fp = font2; fp - font2 < fc; ++fp) { 1129 | 1130 | if (**fp == '-') 1131 | pattern = XftXlfdParse(*fp, False, False); 1132 | else 1133 | pattern = FcNameParse((FcChar8 *)*fp); 1134 | 1135 | if (!pattern) 1136 | die("can't open spare font %s\n", *fp); 1137 | 1138 | if (defaultfontsize > 0) { 1139 | sizeshift = usedfontsize - defaultfontsize; 1140 | if (sizeshift != 0 && 1141 | FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == 1142 | FcResultMatch) { 1143 | fontval += sizeshift; 1144 | FcPatternDel(pattern, FC_PIXEL_SIZE); 1145 | FcPatternDel(pattern, FC_SIZE); 1146 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval); 1147 | } 1148 | } 1149 | 1150 | FcPatternAddBool(pattern, FC_SCALABLE, 1); 1151 | 1152 | FcConfigSubstitute(NULL, pattern, FcMatchPattern); 1153 | XftDefaultSubstitute(xw.dpy, xw.scr, pattern); 1154 | 1155 | if (xloadsparefont(pattern, FRC_NORMAL)) 1156 | die("can't open spare font %s\n", *fp); 1157 | 1158 | FcPatternDel(pattern, FC_SLANT); 1159 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); 1160 | if (xloadsparefont(pattern, FRC_ITALIC)) 1161 | die("can't open spare font %s\n", *fp); 1162 | 1163 | FcPatternDel(pattern, FC_WEIGHT); 1164 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); 1165 | if (xloadsparefont(pattern, FRC_ITALICBOLD)) 1166 | die("can't open spare font %s\n", *fp); 1167 | 1168 | FcPatternDel(pattern, FC_SLANT); 1169 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); 1170 | if (xloadsparefont(pattern, FRC_BOLD)) 1171 | die("can't open spare font %s\n", *fp); 1172 | 1173 | FcPatternDestroy(pattern); 1174 | } 1175 | } 1176 | 1177 | void 1178 | xunloadfont(Font *f) 1179 | { 1180 | XftFontClose(xw.dpy, f->match); 1181 | FcPatternDestroy(f->pattern); 1182 | if (f->set) 1183 | FcFontSetDestroy(f->set); 1184 | } 1185 | 1186 | void 1187 | xunloadfonts(void) 1188 | { 1189 | /* Free the loaded fonts in the font cache. */ 1190 | while (frclen > 0) 1191 | XftFontClose(xw.dpy, frc[--frclen].font); 1192 | 1193 | xunloadfont(&dc.font); 1194 | xunloadfont(&dc.bfont); 1195 | xunloadfont(&dc.ifont); 1196 | xunloadfont(&dc.ibfont); 1197 | } 1198 | 1199 | int 1200 | ximopen(Display *dpy) 1201 | { 1202 | XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; 1203 | XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; 1204 | 1205 | xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); 1206 | if (xw.ime.xim == NULL) 1207 | return 0; 1208 | 1209 | if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) 1210 | fprintf(stderr, "XSetIMValues: " 1211 | "Could not set XNDestroyCallback.\n"); 1212 | 1213 | xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, 1214 | NULL); 1215 | 1216 | if (xw.ime.xic == NULL) { 1217 | xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, 1218 | XIMPreeditNothing | XIMStatusNothing, 1219 | XNClientWindow, xw.win, 1220 | XNDestroyCallback, &icdestroy, 1221 | NULL); 1222 | } 1223 | if (xw.ime.xic == NULL) 1224 | fprintf(stderr, "XCreateIC: Could not create input context.\n"); 1225 | 1226 | return 1; 1227 | } 1228 | 1229 | void 1230 | ximinstantiate(Display *dpy, XPointer client, XPointer call) 1231 | { 1232 | if (ximopen(dpy)) 1233 | XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1234 | ximinstantiate, NULL); 1235 | } 1236 | 1237 | void 1238 | ximdestroy(XIM xim, XPointer client, XPointer call) 1239 | { 1240 | xw.ime.xim = NULL; 1241 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1242 | ximinstantiate, NULL); 1243 | XFree(xw.ime.spotlist); 1244 | } 1245 | 1246 | int 1247 | xicdestroy(XIC xim, XPointer client, XPointer call) 1248 | { 1249 | xw.ime.xic = NULL; 1250 | return 1; 1251 | } 1252 | 1253 | void 1254 | xinit(int cols, int rows) 1255 | { 1256 | XGCValues gcvalues; 1257 | Cursor cursor; 1258 | Window parent, root; 1259 | pid_t thispid = getpid(); 1260 | XColor xmousefg, xmousebg; 1261 | XWindowAttributes attr; 1262 | XVisualInfo vis; 1263 | 1264 | xw.scr = XDefaultScreen(xw.dpy); 1265 | 1266 | root = XRootWindow(xw.dpy, xw.scr); 1267 | if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { 1268 | parent = root; 1269 | xw.depth = 32; 1270 | } else { 1271 | XGetWindowAttributes(xw.dpy, parent, &attr); 1272 | xw.depth = attr.depth; 1273 | } 1274 | 1275 | XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); 1276 | xw.vis = vis.visual; 1277 | 1278 | /* font */ 1279 | if (!FcInit()) 1280 | die("could not init fontconfig.\n"); 1281 | 1282 | usedfont = (opt_font == NULL)? font : opt_font; 1283 | xloadfonts(usedfont, 0); 1284 | 1285 | /* spare fonts */ 1286 | xloadsparefonts(); 1287 | 1288 | /* colors */ 1289 | xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); 1290 | xloadcols(); 1291 | 1292 | /* adjust fixed window geometry */ 1293 | win.w = 2 * borderpx + cols * win.cw; 1294 | win.h = 2 * borderpx + rows * win.ch; 1295 | if (xw.gm & XNegative) 1296 | xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; 1297 | if (xw.gm & YNegative) 1298 | xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; 1299 | 1300 | /* Events */ 1301 | xw.attrs.background_pixel = dc.col[defaultbg].pixel; 1302 | xw.attrs.border_pixel = dc.col[defaultbg].pixel; 1303 | xw.attrs.bit_gravity = NorthWestGravity; 1304 | xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask 1305 | | ExposureMask | VisibilityChangeMask | StructureNotifyMask 1306 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; 1307 | xw.attrs.colormap = xw.cmap; 1308 | 1309 | xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, 1310 | win.w, win.h, 0, xw.depth, InputOutput, 1311 | xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity 1312 | | CWEventMask | CWColormap, &xw.attrs); 1313 | if (parent != root) 1314 | XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); 1315 | 1316 | memset(&gcvalues, 0, sizeof(gcvalues)); 1317 | gcvalues.graphics_exposures = False; 1318 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); 1319 | dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); 1320 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); 1321 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); 1322 | 1323 | /* font spec buffer */ 1324 | xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); 1325 | 1326 | /* Xft rendering context */ 1327 | xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); 1328 | 1329 | /* input methods */ 1330 | if (!ximopen(xw.dpy)) { 1331 | XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, 1332 | ximinstantiate, NULL); 1333 | } 1334 | 1335 | /* white cursor, black outline */ 1336 | cursor = XCreateFontCursor(xw.dpy, mouseshape); 1337 | XDefineCursor(xw.dpy, xw.win, cursor); 1338 | 1339 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { 1340 | xmousefg.red = 0xffff; 1341 | xmousefg.green = 0xffff; 1342 | xmousefg.blue = 0xffff; 1343 | } 1344 | 1345 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { 1346 | xmousebg.red = 0x0000; 1347 | xmousebg.green = 0x0000; 1348 | xmousebg.blue = 0x0000; 1349 | } 1350 | 1351 | XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); 1352 | 1353 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); 1354 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 1355 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 1356 | xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); 1357 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 1358 | 1359 | xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); 1360 | XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, 1361 | PropModeReplace, (uchar *)&thispid, 1); 1362 | 1363 | win.mode = MODE_NUMLOCK; 1364 | resettitle(); 1365 | xhints(); 1366 | XMapWindow(xw.dpy, xw.win); 1367 | XSync(xw.dpy, False); 1368 | 1369 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); 1370 | clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); 1371 | xsel.primary = NULL; 1372 | xsel.clipboard = NULL; 1373 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); 1374 | if (xsel.xtarget == None) 1375 | xsel.xtarget = XA_STRING; 1376 | 1377 | boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); 1378 | } 1379 | 1380 | int 1381 | xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) 1382 | { 1383 | float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; 1384 | ushort mode, prevmode = USHRT_MAX; 1385 | Font *font = &dc.font; 1386 | int frcflags = FRC_NORMAL; 1387 | float runewidth = win.cw; 1388 | Rune rune; 1389 | FT_UInt glyphidx; 1390 | FcResult fcres; 1391 | FcPattern *fcpattern, *fontpattern; 1392 | FcFontSet *fcsets[] = { NULL }; 1393 | FcCharSet *fccharset; 1394 | int i, f, numspecs = 0; 1395 | 1396 | for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { 1397 | /* Fetch rune and mode for current glyph. */ 1398 | rune = glyphs[i].u; 1399 | mode = glyphs[i].mode; 1400 | 1401 | /* Skip dummy wide-character spacing. */ 1402 | if (mode == ATTR_WDUMMY) 1403 | continue; 1404 | 1405 | /* Determine font for glyph if different from previous glyph. */ 1406 | if (prevmode != mode) { 1407 | prevmode = mode; 1408 | font = &dc.font; 1409 | frcflags = FRC_NORMAL; 1410 | runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); 1411 | if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { 1412 | font = &dc.ibfont; 1413 | frcflags = FRC_ITALICBOLD; 1414 | } else if (mode & ATTR_ITALIC) { 1415 | font = &dc.ifont; 1416 | frcflags = FRC_ITALIC; 1417 | } else if (mode & ATTR_BOLD) { 1418 | font = &dc.bfont; 1419 | frcflags = FRC_BOLD; 1420 | } 1421 | yp = winy + font->ascent; 1422 | } 1423 | 1424 | if (mode & ATTR_BOXDRAW) { 1425 | /* minor shoehorning: boxdraw uses only this ushort */ 1426 | glyphidx = boxdrawindex(&glyphs[i]); 1427 | } else { 1428 | /* Lookup character index with default font. */ 1429 | glyphidx = XftCharIndex(xw.dpy, font->match, rune); 1430 | } 1431 | if (glyphidx) { 1432 | specs[numspecs].font = font->match; 1433 | specs[numspecs].glyph = glyphidx; 1434 | specs[numspecs].x = (short)xp; 1435 | specs[numspecs].y = (short)yp; 1436 | xp += runewidth; 1437 | numspecs++; 1438 | continue; 1439 | } 1440 | 1441 | /* Fallback on font cache, search the font cache for match. */ 1442 | for (f = 0; f < frclen; f++) { 1443 | glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); 1444 | /* Everything correct. */ 1445 | if (glyphidx && frc[f].flags == frcflags) 1446 | break; 1447 | /* We got a default font for a not found glyph. */ 1448 | if (!glyphidx && frc[f].flags == frcflags 1449 | && frc[f].unicodep == rune) { 1450 | break; 1451 | } 1452 | } 1453 | 1454 | /* Nothing was found. Use fontconfig to find matching font. */ 1455 | if (f >= frclen) { 1456 | if (!font->set) 1457 | font->set = FcFontSort(0, font->pattern, 1458 | 1, 0, &fcres); 1459 | fcsets[0] = font->set; 1460 | 1461 | /* 1462 | * Nothing was found in the cache. Now use 1463 | * some dozen of Fontconfig calls to get the 1464 | * font for one single character. 1465 | * 1466 | * Xft and fontconfig are design failures. 1467 | */ 1468 | fcpattern = FcPatternDuplicate(font->pattern); 1469 | fccharset = FcCharSetCreate(); 1470 | 1471 | FcCharSetAddChar(fccharset, rune); 1472 | FcPatternAddCharSet(fcpattern, FC_CHARSET, 1473 | fccharset); 1474 | FcPatternAddBool(fcpattern, FC_SCALABLE, 1); 1475 | 1476 | FcConfigSubstitute(0, fcpattern, 1477 | FcMatchPattern); 1478 | FcDefaultSubstitute(fcpattern); 1479 | 1480 | fontpattern = FcFontSetMatch(0, fcsets, 1, 1481 | fcpattern, &fcres); 1482 | 1483 | /* Allocate memory for the new cache entry. */ 1484 | if (frclen >= frccap) { 1485 | frccap += 16; 1486 | frc = xrealloc(frc, frccap * sizeof(Fontcache)); 1487 | } 1488 | 1489 | frc[frclen].font = XftFontOpenPattern(xw.dpy, 1490 | fontpattern); 1491 | if (!frc[frclen].font) 1492 | die("XftFontOpenPattern failed seeking fallback font: %s\n", 1493 | strerror(errno)); 1494 | frc[frclen].flags = frcflags; 1495 | frc[frclen].unicodep = rune; 1496 | 1497 | glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); 1498 | 1499 | f = frclen; 1500 | frclen++; 1501 | 1502 | FcPatternDestroy(fcpattern); 1503 | FcCharSetDestroy(fccharset); 1504 | } 1505 | 1506 | specs[numspecs].font = frc[f].font; 1507 | specs[numspecs].glyph = glyphidx; 1508 | specs[numspecs].x = (short)xp; 1509 | specs[numspecs].y = (short)yp; 1510 | xp += runewidth; 1511 | numspecs++; 1512 | } 1513 | 1514 | return numspecs; 1515 | } 1516 | 1517 | void 1518 | xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) 1519 | { 1520 | int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); 1521 | int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, 1522 | width = charlen * win.cw; 1523 | Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; 1524 | XRenderColor colfg, colbg; 1525 | XRectangle r; 1526 | 1527 | /* Fallback on color display for attributes not supported by the font */ 1528 | if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { 1529 | if (dc.ibfont.badslant || dc.ibfont.badweight) 1530 | base.fg = defaultattr; 1531 | } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || 1532 | (base.mode & ATTR_BOLD && dc.bfont.badweight)) { 1533 | base.fg = defaultattr; 1534 | } 1535 | 1536 | if (IS_TRUECOL(base.fg)) { 1537 | colfg.alpha = 0xffff; 1538 | colfg.red = TRUERED(base.fg); 1539 | colfg.green = TRUEGREEN(base.fg); 1540 | colfg.blue = TRUEBLUE(base.fg); 1541 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); 1542 | fg = &truefg; 1543 | } else { 1544 | fg = &dc.col[base.fg]; 1545 | } 1546 | 1547 | if (IS_TRUECOL(base.bg)) { 1548 | colbg.alpha = 0xffff; 1549 | colbg.green = TRUEGREEN(base.bg); 1550 | colbg.red = TRUERED(base.bg); 1551 | colbg.blue = TRUEBLUE(base.bg); 1552 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); 1553 | bg = &truebg; 1554 | } else { 1555 | bg = &dc.col[base.bg]; 1556 | } 1557 | 1558 | /* Change basic system colors [0-7] to bright system colors [8-15] */ 1559 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) 1560 | fg = &dc.col[base.fg + 8]; 1561 | 1562 | if (IS_SET(MODE_REVERSE)) { 1563 | if (fg == &dc.col[defaultfg]) { 1564 | fg = &dc.col[defaultbg]; 1565 | } else { 1566 | colfg.red = ~fg->color.red; 1567 | colfg.green = ~fg->color.green; 1568 | colfg.blue = ~fg->color.blue; 1569 | colfg.alpha = fg->color.alpha; 1570 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, 1571 | &revfg); 1572 | fg = &revfg; 1573 | } 1574 | 1575 | if (bg == &dc.col[defaultbg]) { 1576 | bg = &dc.col[defaultfg]; 1577 | } else { 1578 | colbg.red = ~bg->color.red; 1579 | colbg.green = ~bg->color.green; 1580 | colbg.blue = ~bg->color.blue; 1581 | colbg.alpha = bg->color.alpha; 1582 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, 1583 | &revbg); 1584 | bg = &revbg; 1585 | } 1586 | } 1587 | 1588 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { 1589 | colfg.red = fg->color.red / 2; 1590 | colfg.green = fg->color.green / 2; 1591 | colfg.blue = fg->color.blue / 2; 1592 | colfg.alpha = fg->color.alpha; 1593 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); 1594 | fg = &revfg; 1595 | } 1596 | 1597 | if (base.mode & ATTR_REVERSE) { 1598 | temp = fg; 1599 | fg = bg; 1600 | bg = temp; 1601 | } 1602 | 1603 | if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) 1604 | fg = bg; 1605 | 1606 | if (base.mode & ATTR_INVISIBLE) 1607 | fg = bg; 1608 | 1609 | /* Intelligent cleaning up of the borders. */ 1610 | if (x == 0) { 1611 | xclear(0, (y == 0)? 0 : winy, borderpx, 1612 | winy + win.ch + 1613 | ((winy + win.ch >= borderpx + win.th)? win.h : 0)); 1614 | } 1615 | if (winx + width >= borderpx + win.tw) { 1616 | xclear(winx + width, (y == 0)? 0 : winy, win.w, 1617 | ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); 1618 | } 1619 | if (y == 0) 1620 | xclear(winx, 0, winx + width, borderpx); 1621 | if (winy + win.ch >= borderpx + win.th) 1622 | xclear(winx, winy + win.ch, winx + width, win.h); 1623 | 1624 | /* Clean up the region we want to draw to. */ 1625 | XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 1626 | 1627 | /* Set the clip region because Xft is sometimes dirty. */ 1628 | r.x = 0; 1629 | r.y = 0; 1630 | r.height = win.ch; 1631 | r.width = width; 1632 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); 1633 | 1634 | if (base.mode & ATTR_BOXDRAW) { 1635 | drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); 1636 | } else { 1637 | /* Render the glyphs. */ 1638 | XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 1639 | } 1640 | 1641 | /* Render underline and strikethrough. */ 1642 | if (base.mode & ATTR_UNDERLINE) { 1643 | XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, 1644 | width, 1); 1645 | } 1646 | 1647 | if (base.mode & ATTR_STRUCK) { 1648 | XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, 1649 | width, 1); 1650 | } 1651 | 1652 | /* Reset clip to none. */ 1653 | XftDrawSetClip(xw.draw, 0); 1654 | } 1655 | 1656 | void 1657 | xdrawglyph(Glyph g, int x, int y) 1658 | { 1659 | int numspecs; 1660 | XftGlyphFontSpec spec; 1661 | 1662 | numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); 1663 | xdrawglyphfontspecs(&spec, g, numspecs, x, y); 1664 | } 1665 | 1666 | void 1667 | xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 1668 | { 1669 | Color drawcol; 1670 | 1671 | /* remove the old cursor */ 1672 | if (selected(ox, oy)) 1673 | og.mode ^= ATTR_REVERSE; 1674 | xdrawglyph(og, ox, oy); 1675 | 1676 | if (IS_SET(MODE_HIDE)) 1677 | return; 1678 | 1679 | /* 1680 | * Select the right color for the right mode. 1681 | */ 1682 | g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; 1683 | 1684 | if (IS_SET(MODE_REVERSE)) { 1685 | g.mode |= ATTR_REVERSE; 1686 | g.bg = defaultfg; 1687 | if (selected(cx, cy)) { 1688 | drawcol = dc.col[defaultcs]; 1689 | g.fg = defaultrcs; 1690 | } else { 1691 | drawcol = dc.col[defaultrcs]; 1692 | g.fg = defaultcs; 1693 | } 1694 | } else { 1695 | if (selected(cx, cy)) { 1696 | g.fg = defaultfg; 1697 | g.bg = defaultrcs; 1698 | } else { 1699 | g.fg = defaultbg; 1700 | g.bg = defaultcs; 1701 | } 1702 | drawcol = dc.col[g.bg]; 1703 | } 1704 | 1705 | /* draw the new one */ 1706 | if (IS_SET(MODE_FOCUSED)) { 1707 | switch (win.cursor) { 1708 | case 7: /* st extension */ 1709 | g.u = 0x2603; /* snowman (U+2603) */ 1710 | /* FALLTHROUGH */ 1711 | case 0: /* Blinking Block */ 1712 | case 1: /* Blinking Block (Default) */ 1713 | case 2: /* Steady Block */ 1714 | xdrawglyph(g, cx, cy); 1715 | break; 1716 | case 3: /* Blinking Underline */ 1717 | case 4: /* Steady Underline */ 1718 | XftDrawRect(xw.draw, &drawcol, 1719 | borderpx + cx * win.cw, 1720 | borderpx + (cy + 1) * win.ch - 1721 | cursorthickness, 1722 | win.cw, cursorthickness); 1723 | break; 1724 | case 5: /* Blinking bar */ 1725 | case 6: /* Steady bar */ 1726 | XftDrawRect(xw.draw, &drawcol, 1727 | borderpx + cx * win.cw, 1728 | borderpx + cy * win.ch, 1729 | cursorthickness, win.ch); 1730 | break; 1731 | } 1732 | } else { 1733 | XftDrawRect(xw.draw, &drawcol, 1734 | borderpx + cx * win.cw, 1735 | borderpx + cy * win.ch, 1736 | win.cw - 1, 1); 1737 | XftDrawRect(xw.draw, &drawcol, 1738 | borderpx + cx * win.cw, 1739 | borderpx + cy * win.ch, 1740 | 1, win.ch - 1); 1741 | XftDrawRect(xw.draw, &drawcol, 1742 | borderpx + (cx + 1) * win.cw - 1, 1743 | borderpx + cy * win.ch, 1744 | 1, win.ch - 1); 1745 | XftDrawRect(xw.draw, &drawcol, 1746 | borderpx + cx * win.cw, 1747 | borderpx + (cy + 1) * win.ch - 1, 1748 | win.cw, 1); 1749 | } 1750 | } 1751 | 1752 | void 1753 | xsetenv(void) 1754 | { 1755 | char buf[sizeof(long) * 8 + 1]; 1756 | 1757 | snprintf(buf, sizeof(buf), "%lu", xw.win); 1758 | setenv("WINDOWID", buf, 1); 1759 | } 1760 | 1761 | void 1762 | xseticontitle(char *p) 1763 | { 1764 | XTextProperty prop; 1765 | DEFAULT(p, opt_title); 1766 | 1767 | if (p[0] == '\0') 1768 | p = opt_title; 1769 | 1770 | if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1771 | &prop) != Success) 1772 | return; 1773 | XSetWMIconName(xw.dpy, xw.win, &prop); 1774 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); 1775 | XFree(prop.value); 1776 | } 1777 | 1778 | void 1779 | xsettitle(char *p) 1780 | { 1781 | XTextProperty prop; 1782 | DEFAULT(p, opt_title); 1783 | 1784 | if (p[0] == '\0') 1785 | p = opt_title; 1786 | 1787 | if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, 1788 | &prop) != Success) 1789 | return; 1790 | XSetWMName(xw.dpy, xw.win, &prop); 1791 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 1792 | XFree(prop.value); 1793 | } 1794 | 1795 | int 1796 | xstartdraw(void) 1797 | { 1798 | if (IS_SET(MODE_VISIBLE)) 1799 | XCopyArea(xw.dpy, xw.win, xw.buf, dc.gc, 0, 0, win.w, win.h, 0, 0); 1800 | return IS_SET(MODE_VISIBLE); 1801 | } 1802 | 1803 | void 1804 | xdrawline(Line line, int x1, int y1, int x2) 1805 | { 1806 | int i, x, ox, numspecs; 1807 | Glyph base, new; 1808 | XftGlyphFontSpec *specs = xw.specbuf; 1809 | 1810 | numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); 1811 | i = ox = 0; 1812 | for (x = x1; x < x2 && i < numspecs; x++) { 1813 | new = line[x]; 1814 | if (new.mode == ATTR_WDUMMY) 1815 | continue; 1816 | if (selected(x, y1)) 1817 | new.mode ^= ATTR_REVERSE; 1818 | if (i > 0 && ATTRCMP(base, new)) { 1819 | xdrawglyphfontspecs(specs, base, i, ox, y1); 1820 | specs += i; 1821 | numspecs -= i; 1822 | i = 0; 1823 | } 1824 | if (i == 0) { 1825 | ox = x; 1826 | base = new; 1827 | } 1828 | i++; 1829 | } 1830 | if (i > 0) 1831 | xdrawglyphfontspecs(specs, base, i, ox, y1); 1832 | } 1833 | 1834 | void 1835 | xfinishdraw(void) 1836 | { 1837 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, 1838 | win.h, 0, 0); 1839 | XSetForeground(xw.dpy, dc.gc, 1840 | dc.col[IS_SET(MODE_REVERSE)? 1841 | defaultfg : defaultbg].pixel); 1842 | } 1843 | 1844 | void 1845 | xximspot(int x, int y) 1846 | { 1847 | if (xw.ime.xic == NULL) 1848 | return; 1849 | 1850 | xw.ime.spot.x = borderpx + x * win.cw; 1851 | xw.ime.spot.y = borderpx + (y + 1) * win.ch; 1852 | 1853 | XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); 1854 | } 1855 | 1856 | void 1857 | expose(XEvent *ev) 1858 | { 1859 | redraw(); 1860 | } 1861 | 1862 | void 1863 | visibility(XEvent *ev) 1864 | { 1865 | XVisibilityEvent *e = &ev->xvisibility; 1866 | 1867 | MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); 1868 | } 1869 | 1870 | void 1871 | unmap(XEvent *ev) 1872 | { 1873 | win.mode &= ~MODE_VISIBLE; 1874 | } 1875 | 1876 | void 1877 | xsetpointermotion(int set) 1878 | { 1879 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); 1880 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); 1881 | } 1882 | 1883 | void 1884 | xsetmode(int set, unsigned int flags) 1885 | { 1886 | int mode = win.mode; 1887 | MODBIT(win.mode, set, flags); 1888 | if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) 1889 | redraw(); 1890 | } 1891 | 1892 | int 1893 | xsetcursor(int cursor) 1894 | { 1895 | if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ 1896 | return 1; 1897 | win.cursor = cursor; 1898 | return 0; 1899 | } 1900 | 1901 | void 1902 | xseturgency(int add) 1903 | { 1904 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); 1905 | 1906 | MODBIT(h->flags, add, XUrgencyHint); 1907 | XSetWMHints(xw.dpy, xw.win, h); 1908 | XFree(h); 1909 | } 1910 | 1911 | void 1912 | xbell(void) 1913 | { 1914 | if (!(IS_SET(MODE_FOCUSED))) 1915 | xseturgency(1); 1916 | if (bellvolume) 1917 | XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); 1918 | } 1919 | 1920 | void 1921 | focus(XEvent *ev) 1922 | { 1923 | XFocusChangeEvent *e = &ev->xfocus; 1924 | 1925 | if (e->mode == NotifyGrab) 1926 | return; 1927 | 1928 | if (ev->type == FocusIn) { 1929 | if (xw.ime.xic) 1930 | XSetICFocus(xw.ime.xic); 1931 | win.mode |= MODE_FOCUSED; 1932 | xseturgency(0); 1933 | if (IS_SET(MODE_FOCUS)) 1934 | ttywrite("\033[I", 3, 0); 1935 | } else { 1936 | if (xw.ime.xic) 1937 | XUnsetICFocus(xw.ime.xic); 1938 | win.mode &= ~MODE_FOCUSED; 1939 | if (IS_SET(MODE_FOCUS)) 1940 | ttywrite("\033[O", 3, 0); 1941 | } 1942 | } 1943 | 1944 | int 1945 | match(uint mask, uint state) 1946 | { 1947 | return mask == XK_ANY_MOD || mask == (state & ~ignoremod); 1948 | } 1949 | 1950 | char* 1951 | kmap(KeySym k, uint state) 1952 | { 1953 | Key *kp; 1954 | int i; 1955 | 1956 | /* Check for mapped keys out of X11 function keys. */ 1957 | for (i = 0; i < LEN(mappedkeys); i++) { 1958 | if (mappedkeys[i] == k) 1959 | break; 1960 | } 1961 | if (i == LEN(mappedkeys)) { 1962 | if ((k & 0xFFFF) < 0xFD00) 1963 | return NULL; 1964 | } 1965 | 1966 | for (kp = key; kp < key + LEN(key); kp++) { 1967 | if (kp->k != k) 1968 | continue; 1969 | 1970 | if (!match(kp->mask, state)) 1971 | continue; 1972 | 1973 | if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) 1974 | continue; 1975 | if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) 1976 | continue; 1977 | 1978 | if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) 1979 | continue; 1980 | 1981 | return kp->s; 1982 | } 1983 | 1984 | return NULL; 1985 | } 1986 | 1987 | void 1988 | kpress(XEvent *ev) 1989 | { 1990 | XKeyEvent *e = &ev->xkey; 1991 | KeySym ksym = NoSymbol; 1992 | char buf[64], *customkey; 1993 | int len; 1994 | Rune c; 1995 | Status status; 1996 | Shortcut *bp; 1997 | 1998 | if (IS_SET(MODE_KBDLOCK)) 1999 | return; 2000 | 2001 | if (xw.ime.xic) { 2002 | len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); 2003 | if (status == XBufferOverflow) 2004 | return; 2005 | } else { 2006 | len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 2007 | } 2008 | if ( IS_SET(MODE_KBDSELECT) ) { 2009 | if ( match(XK_NO_MOD, e->state) || 2010 | (XK_Shift_L | XK_Shift_R) & e->state ) 2011 | win.mode ^= trt_kbdselect(ksym, buf, len); 2012 | return; 2013 | } 2014 | /* 1. shortcuts */ 2015 | for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 2016 | if (ksym == bp->keysym && match(bp->mod, e->state)) { 2017 | bp->func(&(bp->arg)); 2018 | return; 2019 | } 2020 | } 2021 | 2022 | /* 2. custom keys from config.h */ 2023 | if ((customkey = kmap(ksym, e->state))) { 2024 | ttywrite(customkey, strlen(customkey), 1); 2025 | return; 2026 | } 2027 | 2028 | /* 3. composed string from input method */ 2029 | if (len == 0) 2030 | return; 2031 | if (len == 1 && e->state & Mod1Mask) { 2032 | if (IS_SET(MODE_8BIT)) { 2033 | if (*buf < 0177) { 2034 | c = *buf | 0x80; 2035 | len = utf8encode(c, buf); 2036 | } 2037 | } else { 2038 | buf[1] = buf[0]; 2039 | buf[0] = '\033'; 2040 | len = 2; 2041 | } 2042 | } 2043 | ttywrite(buf, len, 1); 2044 | } 2045 | 2046 | void 2047 | cmessage(XEvent *e) 2048 | { 2049 | /* 2050 | * See xembed specs 2051 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html 2052 | */ 2053 | if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { 2054 | if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { 2055 | win.mode |= MODE_FOCUSED; 2056 | xseturgency(0); 2057 | } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { 2058 | win.mode &= ~MODE_FOCUSED; 2059 | } 2060 | } else if (e->xclient.data.l[0] == xw.wmdeletewin) { 2061 | ttyhangup(); 2062 | exit(0); 2063 | } 2064 | } 2065 | 2066 | void 2067 | resize(XEvent *e) 2068 | { 2069 | if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) 2070 | return; 2071 | 2072 | cresize(e->xconfigure.width, e->xconfigure.height); 2073 | } 2074 | 2075 | int tinsync(uint); 2076 | int ttyread_pending(); 2077 | 2078 | void 2079 | run(void) 2080 | { 2081 | XEvent ev; 2082 | int w = win.w, h = win.h; 2083 | fd_set rfd; 2084 | int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; 2085 | struct timespec seltv, *tv, now, lastblink, trigger; 2086 | double timeout; 2087 | 2088 | /* Waiting for window mapping */ 2089 | do { 2090 | XNextEvent(xw.dpy, &ev); 2091 | /* 2092 | * This XFilterEvent call is required because of XOpenIM. It 2093 | * does filter out the key event and some client message for 2094 | * the input method too. 2095 | */ 2096 | if (XFilterEvent(&ev, None)) 2097 | continue; 2098 | if (ev.type == ConfigureNotify) { 2099 | w = ev.xconfigure.width; 2100 | h = ev.xconfigure.height; 2101 | } 2102 | } while (ev.type != MapNotify); 2103 | 2104 | ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); 2105 | cresize(w, h); 2106 | 2107 | for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { 2108 | FD_ZERO(&rfd); 2109 | FD_SET(ttyfd, &rfd); 2110 | FD_SET(xfd, &rfd); 2111 | 2112 | if (XPending(xw.dpy) || ttyread_pending()) 2113 | timeout = 0; /* existing events might not set xfd */ 2114 | 2115 | seltv.tv_sec = timeout / 1E3; 2116 | seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); 2117 | tv = timeout >= 0 ? &seltv : NULL; 2118 | 2119 | if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 2120 | if (errno == EINTR) 2121 | continue; 2122 | die("select failed: %s\n", strerror(errno)); 2123 | } 2124 | clock_gettime(CLOCK_MONOTONIC, &now); 2125 | 2126 | int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending(); 2127 | if (ttyin) 2128 | ttyread(); 2129 | 2130 | xev = 0; 2131 | while (XPending(xw.dpy)) { 2132 | xev = 1; 2133 | XNextEvent(xw.dpy, &ev); 2134 | if (XFilterEvent(&ev, None)) 2135 | continue; 2136 | if (handler[ev.type]) 2137 | (handler[ev.type])(&ev); 2138 | } 2139 | 2140 | /* 2141 | * To reduce flicker and tearing, when new content or event 2142 | * triggers drawing, we first wait a bit to ensure we got 2143 | * everything, and if nothing new arrives - we draw. 2144 | * We start with trying to wait minlatency ms. If more content 2145 | * arrives sooner, we retry with shorter and shorter periods, 2146 | * and eventually draw even without idle after maxlatency ms. 2147 | * Typically this results in low latency while interacting, 2148 | * maximum latency intervals during `cat huge.txt`, and perfect 2149 | * sync with periodic updates from animations/key-repeats/etc. 2150 | */ 2151 | if (ttyin || xev) { 2152 | if (!drawing) { 2153 | trigger = now; 2154 | drawing = 1; 2155 | } 2156 | timeout = (maxlatency - TIMEDIFF(now, trigger)) 2157 | / maxlatency * minlatency; 2158 | if (timeout > 0) 2159 | continue; /* we have time, try to find idle */ 2160 | } 2161 | 2162 | if (tinsync(su_timeout)) { 2163 | /* 2164 | * on synchronized-update draw-suspension: don't reset 2165 | * drawing so that we draw ASAP once we can (just after 2166 | * ESU). it won't be too soon because we already can 2167 | * draw now but we skip. we set timeout > 0 to draw on 2168 | * SU-timeout even without new content. 2169 | */ 2170 | timeout = minlatency; 2171 | continue; 2172 | } 2173 | 2174 | /* idle detected or maxlatency exhausted -> draw */ 2175 | timeout = -1; 2176 | if (blinktimeout && tattrset(ATTR_BLINK)) { 2177 | timeout = blinktimeout - TIMEDIFF(now, lastblink); 2178 | if (timeout <= 0) { 2179 | if (-timeout > blinktimeout) /* start visible */ 2180 | win.mode |= MODE_BLINK; 2181 | win.mode ^= MODE_BLINK; 2182 | tsetdirtattr(ATTR_BLINK); 2183 | lastblink = now; 2184 | timeout = blinktimeout; 2185 | } 2186 | } 2187 | 2188 | draw(); 2189 | XFlush(xw.dpy); 2190 | drawing = 0; 2191 | } 2192 | } 2193 | 2194 | int 2195 | resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) 2196 | { 2197 | char **sdst = dst; 2198 | int *idst = dst; 2199 | float *fdst = dst; 2200 | 2201 | char fullname[256]; 2202 | char fullclass[256]; 2203 | char *type; 2204 | XrmValue ret; 2205 | 2206 | snprintf(fullname, sizeof(fullname), "%s.%s", 2207 | opt_name ? opt_name : "st", name); 2208 | snprintf(fullclass, sizeof(fullclass), "%s.%s", 2209 | opt_class ? opt_class : "St", name); 2210 | fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; 2211 | 2212 | XrmGetResource(db, fullname, fullclass, &type, &ret); 2213 | if (ret.addr == NULL || strncmp("String", type, 64)) 2214 | return 1; 2215 | 2216 | switch (rtype) { 2217 | case STRING: 2218 | *sdst = ret.addr; 2219 | break; 2220 | case INTEGER: 2221 | *idst = strtoul(ret.addr, NULL, 10); 2222 | break; 2223 | case FLOAT: 2224 | *fdst = strtof(ret.addr, NULL); 2225 | break; 2226 | } 2227 | return 0; 2228 | } 2229 | 2230 | void 2231 | config_init(void) 2232 | { 2233 | char *resm; 2234 | XrmDatabase db; 2235 | ResourcePref *p; 2236 | 2237 | XrmInitialize(); 2238 | resm = XResourceManagerString(xw.dpy); 2239 | if (!resm) 2240 | return; 2241 | 2242 | db = XrmGetStringDatabase(resm); 2243 | for (p = resources; p < resources + LEN(resources); p++) 2244 | resource_load(db, p->name, p->type, p->dst); 2245 | } 2246 | 2247 | void 2248 | usage(void) 2249 | { 2250 | die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" 2251 | " [-n name] [-o file]\n" 2252 | " [-T title] [-t title] [-w windowid]" 2253 | " [[-e] command [args ...]]\n" 2254 | " %s [-aiv] [-c class] [-f font] [-g geometry]" 2255 | " [-n name] [-o file]\n" 2256 | " [-T title] [-t title] [-w windowid] -l line" 2257 | " [stty_args ...]\n", argv0, argv0); 2258 | } 2259 | 2260 | void toggle_winmode(int flag) { 2261 | win.mode ^= flag; 2262 | } 2263 | 2264 | void keyboard_select(const Arg *dummy) { 2265 | win.mode ^= trt_kbdselect(-1, NULL, 0); 2266 | } 2267 | 2268 | int 2269 | main(int argc, char *argv[]) 2270 | { 2271 | xw.l = xw.t = 0; 2272 | xw.isfixed = False; 2273 | xsetcursor(cursorshape); 2274 | 2275 | ARGBEGIN { 2276 | case 'a': 2277 | allowaltscreen = 0; 2278 | break; 2279 | case 'A': 2280 | opt_alpha = EARGF(usage()); 2281 | break; 2282 | case 'c': 2283 | opt_class = EARGF(usage()); 2284 | break; 2285 | case 'e': 2286 | if (argc > 0) 2287 | --argc, ++argv; 2288 | goto run; 2289 | case 'f': 2290 | opt_font = EARGF(usage()); 2291 | break; 2292 | case 'g': 2293 | xw.gm = XParseGeometry(EARGF(usage()), 2294 | &xw.l, &xw.t, &cols, &rows); 2295 | break; 2296 | case 'i': 2297 | xw.isfixed = 1; 2298 | break; 2299 | case 'o': 2300 | opt_io = EARGF(usage()); 2301 | break; 2302 | case 'l': 2303 | opt_line = EARGF(usage()); 2304 | break; 2305 | case 'n': 2306 | opt_name = EARGF(usage()); 2307 | break; 2308 | case 't': 2309 | case 'T': 2310 | opt_title = EARGF(usage()); 2311 | break; 2312 | case 'w': 2313 | opt_embed = EARGF(usage()); 2314 | break; 2315 | case 'v': 2316 | die("%s " VERSION "\n", argv0); 2317 | break; 2318 | default: 2319 | usage(); 2320 | } ARGEND; 2321 | 2322 | run: 2323 | if (argc > 0) /* eat all remaining arguments */ 2324 | opt_cmd = argv; 2325 | 2326 | if (!opt_title) 2327 | opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; 2328 | 2329 | setlocale(LC_CTYPE, ""); 2330 | XSetLocaleModifiers(""); 2331 | 2332 | if(!(xw.dpy = XOpenDisplay(NULL))) 2333 | die("Can't open display\n"); 2334 | 2335 | config_init(); 2336 | cols = MAX(cols, 1); 2337 | rows = MAX(rows, 1); 2338 | tnew(cols, rows); 2339 | xinit(cols, rows); 2340 | xsetenv(); 2341 | selinit(); 2342 | run(); 2343 | 2344 | return 0; 2345 | } 2346 | 2347 | void 2348 | opencopied(const Arg *arg) 2349 | { 2350 | size_t const max_cmd = 2048; 2351 | char * const clip = xsel.clipboard; 2352 | if(!clip) { 2353 | fprintf(stderr, "Warning: nothing copied to clipboard\n"); 2354 | return; 2355 | } 2356 | 2357 | /* account for space/quote (3) and \0 (1) and & (1) */ 2358 | /* e.g.: xdg-open "https://st.suckless.org"& */ 2359 | size_t const cmd_size = max_cmd + strlen(clip) + 5; 2360 | char cmd[cmd_size]; 2361 | 2362 | snprintf(cmd, cmd_size, "%s \"%s\"&", (char *)arg->v, clip); 2363 | system(cmd); 2364 | } 2365 | --------------------------------------------------------------------------------