From 801abac2d53e9eb5ae6c550cd8e60228fe53a8cf Mon Sep 17 00:00:00 2001 From: Horhik Date: Fri, 4 Dec 2020 22:20:59 +0200 Subject: [PATCH] update st --- st/FAQ | 250 ++ st/LEGACY | 17 + st/LICENSE | 34 + st/Makefile | 57 + st/README | 34 + st/TODO | 28 + st/arg.h | 50 + st/config.def.h | 468 ++++ st/config.def.h.orig | 466 ++++ st/config.def.h.rej | 10 + st/config.h | 468 ++++ st/config.mk | 35 + st/patches/st-font2-20190326-f64c2f8.diff | 126 + st/patches/st-gruvbox-dark-0.8.2.diff | 70 + st/st | Bin 0 -> 102568 bytes st/st.1 | 177 ++ st/st.c | 2605 +++++++++++++++++++++ st/st.h | 125 + st/st.info | 239 ++ st/st.o | Bin 0 -> 73824 bytes st/win.h | 40 + st/x.c | 2136 +++++++++++++++++ st/x.c.orig | 2063 ++++++++++++++++ st/x.o | Bin 0 -> 74744 bytes 24 files changed, 9498 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.def.h.orig create mode 100644 st/config.def.h.rej create mode 100644 st/config.h create mode 100644 st/config.mk create mode 100644 st/patches/st-font2-20190326-f64c2f8.diff create mode 100644 st/patches/st-gruvbox-dark-0.8.2.diff create mode 100755 st/st create mode 100644 st/st.1 create mode 100644 st/st.c create mode 100644 st/st.h 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.c.orig create mode 100644 st/x.o diff --git a/st/FAQ b/st/FAQ new file mode 100644 index 0000000..0f9609d --- /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 patch 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 0000000..bf28b1e --- /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 0000000..d80eb47 --- /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 0000000..470ac86 --- /dev/null +++ b/st/Makefile @@ -0,0 +1,57 @@ +# 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 + tic -sx st.info + @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 0000000..6a846ed --- /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 0000000..5f74cd5 --- /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 0000000..a22e019 --- /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 0000000..f524b42 --- /dev/null +++ b/st/config.def.h @@ -0,0 +1,468 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Mononoki:pixelsize=16:antialias=true:autohint=true"; +static char *font2 = "Apple Color Emoji:pixelsize=16: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; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + + /* 8 normal colors */ + [0] = "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ + [1] = "#cc241d", /* red */ + [2] = "#98971a", /* green */ + [3] = "#d79921", /* yellow */ + [4] = "#458588", /* blue */ + [5] = "#b16286", /* magenta */ + [6] = "#689d6a", /* cyan */ + [7] = "#a89984", /* white */ + + /* 8 bright colors */ + [8] = "#928374", /* black */ + [9] = "#fb4934", /* red */ + [10] = "#b8bb26", /* green */ + [11] = "#fabd2f", /* yellow */ + [12] = "#83a598", /* blue */ + [13] = "#d3869b", /* magenta */ + [14] = "#8ec07c", /* cyan */ + [15] = "#ebdbb2", /* white */ +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor + */ +unsigned int defaultfg = 15; +unsigned int defaultbg = 0; +static unsigned int defaultcs = 15; +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} }, +}; + +/* + * 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.def.h.orig b/st/config.def.h.orig new file mode 100644 index 0000000..4c46c78 --- /dev/null +++ b/st/config.def.h.orig @@ -0,0 +1,466 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Mononoki:pixelsize=16:antialias=true:autohint=true,EmojiOne"; +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; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + + /* 8 normal colors */ + [0] = "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ + [1] = "#cc241d", /* red */ + [2] = "#98971a", /* green */ + [3] = "#d79921", /* yellow */ + [4] = "#458588", /* blue */ + [5] = "#b16286", /* magenta */ + [6] = "#689d6a", /* cyan */ + [7] = "#a89984", /* white */ + + /* 8 bright colors */ + [8] = "#928374", /* black */ + [9] = "#fb4934", /* red */ + [10] = "#b8bb26", /* green */ + [11] = "#fabd2f", /* yellow */ + [12] = "#83a598", /* blue */ + [13] = "#d3869b", /* magenta */ + [14] = "#8ec07c", /* cyan */ + [15] = "#ebdbb2", /* white */ +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor + */ +unsigned int defaultfg = 15; +unsigned int defaultbg = 0; +static unsigned int defaultcs = 15; +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} }, +}; + +/* + * 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.def.h.rej b/st/config.def.h.rej new file mode 100644 index 0000000..8012bd9 --- /dev/null +++ b/st/config.def.h.rej @@ -0,0 +1,10 @@ +--- config.def.h ++++ config.def.h +@@ -6,6 +6,7 @@ + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ + static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; ++static char *font2 = "Roboto Mono for Powerline:pixelsize=12:antialias=true:autohint=true"; + static int borderpx = 2; + + /* diff --git a/st/config.h b/st/config.h new file mode 100644 index 0000000..f524b42 --- /dev/null +++ b/st/config.h @@ -0,0 +1,468 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Mononoki:pixelsize=16:antialias=true:autohint=true"; +static char *font2 = "Apple Color Emoji:pixelsize=16: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; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + + /* 8 normal colors */ + [0] = "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ + [1] = "#cc241d", /* red */ + [2] = "#98971a", /* green */ + [3] = "#d79921", /* yellow */ + [4] = "#458588", /* blue */ + [5] = "#b16286", /* magenta */ + [6] = "#689d6a", /* cyan */ + [7] = "#a89984", /* white */ + + /* 8 bright colors */ + [8] = "#928374", /* black */ + [9] = "#fb4934", /* red */ + [10] = "#b8bb26", /* green */ + [11] = "#fabd2f", /* yellow */ + [12] = "#83a598", /* blue */ + [13] = "#d3869b", /* magenta */ + [14] = "#8ec07c", /* cyan */ + [15] = "#ebdbb2", /* white */ +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor + */ +unsigned int defaultfg = 15; +unsigned int defaultbg = 0; +static unsigned int defaultcs = 15; +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} }, +}; + +/* + * 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 0000000..c070a4a --- /dev/null +++ b/st/config.mk @@ -0,0 +1,35 @@ +# st version +VERSION = 0.8.4 + +# 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 \ + `$(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/patches/st-font2-20190326-f64c2f8.diff b/st/patches/st-font2-20190326-f64c2f8.diff new file mode 100644 index 0000000..fdb6efe --- /dev/null +++ b/st/patches/st-font2-20190326-f64c2f8.diff @@ -0,0 +1,126 @@ +From f64c2f83a2e3ee349fe11100526110dfdf47067a Mon Sep 17 00:00:00 2001 +From: Kirill Bugaev +Date: Wed, 27 Mar 2019 01:28:56 +0800 +Subject: [PATCH] Some glyphs can be not present in font defined by default. + For this glyphs st uses font-config and try to find them in font cache first. + This patch append font defined in `font2` variable to the beginning of font + cache. So it will be used as spare font. + +--- + config.def.h | 1 + + x.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 67 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 482901e..88eee0f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,6 +6,7 @@ + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ + static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; ++static char *font2 = "Roboto Mono for Powerline:pixelsize=12:antialias=true:autohint=true"; + static int borderpx = 2; + + /* +diff --git a/x.c b/x.c +index 5828a3b..052b10b 100644 +--- a/x.c ++++ b/x.c +@@ -149,6 +149,7 @@ static void xhints(void); + static int xloadcolor(int, const char *, Color *); + static int xloadfont(Font *, FcPattern *); + static void xloadfonts(char *, double); ++static void xloadsparefont(); + static void xunloadfont(Font *); + static void xunloadfonts(void); + static void xsetenv(void); +@@ -296,6 +297,7 @@ zoomabs(const Arg *arg) + { + xunloadfonts(); + xloadfonts(usedfont, arg->f); ++ xloadsparefont(); + cresize(0, 0); + redraw(); + xhints(); +@@ -977,6 +979,67 @@ xloadfonts(char *fontstr, double fontsize) + FcPatternDestroy(pattern); + } + ++void ++xloadsparefont() ++{ ++ FcPattern *fontpattern, *match; ++ FcResult result; ++ ++ /* add font2 to font cache as first 4 entries */ ++ if ( font2[0] == '-' ) ++ fontpattern = XftXlfdParse(font2, False, False); ++ else ++ fontpattern = FcNameParse((FcChar8 *)font2); ++ if ( fontpattern ) { ++ /* Allocate memory for the new cache entries. */ ++ frccap += 4; ++ frc = xrealloc(frc, frccap * sizeof(Fontcache)); ++ /* add Normal */ ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_NORMAL; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ /* add Italic */ ++ FcPatternDel(fontpattern, FC_SLANT); ++ FcPatternAddInteger(fontpattern, FC_SLANT, FC_SLANT_ITALIC); ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_ITALIC; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ /* add Italic Bold */ ++ FcPatternDel(fontpattern, FC_WEIGHT); ++ FcPatternAddInteger(fontpattern, FC_WEIGHT, FC_WEIGHT_BOLD); ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_ITALICBOLD; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ /* add Bold */ ++ FcPatternDel(fontpattern, FC_SLANT); ++ FcPatternAddInteger(fontpattern, FC_SLANT, FC_SLANT_ROMAN); ++ match = FcFontMatch(NULL, fontpattern, &result); ++ if ( match ) ++ frc[frclen].font = XftFontOpenPattern(xw.dpy, match); ++ if ( frc[frclen].font ) { ++ frc[frclen].flags = FRC_BOLD; ++ frclen++; ++ } else ++ FcPatternDestroy(match); ++ FcPatternDestroy(fontpattern); ++ } ++} ++ + void + xunloadfont(Font *f) + { +@@ -1057,6 +1120,9 @@ xinit(int cols, int rows) + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + ++ /* spare font (font2) */ ++ xloadsparefont(); ++ + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); +-- +2.21.0 + diff --git a/st/patches/st-gruvbox-dark-0.8.2.diff b/st/patches/st-gruvbox-dark-0.8.2.diff new file mode 100644 index 0000000..c8390f0 --- /dev/null +++ b/st/patches/st-gruvbox-dark-0.8.2.diff @@ -0,0 +1,70 @@ +diff --git a/config.def.h b/config.def.h +index 877afab..6a1699f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -84,41 +84,35 @@ static unsigned int tabspaces = 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", +-}; + ++ /* 8 normal colors */ ++ [0] = "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ ++ [1] = "#cc241d", /* red */ ++ [2] = "#98971a", /* green */ ++ [3] = "#d79921", /* yellow */ ++ [4] = "#458588", /* blue */ ++ [5] = "#b16286", /* magenta */ ++ [6] = "#689d6a", /* cyan */ ++ [7] = "#a89984", /* white */ ++ ++ /* 8 bright colors */ ++ [8] = "#928374", /* black */ ++ [9] = "#fb4934", /* red */ ++ [10] = "#b8bb26", /* green */ ++ [11] = "#fabd2f", /* yellow */ ++ [12] = "#83a598", /* blue */ ++ [13] = "#d3869b", /* magenta */ ++ [14] = "#8ec07c", /* cyan */ ++ [15] = "#ebdbb2", /* white */ ++}; + + /* + * Default colors (colorname index) +- * foreground, background, cursor, reverse cursor ++ * foreground, background, cursor + */ +-unsigned int defaultfg = 7; +-unsigned int defaultbg = 0; +-static unsigned int defaultcs = 256; ++unsigned int defaultfg = 15; ++unsigned int defaultbg = 0; ++static unsigned int defaultcs = 15; + static unsigned int defaultrcs = 257; + + /* diff --git a/st/st b/st/st new file mode 100755 index 0000000000000000000000000000000000000000..aeb239d2d0796c339b49a6844ce19636f2bb7879 GIT binary patch literal 102568 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

