From f56cbf19b4587f5ce16e70daeaef10036654e416 Mon Sep 17 00:00:00 2001 From: tomit4 Date: Sun, 22 May 2022 19:26:35 -0700 Subject: [PATCH] created base for use of suckless simple terminal --- st/FAQ | 250 +++ st/LEGACY | 17 + st/LICENSE | 34 + st/Makefile | 56 + st/README | 34 + st/TODO | 28 + st/arg.h | 50 + st/config.def.h | 479 ++++++ st/config.h | 479 ++++++ st/config.mk | 35 + st/st | Bin 0 -> 111032 bytes st/st-alpha-20220206-0.8.5.diff | 146 ++ st/st-scrollback-0.8.5.diff | 350 ++++ st/st.1 | 177 ++ st/st.c | 2761 +++++++++++++++++++++++++++++++ st/st.h | 131 ++ st/st.h.orig | 130 ++ st/st.info | 239 +++ st/st.o | Bin 0 -> 81536 bytes st/win.h | 40 + st/x.c | 2100 +++++++++++++++++++++++ st/x.o | Bin 0 -> 75848 bytes 22 files changed, 7536 insertions(+) create mode 100644 st/FAQ create mode 100644 st/LEGACY create mode 100644 st/LICENSE create mode 100644 st/Makefile create mode 100644 st/README create mode 100644 st/TODO create mode 100644 st/arg.h create mode 100644 st/config.def.h create mode 100644 st/config.h create mode 100644 st/config.mk create mode 100755 st/st create mode 100644 st/st-alpha-20220206-0.8.5.diff create mode 100644 st/st-scrollback-0.8.5.diff create mode 100644 st/st.1 create mode 100644 st/st.c create mode 100644 st/st.h create mode 100644 st/st.h.orig create mode 100644 st/st.info create mode 100644 st/st.o create mode 100644 st/win.h create mode 100644 st/x.c create mode 100644 st/x.o diff --git a/st/FAQ b/st/FAQ new file mode 100644 index 00000000..969b195e --- /dev/null +++ b/st/FAQ @@ -0,0 +1,250 @@ +## Why does st not handle utmp entries? + +Use the excellent tool of [utmp](https://git.suckless.org/utmp/) for this task. + + +## Some _random program_ complains that st is unknown/not recognised/unsupported/whatever! + +It means that st doesn’t have any terminfo entry on your system. Chances are +you did not `make install`. If you just want to test it without installing it, +you can manually run `tic -sx st.info`. + + +## Nothing works, and nothing is said about an unknown terminal! + +* Some programs just assume they’re running in xterm i.e. they don’t rely on + terminfo. What you see is the current state of the “xterm compliance”. +* Some programs don’t complain about the lacking st description and default to + another terminal. In that case see the question about terminfo. + + +## How do I scroll back up? + +* Using a terminal multiplexer. + * `st -e tmux` using C-b [ + * `st -e screen` using C-a ESC +* Using the excellent tool of [scroll](https://git.suckless.org/scroll/). +* Using the scrollback [patch](https://st.suckless.org/patches/scrollback/). + + +## I would like to have utmp and/or scroll functionality by default + +You can add the absolute path of both programs in your config.h file. You only +have to modify the value of utmp and scroll variables. + + +## Why doesn't the Del key work in some programs? + +Taken from the terminfo manpage: + + If the terminal has a keypad that transmits codes when the keys + are pressed, this information can be given. Note that it is not + possible to handle terminals where the keypad only works in + local (this applies, for example, to the unshifted HP 2621 keys). + If the keypad can be set to transmit or not transmit, give these + codes as smkx and rmkx. Otherwise the keypad is assumed to + always transmit. + +In the st case smkx=E[?1hE= and rmkx=E[?1lE>, so it is mandatory that +applications which want to test against keypad keys send these +sequences. + +But buggy applications (like bash and irssi, for example) don't do this. A fast +solution for them is to use the following command: + + $ printf '\033[?1h\033=' >/dev/tty + +or + $ tput smkx + +In the case of bash, readline is used. Readline has a different note in its +manpage about this issue: + + enable-keypad (Off) + When set to On, readline will try to enable the + application keypad when it is called. Some systems + need this to enable arrow keys. + +Adding this option to your .inputrc will fix the keypad problem for all +applications using readline. + +If you are using zsh, then read the zsh FAQ +: + + It should be noted that the O / [ confusion can occur with other keys + such as Home and End. Some systems let you query the key sequences + sent by these keys from the system's terminal database, terminfo. + Unfortunately, the key sequences given there typically apply to the + mode that is not the one zsh uses by default (it's the "application" + mode rather than the "raw" mode). Explaining the use of terminfo is + outside of the scope of this FAQ, but if you wish to use the key + sequences given there you can tell the line editor to turn on + "application" mode when it starts and turn it off when it stops: + + function zle-line-init () { echoti smkx } + function zle-line-finish () { echoti rmkx } + zle -N zle-line-init + zle -N zle-line-finish + +Putting these lines into your .zshrc will fix the problems. + + +## How can I use meta in 8bit mode? + +St supports meta in 8bit mode, but the default terminfo entry doesn't +use this capability. If you want it, you have to use the 'st-meta' value +in TERM. + + +## I cannot compile st in OpenBSD + +OpenBSD lacks librt, despite it being mandatory in POSIX +. +If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and +st will compile without any loss of functionality, because all the functions are +included in libc on this platform. + + +## The Backspace Case + +St is emulating the Linux way of handling backspace being delete and delete being +backspace. + +This is an issue that was discussed in suckless mailing list +. Here is why some old grumpy +terminal users wants its backspace to be how he feels it: + + Well, I am going to comment why I want to change the behaviour + of this key. When ASCII was defined in 1968, communication + with computers was done using punched cards, or hardcopy + terminals (basically a typewriter machine connected with the + computer using a serial port). ASCII defines DELETE as 7F, + because, in punched-card terms, it means all the holes of the + card punched; it is thus a kind of 'physical delete'. In the + same way, the BACKSPACE key was a non-destructive backspace, + as on a typewriter. So, if you wanted to delete a character, + you had to BACKSPACE and then DELETE. Another use of BACKSPACE + was to type accented characters, for example 'a BACKSPACE `'. + The VT100 had no BACKSPACE key; it was generated using the + CONTROL key as another control character (CONTROL key sets to + 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code + 0x08)), but it had a DELETE key in a similar position where + the BACKSPACE key is located today on common PC keyboards. + All the terminal emulators emulated the difference between + these keys correctly: the backspace key generated a BACKSPACE + (^H) and delete key generated a DELETE (^?). + + But a problem arose when Linus Torvalds wrote Linux. Unlike + earlier terminals, the Linux virtual terminal (the terminal + emulator integrated in the kernel) returned a DELETE when + backspace was pressed, due to the VT100 having a DELETE key in + the same position. This created a lot of problems (see [1] + and [2]). Since Linux has become the king, a lot of terminal + emulators today generate a DELETE when the backspace key is + pressed in order to avoid problems with Linux. The result is + that the only way of generating a BACKSPACE on these systems + is by using CONTROL + H. (I also think that emacs had an + important point here because the CONTROL + H prefix is used + in emacs in some commands (help commands).) + + From point of view of the kernel, you can change the key + for deleting a previous character with stty erase. When you + connect a real terminal into a machine you describe the type + of terminal, so getty configures the correct value of stty + erase for this terminal. In the case of terminal emulators, + however, you don't have any getty that can set the correct + value of stty erase, so you always get the default value. + For this reason, it is necessary to add 'stty erase ^H' to your + profile if you have changed the value of the backspace key. + Of course, another solution is for st itself to modify the + value of stty erase. I usually have the inverse problem: + when I connect to non-Unix machines, I have to press CONTROL + + h to get a BACKSPACE. The inverse problem occurs when a user + connects to my Unix machines from a different system with a + correct backspace key. + + [1] http://www.ibb.net/~anne/keyboard.html + [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html + + +## But I really want the old grumpy behaviour of my terminal + +Apply [1]. + +[1] https://st.suckless.org/patches/delkey + + +## Why do images not work in st using the w3m image hack? + +w3mimg uses a hack that draws an image on top of the terminal emulator Drawable +window. The hack relies on the terminal to use a single buffer to draw its +contents directly. + +st uses double-buffered drawing so the image is quickly replaced and may show a +short flicker effect. + +Below is a patch example to change st double-buffering to a single Drawable +buffer. + +diff --git a/x.c b/x.c +--- a/x.c ++++ b/x.c +@@ -732,10 +732,6 @@ xresize(int col, int row) + win.tw = col * win.cw; + win.th = row * win.ch; + +- XFreePixmap(xw.dpy, xw.buf); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); +- XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +@@ -1148,8 +1144,7 @@ xinit(int cols, int rows) + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); ++ xw.buf = xw.win; + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) + void + xfinishdraw(void) + { +- XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, +- win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); + + +## BadLength X error in Xft when trying to render emoji + +Xft makes st crash when rendering color emojis with the following error: + +"X Error of failed request: BadLength (poly request too large or internal Xlib length error)" + Major opcode of failed request: 139 (RENDER) + Minor opcode of failed request: 20 (RenderAddGlyphs) + Serial number of failed request: 1595 + Current serial number in output stream: 1818" + +This is a known bug in Xft (not st) which happens on some platforms and +combination of particular fonts and fontconfig settings. + +See also: +https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 +https://bugs.freedesktop.org/show_bug.cgi?id=107534 +https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + +The solution is to remove color emoji fonts or disable this in the fontconfig +XML configuration. As an ugly workaround (which may work only on newer +fontconfig versions (FC_COLOR)), the following code can be used to mask color +fonts: + + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + +Please don't bother reporting this bug to st, but notify the upstream Xft +developers about fixing this bug. diff --git a/st/LEGACY b/st/LEGACY new file mode 100644 index 00000000..bf28b1eb --- /dev/null +++ b/st/LEGACY @@ -0,0 +1,17 @@ +A STATEMENT ON LEGACY SUPPORT + +In the terminal world there is much cruft that comes from old and unsup‐ +ported terminals that inherit incompatible modes and escape sequences +which noone is able to know, except when he/she comes from that time and +developed a graphical vt100 emulator at that time. + +One goal of st is to only support what is really needed. When you en‐ +counter a sequence which you really need, implement it. But while you +are at it, do not add the other cruft you might encounter while sneek‐ +ing at other terminal emulators. History has bloated them and there is +no real evidence that most of the sequences are used today. + + +Christoph Lohmann <20h@r-36.net> +2012-09-13T07:00:36.081271045+02:00 + diff --git a/st/LICENSE b/st/LICENSE new file mode 100644 index 00000000..d80eb47f --- /dev/null +++ b/st/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2020 Hiltjo Posthuma +© 2018 Devin J. Pohly +© 2014-2017 Quentin Rameau +© 2009-2012 Aurélien APTEL +© 2008-2017 Anselm R Garbe +© 2012-2017 Roberto E. Vargas Caballero +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013-2014 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/st/Makefile b/st/Makefile new file mode 100644 index 00000000..01a6d06e --- /dev/null +++ b/st/Makefile @@ -0,0 +1,56 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c +OBJ = $(SRC:.c=.o) + +all: options st + +options: + @echo st build options: + @echo "CFLAGS = $(STCFLAGS)" + @echo "LDFLAGS = $(STLDFLAGS)" + @echo "CC = $(CC)" + +config.h: + cp config.def.h config.h + +.c.o: + $(CC) $(STCFLAGS) -c $< + +st.o: config.h st.h win.h +x.o: arg.h config.h st.h win.h + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ + config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + +.PHONY: all options clean dist install uninstall diff --git a/st/README b/st/README new file mode 100644 index 00000000..6a846ed0 --- /dev/null +++ b/st/README @@ -0,0 +1,34 @@ +st - simple terminal +-------------------- +st is a simple terminal emulator for X which sucks less. + + +Requirements +------------ +In order to build st you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (st is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install st (if +necessary as root): + + make clean install + + +Running st +---------- +If you did not install st with make clean install, you must compile +the st terminfo entry with the following command: + + tic -sx st.info + +See the man page for additional details. + +Credits +------- +Based on Aurélien APTEL bt source code. + diff --git a/st/TODO b/st/TODO new file mode 100644 index 00000000..5f74cd52 --- /dev/null +++ b/st/TODO @@ -0,0 +1,28 @@ +vt emulation +------------ + +* double-height support + +code & interface +---------------- + +* add a simple way to do multiplexing + +drawing +------- +* add diacritics support to xdraws() + * switch to a suckless font drawing library +* make the font cache simpler +* add better support for brightening of the upper colors + +bugs +---- + +* fix shift up/down (shift selection in emacs) +* remove DEC test sequence when appropriate + +misc +---- + + $ grep -nE 'XXX|TODO' st.c + diff --git a/st/arg.h b/st/arg.h new file mode 100644 index 00000000..a22e019e --- /dev/null +++ b/st/arg.h @@ -0,0 +1,50 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/st/config.def.h b/st/config.def.h new file mode 100644 index 00000000..279890fe --- /dev/null +++ b/st/config.def.h @@ -0,0 +1,479 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/st/config.h b/st/config.h new file mode 100644 index 00000000..04d8768f --- /dev/null +++ b/st/config.h @@ -0,0 +1,479 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Source Code Pro:pixelsize=20:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.6; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "#3b4252", /* black */ + "#bf616a", /* red */ + "#a3be8c", /* green */ + "#ebcb8b", /* yellow */ + "#81a1c1", /* blue */ + "#b48ead", /* magenta */ + "#88c0d0", /* cyan */ + "#e5e9f0", /* white */ + + /* 8 bright colors */ + "#4c566a", /* black */ + "#bf616a", /* red */ + "#a3be8c", /* green */ + "#ebcb8b", /* yellow */ + "#81a1c1", /* blue */ + "#b48ead", /* magenta */ + "#8fbcbb", /* cyan */ + "#eceff4", /* white */ + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + // "#2e3440", // background // + // "#1d222b", // background // + "#1a1e26", // background // + "#d8dee9", /* foreground */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; +static unsigned int defaultrcs = 256; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/st/config.mk b/st/config.mk new file mode 100644 index 00000000..0114bad3 --- /dev/null +++ b/st/config.mk @@ -0,0 +1,35 @@ +# st version +VERSION = 0.8.5 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` + +# compiler and linker +# CC = c99 diff --git a/st/st b/st/st new file mode 100755 index 0000000000000000000000000000000000000000..0bc5f90b48d53a436207d54b9854092e4552f79e GIT binary patch literal 111032 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

