From fd7346ba9fae404b6b565577e2c6d1d1f17a3585 Mon Sep 17 00:00:00 2001 From: Santiago Lo Coco Date: Thu, 19 Nov 2020 14:48:20 -0300 Subject: [PATCH] Added possibility to scroll without shift --- config.def.h | 10 +- config.def.h.orig | 474 +++ config.h | 10 +- patches/st-scrollback-20200419-72e3f6c.diff | 351 +++ .../st-scrollback-mouse-20191024-a2c479c.diff | 13 + ...back-mouse-altscreen-20200416-5703aa0.diff | 63 + .../st-scrollback-mouse-increment-0.8.2.diff | 34 + st | Bin 102560 -> 102728 bytes st.c | 125 +- st.c.orig | 2681 +++++++++++++++++ st.h | 2 + st.h.orig | 128 + st.o | Bin 73928 -> 77024 bytes x.c.orig | 2065 +++++++++++++ x.o | Bin 74480 -> 74648 bytes 15 files changed, 5921 insertions(+), 35 deletions(-) create mode 100644 config.def.h.orig create mode 100644 patches/st-scrollback-20200419-72e3f6c.diff create mode 100644 patches/st-scrollback-mouse-20191024-a2c479c.diff create mode 100644 patches/st-scrollback-mouse-altscreen-20200416-5703aa0.diff create mode 100644 patches/st-scrollback-mouse-increment-0.8.2.diff create mode 100644 st.c.orig create mode 100644 st.h.orig create mode 100644 x.c.orig diff --git a/config.def.h b/config.def.h index 2d957e3..e69f093 100644 --- a/config.def.h +++ b/config.def.h @@ -173,11 +173,11 @@ static uint forcemousemod = ShiftMask; * Beware that overloading Button1 will disable the selection. */ static MouseShortcut mshortcuts[] = { - /* mask button function argument release alt */ - { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, - { XK_ANY_MOD, Button4, ttysend, {.s = "\033[5;2~"}, 0, -1 }, + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, - { XK_ANY_MOD, Button5, ttysend, {.s = "\033[6;2~"}, 0, -1 }, { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; @@ -199,6 +199,8 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Y, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, }; /* diff --git a/config.def.h.orig b/config.def.h.orig new file mode 100644 index 0000000..6fc6364 --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,474 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Fira Code: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 */ + "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", +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 7; +unsigned int defaultbg = 0; +static unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release alt */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\033[5;2~"}, 0, -1 }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\033[6;2~"}, 0, -1 }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/config.h b/config.h index 2d957e3..e69f093 100644 --- a/config.h +++ b/config.h @@ -173,11 +173,11 @@ static uint forcemousemod = ShiftMask; * Beware that overloading Button1 will disable the selection. */ static MouseShortcut mshortcuts[] = { - /* mask button function argument release alt */ - { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, - { XK_ANY_MOD, Button4, ttysend, {.s = "\033[5;2~"}, 0, -1 }, + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, - { XK_ANY_MOD, Button5, ttysend, {.s = "\033[6;2~"}, 0, -1 }, { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; @@ -199,6 +199,8 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Y, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, }; /* diff --git a/patches/st-scrollback-20200419-72e3f6c.diff b/patches/st-scrollback-20200419-72e3f6c.diff new file mode 100644 index 0000000..e72999c --- /dev/null +++ b/patches/st-scrollback-20200419-72e3f6c.diff @@ -0,0 +1,351 @@ +diff --git a/config.def.h b/config.def.h +index 0895a1f..eef24df 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -188,6 +188,8 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, ++ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + }; + + /* +diff --git a/st.c b/st.c +index 0ce6ac2..641edc0 100644 +--- a/st.c ++++ b/st.c +@@ -35,6 +35,7 @@ + #define ESC_ARG_SIZ 16 + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ ++#define HISTSIZE 2000 + + /* macros */ + #define IS_SET(flag) ((term.mode & (flag)) != 0) +@@ -42,6 +43,9 @@ + #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) + #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) + #define ISDELIM(u) (u && wcschr(worddelimiters, u)) ++#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ ++ term.scr + HISTSIZE + 1) % HISTSIZE] : \ ++ term.line[(y) - term.scr]) + + enum term_mode { + MODE_WRAP = 1 << 0, +@@ -117,6 +121,9 @@ typedef struct { + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ ++ Line hist[HISTSIZE]; /* history buffer */ ++ int histi; /* history index */ ++ int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ +@@ -185,8 +192,8 @@ 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 tscrollup(int, int, int); ++static void tscrolldown(int, int, int); + static void tsetattr(int *, int); + static void tsetchar(Rune, Glyph *, int, int); + static void tsetdirt(int, int); +@@ -415,10 +422,10 @@ tlinelen(int y) + { + int i = term.col; + +- if (term.line[y][i - 1].mode & ATTR_WRAP) ++ if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + +- while (i > 0 && term.line[y][i - 1].u == ' ') ++ while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +@@ -527,7 +534,7 @@ selsnap(int *x, int *y, int direction) + * Snap around if the word wraps around at the end or + * beginning of a line. + */ +- prevgp = &term.line[*y][*x]; ++ prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; +@@ -542,14 +549,14 @@ selsnap(int *x, int *y, int direction) + yt = *y, xt = *x; + else + yt = newy, xt = newx; +- if (!(term.line[yt][xt].mode & ATTR_WRAP)) ++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + +- gp = &term.line[newy][newx]; ++ gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) +@@ -570,14 +577,14 @@ selsnap(int *x, int *y, int direction) + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode ++ if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode ++ if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } +@@ -608,13 +615,13 @@ getsel(void) + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &term.line[y][sel.nb.x]; ++ gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; ++ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } +- last = &term.line[y][MIN(lastx, linelen-1)]; ++ last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + +@@ -849,6 +856,9 @@ void + ttywrite(const char *s, size_t n, int may_echo) + { + const char *next; ++ Arg arg = (Arg) { .i = term.scr }; ++ ++ kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); +@@ -1060,13 +1070,53 @@ tswapscreen(void) + } + + void +-tscrolldown(int orig, int n) ++kscrolldown(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (n > term.scr) ++ n = term.scr; ++ ++ if (term.scr > 0) { ++ term.scr -= n; ++ selscroll(0, -n); ++ tfulldirt(); ++ } ++} ++ ++void ++kscrollup(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (term.scr <= HISTSIZE-n) { ++ term.scr += n; ++ selscroll(0, n); ++ tfulldirt(); ++ } ++} ++ ++void ++tscrolldown(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + ++ if (copyhist) { ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[term.bot]; ++ term.line[term.bot] = temp; ++ } ++ + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + +@@ -1076,17 +1126,28 @@ tscrolldown(int orig, int n) + term.line[i-n] = temp; + } + +- selscroll(orig, n); ++ if (term.scr == 0) ++ selscroll(orig, n); + } + + void +-tscrollup(int orig, int n) ++tscrollup(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + ++ if (copyhist) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[orig]; ++ term.line[orig] = temp; ++ } ++ ++ if (term.scr > 0 && term.scr < HISTSIZE) ++ term.scr = MIN(term.scr + n, HISTSIZE-1); ++ + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + +@@ -1096,7 +1157,8 @@ tscrollup(int orig, int n) + term.line[i+n] = temp; + } + +- selscroll(orig, -n); ++ if (term.scr == 0) ++ selscroll(orig, -n); + } + + void +@@ -1135,7 +1197,7 @@ tnewline(int first_col) + int y = term.c.y; + + if (y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + y++; + } +@@ -1300,14 +1362,14 @@ void + tinsertblankline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrolldown(term.c.y, n); ++ tscrolldown(term.c.y, n, 0); + } + + void + tdeleteline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrollup(term.c.y, n); ++ tscrollup(term.c.y, n, 0); + } + + int32_t +@@ -1738,11 +1800,11 @@ csihandle(void) + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0]); ++ tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); +- tscrolldown(term.top, csiescseq.arg[0]); ++ tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); +@@ -2248,7 +2310,7 @@ eschandle(uchar ascii) + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } +@@ -2261,7 +2323,7 @@ eschandle(uchar ascii) + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { +- tscrolldown(term.top, 1); ++ tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } +@@ -2482,7 +2544,7 @@ twrite(const char *buf, int buflen, int show_ctrl) + void + tresize(int col, int row) + { +- int i; ++ int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; +@@ -2519,6 +2581,14 @@ tresize(int col, int row) + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + ++ for (i = 0; i < HISTSIZE; i++) { ++ term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); ++ for (j = mincol; j < col; j++) { ++ term.hist[i][j] = term.c.attr; ++ term.hist[i][j].u = ' '; ++ } ++ } ++ + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); +@@ -2577,7 +2647,7 @@ drawregion(int x1, int y1, int x2, int y2) + continue; + + term.dirty[y] = 0; +- xdrawline(term.line[y], x1, y, x2); ++ xdrawline(TLINE(y), x1, y, x2); + } + } + +@@ -2598,8 +2668,9 @@ draw(void) + cx--; + + drawregion(0, 0, term.col, term.row); +- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ if (term.scr == 0) ++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index d978458..b9a4eeb 100644 +--- a/st.h ++++ b/st.h +@@ -81,6 +81,8 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void kscrolldown(const Arg *); ++void kscrollup(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); diff --git a/patches/st-scrollback-mouse-20191024-a2c479c.diff b/patches/st-scrollback-mouse-20191024-a2c479c.diff new file mode 100644 index 0000000..49eba8e --- /dev/null +++ b/patches/st-scrollback-mouse-20191024-a2c479c.diff @@ -0,0 +1,13 @@ +diff --git a/config.def.h b/config.def.h +index ec1b576..4b3bf15 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -163,6 +163,8 @@ static uint forcemousemod = ShiftMask; + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ ++ { ShiftMask, Button4, kscrollup, {.i = 1} }, ++ { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, diff --git a/patches/st-scrollback-mouse-altscreen-20200416-5703aa0.diff b/patches/st-scrollback-mouse-altscreen-20200416-5703aa0.diff new file mode 100644 index 0000000..fbade29 --- /dev/null +++ b/patches/st-scrollback-mouse-altscreen-20200416-5703aa0.diff @@ -0,0 +1,63 @@ +diff --git a/config.def.h b/config.def.h +index 4b3bf15..1986316 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -163,8 +163,8 @@ static uint forcemousemod = ShiftMask; + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ +- { ShiftMask, Button4, kscrollup, {.i = 1} }, +- { ShiftMask, Button5, kscrolldown, {.i = 1} }, ++ { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, ++ { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +diff --git a/st.c b/st.c +index f8b6f67..dd4cb31 100644 +--- st.c ++++ st.c +@@ -1045,6 +1045,11 @@ tnew(int col, int row) + treset(); + } + ++int tisaltscr(void) ++{ ++ return IS_SET(MODE_ALTSCREEN); ++} ++ + void + tswapscreen(void) + { +diff --git a/st.h b/st.h +index 1332cf1..f9ad815 100644 +--- st.h ++++ st.h +@@ -89,6 +89,7 @@ void sendbreak(const Arg *); + void toggleprinter(const Arg *); + + int tattrset(int); ++int tisaltscr(void); + void tnew(int, int); + void tresize(int, int); + void tsetdirtattr(int); +diff --git a/x.c b/x.c +index e5f1737..b8fbd7b 100644 +--- x.c ++++ x.c +@@ -34,6 +34,7 @@ typedef struct { + void (*func)(const Arg *); + const Arg arg; + uint release; ++ int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ + } MouseShortcut; + + typedef struct { +@@ -446,6 +447,7 @@ mouseaction(XEvent *e, uint release) + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && ++ (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); diff --git a/patches/st-scrollback-mouse-increment-0.8.2.diff b/patches/st-scrollback-mouse-increment-0.8.2.diff new file mode 100644 index 0000000..9556a9d --- /dev/null +++ b/patches/st-scrollback-mouse-increment-0.8.2.diff @@ -0,0 +1,34 @@ +From 63e717e51dcd2f59c7a3aa75b659926aa92e08f3 Mon Sep 17 00:00:00 2001 +From: Jacob Louis Prosser +Date: Mon, 5 Aug 2019 18:20:25 +1000 +Subject: [st] [patch] Exposed variable to easily change mouse scroll increment. + +--- + config.def.h | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index ad20c4c..47e4b66 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -154,6 +154,7 @@ static unsigned int defaultattr = 11; + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ ++const unsigned int mousescrollincrement = 1; + static MouseShortcut mshortcuts[] = { + /* button mask string */ + { Button4, XK_NO_MOD, "\031" }, +@@ -162,8 +163,8 @@ static MouseShortcut mshortcuts[] = { + + MouseKey mkeys[] = { + /* button mask function argument */ +- { Button4, ShiftMask, kscrollup, {.i = 1} }, +- { Button5, ShiftMask, kscrolldown, {.i = 1} }, ++ { Button4, ShiftMask, kscrollup, {.i = mousescrollincrement} }, ++ { Button5, ShiftMask, kscrolldown, {.i = mousescrollincrement} }, + }; + + /* Internal keyboard shortcuts. */ +-- +2.22.0 diff --git a/st b/st index c9753c1df7e69793a468688148e10c87eb98d0bc..e43b3fd40a9f7e713533f37c2427d7af96bff5a0 100755 GIT binary patch delta 38485 zcmce9d3;Rg+xIz>oQNeku?(>!f(e4yYT8U041+1i8n zt*xrkR$IGjWFd*A8oOFUsWZl|VmI&iy3b4+p8lTadH;Cdd_Lz~*L`jGcHR4ZCM#Bk z6nq)7upwWtDDGUj)C`XOrDmv=_@?EOTIj)~7HT&He{7})*L&3*5~Lyuh!{>&(RP2={KLF|9DWB zk1;=+u{PN{y*Z<|;kZDKSLW)Bgy$4CWa@OsbM#)B8&1|vF-orue>Q?(K@X34j-C*z z( z!76_dHGhsi%1?JZM-Rx4d5#{CpZgp=kkB=tze z(LW95RrRImXrvVrggUXE=X0FH9rnAMfYp-5aX#bcf1!XEBKR%*%HbBiR9g>h7&?^& z%oEjd16v!cq3W7}4GdL6Hyj+ehnK=)8}XbXY#GCImisf3gRI{Ii_4#tf3OVfCg(fG zW*zP^GQyF4zk&QP`(f?4!m(L}V8!J-l6{EbLL4Ge?Dr!b?xdTJkDhflpN7v$=iitr<8MkvAeNz=PK-8ZGTB7Cg=G1z1f$Ed!+ z&7`)F*hwX6$(y#7A>Or$&=6Gj1Ro1)(|tZ)u0s)OvSb)*5s-~x3<%)>F7v+Ec`jJn zMS|jHVJ$*y3{IP3{0^8BMqyXc)49STAV@=kBJ69(b9Q%oUw_Meo7nTdY<(Ln%WbKQ z<(7O~?0HZ&jsziB30n?96@Q(Pl;7Q>*u_8**cmtS2PKr>B*MP}Mk-07(@8okGi&4>o)E+}?L{<#@ znuL@Y5f0h97N{~QQZ|2^qDn(sNS_kRZCu}g=VtCOoNkMBr8buDyEL2YzIAdPISb56 zM^LXyn;b{F&nY_GPE&>LG&pEOANzb=lmA9pPJy~Zse;s?BpQN?cN?4|UAcVg zsQ%ziuaR#zM8rT1iSDY4-%gp;c$A@v?-5A9kLJ1j;GfC85sLE*Ag`T}(O?5;M)lQU z4Gh=nt22igV-M6vjVAdl_r;zM(~GCnw%nA0P{Wp|(`(A+>98eR-yf}RAJ#a21|%(c z(+lN$taQEG6`>yZ>Ao3#m9Wvo%${U+Pxd(!YwOW!rQyx$)F<1o`YbtO=NwtsKMEB$ z)svp(yyWi1I~22blsa;_T}qEdP2T67k*@m&G0kj1x?Q^>2M@Iku?@8ivklKxLKI^w zC|e4rPY4p~1B)vZ^d?g17<))>A`ctL+1U?j$L$?^h&7QysyE{Z*mfv=b0WJYhxl8E zL$;ejmFD?&eOykLik)vE$wg0554m@JK1X_%_(_(yISX>Z-ac_;NHNx7hN|Rvx5Q;T zlJ}sdL`DfN@Q@42zjCoZ|^WJ=o-EoBSr1D33CQl2M!TqD|?+DYZ=e zohhM`B`O=z(yxkj8=yP5_GkBe@?j;|B=6>b6X)J~>hoFRKL<;ke+f8}*Yh{A=WRu( zn~X9k5a*ABcL#+Z zAqx417W@#go)G6d5mb!ZMk1yw;{0gDBx^y!*9iZw>!7F}m4!VgGBNqjuGtrZyYjm_ zDQ!$!QC?i9bf4smw98LX10$ZG2JC;AssUTE-6RBy>6u7`1qCOj|Mi3wK-zbpIFxCo zPs;c*N#G0gf|hdOXF>F2x#-aBX)>l?E@(3pZ~hQ)TC%4RIt2sZIKHn|_VmGnd6{Qg7=`vRkd<>0if z^35PY0UlT2_s;^y*gd*`K4>84XZvc$Js7)(`6tEa4$G^{ODLSg>`02y>y~rk_BxUe zAtjsYQmIuqsHepEB?YrDASD$kB}M2kf`Uypxf7>YXM?9_$z1@t5*9|u$)54EoD*bx z@*0w}cUF$>7JEyl=UDdB+Htoee}Y)N?ywhGyom?(1btx3521Qes=6ooklzKB-~paw z#Nu#W4Ru-f)j|I%rfne+;T!^r@gUxoLiwQBvp^2ph=7=g#=n7hLN=2iyhP$tDIoF* zknJ#!knR%bJ9s!P#1|_64F1xCb8*Isu&#A`4bP z#oCb=?Z9X)v;r!LiZBEaP9Y2RfS{iV&F!cdO~4hQKHli6(#%N0^e9Crhp;UC9Yeej zhTjD4NdQ|B?gEvC~^|;n6k2#L{wia_ot&=>m<*E-EJ+*|s?zYdj zl``s;k=0*ycK;IgyxZ!g&6o|5MiA)G&Z2fF~ z`}LukvMaG^XVn&?8XJOctHVZBi#(0QYG2<%R;(|a1^VF?pD$l9omJhVY8vicRo9N{ z)71d8Y<$YpE5Q01Deb>Wm2WjggD9r`ftuH*UIj_so#>7gYw_sk{_G)N^43ESpY;&j z_phm)N4J%p>S=X?q!-Ux81jA(S04e=Gx6+#sMeU+^TF=$JbNv%D}e18k4!=$=6MM> zNnE)BiEd27{PaONj@yZc(>7jJ!%|F!7jLUCq?jx3`xLp@9Nhd~VO?}E7%_H!s=lAn zv~t@hYJRl}wU9d~>l{Uhi&A%_bT+7p8vMFBv=}*dS(n#RZLe38<{;D3^})Hh`9cCv z`pXwyEWz3XLn?f^EZZ(?HH3L*qRZNd7*6Dtvfiyp-rYbk zJ^;crF$=1ugxyBaa%)GsAv>O&{CWSBSP+Up_TCTwA51?bew-zSZqDMfWFe-8I%{kVDF{qH^`Li6uFF~(xKB*`8abhc zs0jC}qXO?wd#SZ>Z2&r}%9w8w<>9o}-gxakIxBBA#dwlP$E8qeBTW3>7-;8Ay_ z7&CS8TNdMY#JdP%mlWd);Fdz^lzc0wX4oo-$YIOy4l=JG-26G-ign5W)%?cmExRB< zbG`zx*Fm|jsX`}A{N7fC<~rGKxoOAzqEq@caV~lQlwjrR>VY>VmGAy4EOkT{I=`y+ z8W&@j5~;p3u7%-De|6QkW`+up>d|qnrRDuG-aNu=)pAGdbvFHwb0CJf?bWCv62WpO z@s8x2(vuT53f&xglSW0kFz%)-m>`GI(UpbCsxv#(k$e>0sss}bca`G~`&7cigE;BCH6 zGV*0@$~EfPi~6ZO$Jdk|Ri)@!l}S`Wy1A=_V$xpCA-6@Bb^(k;Ik>7Tn@0*m72{`6 zk2`7EJAIGKIsiE56qtwJJ0+jRL~1<7O7C^O7nYwWQT-uei5i8svo=a8{TfS(Xt!EG zlFw=~QsHn(yFv#K&D~B-igmtRNU9>_ z^hKT*Qi=SJ1)l*?z2N)#OWyh`krd-~BP;!frAyzu61+IdVA64mk%)*3d*tPHLgc zdZ7;$X&Lf;ln5)*V$4mw*VXlJ#+8jt=eR_-d03iy{>>Mq;PIHy(E=iqIwLz$c6%Hs z^@tLKvLZ?mQ7VE0|8|54T0f9lx@T>bQ|ijtG;e~plCr3QkTky(LYe+qK#^`vwq(f3M3Z@lr0Xk!wk zZ463vG>P^q5#9ByC`Y0ZOjNp|-2u~s**#PI;#tY#6y#>=yuBnyEj*Kp(J5Pr;$ak& z5JkM2D90HED^;R=OcYw|xUAc0n@LQg(Fq09Ui>(}eRXlOPs_z*!ew2mbH4z$Sor%D zHS_I8ozuYazOD$@D#1$}Xl@!INxo9$-UY*ecZc?qhXCd{A5CZTpi|ca^M;C-)TR^a z8TO7<`%JJ(*|W*9gJ(JT0$&kTPgz%3g;y|ic-OjOgMd8ot^_$1i2vidx&sGygDs97M6q;A9JaPHUj;okky>JHl zWLo><6qVA|P^~$#gC(_7V566|=p5`OXz!f`6&Z3{KcA*vomflyUSz}McSw+8U5Y?s z>jJ!8`Me@*CnSp(*Wb^+Y{1T!HjkY1_vs@Dw^nih`@~%X;wLM6l zrbvvT|-DtXK#?MO^o3XAe%M8KjP6^L>MMfpsO!`U|9zwcpe3#TZ@=S14voqbB8DVWy$ z%_+{nj^A#F#XGsOYdRMrrepbriN>`EkS1M2`XZ$=c8gQJ{cb&p(lig9ENjNm@MW8v zhZ$7*PK+wHykJwg9_~>l-dm^`cO|kU&*;NS=x2nUP40h_+G}C{0_vJPyn-Y9Pi#o$ z+LSI;WV>Vv_8dY~u<73=*~?^ubG%3Xp>6ilJQ73$HaxQ^iOdR&xSuhS&J>#Dc(Opo z=!NCFXFs7RfJ&Y-gt>C5II4(q3K1&qC=&5c#QeR4K5FTHa z1{OFbns{7Ub$n`6^>QGgw5z}zSuJK{)#B2!Qs2rKmKUheQ=%G8fR>yu3(c|H28W)D zU3tA%(>fpV`4SPwAXK~(6%nG2p7P?z2oe{fapnPGc``Z^?Y9%Gh&LanO3Zf1C(-?P zq6ighhf9G}PeB2C93;L>7oWq#a~$$@q_1&FIX1elKA93Fjet1n!>X-8iOWJ~0X2Nz z=j?%s9gD523R=-&L^=d}f>BMuLyYSlqqHkh_h|;p6G~Cl)T0V!QBjAPS5k^=?}7ga z3^>QdV*&Tu?&nKb)I&hAM(4v|Dhz7v@nRz_i~hnPgy4T13mu^l6*UXxA+#L zU^oSPBZ&Sc5$)wwl&d~KJ^`jRM4a7FdliocYVZEL0dL!ay=Y}Rw%482wOgzNsrLUa z;b+iZwcC2Dgz&*#b>;h2!`pP?xgMAYHO3xwLML_i`*DV_PU_9~BkR9F<>9&-ISM<| zu?>+jHiovyQiMJ5zZ5yxqzJi!+H_h?sgc3I>7bn`WL|8+jjz)vzpwY}eAIQd5xP&)HTS+;@e89h&$>Y&sEhQ}%t!agpNC3_NX1roo z!Ws#D2$!e`4FvVTv>~msf-%>YYSQHYsa6$j%Hi6LNx@o>G1gkN8JFZ6ig7swAM!IU zAlDhe>agjJq?bAd_8)SUm#|DKuLxH$2}sY9%H{@{J3+yFjgn-!DfYZc>&^q<-aD&z zcN(Uj+8ydDSprGF5{|7Pv6XG#-n8V*ATv*cVCF9}!{3&T^T5~j*LWlt*bp$lvW^nR z3?5^YAZ!3dn#qqu3v^y=v=UvbmRF zlYO$&6k#z>L}KJyV$ZO#KqNWQRK;RV25OU^VnuHr0F<`EVIIpyasJDIzfz*uT*hL) ziN-m_iUNEAf>`lt+DE8-gc}FKvk`U=#ln|rARFS1?TA;Kz7M+~G+pC@hL|E+M|lm{ zmylj`Bzqm%mx5s6PY__Rq1Hh+cq$NXi7-~dFc@ly+61a}(2)!q4tJ9?lrrjC73(T6 zv>(XdWnnR?GU}&9U93|-B&xgBQl_I=%Oh!$HeKiW+M_`v-{|HJ{$4j}H$~D&tytqB zroF05)dAi$`%*AAF+0Op*W_ak`Jf~Fe6Y>k#b=MZ>2QxX@W@VZqCDOd+ExC|`!?oc z5q9|@ynf!B%$1#Vt5(gz+(PTSW?#(!sC~FR-GM#7E zgs^_hCsoc5HpEaFy0S3+*`0dUyFO7t+R>1!pevrAfcmn@yC`kU0UYufM^Jogq$H8b zcRHuFMH~(sG3cDQQXEr&Zh*qQ{h>du7%nx*QEkd4poHJ5V7NYa^6SCj9s>ViZJpJAq#VX zxyO`j=d8rIp_rSs1KS$gPD0&IM(2aA@3J7-#Vzpyv4kMJY4M;4Puemp5}R^57NZEa zbc}Wet^l2DH#KIfBq}w1Zr{X1VM1k7Iv6E zX6>bhWXvWZmp9m9Ir*;OYRx%uiq`@i22P`0I0b@V@_QXPnsQZx-F)N@9fmPN7g*@P zmVc0O2AQxFPCW0q0~fL3+y{D1*uZ6%?`c;rp$w?uCrYLWsjk)&eg00eqg?~$^72mR zF=t%UOjWoU$Gkhqg6u&_SJ6AJ?_j`NfW6vdR*VUDIsOnJG9QWqTpf#)g*+XLk%cTB zGs(hsVB%&J*IZ!(P>1B&l~C#sx1_rYl^@A>v2c3{X-3@?Kz956w*Cnly233_k=T#+ z&^jT0FqlZn008+qiZT2VI@LYcZK7UTH_rM=J?TqdA!o;U!n#MP)Zs^bO){jIV;HbT z0ca7u+59g}Lg`>#2olb>=6*&+E+ilg*q*>?P!HTVxYCB)M#Gz?KC?(7tP)AQLtCgl z@=!A1VQ)|X5i5}7z0_|+If5$W72AYh>_}YUW#iW*lWhDHplkAO)i=AUbkwKBZ+pUu zVKV?WT<5QZb@>`=kKUKr49r8vyKMI5{Xjn9UpXldM~9kYm(R<$Goiz2FSFdEFSU4` z!{oE*b)U9IjPBsYt_03`iOtE7(Mup&PR`gQp9qqKqX_)3S9TFB&S^Q3-Va^YWzgiD zol!@>0#k(bc+0{jytTV1SjD;;sNHfewJf&PN!VFlF@H+z;eGU48a%X7$q7ID#$RyG zg~8uRYKkxxKrs)-+mU>Xifft6RP%;5t2@(;@T5??qam>wO*@O8s7=xu)C_^R7y17E zKJ9i4eL%%R#bi8iUwtdBnqkv@bxvB>inqb`ma)r%2Z$2&=Y92jTD99|8pFUqqaX-~dl2Jb|P< z7y+165fE_35hs&4zcY?OoS#RC)onp}ifOdifmkBM9P%_%gjT5l8O6-XRrDDBxD;HR z6XX!>ISw4GINa0P^O^-aYB+nN=~=|g1eiEXd6vD{6@7IgZH~f9qU_U7V=96(Ae)%k zCIpr%5UoI`brLh1giv^EN!Y}~v~XcMo2!J-Y~Q6W{or-U(;0a?+4+>6 zd}IiF;S0LxO|+1$&re}2jCh@Ui$ejNaL9kiS1|If9mRCZBi^lKN3&5}*i?i?^W7tX zL%($}>w$+6^&F_^OBPd1G~Bd~AB~tr*AI*>I4H2WR>SmD&K+8xJoVO`IB70abn}_; z5brAFG!>+04lPe0C0aXTr-R-9d3GkTW5D(#lkX~Ft)7=yNfJ-d@p){z1l<1XG&}O)_qs>sMpiPo106i4wQjlQR2~j=AYr)2O@54LL6Vo~(X}YdZ9PGmeP493xirawT1dNxyMZp~LSr$wg zUakVDh;I;}xY_IOb7tnlL~%|tD2ba7VKcTpeAJMv6kB=g&O+XSiKbrYyxj5%K(j#t$GaD<7Av?jJVFh4|o%Z_%#u&jX=!45#+keYhRrObpfcwLbX;H-6EW2 zv`hElGgGupiRk*?LDX|^bKEv`rg|MalES23s^)I9d#Ap#P%$3PYm+Bm6o(| z_hsP-DBjje*cw2^xD0Q&%N&56?I58A|ArcO8=F3BSz@MLu?tAqWt|JaiRpX7sE1xi z0sMKkBdF)!gAHUamH@`~;GruY3y>eYkmS7E(P`8_>Y)Ly!M0e9+q)vD<7ws$>hJE?-h`jnFLK|7X|>n-)9M7K=k?&3=zBPhkx`%BD&sc=D7MLY^hLf4t zFmTR!`Vbz*1CQ}^A>8VQMO5|62%zG2?Zd@js;TdGK3j83E5ZS)LP!yYLW7Fp8uIP3 z9-=L7G3^nC2s%$Rw$r%_Zi4H%1gau5VI+Ui)J6cuxIxyDP*lvX;jR6E(bKby$#S=F zF*!ZUFMJ18rC2SHvBzCj%&kaxHVM0#@DZ>}Wb+YV10$M^Q09pdM zrpHs3a^Jjk%=HkkVXQ}gF@`K`rU5UT$%QgGn`lqkB@2&lK-KsV@AN|`iwp3nYY5=R zC)3t0klu$(?;r^U3TzhwbVz=V{O1kE|LKO>ZedNyvH!V2W%MvONF_8-tp+x}JPkpkCw^Nbn~JvJ*x(nR({u>MGpYo2LkG6Ne5V-}o;gd&u+O zVsULkhfUX0o8o$^vonoe{aNy^#Z!)&NxCj&=>#a+LeexdiTwyu%CZ3Ul)X#LNXA^x z)NjE!D-fOEhxqr)P!`*PQ&RdZ5nz2C{7|u`Q9Qp>CVwjfr&fe2rDe-JBRh~}n_zVE ze96puu9Y%-8Ue*xA0ifObG*d`(;?K_+K}Pz01rf}d9af93Vy1Eilz;V#sy+Y>L-!BHLD3lvlnl1K{Xal4#Ufrj^+^+zyn-4RD|}WO}s!R%+sNu zSl`9l=I+}DZlsxxNKn|r6e5^{m{uF*YuiB$MV#B5aCD_cDs$ob)l#WCj{rRS9tMv* z*hptK_Lr8}iGU54w6TZIAx>=*?y-A@LJnoH96TlL%vF>>3gJ_P*qlncPUZA6*^18w zF&}3f_@k9wEQoAIg_%z}f&ac(Z}E z1QzVw0<0mhQ1AD^qJc$tzXDbTSd8~GVC8_d_by1873Ra&2{SowT8b<7F3`uG$(Y6< zpds+c^M)RfP9b#9GgJ>9K;Q;DB8OZy0_Qw5V?Y6K*ory)Y}73?n$>n9iOr#Brbiz-nPox!$w3Gy&>YVrd{E<5HY<)Fu7gc$akTKj#KcG{_~jYq+}S`2=S5uICX2KcFDZgtV&^oJ^5Ect=c+p$M&; zJYf86qgchX!Qgp9A7Yzx=A$mo@R^U=r@rlqeb^AS?1dNI0@B_)RLsOJC@cfdt$}Kd zZO9l-zZzpyy8u3>RQaGgaQE6Bc5MXGUtt#3cz+_Ax$`3E3kq*7nNH&V=*Px;cS<$p>6emWta6!7~!Ni?`c{m;_zi>Cxo@F$QApL zVkX$rE@Ji9I({n9>6`2_W~XDB(r@0&xDO-?tzgJAiB{(YF|tsfLStB{y(~mhXb?g& zF5Jw(d7`H$v967m<3or=YdqJrhI0I4e1G98z~8nH z?Ay&KW6xA5X2Qw+o|DoJf}=}lpyLFLXs>{#``X8ph7J^DzLrkG7Z6;7%M^_PwMAY~ z>Jauh8UwAC6t37lfN#I8Da?tTeq(F@>wS<^NwFT!4K%^9?a;e|#ie2%hHp=BH5_lP z6}ALu+&&0hl$jkgM{>M{0QU1ydNVGA5!9rxSZD_#?+hyz>JlQR!#%N#ibKHQj8u#Q z5Jl(?@eb|^jV#&XylsOa!EQ-hiKSX?4xYFmA4DbWG$@Mk5Z>_NUjQ)uZH>IjQN;+@ zTu*Q-We=$b#eWZiy>tz`?9b{qzfOAwu|DcIdjxa5USyB4PDEA) zlI`U9o&@x)sDG&^K8uw`q1ZeoDCy$g>*D2fai6I~+>Qt-=bo@Y1P0vW2OpzNYoRVB z$6X7o{7w-_6ql0UsR|eDDF=#T-T^VX5LO2Lo~IHC3H>f{Y}2}h5_K79~&g+pNjzRMY3H$Q0xT|!BHgcn{2XJcZ00`iCZCx@FIvbc{9cXK~H?a za~!UhCyXRvm)5?GS)4N&2PN1-bGYM-yWlDqr1iCxyU$G5!`k8pQP)|kOE}NrjOrqG zr|O?quYF;%av)%MxmASLGZ=FUoP1pZ&UPCxQaFOF#}K3wr1~3hy3EHON6=$I{MzV^ zYR@IT4Xd`QUoPnwvEZuD$9rv-(=)ag3mxk6>fI%6q>do&B+u0>z`V;{H`~*trJb0$ zb6R~O&0}j(Pa>*S>x~%tpk&`WM-##rq$2J%`ss0K%nm+JvAzMicI^lBc(~qx>zlba z*=(e@^f{nvy?;h&C1|aGVm%5*gIA+o3AEVrSR%-ILFU@9A)Bk?t&e+dYZxqO@1pb- zqXFc&9E`tbQO<`X0UyWVE)LFE@Cl9e5+b7M) zBi=&-XjUu~wN-J~9dWQW*EqeIoWv&85`HgKXCccN?D zF6U3!+nw23{bWUiR0j<2No^O(V3xUVZL|+uUdmOvw0CKSDODBYBc#>cjgC!vW`B0i z%0WW^=)rqS>0_tfDr^c&igOf=Fyj{Y2t8{U<`v zQ}1zjz?X5xFG*f8mxoD2I}agk@^|W|Up6*8oTuh~dBHfN78AJ4fOQOj*5`Y5 z`&SJUWhyzGsKni}$$R1++OVKdjCNQk7LL}$+Q1pB2-CkncJyzI(K|oX578%(kw@S9 z6Qu|@Yw&8*RZW6YYx3&QRn>#~0Zv`@LeMw0cy;|MOHeYPcU7A*f7gJX_^sOX>-qJ3 z`0^k{ys_tw*r;ojJ7R=zU&{ZM&qM?S`Va~IRi}*spd8B zTK&e@3+vRAt7}N#P{!olIE9YHOiEaVx9FaWdUYg6Wgx(AI0i*6B$VAsCf@ajLjB%pSAN~m2PQ^AhOjnW+Tw=NR1TP+WHqXaa9 zh@4BSK%q8J%2Ca)5DgAy60|xOwYHW;CGRQ+rk#=!?So^BHI{#Z!TqM$p zMbJPjKy1akd1oz`^&q0dWE!h&Y$S5tw{c#9;!;n2+cn;2M7ppk28&3S2O;eUlHoP_ zh4B@xzYed#{wr&%1RVn$vo^YFB4lI4w9{0Kit!uvF@@)2b;;VqdaZS%s~E7xlFi^_AMFvgohWuwt@JE8&n9*% zR*b7avEy{_mwTwHX{K6mz;h@ryIdS(UdZ%k`1SKdcM)-vuqmK)l8X?B9sY$6bkW$a zJE9dUB~Dwn60=bZU=ErQ+D+YPQPZU|ug>`{ruIc>Y75|)xP{OVpI)0{aFU0T-%s>1 zE7iR3qRV_kc%`pY?{{zY+l#Y0)XxhbL24sH+Hz=n{>G6Nc0Jq!LjBdV4HCsdHweUt z=}*D+oQLlzQL`#Slif2u?CD38_~VPzAHMHyY@}PpFSL;5DrDLBr5gT&De4obAWM`p zZPi^^DhuyqGJ^LZ%Ar3HEWE+ zZ+xL!!__yEzWCHzNU>mi+Yd4Nv$4@i{dRrz$^%I=Z7DRcP$xX{3-##w#!_oCBJbWx z3J&Z;rPT%I-&^p12DO-yFJHmQDD$% zMl$`5F^q@pY)r>+OI?rSHMhBPSH1r=AoHY+6;f%5e+14i(*fX-p$e00Z8mQXUX z=^AIE8z1pu$D|wRNM%Qgdd3R%+>b5Flqb>EOVnyVb!_{}XSgmwx%D2R8_FFJq-#en zmymy<?3^1 zQuWqP(RG`UV9ip3ONd!a{xO&Qqs=n4#m`L)KdexP|6EOqPeQ--!6KGB^H@kyB}X7# z7?6hhgFJ#MUB$O{f!5hi3u9S`JzD{_!*)~Vg(K@#;On%iJpy|eJF`;6X2LHJ5$Bxp zQ!tteqak`nY?37lrz9{*kjULj$k}AoZFY6mlQVlU2EF zM-k`i8F|Amiu`c+r!s$w7lQiUcZ-ia!ht#^a z#fV_jZLy*-tUS*lAoORjqR<)8GwxaC#eU@vb>#?$A_?sqGi=1Tnn`Z^}|SBs_-^(BsOr2MXpziYun1#MYKDqKIKDVJs?P1QTN< z7+M7DlddM{%Ncd-rcRaKL_FwiD4u*21rnxaZK^sv2HG0;Imnn(MB(;9-_>X=%}~oF ztqKE&7L5k3&$->kiLiunEAHsul+^3-G}*xZE|_{R=CZa&9OlgprOzOVo5K0{i~?JT zYY^HV8qD12Tj1yxk#d}T@grBY6B!{$=A?nTq;rBDN!lN4U5Y&$rr%J z1JjmLYo&WfbnPKsJ-xX>nMhdFl0Q+)ZK+i+J`~nzIb|0<{E{34%Q_5=|4^M@s$IAA zYTA)yDc6zD4S+u@VYMM4JE|z=N`y?nZ_rdEB&mraR0!qOye%=dhKL4J97lf86d>}u zG4i{Sa(9#5y;iY6NeFV<9ka^w1{#ZbgxQC>kI72M3X1U_tTfn~RAXZqBoH510caC_@zhkqqfM%=!-~RdAJGdZ8AfUK6FTziPeo* z^}y28LzmZKK?TsFKLRU=+SUlG!NH({^+w(F~Z866IP0jHxmBY@>g8RisF@ee?P#psK z!me4W`PXiBuC#e3LDSt>^in~va>*A4&Qe$W+BVvaJoj-ZwaN7kE2YT^S!#{E(JW%v ze0BKFI#T!lV*V-hmiS23#w>raB+$nmgrr9xZ6z!n)a@o@FVOMLgS6I*B>tp&|70O1 z=2AmkOj<}~fcEEVSJv4Cxpz@wu;(CX6;ZPoZVvlU_&Kf%Ph?{O(BfS&sSacln*oaT z)e>Pd84Ozu0e>6iZ;%^*3POytkd;Pe5Jly-HbRkTDd*56wmw6zO7xyYUy)m~nWfIi z8f0j+N@;oXV{(y~ql3CXx0b&+rU=l8bKseGu7P&{&5s z#X5+jI>~B5j>+)nWA%DY@8R9&Qjt@)OLIKZ;i*k~`^rEY9o z1`f@Lh?H|%942&X$6*#QAltUXfa1Ye8`c^W%&ZFD?c!)G^na`_&oy@LQc5kG)SjY= zlNu=){0cTv-PK9ld=Z!Ojp0xbD;lc-AXv*JR*ZV|NPU=FtHM~!vORy36Lg=a*4oFG$#P zh`~1j&Uud3jZ<$c?_*Qry0-WfO}LzY;-@B@zmS!wzj%+$e7`y;F3`&Hzpsvh|k`CY)Mm`~~7eXK) z3s>*M57x)u1(4(a#2Z6X64_C#WJi09qydj5QnC>K5Fop#j3a1{hMKS|?Gx~`P|#A0 z>vY`;q|`-WMTHrXpquJX)Ri*&i;iJ)nD}(OGhxDi)ayPCjpRH2t(p^HYJ0I43=8{7 z+)P)Y6>EEYg{ zQ{983F@}7ER;lG9W_|Z4KQ_?YzDb)exT%q}le!n};ducelQAHL65V@)2zpOsy@z|q+HDjU+_SyIy`(nFhrVfXns{XpAuBx^of@U7uk3l%(88jw z-P74|FyWtue;0U)*zn+OaY!LL1;ByEJ&a$muPGFLi17=A#5uU6&h4brG;ISmfYD{d zznh>Y?6rf^~~NHhAS_r5BByHZ_lE9i*g2*30l82wf{bQ83~<_ z_TC(I<-SNm^Ow|L_EmQnUt)R7Mc&ZK4HV9xmqw@AB#Xvb2QAFoBi^V?khdaQE>Y}| z<*A0|sB}qaEv(0t%GC1k+B<5k{dJ@<2&QMvXhvVt2!j#8F%E4i2MN6?z_vc=7N(Xv zgSK8QOdQ{ZxvhsNWtk7CuB}fA|5?WyQE9B!@&1H=4qRXA4hlkPUAYa~)Ju9bR`Rz3 z`UY9^;*?u!tsdAPRc;xCkO?8rtlr%pZ|LGuO~2JObib`Sev6atlSg5I+ex|84jiVC zz6Br3Ttll5w_+svaU}dQ6m5gh?+QlEXL%A95MW#9CmfdCcN1`b9gSch;zS>i!+^CT zB{Q7Ggb&sgy=+*?;K+;gEx8j5u(jC{I5zQS;muCz&^*2!0q1Uuogou=n3QolW8h-i zA8cnvC=a4Hk8K?!H3_tt2;$MVcb2F-ha+uW-!>Hfqn0pUh_TC?nJ@f&Q*|F`W=OxSW*&&<$EarxL?+l_0r`tXF(7~Q8L)}~ z+R2%S7QY+`0gQ#g7z=|Vna_9^Yi(z#@xS*owE9?0`@L1@!?RTVm2aw9zt^bt&D-cR z&(ar8hcJIfQ+)wOy4_N5{ocY*?UrgPY!NYnF~qd(C>KSDep?+~*d%(*O%_k2SaFCc zrdVix1H~hzVJi_L4R5IH3u`qtfadOwpO*;x`#OHeL|lK<=I)QLD=?1O^pDRK>*tWs zVyCOcg$)dkuc@^U+W6k;h=YyVw}lG!ssC_yH(-z2`~n3_w71eM0{-_0sM@G$7{X{W ztq5TV%YPt3?z+0~U_I#z;@1aX5ejjMP0kkA?{j4v?D)mg^~M}fTsa0iZYtXJ^)EYT zc%5`^zfU?#O6eeS;@t+<}9801~HE^CYf!_ z%!V#x1$f)5o*biJeu)wD5xM1D?sDJ{msU?ERO7|?G)HJ?m*GVZwo4wlfxLVqx0ob~b1)fkYd z(JEE&^Ht^yX2MGMjkNBhgwNvWgXakt(n-vF0)G4pS%yz1QL0kJ_0|v&T=|9+*}8_X zJRU^zHK@4qKs0{>;x@PzC2j%fy3OC$1CaAs%C)aj5fR11&Ji(lc@dH1k)W$UYL9WA zMd%J24^m*AeUlOdR|U{6QjM|xO%WC{wW6Kg0!46wDd&{EeeYPEJbXyp>9%Yh0aA;+ z_QiQIsrac}BwuU64iXVLb5@s+F5X$&LX4#?*H|pXBVeFk)};;=Es@h^shf{C@75Sm z(OA6V)W=UsM#1HVv(y zoBu#U5LsC87viO4kD>(N7tRpY%Ax9Hd{H=(@`vMRqSe>sj}zVqX(Jt*uJgquvVqhJ z*$se+_WSr{;>{q^=%?9EAR*>zx~Ujaf1f-Z`G%=@jIpx zLn_9_OyvSp!j>`>X%W-uT`La1t2l>r?1rbZFdY~cd02I@_7fy5Bt->oEnFu(oFoF| z3wQCR8o_8+2@>LpdXN&j>mrmfq|HrM;P};OFz{bb&Su4k6AURU)u@<*bjb?P(~iGG zHL^QViA_aNG1KKGSh)!++WXi7#6*7-NUw~k(GdN6-#7TAH|UgX8u;i*-vLmMmjI18dR*4iQDomGK}MUZ1I>=bxXsDG1UnMPHXUc0LHjzQZV<_+3kp3 z&Z(Y1UC($wigBvyoHE2Y^sZX{Ok?Tj1>irv{m<#b_e|9KE73#AxGekuoOucQfKPSy zGGcr5y2&ZUo~yGZV(-=2>q@aF>Fn2ty^h$OWZ`23^gR!^aSCwc;1vd}FOyj6gn!c3 zO;@i=^f#Gx{IZNN_<|aGHd+eOm9Cx#GXu(wrjtjLMDqzKY8bp-pZW5aS z+xof%=c+_lPj%@w&jhY_yWwv#f>c6yf=zBTt8O6BAJ8s3DwPz`Vywsz!zj?T^%Ehk zZ>DdrP4e_*sw{+phlP}hmL+KPU0KjL4vLvJA_XowM<6!;3ZD*_dO|=;M548HtirXo zG)kPk29K{=Q`JdD#){30um=2)rtN2P@TxOj?RGAvmG~OP7FK})<5Cvc#K&3z3P~0| zLeSHOR2GoR;c--QZ=P3|or|mZ`8n_&-v_xD@zinZxT2VfBZ#_>sL@0nsZ$#g^>d=q zwP~$|PQ7s!)Uia}K~#=WrN4-pKvadOzrTTeDCUKrB9oPC1m@o>vAgBjbq^AwAm-T) z441Av1}LIC*Q!I@Wt{~r4*Qa_Fovwg6H_tHBU|`sP0gVgynn)KKBToIGN|0g7zxT= zqO>E*576;%p}B?CNByTQ|MZKf%rmg3g`ze*Nsn-XR2wt%=!dpf@gr(4bVnc2zpH%A z)~~C4*Vfl2KkkPw<4hp;NsN2K2K;kl5M%a}AZP6sr^MPbH_SVKB=qN-F@jVD>$h{5 zN)QY@w-64(L@fai(cT6;K3#t(9KcJ%fNp780Pj~yKS1+C>0gaKC&Xm_E#mP=R9`%o z@pSRSC4NY>e|t7OLq|nCFSHi|AD{#{z@Gq5;}MVp3ReTn{U08Z`=4Y=_|pGiEbf2c zml!G~Hvsq?JgxD(`L)k?2^%l(`yt_90S`P9Aw>M&KARxB>tE#EpsoBDI@#ThIPR8; zC-@&ZsU+g)e~3Q0l<r5?Ww=`YHQU9eQ`vgYl`D3xqcj&n% zKzdZSNQGDZEU=YlJ^gTHyZ|F8%+C=s(0G{g-0? z4}$-Xcse6tZ{hhG&u%=~U*H?AB|hIBJOP_2OaH-N0)1pD9?878 z)F(0FxupaH$^^z!2+IfubP5r{UC?eV|A$6AJ4*6%e`$UT z*cVNA7@oH>Q2)oj^!cVBkcQ`H7KSf56v9@^L!1D>7aFp1Kc;3V# zx$%-wl>Jalzu@^6VS1XPJ_EQ9{6?jCM1SfJCzX;5$P=VUDB|--z>jz)LXHwc5odw^ zIl`}e>T6aK$zmA%6~t{weQ84#P}C#w5bd318#13V;^_PXlLCGUI0bMM;5@({BYeIUfYSgs0{Q@p08fs@;1`OWfn>C(P>JJO zjfNly6HLckq>j{*OB9TAk}xbB$QI{|xR7P}D82e<~XC6+k50E4lvh`=?EW`Ioq zM*=zkZvi?1O;{T(1iT3N9pF7IZSDh_CnF(JdF(p?Ndhba90TZ>;`2=dTo1Sca0lQ< zz{i050ozRV`7Q#!4#-vDxVHhT08R&N0;mAm0b9*L#DH$V0>Bl3MSwrg#3VV4<8}i| zNkAHZfFcGA`poCM3Ro3Roe+*S72p8CZRlH;0Iph!CJMM7@G9UDKu+Yi2Y^)o%Pm8R z0X7C~1NZ`95?~_WD8Ma%(*SFuqx%l9<8q(m+Xdv@3Zx9M=}HtmV0p}b60x806V48o z05kpi4B&vDArH831DY#fnT_x*z;8C8iYmeGX2=76 zycJFjcyKFv(jS4G+KwUxd;rK*=D5Q%5uoENN+1#|O2CB~KULrxRF6pX`I@1t;Ou2k z2XJgWt?)!<+yXmp57@nkjz8j0z!Tg9$J|7~p94=Cp6(g8FuuF-E?6?r@kinp@w}0- zGK{Z0Hm@hDm+1H-7H%wZM2lk-ofZSyDbSKj(OQAlsF%-&-46Cgx(?7@0Bwa%;+g{v z0c|H}4>Eod`D*;Wj5^_b<(3OUO-u6mUiV9q_A1bhgO=f^Q66aXc@TQTf}o7E5xgnm z!*D)2B-p@l$NFac5YAVv9|L?3y1Nb#kD30@M;qef0V5;pw$75cFO3FXwyLZ8Z=hJ2sgyD z5VS1NTzbNiBKU}q9iUYkl#vp__Y8>$=D1~pGPXzXHOe*z-U5C2HyIU;C>6dUUo)%; z*#&*x5O`t6tXh2KsGtUXx$sC^cti(QCM=roAhLKUntVpTihS*tDdrO(Z3U?jl_8`* zFxbK)T|xHnn4qeD12!^16%vfmWAiYdZ#1ir*o@yR@==45K>QtDKCb>T11D%QFgy(;r{${hji9%yZYb>CQ2nXlP10%O|)3{@Y&01_W#7_O(DGTa2b zx5r~fs`DcHq9CZw?VwKpeGJj@7lUU2XuHN|Jgm$|OVfat!7zt0f&G!Kg`jbuwI|BZ z@Q9KALR|xT!vK9$c*J1PDKlB1CxPCWl~j1+iZthQ6H{?}G#sNH-XUPJ&ja z6s>b4h?hY`0kS_5Yy#RT(3l@0OeNnQv?9>Pkrb-ikP;qB{l);$Us#Z_x*FdodM@yB z3;g{XWQO~Fi)KfY7i3(ohF*6E@OumWLl9(U_zNl#woZW8Xpzr%Mt6~n9+7;#at$kB zMuNr0Y9XU(WxigQe11FaWm@W>L!YY21~=$*dEs8k)z znSMIz9mr6fvs`ox4^03Xil(y?^j8gfQ+=m8x^X**JC{cq`=UeVGwbgKt`&=4kXC)F{8)FuxpyLy^kk;K{6?s+Tdn2H!el z0*Lp&&d996R}Gm5y!z^llQ0yL1^mW08R0cSKMnlWHyMp;@*NtP;FGRzeZDU!f}Y_K z6IcWWYSL^JvxEN>{Ez*k*!MM&tC=A7Uz>4&j4c74vNq!ijPh` z#DPEfeDS2nA{$4EX2T54Am8{QBRYz&AMzOZko6heqWGr$o5PpC!;G;J=?p@*@AobT z=$W7|A-dlBJ||<;tBnG$?M7VpBOZF&N&YGz-c0aPH)b4&;;UL{Ho69KrTZ1i_(ssb zdXC-@^!=bO1O16U;x>=QhM>Uj>bHr7AC0|Hf6jK&36kK51iYUaWuDeJ|*kgR(!8r5W^o(AfM8 zZbb!6KDz_-gFAc~!sJn&z^ z1KeJjq`c*|Sbj~YVa(+%<(l&S>wKETHfBi=mG+P5*YOypX8{T0(t>?ii3FcM~MjN{Tm>_buKGi zkBff7n*QiB9RnA})g7XTC+L8~2c?Yuk)KXYjsEcUJfpAn)8lk9*U(S@-A_-}=_9%3 zenv~y0bmvDSGa(|jUw*lr?2qS>4$|Wq1;Fy_@e=j{;YmGH~jJsbTZdD<9vUc zYr}N<>wflNKfTv*oe)@bCHv`r`RN5%fzV$qR2xM<(_c=k3j+QBbanM%SypHK92HRV z10=`$It|z;C0O{zn65U3rk6C^RU|2v1sEX6m&j0XcJa~DY$Dj3JAKf^H9t3+O2#fL zgKJvb{30!4Dib#^U3l#pTwayc(%tvDf9H>$^T+dhpWpp??&qBIJm+~|u>|_Bqhs{1 zIc=*E{!aA{FFMrh292;01~&^Vt*QjnugvzvlrL`#x%;aMg?Ag;ZQxIUw|f;**+o3( z75g-Opq9i`WN)%8L;KOT$ zXa+x(c&<<`SDxB#@YA4w9Qs|%^Q2CO!CV;dgyoh3gH3DYoPT}EZ5H%1Jo>YtUxv9> zh~*{2>Q7JuUyJ=s0kw$u@A0)ivssuvx`%RGY7Km8;KvKhFzK>q+^rl*D1G5-U8891js!q9%8}2 zgaC`NuHO!R8~Ak|eh2vN;OWuCwtev5W&P77v%i5SQ3nhTz#t9={2v0@!h#v|YY-3cN5aG)XO}p@q*MIY(b>`a;N5Kd2>ey>ttfS( z>3)o^=m5kU_K3R&1=JP9A9zpnuY(_ielPUu(`EDDY*P8-&mgFJ zo6qQ#mVjCc{wZrfFYu|S+zL(P<5a-a5P#SybP}C&Yarx~&+_zdGx!-E{x|R=KTuby z3fE~T_&*}8Ht_A@r%qizB8)5Gn+P!76QB!@9y}}w<_oC5f_G1YJO{a*0q>q3e=M~6 z2l(1t5%iw*EHw{@jCVa@!k?Mgs z^KzcPL*Fpnx z(iN-!ySR57@lV0;S|PlfJG{qW{oE_U)2VF}f?GyL`}r^YvNXRz*faqZV5y!L93JWO zkqW1iU#leg4Qv>4~PhT3X|E>18+aBMt$5ZzBi9LR1hB-h; zFm6<|j^bnR#c-cqVloS;O810G=G%TDp=M)5q_g7vS&$~45or|i;V*Q#zo{F($&cau zM~qiRSD*-3Zi80^8^U}(n5Hy}Y!3o9v5LP9(!;+M>25gKZ5AA1b7y$DRE zi_7`A{kgk&-U0qOT;-o&;h7LI9tqeiy|0s=${fvq*p2yiufY4Vg+4G?rdpL?!%?K5?IBV=Tm=6zSmeb_j9R zbQkFuoon_N(Pgf-5O{%-~FgPFQEDH2D zYa*^ou?^}|MV@KE@76h)BcAeakX&04<`QOSBEsyHT;Darl#D(nXM+M?h-nzDL)?KI z=f!3QOe0JKRY6a&;GYJqdp{Jh+Zm^d_&$iwuNUzWIGHcvhe;ec!lwpsTZU9UAu-AY zo)c&_m8Cja|5?sXo9(5P#szzpa1C)5Az5ti)&IZ`T^82`CZSBn_@il|<-UEsTaU;zy^U=1ojC$YvRf&&LKHFX(4a{ zaqjAu0Pg+dAovqlkT)X!XJQ?0F0!vl0x1^!_l%Pc&V{e3ut^hf7KtM$b#L8s#YvgK zT7iuMFAKB@bO;<5_*mdy76SaG99P%9Qlyo@wsEvJF~1mtk<@I9HhC*)#w0=9for$` z;@MWL7YZyBSZ%??R%)Ma7V1@jJp%6t^axxM7#6r`!9Rf(3BE7odgheRULAi5-RQVT zeecU%6zQl$D-O&gYwpc1te`27&jJ5Ztq_i%Z_N@Og*dHG#BDGu0zcq*<1g1Sre_s( zuvHS;ODa6z+*NaRJ#5AL1A&VI!vbFmOfXxjXpnqYV5S8>|Bi)w@H@|~dy#0Z5NHtP zt)v`5#d5)swrUlv1+ql$m9u!U`4a+sTqD}96x~)_$Mc5!^B2zq>V?^(80k@n)ek;? zg%D4JzYPA%p9|3reh|ESHSKPV*DSA$siL0(gqq4~s*GPCQQ&TY*#bYc5LifW6u4Y@ zm{XhRhQciy{AnzK<+Skb|6#Kt`{H|hG6CBDAU%geIZsH s>>e3qdYbk0N!Ou0`gYNtLU6#0Yte~g)EaSg(4@EMnfjoqZPC;I55#lKz5oCK delta 36919 zcmce9d3+4_|NqP;6G1i;#}Y>(NJtP@ZJU(UY%Mk#MA4$IiiWD9B#ssvT`6O%qf=d~ zRZ3f})?IhWL4u-g>I`+vTI!D5{+_S*%%+j==llKret-Oa`*_T}Uhn7Yc;DyD?(Y6B zXz`MuIk9}y+_>`cXOo^f}@&gLG>-=Q+`qTkU%XMqQn_i zRPD1q>*Z+y)q+1=|HkTmE9*D8Su6I`I@J&!$}e8WSNhiIh~BBG4{XIVCRht{f97e- z&@y)Ky$pWwcg@e7 zGWZ+3X1}})?$=*k2LB>Z)8DP(j*<-c4Gxwu7^_F1mcjk{x60sO>Je}QU(8sbpod?; z9UPYstO>$i5O7?WZV+AuA74(>Z(ashCTS&)^O-tN2>=2ZF2+xgkP@o&n)G9|8Ww80&xbnV;B(FOLbV<#3&+{75~&vsdOjkz@? z5s^$S7!g{6IL$+F50nKHh-;LOnW%kyK3~4LK}Z0&+>l|FO|OEJjTCkkJ)bVr0fX2R zWZ^Eko_e6lu9{?vz#iplwdSrkGE|H+zC@<-?hOj8gh-?}Jz%Ez4lRjmzp{nl*kbyu7 z1J_bBM(A~2jx@GM76T2ynkuXNHw+IdWqHr{%=;aRKSdUWP9m7)a&r*V1li3s8!B%aB}Tv{*Up&i4oo4+i^^`B!jvF>nwQS zla9!N(N<}vmA@xWPe*&QCB`&_RGiftf$SW#&3j^(z4o+Xd)gj*?ggY!5LpUCZWUVv zUYy#3jHIh-g(p5=;z@Bq-7l4&;`TzInEevGBq9r9_TlH04sj~^waXKugKe!}6Q@=|$g>e5xlJ@GbhIpt zMJ(vuRw*dkC(insMAc-WZ#3^97**twfZ5a9MB8((2H4Z~A~ztyYm_=x^yUPX(7@2* zr2EKNAE*aZMd9@1?EE8HsH{bhm^X2cr!8dG!XV0wTrK;sI4k}I-{^NCyB<=4(}eSm z`Xv#ZBsRbw!Q*G8Bgj7NIPAF!$nLxz?6eGpDiX1gVh|=GAe%9+JcuCiA2ra>FL27(S3(O zXf0cAgHg?oP!+;Ou#|uBU7s+_K7BF6!f!Y0o*W{ix6G zDbEKFYB^;l!z-8I)9?9wn<%4R30eJ06W@L2H+%n{^7(s>-h5>iGuu#tH}RW!8o^fD z3tL{Z{k%jcxI`xiI@X@n9zDBLP1)p_cZ-zJ^u{fU?xKLRbH*F({4RT3e&*%Q**7K> zVmjN5%Cz+E$=^QZI5re+*Q&%~A)}w}P;;>4 z)&VF^{R?eDed`}U(ykO&%r6lY$S-R}4kHMxNJgB{HD)oo7b)42xUkMQFaOyNT z^8RX9ZlkjeN4BByds&TPq)|v3otTDs;gbp+w*wca z&izNZHYD0m<(?8Y)LdoxeB@$7VDkqD>!D|3Hu!qJ(rakbDyJjSVC32d)xS~3Ql2an zL@Eo0b})=`DHn#CgO4D`PD`6eCG!31j%esQ{&^=qzfd@a{L`mUI8wr$1&37l@>#Z> zmh}kp87WRnAqg_t$);v-kbBMGyvQe-@&RTxb6P$JHDolDO<#gxmY9dD%f{IVCf=TC zOuUoPY!gM6_~Gr0=J|={kq|f@XmsMuM9VuAzN%628yc1K6D=JGJnSt@wAjFDus6?X zu^{Bjh$$3IV6+T0BnThb905+Yh`6gW?<#eNnH=Y9BMU5>f9aeoP<+~qF9|n++N7AB zAX5Bcc0Wq@h`sE-4|laWdaBd17%Z1jp-`9!#sHVHc@a3-GKX}GXrMGFVL>qpVH$Gx zK}Y#*SUtxu05;h&fRPv*Bk}D)WT7$cm}^2LiP$|cz)IJ+#Hek1gtpw9mdF zPDfWl39hcGtQbDF;y}oycF4jzHI*hGMj1>sl`bE)Fx;u3O!=^x;inqP+7DZ|U0;Le z;+{D3`NX?o*K;;kbYrV4B^smV+Ylg9=^t$!S{oUX&7T3Jsd@TM8d%5IP*#qp<){VF z?rK#oMGorZsv3;dSarM976XU@f)pvRnlqQTJ9C0%QwsobrxN#0+VkMFbWXadKEo=F zH4$y-lb*>DkHS$f5#^wnQ3uJ&zQN)bZI&|-Naxf=h?+|FoqqXjWRnM4PC%j>(b~O- z{eHHhAUG6Lm>lstr2pnnHvdXtihL!80ok|+L0Ra5JLUF3+&O0fuVuL^rY_kuIb5kd zs*$BK6fg$|+2uqnldY=Rr4|^z9#w}xYE#KB<=LHTa7toc=0PoQSia~9Rc4KfD<6oR zNs7yCnWp57dfAcmKF4{?=rh}?FWQitri9cdBnoLKq&6XyfS_(OqOl1WinyG+4bz1I zVAzsUOKApj;!)uTIyTZNdgeVvMj=jPM}&FL9}qfYT7!S?*#Tb3{HT&+^-%c#9wE)k zTL^9s&+!L8ry!T4B_*3wKQ2*i{G#d*3I^+`?nA0wOH{cRRohT7jHxG}+>;+e zWt-Xr)30|M=J=1Y*gD|j+x}Vd)>XwgroJI)h|+S5#j!XW;^4`J43I``&D@^*fRl5-5KKn|G@Jhc*K0j!D9aT zmOtjGt7sCbBrMq(DF2Kp@5q7`ee8uow$2s`PMwp58%H=Sq=q0Y3!8D5gaNqQ(@f2Y znxl0)&OoNVEoyQwtqJG{p3g zg;Q1Z^<51>g+dkFtq>FnmB0nyQz%r_mA9gbOE>uW*L?c^IMho?m#M%AfpmW;&NNMF8YAsezFjI*M^gQw0;Th61DZTQn*e$zpH+G;YPnRUw?wl*rPn&I*|;nn zaZbioaO(M|O7Mh+q1!`f>_vk_FW3^ISSCc&SO^FmYCZ@Frbm1V&S`8Mri_`;xXHKV z>)&QI3PHrsTThkC^7%x63=vP2y%XwHx?B-`Ya8TP`@fth9IvQ6n-JY~{l|W_Etpx< z8<4}z@w1fBJlQ-KMrflWO0>R!7LHn67DI{uDS))G-f>zP^>MhN=Xqtu$4x3U!#HZE zN<5?Fd|cDe{ep7l<0wOmGs^Ri>sgIx|D9dYb?gt0B=hHzox6g)TjBNfPyESQhEWi} zZxj500)QF-d|p*YoKoKXq-m%Cbu|%%>)o%ueop!3lVSDI%F7d9 z9&`cai$&}onE!J`X`Bybwdf2ht7)gHta9zrDU6uaC_>qCMD?dS8Fn5!sgKmudolGq zyYvsz_p~H;H6AGkCPp|~LLK#EQ751xB;iCbYW$%uqcbY{eH&Pw-ULm%N?9L0SdC^=3Y4L z0EcQN+}PyAqF;9$AsXLU=z9uLQL~U6p)Ck-+e3S3kk-OQctD{|?@dD+<>4PFf(ceH zgHe-Mf$S@)nFTq%4Y$B`^m5DqiO4D^&_7P|lLEr8LjkYIGHDk%F+h=EfjG#YQ7IvQm`tEpNlWI)bd6bgm=hv+l!B?q-vX zTZ8!^4hO26f|YNx`nKu@5$~vl^}h66b)&P{ox3p8wk8W4!>N-mOE+axa|%A@CtpM{ z#0D$PrZjRS0n+xgY^2w^h9x}^F(WiaB5`Qffo-cyBn(ey=&T zQNzRn(XxRQPuypEKZ8dL3&AKd>s$0m96}}IwAU&X(E5m#k6k)z4~UN=hm?d#oVt+N zwnvgkQoWR9%6*>$35Lj-k3HGl%zPQA1lGixliMZdUJA6QoyCd-%dWF20r4l%7HJlt z^NM7cXy{%YeSkv)LQ&%63oabHWnnR-t!>f4;>>5r6m<nQbu$vdV3RTzfBq(9b%JF+McKM(9b44!qP7o-E#XV)-B6j@OR5e z=zE@?RM2TxQ6gw1mc!^7{x;)k)htE2s6IG?%8@NOB+;QufIMGeYs?z1XEjD4d)jWC z%6pko?HLqfT%JA6aroc;0+w8^KgpG;fV7<6Kzrh;F@m!-mvKiHp64TRu0W?984AP0 zH9OX-&J}Q6$j+%`(^*YnjvXhd0VXH8NIW?Df@d0Zs6t*=525dBBkyF4H(2UtBsws0 zg2s-(3g--LmxP1!&UhFY44Ethkw(t=NwRSpNk5`frp^VD z@DD;b!dXRRzV>V*&>j#{fJZce*tcHeC6l9&glVQtAoP5O9(e==9v3$z1*XSvf$4Zc zWyJs^$EQI7+n<3TsEVG_4c48#HQ5rxGY0D*Ru76zOTuN`)lP4sh%Cp!qOikki4QW%e0nQE1V!H||b zvHb}>OKnBn0;(@g6|u@EQsq|Tq3uOp_F(8J6yAPLqayWA({2C-fni)$0!}Ih;S6Il zDiMWXS@lfGm6Bh+4!O6SO%nbFBS)OY9pnDg$5^Cnq0@0z94H4H1nL0vH*te0{F#z8 zBO&yyWY#RQM?+W5S3aB3-SIsg@83_s35f{M`Ggl|1%ZLROp;GJGu}b`pQPo%^un}? zz(DIglJta@=T9avh_ecav6Q1;D^8`AJ=8it&5kKDOikI3GQlFjS@aagfl4pAvSIFZ zWw>0U-CQghQe4nUk$h_6IZ8O56K-!41iKKzVl=G1xV=dbg-s>lXciXEDZAydj;szy z(oozyKO+ffO$Kj~UGjQQX9qEwO#;IHQl>!F z$54H;kCI;~?Xu$(vO!0!n;pbJXwN>4r{bN4W5mVE>!e-!OS*<3_YzL;kbeR3YAc~n zC@$>fftqjr<$t1a8E!FJGxRT@7%C4_Of=uLjvs)SMdwN;7G9^ohT5^&r!%&xu0OQW zOv|^+m5Xh!^GTUX8&|mFQ~1#0dx#d`U52cW1oXnApvuFLp9v(+hB&pX_#slR_JP=w zhQ0}nre!rwlSTq*ST&6h(h!i$D1Y&$eE^ke6h~N_4>D6x64E0eQ}!_wc#YZJvoo)2Re2qzKiTl2sImX zA>6YFQ&1XcTjT#wie}d_^!`iX=hSedd`LEdU)lMCz9Mb@^wIo|^EzZ|CEA;7Jk~Bh zfl=Q-1i=;(m=_?d%|ty2q;;Ka`5IvE%>d_BULB1i1J7)r#exJhETf!y`5{`ucr^th ziT5Kssr&Q+hPSsd7Hd^!mrk&$urJh|MaP`G@z^&NyvJvR25r0I9!f$7Al}xpu_mZ& z3d0?(;1L?pj@O`}sz?l6$39SQ&8h0(N!e+63^9ijF8C=F48Z8)S%9FH-%WHTaR}4c zS}0;vrlwgKI0tpWdADFNc!IK^BcpbO)Jh7rkU53_sse=ts(>AWcL4_PrD#fa*Xul1 z$@7Wr;4;oF##vD9N=|Q*^GVyWA$H4icojd3WJwuF>B6T_SWC?nAG<4l04%aBy>Ndm zqXp`;Yq7FtZc{^phsvY50}O2ol|G->blgGF*$d;|7>mc21 z=sGP|kWE3HHl7)tLI5eeSvrMkHEbF^y-04M_M-N{Zqwti15m%Z0=6yriE%)5-qVtJ z;~4N!o(9BQrokm@z6JuQ$3=%gP;XauzF3bF(K|i&D^TL!h5;4DKIGeJvB4Eaky8-S zlOa>0CY|m06JpO!X3>CQdfC)K01w9kEJwk}=EJzFpJUGR%xAifQCHbHy6z1GWXlS& zh`TDAe<0m-(jCrp50uu;MSvA!7PNNIx}H;8ldhHNE-kJ5CHXZ|148v^T{MhTt1;E_ zOf{pC^I?FPeH?z!?4JVrccCs@?39#%T-aPJ&lJi5K)+}Xf_}kXCiLuvNERBEjyVDW zE9P(v7pK6gU(vj_nCTgqo>jE%l!W4Yur%GnJ^L`q;v(AARRpklV#aC#nZ3`<{vaiP zfo(^Co;>Cps0f8*rm*6kGGSgV$Lpxs7p97D(-40O`l~t)`&&;CDVv)k5wh@@;zDiS zmMyg?mD|z6WT7g_jHe)TdUz~=T}Oh+WIrjt<#UbF9|+E+=D3MO!bQ@3$U7orv*QGK zI5bguwIY9?6z4!udu~K-UU(*v47s;J7TzZbJz5(4UsM{9i_R>rRd-oj8BOuFS4Ib# zlguo617IWzaSqLvveXHzdV@MiRi@DvVMm$As}16gG!?1 z7u?0!=}>BIS;qK2;1QLYN0D?!K-_NmnJkPd?ieGMfM`>7ABw=yh{Z4yXi0J%q;IjZ z7~WuB1r|d)#y`SqRuscLW)>g7M2S8W0JOYnxVjDCWbsp`Rw=w-lrY}yd z9E|>G-G;7&akD-OoyN^zR&7sjmrBr01kijRoL6sR-R2q3oSZ7Hu^#~|-e1I#?q_hW z{<;TxsnA3Ae}zmodT~egmw~WFmfDX>D1HF>ivJU6A&%Vf!HWR1!U1lg0MzDAn0G0-ufRol7l4}sF3CGPeTp#~To&h@n(mDG z1ng7KI4nvK7=geO&oC|UE&>lc{j@+21a5j#9a^X(LgziSu7lC52<-5@Oo1seO%QPS zJh2E#*Kj;U=Q(d-szGm=UWGs*3>xgrT^6!eQ%-%i06f5wu*4unH@HZk@Hfd%fR%+N z4j4C7&^iX4)S+JpTFjsy2nuyxds8xfPUsAVeyyW@B~uom z89=d)8QPb|Sw+Z5&|V%r~v}9 z5X~UbVG~w>lLQm)C1drEyZwjgaKfpV3y{?5aH|a^cPTi5g0z-WFHvw9Mf&I?F}oE- zXvzNvf__$tRh-%dGEZ&bSx-7;LU@Mo z{4C1JMv@!DrTEz-N%J`n(T!LsJ{v*jZk#oaBD@Z?aOe-VweU9X8C5V<$(B}N)rVN- zcp9VSv!Oz^yhPypG#yq0s8vZYo?Lj!A&BDh-Jn7dVxgw5emytP=w#s<1hT_&4chZv1r&V>1zGc&K|u?G%kgyF463g60%=U#{VjB& zfI|ST*w%|r+T0WoiS75ZU3zkF))?*}#ad?>x&vnSu<4V{x344XmaDis)V12!kbMZY zD6SI#?P>A*h_IKEg$2Y0G&xu-90tc{7>kA9h|wOd7Yo}EFl2;vMH z0N7f`8$raG7lCCNTV3arvx^!uA5N?87}Rw7l+J-1uNB$#!>s17ds$X45Ye)-(yP4m zZH#>fip>*U(ptXJ)W6ZxKmS*K0I|rq$LNp1APb`bViIXBSiH*OZ)>M?MVQ_b2*IG{ zsSHIt-o1w6p)O-Ii@;*< z_5mYp?#QNo*OaWqQI2uU`Fnt6p&9O2A=%=Ja7J)jy+=h^i^VcQkHu*j0UhiW0vS9A zFnwf8FTiTxKh%x70i<=7$qptv$8phf0&T27m_*7>HK~nRoHqX+JNdG^;!JhkMZ)|$ zHCOj6=i@r!XKmpLy{r2bI<>!qQzYvZ#l9rkQVRfPjt~&_GCCHvpd3N;Z3uKjdw}Kk zWvm2noB7QYzv+~oDHx0hum1j%Zg{{8s^YLD5;+e@?iiKk;r=)(@ zrsrUQJ1}J8NC=$wTj#ALdUaz5rWsD{NYG5SZ}g;~R@Gk-LwA(yyh~V=b5Te|+#L+T zpX~(47s!@u;HrJSa^btGj*IKaU?YkrLH%+A8!9lekAKVG75iYgk_rOMufj$$w_+X< zDRg4tP91~NmQ4|W-{NQ-NT>4 z4D^YV#LFmJyV&6v)*b4MO^Wz^v?Jp;maBdxHls>x>cU1Hg2Km3Ki)so3P@SWv&3M# z^dwojmwh;+e6g?swG@_Fy;%4TOmIfc6j%J~@X|T^Qsz6}>g=(%8iLa@?^(=9G~8~1 zQ|)|Ox%+)3$NJSiUnf_S`s|5tdzGHs#hoL)&qz;F?QtiQ<924?eF$$>Yh&h4yXym< zy(2)6vIjxOduoa~01b&=YRQ*7COK8Z_-eB043gvOMBh>AlDpu6Y2j6w&de=}kp_T#rz6&ofmO^xD6ylT&q-SrWC|BO_x^)T{$)-%`9poj zXh7=zHON4rFbF(-(Du87rLO?J#vbjW19SqQEqGk!nf;i~5HcN&ioujbLoW6FG_sQ| zDI}^+l|^Uv9M`? z=*KvdlcJZ+*BP;VNU?;kRbE-%*s$<(W$^NgrUB$oHt%M{k_@8WTdTbDQ*273wdD4C zaNKRHv?uPdRXT`F#K1$bumMv>R7Q*}L@t3fJp*fZuLs#&!1=2FDl}=bn)h% zyJDl{s&~aOVI;DfZhcU|88U+@oHj_wT2U+D6nrgO@n%3m5vSZ;(YW7$pIK*7H?BbR z^iG#8Isxx_)4N^Eej~oy*&98L>MuO)4I<)(GB+4$e zi}f7CXyDAj`SrE4h%N1wJ@-l=l70yxwdX!k5>A5mq#|;47OXI$k7Kn(Z@s*S{--Uj zTJKRNu6)Vx{1;`-%JlG8VIt*O#Z?DgeGF3H=y033%2@X-r!-p?9rp-n!_K6409HB0 zyZDUSPRm+?GOA#kTM>+RB#!z~T*|~%$+n_1NSAC*hDW4pFG6Z2lHoP!%J{;XUWW&u z@?aP2c`=*T`E1P-5Wf-8mR}uNEgq&(;#Az>$TS_wvM}Q-#j-l3er+v|Y(^|WAnF>* zqim|l)b5?)lwVfAVOn{brKP&2HyihrL zBV`LEPEGj{%SU{9(2hO}(uvX#izLRbjjFQ?mTGhKMS8++#~T1qcu0nd_?7VGKPW@i zMwXjS{JQ1J%(WwWuK0@;QB^?Dnum~@1Z&SdOlLTKaPL*IeJEQXzS%On4l%?_NJDZ?DgAT>JrbkD*jO zjb#cOs{LO+KttShg5_)_h?)pX3|n|dt0}f1S^MyaAb`d!yc42nO((7TY$Q9cR$QWa z!~0I8ed)RPAjN{|?pLVM$GVyBN~hmyRC$#wW0t}KuiX&8d9gC~x5kDuiTTiB2wKy;@em;@9|8*Qi4yBHp(80=^f0N4|6E4Zai?Ui`tyg;#-C zwnS6Zb$(E?Hnb@B3m~=Qcgm&>?b_a1gck+if%6`wmsS5pkX9VP@xKhN3qG;_IRq zVAr6|3)`_5(eWtMMILIE@?aq=H#LswP*l$@RSs`#V(9wA=bNfK4)nwj_SsyPYjXuC z(&}ps(u>mL^x$_0rYDOdu>3M#P0>_V0!xu%b{wG~opyY943W_HQBXlxLP$rXAzDVD+P${-Bwz*40*#DJ;qKYaMG{ zV!HV#B8}B8v&j<0HuGyGZF3_>5o;nFj`4y2xUps&W|tj8QFls@z(d`egN^A) zgwyTfh#X`^(DnCyjl|)L5t{08oEdoMX^H(4!={$lfxjcMRkZk^D^abKYPDOSsIG^f zk_69&n{)?S%F%SOsc9H|i!-h~!;Ue#2zSa-Iy5{-5YXZpToTv)ZHOz1#YH9dMv34I z39Kc8mL-CG5)6VsU9%YeoYxa*^%)bZRM^_Sau>vdk%ru4aiLfyyRQJ zSYAY+ul%+GeWe*{rlhxFPoVC=c*908^!V=ZgmQDkk_|>$(M)2lg|-l~{*HORF5)=8 zHP-PeH1TTa@`Ge&Oh#x+a3J&12|Nam1CVH5mMx$J3cE-ZZ}cEhnGKPsXFtQHaj8UI z?}|i~L!ww*p0U{aY20vdYFo+=jq>#JK(;bzTf=gX{)9o`LS@Ca+Vw-h!!zFd5dK&W z7Ki4j%-Da?-}^4`;>}2mN0l6MegXUerFjIBAZVB z$tk;kPwg@fZc!|gLDUIfQC@aNQFnUg29H6`{_iWjL6D`sldlf?M$0lXGLozlJH}>I zMqn5cmcBi@;okimzNvJ^Y0L#<&?b@A#aL|ZQNPUhAJ2`LudLbLICO3Y7X21zs=K~X zu5EugJObskrFN+butG;|xJXIZk>I!uMYzBKY5WfC7W&l?OqBGaB*=wGZcZ&i)2vUU z!W3g302~WbTNqPH{lPw_UIe19fO>M;F*v!Pp?e86p>cR8*W}`zN^wRFY4WtJ!nKP zqdkB=$P3Ft=+EN3FucNN`DGe4qFmXy42&~3N{X)oCCO~U-SaAvnP{#6HZ#xrjbzM% zjuihK!@Xp_hr6c+6bgm+rYXnsI@Oh5e<1~t3O2!%b5;;Ij{8ip=C_S(I-jy8*P-?| zdLD`yN5iN`HoOO4RKCf7uiiQ<%k3A4LH!o$^uCzfE&;E5X1rxj6x!_Ol<1vth9oTZ zcE$!=Ddd!ocQ!E$nXfG0S-Z`H*O>9)LKvTA?YlcRSn)Fuw&UIKj1UB}DS+>2cCh0y z+0@p^1g#}}%ICZ4I<7ODc1g6s>_uaS=Q(NwM!Tf z);GO?ZzTK+!WZY496l*^3i=rS`BC|#AhO0&I$@#F{hYe72x=!N-4BE%yOgyB6AXvH zQQ`}`8h+fWOfF0`e6>^AT-Yi7=bio+7G+Zx@cvVfPrhMFFoe=lz<34-7FD~)bhI}X zUddOI_Lw@vmNLsFvu|}XGFYk`SVd)LdvU`h{8o(VW*#QyN~VV(2-c1kD@B~iQx@*2 z9TJZPj^`dViR*Kf{5|!mjD${-n0*h?Cri(eM_-=ua8I4^v7h5C0BcV>-dPXFt7u;? z#H8n1nx!<`8);ZPQ|Y?5wc(FkW#-;GFSpEMqNti!V)Rd+^33;i+#ZbT!88No{RDC9 zZ`2UdGUz4niP743W$^yzg}qfA7fIsWIlk1}tXZFgbCY9nnh=$e_;`KJP!ojG{8oY9cpsE%sm5T`aK zH#pFa61V4sWZ~pTz#@SP$x}(${zpwmF_r6Kv@#ky?ee%N6z4klP$$ozM)FB>^}fWI zSgprZpGDsQ)OKu?56Kb_ZDcM;GB*T=8Ko+SEL6kY)$6Hra>0WMRV5rsmIvTe0}%|P zuYl4kxLwXVus-gpO8;PFi`s~UofKCI9!!mr@F(u#s`!%>h%dk$GtobG+! zS(ade(uCVYa*N8@1E#4QZOW4pWaE5T$);JF?M)E%8X3`78U>Z1md=Uf%ZB{pivxi0 zYeu+lhYS5_?CdkJNV}^a+-!wV{rXpOBCH~Hb3ENG^$DM`Ua7yoS=IZnmgDc@j^rhg zV8sk2eSe**HHjByVu&aAbV3y?D+~75uicL6nxcug%+Fq7mZ1o#?p4ak{gZ7AUZajy z3FlTgK%gZ6dp+R;%oCp*pfya^a^3&qManMw9HWucM0yXJ-&QNc1yW0ZT5;J^Ll)-hsCbN82i$|LkDTXse9_ z)S>5tPZwhk-4Xj9L}v%>Z5qzxmGG?wj18u?2#M2LBibEdh(gWAaU(3_tF={5|7jU| z)AFyg0A|>CH*$>eN#C*Mj;X2cL<>QgzOxPX8;~#@ebx4?hBr78F2e&N${kx_ijlo2 zAIx!6%ztl%7@QVq(pn|!U`>O|qI`X@i#Tr@Wm}ZoSoy0DCFRjUTirp>S0m8nXy4Wg zeFv`lQX*pWX%IWoM(Ka3M%*S0z#7ySV_R<_3t^0pO>$@kw9~dc2}y}~w~ey+kg3Aw z7`HPkq9JZkb{wkfcmqNF8d@{@_OZ|w0qxtmI07|OfSqkF1gEA?rNflkOxK+!WlYcBwC<48b_w7H2)>Aj3BoxE)bwW$IU$InY!8 zfn#q0I}o_r!rws-(diHS-bBQ4$j6##HH+V?!)u}25oXy!n?HlRW=G~^9!r*6ov~w_ z+l<0rlmO>*;lR7SP?)z-X@0bsVd!S%-J_BGFeU3~ctS&XK>mVJ49MR{j9Bh$gw>?a z5G}qw0tHNSftcn3!>O=1?|k*bXUf5&Jq?fDO2cEV5}%%<>M!1a_m{K8?54<6x^>D` zmtlES#wS+ipHXsrr%a5E=WNmgbv{;2=)o_+bNnXdi(@Sek2flX$6ACnWCC$26`3sL zW+=hOo4h9@A&5L&tkgF;s#vIkDDX5KzW@N0iU>F#28pwOfray7pg5b(eDMa|Uzt-<`=8>jbzy}2_DqlpDGKEH#D4IujC!K@;#Km6OEGU z!UTubf4SZ;;K15kje;fCn=p`}tSsArs^vb#6h|v*S-7LdAtoT5L(%Wm3IBI z#Y9E}VT5dUh6=JN{UNT3uxPwLuU755CCua^L~1-tJOgPK9pj`~)OlU8oeVdOS*P?r zS$9AbsWgU)c5qo2LBv&oP*WDQ1Y=u*(DY<;peDSr4#IFU!I5j2I${!WcyjaNhBFBp z&Lf{HZBIrTj;y7q99d*1lT+qx3WA7g7@=k@sxJv*!oixbGYQ8tAw_jy;(r^}OQ;c9 zIJOo=SH!>g<(#2b={}x^DU1FJHB?!rto$pA4^sa8>odNu^46&wL;25@@YAat*AAn% zC^TRUh?}!DRX9Gkim5q#8=7Xq5SOQJItbqjk@nRtn zXjDc6Sy(ax5RDabRs7(f`D5n7l14`VkdcFiD_Q8iN>lTmL{C{np3!SkDOKs>DvJkF zXQ3fovd9qO_iO-~4-!}a&^#U>x-E=k(`d4FnN#CHq(YW*wZkWfh~i=6A{Iw4Ba)v; z(Yf1^gmH*Ph?mm=`8ViCC_!lTAnG)#b2hBX!Wd>&w8Og_uUA7T<(0ql;CS5vd<(Yy z9m#A1)S@7%xF9MMzZ8k&t26P`h8eRa?ex)Gc9yzOV`Ql6F zw(W6RE@4%}Ne{&Kmeb%Q%Q4)Ymc4}H=Qi^RGyjgex(rYE(kmgvm-AN?G{1|p{6+q^Z)DRqaQxYnw>n`Ftg>mN_gq(SSl4&{^ ztZXF^SNlLomZWx|*q?uQev)n7?;BXy;_JEDFsKxJl#vPYU3m|m$sPcGumNRCqfLWF~C)lOPVNMjkbq>xscU?vx3Hw5_zI;ANo_3A9pcK;|-5DdrH(?S079EXpCe7k)bt!Bm?=Hhu<1 z8`=Cr#~Eg7K1S#JglY{~$HvY(nDU1@0?-OY@GsxyQYGhd3 zXwW!<4xuGt3<#s9>kLGVC~Ys6d#w+Nj*TI0yaIq@A#*Ry#!7^WB>r`RGWBA8(?U)1=qHxuYanXo1ZCUB#*R(IchCIyu|z6UwJaulg;w(0z_TWT z0U%KmPa*M4P5gZ+abrz9h{PXi;#sA{H8gQM5_chSdr26Ffc}L~(>vgi!B&h|B1kP9 zjl&=Lje0Eo0^woIJAS1^X#110;_pbumE}zMCy4qggoVO*uoyErwF1_W>P{+M*}Owj zTTW_K;F~QJQIT!}-lU3jnbW{)L+qN-3>Z`bEGD2(hzCF)u#GgfdPxzr)>7FKxccUJ zWxyqq-TNa{u{)yy8a=!TM5wP&C~mhB$YsQd%G@N+(hN5Z(sqhj!>1* zD^ZoIz?o;pBLL^~C|@5bH!elBTKgXPG_GRAl*M8g{Z#z`B_Ro!2zuI($y_oSi)adk z)a8oxa$KcJKS1{MA>eK#d-)?4iH*=d2#q3C(9nv6ensfbT-=V1V!DUF2l_st+X-FD zP{$rZ69{z?I(if<{xLu?ZEUFHU&jxci}lye2hd1|nr90*yrbnYzz_qr3_VOjgJH$t z98(hBAg}QxluaYa7v33D?Xto9H@psmwpxiC$`2#S-tr+K93d-Vqo1<5gyqMSIoqnX z%Yg%R1q#EH`h<2|b#ShASB!jYeH*_@btDx%|E*-{xV2|8R!BXp($k*!s@hJ#-BaS8 zv02+OVqKl97lPM~YQI=mdn-zCn9-{_6gg4;_vr4-4?e+^rRfe4R~{$E~>|H=7rwCD%8>LXEMc#*HByvt`H=w6|N6($+)Ir z6`Abz`F_Ub_ZdF_U;Y{7^-IY}M|JxgO!?JPihg6F6bVIq9tl{9t1t8@F%fljyv!@+7?TX5~fbpY2% z{kjNB4<3tg?LZ{|Hy026P+YZe#p! zbT{bjEF{o~b8-bk2I$r=(34h!Nt@^M9R%%~r{bf(gTm zpjB}i@fp1k3i<`;K+x-;GeKMZh!zhj<7r(H=ti94Rjq_Zh!@rqK+{2cfCjBbor4Yo zT?Tr0Ek@(Y9M^mu>Kydd-%v83y+FrarsG<0GRN<5P^G72;Y#8b>9x*tMkp> z<&6kkaJMk>RZO)zV;IIbK_5G=skqYIy^Z`E4r3R*%R@Lms2uJRu9m|y9QVqLo_UkcI91tzKBv-D`4e;E!MH-pmt#MfE9C`yL%13O2_7g-4#Nk ztfAr7(6HA-gRK==6ceHLHb%4|rQFePulx+mtz(p%Wx(Pw!r9yxYVy(U-$MC1)?2`0 zy5j{%P|g+_X2`F={DvbKg2yo7JunmcWp+)L09h<#ZF^#vc6Y1J*K{`%`5Hlkfo1k| zcMUpb1Sa;vdp+)MB45=#DU26Gmjg=& zcAE53-JzBEDEF2yzCqkm2yQ^Ijs)G1@g$Zx!@$sBR`s

}U1HB}=l0_2!gTF3NB#?dqX}@=TzK-tfN_-u2G@u+n;{mb9lc=}P45&Y6fGsr2 zu$CEHLxZVyROnU2H1M8#ZYUx&ROTalx5AVWjUgUC)`#aMWlQtmZ-6G1;(q z-bXB&zw40oALKq)88sD*DJgT1`*CHyL0g)>PQs>aJd_6d$%UJc<$y;Tq6{0fL{L75 zL#Cv;`&Z#R1Z@J}XR!O{D*Ve`?t$-%+30h>%>d1Y@*0Mz?J#6DG+9^=q>frfEbt@1 zhZ2rY6s|VF8ouvtT9uD<&{R1LGaH6|_Mx=Vw3!Bs+TYOzXKfOtyXQoQ^BSJe+&3(saftHe&n@U3`-=m){ekIh;H2% z)^_mQfNuq!?&fRo_1q1s@pa1wV;ZL;;EnEX)%b>n`y#c0>QqEz(F@WxpSc%RL-i~J zA27{*tr}l5XeW4in!7@EzHRwnOz^KuKHvAO^9M9Hw0DoF&R28TfUf|K>6(40NP7YM z4p>YnjDDYIT^TG3*iK-W(AbA;zW|mCjO7Yp!j=O&01O#pAHuc)%aeSKwe|skisT@` zLMVPfYBaK?hXywRs{*e%!`(ETZ}e6;Qu-!d9-)3pIs;H_)JB>E?+^TI>QvNz2WgFj zWGRrH!%H?@!IRBoz5S#hI25unU zZcnkW?E$>Wm+r4>@C_of!1sa zEnMjCQj?FY*982vudx=k??63WrGl?6#;8c`fTj_kXcY0m;+gPEU)}y)Ci=A@a4dxfp;VuY8#kLDL*;DyZ_)0 zir|};zXv`8OR}2UZ24{kU%#FWt*z}(K3_c9vA8~9d8GOq2wB&k+&@I{4a(DU&iXSp z!R|E}0UCA;y%oUX%>h1lCB_D_?}M>fZ*M1oFJ0-5j6^582fqF)ygfiVXnSMz{8QTt zL+|JVSyx?VXs-z=PfZ|mK~{PIA>0OhdKsM3+ZVVK_%rhLdT7{8nwLHca1RjqAou1- zUJRl|W{=hG1Ce}W`Cq^nV!=6rW~XR(jXHe&Mo)oV#-ejEd0?es3r#hAQ6V%8QySJL zQGlapy}90(zdw znTi^EBQ%&w;5zW3z~3MoH8(*o9m;7ivhIegImyyP!)T;NVRBKxR{_ULk$p(F6|gnH z*b=OZRuk0nQ-E*S=nL@8Ebm@fg|F`ZHHz;TM62PV9QWfW{`K-t!3XF1e8ReS+mGQl zHm}D=8R|vv)Li@Tf97eN|6gp&=PFi1wVM7vsr6I8dec=THdI=|NJ}D-2ID^E-S$lz z`Ts}Oud6*<#k*VV8Da^_t=vS$8wbYl%YzNOFKn#Plz*$PJEt2T7=$$!*E7#OqZ@_? z|DUn?{|%NL`1DU8_=~^0e}-P1U(f%iuBG*`B7hzt*c7&}k`2SK>1M%9a&b;f> z82?|Z`sFmR(#J1nf3d1T>`o~vE%*P^s$YeYK%btPe+TFP%T=t}UL%2D;VnG@rQ|zG z%Tsl^|1VfcMjyXDo2L=>{}n3}mXg2400;V*Ugc#Kdh3AyuUY+ae+i`N2`DS~C%~c0 zS1k5hYP4g!cCSXocI?tKE~-WIjW@dSX*{ak{bqN*1HZ|=pgVq`WU;$<5B>zdd}E8A z*yA`7bqBPX=65P9t|%-vaq@`ngrYU+=3a`nNZcIzB07CCyP3l{4J&0s(Y%6S(IJ~Y+Q=Ox|I0yYZZFhPU4HcY=Nm()_z zr%eofI_Ubd`)lDL8sWMz++pO3b%B3Z)LS=j4bU{{Cxhry8O@gxl%$ss8&QB$`X~d3 z-eqbW+ckk5q2on^G@R|2z|xnFNWb1-1kgsfF`As40s)Vu#uqj)`1rfaXS%^WGQh{b z8J(x&w{*ObX2yL3N(n9cR0DeH8Te7xm(w*rYe6V}ZjCOm=w%bF$+?X>{{8zJuujKw zb-dRw4Isz#IiTa(@`~eDYA|6Jwm@K?>GcmV9n62YgfKv%Yed>TEKFf7Mgl_OeH^|ZP z7~LRHFH(Qi#OeB5$7+i7iX?rS>iWTD^jqorFHP0-(u4IL-PV1(H($L*iY{LuY4VMj z+3548yKWyIZ%i?_BA4?_gJ~&6pMJVo>epJ>pA6c3Q$D#e$@tKZ57N^crP+mNFH1o_L zOi$@?UH{5b%@14QQI9djM|J$9KDbuU^^1U09Y^aC``g52_mch?YHsS{ zs50Vvx;y_;FH(<&j?SnB_*bH*^n90tU&8&X+l9KmQO7fLG~f#zucqM+u6(YBG|>f- zx`A28PwP02R9H<99H7OxSjWw}zJImdUdP+&xPNK&l8$%Oac#+lTK3OU-E@I}`A+}s zL7!ea?qBBD)C;yTaB6x{12x4gU4MX%C$-lA`f3?{(sVy<3$!rXM^Y;rsS7^P1+=uH z&o~{I3bpW5gz57MDAnk|GEIrz^dsC}2A`qhNo8;axO@FTzN%vsII2A3J}m~eMFqQ1 z$6wKLecIvvSJze=TU8N-XWCLKw)kEV`Yu>YBatqIdPUKM#Xt>8HK7VBMu@yZr6@}u zy9t%5SfNsF`!GW^m^OgcL=%ajt>QuxN&rKRROJUD0Z~Zf46NBfR?@S_he$1Eq zojJ>F=iWQ_y}fF&F=Fu(ZSS9_=fj-oR{rjjAm%B52zJU$i5jA7DMuhM_e31DyHwH^ z%o-3e4ZRMrR~uBd>WCVD&f}3;=y}(-z!n7;g^rse<8{QVz?t-BJf4NM ztx6{|3+Rs%T-QpyQR+6S_ey<`>+leHjFW|u$f)!yA6tS*e3qwI%*=vx_3-SvU^o!k%-V?tM#e~UOb0`onxGcQeWXZ%%U0OUikNBf4Al53Fa^9 zWL9dX8jH?PQ}N$x;pGCBgPAR2`xIa)U>%?lunX{^KzNW0#c4a4$gcfX9c+k<-*IUF z1o#zjRlpstvsrdLonDLz@1j#?4nMgZ@fGP{lq;Xz0b;-M_qo10n(8XR^8(>3)HrT# zqwkAorNeDn)X4i9n?6|^2kkC*UPNr@s9sgo=T1Y%zXkt;R@v?@T>Lz%A+E-3jyl^O#mn0&aJcw`<08YqWF&s6`oaR zpZ^R~i`C*q<*Rj<)++y}sKsqAx-j+y@F}-ql`XP`F2%M>1;T0Cs7L3kPHpgy5qKvg zB8#C4+KT`wz#D+~0Bc>_YI3j-SG@w^arN_-j`|q&!{_}x&Sl!S9m=1B_BFr_K%pyM z!?`XGI-P2(P<6^6o=M^ug&rOJDiPaf0LuXD0NVh~fIWb2Kp)_wKscn%{m~y~1}T3j z-_(F>x|Cww5A_uD7)oW_&zQ#in2Jk8Y-a!-2P^@k051!;B`I1MX;2>lz61;a&H#o4 zg6rgvHs%`0@Yh{IyaGaPQeX4EMfZmIIe^mxJZA=DmL@t#v? z-=S(xDnCQ>x91mOsnompI$II!qE@`-{KWa8?Lw_;b*(=?umcI>7iq+TG z%0Hr;?RDjkDxc6n|5W)C%KOt|e^(q7-nilGZQNc&#Mc40y7L=oAo2ls0?Gu!DRdse zbNh`h#hsM*x$v)Xn6LOA*Q@D;hztF&f^98=cD=SL(si!4G}EAbLOWFfWz!^T-J9$v z+f%sN<_zlz8|=6dJx{-BPYuiH-|osX9wj$okzcZoYG1gQ1 zHk|;|uN{Wou@(7#4mL-Uy9% P-B7E2z~;H)Haqb@+Rd<- diff --git a/st.c b/st.c index 76801c1..24cac0d 100644 --- a/st.c +++ b/st.c @@ -35,6 +35,7 @@ #define ESC_ARG_SIZ 16 #define STR_BUF_SIZ ESC_BUF_SIZ #define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 /* macros */ #define IS_SET(flag) ((term.mode & (flag)) != 0) @@ -42,6 +43,9 @@ #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) enum term_mode { MODE_WRAP = 1 << 0, @@ -115,6 +119,9 @@ typedef struct { int col; /* nb col */ Line *line; /* screen */ Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ int *dirty; /* dirtyness of lines */ TCursor c; /* cursor */ int ocx; /* old cursor col */ @@ -184,8 +191,8 @@ 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 tscrollup(int, int, int); +static void tscrolldown(int, int, int); static void tsetattr(int *, int); static void tsetchar(Rune, Glyph *, int, int); static void tsetdirt(int, int); @@ -414,10 +421,10 @@ tlinelen(int y) { int i = term.col; - if (term.line[y][i - 1].mode & ATTR_WRAP) + if (TLINE(y)[i - 1].mode & ATTR_WRAP) return i; - while (i > 0 && term.line[y][i - 1].u == ' ') + while (i > 0 && TLINE(y)[i - 1].u == ' ') --i; return i; @@ -526,7 +533,7 @@ selsnap(int *x, int *y, int direction) * Snap around if the word wraps around at the end or * beginning of a line. */ - prevgp = &term.line[*y][*x]; + prevgp = &TLINE(*y)[*x]; prevdelim = ISDELIM(prevgp->u); for (;;) { newx = *x + direction; @@ -541,14 +548,14 @@ selsnap(int *x, int *y, int direction) yt = *y, xt = *x; else yt = newy, xt = newx; - if (!(term.line[yt][xt].mode & ATTR_WRAP)) + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) break; } if (newx >= tlinelen(newy)) break; - gp = &term.line[newy][newx]; + gp = &TLINE(newy)[newx]; delim = ISDELIM(gp->u); if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || (delim && gp->u != prevgp->u))) @@ -569,14 +576,14 @@ selsnap(int *x, int *y, int direction) *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { for (; *y > 0; *y += direction) { - if (!(term.line[*y-1][term.col-1].mode + if (!(TLINE(*y-1)[term.col-1].mode & ATTR_WRAP)) { break; } } } else if (direction > 0) { for (; *y < term.row-1; *y += direction) { - if (!(term.line[*y][term.col-1].mode + if (!(TLINE(*y)[term.col-1].mode & ATTR_WRAP)) { break; } @@ -607,13 +614,13 @@ getsel(void) } if (sel.type == SEL_RECTANGULAR) { - gp = &term.line[y][sel.nb.x]; + gp = &TLINE(y)[sel.nb.x]; lastx = sel.ne.x; } else { - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } - last = &term.line[y][MIN(lastx, linelen-1)]; + last = &TLINE(y)[MIN(lastx, linelen-1)]; while (last >= gp && last->u == ' ') --last; @@ -848,6 +855,9 @@ void ttywrite(const char *s, size_t n, int may_echo) { const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); if (may_echo && IS_SET(MODE_ECHO)) twrite(s, n, 1); @@ -1064,13 +1074,53 @@ tswapscreen(void) } void -tscrolldown(int orig, int n) +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + tsetdirt(orig, term.bot-n); tclearregion(0, term.bot-n+1, term.col-1, term.bot); @@ -1080,17 +1130,28 @@ tscrolldown(int orig, int n) term.line[i-n] = temp; } - selscroll(orig, n); + if (term.scr == 0) + selscroll(orig, n); } void -tscrollup(int orig, int n) +tscrollup(int orig, int n, int copyhist) { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + tclearregion(0, orig, term.col-1, orig+n-1); tsetdirt(orig+n, term.bot); @@ -1100,7 +1161,8 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1129,7 +1191,7 @@ tnewline(int first_col) int y = term.c.y; if (y == term.bot) { - tscrollup(term.top, 1); + tscrollup(term.top, 1, 1); } else { y++; } @@ -1294,14 +1356,14 @@ void tinsertblankline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrolldown(term.c.y, n); + tscrolldown(term.c.y, n, 0); } void tdeleteline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrollup(term.c.y, n); + tscrollup(term.c.y, n, 0); } int32_t @@ -1738,11 +1800,11 @@ csihandle(void) break; case 'S': /* SU -- Scroll line up */ DEFAULT(csiescseq.arg[0], 1); - tscrollup(term.top, csiescseq.arg[0]); + tscrollup(term.top, csiescseq.arg[0], 0); break; case 'T': /* SD -- Scroll line down */ DEFAULT(csiescseq.arg[0], 1); - tscrolldown(term.top, csiescseq.arg[0]); + tscrolldown(term.top, csiescseq.arg[0], 0); break; case 'L': /* IL -- Insert blank lines */ DEFAULT(csiescseq.arg[0], 1); @@ -2254,7 +2316,7 @@ eschandle(uchar ascii) return 0; case 'D': /* IND -- Linefeed */ if (term.c.y == term.bot) { - tscrollup(term.top, 1); + tscrollup(term.top, 1, 1); } else { tmoveto(term.c.x, term.c.y+1); } @@ -2267,7 +2329,7 @@ eschandle(uchar ascii) break; case 'M': /* RI -- Reverse index */ if (term.c.y == term.top) { - tscrolldown(term.top, 1); + tscrolldown(term.top, 1, 1); } else { tmoveto(term.c.x, term.c.y-1); } @@ -2477,7 +2539,7 @@ twrite(const char *buf, int buflen, int show_ctrl) void tresize(int col, int row) { - int i; + int i, j; int minrow = MIN(row, term.row); int mincol = MIN(col, term.col); int *bp; @@ -2514,6 +2576,14 @@ tresize(int col, int row) term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + /* resize each row to new width, zero-pad if needed */ for (i = 0; i < minrow; i++) { term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); @@ -2572,7 +2642,7 @@ drawregion(int x1, int y1, int x2, int y2) continue; term.dirty[y] = 0; - xdrawline(term.line[y], x1, y, x2); + xdrawline(TLINE(y), x1, y, x2); } } @@ -2593,8 +2663,9 @@ draw(void) cx--; drawregion(0, 0, term.col, term.row); - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); term.ocx = cx; term.ocy = term.c.y; xfinishdraw(); diff --git a/st.c.orig b/st.c.orig new file mode 100644 index 0000000..24cac0d --- /dev/null +++ b/st.c.orig @@ -0,0 +1,2681 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); +static void tsetattr(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 (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(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; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, 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, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +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], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +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, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + + if (term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st.h b/st.h index 39cc054..36cdb65 100644 --- a/st.h +++ b/st.h @@ -81,6 +81,8 @@ void die(const char *, ...); void redraw(void); void draw(void); +void kscrolldown(const Arg *); +void kscrollup(const Arg *); void printscreen(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); diff --git a/st.h.orig b/st.h.orig new file mode 100644 index 0000000..36cdb65 --- /dev/null +++ b/st.h.orig @@ -0,0 +1,128 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +int tisaltscr(void); +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.o b/st.o index f89c0cd11dbcb2ca5f7d9e18327129eec41d32f5..1558670156661b1b52495e95f64106a391058ff3 100644 GIT binary patch literal 77024 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 literal 73928 zcmeGF3v^UP7WfOFP8tXp(os-QQ5p%72x@pK5!5s^a-soIa74x7(FqU@NhBQ-9Uj3> zfHpLuqM|Y*K8Nu!GU^~IVj#R!9Kk0lYDAQ78x$2G0+PFT)!s>E!*TxKx9(bZ-F4?I zy8E18?Xzpwu6La}Va~AJD-v8Tiw~D|nbmy7)Uxa@%`{u#Z0i*3WXl&m>d*K@OFQGcc~Koa&-b;_^$W;$#X0O}jKIlLBS6A6-)wQ!vv0ZP6 z?%3}wYiozTPYp#~Wk1zbH&$G$Z;p&=%xdtAJY0RSqIcH*If;>y#;jvA)9t`!dtH43 z>D}y3Km$UXU62XxtV6T+)UCz%;~(nZH`T>Epku$*szBDJnX7fkGd5XDW`?!3VP$@3 z0N(8;b4nW3EzuhxjZk`&#EOKVPv2S79%V!I#}=pnNX9^YpFfff>F{|*HiT8l%=&pw zo2;M0KYK=Q*OArHtufbYs3QKlwrqQN|3RfHK_$pDOc~?a6rGFlYu)Rj388vdbz|8; zd#N4ixDoHSGuGQ&72Jy_dy173&&d|&M0ixHHaR@1b6u#>6?E0%-OBzZCE<_lj7@f= z|G$3m6sLGHbYRWwR9h#aW=7|*vd7kf;h$@Q|LRyX^Pbe`E+`)H=c&A>hG(WF+u>is zANays?SL|cYFFjVgfUiGuW&;s>I*eoTHY;OOCfg6;PrNOW6*;|Joz;@Br3YYyCBJ+I^@xDRej2u^~sJLoL*z^pSi`x8>VaK=fl>G<>Hxp@g48;>*< zn-{Uz7~zo4VV37)Hy11$?tw~`98OMv>X(YuukPSRcW^D}-hTq!2O58mKMuK`OQof` z@OEt~ytB^KPSZZD)A{_ZF0WsQ_lJ*#H^;wFRI}~P#WrixtZm^#8o#EasBWA+Mi-r| zbu$J;%Au;M0v1n(>mH#WT%lu)!Ly*@gUi#xP$)V+7`ntN+l5VGXuUgYUHN`}L*)9# zto5EN*6TuFcYCb5Hu!DiYL{K3vN)a$*B^z}xkA6Wg6~2t4Q^4|9zbooq2{`==KdUi zu|-SQIVXG!TB&fyG;M9D!4>QrYDn-@<$&kGrJaoy%?_m4fi{?(oFj?ux3PIls>$5! zPs0*mRUZsyQVLZIV^`OD?#I>_#N6>yec=P~3Sao_Vc~peSfO=7?5j!f)s$H^UD90d zR3v4o(lO^FO0~P|Lx&o}AJn+heBli~*8$Ih$xyTWSv@c|&!QXPqPiC9?E%lox9z|| zJMfmh?rShm7vjX^{Re%mT%M{vcvE;!wB3=$#;hHlMcv>!C$PZ|#O-z8Hm9fedVkF< z-BS&NvfbGqK6b*?930ll?WyhuO0pmUp6d6ZIzdXSP_11vGcCzK=yFe02e^v80zox{ z)1c5zCm%;$zHsa6M$e*U7<5__h3t)l0oj4UY4*B3iFV*^NDKVvv?#yp>YV6KIhg$` zB4`XXKW!YK=U(RAB$R#A{$nqaD#HjLbe7y%u76 zO*H@pycD30Iw3enmyzgz-yk29lV-Qp)q?`MK)@gsFvrv694!H5JE@L31tkIAuNnXr z%sNX;k|GtR&tn52`1rv1*XWKKs2l5W0FxVWS7X_%anFZ}6wHLdjHl{#07GiVv^mP@ z(*~+Bj;bfCRpQYmeTfDFunmj*`5bC&;5rUz)Ze4(R_WhY@K=?19I4*!nB zDQI85x5K~K;m_>w4_Kj57#f+1_O@%Iol#fz?TG4qw{NELr5@y{f~rn8tK5Y68skr@ zzOSaMXH~E4s(rx>7_Mnu;dQoW?MA!i?lu!wxj3#@0>0>K)hWqTFnat;@0HsrnRDQtTUZBgz4};crHSzm0dN=3R&4 zz%RTbH={l$tJ;Ebt7qP$aHnV0AUp7hT{Fx&=pO|pZw)oJ3!WQlY#+=HHJ%x~IegeD zlj6_+p@H#5e;6Dq1V{d{#Fa`ym5?{vH6^k+S8=qR1fvf(=4k|eRo@xyaGjGW9JN$NN*X5zND;IQ3To;460`zKo!@y}crP{79?5ucj zTzU80NM4#dH~d}AAj-Q)u-_Jm_YhIOP#ClXeRU#+7K()PAkG0VT!nEYz2!EEk-yiRR(k=;nK_hG{)>OF z)9otT9$!&Mnc=B=3QSi$nyL<24Kpv+UFhr%)W08|IVgU9wBZo6qMZnRGABuwpjlpB z7qBO5690+9c7w8SiEf+odHAP9?4m3^Qb<&((SFb#%X-_htTyX6&$4xGw`3iIpQJ5i z?yR3@RcY%VX*p@{jFT8&_*Y-}V`vVNvObyJCOihov6?YyNxrO)X1DW)qdv$bsDU_V zsD7(rrp^YO!*20hy&dI;!Dzm?3H!75`#mFn9#*~2v)~ArY6L_f;CobE{eHz2S=;8c zftCnW`yro_JPR8@Iw!DEO@g6^umh0QYUm6ysj)DG!jL%`eysRHpPn&11SCn3*ZIu% zhPqxw#bsy`pCnJ-JQN1pn-jet5ZdUR`@Fm=u~JXo$tPTTBG!unJLMa`e}>zt2rBZJ%K*5sw7#{aI9>RB)m zS`;0JHV(#33vr;UuVM#rklC`Z>3XYLFK}*ZjjDGUN3aR|#dGzS{zzIcUnDn;v_fWt ze|Qs3j&=cEREMBpUGO7EoUtQo1Poo7|e#S4KJ}!4$QV z9lngZ(A=>5G?*X37%fSc!Hmzc-k$Z=q0hr#f{%WOsjI5uY6U1M{JS<&H?qO5U~Kh1 zWtGRTE9{rizkxxjSGl=Pwe!?gwFhU!-)A+B$<_(Wz_|bU1Efq<1?^aL!GAKo3~h30 zKXinSLz^5)?v?fS%#pc)^;bsR8_`J_Z|7ue%UM^i{BY<~bOogs#`mr5nk?;z{-@bt zSYT3-*x|t^+c0tqMU!%~)|YM5>4%9CC8lMw1~aPMNZMVwS-U);fr(ICe}_tDhd;>G zOL)0@{1>|e8qLt*3qA9D;<#jfH~0>3%L%yW<+}Da*3UisA*{@Ks@nmE!tS#CR4iiy z6oEq5M7ksKLeiSZ$v~hA24S3~3U_Erf?mH-^+x+B2tJHOK@ZE+{~rGgZ%PiSM7v_| zKwzi``s`TjO*n_ZY|oO-*`8I6Yb$}O+7WvSLbOJdT-&*}#if;Pn03b=iegP}O2O5C zD4J@DrhzEmcU<(9&0#TYkJegVgTxd8XYu{9PZWWMqKROkq3+I8h26X1hG_1-roPZq z^#}y&wJmqk6h67E8#WrrWtYw!bULUlyWPCKJ=`9jLS@))uFpm`RB`u&LekrhT0u!=u;lS2pvO3EhT4s~_cqV%fEud78m(S( zG}kAzobIkmyHZMF@LY4vgw1ZXmX@e0P?pbK9+gpH|D{58fDhcN&l2Hi7JePd7uI2} z7fZiyq$SssI5*Id6K8%djBcWRKt2?Am94p{&Yrt>2k5cG!8BNMACs18L(e@h7kbF- zTsX6rT%hD?09#6)TX^YkGb7{-f@5^Z`)$f|LTKH`- zzQ^Tzx9VDVfBlhIM-q?0{aMSLX4E@qDnv z@@4Jw-2G=zFe314&bsdsbHW>_A&z0ZzR(;uEZ*VZlzR0mYuuTt4;q$Loa(81A46QU zFs(-{Za?U(+GO6+JF~`}2BFaByR)`=7Old-zo>-zIljyAd#9a^dL1S5w?MC$tI1O8Yzu?*|=N zsf+i(*w>u~gX?NshiSeezBMN>9tKa}@Uk9^Y%+Z{!#hHjxnsjYmcwJX{?-(7KGwRW zXmsofJOvw1iCvFpA)KDYJ@avTdT?kfciFz?Xa|1Pqd-XLaCco&T3MdSE&37AO)q;oUga3jcs>1DMbp z*G?Gkqo#~6VXZ1SC8uT@tYGa6{<-OHxGQo6l-C{8vqr$8hzkoMG*GzYl^fPA!-z;) zSNqThIj%2rLK~CfuOFAVg)F{j2@2a1s z)vbYgO)e+A)$oWvEgjrZ{eJKQ7$fSU<$V>q4AKOTqJDWA9>{jFs%p!MbXmkU7CfL^ zr?pBr{9|)78s4n?C1@EIL_kz^KFSpIDK3HQu{!2rdQ)mV)eCX7x&!iZpiWTTYpWop zwLXk3{FR|!^?IN^Tn_DlQruLW)}h-SmN;YfapIWK$9Y~oAj8$IB%g}(RWQI(PZ3pI zQ1UNTCpwj(hQ{CsXio~@F+#F7W*j^xQB@Tzc@{=8Z=MVaR8TDGI1`TVhO`prZpx^I zN>ur`DRw=zVOv!0G=-@|tW6mM{TT=}dlYl(2Jm&0Ae`vVQ$=8hH*Q8B=rlC7nRWQE zsLBWTnWYYd6~7Vqi0a**s!4cK(>tw&kgnTWyK$}`AJp_SWt~}5-f2BGrCu$2yh62B zVJg)FqNaS&mtd$)qQ1m}-R$5up>&#;&|sLCDSQb%6F$#}ORIS>qAr^X>G4?(53%6! z)#*ANCm5NK>CHK2*W8+3ISU#kkAGk6eyCEpx<$hlsuwR1RM*-b|HfQ+ZU-YJe_Chk z2fb(*u7sw>ZpEp;9?9Ss>7?B7_c@Sx;cd`?hxf&)Ydk@RpzoOsoj;%X(-J1*vhZXW z8n%W}+1id+#xBLUnr()PbE;^$FKBMLo4n)rh=LYau~#$a?-&VohCE1WaLvBPuV>Eu z*f{&xh{%}QBwysVWA?i5pzGY!(Ff06XP=Q%1EWcw+jjlrc_p>_aM{_g+5kh1EoJ+2 zVA)|r*0!=wl>K^1woY$EfmO{TZ(yvn95?4o=!e6{q7QzFLzRs@IE=coqO-pZ|EO02 zLWf$!5ODk#t?=y9!C zu;csm{tB-izZAvLCUA}#3miL0S4KA(+B*<(F3A_BHR14n&q&q4LWxP%Ynfr#Fv6%a zJ_sETRipZ#XVLZWt307bta=5sVsKL`2S;^*rZQGT*O8lHQHR~+&uGYs&e{s=pLd@R z+Wf8~wB6t${j3*OM^x0XHk2;3>1$Z&0d4#kdV5abD?8Hr3soqfeK6WeTesKP;-7#< z`u|nkb9_*4ATKQmmT#WYI@PoTrfALiK#HL^gJ=4lMUP^87ydS9-M4OEcoPo#m3Lqx zkDaInf4OMEEZun6;bG9ZIfjJlV9Do~vN~Mg^JV=wduYzO-EKSZ1q`lXD)B`wE2N;Zp32~l~$0ig@gAC@zMbYh_!2m(^JhMMnGwom&^uC>aSx0AoS-o%e zXTHc7SZd8pOIOuDx;hFgF&i27=B3xi5Wj@(g9pz&FnN>x^k9OP=%L)|T!#Ga(sbt4 zXP#C5w4Qp6u6g{W2bB204<1M=N4|=^oN5qZexg%PnsoD^zdupFkG>5nd`(S{F?J6@ zYt*9s;b+J#pT8(2IKa3KoTD8Hn<5t60~Y9*VbKECvPaNOC56J280^Y&3DhO49c(nrG>C8r)=pJ2w*DAA4TOc2DkMxhKO@B46mkM%}~G z^d7S2y6`?#v*Tx|aaF7o#&*g%@w@OLG|X6oeYy3DxR-_oTERVf9L2O|@D6`C7XD0U zE#_Jro@6UmQxE3X>7q$e;NxcV-~n=~U1KMW2tb=xi#A`G=>DX!B&jizpR-d*Ff&Sd zmFi1qL#jXPP+41gVii0|$D(TvRc=tZ|U zCA)cTifYv+cdAu65T2_~g5|(hu`W<~i+6@c$uKwZ+mS0=n_5FRkIw^sJ2`lsFTBML zyWjJyO@-i2p~HiMr$E=~f@?f~3$}gkkHhqSQ+%CYmw3RjR619=^G3I6TqzRAoTdG&>M#=&#pmLJd7f06iap za$7we!j&&$Q{ilOr^>_QJ&SIJ)5^oQda7(ViQT8JMtQ2Pz^iKb!YwGHJ$K&$r!XYz z4kgc1Jq;#yt=<0PFjS#r$Q1ui#FkjDtC|zIQ=6r8-@m{ zx!H~&&)VLt@%q%J>oj%Uitky~u{BOlLXC~V>wTf$8-v%txapN7A8>;X?vF{qhM^Ii?rcT#J|T^Dx#gH%uL z3H=juM%QG|sxIl(p9j}a_CdE-XV;vfchS5NI?^t9PUuMc;NZ}aGlMsTKZ9uvm5Js7 zef&vw2GoG#oe{78!(g@j`Hv-hkTooUeamtFj;9X3wQQ7GrZHd5&E6X@!J!sAy>+h8 zbEm=M=d2lg^PDY%M+G+wzS&dtJj~ZQ%8n$k)iKx~ zcf$5Ym4)U`z>b^+F+&63ss0TLMQjy3>r=VZqLsD7M_RPA1N+oQhR+Q(c(XkZYz#Ga z^*m797Jg^XXs@3NWqYbI_ZN(Vq2wAYxIVSZTnBnR)$0N2lCi=aOoMFq%-;wT=KvL*Ze%XH`5h`isiLxt{qca1XpT(DbyY<2^8(YTTvPVszTW8&rI` z;8rTxpc7VK)TGSs@ke90X%x3JLwl1Pz2Q^vTyG$rRYxk1JnEVM0~qRA1@%kkU%>sa z?b_B@ug6}EAAmGAThz-{(5N?BupI)hf7hL(9>c+7g&(KXZ_vJ#j7Mbn}zl0|+#t&EBmkei>S%b=khK{8MFS8>BxLwaq45`)m ztc|7VHav%d==3lF=WV=T3{Oq$tar=)s>gee+u@jJm5T<@k=r1CUC4OI|FI`DMiuZJ z*8)g7RHLYO@O7T~*fm3s$l?L_ovKWNy7&t^8?a~i5~_+CX?hmyhf}C$v5(-F1^k|2 z^*q#51;d~5oi3QsXa565@?TKnP9W2UF13Rmn`XfN4z7<)mg z`Sz;MAP9F}C{Kf%?tTwKV;hVc49CEbI8nt*W88b+1uK`HSXL*=pRtEzC!=iPiDjFd zs2hesDC#@0sMZE~qeW9u^fW^>cwVR>5r&oEG_Hv!dS)Ek)wWqcy9TN%x;nN6LfBbG z+I`R-a&z|ZR)}@EA?Ne>H|W~Rb1b-X7ZoRXIY`|c-GsLl7`GYfakf#5WhbX89EYt{ zcY;ak)dcL=U@+VB54+W9`G=sQHL6f$Sf>N?_a`c1YoW+#s@uX)%mp*N$OlP$OXN?D zkrLX1a}So!)fl821TbjMSz0mZQzb^{LIKJx204dE;5iT;-$cfo_$m3hsA7Z}Rqz5F z#$SQ`_kbqrj!)1&tTP>(1X>!l4FeCsCY(uiyYzl-v<^KV*mPY zQWh*c#A;3~rJYavK<}OmLW5L2^4xtRoI~-CuE#h=z=db6u3@UKQ;VSoH8)}e=l!2V z(8`G5H+T&ZBPc`%?}dvKB$d3QB8Ekg!BAYH_ruIIw#Jb6RGk7|_N~E6;#trg&!GuT zLeHPbd3z3h75(uY&{1LNFx=~d=b?>MD4%}|qldPVe=`15~2!_;6H}s$z@Lf3y{{&83+^`8iWXP^iL!0uGNaiJl zc9>|nLQf+~34MR9?qXthDY}g4IHEzIPII*A(8!~PwdqhyT0HTz1N&Qyxnf_!d|wZepdWqmFekD_E1>m)2mMB=X=>5`zJ<2z z>*nNJ7(&GsfL49^3trxim#PTw#>-nU&fjkHRQJdDRE>*bfrc2WMnYJu3-HP#!-FZ6 zN3zQbbrE?p9?*p^JAR{QEo|L1pH<*fCwTKzHOD;g&)7m|HOX^nD!d>!bxgdAe6FkF zj<>2jGBD_A9ze!6z=+Bp-bXcx1`BgC;yZCJ4U^b6hk?n_mmonf;n$BEX6}X=vYsH% z`nNwE=bPS*?}43*R7wg#Z)giGm&X3AuCgg?6kNf;@YomNB|nDjE7WVRYKj}$m#(iu zTWFm<))j;^Hi)PQqKT;Mbu7esUd-cqInIwg2r|)Mq1#cOS6?xB8M1LLPX4uB zSR7)-O$;x!+)coA$$AK5&&5ksvhKpmOW|?_Y<3y|ztIIBRX{bv`ZYpTGwj6P+w_#p6{NW`th%j$TQp4w zcGg1%?b9KVP7S$Iv|D4b zW1BxCO0SOR1b&Ct7vUl3kML|vD@|)EYJdH&>e{mb3EDru9-S?SSYU!YyRU>mjdCK_Qh9SR!;G>~$92*^=>@ zeqsX;#(q_gcU_z8z!9A!{SaHn7#R(t1BkT@8)bZ#(}-2Y-%EY_v+ChhfqI>bLn7{O zc0i3td45|n_-Bfjo)kg6`*pm>@SQ?@df+nS)%Lv58!C|%oYYvo8zf+ zH(~SuwF&MYxI!Bfo;kZ>pFnxnGhUcbW)B|VnZFcXggU{5GBUa{bhxW$-esVO>iLMk zZdxaVXPe#ato_q7ZTJ%m>X>6^ZSu^&9jXz&3 zfwsr8;C@w{S}a=~x4jG>B!3C=uu}_OH-^_i)Ku4}N^?BVAKs<&7dN3nw&uEij5fXj zRcRwU{vMCDB*~~H%R!IYLS()?v!b%>n#ia_@C1HFt58GxvWc*c?2fM7+t}!e`?7Y< zeoa^N?6Uh{2^9Xs1@xD=Q_&y(9bPU8z7J8jLk(SLtcJfu5qv2!3O!lA1~#~%(3^jT z7^8Wh>&=_+>_#|?KN{Mf-5fX^(L$A|ST49%xnwjIs@E(p)a#6&dDYjo$>>>eYZ2`H zuY;7Hr&~{mLbs?*yCsg_OA_8>zM7-Q4YVVs6bcuYU79z)Kc^L>8g@IvLr?g71u7%q zl?=W6D89StuWZcSn+nwv@(BL!fU)dYm4+{%^r?lxHT>Fy=fA@|X$|i87zZ|-soNen4x5gExP3w+zUH1c6l!+Go zq*gBN59;miRUe`)o~lO3AXPqKpK$DFzJRWna;AChJvZxHsBgCCijQEpOt)-;0aeB6 zc2?9AngjQO$n;*h8NZHj{i?RRE2%#yI2>{O9d*{UZ!if~nsdSj;6YnkTG=e?q)(E9 zXXNS)t+3Op?521p<>P2Ftf9xhz$XUK@4betNYm5AIxu=I_HaDRxH8haJD6>IhDC8_ zM#h&IFc0quN9~LqIT;&*u%|CxIjDWv20L=AOKoVw4OWvtCjOaaWe&mjQ}mHPeR_Ur zAidwfj7z*ZMS)_t3YC9;NN9 zz|?}mJTIhgT0s%Sm*>qZ%l8J0y`=?HiY68|$5$E*-rnpCZ()AXl;BhlEcO-_7foqr z1;NOIJMu5_f?*Jqy25xtRgb)iCbKQJyP#;s#6qw~`|XxXd*t=@mRyP!nf^`B%bz^4 ztS|_fPuKp0bQPBcOUi=Y$>2;2;=9R8A6JwCU(@j` zAHG0oMTXVRnl`bpusERoJw4yJC=E`}D=RTGBEO;lGoicyoLd^47%VGQSy0eaGF#>Y zxNB%}Sz%sL&!9H|is7E%?bi8ZYjH__5#~XU(o4{>c3NWYyGT1B` zP-1Rv0Sbx(!9wqtoU4b99y(^s4K2c^qd+mZRO^S95&h-Gwj+CLw%#H+^dI=^oKll!vLC1TMYRN-ruaEX-)iJT6|7CYI)3)W11Xo3bBjA!b^S0Dg}< zULa6f;I+=YsYk(}9tGE00d_LP1*Kwo@icGg)cnFiZBJ=_AvPMO^RSrYdv#qXF7iUI zXwmlVtX=c&fzN&Lxfec*tz8c+fX~C$uB9Q%Ywdb)9$Z1l;xK#`!Y2ZsyR7vqt@Zzc zvnSzGW9?e96h3h85|CK31U_r5U5j3W&+Bkr3BP##2AnU058SilW%xV?pF6ExVYm)I zWxWSt;pNu)C#?1O_dWi1{Lcgb^T7W+@IMdy&jbJS!2dk({~`~-lZ5{}j>|yF<^2a- zbn(EfO9lq(u#l4798>&g^p5*Y3t>-XdJ|BnApd0_a^p_h2mhmX82!`mnGg1#4I>eS=LMm$`1Tx+>1Qe9^zw@Jd? zURavCs1VD#ntqYdZ5A$Z;$g$bug>5Z3H}aANxeH^NJ{5Li9=Gn5%-Xk^o6Z_DVg(H z`%(sm+W1o@+}1v}-UeHccTt z$VL%+kQn+Wj~G4>{IC8QHPO*Oo#FPCq|1Z9{*=^&x^~KW7`BzdhH#jz!v;ba{(TiZ zO7RO^9>F|Xi0PZ>hO7v+f<#`^E+riRB(phBdV_cx+6{-wa|kkhlaSla@QP(&Tw_T7 zJdD#JuEf(_Eh7U_EN_eH-rL~Z0&j;B{3)FiZb?b?r6gfKt)%c5F55@+U>2kILW8wgRwB+wh!&4e0YN z6o#9QTlzfV7Srh}{SJCCEEP5);ZciUkYfp}bV4x?dQ(`3|DMj_DcigWyB*Rw5Om>& zDm*U3yny^XL3=bI;UC90NND5Bbnm^W0BszvZN&WBMqz3c2l+GxyoYr3*#$1)T4O##rXRZQ+Wh&jHR-G@}npW^9&EPDUTRz`&Dgr z4wbc;C*bJkCB)Ao-~3@&FG)Pv?NHV+?t0QSl;WOEaW@d}KztW+&hV7?#{2!Ecf6AzY$xhm^oz5d&D^Q_oZxZgNB4cF(s-o~CRIe6c z-yFg5(?aZ<=i&G%)OvVIZ9>A8?bOH#dTC?ig!KtG{NnKxex0~JWhM+ezCNM&i4HWy zZT+NXN-{hV<`O4561g)Kfr+3=9$57f3_U!?#2sKRtBrMl(nC#^h2uBmzX{%h^zi*W zF6~S5^(2qw5Dy$zqMlm8vw@>M&%iGpY8(i^SHTDQa!N9fOUD3rLAq+Y!wDQE`p3Ef zeo_8Yl4m}Ncn*z4c-&e?e6n*FxYfi<|)G`^nx1Nd5s*H3hEmcnm(M=Xc^fzWoRBC6rNWj0@~q(1-R^c83c% zGWC!3FZe}0JE;9p<5&tyv^cj3G;wOE4UD^qQO_Q#_iD@wYzuLIP^-qfz;N4_3(6;Z zh<`sO`DOG(k;~7w#19cyV_9JPlpeR`?FJ`sjHUcJ1m|eay~zq&YJ3W(_+PLf_frRw zrh~0E#GmtMpvG`;3U9Y4`SGcmtFarLo=vq%a<&meLK>9>V^CCOiMp4OxK8aTa6 z$wNCp<%b)(r6j-nLM^ZQ6FB{wlBY@p*L?N_=~+&C=*Rj8>A8N0*28vUzl!O~_G-@Q z!grk&*VwWs9QQt9`8ma}15_VDGKoh0<=gN#(V{|VLdAI$(;R?*TGN7hH=!A+vu(r$ zwmu@x?F)y0rugyo^ef^xZZePE#MOQ}BOpdR$6$E+1My1)|AqMFf;SK+w>5?SPJC1o z(I<(j9=Ea<2;P?XYQa5#d5;d~hJ^@3kTe1qWE5dTzg{ChBH=K;ZQCeG_E z92_8ijj*$T`1OJp6CW>l8S#9<|3Vzw8}pb;9NQK1m`@zr74wJ?pCkBv#7`IeA>soB zf0X!8!Ji^tCHS+%9~b;zinoGSB26nT>lKn$FZLQiZxF}7r)C~?q^Csiw@9AH2Ye6y zy#UN#-5xNttdEG}3%};^Iq`#nf30{c3)@}u*hBIsso&<1eI#EiR;podP4%ET-jj&y^4rYuEYUitC4j4wL7uws7GXUdIR1U+6Uh&E;P{%wiR2TZ zLpc$@%|ZS;2R_b$PjcWT4*Vg=j}zHb4)Ui!qt}hPDz)ARp1PCxRO+gDUG6U8^NBAd zuj1d$Ks{a62D#9Q3?E z@&zQ1^CdjW9pvwH;0qo2QV0H|1AiVk`sZ|-KX%jRST8%szwW@_a^Rmh@b4V>4-UM+ zfx{5(g!$Flfwu>a>1}_7PUusV-qRf9yE^dm9C#lGJ_tC*dkYnu6%_Af4)Q}C_*D+v zDh*x`u*!my2j=AmV8{KW+k^RdIc;L;t@z8=1J(9**fFmH3yQQ%1^&>TRuK1E!$x@v zch_4%*vgz&Fg*w-X$9G?k~;PGa- zrMR%r!ae7~iIXhYbX;0Au>^mMil@UCbJ!kj1@rPJs~zhgHoc$-BDM;OC+Asu8#JV7 zJZ#hsSg^S{uWVWg{=)uw*a{Cf!Zq5f&y;qGFHo325#EZJQcw)`Y9q=@tYA@oxt7-m zc4%X8KBOoQl&Du0v@S3Z0)ntFU+=gF4@0Pm6<#t3OfAd<0=w1&)AGPl2-KVT$>#BS z1yc%wr7GEI_yv8f8Svf53aTyE)}*q@;NlAg;K>F3El{NR^oixx1-V1};a4Vp^}(;c z_|+f32H;l~eqDrL7vtAJ{OY5nm3SW|-$yCwqi*Y??(3r^)t!CRt$mcLzUtn-Ix2N9 zYytwEebv2v)xCYyUHz0~KPA~uN%m8c{gh-sCD~6)D*b)+w~Du~imRVG@2AfDD{cLC z>Xgp@N?U)Wp}*4EU+L_x^z>Kv_E+}~Q1=c{cMVV)2B>=nsJjLXu*MJ1y?Tf*cl^~? zTrqmsnDJwLLvn|Wx5kf$w-W-ljt@+|bv*8Sx5|sB=jG)W7EFVE@6$`I@<3@|>U7A> zJlF$om8gyIR-mxBG#_pa1aB|NA3te4ycA(g*4c>F242U&vIFI%pr{~-zp$%5ABseN zp(=x_PzEPL6~ds3AngCQD!9bqNlCFP^3#fgSXS{pi}HzuSY)xPLCwH7NhVL>vq@8| zY5CLOHVdj#Y01PueyNpLkZ;YHtg6mrt3toG0S1HlMa5|DM5P%EFun|8L1h9{u*_mL zpA7L>74(7!Sf*a?ukFR#9l0JUo^eLqnccA2UtMvBLU_P`C!P}uisaG^C zcqOE-I)gVdEPM+Cy?uM>jPd%N97uf->MvAmcpU>xfY&bYN4FK}%;#oC{gSKoIaYg$1=5NQooX~J)ieYPg7DT1W*VPi#^zzQt-x>9w5YQ{ z+4NFyb;V>T6s1$OL`A{0(vspJs8ZM9>0oi8(xw_em0r|c5GXFv`c+}jFOn3Olqylo zxr#txK}nz(@}WFXUXT}@YE?{v*E*o(Ey%;>nOYO8w1~?DXeEmvRxIKbg~b!|AX;dm z(5n;ktP(7qfuPD^5YUJEiv@6s%Lq6&+fR>FXnDGc|4JO68{xt7JBd$Fw3)9VzKq5{ zFr2jRfDgP+rw(2RLOmG9&!L_pdFC5v{HE7owSJUeL)?vmaIpM`#0Ln@^7t3COnKHn zTJWu;=SIO#q`=^}ix`M@rXP!P##+41OuZ&-UP7a>aw~;qulQon>&& z#|*()9@isGd9D{Zg0uY14!qidFB6>8^{U{U?{5mu<#w;&tfx_M_J3QLCz$DCKXekj zOPZFm1!ws`3C{Yb2+n$N9%aT`)m>kIE;#%1H-rBLL^*$t5y#}sH26g@?#F}ko$bW= z!-@Df!P##!1!sGnb>RAc>DKX@c5WqkY3IrGh*jFzQ*gG&7M#;HT5z`WHo;lX3c*>= zOM+1YbtnEBHd<8G&C zEDtPyrQj@ogW&9kNrJPUX@YZl%N+P$1m}3?3C{jmBsk~egMzdCN%6R z^v^|xd?M)LgWteM`8n`m|Iak!{RW?F=&v;Rd_&&UztE7MYslX#6K4EZ|^c{e?p zLqC}5J=NgD4EZyNW4b~H?`Ftj*qPno3y$**c{ARNg?yK8TK)=yoAz8~=&1rxPFIZ~ ze}%#CHMpr~nW4ul=TAAvuQK>>L;vfB9@7sS40$u&j|^@bdOmZ|^PM4Y>N#Za97E4h z2R%vj{7~laDa0|aOn(*|^1UF8=WCUM^L%Zk!BH3YkLv{Ib~r{HWvk)C`Ph~g^wG{M z4W3OL<<0rs6^1;9y-wwAyx`ow-y(Pg$p;0mBtBbk?spam{uh#eQ1AzdFBhEWBhLxG zg5+Noocq%^1n2&AtKe+sZoyf8uiz|yKydC~jtb8GR9kvLg87C1XZa3-vwSzf*+1tA z&iT?`aJKU@2ksY~(|aRvWh2&j=o(cwZ8n)BA?t?B_bc*`FT>&i-+Ez`1a6d0=~z1m|>l1ZVxH z3C{L(5uEGkxq`F)-h#6|7YojME)$&PhYQa7|0FouIl+NX5uDR|n**OGIQ#iw;<7wH zF66mg`lsMr@7@%g?RiUZo=0yNoYTA8f&U;l>-m+qw4Wcsart5W?dV}DbJla313yP_ zt}p2h`~tx_-}^i8O9bcjhq1(Ex}Fg7wNxKpbKoBczJ}zlI=N;2+D&|n;8Ef;1m8e> zK5?{jK76=6Tx{?K2LFf9^C9W^OmMcd6YZy#`IRX+`(c>ioWG+Tc!}VguCN1tSa8(uXFKro9e6*%*>58RXFG2foc(jJ1OLk4INn~P%&N28^5YFW`!{FvT zs~>TU)0`)cHMr@w2Ms;F;GR?$e8KUo!A<*LcHrwB_&W~#QwRR71OM6JxnLjr;WvYu zen{l^T0qwHLs#PL=N(jk&l8;cqyB<(dwZF|7a8{W4UT@}{_aYH-)+c`GC0a}yw?lP zUq@QsOKp8^B;n4z=9}t}5{atWQ@2j+r70ZKJe%2DlG~EXuE^k{5zQo|$4gP?^>ka;( z!S@(^slk6Dj`oZ&_z^??UW0pR!y=bw9w&DZoX4f-2+rf@UV^iK1`|g=+;7Bd3whQv zQgDuUs^BxJoXioN`~61*XM0{0oaIN;`)ce5<|~P#{pPyOZi7E$r0ameA2xU!+INh4 z9x?bS2LG$UyBXZf_jH4s`QDc}+CS3JGuV(f^F7~?N1MiZ;R}x21kdv-Y|SwAECa;l zd9j21gMv>bJ&y^_@%~-#lhSm&9}-6!A2s6r#^9!ZzBjn(p96;es|`KJ40+Q(=g>wC zE)UsWxCxG)#L=GR@L@mnage{>;G+!riH06C-pK|x(>2ZDX1Zn=`q3t~bC$ucG2)$X z=rQA6Y{*}0$Up2L|D?emGvwDA{0@WfHS|Aj@B@atSzl7m0wFjsO;5mw{oL2!ru?4_ zZpu$1j`=v+&{J;6n|_#W$d57P7dXh@Z}2A#`HhC2>kR(3Azx|m?FKjfmT)!*!NKK_ z$AKP${|Um`Kiv(UXz(G#(VtHn{7OR}!~9NEcN+3$ zdgmM5)W68!rv9ad{u>Sbj~nu){uPG2S$>{3radWMq{s6PUWJvs>9=kMe;!1*oTnT7p9UX99Lvv52Iq2w_Pk*530}B> z1LeoThxO-s;Q|hnUkM-PgFB6ykdDTJwEkk}H=wkc-;~@W;A^)Nw|Fyv<8T>oq(#~HE zZq{FSH@F!Nls9!OikK(coq~a4T`lmw=(C z%#b(Rfw=}Z_0$-8@(exq8}g=}WrlpdA^)_4{8~f)WkddLga6y$zdCTwIUocF=A+ra zXAnm}U>MIg`w7nTmr;Uq`+p;G)M2(u6$Ur+Wsbp3e})bHlR+2f`(lHe{p`brp4o={ zV+J?VyVB5uwq|?b3yyyq@~h#R%UuYeop$7;GAF25Jy|yFw*;)AwR|7ZyNHI2H$MRqb?pF@_Kv%Fyx%BwrOwy zhvXdu=k)UW5X&>q7V^BVl_NO&=UU=!i2D)vaQ@yP=vU4+ZCVKQ;6$GyG8RApgDKY|oDldJa0sx9S0r!NK{+&$HSQ zcSD$I&&dw*=L^o|qpyRWEC=~Xg0p{$9Q4c-^6ZBN4)QMvdDj2BA#biny(Q#X|8@uY zJwl%A#SsUdaISKHlmEG#bQYY;^Vx#$rFwCm;5&);6}+B!mf*XIUoJSW8|Mhl@s1Xp z?fkRg{52(f^+_E6P)w+6TvxuzZRVHH!e8m z@2`S${;EIn3ife&Ie)vP12j07=L-#PKGzsva7-`TImqC517|<`436?_zfBz3IzxVf z!8aQG4xxwBJ6G@qsuv3cKT14e=-+JUe?-W$o_`3=?ZYdAv-}%^bG(}cXFq==ILq%8 zoa;-2;2dwG;GFN?3`is#obMcOPr*4|*9p%3%dLX5J+p~pK5l^z=lgsI`9*?rdhZvU z)AfkpoUV6>W4vz}@xCwQSv5e2H^agHXWp7P z>VF45?6j&VyuqumRVsLp_(BK%iQq>`zTSa< z=fE2UpGkV$_z)Tn^v}ESVgH;&oc%e@3xRM9GvxmR*DQan!M7UxX`!de3xRMvYskL` z*Q}@3L4KnlZ?;!k9ptwg@}~ap9OUDMys5v@klzM&@;-$2y)hNd<&D>6IuS>IJ`5jD z*Tn`m$5GcBdN8UrN$>^7N$2Zd)UzEvtS8mr?;E@)am+7soG{Sf1%~{khMthYuQlW` z?uB0XfGe4J|Nc^Bl^&N73WNeBW-$8zu z!G|0AuQc?S`8(Q>H{;DSxNYbuaL`j`$eViR89c|(Q{$lLK||iuv%=tohMwmg^t@)s zqwi*V;R}v84SBO(M}_=RuM)R@6nq@Sf<%WKx_oei5;Fmb?YJ+2YyTb)va4aQ`x~t*C{(08m z9~gX((36}9fpE0yqkq{>9zUlD&f}u99XR~$6Lqj2ULVU5oW~(!iKG1=!iVjjDCGJ5 zsdB;DpCJc+zu^2{`CkR+bUh(B`{5bEcXrq5d|hz1bGzW2-W?A7Tfx0)TEDBW{$+o% zJxPM|yt1>wKQjF3C60bJ`}a(Pe{9HS8+!1bE|lJ@9Qd`8)B1R^;9MSV7d(&jR|(Gk zyi4#iN&gbTS$>1y-AR9sevlYAFimFr;3Lj{E+qL}!CC)k$w_{);M0iz#o$A>G`;B|s?dEP2Gm!F-2bG$JJ{-;pVVI^uc_VZN$EOOaE`Z& z!O=gvTkHGb?-i+o^M&KRT5xWMZ*t&M1ZR873~tt!S;S?1i5T3hFG~$QWhXgAKr4{A3N}`1ZV$$D>(b@SHU^ngaIx6%sfeO)_=0#tiPM!tOx#*SBrdPezD-J zCs%Nmzrlfz7o7E!3(k6K1n2dQM+E0~bvbd&+r{wVe(E_P&;EHyaMrU?aJJ`t2mXcN zoUVO>v!34^c%$IGsXiuO)G}X~rwPvb`#5mF1D|Mca~wF`;KhbN%Za0(Lk6F3$YU7$ zAtE^E<5IynU!D-0_54F{URQZdaMu5k;H;rd-y-FzOM!%ReAE%P$k0<6R{<+h6Cvw+e1iySh_w))RB! zKMT%!5(c*PKkM;0@KXin^qwg=>+dNz?{D`Bo<{X_xZqQXj})Bk3^?!-2R=h^wtuGJ zZ2tnm+5X1_XM6tXz+V=e<9$tVE?3(HXZ^o8aP=Sl81;zdPt7`U{+{E&&ljBSxlC}* zFTdbZDZj1~ob8z)ILrSB!BIX+@}CL*74h8;{09f#;J_0ufym(C_K@`_JMhySxL0uYPcOk)|3C*m%zegOF$);+aU%w#}&E6(MEH9X`CUCVLbjX5S-hea>2R2-)ZpM411OkN8O(o z{7HjZPW9+52mYSmy-9wD;5>f#QgD{-5@W}>GH1ynR=>OE<<%T@Q-8l`u;FxW2Q_li}V|%!ao=4nkaMZ(oc+B9_ zfpfZ67~CvZ&l5)-X1RLJkjF4y-*{K>os=&h2+sSxeioeV{9SM!FC|?Hk-@?F#rxb& z6P(w_(j0hS2c9c9ubbT{IOj`1aE`Y~aIRlL!Fl{JM{urpO9ba}$jgGW{Of}A{Oeu8 z*`5ysXZ`hpvwrKcX8(T#r(AxLh+|oJ*x;QFZngtG4ZgvU?``nU44!T98iS8<;EN1? zry>6gaV$Tj2LG4AXBqr;L(go3Z!+XD&Fs&ug0tVY8+zs&dg_Hd>)9hX>-kym4OG95 z3eN3PtIHuWIJmy>-&s9HaF#z)aE`Z|;9MUs5}f4+3(oSk;GEu(g0r6M1n2f-lHh4+ zI-RA0vz|KyXM5%e&i;9UIOgpF_;5LYN654N?+ecHejzyLmpfbAi~83a`p+QF`NjIX z3C{X^3(op|g0uXUg0ml{3eNHVMesB#Z`Fdco*Kb9-lY!wF~K?B6@qiRUKE_;{gAly z=VwBm_5UO|r|XE|tl#Cc)WP|}=}K|nrwh*UdIjfnohLZQn@e1#>slet`o{{+dZr7` z>76M!uhWJFXZiaiPt$(+o8UZ-`nv;PC3qo~tE~pd@yv!cT7T5w=!>9N^PdGTB;I+5 z4(I%0-c|5Cl20R!x|hL+>w7N;`AZ!55JS&;Lr;#7XZuGR@^={WzX^G6pC=CmAvn-Z zQ@)6J0)-9s!U-I+B-hxQC%E6M18NNYI{~pD9y9n7gFkKX2Mzwb!Iv8R72;@T(BSI~ z`FjoimLZR6=5)R1z(1E9?!n_b$-N3&KRED2zYcHmr@pk3iK8v+;lutZGq~x8wL%a3 zIc1nuh2FC{)!@N(kg1pf>134+fdo+r5LR1KyIKA+@qZxtR~-fD=KXxzl_ zCLR?0KH?RE^WQm|DflBKUn%&b#H$23Lc34~VZ8{1f791TP_8D>%158wCG_ z|DS!o z;BiXV0l~YIo(92t5D7{?-Kb6`Oui$>-X@cKIJY8@uhrI=_A^A+f2a!Gf1;3p5K*5I+A1pZc zJK2I?Me=^ZuO@B_KAL#0;9M_83BHu%#|S>2_*lWY-x(+P6q27H_^rhA1TQ8&Rq#^c zg@Tt8FA@AN#Djv*AzmSPi1SSizEJQ7i7yuX5#mb(f0X!A z!Ji<$Oz`%^mka(Z@h1g;f%po+Un2gT;I9y0DfsKeUl#mL;;RL(BfduP&BSX3e~0)6 z!QUgkMeq-ZZx#F#;@br0`u?Hd?B^YV-$Z(L3f}cn-CorTeh;lz?iPFi$wvi$h~)PQ z{#WAr1uq~y2Lzu+yg~5Wh#wWajJO(DK~lIq=k;Rq{}IEmf0I2)cmW5?ze+q=@HdF3 z3cijw|Nrf*XCv{>LjEn{T?BuZxL0sqk4_W(Ba%-S{8Qq+1%IA+rr@>2`wRXp@qvQJ zhz}NgAMtF#e!ddL43L3*ARbFaDLvfLhu_%{yD*KCcaYeiNs$PJfHY# z!MT2|5&S3eL#^QBNzVqsyOaO72+q&jwhG>hu8Pk-S^*e-lp< zoa+U@cfxwgNq?%4|Crv3=qUIH#5)WA3Gpt1e@@)|e-bh7uZX7!`R|FR3%-YVZ@~k^ zGX?KXyuaX`i4PR~2I7MSznQrC|1+Zf{GN5X zUrzE>g2Vqwt&RnP4=27*aDK05vEWye{1U-O6JIL$^~9G6eiQNKf{!Quq~HPKD+HfH z{5ipICB9N{e*foX!AnVgwczE%*9iU>;UEm))Lp)XR_lS2C`~%|V|Lclze?q*AkpG;xSMaZhrwRTI z@pQrW5brH`oOq_-KN9aR_%Fl<3Vw+AV8M?N&lbFqxL@!@dN0csybbYO!A~MSN^lSH zF@kp>K34Ef#K#GKCh-Y^cO{-Dcz5Dc1@B3`Q1D*FO9a1=cu??u#47~9nD|V=2NACn z{Bq(|f)6FWK=9$j7YcqA@x_8)O?-*q{Qlum!LKLzWrE*Ce7WG`i9acLfcOf*rx1Tm z@LP$m6ug-D%Yv5@UoCh!@il_;`;)bT&ms8@f`^E25qv)Jt%BDO-zNCo#6J}LKH@tB ze~|c2!5<-BFZiRxcMJXm@u=W`C%#wkXNm6@`~~6%1b>NmgW#_aKPvd^#I05>+xa(% zy9KW!o+S8Y;>m))Pdrud_lS2C`~%{h1^PZONq-%b~N7s>Y) zd_38oDR>R>{(`fg2MYc@$q$zNVr_r6;424e?iZZjZ?pwJjr8OS&hHzK68vs@A8(A{ z=a8PUf}cx#oZyvY=LEr@A)Y6AU(z#G@Qa8S3VsRk62a$?{XxNpkbH&UpOXAc!LKCw zO2J1GuM+%P;tK@-6Y+(D-$;D1;B{0UmI(e1mBXci|Bd)E!EYnJT=4Ff=<@TV;6G43 zS|NBM+4-E{_mG_{1;2^(zbyE9WY22B=aK$3g5O2FR`4RquML9VOYv@zochzPfqQ+($fJ@DzG~vbW%;5YH6+G~)dQKZE!{!Otc>SnzX*XA6EValhc_5w``ufOxLp zeTk0}{37CG1iys%SivjFZ{q~Nhxi1+A19tC_)Em63O!h}NBl{_ z?;^fJ@I}O*6Z~G{D+Pam_{)MnOnkNAeM)04g{Erp<2}IcG;5?sNEO;5O{|Npb@uh-aK>C*n{vFBd z+G|<3y{`*=CSXN|gT$M79`RHi+{C96?<{yBaj)Pd#M1>263-O8f;jJEXFF#SZxHgc zi7NvkjOFhno<#c?na?AhD)?Q*I}5&uxL5FdiKh$x0P#%0A0j?b@MXla1z%3w7M%BK zjS`&qX^j<}_i0TKocC!>6`c2Jl?cxJv?>H&Lv~gQUQ2v|;Ji<3vEaNxpj>ocC#M6P)*H?GQXpdg=xLk$6<_pNV^^oZ}@S zMR*#<{A!Z3h5R#gy+m;K;|jq)B|Fv#o;cKYx#SN3njAfSoNInS!q;K2z|I&eHO7eXPSdn#=X5r-;jSp|!*< zF`hrSyVjE?c!c;s!5=0*M(~ZqrwaZq@k+rzC%#nh7;!ld>OcbrIUo5GaXBw2ATGy^ zr=O?OE5~n_5Z_Ae0hfp67i#$~ZS^nn>;JD(u3n{qAP5gw3-M48Yz`Djp^ND(gNi{A z3sFfS=q8>Vn#)1%KvUS;*jW1jseOVxfY0DFq|jEJxtX~n<8>ZnXXd-_+n>4Jy~_{T z<2W|>-9F>b1`iGx|1kLCA>-h(VUqg}+I+w^R&NmXyvw7<9Ndl1J-AMmfg7Y05WV4c z=fehuq+yB#1sh>RT`%qW9=L9rBvBu$?WcLEQoNh;rJh zWqOyDHN|_^JV-3tddZ{ZL|aO;MA(U+*iNq5(so5!xr`vo95w}CU|LyzNxRC|6S942 z2wUh8$LgWQl%0)|S~%f+2=ud2>X-aXYkF4n0Zpu#L;It7XyUmleQ?C0Z=+Y8+B4A` z8idEJ|B-pg5kRPpSbDyw%76Ql31j(ugz`^ITKlj0_lQwnZ2rOv1o4+h{{w3m7 z?XLzb97)C0J`KgTpCd+0eV>7Dan#yu<#r<|b1Ben(KJ)PB%!k|`dN&8%Y7Bh=D)6b zd4AA(wEr5vpi?#e@d~%3-w)IH>5)M7ntx*1wh@AQhf4ha&SkcATOZ=f>M5u8+L5C6 aKqbJbS=e^`OUS9xkJgy^R@5>rto|2Ih2 +#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; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ +} 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 && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (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/x.o b/x.o index b38b993a30a7069322188f43b04ffb5718930ee1..ee1851d134561156b7f2d55f2eb808b5d1ede274 100644 GIT binary patch delta 8303 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 S+Ce&;(aA}2+zz@~*8c!K^<=&P delta 8209 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