G%nu+I%M?H8rd|Vz(sgcS*3jVsg z%$|!)5i>a=1}NI{$`M0y9JJ*6ZT*EaJG0Guvwd4)g6mMY5l?8f@(w0h5TD|Ky;4oj zgHXH~MwINK+V&<3l45;Fxs!xNo2iV-mK%GD2{jz_%62$v@N0Plsye*8HB{M?%ND=V z)riZMg6gQ+;kh1bL|@!jab{D}8k#bo=^M0&Abx-9T|AKOXR56AmNas1j8)_cr~zek z+Q5Xx5oRpimPtm_0ZH#?_FR_HE<;6K6xYHYG0=Uy+@(XMRqT2l#my+d@W2dau zXg1OX1QMtAhWJD#P7RHMo)6dj<=9}G5Yw1TG0yXe_hdd7zD{?>J z?#tH}^5mj_72z>Lto+dr(EcEvlO%3L94vYNJRJ8DO4|idffzF3cjOHdcA;UFY3QYJ z{x8#%`;`*^X*gaA$InB&`jImNb0Q#tOX~j4B)q z6Xl)E;w_B7AAD}rLB4V-1*AbX2=yYNz0iShPYUl)!Z8$XK$wj(f^;Lb$y{({P^jZt zh1XndInj&Ie2b|m6FSrK=WV{z3+z=bAWvE7))ztg5+=%O%CF zLl}>ZSL5Q(yqN1BUmN4}KXy^5`wdfPLmwxeyDsdTEnzk3vYWJ$6UVt*2`AIqrgXbNLmRhsBHXOBfRbtd=Bh7}+74 z?efAy;&CZHonX6p;Q`ck3c}dAkttjM-7x0>HokK%=|lPN@vaHyAN-V+ld_Q1IaX??cg8F-{TNz|~!G z$I-%%U>A`0P*2sc-u66o3IB=m-=R({#NxU#~tMXEic5;Aj#qhNO;fDIg3 zJ_b91M{9U5u>T?K<50~^0P9L^v&##J)i} zqU~%KraeWj2FB(NwBgC~PO#?&ffQ`q;2 z_22Y5OCvenrl?pY>LrA8@~7Eiuw(NNMJKz{z+IW6p5zXmW{N*U7|tAvF>u+NG{lLA zvg;06oMHp5^I7fS?2Ztfb~7m?3l^BPzCpbQ&us^7*MP*UlST|!M^?3PKiTgKVhoS57 zOD~{I)CooIWpFOhu?!cdzcjpy=Z70h86SX^9B<)QF2ul$$9zwbIR9DNlXR6b%1Iz@ z0e*4oXO-gSD4~8*++#{8iC-1>2!cy;Y}c`yP};=xKNh09M+`}W#IIfAH0)c_(mjC3 zT(IRVZCaxHf9Yo3zt4WH`#+679+*J2oPQYg0S|HPL!Z$6#edTcIpP7?!hFT?HN~jZ ziJeIJ3icG3&$83@LjG2qA82Dj{hp>AQ#T3uJ34OAaR>0qx8%d|J4whFvB6ItIQ1&hBe$=d+eBS6P)(*3-?wXUAV{TACKMD99s&TrQ|I#`uG-&9Hi~G{)gyK zsQU1%Ia!Xzi0l&64&v$(GxpNh0?xLi{S1?Ybr0Rs=hiT*?{} z;|4v0ba)WAh9)*P2I7`P;<}-5BIhGbOrOX-fw1_4{Q5b}az9{Gj}65%^eD^qcgCSM zd>EZOjX(6*coY)CkLaUyc;q7BB437CgVq#}p{23s3kv5TmOM)y%hAV5u}^{R>s%c1 zibtHp^(KkhxeKJYFDUl0kbNtk+aPl1fUv9jIbD`3$1*HJbZi68mRPWPbi^#ie@Aio z@mp}Ab0u_GBiiyk|5>J`Fxg}&#DgKH@4Jwc=7s?7=;@QsXqpM)QP#7Aec4hfFbm5$ zRyo0^*~4BuXdHskYaA)}TbBEVnmmV80(AR5BzvY+vG!$YbcfIU5=jBiQA0}*N^{hb7wP~sj#Z1{}cD)d76Cb;qc zHYnEdx|>F9;P{g1>&ihC`+JNyjoh@0Xhr;?SM37PV6mDihGI)|Ds}+Zpt(_Bj?WPh zOu&*Dw<9jWKg!(D1y<1`fU}o)1~|p7f@t}c-(W@bWxn=do?di*1DL{J;G}0u5O?$q z!8(`7oh4OW5okd`N<4yJ%r}+oSj4UEj@(%Fqu%dAgj|f?&aaA3M!2H=A@68B#O5ng zF*PmB_qE-mY|r@GX7E+tBf0OKnSX}X zc)aii)^0htd$Q8K@2I_0wK(v#VR#pM;78mLh0EeM4C+Go>M*u3+)GQds!tcXBk_eo zo_n3X3n9;4cp_NWX7M#gW7Dl$+mdKHcH+6kB{tW!;mjuQ#02c>Cgb}b740Dp9lVWp z)*bBP?Dm~&2_2eMjjehJjfPPj>xhPEb}yeu_wt$9sH7D91l~iXAT}!4j3Nbp!gCmL z4;<$*avarH|6NRV-GruI0LFI?HA<^PK}zg-&fsIG+aVgmDzuT-*L&sPkO_@*O_-R3 zs-JKa;$Emj4eu7jAH^e>0Z$Z>E=Mn9t1N(*P*&I<1e5nHB@>A$V!74;QWQ^u0dOD)niDyU*GfK!P zuAD^@PBaZ9E++|2SKn?tmXWWaEUd#H_2$8_7td0fTq-;6ZFKtxn4oDeOVJHxQlboL zJZf^{GJI0`=ffW=-#R?cvw8SKeC5B-vn=1;jV$}9pExGtO>*UCk~ zEi(BkYW*b-Vk!AKIj)oTvhkGY5PUR=ui6A4alZhpw0LZ2c7E{|+!L>@Fqz^6{_Q=y zZxf)ex!t!FD~V4bhhtQH)$1(TyMT1Y({~Vu3g=;&uri9TIs*MGk?r9g6uHIeI~2oL zwF0a}b|Yh{4TAo0zf`p6@QZsQ4c-*MV~@q;{rP>K#d0cifNj6X&tYW*?JU0>R4Y8Y z3Qh~1*l=O@VJ_Y!2L>aGPB3GlIZ4L&+owqh6#K8Bp1IfDuA4u~*^5yT(ZI;sfLzUE!NtvKHJ z5W;~_93*&%rM8LOI55FsQrva8O7V&Kh3|F2W#jFTkWa%FXwLW(&A#NGl-B3yii-&w z{!rV<@dWS}co|SB{TA9F+>RB{M)GWlqyEuosV4UbuEi)8^BrhZdR`;>YQtJePWZ>( z5O`Vcg`3Ra%b3h-;Lw791kSl}|6swmH(KFw{VwqZGkxc7n;k1SE#8H%?xE@ty18>HuRFs=E^%;F~$z#3nyd_EST3i~kcNZ&=P z=QhD#L?`rwXdmsbBd=L00vie*ns>F(L&% zLXVTUB)x2J0GIe%bjA)!|FCP z+5}Exyp*_UNI(_w6-teL7O2Abx(88o*dl(E68p@zhz)FJ32tPRS~1rGU`ZV9*pGme zu@ArUo0x$E%b9Ffy=*&LO>(TJ6sd=$j88~*7ReSf*`E+yRjca#27pzfA5zM|Adsk& ziPq>v-zK#gL*A|UsB?^b0*bTBBccGGF0lg;8+gQ8_?<&!Xpy^TN<8y2(I zVJ5#5ZQW^F{U9Gk-G?3}SJDXFg;ZoX$QrIIeyM#=NRAW){}e-~A|Cr6ksC=xF3alkQ>^Ts>uDK3istVOW?eB7N!-m!QYz5B2+Hl$ zXZkRKJAlcs+i_J4`-LLu|DcsJt_LY`ZP3lCLy2Ec@jp;JHwrP~7O?}sDJ4e?WpNue z7XlwKsU*ErZ%yg|0&*Ot*MP*N+E40q9R-jaA40_LSc6~wnL-E+b9}_Y_als){1eUjAa>2WJ)=fK^_aA#hZte!91$q z-vMxbPM>-#rOvQIR&sQ~FE&Mn!(HUJXn*K)CO?qL^Oe0&%g)cKlkiKfp)iKf7^-=$ zn_lDj6U?2@?32%9c@db)bQ}g5ws;SK6OTag+~s+sl-GU%xje|B@M{q##kJuVh5rZ_ zc6%T{LiLkIs?vT2sm>^PVzw*+B%466LR`d>CH`jyo7{p!3Ku2Ykbwy<_tr(PG1#xa=FOj)mn;sZXV^eK5bcFD6;B3Db#BFz?0-qrSKwq23() z5}|JqiVD^tv;`qc@H2$gAruq*H$r5#HTX6{|3oM$_%cGTBa|6@rf^Z*a|l(Kf|Z57 z53-AQMxxga|C;>PVpt~x>OaVB~j?e&%2iO;&buqwbfQ^Bh2`oys1C)mX zDL~>8%(JwknU66AU1&*fBDq9Jum@&%hogq-AeM+TSW<@*3{ex%>v6c(Z$bPI2ud7j z7rA{zG=T7rIN)pRI;V%oZ6)dxM(tLqHAJmo)VoCW^c}fdjDM5Zry2X2%04K@`-$~{ z#e&THAPvWklCQLzfrFbcsC`IxF?U14=Xmot{ugS#ir%z>A0wq-z%t=M?3QxbKzN6d zRt+CL47W)^yN4fg4q6BO`7Pl!R{^Gsz+T+%S2zTy#5_t*j3q7;Anc|$0!Z9oM)59g zH3A|>-vtSe=>OfSJ?MfClYA0OsK63vRYn&RM&!Ueg0u#dPf+Y5l;_#U_^O*IKSvYR z)ACg=%9O7hi?~2P@D=AqdwN!!%k&oelKseUV=&(rSBXGo@P2*+PHSRk=hpuqrFR30 zg;@bFo5p()kCool;Dyw!2no|+e(WOL3SUGnOLDB^gZ)H$lzYnqtd?WGga#ib!ys32 zZm5TEWM!j*1U6$uKXo{Hj9FT7-_SqK@-5){{cMpQYxM}$H^v1*`Ih+W}+!kmVh;-^> ztxO_k)8u$5NTogE@n|v76%dsdi+HR9GH_9s?17_jIEg!R6qz{8;PDPRGC-R(gOcCD zIxDaZBFgY&C0*+Pu@p<%3SC%=%^;uCt@BS{R1UlZ8954Lu{;o`z~muA!QuVK&<1$4 z>L&-B;7Y?Bc$54MaSwn~JnLP`J%=Fktd|Hn09WFCQwFZ`$3ZXyDHH`O=!Q+gt1%PJ znQN|xIzH=bwk2}UeasO;Fqn;?vUekr{ein>DdXbrRPaCWD{oYWgsSaGin=-oPRLLD zkuKOBLbbh1!RSFvt=v9@%pTk;Z=!&5I;xfX0wBthAjSU!0g0Q1-x2=U0ron6=^E(7 z7F0C%G{|AxMEvs4G=jvQ2nolR;TJyp04}K0T$EHyd=UV$ z8$BmvGZ}CNGvKL!Qf!~&3CQ5C#=_`Q(DWxc3P8)2W7MG^21zTx_z4J9)s`O!`~ZJ% z;GQO7pPV^7gI~P-54OdMnPkuuYBBYO(m$@k*WRCh>NY4sKcD;?Jv=*zeZsK_;o0Uj zg1x0IUBFit*k8F|Z{Qct0}{-wf|xE*9c4V|{F{^vv)JxJb*65wza-Y~`+AnaDx$=okE%_SaC+Lz9q zp;>dx$K}`!=t^5~3~Cbfk{DetSphxl)R6uahLsiGyZm`)@X&oW=WC1ePQgC1MXvch z6fy^$_MaE7ZRHNgn4!GSENpYVL*&!d{0 z^EuA6k5dww%Q<_5{}9C&%ce=&35G!YK_*Q2BZl+p9}CzSp(c*9Wn?N!tebbO@h9&Hqy zj(I|Ch|U-d6=H^yWdO4vlL77WEI5}GKLB*4E8jF6%IYVkAd}=_q?O18USp%G*!{QoO!T*}Ag7T|s_2L+S5~*vRcSvT@ zX40gMWA$87ulKeH{z_W~5&jo!EH8b0lNT@A z-~TK6$LQN}Kq0%auC0sMUf15g?nqyM>}9_Fx2JAbjsxvHfMRy*vsvi&!e*vUJb)v- zP8!Tjx)WZsF@FG+O$*Tb(P)z6JO*c-c42u${nzx_?42xeUs8g++o2)5sjj^X?Km~m zUF=_X3>Kx6MJL$KrLePExTf+uvW2ewD=)&BoMS(P z{wDCFc44_JzXX@chA-gx=jDQ3=6&Tz3z4#cIjN^}&8OK3cX`pL%yAwGt6UIPc|BCh z)0K>aU&@!EO~OY7W3bblE&hQAZQWYA)#%w)?`5srhX}-Y`sVo4?t~T`?(#kmyo}Yc z9E&~l;4k#V0Q25j8b!iS6YF90hB%a!qUvfwp)vkJ*RZomSu&l>NZm^jPf;3C7E*U- zr*8J(7+RnLasc?xj+E0H*oJ(9_|u zbuXWB90OF&>;z9pqg|@d*3Lp(bV4{~dq2~P3WrCU>V4%YQ64PRS3-#G(!NR361SXi z^TpsX>~$e{Q8^Rj!y~Gmi+(%44==V~RNmz4{J=W_R*cETw1pm{Ew2(8q}{S#sO1HBwU`mD(9^0J-Og4{D8lsgx^ zU7_3|eLzV+Q(TbsI?SspDV^kamZBa+uOolKfx?hys&a@L4v1j~61R}VD{vb@OdLpw z=*_YCB81pt781E6kpLYUF`S5h!t;e;TqeTau8KF-%eP{s9X!d-Nns`&gwaC~F%UZj zOw~GC0w!w3794yN#1q+KlWy#D*>E0Oy`3+lmlJ4}5R zK=MOWY4oii42w6hZxmsZh=x7XbG?6biv#yUrrB1!M1U!w;$=!0+ZOy}QRpyu{ux>% zh>f&xE81aS_UllguKk+2w*Isf*(4OzzW?5P1MplnN!(s{wtwnYe{81t3$_I#Hl=R$ z^e8NqZ9d(%Zblr%?@#J2xOMIKueeKAOvJB_@lP5wTa<{hkw#kSNY zE%zl#?)1{PxF2=&4u(n8Q5>r($1)(<{xQ}^bnCw8IOb(jVNs}i_rgVuF)@YWaGQFx zaK0@nxIr$Z+JsA{o^Xm^q~d#3P0;uVRJC&F_M1#eo@9y3Ux5tRqO1wazjhi-@w|}o z+5aE4c_r>rNRv?T$4-lt8T0@=rBjs2DHHz)|v0HWTPt(A?KB=oCM(5z#JNKl_?48MB#1F$9`sqq z+Y(OkgLV{(d9k>TxAjA)C-T9IK*$HNm{n1CeO%~LcwH4oRV+3Eht87t$~&XUK0H7X zx8exdW0peaqKhVrXLq3!&q*pDL=mtBklf7b<_pwKb|2)&ou2~2d8WY><)!?&7T{C- zeOr-Rjtxg%PWldT&W%t_tDXL=MsTdL(ASPHcpQT-DGN%$Uyg$#bl$PG0Sk^@h#2i3 zezVheI0_7QVD7{R-0m8Rr|;Y8rKT9RJH*y*KF5N6P|iOy1NEcSpl@yp=Ip2_wYmk# znL^Eg=h5@h<#+zYW^wuadngaRU4r{LywOWV^CsUqVipBA@B+*epr+qrG{<^4;@ zAesM-!v{@{IDoJ(9DfLKpklw1~qWVe~h4c8hfcZHR~{G90I>k*Y@M|<#< z?^DxZQ^uYc50YQD9Bq>={>)B1N*o7njC7OgveSX&5y%NUNNdpL5C38^jk3?bI>x(R z++t7s4iKkPp#TTx;K<5N8FS?Po$Qotbo_rIgYyo8XccFWLp{hGdguQ*^n=Wy5sR}& zINV4M9TE`#ynk{oQ0u+I5H)$3+ZsCSmbV?%+SLme}aRwV!U#tF$eL=-^s>>C9p)bn#no z@JHMw)E!MihumU{a`J!MjL-ZXbow^qRB!puX!uP|{=+8!{ZS$+4ufJ`A;>K88>g=! z3UELEHDm9@*UaH?9p@S7r0;&k`5d>QU9qEiAG$Fpd^E)8ID-no1{vnr9tZ?WalnFH z%jA6+o)m)#;SAjfm_7H<+_hSQd-8;7iIL$h(C zD8(nkFibzo*u}x)BCkco2jkTY@nI{q%k+jJC@u2ht7w;zZL375oktQ@lxWg_y>Ga! z>zwX%WRR~s0jXB5>?ce_d?mz?QT*4qO5C?Ju`6>DZf^L>Baq}j8yRz^iP?={cO`f$ zg0V_)ID$PX8V{-;#30jAhAGGaW!T0pjyrIXTghvr_(?1``yO3xTlmUlEVnE*w@m)C zEH$?g2&%bZWX@u_jS~FDw#k%RW;T}OQv4~>hCLi5w_kwdCCIH1A2_@|3ddfc9Br56 z=|<1e#UKVFK&z)mD^~>B7=h3F1wVuO>JD|6HuMf0^%>Wg9)7J_D--l3W?AGPRH4~%L zCB<(8L#y9)XV5F>+xlUk+lPV9CALOqtYYckQPVf@{$`57d;VauMELFr7u*k@7{hvk zMp3d~e)e5d1H4ecOohuBGGQhXP@|2-dE9BX9I8jbsT^4mPbyz1>rQFC7#r!7uYfbi z=#YD%^CXhWNB@Cd+qwk@XS}cgp$figIYJvy6KDp<*Cf>TPik$Yn}$aLzUo8B;dYz) zxo#j(9XoI}1|u?a6#9&;^pg=dQKxmD%(_G9F~v5Vz5KvdL78>9K+A6C0{00UL2Z>hB4S^)S>LOwT`;n`Z_|G4ZlHLh5?+i>JCT;zT#9&X> zyese#80rm$+vRX~P{XW0sNu*RB2Tez4ZApQCHoGciUQN1n7z-2c4HMtZ78ohji@d( zS;Vb_U5I1!J4AN_%^WHXCBY2fWXDtC0ca-N5l}PTLF!+82f`1N4lWv!=2|*fUi8I1 z6DCX$>H`M|PDk1)A_@;@XqLcYcA$mbchDz6H>ZTr@n`w3t=ejNFBv1f|9A?D;$`?_nG!U>L8-YA4zHk zP4u{r&s(8r!z(c)eehxs|8W3rARD=p5Tie3;{_j<4R*uHC}P&qkLKOwr0tQnpha^0 zMB$5XDjMq}ehUgOM@@j>Y3dTRoXy3U0gG|2YZrC}+!it8V+81HgS2HUIX(cA6E{Oz z#hx)_PeIt8Mv4gAvz`#M=YGW={3`amNiqJ6JJh^gWY1Q`o?BF|2}(QLvyj@O zqkbNM4z+&9lR8#EYY~!n!Eu!OxrHLa_M{PF_Q1=ierN^H>W8Nof5xXdq-US}-#66d zFu1>~T;~7Sa|}{Cd%oNmwr4Yfiai6!o~*Dv|D=epJ+BjjJ)ViMg{B*3i;pGVC*S@$ z)T0-5>^XaHMR!rZ0)}0wD1(7pZAk`}8Hz43>Um5{tw0a#THT(Um;>8R>T@EBO9@XIr9`v>DM!AYK)? z2>&GQ{b3-MtmV~E-Iw`D9lerC%i~x)oR|(CMy}X_=vT?e#*ryd34S%^KFhC^gHJmPj9aqfa>=WBXM?&xa0kIjfV8=IZ5d%_yQ0?+>7@8%NUiHcA&a^#e3gUxbydD8J)OHc}O%xa>dv_*2DSlgJuW z7`Ep+6jy%46tj;(c#z(y7U6{=;+}z2nuy0jydm3L^ z1xw{GnUi3<4X;ELKf5w(@-;j^Hq2yASJ-wY(~Dy z4Pcd+JgkXQ&;$Pf9>2=;9VB>0)pwKyHa(C7mjDrd@lPs4+aQ}Div5IIksFFQZF6L0 zn7xve`ZHDtiZyS((lKZ*Rk?xiQ6=B~l<#X;bx^*IN;!&EsRBy5l^V*QF;V4Cu)&oq z-bX2?swv-9QV!DeQ%WSHlnE;L+5c(p&EumgvcB<~godzW0SSVDwh}NXiD3yOKr}mv z1c-zs2pT%wour}D9n;+*0heF`nrWk`h`R%$GC1Q5uBf;WcHGfX1{D?2(MDVl6?gmh zJyoZY?o00A^M0Ps`+NVO;pWsmTb(+!*R5N(Lo$sdlUS1cOHPZv6DEZ<8*wYHC7NnF zgg(*f1r~_fvi-0VIZ0zn6q_k(-go_GWbpz)_(+-XC8!j~Cx`=u=A2`3@!b-S#q~8k z`s7Fw)WQ1}5EyL92{4;dQ8n2`FRZ_v4;C1Lqhi2U-K{`4};60fyv_z~;;qNFr7`3U@~} zKwGQF-0B(FIyJF%>X73mN`4gHgInG^0)rCl1;D*HQE;9J^zkRmLZJCj>eFKU^mDv7 z;prX}!_Q`KZ#?-P)ZTpX$L98)t+|C_1dhy)#u5TjUs8I)!%x{d_|y8rX5|0RFCnSl zQ`R@{FjG_0xEf(JXN8?}37qA+qh$W}zz3+&A_Bal39-VADM@%U&u{uv-lTgcfH4{m z)KbkpEGA6JqC;YF3_VtXeE(@4UI4b=jvQ!?$wVMhj%@0_MW(+P=+~~c5Pgp5!&d3B zl=Ev?c|iSDEnG~lNW*yO3uL3^##7R4Oi}SbA4h@xIK-DZ&F@T1I$|P_P6sB8dLC{Q z@2m|xd@;4r{+K%-K=~d5tSaBn=Rn*2&_-V9oKeNCI7|YlUm??N(e4-aM#~YmVkVK7 z53hkSNDTVXm@^UFOpo|5gZj&h1Ty^qfjOunhldIFm{`^8Os0tL`3e!>ZJ~?jMw}%$bdWuoy zyPy?XX`e;hM$9_#6_O~qC@tp0U!Y#QSqczHBa<;-rLNq8 zb|V`LZA0Q(?nAIR!%L49=ooilN}+XuuwnsOaReVx`Q9JilouBFX-tot_!vsJoykuEm4W;xC548RoN-{ zSCN6p8$7hcW}(l7Hv%twNVySSJH#7{qIPW0NZzv(joo;(TQKK`ZShUGd;R#fc)XFF znbbm!-By2$8G+`l=GNr9!Q7g5GyWc)h)l^yTZy1=+mU-)V}5N0DS4@2s3nF$VAyT? z2xw{wDREgy$qF~nZI`qo58_r@5k69}{I?G%jW^B4-SXdMxCr6qt!U*wLxRsng3m^R zdxR(WuAA_5naYtUJG)Kw9Mv;n@llb>YwNQDd>TIG(|i!mN^tshx1QUwn?H)jzOr5s zTMyGozJb4?K$RBy&!XJ^>SnkNJ1>zfs0SYgmV7{`kBM@EXOZFM1WzJqKyntb`lpCT zPnbMS!LkvSz7asd!eM-c{<(#;xL;D(d`*_-!_o#CUC0%ZVXEX_aG@x<@$g?@Fj@_I z+t;6)j(OH`+1hMH%(k>ZarQ*JITl1mcS`&sNczlQ1hi;tqNcc0K=-{16 z`eLGR&pl`Q7t~^Zi#FQq-76gRLKMY>lj`8Dz~BR@1y6_J#}Yn@@N2hsEKEeB;I)3A z=dUWsUj7Ls94}>TfgZd#^1c|rO(leI)UOc5qYwFJX?FAbaPWtL>w&}z7ZfZ*5L+M* z#yd#n1x#54Q_ux=Z(cSF{U3%dZj$<; zgZ>*5dL%#*^2ikD&>&uUJOTCtL$BoZXTF1QpydXRoSPhCj{VR><|f{)L<(9;<3WG^IoEpOwLc( z2MW#33AOE5+~uYfIW*fW+nW7cB$Q!N7qbi)O!D`5rWBrUx>0Cq57o4%@cfu-Ley*} zH5alPQjo(6vg2+Qj~DU%po9Vl#Pv;>0f1Y4*rD&xu?SPr&wf{RnaAFtQih$Q*csQd z4%O{Ds&2Vxw!?;v4Pm`*_2~BFrd=r4S#c}A)39uat&aZ8IiwslF^!fzv3!LGz|`Ok zd?pJlwDay&XhnmB6(T1d2(%6T@(vn2-roe59vMYTJV0W2BDWT9mQpx9xm8LX*&{b< zeT)+*GZshtzQMP2(9s8;KngG^#Ou$?1_n#xk5uOSYtVrI7WAe1P3OQ!oGg^x{3#9? z+MT@|{d%M}Ttkd71gmR9Tdxu}n9qO>msc+TE%@t_7f}+TX__e>OwwqK26{MN2akV6 zXA_9oPuZWBSRGPc4iR7Tj)suoY}2xV+SN(Q>W2y^o$ov5UxvQzt3VJM5XHx|Yj!{J zHSDMF@N`Eo5??jKOWB$?EpAipp!PryoFDissyL>QSan(`TH+4_-~0(CgmivO|Bbw> z2C-?`AT93C?MGe|Pr_QVfjVv)j6&bddoCSaW1f6XT+8{Sg?t_NLVKSkY_nK!uXOU^ zzMu%U>+=%N-Uar-=>Npxt~l{LFtSaK;|l#t%*gXzP#k#sI&#Od)IhNeB+2&Jg-XKx zaWti^HM0ft1thq8YueQaVT`^Q0MGsKx2^2g*8O+>(Ab|IKP&{IInWKa@#4LEBz*e& z=qP6daLhZJqbFeOerzAg0$;0X1pdZtOum3_l853ihQognAd~b5j>BY_T23gdsSp<9 zadMBssOtjAvOIrQx|l}2c<5foJPHs&x%S z(1`D2&H=tx{P&2tx&PtBAyfsi*DL0(XDN<^Wdz#NW~GT3?YIkk^)SV_OJqP2+KBA7 zf{Rdkv*U_((FWq3d96pc{&?2bz!}*%j-9sbZWVQWTSoK3d$iI>-SUPu;LvHeZ8n;lcFPWfjizn`~Ec{;0f~bXiD=J z$ZpiqEZVDmknrLm<^Uo^AU@$*YEBbT?%~?@LTiiQEAn^SfjOghBZ$GhM>b%;OGv!E zQN5!vM*K50if8G*3#c(Ep}`u4i8NwE$c#j%c_xcQqxGRglWB*=zHq^QKCwG={e%z) z#jJXqcIqll6MD+fZN$e+K)5n4>5XG?3-*ErI4_~Mj0ev@(F-NKKM57;x=Dy`OFW_s z9M~w99+RI09$DZNt>bKIGong2G$J+3h#Q5tG~zEJykHO1p$8>4M7-5=Mj-Y{WD@p& zVw4<+fse5%X2{`3UU%AZ$)j;JvTu3XL=H(~Z-=oR?!-Wq-I+@693>L~ zcOF?or7R07#JW}?1z^P|u*Zgn!05J+>E6VJ`5E8mwf>MDH|@8Mo$nNzoD2lMXrX+M zYrP#a)W8fB0^XbN!aWVm7t#j&gJ2KapuY_p^idpoUb$l-EdmUrK}39EPZUWc{25FX z170-lG2C8Ns%GDPzltMV2 z!jH+Yjl%0?col{3LYNB0w{e$7!6}KPZO*5BWLRL-M_l)0%S+6GmFN=!bTGR(e;NZh ze>bs?OMZ$TjY<%2dTo;^J*MB&ab&UnK+S=LthZyHEkgv_CxK*ekc!SuJ545ZiNNtyN(Hv=>Z7*j?#(22R*3pE14)$0|hg- z*I_i3Nm+x2XgU5qi;G&+3@6&d3$g297SX5)pd*|EpRI$gR?{RPkL(f;s7F3M5h zOrp`zk{LsXfnJ|R%V%^vXH-IO+>2D9k^BTnMI-rkk$>C)+VuSl3c=X%(EY($`1yt1 zrbADN2*v&z6t(6r%wf5R!8~=4^exjqg*dr#($FW7%K8CReQ4m`N94DUrn_cZI64%l zLyWE4q|lq*e~V2ta8^`|S9mF5He3szy&yp5MF0UT1EKXWm4X3dW_<9hZ?}OWl}GLf zq`;qHgW^JbbB8|B)*r~s&@TW*TjKTSH=<0w!+ypeF@}$%w@hA$kce^X8<~?yj=Ew{gX|0Gfu7yY?+ho$XiGjtQ;8EcR`~0xM{)T7`5XolMu(bxwvWm zv3uN24?_VRICm2kV(9RUtF3u{QwpeUQ}K0y7&AioaT||jx6Q%PYc#D8yP43M>;fgw zw}~OfAG=}zPtG!=>VjqXFwzxyFC*VE0J5D zA-56}DYK$m-=Y27{>h2r$m}Dx;9YoMTcICqOSreAMv%9=B_8=J_RK9pY}gL^PTN%4 z`?rz$QBhx%G^paA6!i`dqQMj8eE<5 zj1a<-XSmSiB=iu3kS}Y%f_!OxId0?7l`^a{N7^UjIiE!8A#!*jhM0lpkV(`KWQ8>Z zs8pC>6yp0t&tYI7cBqTP0VtQFu~Stnfyq@tf2ISG1-#HGcU~>REqqW)*vqe?A`o*O zq0nOa)22z{cp_RzMAWpWmg-B2kDlOUVHsq|N$eYPa{FEcOW-ZCM40drN%%7#C+yYI zz7K^GvhN>~xQ2*Z1(8~U(~a%BQsnZP;Kz5FL+2XJUsQ`Pi0wtSOV4h74b9i&Vt>K{ za25kqQfU1^ZaMs=b&uE&fTqm&z$B8b7%0@M+C z!ZDrIhM@?W&&s$R7^a9841*Q-ti-Iik@y7fEZKq-hPDEG3VZIQraTp&f;G2pE9{BY z6SN6=K&9uQqAsAD7}fI?m{Lq*V>cR>xRFB=QBmWmmeNrA>>pu5G48e=GE>#wE2?%LHWjAgGK)>A zS$Wvzxd0P7xOAuZ#0T}T^eME-S6`8r@!M5SYzDT{YZ|{|!u_E?3e9MlKQ|g6KF{yz zgZS`f!`FeCv=o9y|DbC058{y2kw~i<-xP_DyKP(EBt(nP5k+sqch^xDD)T1U;Jpb9biUUZHe(fIH7Ums6-?Z?H29D3~zklcKRd@ zry3sy?#km|?M1w|(kNw-wg}De^Z(Ga62Iv&0&4x2yn+hVT@=%VAV?^*+CN2!{s?!0 zA)=@zd`~F!(#K8L0&Dh;@S8qg3=}o~{{Hynb=vlgLg--@v?~J0v_T>WWZG7QM8YvzdKO{KZ|@XM?`}Da zxv7EU5pTI+!GikBW5n3JCktaA5no%(9l)r^ibIHs_81_rml_N*6H?C!se@aN?D3n{ z!sga(J&*mc@9bT8-8TPNYg!vnqjzN;-(~jUW9P6wum>-|2w~CYHV5g%mCQw86Q*rH zZkmU1Bz+nk+;TR1-P$F-NOReeNR<&!KTLCB6#aIaiaJ7*IJ5;* zXf1%&0&O1lsG6q!o&2*nI=lJH2t3?~hM(m}D=fYO+8Z6)DWrB$GLY3jfbxnX8h~`z>h>|{D?1_p3#DM zCSz9D9GEqE7{0NFAkKWl=ha$@BMt@+Vp4TR1ddJh^)2-0lU3f4!bB*B)&Kb$l;P8d zWOQ3KmKn2z;VY&iT}Mt2T(lcqio=Hn40R3X*nwEhs6pP7hIYv!5w+njkCVdL`C`VcOyJN^a^ zV*8~(`Il-8VTS&MzkyWj^gyf7N6rK4aEBB;OGc2dT@=4R^Y6G19A5%X9L{_rWeV1R z2-i>YM*$TW0Mkfjs+8IN5b1!irti-~C@$~E!q&_~K+=RI5dV&vMSc1&Sd^9_mFAGr z%cuslnl7a9HQyrAchR{YfF$j`NFcpfX4?bMmfsDwB7psMkUzdP9$TEPXt8)! zDI-ZXo?)rui7j`H|bEcnY()tnYvPLQs zF)I;E(x;BoqFPzjBoGHn0IrC^o%=Fs+v_d?p@t zi|Q_qx4faGsZ#AY;@(0AqPg*#-T*7!z>e7o82aA7KVh)oPa7g4$@~rU zz#Swej}!?!N*fVcO(sE(`IKVxC(ICcX#sJU`69`DCktM(RI`att5Htjf_N&}&D}7I zyo0jUpLr({`(HtNF~`G`*RN+<*-Xauc zen|MwW%m7ouqb|LW0pyBJCR?OiJ7j#t$eoJzoTylqxMX9{DHRNpd%|{NvaW2yG>6$ z2nwF81SVrGAaEV!9nKa45^SFV1ZE;qxm@bc+((F;shG5-{RLs!pB;4z#JUc|hlJQ| zasoj=1RWeyp1bNS5y48J12>~xr}Ba-ycuX}_b4gnfkvyh7?Qx2n8o5Yp#&T+WD`n# z9_g41P7y7KSlk#3hCm8z4*UtrHGw$+vQ6LhbaWwzXXXQ#AU6F3-gnyZCrTvdO7vA1 ziN5M8bVNb#Xw~Q79gc4*$gr5N1W4E-?5g`U@He=#)${~z504>je$w_NjN5Ix2}rW= z@GC!_%$n!!2iq~Q1*Vd`5Ay%r{ALo^N&;t)K;B6OE+&ChBye~O{+xDFfxaYAP6AJp zz@eIxnG{U|DI{ezLKk~@O(E1R#10|$zIdQaQK1_&<2=M?A z{4~1Fw0K`vyDo!J7x)~GI9&1zo}rzUM{5{o{+vA*-z|K6Hd@X(MXmd?XBXki&9kyw zf1Qo#(eK5b)dY(%7dwXMrZ_Y-JJ|n2xy1Iq?{gX` zhCF4gtW1eJtA*G_eA^k4PU58dQ@>LlIOTy;9ysNJQyw_wfm0qh<$+TkIOT!=eGf!n z3QRwHU7g)yO&Xg%a-zA&Wp(4$>NUH3H5K+cv%At&(M#W}j=Z%e};Pfc)g9C&M-Uet}1Ue7~N*4+f~&|^TJ5SQu{vkPN4%Z>y2597S?z)|wPASbm@@OZ!>#A`>fofD zt*nyvk^T81VN2?yKanoC&s*#Bnk(T<4e`y_k`}l|;!hp@vEvVvHjLD2EKaA}D!pB2 z54rJp>ukQ-&}^_bI4BF2IN(~3*W&egL~y&7NT%o9urffTT4Zl%xj=bY9ENbXtzM^jR?&>y(%f0I=7rI! z?B3cXJ_i@6CDm@TrOs|{bo(yqrIi-u7Z+=}Gm2+Vn_j9F7fsJE)n?_-oR*tiq7~1W zGCg}*zBaowf2LMA10kYxCW?yv5{Jv?UV`#Sg^Lm*^TA_x*(h}taOq+#$>no8M|NbL zDqIy7kA3{O&MfW7eAGe8vf)L+kLjO$wMP%G|RAJkq4TkBeF+x^-$+B?+6FAWb)w8mhNpt}0uJ`vYE#;*eW5s&=n zA|4eEg-SLQr^59i@(TAqg3wXHsD%-D{gHtcokNEhccvaKEk0F;r{3~4ZI7w z;2#cLMi+R}y8!Xs5)%Kqkg$sP*$~|MAz?-D3VsQ`2waO12g#@_l((;eO^PQpt_jdV z*DZKH;@4dY7X|zlyruLAt}jEb{RoS9Qdv&%D;TQJ%RF-d&){$<{Z#lw|Nr0eNmuzr zasM+UpEietyRz%pN$5)EjSxPPQP*X62ZMKE`{Dmz*NZ62pW_+;o1#_+gBReMk835a zAg*&^+vT{f#t7+^r$PS!m!c88 zpXuL+@P*PT4vP0KTn=2PBk#!;inkHhMY!(;ypqWwJ-}TK+ZB%Bqqy&Jr^F2aq#;XG z7vq-y1Js3(xoBM4_zCG56DLi^yDOT|9uXPU&D6a|bkAPB`^3b?#rN%ZTK|Lrrw<$? zqc~&mkfCRuWj_0yVTr>>oSQWAyz@sTr(BRadd%4W0~7xDMS{V2?HWo-4_rtO-FNkC zB4jlEiO|}c4j%onNozlNG`F~@BxgqU%sk8w#f(bxV1i+(z)Z*Ew79(561$_S+N)K% zU0$=mm~C#m$Lw-ro@()0t9wb3WIKUowa}DB%t(X=8rIi38thIQt(o-<~2uZHZ;i+|68iAuZh0y14 zIWT*GBdu;%rK2jPmp06jo{^D0P8&8Ro&IU%W%<)`^7FL0)5`Pmi}PpYm(MMlo;PEz zRz5v{RynX@5~Xg6a%W5r$CMQ1X|rb)q?eb@npre`iZ{I29MvQ*a!S$4l1n z6dohPVH&QnlLT@MDH;{xA|8eA@1Zd|pvuE6EN z<-@fES0jXA2$BRaR)TR7q)9Mdf(a6&OE3oMCIJb?NsuPNcnKy*kS@U(@+t%*7$-rR z1mh)`AVInWW5|OLkYJnyX%dW=V1fkc1TNJE$7qAEK`2dnX1oA1$SFvl1ZfhCmtd>} z;{aw{BH1KJlVH3AVv{CBbRT)!JQH~S%Ur6wj%BW~%q2?ym~qTCj&+S=u5rXQ zNHb}}tkyB(M%%Pu8R;1lMq9LDwh0*-V@7Mk#-*jFrKfAdDn^eVlRjP>Ha8SA=do0Y1sC)DWQ(KzG61XD>a=(~6s!~t7KN(JRdzQPNb4Hu z&SiFCwUk0`u`YI5FLTFle)24{*Wq;vvbQt1gqL_6HptCS2HEPaslkGtdA_Bt%41GR zNx7^;4;t0~oAx9-<&xQaT3IXaFbAuOWtH9nwAnMTbXI~jU3;C2I~uIm+82uzSMI-r ztK|9*{F+@`>$ID5#cFYWjeC(}Vn@f9IePp=^sNr8i&{LH-a4OsqQ&QRS36u@aYxyr zm6h7PMbqY3oIbl}BJ`+UgqF(6rssuso#;!-aYp}F_iEhVhZ}5Vb zezQB*Qad_Rxx$6s*NlZyXM`*Q!J`Up zk@rcUb^e-KQfMcfrnO(2ML=t>lTb4=Y?UzUCJCFe6uv!TaMYwe-4K(eX{l3!!7PO1 zOM<~9z@C?2oeZ!H@JhhP0apP&G&2}H0C))SDB#Re%nwZ1Ck;3d@C(3EfIVkHAK*iP zvjG1*I~cqku=|`~@E*W>0UrVUs4N)#74Yah$YX=znEAorIKT%1YXP+d!QfiJ^DaZY zfOiA_3OMcZU@#Tq*ed{w0apPo1WYQ&Cy4;(0$vaJF5nY@PcFn7AI7xR7Nid_4hJK7 z0iOq43)syT488)m1@Jq-#A?{z6Z#xjmjZkn&=71xRz!2LLkxp9Y)*_zK{ecOw0G z&#o146yP_23jh=Dg5Lliy&Lj?+C9PG!+`IvMLq&H<0b!MOhbMGYy#}@*I@7tz;eJH zfPV!%09cE|7~=cFUk`&H@Y{{x2dsP)_5l6`upKbxapWK1dcgR8@arb{AJ7T78nFK9 zU~muMYQRqbU7KO=Y4F3d&&uT>%AnpNxSHbN#~7CJO@~M16f;u>&{}V(GUVZ!qwt>3%L1& zz^@Qj3$DS_aOepka$@6eip+@}cw z_{ij?uBdt`k5;b z*5K{t$h_Erk&9yEvty$vpN@ilJLr!QU1tmB(+#?OBEKYJfhl1w>N3e(OghWpv+W|k zvOpUO+F3-ykMeL9a7n=3AutkG4cx=PiMj-Q60R$O+YMYZ@#$=dT&CQD7db%Q0Nyjp zus%w>B`2vPC6Vh43c<|S=cgl};@TP+I*%Npp9}`-^OKnU5eA~fiN17@Alcc3M${;ON9>!tpasXqG zZsZqH#-bu4;4^A7OF-L!@y9<9)~3WBBo>mX27VsKBV!4VdN*BGE!qBoRtwt4f6UKY zA`CK@;@JybJu2|Li{cTsIbfNz;S10PfTp*Bd>)PQRWfKdv0SImBNv4_U8EnptGdub zv=Y$%3R*kqL5@Xkk_n|eaDp}!V=p=t;3Ro4C3del@)Lt}rbGN`=<0`Y+l9zi($z(K zv@r6a6B{J5@jJ-&$5`(cgoTae(niX^c#IqW1e#dgMLx}f_oz-M0q+OCnq=@Z!6W!|+O!lOirx z8-O3I!;??jf$t0adF+S(TCQuO`gSuY*A#aj81l6<7|i9kujaUM^pSQ(4f2ig(}AZp ziQf850REAa=VxAQR@C%}*utFHl0~sumvwD~NI!I5;llHDj%xwpDnxh#@JoTOL>WFw z*})hg>eg-sWv3Y8G}d43!Sfu91y8E0DI)6oI0I!6C)@Hc7x)D)9sPlFoflgYS!tw= z+GLt%Y+H$E^a#uP8+j8K87&K_M?k;$x?u1Z_W6y}54X`+=?3bDSJGIix%-sZ_Q=SK zdx=pJdgPE%(q!oP4mu)QgTV(8HmEzPk@+Xq9b%_)i=}LQ^eGrb6D)p&D+KN`;D(3b zXfEW^;c9`q1~?4Y#gAlLfV&bnk>|K4+#SHR0*9;;Kf*l>+*QEwlNT)yxOU*K1#S;< zowQw#+}KsgqjDFG#6;o@r^Td)#zmE}(KL2313w%1YQh(U_C=91L)yhD)UT4B67aWy zKau#MryS*%+JS1|?*#rH$~S5kD`c(`tp&6}H}E(aGFNkkQ(0XLS}|zv|B?E=HX>?^ zQNf2U(sva4-dPn4u0MG_qTVYi5M@+a<+wMi59V8Ig28&|CjWCDS2y3h_6LE`80TM1#cnoQhw#i@!ekFw*&to#(5{&lNz-$ zGTfkauywEz3eyo6Z=eV4O7y6=XJxDm8gPXB-JLn3cBC=pS8 zB4Qf~5O+cB^|)5yx+5Ydc5Q?swpP>uC*s+7Kc1_T4Z`j#gcX#g>p|NKS}(F8H1A6y zABZi>j$LSrEvXJE1mvUVAhZ6#VDLQm@k~=hyglx!XBhQQYIdWh#F}$C`FW%o@(e@_V-{g z8DW{8#Uih$-nW2u188UJ^U55R5TOR~;G?uW0^X}14hCCJ-X2bgJsKta@(E;Kdjw-7 z$ZMBk?2VYoreA?S2>g1&3m>DbAzz6$5FON_M{$-B(a-|bhvdyT(AI%AgK5-P(u9Qa zpaitHKnoo!5xyGu*L8SGJFREE1pIHLOP4p1-*?W7%pgAu-n$>;wEVHBW1D~fmwcl*%@`x4ZsN5Ljq_9x1dhI`NooJD?GF03SN@BwH4GgoXz&HW_Sm~dl;@|_yEHV3?F8=k>O(u zH!D62sRRzR7SO!*>~e!0-^mPZ@sB@GFML7#?T%GsE8*Mtm0AUl>N2WW4gPd`?1ksQX2Dt`U0g)7>xE-QTFYze{)j58eF>e2?Wq`74nC%Vgqr z{U;KtwdTDU(z3x!$1;KVoxyNF(`n6!ev1D6??Jb?r};R3Y7Rx~0rbP8DDk7`$n;b5 z;{mJy%eCT1b5r_Zdq~)i#jp@1?Njr=bWTWr=H}#T*#04YwdoQnd@3hQ*_|aOuGk~P z{3`xm@OliR1ncj7{|GxG$oI;QqkMlgJMt&Kr{mB6U-&g;c5MjuON(Ct!#NBq8P+pg z$#4zB^$a&L+{JJo!%rC=XW0D`8P5QQBN$>Mwv3lv^_s@XE=f(c6iJ98O~u?$*`W`N``9~u4lN3;Vy>z7=Fs|IK%Fx96!Sm46$`w z#?NpL!%Bwr3|BH-!*D&rO$>K2+{f@!hQ}FppT+Sr9Kmor!vcnL7*;Z@XSkB#8iwl` zZeqBL;Xa0+GCa<(`)rP%;RuG~85S^{!?2QJJ;Rj@*Dzeqa1+B_4EHhol;LrP-RE%p z3`Z~=&#-{u9EOz)>lv^_&{XE=i4c!mWG=P;~fSkG`J z!!-=oGu*^*7sGuFKV^8FVfQkQpWz6G;~5q(oWrn^VLii@4A(GR&u|mNT@3dz{FLEw zhB9_M|HzZ=P-;$RA4s&0_FXaRU{^1mb-u%P;YC^5Ik|cH1yc%(E}mLEZTgIoOJFFaBofX`v(|?{$zn-3=6^^!s%3D|GSe={GQ4rC-ri`fol7{Wb&r z%D#IH*;?pkXQbD)IeU*HyOw)`@|P(kU7dfDt<-Zs+0d4Gb;Kn zrmOMm-*xntbo4eI{k4AdV`d2#nr*(AIKPdTUbo667 z`I$^t1fUf*=u>oEA=Mn?DO258; zSLsu9egA%m&Oc>3{fe&qQ*J<4{;4#eEC19O(3O9@26W}0Wd?NRpKA^1D*gKLiAtZM z>&GXhI{&ol^eejZ&ng4D^3UxCbmgD>4Cu-~4;s*we;ziVEB`!hKv(H6Q1yq?t&F**x_*43=*qwP@rRzSAAcyi zvQIz$(9`wf4>@bV9yU(UdiEbDOViW;#&qRRMOXLw@rRzSAAcyivR^;`(9`wfk7#8m z+rLehe~Pa1cdL$GqRT)1{7X-d)s-(rSN7}YUwXQJ{xz55SN_+J&-HZu_*~JI|MlZ@ zJzYOOS9E2+etbS#XWtH;{}f%tKVA8U^Fz^}GIUD@|{9esz+zQ+yd%D!g|=*qsW26ScLiw1OM-y1smPMv-4 z7|@k{9~#h=eV-Z7m3?0u(3O2Z=;*t2_5}^-%03f6zts4r{tU{*ul*)Ia{T&@$-+#WVqwD*x_jGi9|8YP^*SFvA>*)IS^8+1S-+q0lqwCv`gF3pt{?fRX z*4lpGfj=QXRo@6w-KyLwiTd$3$%oQ!*VP}Ar@6b5*N?ABK9qi^PQEwqLusNTb>o9 z=xQDjLf=B1imv8yD?8++9!1Y(wXuKU00Twm$F94P(Quz&7b z8?Jv!E>5gUnlgR%Nb~5_lrbr(;p8swqVSY*gIeGX;fTVw9(HtzkJ18N;=5_lT|9t| zf>+gkoe=eSR@+^?=M##faFo_VQ~O^+X;FBhs`kx<<9ljqKTbFvM@*)NVq`dq4j1D6 zB%#zOt&e8z5|7m?m0=+QQ8*Mx?RyGA!+t#dJsOIU;V8Tva+K>$C^bs!ix=Be$?v2@ zY5g>{kE#<({y9xk`=`S3{k4O}WBOA)|tz69DKK+>Zg`1NP}Va6Zi^;3nXVoZAO zXFbQ6{};yJ$%-==Pu&Iit!ahySS{mw;+FKh$@~i6kMTJRB;y3;r*|8Pzmf4)#*bk9 z0mf4~px=8aK*awOEAi(`pmj&ODZLp!iMWaJ zrvXp-XlDOxVEj<#|AqP689$u)yD`7Ymyyi>Y`tU@XJR0j!u-)X{&CDdqDAtH^F0u} zQ1HiT2Y-`zZVt3!=D(p;;>8&r2+kDz1GQCLPE@x|@AOmq(9U|)JnS;olY5WUqwJ)! zs>lzT&dwTzuYhCeXBK}o59|5;UU@IhH_kDgfk*0X-?I;mfE+Yd2+w{?>5Z03K0@xvLf{I;3# z$&81m#IK$41&rUr_*a0Z`n#|^LSWW1{&gs%a;V0eiy8lK!H@Qh{h->n>DZ`D{EdH+ zjMdD4faCp$@v5F4V!Y{o$tcd-m!T*F7K+s-OA}cu_B|m-he2 z{68@NQRWxtbAZ+x1&{b|VEelw{3^yPKMw_-();Q|Qh_+H1HsXZ_iU7S zu4dX~#;f-caQuPzUGr5+TmbK=59{KS*oP`QdMjuhroxPIr{_(^SL^TxSdV&d!X~59{=<0neuX$Q1GKLhuinSVWd5HSzhH|* zi1RHF?2dv#iW9`<~Qs3&j6mvN#$0l|4fee9LBdWUYvsgS~BC+ zdn|V||5(QVv|S=rGk!AT)q63c7@x;@_1;WAU|w?4h8V9iuyQEGcS}G+r;_n0zZ|vtbB$2?YNn^bgV&>dMJ2j4#yT?__+I4*wT{$9QZK+sXD{13cxUdjE&U z_w@T4;}7zCLF*-2dyMrr>GVI#{3~CVjHS%KlksYOK(%i#v7RK>gIn?Y7vocP`q%Z8 zeokaRD}HMK$p2;?{t)Xa(djwD_%a=SC}Qub-~3+i_tnOo%?@Kb33SCDB_Al&o}2mPd|!U z_p=({y$1Ld2KcoG_{R+JI}GqI0pAz)C+YI{Eyl+$l>Sux*Lwzfz83fxZS_8xV7Nv6 zerEhj)$;y5#&^RDQvFabnm&;Tu!&!Pf#;hLaGnAFLIeC%;3-|Bbm_VThEcp~o#X8lbuvL8b2EC00l&=vUuS^7)&PH#0seji{F4Uw z9l-ac`JJx6drk1iXi2(qwckL`CkFU$4e<0LWmow!0C+Q(jZiQ~@W*JDDv3Rdi4z(B z)1M{cLB{7A=$~hRcNyT@fbUD|X-B1Ebi3krkKm8drn7&3WBhspJx>We1GQ9LySmGO z|7`>OAp<;}VcC^Ge>cF#qCKa4kJpViP801sCnf~YHz~{^_bQmp{la}$N{pKSP6a-` zi~hZg`TH>cEv)GIc(Ol6o6YT&I71b*3h+~Z6s_wot`PV^S~p#}Z4&yGb)E1wj#oXe zI-f~@HsF66_;WhNJ3?<6@PBN8KW2cZArYmw-@8(ED#y@M;5(x02qYNb&8%nY8<{|F=QF=PX)ejB<5chZrGTAt^t3Q0lwJ)f13e*y#f9?1N_Utlb!0l zK{iv{Z@~YBz{hBd-jSS_vB39?kLU4~o$;nVUDvO8fsfJ7sFaMn?5GVk;2&jxPdC6{ z%=(|=`clpIlmbuncc8BR&NI-n*Z|*TfM0EZf5-s81WjaD?a$)|{JRbC`vpEm%i(%4 zfYbXi<3HLj5v`2>+CWbfhN@lJ6KjAUWq=I>rAHx9g>yCj|SNSqn;A6DiTpy=0 zf3g98jsgA>1H9b;UvGfF+5mrp0samH{KE$LEe8018Q>2Z;J-4!{|-F$o0_iQ>|sz( z(=m~xayzg!yq!P8fPaL*$7nn2WJalVtrW)Z;pZa?Ki)u35%bS_Tk2WM`97EN-|6rc z13f+iyx#zSC-BsMs(Px*=RXYi+YRu04e$pI@Lw3cGQ6XPOwN(dNXfS+W5pJ9Nn zG{7$fp2ly=KPu|w2K;p9dsp@PCIkM{4Dj~~e2mszSANzT@NY(OrF@Lm^@Go{9#f07 z{A^CZHpWlkj+9y4e-MZ z@D~{1(*-_8^Kt(r&RK=MnT$X5hBRKBkBeZDfgXzi-p6{*x=QNd>6vyd;|H>zFOX!- zZ=grBR5+6HnOu8{RVy#YO}Q6SJ^BVL5Y{>`4q>RQM9^lhvsXDh_^@EPx2D|cbi43X z+46FmyS&QjuCO@EZC-bsr`+Oez^7zu@J&Il-G)t4U2>FHLO;GLJ5?-pX{F-Rid4D;m9ax~;Kz786wW>Gk+*p$%Ue6hwzh z@-z@&NzLnYxbU4omxe^uXkL7=(B`P~BCy0#>%k`piBjXPw|m_ZS`e_Nj1`>p8Dg#8 zJ32MBJQc|#VTib$PR)yN78 z?n;|hVe#0hdDUXMsR8J{vntHj zxP2bG#VWqVT;r*B*Lkfzn9@M065kBf8k}y6O**|n_)4=`wT2qYVtbXdv9_ANbnL0M zTRkX*AiD@yp&meMu)#(hS0_%hH`Ka4c8bgEtE+E^gtu@dGIX#-Y)>}>= zI~4)(VN~Rn-9w*Em1U4Vgo!UhyK7O~DJVQwXRn3JGX&) z?m}+copn?+@CoTkx7Fv7g5=+d8fXH0MIEXUN-(kF^Os00zKUHdLlyWIwa23^W?+Yl zd@jlnk#^KF@rms^A0kBpP`(6oxCIaeSQfQ9ZK>N`W2x|TI7J|2IVO@uO3~(w5t8D= zt{O^_bc~3MBCyv~*llQH3T9?c%P-HLo>yLuJ8bJL&o5+TVctxwd`j_*ob2NA83hHU z_@wo$?406!Vhn4INKXg?Sq)E?=gq^{uZwa!KXj@USaZ=@l-j-7HXDV2;7_#j=TV`@ z)99%$M|E>qoLN~@ii>h`%g11sX_{v9)s6|r@E24IP>OPWbsl$}R#t$<3N5rOA600V z95xh;8hZ^&X1H*Ju#$>3WtR9fDJ8?{sIZn>JwDMgYaW*^e zK&yxHZAHJ(nLr;JEqp0m6b-u-eS&C5#FuMPNFWui&*8Rubq=sppn!&RAz{|)x^Ntn zSr=35>2=hEC>=c^#26IW^75)0x2s%u0a;&G>~=5q)t2Hr>8`3yPGnS`+ecrQw>Q|W z^|j$qp<g%+k9j3Zn1|9Mn z@+GLCU}>c4hp*z=pv$6jI{FeMHryu8LYb}_i^CO8_M$m=*-%d{i^B;hcV2riwKwa^ih@<&6-x;Ipmo&4JF)VpC#I7B$8@nR`S9VohYQYDibxE1Y zgQ%`uSY9*(MbqYRl~c>#(Q|o(kLFI}vVk#yMnzzW#Yz1=jZ^3=%yXv|;nT^}sS7A8 za5yox$fvI!qa(A2R0wKQ;LzH}Y!n7^B#qQM3Z2i3Q3twQ^n>As3x}xu4|4~~DO%_% z3^_2?vv@7CZpq>++V&;xI-3nXtHDs9jx9%5j{%9h7AdRr=Akv3LR~S{lu~rXXqvdN zLZ{W+SZgn@C=V<0V~NmUq2@{qtip{YZxpy;ZJpbP4vf6vL=iw&T!ku)f{bcflv^M= zGpe#;NW_K^AG=eylIWuFh`tp}RCtSAHhY5@Vrga5(TGY%mzTpa+^5J4fnU9DNF$jv z?wkeIVvM0@xhd4)^u;jm!~}$~AWQ6Zb#7gor>a4zECles$6d}NGe~&dI<*ZLGI(m; z=%hLud{LCkrdevac9NYK$ZU|1HLXw#H!#Z8H9=l{b{vC9uZGW(*QZj( z=2E50rp8LR63sRZr^Ay9J5OlKmslKKwE`%xUi*|>s>2>r>XmE=#{;oo6!s(=OP~d3Kvrv1Zi=@Em^HggM6?TmCVL1BcaE9{o z3Xg{?0F?msVe}4Xh>n^|8tA!btvdS=^g20ijDE{X8(mgW20J>68B1K$?8&;qf*mDV zG!HJDEQBIjF54JZcrEA^t<{UmD=m(Y(tts@y9$*-G+fZU#Ja>`^HztahFn zBKwEw7W?y;^eeX zbK4x14r&8^)B+in;XHe-w;JUO{g1A~!;fxPrK2ju`(@I19gRtua84-(6|$a+v0;e= z3BZ)bU8n2*3#=#RXQh~&g%<@>y%WbA9d!u>SJfq?i|US3SIaS^Iid9`w^Mha#>s3V z7jYgMG;q8a(%W6N-bTGqY8)$uJ(|s7$NUE6m4+cSMyw4H7(Gr5vUD|y^oA=fuohA0 zF57SE!UEBJad9d^*FWG9zR&nvJH=fyq?pSeQruC=OyCPHPR+V$wKV zPD^F+pJI2{*lA5fOYt<;&`LrIwdDdE?0R_SwOO3+{6VNZwpA-?B)Qr|tOTltd4UJ>|cYcGR zhF03KeN+6@`*{iuVY~w2tMZyQ35$*SiWE##f(+HZ1%)1uK&ZTWFH1qQVq~b7 z&jbEA&aPJWtM>vG9LI8Mf0|lfS8AstfJdzz_Gd*&ih@bJ(2DI!!cR&bPc+1D4*rse zieH@{uAn+ETyjetDol`G=fS(C9rmmD5fw~fhb#M4JPIy`Je~Ee?$vvX3O0dGKeC^A zl>LOG-%?!UQzft7Z&Xm7H?HDW@+$o;ET02NKlL7@g6h0;lE?1Oj`)@QjlfY7$v*Kb z35QfZLx1}Gr{}Ms@@AG-@Srl7m0{1l}9)S>cfU!j64{4Bsg$tmnzI(hY;rh<6< z81~cKzZO)AU-@6{?@{omaujPZGgM#$0-^Hiy-x-E=pxkJYMXTO9q)(od!p(6RZJ4< z+^Mw#O;v=eO~-qrdq?yD2SF-(icResZc3K+mvF|Y@~P}r<=`vGhQ_bnbKOfX1mc$@{&xOR@(O+n zQmA~#{^wLF-=xbhC8yx8I(fB^xoMn~H>-^TY=W7g0+C=NT`K+RIon!#;Sj%ua|P)5 zW9|sa0KJY+oT)mzlGi^|l3Jwh2G{@hbD3R0;J%&rATU84@?(48!a>~CA g^)jZr@SG>~r{Y&QBCE739KnA4UQxVGKzFPCe{0?W;s5{u literal 0 HcmV?d00001 diff --git a/st/st.1 b/st/st.1 new file mode 100644 index 0000000..39120b4 --- /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 0000000..abbbe4b --- /dev/null +++ b/st/st.c @@ -0,0 +1,2605 @@ +/* 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 + +/* 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)) + +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 */ + 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); +static void tscrolldown(int, int); +static void tsetattr(int *, int); +static void tsetchar(Rune, Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, 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(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 uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static 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(char *s) +{ + if ((s = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return s; +} + +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 (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[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; + 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 = &term.line[*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 (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[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 (!(term.line[*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 + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + 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 = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[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)]; + 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(char *line, char *cmd, 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); + 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)); + close(s); + close(m); +#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; + + 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 +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + 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; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+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; + } + + 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); + } 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, Glyph *attr, int x, int y) +{ + static 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); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(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(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, int *args, int narg) +{ + int alt, *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]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[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 +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 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset, here p = NULL */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + 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]; + 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); + } 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); + } 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) { + 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; + 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)); + + /* 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(term.line[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); + 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 0000000..3d351b6 --- /dev/null +++ b/st/st.h @@ -0,0 +1,125 @@ +/* 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 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(char *, char *, 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(char *); + +/* 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; diff --git a/st/st.info b/st/st.info new file mode 100644 index 0000000..8201ad6 --- /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..6dc68636236a069f0d3e12bf266a2d0cf2f6c171 GIT binary patch literal 73824 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 literal 0 HcmV?d00001 diff --git a/st/win.h b/st/win.h new file mode 100644 index 0000000..e6e4369 --- /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 0000000..eee7b0b --- /dev/null +++ b/st/x.c @@ -0,0 +1,2136 @@ +/* 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) + +/* 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 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(char *, double); +static void xloadsparefont(); +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_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); + xloadsparefont(); + cresize(0, 0); + + xunloadfonts(); + xloadfonts(usedfont, arg->f); + xloadsparefont(); + 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 >= 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, + DefaultDepth(xw.dpy, xw.scr)); + 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); + } + loaded = 1; +} + +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(char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((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 +xloadsparefont() +{ + FcPattern *fontpattern, *match; + FcResult result; + + /* add font2 to font cache as first 4 entries */ + if ( font2[0] == '-' ) + fontpattern = XftXlfdParse(font2, False, False); + else + fontpattern = FcNameParse((FcChar8 *)font2); + if ( fontpattern ) { + /* Allocate memory for the new cache entries. */ + frccap += 4; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + /* add Normal */ + match = FcFontMatch(NULL, fontpattern, &result); + if ( match ) + frc[frclen].font = XftFontOpenPattern(xw.dpy, match); + if ( frc[frclen].font ) { + frc[frclen].flags = FRC_NORMAL; + frclen++; + } else + FcPatternDestroy(match); + /* add Italic */ + FcPatternDel(fontpattern, FC_SLANT); + FcPatternAddInteger(fontpattern, FC_SLANT, FC_SLANT_ITALIC); + match = FcFontMatch(NULL, fontpattern, &result); + if ( match ) + frc[frclen].font = XftFontOpenPattern(xw.dpy, match); + if ( frc[frclen].font ) { + frc[frclen].flags = FRC_ITALIC; + frclen++; + } else + FcPatternDestroy(match); + /* add Italic Bold */ + FcPatternDel(fontpattern, FC_WEIGHT); + FcPatternAddInteger(fontpattern, FC_WEIGHT, FC_WEIGHT_BOLD); + match = FcFontMatch(NULL, fontpattern, &result); + if ( match ) + frc[frclen].font = XftFontOpenPattern(xw.dpy, match); + if ( frc[frclen].font ) { + frc[frclen].flags = FRC_ITALICBOLD; + frclen++; + } else + FcPatternDestroy(match); + /* add Bold */ + FcPatternDel(fontpattern, FC_SLANT); + FcPatternAddInteger(fontpattern, FC_SLANT, FC_SLANT_ROMAN); + match = FcFontMatch(NULL, fontpattern, &result); + if ( match ) + frc[frclen].font = XftFontOpenPattern(xw.dpy, match); + if ( frc[frclen].font ) { + frc[frclen].flags = FRC_BOLD; + frclen++; + } else + FcPatternDestroy(match); + FcPatternDestroy(fontpattern); + } +} + +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; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* spare font (font2) */ + xloadsparefont(); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + 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; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + 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)); + 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); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + 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); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + 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 '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.c.orig b/st/x.c.orig new file mode 100644 index 0000000..120e495 --- /dev/null +++ b/st/x.c.orig @@ -0,0 +1,2063 @@ +/* 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) + +/* 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 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(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_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 >= 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, + DefaultDepth(xw.dpy, xw.scr)); + 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); + } + loaded = 1; +} + +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(char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((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; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + 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; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + 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)); + 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); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + 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); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + 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 '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..efcf9e4e3f6221797c71ecffdf03b3fcd412c623 GIT binary patch literal 74744 zcmeF4dwdnu_2?%LAYjaiiW(K`K@K%2V1NLLV$C5ZFwp=}K%=4%@_W6N8E`Kt*%c+Iy{&l|{z%ckk!^KKGA1 zI%j6Sd+oK?Ui&$FCc{;E1+(Jg;vC-MoF6!SS43Fo49bs<4(LejSr7~+(|U23E^WOcaqF$V%P^_Lvnl@ zw&wUA_^{*O$@^Xyz13|!9utmsMRt7~4F!@5TJPTo*FD4C$Yy^*_GjVKL)j&1^`C@J zUy)ef6KXj<`cWv_+qJN_x3{^yKD8wrNXF|ta~*!hZM{6TcYcz|Mb8l9GB>g%?V^i= zi-H#iF9|N*4Av*PEykr?n6hvcVYj(Ae5MVvetN{nwVHzJ7d86S!zRcUSihjimZZ(7 zXsPj6k{kWnjegNjB5E@3M!Q`b{Ru26DRB^RPdCL@KMb;zbkW6|jg38@QkJZF(NEo0 zlR1-jn+szL6q+0TBtP0&>b4|~O~^*0E4BA$ux+T4CtFhN@N#5Uc|GM7yf%|^)0sKa{ zxh0)$#dF!$zmw&5d^M(H{|VzZxh0$2iuSwjy6a@Oqst$Cw&Tka#%*s+&WU^a35eoE zJI8GgA5vb?o}S*?Hhnr+8qEy2B`<**8|MRmbpQXRqHrMIE!ieh|ElAw6UKG6j?9UB z&J=NU|F}+0{aTxPK_JnMzUA$s?w7vp?OpdV7D0JM?}!oQ>$W8&l}Bd;%A;!oiTD%W z^8^*p?xb;_1fx%n`zjdiB#nEG@BHDhwvoCWyAs{F&gdu6uiWTUvYKFJpq7k6Jumlb zsmonRD7{~q7founI(_$Uk95SB`##?LneXIJKJy(G4y1*A_nL%*k#-E}{VeD^c|QcL z4P?UQhalJmSCxSb7b;3277XBaz$VsbzM^dq914tTe$IEjAL^3}rPMmJ*O=cL8k&lg z&JR&XV$|T1_{-G40=2PaZD1spDY`8o6o43@&TYx}yV0a*A?Qn85J>yXH~zWuXkjuO7X(JZSqh}H zAdms+KxkxJ9QbZz2%1zRF^~$)Cg7e9*87^~C7^xD0XMomt1ujO~V zo9No?oYj)4t88<7_+e`Dv$EHom{k9GbSo=39~3~ng^JwKSCLDecRSt&4;+apyDf*i z(bVX2tWb+gg?|2^3cbiwXb6pT#6>|H)U@uCzv=DG%dQM0`KfhJ)8axAx71{q z5;1+=L1mJg-u(i^i?pZOanUW=<956Uc{3G@DuwZJcOh7AK#O%1_E~yd>Rtgt+!bJb zEP@dw(TphdN5JJs=-HNwk=y7Ko!h$IccKTNkwBw{u>fm3PN?fB3S4RWg!AN1Y0CC~T3yTQnoI9pQDO+nwiovjzfN5OH> zDVpY-7yU;t@>D!rz6JZP5YAeLlt=at37-<&yEQR33={b@TY#9}B`vXd>`o}LD$$@X*xoTgy#4=@jf zgXdx3)VBJXt~aUWQEEB92lFVknQ%&}LC>6LQk&(rRtA<}YB~AQE^m*ON7e==#`~Ib zK{=G2KRnz82SlPR}vpYh*Ii^*F z;=Y8cJgT`p`c%-DxAzgy?bv>9=zeJ2p+MTK)*1d-#9;QewNnSu=bK|w5W4KN5of=T z?t@mjHZTf0x;LSt3q|)Pm`*sRHv1`G>n;pwp?VB;_mdD*zqAm}C;IiGSPNYky5MjY zjH9MKW1-D04@FA@1?WdNXeQXuk%Xe(%H|K96^yhCutB~dIk+py*IW-4z*(cO`6f6s zvpOl{w%BGG>jiwx_kx_eZVR?^(GgI^?miJZBQ$dsw3#m3PG&~CVvQ%KKOM_a&&qlz3dbTe97)c>27B3m3W_-F}=|cZA|T`OfI$AiC|P z?QLR)3%#2gg?okQ^U=SH{um}?liJRv3G-R z9qdfPfcURqS_KUTCbK+Bz)j+o46GRTRtRtHO!8(*Y;U}x-wbs765ZVVVR)k3;xz7v zb79sHePuwYH)Go1ZVRQ~{kEsm+(7o-UkA-tSlYUWb~qEhh<8L^j1m?F58wi=)<8kCZ|nO$%LO|JUrf z(AtCb+0&lw|AY3t1ZjbPa0c&b4}|>>`6pZiwqRG$XA2hyv?9EFr@7W?&|FeYD{`ae zcGcE`jM=t9}KYA5Pchls6L_m=vJuBzNT-{mFiwNn(H@VpBOj8ExKI0;Tb$e#>EI6 z4?G1<00a9q#b}4yQs>9%akAS=lW)n!hJ3Xv`8 zb})onGFx7Dx$8N;Ea-3}DgJZCP4^kaBcVu?1ie+ z{4Y!b7Cj&Z(>eOCd6h`tGRXLLdyCY6afGQ#YQRpjFWp;V)`976J2)SYY{!D?-vtYc zmr~5%4G_2d1z4PWz*FpRqnQ?JaR$HmcIiB$yVmezfJ>yh1yOu$>R zDsmc78&CYymzo&()OVP1E?m&jUYnanSa#{aT5v*8tDst$p4AB-gX>Z_<4dL#r~j#u&A~%t zQ(|=fs7QNaG&haQ>7#Y0-nH5;y%X@ zG^LMh@pD&$_Y!X3ENCc{Vc|u6Hxlgl?Ahtz!}~6>!qZJ*N3P27*TZ7M>>M|;A0BPN z?~wX`+ox#X^t=`^l``H=T53P*?d^Hm__}WvBNnot@#aSl^s5ihe*5)sVu>vuRubSA zHM0MNaB^h-sQNSJv{VMtdxqd4YYyDkjDH;5<`lHToniM`P!3$bM*%&kUG~4D@-=s3 zgvgdGal4HoN$w%gbNHIi#o%>gF@fIRCwZCs5q!iqfV;cL7;)nZ$H{>fX->Vw^GjYB zKuOOPZfir_Q_i7ymp1OJCq_Wpw%$U^d)HR1I9RK}S>-Db=9Yxv_Q!WG+;zGgA0?O@ zOV`{Fqtz^*?81UcDlC|6!YHv|w*;m_5E*ma(KW=4+sbl(1-an)w$JJN9c8LuPT{3a ztPY|pqTK~iSW>o=3{iIA*$f=YE1=&9!b009U(>JPv>-c-j}@MSQ)7x-vdCDm2(9Qz zaO1x2?u9T@Bc2$F2Ft0~A<*4;1dV~9fxX(D01Ay8=fkYstFA>Z&vNR0FrQ6#vp0p4 zTxf^k_={lP`DWJ)7{+=&H#gZ?ph4H(u2Z2Ggo!#xo`q*mwd5AIoF3^NYCRsZ?LN4# z9iJK350CT?@io;!ZE2am03LitHqD598Sk6-Or$r_*Yq;v*a+aHUBt|RCC4Vb2_!YX zrXDN#&`LNyG6#Nqd7Iqm``BR=hFa&wgYsZzrG|&wjxZtIVf)iahd*kB?Tvf) zX;3KHrfSr0>iMtep{S>yz%d|T7Z|WB#LVSDPMWI$x1Q4uW#GHI1kQVwuKVT<5b*s3 zpN6md)}H6XIiv{LfDDg@438c-!=@f}W8LXX>fwIrVU-KDCO1P8PRIU)$S!zR@=WCI z#NnHQk+)K3wkD2<>*!wBSB+Xuh;^;er);yM`SnGcO+A5Uk^SolJc+~!$qA4E^rYs| z6Wlz_hGipI`i2vzSfN0U+j72(i~EIc z^c1P-rMn&E1_}$gL-2#p!#Gw_jmpizC`DM^y4QgFJ#)}9ADm$-j%jzve38AW zewOL&PKVfh&*!$B4Ib;WqHrOnpEPjN-$KE8&5*i>pk})N3qbsXhK=rbxZL5*5_nLs z9)z%!Ma~=^9#4z9`1->TlW7{{H$~q5#;zLe-Qur@VI1F)u)SlSY`>8+YjsVg(;_&HZi{xqBSVN|a@YJ}{SlZKx+%9&d$zeP7xA+%m_)zP z^${f1^Ab8~pQ$)$?a*Ni-8;K5F6^^6P6g<7bV}D9u-4i0Irhv4$sS|N0!+CSOo1VA zXyoz*F#f^Rr5a=Hi-ms!8SUx91Z`EIDY9ArrKccPw*LSHWdcz=YbDG+Ok3dzH!h2q z4z=G>KIVdM;Grw(-|AsW=%8F>8V{g@H@qrx1=C-csm3O0zUKX=o^A1)<*KeLzl0pV z2J6V?$+#&Dw*}|SxZnXDz$w;;&rk^7og>=xsqYd~CZGC> zI)abEh~&Uco#~k2CVzX~Z1)t~)+^ejrtR)2BxBWdNHtPH(07A*y!e@~+F0Z}%pBly zRKR!G@sJ#JBX4$%euan3PhZAb7K*+d{mA61`*Ut?{icl#{YKs8liF=t$?-LR4l46; z?c3Z(Ve6WT%doI~1rLC+!%KTO2Sf@;zwN^+;emjC9jl=Aw~f#MddA}t4?vin*xB_X zHkMs>!dN}q?TcFPgumxzuSlx@B-m091AW(DAO~<$__o`6d0OxM)SmLZmX*+ff74r^ zVS9rhRHVpziNWZ$;5r!B7aj_y@!`M2gfZC(PxU>JdrW)Ru%`CzpIO}P-pe%udkeQ^wZC6GY`NUN#{uJ9dS4$)<8cz^Eoy2AWOyh& zSFm$*TS%=%2ckQFxo^!#e*6&WN`%W)(>p@O=e`aD<-D%wM^Jmg(@u0Jy*^|Y4Se^S z;mb~L2AU32^^Co=G0aW41{H-`aBI%Ga~YsIXNPH8oUA{ z%{3hwu!PYzw{t$$>qA+v{@EQP% z7YuQXy!$+?zAu2+pPS1Kk;jbWuB$ipNfM|Ax_p{nus$tIP*iM zO;vl+s?zk13u8uPc^Z-NokoBG+d&kI^?R^73}3rt2`&Pp;Upr}9S&7eW*l$ci=Z8J~MKC?Mz|JyQ)HPWG-93ZX0~@94Jp9g554Yti0uQj z&z}*7dFJkRTJOwRgR#baJQaIjWQzmK1YbsU z?aPF#rsDEh-;0ALS1@Y73t5Fu@`e1C%-8Ux?}xb(VEmpZ?Aib%Xx_hpsutPp?^lX2 zna2UrY3^-Ec1zxLPd8P0)lt}daN?dkHXTCi@pjjb3rBBv)k2EVO*YbV;g`&NHsCD# z2Gw`?%=#GS%!x2(PE0eo?77MKK#bRs-EJ`YZS*rYIufc2^kSjt*1YJBel};B7Xg#a zi-2FogT@VkMlcfJ3;ZBH6#Yktr(T=zwYWY0=I27bye^PyvT{M+maXx=ad4CG!h7&A zXugGueXyL^l?eBE*3LT{ea(}>G4N{e`>pYJ`omWRBmeZ<=|c1k=)H{LFd9vxbp%j< z=N_;Edvch=K_zv~Js4E?UUzh-dno>m3HC2Z-DqrIthQ9B3OE`K6f3gbXX5nClZpD8 zP_;Gmanu`aH?P^arWds-KI0CDr85|N;FY<)Ito^%`3_qY@Y~rdye)SGM7NqE`-l2| zat|nm1##a`wwjapD@#H)wDs&bev8s~@19`mdGYqP!o1DIy$QsDoWbHxtPAipUx)dQ zY;naDo-Fr)bT>|=o7)?nhL;(w10XNMyG*9xZO=fU97^kbD6K#M3o9x5GQQ8%CZzl$Neoybr!Rw$ex1Npfewy*WF9Q$Z?V`+pS>UrK zMqj2p!+ZATjnWq*n;_p%!uCC?P-_k>F!X!foK`~kLc=L*;ywGC}(0Q7cc5qFp$0s;BundAh*irqzY@aRV$gVhf zuMye3JQ)1~{saV?T~;vq59q#T6xhMP08pV@@`mXxAKl~*ht(746GDKpLeNkb;h!(zOL}VqgN;WmGClf z3I3VJ0r)eGH(L^*8!c+dO@c#esHH9$(u2QjphXSn^mlQmzpMGfE5|`z{n;=X^xe2A zbUGyKyRkiKldl=yXeke!UJBl8z5~vCn(BYlos7NFhgZSlz<#f;L)M@t@@)ZO$ZKgJ z6+k6u95+4ynFP1vyO{kuUvNA8B?{+y)4(48s1wq?0cR?7-DOh%8zVgZFh*es4RrP-jx5%S7I;V1W}tONQZVvi z96a1eGH<#)2QoB6Y|NVAYu*Icz6T29nx6ADJpkv8Urh+ZY-}OGnQ@PTF%GO;O>?7N zJu4d<;+^_;-N;s>vHMyG>oWtR;QZ0|K;p4Y@HAvb5)ABJ3ucV*U2_tg!k>iZmn1e7 z#O>_+3IMQA+5{dQAv(5ym>A(m7b%!&fG@#39u4z zeQHw`rY}Y>Sa`98A{gb^Xu>#W_r<~SvDHTKh4#DFF1%ssN`0B{ zqTYKRW{}22nUGW(eI+)yC)%eew9M9;muStgqvzg^RCvX?#OmsJZy4!w6COm{T2`fv zd(n<9tqYF&mLAJ=_%X_n#;qN1LlkrW>L&cuL^*fVxJ|a{w1(oprL=5*`eummnsltt zO<3P81l_;*HjV)=+Iao^hYOTPn`xN6=Q3uPmp$u`9LOhI3tGma%m87wM>uEsvFXtI z7F-$!59nzgN7EjgGg@8jKLca)VZD6G!!WnDPl?^=OP{%SLw5jAC!Yl);~<;;CT<+f z)`h-5MElRyG>tpRDbks6i*aymz(oKP0saEpl#qD!h5OL~qLqB@cD$cxYG%w>TfpRp z0pj#4pso#2Pr(JLSg|AUO!JAGz&-sdD%|72lh=c7h6f2S7qnID;9$4~Jre(vBhCIr z$FcS>3Q~i3{=wrxF=Omf{LlN_JB#w7C`r8)Yih<`ueu+9q{K| z|A951f>_tt9RELv(18(-{cl8Aj}f|`gddxmK^?RhBRurqh%gZ&Toa2xbxN=CcRvDe zopvn&vFNrPtBM#c}nKgY16Z^Pdk0a z89z8PCpa@Vls9X(n}62X1#{-kD?I1i`3n}FcYe_Y7hbgJ;!74UDPCGqT2{VndBvqG zDpyuj*IZUx7p`Bmx?#-^FJF7bmGA`+$0>vz7c-Jz$7UzD+=BeVne&3@hMbb+#kF;1 zVW+O7xUzU@Wtme~SzHx%R+m*QTOM}GtE<9(L;R)HWp)0l>ac%hakymp2peR@4hkzN zt~x2~udXSp@*53f>qa;=6%A#TbrnB^6!53CtS($zy(YJ~vT|v0$qJ{graD|uT~Zvb zsIGEKDl5vW!sl01l~%8IDynMg!}G&yz_$Fkx#v2$l@&EhtBY$({ScR8_{)ncD$7bo zILBQyIV0mrbC`sO8DmRNH~)_J3OX+<~~4%b#Jtq+&gIdx%YY@I)2 zV%Ee=c%@MIp|ql|rm}dAR9I0}0cnAUORB5NE0#?h;fyKH%FfQpbjD21!vCDbMR{{( z=7pT|=PVB8733|*TYP^0+|a!9oyBwW7AyujH#jFRc9EYuZ*G55m>+T$E|`_Ic>aQO z^XJZX##YulRs#fptImR-NvBVC;eb1OY+eJL1mTE#Eu7DUBko~1he-2qUj^s6aD@G$ zsi#jyM^D9{JlL(ysk7kpY}i3Po@Qe3G%Gp{&nMgS%t|;fFxN&y=5jnofjO{GIo-g? zsA!6fP>81xKJQZ4SHM2UITU_Z!d?Y?HS9I8Uj};}?Dep(hJ6i015+SVaF}8bnf5T% z9;VsDbbH9Mhslt0j9?F$_Au2RrrE=Ed&shf$>>*zU=Nx0Fx4KW*~4^u$g+pY=tGEL z51IBb)gGqV!*qMd!o!8mafdorImf~O;CbVnY348w9RoSEhpF~3%^s%MLna*Nonyu9 zVX8e$vxh16kO_x*=UOp)m}(Ex>|u&MWWr${HX%sV9;VvEG<%q04#vVsR7c>**QO4@ zH5TPXlcw8WTf&p3lhpLnt<-dq$}&<}B$Y){StON3QrSi-o20TyDx0LTt<+@dBdpZq z3@ZgkTQ@UE%5))cZhQi(}?M$6iz(o&_EEJQ(2$ zi^JivS{Ufe2;{FTD_c=fwG1xpz-C-!hZ68?u%@Q6%%5upki3=EmsXq>8y3%)H0?AP zc`J%5D~jvR2-nt^omN~Qu3lbI6*gC@Gn^uDcrR@IQdRK}3a9wLAUPe*_IQP}eV*Z@;Fp8w_X=nIp5dhb=Y#0?3TOSE z;iUhjLG*itvwqKT(tk7Qr^|jth!-5L%ZCO3%fmhnzm>xA4Su;1cD%yb9uDVg)}iC; zaK7gFUiRNYiddfY^O3{7;{VDcKI`)eXZtza%RgS>TEDhW>(}8N|JNkwmA+TFj?dTZ zH%_19>u|p2aBZKCufsY1T_mW(@!1bb;~gg-Dy_=AIlKQu`A!-IsMOW|B!Y^~_~<#1d(8ffEi_&Uvi8;9%m z!@?YH)>3{KMg1{^4*h|8TgMe>mLBKc3;_pZSCM zhr_-6!{J{3;czeiaJZL$INZxW9PZ^G4)^kpXE^!C|E+DPba_weA#g#TlZ@P7^x-Z@D4vx9^`H%R#N zgM|NUknk4<34d{r@RtS&-#JM5%Y%f!GD!HVgM_~}Ncii6gugLJ_?v@-?;0c==e4|+ z>WGco8y>yd7K&s1?+VBGUia=C|GUCb=DRjbAH!F89=j-QTEd(8QXJvQbrZ+B+r!Ycsjv*#`x=bw}@ zaq>iZC~p4290%SH{NcGyTtjNyammAy)B^?An5F*S>Ny5DDAw4Vb7)nWvFHcDt52xl+5O7?Md{#>8 z)$zfUv`9iI#UFp}h?KNoN@_4ADR{{2l%&m1p`I+N00EQ-J&RM4>^qmlZ^a|VUOEEf znXpOg|O=e8~>4o0$cpM=W&QW%;a>npr6g!IZ|h zl_^UWjiAbj`Z_62DV(FeOGqDH@20RQ<^Ma|P)+<#J!(Wka$HKoEJ%A+%C)eshyA9w znJF9NDpG1pJzs!z7&h~#QO54ej1`!t8VVbMHpsFsjK4jlD44ROG^KF4s05g&+bPcX z;N1FQ5!58e9_;ND_6qqZR+k#%;-3+EE+kt^!0z}(a1jKQ+e-S2xbtj#|P2U9NKS15dh%Y;+>KK2NQe5n3uuPUvnV9mYusjSizFKKQvHjdi zVN);+;#Gk$$p1ooHgTxNrcLZ1z7R8YP#w*QU(mmbntsB;Kl6a?*#D$Xw8bTigL6?H`O@3CGvch&>Nnzj9W#vyeGLHLLki&X%6UjDV`9fa4qpr?Q*^^-W zvV-EB3{jj5Q&Q(b%4pNO#D9m}*aUSA$`!*L_&Yk>=Hn0a*Q-R?jG{2=ul@0PHW14o zi^7hu@l0CC3kTqs_vOUDL3vd+@n3Mq={SA}-axXyKv`pNC?#VJw{qLRn~xYna4oj! z@Agj!YI(v--%)OUP>}@wz-=S`P@0AD8)Aw20goRgy$PU6Z`jqu3q3gRzzv@l#)14synxMbejNwrfhf;%V~HP4 z-1r*KnWH~Zej&;6JTe`wQ9t~cjpuErz!l1uTkMRc<22&e&@~migX1mA|AaWtHD?mP zllXQz&V?gdkKYc&ZKL_MKMAqBl|FbXlsoFLAv;%45Z+Iq{q1y}MaOKC$A9wyx8?R= zeiOKp7VHu@NByQv!FhnfN{KI{3S#D=z)C=WoHN|u0mFG7+UX}f z`$>Kc$)8K|X1)rnmE^A@ISMv&dDL^Q;yBJCUr#)Vj=zQ@<_rCYo0-4D@qXBmzhEYl z);^ zotd1`oDcX~;!hAa^F82rpM&`gz&UPQPg)J{>wlR4g5>>1x=$YKHTrWq1yC{$elZXE zd&JGW5aQxL`G&mxM61Zm^WgNa#4j3S`38G1pC(56kBOVP8-zVY^2bpI@<{%l#Fr2^ zb2bRW`!LjV1M%4;|2FYgh?_YZgyDzsQU28LTR}4~gVTAi$C=*M@BHu91%A0u?oF)Uu@>*hXtp1E7?kav9@f^hi zhEt=4FhALk|7ruKn>Nu#FmpFJ9Z!5FaWi)Vo=N%}t$56yGf2LRfu7-JRT)kar2wNlSRSuAWPWZn}{XSak0^p;4~^;O8N_lv;LLDxu0T= zVhPZ1540u3M$`VG38J3JkOcGBppw}01hcX;5a&_1e^`QJX1)C(T-jN8f4my<)wi10 z7q-^$p?!eP))}695Wku@`K_N(OuUHdCG%gC9_scO zJGT=b9V7PS55&1%aqzvwa~aU_uZAD&=SNAtPRVx=ze4eC#1||6chWEG6N)@fTzmRu z;xxR(w7p6Ce-tD3mm7{DS7;-U$H*mI4`$-#P5+1&c7l3+m-%=1QZ|HizUcn$+P z))VJ`kAp8K?lPd`I>U!JCo6ldF?^`wDu1?;{A?wE1Mzaje@S|lD?Ps^d7O{P?GEC< zR{U<_zgPS|;y53X+k?d06@Q%g-xc3X{7uE5Cf=j?KZ);C`~~7)EB-3+MT);g`~t<_ zCyw((xqV3da>Y?5!O^|5<9r74x=%vc;n499#8W)@Q6Bt64?fj{yB_=;;D^Dif%{4B zU(ffDzsQ5H^x%yi{HMSV1J7NHPJs>y4}O&gzrlmw?7{!&!T;jHANAn>@Zhg{@Q*zBUJsry zY~b=b+Jlew;4^_A2KNi?w0_0?6@Eu0JotGY9M|dxvcKAcul3-q9{ffR zewzos*MmRq!MAzvmp%Bq9{dvzj?WDS@(qO2WP#9{5>B0Q4hX@^q)7izy23JOq`0xDIk73aelUSKJiD0 z`sSIJ0Us#8t3CKS4}PNuzuAM|=E48u!5{bFJ3RRF z9{gPo-s8dddGH~J4D7cfJoxb*e7pxg&4U+s@C!Y7xd*TJ;LRRt;%CFb*eE9;h5*M>{#!*xyrd_Bl9Uz2hgDyxf2 z?T4lsN-E2WYn{>(r(tFBin3*uYigF`hw$oZ%1Y{-@>&S4!b1tDhlA5l3g7xsQhm%R zYpAKNE5o$H^|i~&s!G;?%`0OzSFEh4stcQMD}_M{&8Vv>t}Vk9?WH*@t}CgiSX@&J zA6zmYLNtQLD#$=tsX4P>E-WvuuMEeoAu0UQq0@k$YN~UpYr>21Gg#)td@{=^Sy@(x zAJoF{3&Ix^;S)*l<-qD1IyIiFEvo^SIn@mlCpp!XrSLJqFnkBGdJVj&37vHt((v_eT#Fy5>hnR}N69D1ENRV0lijzHC3g0TMt8-S+p$uG9 zUxltPd52FznXfX|)sx?qm}eKBoqHCL77gM#E&IVY1NxA5X(j z6BoY0i03Bm6ccxfi95v_YvRI3*Fa{9i95x_onqonF>y1Ee5RE&dNPfCrqPyZVr3d_ znI=}I(U584PBnb0k(_EIr<%~IMsli^F*>IinQ11iX(rY*lj1a!)>LbQk(_Gy)J$jb z?1FhSg9VG{&6+hIJ|?*!II|#cF|@WaY)IkS()t>wC?CEJR$CPeL#g7I$L8~QzUHm2 zDywyhW*>y0;@PGRA4^-6;gnXC;iuBf_s)X&vCg^JF!~YmEi-5FVrZZxD;Ae5U$GcJ zqz0b{wT)~UN{68Jp9>%6WV+PQT=;TnepP8%gEOmyKEf8Nuc@qnZ-|xw!eFr3egLiS zI#^nYA$@TOfM`E9vl{w8&|*H#Ilq2s9n@s_!Y81RHLV|;1zibfn1i229pK||eVXYL zZK&DW*9VLa>;xL%`)uee@Jd;2ZB_MRIJe(Lg>nH>4X(FN!TdoBN8c1zEvtm@k5a&F z>`{$E=usJxv{PG#-KiXY_w!#z$#f3yd)%ExZTZmIsSzgN4_37qt{BB?V98;(HYkE%6dBwJ_%!MAd zth4|!-}m`Er)XhS?RPo^Jhjqv`$eXr&xeshy0#Gd`?A__1r!nV=g^fdgZ5R{P=RUS zRoVIGD~RGtvPB_iZQ(MCxW-vowz6b-EylL_#@1&e#YF0=%;;5)8LKMI!>{nM`j|{P zi!7|F183*XGD8WLKc7PzHXqR|b(WQd;Q~}tSFHl$W6d9i6__V1hTw1ov}cfk)(aK4 zXywv^>gpBsHS^(nxK+?d`(4#T*}>RcU1|ylt4wYs4%@c+fC1bTppu)xs0b@!VFgSP zpewRv!&kQYThF0OI0jf+1&skeMOY6b3{r)|DcK8%Q&qOwMlCLNiVE$g z44p81&Cz@hxoB2pJ$w-u91t$8s5RKBLDxVx1k;y_nv&`o7(*c#UBq)6(*R9(XBrWh zZW=O*nVFBNA!g=MQeRsK4cTghp%6tX@MDbljZCKqorU!ttKoubJd2f}ZL9epYkSlA zWu}G1!ch}xfuQr}7%Aw|p@dAkH_4k3y`daFL0q@o_6E6-pCI%+rd7f*|99ZWT# zSr;$Go(`g5--a0mQwz-Loa)kw@`|!rXed}hP|0d4pn$N+hU_<#9cUV6beL~-@#T3H zb@j#A>*T|Sm19DtG_VRmhZ2T9z3ANPYW}n|#|)L#grVQIb<8#@zM7ru6hXzQjP09hCj`BN59^Zq*4H>@kZ~*@>4z`Gr-;P4CU9b2Si(~Ispq`DevmSo#%zof> z?-qI%3Hd(@j=B!Oj@yI8k=+J6>&N%qaAW<+bj{DBnX{hfgr46(Jl6BE(!=?}_tbF1 zbftgzc{J;1`5_2kW6t&*ra0Sk6miu58`#;N6FuZl67s(l@)MOjr#nN)-!A0O67qKl zzCdtX%VT>k5qf?n_^*UK<^kU?#qAEo!^H1Voc(a0(Eodg&*{D)Zb!aE2Yu_)h>`swxFX>0wKL|b&0c@;i6xnyI;@lp`2!0pD<9uZa zj;EEBuQQ0_;cg+1&-rn~%E^>;1TKzVQ7<+(L@8 z-|~s0x~X#x6#OB<@!w>_js3%R zzMwd#+pRdu?^B%fb%40e*RZ64)5YhsxUoH)?rDm1x_1yq{SU()%de7eLZz^6Rh;!a zBlJ81a;)cB5BXPx{G&qtZNVQCe7DfERPen*{&#|(G8`^o!+gnp`v&6dAMPjbRQw(j z+j0J>_}v&0wzn0x?+-i97mB|_@BM#aaLN z6yHelCo9hSXDEI<$rmYp5AjPBXZT zp*XkG2L-=J*!i^JvYj540z$BHe%b!x6=(YcinIOWi0gJSPswwAt5BThnGwa=|JMrr zvYy#ztspm9YW8K zl|1YDsp72X=R(gWq32hEZx;L^rN<>Z-&CCSzbo`?5qf%*d@1SKBjjbeBU2$VZ0Mg7 z*xAp=5XbtnRq#`UJcc#Fj$5V&pQ-qDR@|8_^vik3To3srLjEbx!FEV&hncCFBSGbLmYj%P4E{z2?H{Q)`{x+qs6+Z^jFM;llN4wDXDiO~=L%jX@^v9`?VoZF`6?kV{nIG8 z^v_R(9_gQ(6=(ZzSDgLx2ccj3=YA#6`R!1g^}nh(%fBsnsqoJS#I=9+ddPn(2{>c{lrGIjjJnKJ4an`>~ah9(Z`sMxLdJq0z9{es3 z{-6hcLU1`wy(jpeM0pKA0xn=fKg;!m!-=z>cT>N8jN)f}^=h66Ki`9|CXVGR{kc}iOMl)f@_a(O|>%XGgG@-p3_^Z*9!UncY)LLB`g(>+pf*)DPgm-C86LjQ81ze4Z| z!7mefm`o1OTQg*l#QtC=MltFemm@3504k}7-6vW4gS~yh89&VP~DtBh$UgL;e~eFVp?0kjHfK@9uH?osgIP!*han zf*kwpU7=@%;2#P3-w7UnG+e-j$;tXTf;igpEbOfRI3bT=T;Ilc@KY48ggCgJDY$G$ zvx)0;&lU2hgYCIMaB0s{!B-0TD#2xbR}n`YvOQiQS%RY; zUVk`8@LG}X#e&xfUMe{1VLw+9NA`k{uUGO5$j)ZLrJsK;IQBj4hg*bx>F2))F6&RX z(1SK{{rO19zbN!4(|Z%_=S&F0?P$SYf@`+tM8Q#iDGI|jL2xO5C2^G92|Me#UdUtE zov`Ef3&n4@*x9H!kJo=toX7ngit~F9U5fMj4f_@UoXRVXJ~+bu;qOO`Q2YSNU#vLS zw^GHqTq=m84_=0y{ZlWvTz6V8xGeWul>PwObEo3tiT_ja`^nEcJ@^O2(au+dou3Q- zs^FtXS%v7gdco5LM_ufP3B-}zCgjV6ylgM41YafOFBf`bd-5ABNcdqO)D z=W={r@dKa>x1GdM_iM0oIlimpIo*#H=XAeToYRe`Km6i)cmNZHZ5VN#?qN!v^`t4z zdX5)*UI)Ew|1`nh5Im^#{1uIXZH1736Rug$WghZB7V@&5zpmuj{&y8;`@4mnT_WAA z@4*FZ=(p9dvpr`JN54rs=L`7;Az$Pnzf8!ZF1E8uakjHg=y_e}zd~@?u3LqkMM8dq z;FvD!|E16)+tHUw{x1**w<#ysBiAeDVZqmc2>UHU9MgPD@Lvl4rr?hVJs8IiJ8qj4 z=X75X9M>7Xwy){+zM(kV^S1E-wL1~FkRN4B)HU{qB!e6MsU=# zpX@Ia{D;60(hAusDmr{I4BU95kX;BN{(do*0YhWWi5c9x$jINHSZ zYCdsfw+Z=jCI1+N;r25j|2AB+{>MDzdxZQuLVoy(sLk5B2#%~LRd8wNk;Ju~ekH%f zXoY!;;yZ}v3O&-F<$_B)uM&FJ3OlbBT#jGsg`RhX{H-4He-!)*A^&HgM~-)o2`=l& zi$V|ji07rdg#5pS{`ZtTx6@BNc>EZM3|lyWIH&uj;(R~;Z^bu~ox2qu8D|4NQ=I+0pE%n2 zDeP=#uZR55aaJFf*KX2(oZ@#{F=w>m?6-8qx!;>W9PRl`*fU+pbH09{cqQ4BEA)IW z^yDjfwsW51Y-gq7qe%Z{LjNN|e}jkom5OhKIJh+_&gC8z`o91IOl7d z;#}@Kg#Nuk|A!v(p9=YXLcUk<{elNhh6~uZo-DGL&MAtAh))(g0*v#UEjZSR86^J$ z;>f-f@=pl)O2MBOe1YJv3q6ks{=SfJ68sY(|2x5*Q{VzNwC4lZ+5S<2e8wI~k@M{FWgE;E{M(}%tJf{u8&V54uTOq$m$X_e?KLkfTv3}cw zzbEwf3O(@?-~u-Er?mfE;;7q!K8F3XSnx+7i1{5tkIdIyLSELNEkZsj^t|c8KNh@E z$Pb+e7qFrIvV4yh9PPOsgI9bHBoZISbb)k>bz z{gLAQ{Q7#u`MK)Niu3d3-zm<|e;-kNE!qD%akR4?b}nD%R4c&!L#4fRk_4|3^3#c< zo<9hFk&wr*FzmQp?IHhwlBe4)r(JP=uXvl{0W=u4*A?gYgx^&>Oyg;f;#`hMB%l8x|&*|m}d0EeYAvpdUIqc`>m7c%Y>^L6_eity7|I$NG;^cw- zaGD3NB98fz_N*21GTplb$2gqdhZX1h%BK{6$eQQ8t9Tpnj}*U$c+wPm!sW~J=R=93 zEqBAt{(-OTm<{Do2lLwn$2hG2e#P0IrxoXP|3O@*`?8W}J#Q<{=?=>@X2$vf)}KsV z>pxt{vz~zBobKt0v;G_p{j-!j>%Ty8)?cGI>#z6FzgEe!o@*87bbqTj>%Y@O|DOey z?e00HXS{X0^M&Hv-oFxhMu>KrIJJLyB|{i)hbYc^Qi)?Z;=d8V_3&sVKZEp)R-E;x zE6(~S2>mF_`lk!N8PaBcuHe!?wMswx=SIaj-J69T>7Uz`JllV_;;g4l=t&XwKd9t0 z$v+*6v;OUhv;Mye{n9@#2`>Hfso>H-`<4E`V4=fy;xv2Yeu>BB>!w?d=DAAO+p^~2CmQl6ddC$AV2I^j%mIo z?7URTvpuU6=lUP@;I|0l79%oaC=N~u2;Q^e@^)wewvNP{>OiB3%4T_-%Wfp zam;TTF!ui`LjDd2V?JB(d4jJO{5a6V&vV}r9MdR;9k(H;+aogU8(9DMh_n5Rte7)a zaa!(hrU>2&vaII}q5pWHKja~wFXZvx7-KyPggkaJ_-qWfOB8Q36!Pc6FID_K zE%62qeg$##8~$5-Y-hwnKB{;l>Azm+@e4h-3O-u!-wHjLHn*4iJ@`XH&xt}$yOQU6 zxJ_}^zeDI5BlJA4~RPx-u@)T$N1&Xu&bAfQ#kroZQJm|;Rf=;xj4IA{UQZnT zIZoJlqmoaCFx=V|XFXem9{e}IxLkH9d7ke*uQ=;@MRE4?$BMImzEGU~b3k#nKkf%M zADG|IU}rm%h;w}#N%>6?T(+ZQ1V>%mj>ag?`8`>2&hM$jQHPx0W-EElZ%}d0?|j8M z-3t}xd=)Fs_LLJxdrlGMSSz?(KU|~q;G71xM#Z_kU!yp;uj>_OJ8x8+?Yx;d+Bsg> zd7F~2Bzx{uob_z=;LiyC6NLVklswm~Hxy_6?+85;g`SUv{7)bcTo3mOj(KCheWN(* zk3SQHP!9G}h5nO>V|`u^JL{h)xYR#Yan^sDhyGd0j^5c|zi0sc-ob4=Bob6ml9QD5gJKJ-SlIQtksgR#6@>QecIo;KY zbGmDVo+(1lwI1@VLLTb^+qpr>|I4x4uY~*ufLQ(x;#^+uT5;!I5BUd_d^^c^D88Ne zvx5H|7^k~aa9O^u6W8VYxsrbjbm5i|v`4m|{X9bOUw{awJBm2!e@pOFg#1l{X9)SJ zaeaEG3O-HnMa0=2zHeF~A&-Y>6(9k&gNHyY|Vw+N1T;Q8C_#F0&p>(hUi zl0N`pxIHZ7r9Dq7`Q0S{f#M;n#`)4i&v7%Y0Q+GE$)8Ld?Ud;jDEUm1zsN)W3MD_D zQ4M_mVC$E{SzZ-Hx0*95{3+K>BU z$|uIfjrrTelPr$$_lPGe{sHk+#XllGQt?lTrzyUd_$bA>Jp77(OY#B5<0=2?iVr2u z9W3U!?eH#Fr?3G3hB)d?v{+SN!KBU#WN&@fyYFlAf^Q=MryF z{5;}o6~B;pqvCH;x=o5NCHZR=&nJGJ;-iRj{f~q2c-T+YXZtK*zp;Ed$*ouN_)ZvZZT1k;lSc0e?NWT* z_bk|>_sI17@{5ZKtB4aEI|r$RV?Z#`Y{n}?X_4&IyKh6j}Y4e>0+?;@U~ z_@9Zpia$b}zjM#}HxVyV^1q<#C5k^o^2-%}j(CmYuMlrg{B7cmit~4lHtSc$?zK6Yo@fH1R!( zk0qX}@->clhT;>57b-r9c#YyyiLX~Yn|PbzXAtjHJV<rTa&)Ab(3tLZv5!4?eL6DFRa_~pb46>lV7qxdz% z*DHP<@ixUdpPh>Tg5>uoek<|R#Qyf*Mm$4t{`UY16~Bk%YZSka_W5hcZ z?;yTM@vX#DseQBmpC+E6_}_^aD*jL6HHtq^e7)i?5pPrcRpOnBze#+L;<5Hi{Se#F z-<{4-oZD%k;_s9G8pXLiu2;N=?N^-prB1~^X8RTYjCks>{`Kc8;u(s^QT;1aJc)RX z;y&W*6+eo2o8re4@6?>_SDgFR)TI9Q2S`tb;-?TVRD3e=8pX4TuU9;Wc$?z0iFYbK zm-rsVxt~uR-rs)i=Q9*vNO}qtUqrk{@e<F0mF~BA{CbA;WGK$_qC&-a zUR0y_v+RGx`QQ6#Q~WiyU-5T{?@{~%;;A_4gpKY0gm{MH`-vASet>w5;t4cgS+DpA z;%$l_M!Zw;G~#;{KY@7aVg2nNLp($AlZh89K8bjZ;{5L$tyla^l5bOd7V%ES&mz7@ z@pFjtcZS*jMZ_}>@1L*5#0wQKCtjm?CGqu&hl#f-zLt2W;!VW&D1IIB)Fb-a`7`1f zir-AUQ1RaouTlIi;_DUvGx0XXA0pnVcsubuif<>LN(-0lf1ZD5D87^A3l)EZc#Y!k z5nr$PN5tC{-%Gqx@o$OmQ9P06)2T=H_kS|+48=zhFI0RK@fyVg#Mdi+GVwOWCll{f z{50Zw6rV{v^{D>#pG7=F@j~K-iWd>DQG5yU^@=Yi-lljB@lM585#OWumBdqz?r;Cq z#4{AXo_L|++@IGd&i(3o#kqfLQ=I#mPQ|$&*rPbN)6}&7_H#SRP@L;|q2gQ*YZT{t zvR-j6$2P^;&z*|%xVA@e9$!+A>2E*x^BIbBe_N2^{LiU=UMskiUoSYy z^L*w`!KHkg;3$6+>2Ftj1l?C}7yNvX=dDw4)N>Tc?-U%n#*LUbY$$^p_UF7NjMo|X z0~_*e{GqLoj)+bZyhiZT1z#^XCVBd)7PNWrPQ|+>TChj)8cHLTj(9-*H&D4}D9-hv z(1Wu+)mGQB|YWyDz@%X2?-osvJ7#^u)(Kb7wD$CEzR^8(#JEKxl6 zyhia0Xuf{C;`b5f`#IMCCGkBW7$LG{$nC z7cl4NaZO5|pWkg%oZI6=igWwzRGgm=CQ^UK>GJc$1&ZgVTRVB3ljT&u5R`f|CuYVjOX|Y?E+EB{>Xj?Ys=&so z>Tub_*>e|8s0$aDte{lNmM<=^EnZn>z4?Fs>jxFsWmEN!Da@g>mANrwPN;j2L40hD zT~1Dn*poXq+GECr_K3F&7?-MrL*1(Led5$zT4DHYr*t>Oh&}PE>+x`nI(7UdAuE`s z0{j-oezEzWU6W(Po^bq8$}yaOKIVYi36KQ7qw|R6#E3oF`ISBLc!vJRu7lHO{}%(p^p%^OU6lStHWXxV^PAtkADs6LRQ_4t zTA5DD7^aU+R;RxP*g)wwV#Z+GMe|yFb#VG&2pA~+q&RChuODFgIET~eUk_}c^p{); z0kAb%HTD92{joE95{1J+>F>lphK0>f@iBc|^400z1Pn!V{oAzyAZ#peulmZL>%s3K z0OQlNhQ*w&|1kbvJmT-6_)B6M?Frk%#}10m^I#S)th8se%*5E(iWlS-=NWMx6M~Zx ziZ2kOxA;NOb>$hEcKUKLJ|I N3Is2~B!WQK4*;LV-%9`h literal 0 HcmV?d00001