v&1W 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 hOPa4QzPFNo!S~0~#qxQ(<=rCT( 0 && term.line[y][i - 1].u == ' ') ++ while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction) + * Snap around if the word wraps around at the end or + * beginning of a line. + */ +- prevgp = &term.line[*y][*x]; ++ prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; +@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction) + yt = *y, xt = *x; + else + yt = newy, xt = newx; +- if (!(term.line[yt][xt].mode & ATTR_WRAP)) ++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + +- gp = &term.line[newy][newx]; ++ gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) +@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction) + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode ++ if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode ++ if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } +@@ -609,13 +616,13 @@ getsel(void) + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &term.line[y][sel.nb.x]; ++ gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; ++ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } +- last = &term.line[y][MIN(lastx, linelen-1)]; ++ last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + +@@ -851,6 +858,9 @@ void + ttywrite(const char *s, size_t n, int may_echo) + { + const char *next; ++ Arg arg = (Arg) { .i = term.scr }; ++ ++ kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); +@@ -1062,12 +1072,52 @@ tswapscreen(void) + } + + void +-tscrolldown(int orig, int n) ++kscrolldown(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (n > term.scr) ++ n = term.scr; ++ ++ if (term.scr > 0) { ++ term.scr -= n; ++ selscroll(0, -n); ++ tfulldirt(); ++ } ++} ++ ++void ++kscrollup(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (term.scr <= HISTSIZE-n) { ++ term.scr += n; ++ selscroll(0, n); ++ tfulldirt(); ++ } ++} ++ ++void ++tscrolldown(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); ++ if (copyhist) { ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[term.bot]; ++ term.line[term.bot] = temp; ++ } ++ + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); +@@ -1078,17 +1128,28 @@ tscrolldown(int orig, int n) + term.line[i-n] = temp; + } + +- selscroll(orig, n); ++ if (term.scr == 0) ++ selscroll(orig, n); + } + + void +-tscrollup(int orig, int n) ++tscrollup(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + ++ if (copyhist) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[orig]; ++ term.line[orig] = temp; ++ } ++ ++ if (term.scr > 0 && term.scr < HISTSIZE) ++ term.scr = MIN(term.scr + n, HISTSIZE-1); ++ + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + +@@ -1098,7 +1159,8 @@ tscrollup(int orig, int n) + term.line[i+n] = temp; + } + +- selscroll(orig, -n); ++ if (term.scr == 0) ++ selscroll(orig, -n); + } + + void +@@ -1127,7 +1189,7 @@ tnewline(int first_col) + int y = term.c.y; + + if (y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + y++; + } +@@ -1292,14 +1354,14 @@ void + tinsertblankline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrolldown(term.c.y, n); ++ tscrolldown(term.c.y, n, 0); + } + + void + tdeleteline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrollup(term.c.y, n); ++ tscrollup(term.c.y, n, 0); + } + + int32_t +@@ -1736,11 +1798,11 @@ csihandle(void) + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0]); ++ tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); +- tscrolldown(term.top, csiescseq.arg[0]); ++ tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); +@@ -2330,7 +2392,7 @@ eschandle(uchar ascii) + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } +@@ -2343,7 +2405,7 @@ eschandle(uchar ascii) + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { +- tscrolldown(term.top, 1); ++ tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } +@@ -2557,7 +2619,7 @@ twrite(const char *buf, int buflen, int show_ctrl) + void + tresize(int col, int row) + { +- int i; ++ int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; +@@ -2594,6 +2656,14 @@ tresize(int col, int row) + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + ++ for (i = 0; i < HISTSIZE; i++) { ++ term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); ++ for (j = mincol; j < col; j++) { ++ term.hist[i][j] = term.c.attr; ++ term.hist[i][j].u = ' '; ++ } ++ } ++ + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); +@@ -2652,7 +2722,7 @@ drawregion(int x1, int y1, int x2, int y2) + continue; + + term.dirty[y] = 0; +- xdrawline(term.line[y], x1, y, x2); ++ xdrawline(TLINE(y), x1, y, x2); + } + } + +@@ -2673,8 +2743,9 @@ draw(void) + cx--; + + drawregion(0, 0, term.col, term.row); +- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ if (term.scr == 0) ++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index 519b9bd..da36b34 100644 +--- a/st.h ++++ b/st.h +@@ -81,6 +81,8 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void kscrolldown(const Arg *); ++void kscrollup(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); diff --git a/st/st.1 b/st/st.1 new file mode 100644 index 00000000..39120b48 --- /dev/null +++ b/st/st.1 @@ -0,0 +1,177 @@ +.TH ST 1 st\-VERSION +.SH NAME +st \- simple terminal +.SH SYNOPSIS +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-l +.IR line ] +.RB [ \-w +.IR windowid ] +.RB [[ \-e ] +.IR command +.RI [ arguments ...]] +.PP +.B st +.RB [ \-aiv ] +.RB [ \-c +.IR class ] +.RB [ \-f +.IR font ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-o +.IR iofile ] +.RB [ \-T +.IR title ] +.RB [ \-t +.IR title ] +.RB [ \-w +.IR windowid ] +.RB \-l +.IR line +.RI [ stty_args ...] +.SH DESCRIPTION +.B st +is a simple terminal emulator. +.SH OPTIONS +.TP +.B \-a +disable alternate screens in terminal +.TP +.BI \-c " class" +defines the window class (default $TERM). +.TP +.BI \-f " font" +defines the +.I font +to use when st is run. +.TP +.BI \-g " geometry" +defines the X11 geometry string. +The form is [=][{xX}][{+-}{+-}]. See +.BR XParseGeometry (3) +for further details. +.TP +.B \-i +will fixate the position given with the -g option. +.TP +.BI \-n " name" +defines the window instance name (default $TERM). +.TP +.BI \-o " iofile" +writes all the I/O to +.I iofile. +This feature is useful when recording st sessions. A value of "-" means +standard output. +.TP +.BI \-T " title" +defines the window title (default 'st'). +.TP +.BI \-t " title" +defines the window title (default 'st'). +.TP +.BI \-w " windowid" +embeds st within the window identified by +.I windowid +.TP +.BI \-l " line" +use a tty +.I line +instead of a pseudo terminal. +.I line +should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port +0). +When this flag is given +remaining arguments are used as flags for +.BR stty(1). +By default st initializes the serial line to 8 bits, no parity, 1 stop bit +and a 38400 baud rate. The speed is set by appending it as last argument +(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are +.BR stty(1) +flags. If you want to set odd parity on 115200 baud use for example 'st -l +/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for +example 'st -l /dev/ttyS0 cs7 115200'. See +.BR stty(1) +for more arguments and cases. +.TP +.B \-v +prints version information to stderr, then exits. +.TP +.BI \-e " command " [ " arguments " "... ]" +st executes +.I command +instead of the shell. If this is used it +.B must be the last option +on the command line, as in xterm / rxvt. +This option is only intended for compatibility, +and all the remaining arguments are used as a command +even without it. +.SH SHORTCUTS +.TP +.B Break +Send a break in the serial line. +Break key is obtained in PC keyboards +pressing at the same time control and pause. +.TP +.B Ctrl-Print Screen +Toggle if st should print to the +.I iofile. +.TP +.B Shift-Print Screen +Print the full screen to the +.I iofile. +.TP +.B Print Screen +Print the selection to the +.I iofile. +.TP +.B Ctrl-Shift-Page Up +Increase font size. +.TP +.B Ctrl-Shift-Page Down +Decrease font size. +.TP +.B Ctrl-Shift-Home +Reset to default font size. +.TP +.B Ctrl-Shift-y +Paste from primary selection (middle mouse button). +.TP +.B Ctrl-Shift-c +Copy the selected text to the clipboard selection. +.TP +.B Ctrl-Shift-v +Paste from the clipboard selection. +.SH CUSTOMIZATION +.B st +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR tabbed (1), +.BR utmp (1), +.BR stty (1), +.BR scroll (1) +.SH BUGS +See the TODO file in the distribution. + diff --git a/st/st.c b/st/st.c new file mode 100644 index 00000000..cd750f20 --- /dev/null +++ b/st/st.c @@ -0,0 +1,2761 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +static const char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc4_color_response(int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(num, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc4 color %d\n", num); + return; + } + + n = snprintf(buf, sizeof buf, "\033]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +osc_color_response(int index, int num) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch osc color %d\n", index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + num, r, r, g, g, b, b); + + ttywrite(buf, n, 1); +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultfg, 10); + else if (xsetcolorname(defaultfg, p)) + fprintf(stderr, "erresc: invalid foreground color: %s\n", p); + else + redraw(); + return; + case 11: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultbg, 11); + else if (xsetcolorname(defaultbg, p)) + fprintf(stderr, "erresc: invalid background color: %s\n", p); + else + redraw(); + return; + case 12: + if (narg < 2) + break; + + p = strescseq.args[1]; + + if (!strcmp(p, "?")) + osc_color_response(defaultcs, 12); + else if (xsetcolorname(defaultcs, p)) + fprintf(stderr, "erresc: invalid cursor color: %s\n", p); + else + redraw(); + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) + osc4_color_response(j); + else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st/st.h b/st/st.h new file mode 100644 index 00000000..67bfb74f --- /dev/null +++ b/st/st.h @@ -0,0 +1,131 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; +extern float alpha; diff --git a/st/st.h.orig b/st/st.h.orig new file mode 100644 index 00000000..da36b34a --- /dev/null +++ b/st/st.h.orig @@ -0,0 +1,130 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; diff --git a/st/st.info b/st/st.info new file mode 100644 index 00000000..8201ad62 --- /dev/null +++ b/st/st.info @@ -0,0 +1,239 @@ +st-mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/st/st.o b/st/st.o new file mode 100644 index 0000000000000000000000000000000000000000..5938ab39fcf71cea7551d8e5ff0ff8143494eaa3 GIT binary patch literal 81536 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 literal 0 HcmV?d00001 diff --git a/st/win.h b/st/win.h new file mode 100644 index 00000000..e6e43695 --- /dev/null +++ b/st/win.h @@ -0,0 +1,40 @@ +/* See LICENSE for license details. */ + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 13, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, +}; + +void xbell(void); +void xclipcopy(void); +void xdrawcursor(int, int, Glyph, int, int, Glyph); +void xdrawline(Line, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +int xsetcolorname(int, const char *); +void xseticontitle(char *); +void xsettitle(char *); +int xsetcursor(int); +void xsetmode(int, unsigned int); +void xsetpointermotion(int); +void xsetsel(char *); +int xstartdraw(void); +void xximspot(int, int); diff --git a/st/x.c b/st/x.c new file mode 100644 index 00000000..ddf41787 --- /dev/null +++ b/st/x.c @@ -0,0 +1,2100 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static int oldbutton = 3; /* button event on startup: 3 = release */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 7) + button += 128 - 7; + else if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + struct timespec now; + int snap; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/st/x.o b/st/x.o new file mode 100644 index 0000000000000000000000000000000000000000..93ff4c42bb31ef9f1da151f1a94b527d323b2f69 GIT binary patch literal 75848 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 literal 0 HcmV?d00001