From 47973a2b9072c8638c0f15f1822a9fb6a9139ece Mon Sep 17 00:00:00 2001 From: lucy Date: Thu, 11 Jun 2026 11:51:26 +0200 Subject: [PATCH] add patches --- config.def.h | 46 +- config.def.h.orig | 480 ++++++++ config.h | 516 +++++++++ config.mk | 2 +- st | Bin 0 -> 108144 bytes st.c | 125 ++- st.c.orig | 2705 +++++++++++++++++++++++++++++++++++++++++++++ st.c.rej | 13 + st.h | 3 + st.h.orig | 127 +++ st.o | Bin 0 -> 77048 bytes x.c | 118 +- x.c.orig | 2069 ++++++++++++++++++++++++++++++++++ x.o | Bin 0 -> 80472 bytes 14 files changed, 6161 insertions(+), 43 deletions(-) create mode 100644 config.def.h.orig create mode 100644 config.h create mode 100755 st create mode 100644 st.c.orig create mode 100644 st.c.rej create mode 100644 st.h.orig create mode 100644 st.o create mode 100644 x.c.orig create mode 100644 x.o diff --git a/config.def.h b/config.def.h index 6f05dce..0d81bcc 100644 --- a/config.def.h +++ b/config.def.h @@ -93,6 +93,9 @@ char *termname = "st-256color"; */ unsigned int tabspaces = 8; +/* bg opacity */ +float alpha = 0.8; + /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { /* 8 normal colors */ @@ -120,6 +123,7 @@ static const char *colorname[] = { /* more colors can be added after 255 to use with DefaultXX */ "#cccccc", "#555555", + "black", }; @@ -128,7 +132,7 @@ static const char *colorname[] = { * foreground, background, cursor, reverse cursor */ unsigned int defaultfg = 7; -unsigned int defaultbg = 0; +unsigned int defaultbg = 258; static unsigned int defaultcs = 256; static unsigned int defaultrcs = 257; @@ -168,12 +172,50 @@ static unsigned int defaultattr = 11; */ static uint forcemousemod = ShiftMask; +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "color0", STRING, &colorname[0] }, + { "color1", STRING, &colorname[1] }, + { "color2", STRING, &colorname[2] }, + { "color3", STRING, &colorname[3] }, + { "color4", STRING, &colorname[4] }, + { "color5", STRING, &colorname[5] }, + { "color6", STRING, &colorname[6] }, + { "color7", STRING, &colorname[7] }, + { "color8", STRING, &colorname[8] }, + { "color9", STRING, &colorname[9] }, + { "color10", STRING, &colorname[10] }, + { "color11", STRING, &colorname[11] }, + { "color12", STRING, &colorname[12] }, + { "color13", STRING, &colorname[13] }, + { "color14", STRING, &colorname[14] }, + { "color15", STRING, &colorname[15] }, + { "background", STRING, &colorname[256] }, + { "foreground", STRING, &colorname[257] }, + { "cursorColor", STRING, &colorname[258] }, + { "termname", STRING, &termname }, + { "shell", STRING, &shell }, + { "minlatency", INTEGER, &minlatency }, + { "maxlatency", INTEGER, &maxlatency }, + { "blinktimeout", INTEGER, &blinktimeout }, + { "bellvolume", INTEGER, &bellvolume }, + { "tabspaces", INTEGER, &tabspaces }, + { "borderpx", INTEGER, &borderpx }, + { "cwscale", FLOAT, &cwscale }, + { "chscale", FLOAT, &chscale }, +}; + /* * Internal mouse shortcuts. * Beware that overloading Button1 will disable the selection. */ 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 }, { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, @@ -199,6 +241,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..e1ce1d0 --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,480 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "black", +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 7; +unsigned int defaultbg = 258; +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 */ + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/config.h b/config.h new file mode 100644 index 0000000..15286bf --- /dev/null +++ b/config.h @@ -0,0 +1,516 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + + +/* 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", + "black", +}; + + +/* + * 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 = 0; + +/* + * Xresources preferences to load at startup + */ +ResourcePref resources[] = { + { "font", STRING, &font }, + { "color0", STRING, &colorname[0] }, + { "color1", STRING, &colorname[1] }, + { "color2", STRING, &colorname[2] }, + { "color3", STRING, &colorname[3] }, + { "color4", STRING, &colorname[4] }, + { "color5", STRING, &colorname[5] }, + { "color6", STRING, &colorname[6] }, + { "color7", STRING, &colorname[7] }, + { "color8", STRING, &colorname[8] }, + { "color9", STRING, &colorname[9] }, + { "color10", STRING, &colorname[10] }, + { "color11", STRING, &colorname[11] }, + { "color12", STRING, &colorname[12] }, + { "color13", STRING, &colorname[13] }, + { "color14", STRING, &colorname[14] }, + { "color15", STRING, &colorname[15] }, + { "background", STRING, &colorname[256] }, + { "foreground", STRING, &colorname[257] }, + { "cursorColor", STRING, &colorname[258] }, + { "termname", STRING, &termname }, + { "shell", STRING, &shell }, + { "minlatency", INTEGER, &minlatency }, + { "maxlatency", INTEGER, &maxlatency }, + { "blinktimeout", INTEGER, &blinktimeout }, + { "bellvolume", INTEGER, &bellvolume }, + { "tabspaces", INTEGER, &tabspaces }, + { "borderpx", INTEGER, &borderpx }, + { "cwscale", FLOAT, &cwscale }, + { "chscale", FLOAT, &chscale }, +}; +/* bg opacity */ +float alpha = 0.8; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { 0, Button4, kscrollup, {.i = 1} }, + { 0, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/config.mk b/config.mk index c070a4a..aaa54ff 100644 --- a/config.mk +++ b/config.mk @@ -16,7 +16,7 @@ PKG_CONFIG = pkg-config INCS = -I$(X11INC) \ `$(PKG_CONFIG) --cflags fontconfig` \ `$(PKG_CONFIG) --cflags freetype2` -LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender\ `$(PKG_CONFIG) --libs fontconfig` \ `$(PKG_CONFIG) --libs freetype2` diff --git a/st b/st new file mode 100755 index 0000000000000000000000000000000000000000..0474cf4bda9adf7157ce035384f845707065a0bc GIT binary patch literal 108144 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 literal 0 HcmV?d00001 diff --git a/st.c b/st.c index 76b7e0d..edec064 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); @@ -1059,13 +1069,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); @@ -1075,17 +1125,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); @@ -1095,7 +1156,8 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1124,7 +1186,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++; } @@ -1289,14 +1351,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 @@ -1733,11 +1795,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); @@ -2241,7 +2303,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); } @@ -2254,7 +2316,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); } @@ -2464,7 +2526,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; @@ -2501,6 +2563,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)); @@ -2559,7 +2629,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); } } @@ -2580,8 +2650,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..6f40e35 --- /dev/null +++ b/st.c.orig @@ -0,0 +1,2705 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static void osc_color_response(int, int, int); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 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 + }; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + 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(void) +{ + /* 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; + + if (term.row <= 0) + return; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + int sep = ';'; /* colon or semi-colon, but not both */ + + 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 (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: /* set foreground color to default */ + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: /* set background color to default */ + term.c.attr.bg = defaultbg; + break; + case 58: + /* This starts a sequence to change the color of + * "underline" pixels. We don't support that and + * instead eat up a following "5;n" or "2;r;g;b". */ + tdefcolor(attr, &i, l); + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: /* 1034: enable 8-bit mode for keyboard input */ + 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 buffer */ + case 1047: /* swap screen buffer */ + 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: /* save/restore cursor (like DECSC/DECRC) */ + 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 */ + LIMIT(csiescseq.arg[0], 1, 65535); + 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 > 0) + 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 */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + break; + default: + goto unknown; + } + 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) */ + if (csiescseq.priv) { + goto unknown; + } else { + 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 +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + 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: /* manipulate selection data */ + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: /* set dynamic VT100 text foreground color */ + case 11: /* set dynamic VT100 text background color */ + case 12: /* set dynamic text cursor color */ + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, p); + } else { + tfulldirt(); + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + 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 + */ + tfulldirt(); + } + return; + case 110: /* reset dynamic VT100 text foreground color */ + case 111: /* reset dynamic VT100 text background color */ + case 112: /* reset dynamic text cursor color */ + if (narg != 1) + break; + if ((j = par - 110) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + if (xsetcolorname(osc_table[j].idx, NULL)) { + fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str); + } else { + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + xsetmode(0, MODE_HIDE); + xsetmode(0, MODE_BRCKTPASTE); + 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) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + 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)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st.c.rej b/st.c.rej new file mode 100644 index 0000000..406fd0e --- /dev/null +++ b/st.c.rej @@ -0,0 +1,13 @@ +--- st.c ++++ st.c +@@ -191,8 +198,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); diff --git a/st.h b/st.h index 3d351b6..7ea2fd3 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 *); @@ -123,3 +125,4 @@ extern char *termname; extern unsigned int tabspaces; extern unsigned int defaultfg; extern unsigned int defaultbg; +extern float alpha; diff --git a/st.h.orig b/st.h.orig new file mode 100644 index 0000000..f44e1d3 --- /dev/null +++ b/st.h.orig @@ -0,0 +1,127 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(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 new file mode 100644 index 0000000000000000000000000000000000000000..c4847a8bbdfdbd7d0cf1cf525e52ff726c93880a GIT binary patch literal 77048 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�X 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 H#DZV|&U7;8 literal 0 HcmV?d00001 diff --git a/x.c b/x.c index 210f184..cb69bbb 100644 --- a/x.c +++ b/x.c @@ -14,6 +14,7 @@ #include #include #include +#include char *argv0; #include "arg.h" @@ -45,6 +46,19 @@ typedef struct { signed char appcursor; /* application cursor */ } Key; +/* Xresources preferences */ +enum resource_type { + STRING = 0, + INTEGER = 1, + FLOAT = 2 +}; + +typedef struct { + char *name; + enum resource_type type; + void *dst; +} ResourcePref; + /* X modifiers */ #define XK_ANY_MOD UINT_MAX #define XK_NO_MOD 0 @@ -105,6 +119,7 @@ typedef struct { XSetWindowAttributes attrs; int scr; int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ int l, t; /* left and top offset */ int gm; /* geometry mask */ } XWindow; @@ -243,6 +258,7 @@ static char *usedfont = NULL; static double usedfontsize = 0; static double defaultfontsize = 0; +static char *opt_alpha = NULL; static char *opt_class = NULL; static char **opt_cmd = NULL; static char *opt_embed = NULL; @@ -734,7 +750,7 @@ xresize(int col, int row) XFreePixmap(xw.dpy, xw.buf); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); + xw.depth); XftDrawChange(xw.draw, xw.buf); xclear(0, 0, win.w, win.h); @@ -794,6 +810,13 @@ xloadcols(void) else die("could not allocate color %d\n", i); } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; loaded = 1; } @@ -828,8 +851,8 @@ xclear(int x1, int y1, int x2, int y2) void xhints(void) { - XClassHint class = {opt_name ? opt_name : termname, - opt_class ? opt_class : termname}; + XClassHint class = {opt_name ? opt_name : "st", + opt_class ? opt_class : "St"}; XWMHints wm = {.flags = InputHint, .input = 1}; XSizeHints *sizeh; @@ -1103,11 +1126,21 @@ xinit(int cols, int rows) Window parent; pid_t thispid = getpid(); XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; - if (!(xw.dpy = XOpenDisplay(NULL))) - die("can't open display\n"); xw.scr = XDefaultScreen(xw.dpy); - xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; /* font */ if (!FcInit()) @@ -1117,7 +1150,7 @@ xinit(int cols, int rows) xloadfonts(usedfont, 0); /* colors */ - xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); xloadcols(); /* adjust fixed window geometry */ @@ -1137,19 +1170,15 @@ xinit(int cols, int rows) | 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, + win.w, win.h, 0, xw.depth, InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); memset(&gcvalues, 0, sizeof(gcvalues)); gcvalues.graphics_exposures = False; - dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, - &gcvalues); - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); @@ -1964,6 +1993,59 @@ run(void) } } +int +resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) +{ + char **sdst = dst; + int *idst = dst; + float *fdst = dst; + + char fullname[256]; + char fullclass[256]; + char *type; + XrmValue ret; + + snprintf(fullname, sizeof(fullname), "%s.%s", + opt_name ? opt_name : "st", name); + snprintf(fullclass, sizeof(fullclass), "%s.%s", + opt_class ? opt_class : "St", name); + fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; + + XrmGetResource(db, fullname, fullclass, &type, &ret); + if (ret.addr == NULL || strncmp("String", type, 64)) + return 1; + + switch (rtype) { + case STRING: + *sdst = ret.addr; + break; + case INTEGER: + *idst = strtoul(ret.addr, NULL, 10); + break; + case FLOAT: + *fdst = strtof(ret.addr, NULL); + break; + } + return 0; +} + +void +config_init(void) +{ + char *resm; + XrmDatabase db; + ResourcePref *p; + + XrmInitialize(); + resm = XResourceManagerString(xw.dpy); + if (!resm) + return; + + db = XrmGetStringDatabase(resm); + for (p = resources; p < resources + LEN(resources); p++) + resource_load(db, p->name, p->type, p->dst); +} + void usage(void) { @@ -1988,6 +2070,9 @@ main(int argc, char *argv[]) case 'a': allowaltscreen = 0; break; + case 'A': + opt_alpha = EARGF(usage()); + break; case 'c': opt_class = EARGF(usage()); break; @@ -2037,6 +2122,11 @@ run: setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); + + if(!(xw.dpy = XOpenDisplay(NULL))) + die("Can't open display\n"); + + config_init(); cols = MAX(cols, 1); rows = MAX(rows, 1); tnew(cols, rows); diff --git a/x.c.orig b/x.c.orig new file mode 100644 index 0000000..73545d8 --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2069 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = NULL; +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static int oldbutton = 3; /* button event on startup: 3 = release */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(MODE_MOUSEX10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + struct timespec now; + int snap; + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + /* set alpha value of bg color */ + if (opt_alpha) + alpha = strtof(opt_alpha, NULL); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +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; + XWindowAttributes attr; + XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + 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 +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 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/x.o b/x.o new file mode 100644 index 0000000000000000000000000000000000000000..ade9c9d61b5266094586a0afa8fe70252e34902c GIT binary patch literal 80472 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 literal 0 HcmV?d00001