From 11535be56a4f1c2d868a1193409ef5d1c90564c9 Mon Sep 17 00:00:00 2001 From: TitanE Date: Tue, 26 Sep 2023 03:45:09 +0300 Subject: [PATCH] first rice --- config.def.h | 70 +- config.def.h.orig | 491 +++ config.def.h.rej | 13 + config.h | 494 +++ ...st-copyurl-multiline-20230406-211964d.diff | 167 + patch/st-dracula-0.8.5.diff | 81 + patch/st-openclipboard-20220217-0.8.5.diff | 91 + patch/st-scrollback-20210507-4536f46.diff | 351 ++ .../st-scrollback-mouse-20220127-2c5edf2.diff | 25 + ...back-mouse-altscreen-20220127-2c5edf2.diff | 78 + .../st-scrollback-mouse-increment-0.8.2.diff | 34 + st | Bin 0 -> 106272 bytes st.c | 233 +- st.c.orig | 2847 +++++++++++++++++ st.h | 5 + st.h.orig | 130 + st.h.rej | 10 + st.o | Bin 0 -> 82896 bytes x.c | 23 + x.c.orig | 2101 ++++++++++++ x.o | Bin 0 -> 76368 bytes 21 files changed, 7191 insertions(+), 53 deletions(-) create mode 100644 config.def.h.orig create mode 100644 config.def.h.rej create mode 100644 config.h create mode 100644 patch/st-copyurl-multiline-20230406-211964d.diff create mode 100644 patch/st-dracula-0.8.5.diff create mode 100644 patch/st-openclipboard-20220217-0.8.5.diff create mode 100644 patch/st-scrollback-20210507-4536f46.diff create mode 100644 patch/st-scrollback-mouse-20220127-2c5edf2.diff create mode 100644 patch/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff create mode 100644 patch/st-scrollback-mouse-increment-0.8.2.diff create mode 100755 st create mode 100644 st.c.orig create mode 100644 st.h.orig create mode 100644 st.h.rej 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 91ab8ca..4f12299 100644 --- a/config.def.h +++ b/config.def.h @@ -95,27 +95,29 @@ 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, + /* 8 normal colors */ + [0] = "#000000", /* black */ + [1] = "#ff5555", /* red */ + [2] = "#50fa7b", /* green */ + [3] = "#f1fa8c", /* yellow */ + [4] = "#bd93f9", /* blue */ + [5] = "#ff79c6", /* magenta */ + [6] = "#8be9fd", /* cyan */ + [7] = "#bbbbbb", /* white */ + + /* 8 bright colors */ + [8] = "#44475a", /* black */ + [9] = "#ff5555", /* red */ + [10] = "#50fa7b", /* green */ + [11] = "#f1fa8c", /* yellow */ + [12] = "#bd93f9", /* blue */ + [13] = "#ff79c6", /* magenta */ + [14] = "#8be9fd", /* cyan */ + [15] = "#ffffff", /* white */ + + /* special colors */ + [256] = "#282a36", /* background */ + [257] = "#f8f8f2", /* foreground */ /* more colors can be added after 255 to use with DefaultXX */ "#cccccc", @@ -127,13 +129,21 @@ static const char *colorname[] = { /* * Default colors (colorname index) - * foreground, background, cursor, reverse cursor + * foreground, background, cursor */ -unsigned int defaultfg = 258; -unsigned int defaultbg = 259; -unsigned int defaultcs = 256; +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; static unsigned int defaultrcs = 257; +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +unsigned int defaultitalic = 7; +unsigned int defaultunderline = 7; + /* * Default shape of cursor * 2: Block ("█") @@ -174,9 +184,14 @@ static uint forcemousemod = ShiftMask; * Internal mouse shortcuts. * Beware that overloading Button1 will disable the selection. */ +const unsigned int mousescrollincrement = 1; static MouseShortcut mshortcuts[] = { /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, ++ { Button4, ShiftMask, kscrollup, {.i = mousescrollincrement} }, ++ { Button5, ShiftMask, kscrolldown, {.i = mousescrollincrement} }, { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, @@ -201,6 +216,11 @@ 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} }, + { MODKEY, XK_l, copyurl, {.i = 0} }, + { MODKEY|ShiftMask, XK_L, copyurl, {.i = 1} }, + { MODKEY, XK_o, opencopied, {.v = "xdg-open"} }, + { 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..895b4dc --- /dev/null +++ b/config.def.h.orig @@ -0,0 +1,491 @@ +/* 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 */ + [0] = "#000000", /* black */ + [1] = "#ff5555", /* red */ + [2] = "#50fa7b", /* green */ + [3] = "#f1fa8c", /* yellow */ + [4] = "#bd93f9", /* blue */ + [5] = "#ff79c6", /* magenta */ + [6] = "#8be9fd", /* cyan */ + [7] = "#bbbbbb", /* white */ + + /* 8 bright colors */ + [8] = "#44475a", /* black */ + [9] = "#ff5555", /* red */ + [10] = "#50fa7b", /* green */ + [11] = "#f1fa8c", /* yellow */ + [12] = "#bd93f9", /* blue */ + [13] = "#ff79c6", /* magenta */ + [14] = "#8be9fd", /* cyan */ + [15] = "#ffffff", /* white */ + + /* special colors */ + [256] = "#282a36", /* background */ + [257] = "#f8f8f2", /* foreground */ + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor + */ +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; +static unsigned int defaultrcs = 257; + +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +unsigned int defaultitalic = 7; +unsigned int defaultunderline = 7; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { 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} }, + { MODKEY, XK_l, copyurl, {.i = 0} }, + { MODKEY|ShiftMask, XK_L, copyurl, {.i = 1} }, + { MODKEY, XK_o, opencopied, {.v = "xdg-open"} }, + { 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.def.h.rej b/config.def.h.rej new file mode 100644 index 0000000..19e721d --- /dev/null +++ b/config.def.h.rej @@ -0,0 +1,13 @@ +--- config.def.h ++++ config.def.h +@@ -163,8 +164,8 @@ static MouseShortcut mshortcuts[] = { + + MouseKey mkeys[] = { + /* button mask function argument */ +- { Button4, ShiftMask, kscrollup, {.i = 1} }, +- { Button5, ShiftMask, kscrolldown, {.i = 1} }, ++ { Button4, ShiftMask, kscrollup, {.i = mousescrollincrement} }, ++ { Button5, ShiftMask, kscrolldown, {.i = mousescrollincrement} }, + }; + + /* Internal keyboard shortcuts. */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..e2afbb4 --- /dev/null +++ b/config.h @@ -0,0 +1,494 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "JetBrains Mono:pixelsize=16:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + [0] = "#000000", /* black */ + [1] = "#ff5555", /* red */ + [2] = "#50fa7b", /* green */ + [3] = "#f1fa8c", /* yellow */ + [4] = "#bd93f9", /* blue */ + [5] = "#ff79c6", /* magenta */ + [6] = "#8be9fd", /* cyan */ + [7] = "#bbbbbb", /* white */ + + /* 8 bright colors */ + [8] = "#44475a", /* black */ + [9] = "#ff5555", /* red */ + [10] = "#50fa7b", /* green */ + [11] = "#f1fa8c", /* yellow */ + [12] = "#bd93f9", /* blue */ + [13] = "#ff79c6", /* magenta */ + [14] = "#8be9fd", /* cyan */ + [15] = "#ffffff", /* white */ + + /* special colors */ + [256] = "#282a36", /* background */ + [257] = "#f8f8f2", /* foreground */ + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor + */ +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; +static unsigned int defaultrcs = 257; + +/* + * Colors used, when the specific fg == defaultfg. So in reverse mode this + * will reverse too. Another logic would only make the simple feature too + * complex. + */ +unsigned int defaultitalic = 7; +unsigned int defaultunderline = 7; + +/* + * 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. + */ +const unsigned int mousescrollincrement = 6; +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { Button4, ShiftMask, kscrollup, {.i = mousescrollincrement} }, + { Button5, ShiftMask, kscrolldown, {.i = mousescrollincrement} }, + { 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} }, + { MODKEY, XK_l, copyurl, {.i = 0} }, + { MODKEY|ShiftMask, XK_L, copyurl, {.i = 1} }, + { MODKEY, XK_o, opencopied, {.v = "xdg-open"} }, + { 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/patch/st-copyurl-multiline-20230406-211964d.diff b/patch/st-copyurl-multiline-20230406-211964d.diff new file mode 100644 index 0000000..8fbac9f --- /dev/null +++ b/patch/st-copyurl-multiline-20230406-211964d.diff @@ -0,0 +1,167 @@ +From 7405bdc89e4c43cfbeabd0d4d822bc62d1e76730 Mon Sep 17 00:00:00 2001 +From: Gildasio Junior +Date: Thu, 6 Apr 2023 14:51:06 -0300 +Subject: [PATCH] Loop through urls on screen in both directions + +Using previous patches one can loop through urls in the screen in one +direction: botton-up. This patch add a way that can go in the opposite +direction: top-down. + +This is usefull in a screen with lots of urls. +--- + config.def.h | 2 + + st.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ + st.h | 1 + + 3 files changed, 104 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 91ab8ca..4df78eb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -201,6 +201,8 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { MODKEY, XK_l, copyurl, {.i = 0} }, ++ { MODKEY|ShiftMask, XK_L, copyurl, {.i = 1} }, + }; + + /* +diff --git a/st.c b/st.c +index 134e724..c451015 100644 +--- a/st.c ++++ b/st.c +@@ -152,6 +152,11 @@ typedef struct { + int narg; /* nb of args */ + } STREscape; + ++typedef struct { ++ int state; ++ size_t length; ++} URLdfa; ++ + static void execsh(char *, char **); + static void stty(char **); + static void sigchld(int); +@@ -201,6 +206,7 @@ static void tdefutf8(char); + static int32_t tdefcolor(const int *, int *, int); + static void tdeftran(char); + static void tstrsequence(uchar); ++static int daddch(URLdfa *, char); + + static void drawregion(int, int, int, int); + +@@ -2666,3 +2672,98 @@ redraw(void) + tfulldirt(); + draw(); + } ++ ++int ++daddch(URLdfa *dfa, char c) ++{ ++ /* () and [] can appear in urls, but excluding them here will reduce false ++ * positives when figuring out where a given url ends. ++ */ ++ static const char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ++ "abcdefghijklmnopqrstuvwxyz" ++ "0123456789-._~:/?#@!$&'*+,;=%"; ++ static const char RPFX[] = "//:sptth"; ++ ++ if (!strchr(URLCHARS, c)) { ++ dfa->length = 0; ++ dfa->state = 0; ++ ++ return 0; ++ } ++ ++ dfa->length++; ++ ++ if (dfa->state == 2 && c == '/') { ++ dfa->state = 0; ++ } else if (dfa->state == 3 && c == 'p') { ++ dfa->state++; ++ } else if (c != RPFX[dfa->state]) { ++ dfa->state = 0; ++ return 0; ++ } ++ ++ if (dfa->state++ == 7) { ++ dfa->state = 0; ++ return 1; ++ } ++ ++ return 0; ++} ++ ++/* ++** Select and copy the previous url on screen (do nothing if there's no url). ++*/ ++void ++copyurl(const Arg *arg) { ++ int row = 0, /* row of current URL */ ++ col = 0, /* column of current URL start */ ++ colend = 0, /* column of last occurrence */ ++ passes = 0; /* how many rows have been scanned */ ++ ++ const char *c = NULL, ++ *match = NULL; ++ URLdfa dfa = { 0 }; ++ ++ row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot; ++ LIMIT(row, term.top, term.bot); ++ ++ colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col; ++ LIMIT(colend, 0, term.col); ++ ++ /* ++ ** Scan from (term.row - 1,term.col - 1) to (0,0) and find ++ ** next occurrance of a URL ++ */ ++ for (passes = 0; passes < term.row; passes++) { ++ /* Read in each column of every row until ++ ** we hit previous occurrence of URL ++ */ ++ for (col = colend; col--;) ++ if (daddch(&dfa, term.line[row][col].u < 128 ? term.line[row][col].u : ' ')) ++ break; ++ ++ if (col >= 0) ++ break; ++ ++ /* .i = 0 --> botton-up ++ * .i = 1 --> top-down ++ */ ++ if (!arg->i) { ++ if (--row < 0) ++ row = term.row - 1; ++ } else { ++ if (++row >= term.row) ++ row = 0; ++ } ++ ++ colend = term.col; ++ } ++ ++ if (passes < term.row) { ++ selstart(col, row, 0); ++ selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 0); ++ selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 1); ++ xsetsel(getsel()); ++ xclipcopy(); ++ } ++} +diff --git a/st.h b/st.h +index fd3b0d8..baa8f29 100644 +--- a/st.h ++++ b/st.h +@@ -85,6 +85,7 @@ void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); + void toggleprinter(const Arg *); ++void copyurl(const Arg *); + + int tattrset(int); + void tnew(int, int); +-- +2.40.0 + diff --git a/patch/st-dracula-0.8.5.diff b/patch/st-dracula-0.8.5.diff new file mode 100644 index 0000000..b5c3ad3 --- /dev/null +++ b/patch/st-dracula-0.8.5.diff @@ -0,0 +1,81 @@ +diff '--color=auto' -up ../st/config.def.h ./config.def.h +--- ../st/config.def.h 2022-03-09 08:28:40.186246176 -0300 ++++ ./config.def.h 2022-03-09 08:26:03.194323581 -0300 +@@ -95,27 +95,29 @@ 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, ++ /* 8 normal colors */ ++ [0] = "#000000", /* black */ ++ [1] = "#ff5555", /* red */ ++ [2] = "#50fa7b", /* green */ ++ [3] = "#f1fa8c", /* yellow */ ++ [4] = "#bd93f9", /* blue */ ++ [5] = "#ff79c6", /* magenta */ ++ [6] = "#8be9fd", /* cyan */ ++ [7] = "#bbbbbb", /* white */ ++ ++ /* 8 bright colors */ ++ [8] = "#44475a", /* black */ ++ [9] = "#ff5555", /* red */ ++ [10] = "#50fa7b", /* green */ ++ [11] = "#f1fa8c", /* yellow */ ++ [12] = "#bd93f9", /* blue */ ++ [13] = "#ff79c6", /* magenta */ ++ [14] = "#8be9fd", /* cyan */ ++ [15] = "#ffffff", /* white */ ++ ++ /* special colors */ ++ [256] = "#282a36", /* background */ ++ [257] = "#f8f8f2", /* foreground */ + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", +@@ -127,14 +129,22 @@ static const char *colorname[] = { + + /* + * Default colors (colorname index) +- * foreground, background, cursor, reverse cursor ++ * foreground, background, cursor + */ +-unsigned int defaultfg = 258; +-unsigned int defaultbg = 259; +-unsigned int defaultcs = 256; ++unsigned int defaultfg = 257; ++unsigned int defaultbg = 256; ++unsigned int defaultcs = 257; + static unsigned int defaultrcs = 257; + + /* ++ * Colors used, when the specific fg == defaultfg. So in reverse mode this ++ * will reverse too. Another logic would only make the simple feature too ++ * complex. ++ */ ++unsigned int defaultitalic = 7; ++unsigned int defaultunderline = 7; ++ ++/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") diff --git a/patch/st-openclipboard-20220217-0.8.5.diff b/patch/st-openclipboard-20220217-0.8.5.diff new file mode 100644 index 0000000..196c543 --- /dev/null +++ b/patch/st-openclipboard-20220217-0.8.5.diff @@ -0,0 +1,91 @@ +From 1e752ae33791652d050e7d03e6d8e9df3fb459e3 Mon Sep 17 00:00:00 2001 +From: Santtu Lakkala +Date: Thu, 17 Feb 2022 18:11:35 +0200 +Subject: [PATCH] Open url from clipboard + +Based on the previous versions of the patch, but uses double-fork/execlp +instead of system() to avoid potential shell escaping issues and make +the command line building unnecessary. +--- + config.def.h | 1 + + st.c | 2 +- + st.h | 1 + + x.c | 21 +++++++++++++++++++++ + 4 files changed, 24 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 91ab8ca..a696ec7 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -201,6 +201,7 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { MODKEY, XK_o, opencopied, {.v = "xdg-open"} }, + }; + + /* +diff --git a/st.c b/st.c +index 51049ba..4fbc5c8 100644 +--- a/st.c ++++ b/st.c +@@ -810,7 +810,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args) + break; + default: + #ifdef __OpenBSD__ +- if (pledge("stdio rpath tty proc", NULL) == -1) ++ if (pledge("stdio rpath tty proc exec", NULL) == -1) + die("pledge\n"); + #endif + close(s); +diff --git a/st.h b/st.h +index 519b9bd..3b4b395 100644 +--- a/st.h ++++ b/st.h +@@ -81,6 +81,7 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void opencopied(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); +diff --git a/x.c b/x.c +index 8a16faa..3a4e5f0 100644 +--- a/x.c ++++ b/x.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2078,3 +2079,23 @@ run: + + return 0; + } ++ ++void ++opencopied(const Arg *arg) ++{ ++ char * const clip = xsel.clipboard; ++ pid_t chpid; ++ ++ if(!clip) { ++ fprintf(stderr, "Warning: nothing copied to clipboard\n"); ++ return; ++ } ++ ++ if ((chpid = fork()) == 0) { ++ if (fork() == 0) ++ execlp(arg->v, arg->v, clip, NULL); ++ exit(1); ++ } ++ if (chpid > 0) ++ waitpid(chpid, NULL, 0); ++} +-- +2.32.0 + diff --git a/patch/st-scrollback-20210507-4536f46.diff b/patch/st-scrollback-20210507-4536f46.diff new file mode 100644 index 0000000..f960f6b --- /dev/null +++ b/patch/st-scrollback-20210507-4536f46.diff @@ -0,0 +1,351 @@ +diff --git a/config.def.h b/config.def.h +index 6f05dce..93cbcc0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -199,6 +199,8 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, ++ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + }; + + /* +diff --git a/st.c b/st.c +index ebdf360..817cc47 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(const int *, int); + static void tsetchar(Rune, const Glyph *, int, int); + static void tsetdirt(int, int); +@@ -416,10 +423,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; +@@ -528,7 +535,7 @@ selsnap(int *x, int *y, int direction) + * Snap around if the word wraps around at the end or + * beginning of a line. + */ +- prevgp = &term.line[*y][*x]; ++ prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; +@@ -543,14 +550,14 @@ selsnap(int *x, int *y, int direction) + yt = *y, xt = *x; + else + yt = newy, xt = newx; +- if (!(term.line[yt][xt].mode & ATTR_WRAP)) ++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + +- gp = &term.line[newy][newx]; ++ gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) +@@ -571,14 +578,14 @@ selsnap(int *x, int *y, int direction) + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode ++ if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode ++ if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } +@@ -609,13 +616,13 @@ getsel(void) + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &term.line[y][sel.nb.x]; ++ gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; ++ gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } +- last = &term.line[y][MIN(lastx, linelen-1)]; ++ last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + +@@ -850,6 +857,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); +@@ -1061,13 +1071,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); + +@@ -1077,17 +1127,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); + +@@ -1097,7 +1158,8 @@ tscrollup(int orig, int n) + term.line[i+n] = temp; + } + +- selscroll(orig, -n); ++ if (term.scr == 0) ++ selscroll(orig, -n); + } + + void +@@ -1126,7 +1188,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++; + } +@@ -1291,14 +1353,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 +@@ -1735,11 +1797,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); +@@ -2251,7 +2313,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); + } +@@ -2264,7 +2326,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); + } +@@ -2474,7 +2536,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; +@@ -2511,6 +2573,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)); +@@ -2569,7 +2639,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); + } + } + +@@ -2590,8 +2660,9 @@ draw(void) + cx--; + + drawregion(0, 0, term.col, term.row); +- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ if (term.scr == 0) ++ xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], ++ term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); +diff --git a/st.h b/st.h +index fa2eddf..adda2db 100644 +--- a/st.h ++++ b/st.h +@@ -81,6 +81,8 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void kscrolldown(const Arg *); ++void kscrollup(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); diff --git a/patch/st-scrollback-mouse-20220127-2c5edf2.diff b/patch/st-scrollback-mouse-20220127-2c5edf2.diff new file mode 100644 index 0000000..5c47abc --- /dev/null +++ b/patch/st-scrollback-mouse-20220127-2c5edf2.diff @@ -0,0 +1,25 @@ +From b5d3351a21442a842e01e8c0317603b6890b379c Mon Sep 17 00:00:00 2001 +From: asparagii +Date: Thu, 27 Jan 2022 15:44:02 +0100 +Subject: [PATCH] st-scrollback-mouse + +--- + config.def.h | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/config.def.h b/config.def.h +index e3b469b..c217315 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -176,6 +176,8 @@ static uint forcemousemod = ShiftMask; + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ ++ { ShiftMask, Button4, kscrollup, {.i = 1} }, ++ { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, +-- +2.34.1 + diff --git a/patch/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff b/patch/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff new file mode 100644 index 0000000..6a8722b --- /dev/null +++ b/patch/st-scrollback-mouse-altscreen-20220127-2c5edf2.diff @@ -0,0 +1,78 @@ +From 580e3f386e9215707100e9ba44797701943fd927 Mon Sep 17 00:00:00 2001 +From: asparagii +Date: Thu, 27 Jan 2022 15:49:27 +0100 +Subject: [PATCH] st-scrollback-mouse-altscreen + +--- + config.def.h | 4 ++-- + st.c | 5 +++++ + st.h | 1 + + x.c | 2 ++ + 4 files changed, 10 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index c217315..c223706 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -176,8 +176,8 @@ static uint forcemousemod = ShiftMask; + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ +- { ShiftMask, Button4, kscrollup, {.i = 1} }, +- { ShiftMask, Button5, kscrolldown, {.i = 1} }, ++ { XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 }, ++ { XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, +diff --git a/st.c b/st.c +index f3af82b..876a6bf 100644 +--- a/st.c ++++ b/st.c +@@ -1060,6 +1060,11 @@ tnew(int col, int row) + treset(); + } + ++int tisaltscr(void) ++{ ++ return IS_SET(MODE_ALTSCREEN); ++} ++ + void + tswapscreen(void) + { +diff --git a/st.h b/st.h +index da36b34..e95c6f8 100644 +--- a/st.h ++++ b/st.h +@@ -89,6 +89,7 @@ void sendbreak(const Arg *); + void toggleprinter(const Arg *); + + int tattrset(int); ++int tisaltscr(void); + void tnew(int, int); + void tresize(int, int); + void tsetdirtattr(int); +diff --git a/x.c b/x.c +index cd96575..9274556 100644 +--- a/x.c ++++ b/x.c +@@ -34,6 +34,7 @@ typedef struct { + void (*func)(const Arg *); + const Arg arg; + uint release; ++ int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ + } MouseShortcut; + + typedef struct { +@@ -455,6 +456,7 @@ mouseaction(XEvent *e, uint release) + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && ++ (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); +-- +2.34.1 + diff --git a/patch/st-scrollback-mouse-increment-0.8.2.diff b/patch/st-scrollback-mouse-increment-0.8.2.diff new file mode 100644 index 0000000..9556a9d --- /dev/null +++ b/patch/st-scrollback-mouse-increment-0.8.2.diff @@ -0,0 +1,34 @@ +From 63e717e51dcd2f59c7a3aa75b659926aa92e08f3 Mon Sep 17 00:00:00 2001 +From: Jacob Louis Prosser +Date: Mon, 5 Aug 2019 18:20:25 +1000 +Subject: [st] [patch] Exposed variable to easily change mouse scroll increment. + +--- + config.def.h | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/config.def.h b/config.def.h +index ad20c4c..47e4b66 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -154,6 +154,7 @@ static unsigned int defaultattr = 11; + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ ++const unsigned int mousescrollincrement = 1; + static MouseShortcut mshortcuts[] = { + /* button mask string */ + { Button4, XK_NO_MOD, "\031" }, +@@ -162,8 +163,8 @@ static MouseShortcut mshortcuts[] = { + + MouseKey mkeys[] = { + /* button mask function argument */ +- { Button4, ShiftMask, kscrollup, {.i = 1} }, +- { Button5, ShiftMask, kscrolldown, {.i = 1} }, ++ { Button4, ShiftMask, kscrollup, {.i = mousescrollincrement} }, ++ { Button5, ShiftMask, kscrolldown, {.i = mousescrollincrement} }, + }; + + /* Internal keyboard shortcuts. */ +-- +2.22.0 diff --git a/st b/st new file mode 100755 index 0000000000000000000000000000000000000000..691ce26340cbd2258bda2a075a07d525e1a45c4d GIT binary patch literal 106272 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 0 && term.line[y][i - 1].u == ' ') + while (i > 0 && TLINE(y)[i - 1].u == ' ') --i; return i; @@ -521,7 +534,7 @@ selsnap(int *x, int *y, int direction) * Snap around if the word wraps around at the end or * beginning of a line. */ - prevgp = &term.line[*y][*x]; + prevgp = &TLINE(*y)[*x]; prevdelim = ISDELIM(prevgp->u); for (;;) { newx = *x + direction; @@ -536,14 +549,14 @@ selsnap(int *x, int *y, int direction) yt = *y, xt = *x; else yt = newy, xt = newx; - if (!(term.line[yt][xt].mode & ATTR_WRAP)) + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) break; } if (newx >= tlinelen(newy)) break; - gp = &term.line[newy][newx]; + gp = &TLINE(newy)[newx]; delim = ISDELIM(gp->u); if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || (delim && gp->u != prevgp->u))) @@ -564,14 +577,14 @@ selsnap(int *x, int *y, int direction) *x = (direction < 0) ? 0 : term.col - 1; if (direction < 0) { for (; *y > 0; *y += direction) { - if (!(term.line[*y-1][term.col-1].mode + if (!(TLINE(*y-1)[term.col-1].mode & ATTR_WRAP)) { break; } } } else if (direction > 0) { for (; *y < term.row-1; *y += direction) { - if (!(term.line[*y][term.col-1].mode + if (!(TLINE(*y)[term.col-1].mode & ATTR_WRAP)) { break; } @@ -602,13 +615,13 @@ getsel(void) } if (sel.type == SEL_RECTANGULAR) { - gp = &term.line[y][sel.nb.x]; + gp = &TLINE(y)[sel.nb.x]; lastx = sel.ne.x; } else { - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; } - last = &term.line[y][MIN(lastx, linelen-1)]; + last = &TLINE(y)[MIN(lastx, linelen-1)]; while (last >= gp && last->u == ' ') --last; @@ -803,7 +816,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args) break; default: #ifdef __OpenBSD__ - if (pledge("stdio rpath tty proc", NULL) == -1) + if (pledge("stdio rpath tty proc exec", NULL) == -1) die("pledge\n"); #endif close(s); @@ -844,6 +857,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); @@ -1043,6 +1059,11 @@ tnew(int col, int row) treset(); } +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + void tswapscreen(void) { @@ -1055,13 +1076,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); @@ -1071,17 +1132,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); @@ -1091,7 +1163,8 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1120,7 +1193,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++; } @@ -1285,14 +1358,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 @@ -1729,11 +1802,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); @@ -2305,7 +2378,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); } @@ -2318,7 +2391,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); } @@ -2537,7 +2610,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; @@ -2574,6 +2647,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)); @@ -2632,7 +2713,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); } } @@ -2653,8 +2734,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(); @@ -2668,3 +2750,98 @@ redraw(void) tfulldirt(); draw(); } + +int +daddch(URLdfa *dfa, char c) +{ + /* () and [] can appear in urls, but excluding them here will reduce false + * positives when figuring out where a given url ends. + */ + static const char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-._~:/?#@!$&'*+,;=%"; + static const char RPFX[] = "//:sptth"; + + if (!strchr(URLCHARS, c)) { + dfa->length = 0; + dfa->state = 0; + + return 0; + } + + dfa->length++; + + if (dfa->state == 2 && c == '/') { + dfa->state = 0; + } else if (dfa->state == 3 && c == 'p') { + dfa->state++; + } else if (c != RPFX[dfa->state]) { + dfa->state = 0; + return 0; + } + + if (dfa->state++ == 7) { + dfa->state = 0; + return 1; + } + + return 0; +} + +/* +** Select and copy the previous url on screen (do nothing if there's no url). +*/ +void +copyurl(const Arg *arg) { + int row = 0, /* row of current URL */ + col = 0, /* column of current URL start */ + colend = 0, /* column of last occurrence */ + passes = 0; /* how many rows have been scanned */ + + const char *c = NULL, + *match = NULL; + URLdfa dfa = { 0 }; + + row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot; + LIMIT(row, term.top, term.bot); + + colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col; + LIMIT(colend, 0, term.col); + + /* + ** Scan from (term.row - 1,term.col - 1) to (0,0) and find + ** next occurrance of a URL + */ + for (passes = 0; passes < term.row; passes++) { + /* Read in each column of every row until + ** we hit previous occurrence of URL + */ + for (col = colend; col--;) + if (daddch(&dfa, term.line[row][col].u < 128 ? term.line[row][col].u : ' ')) + break; + + if (col >= 0) + break; + + /* .i = 0 --> botton-up + * .i = 1 --> top-down + */ + if (!arg->i) { + if (--row < 0) + row = term.row - 1; + } else { + if (++row >= term.row) + row = 0; + } + + colend = term.col; + } + + if (passes < term.row) { + selstart(col, row, 0); + selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 0); + selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 1); + xsetsel(getsel()); + xclipcopy(); + } +} diff --git a/st.c.orig b/st.c.orig new file mode 100644 index 0000000..7870d07 --- /dev/null +++ b/st.c.orig @@ -0,0 +1,2847 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +typedef struct { + int state; + size_t length; +} URLdfa; + +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, int); +static void tscrolldown(int, int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); +static int daddch(URLdfa *, char); + +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 (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + Arg arg = (Arg) { .i = term.scr }; + + kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(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; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +int tisaltscr(void) +{ + return IS_SET(MODE_ALTSCREEN); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + if (term.scr == 0) + selscroll(orig, n); +} + +void +tscrollup(int orig, int n, int copyhist) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + if (term.scr == 0) + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 0); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n, 0); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + 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) */ + 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: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + case 11: + case 12: + 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; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* 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) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} + +int +daddch(URLdfa *dfa, char c) +{ + /* () and [] can appear in urls, but excluding them here will reduce false + * positives when figuring out where a given url ends. + */ + static const char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-._~:/?#@!$&'*+,;=%"; + static const char RPFX[] = "//:sptth"; + + if (!strchr(URLCHARS, c)) { + dfa->length = 0; + dfa->state = 0; + + return 0; + } + + dfa->length++; + + if (dfa->state == 2 && c == '/') { + dfa->state = 0; + } else if (dfa->state == 3 && c == 'p') { + dfa->state++; + } else if (c != RPFX[dfa->state]) { + dfa->state = 0; + return 0; + } + + if (dfa->state++ == 7) { + dfa->state = 0; + return 1; + } + + return 0; +} + +/* +** Select and copy the previous url on screen (do nothing if there's no url). +*/ +void +copyurl(const Arg *arg) { + int row = 0, /* row of current URL */ + col = 0, /* column of current URL start */ + colend = 0, /* column of last occurrence */ + passes = 0; /* how many rows have been scanned */ + + const char *c = NULL, + *match = NULL; + URLdfa dfa = { 0 }; + + row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot; + LIMIT(row, term.top, term.bot); + + colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col; + LIMIT(colend, 0, term.col); + + /* + ** Scan from (term.row - 1,term.col - 1) to (0,0) and find + ** next occurrance of a URL + */ + for (passes = 0; passes < term.row; passes++) { + /* Read in each column of every row until + ** we hit previous occurrence of URL + */ + for (col = colend; col--;) + if (daddch(&dfa, term.line[row][col].u < 128 ? term.line[row][col].u : ' ')) + break; + + if (col >= 0) + break; + + /* .i = 0 --> botton-up + * .i = 1 --> top-down + */ + if (!arg->i) { + if (--row < 0) + row = term.row - 1; + } else { + if (++row >= term.row) + row = 0; + } + + colend = term.col; + } + + if (passes < term.row) { + selstart(col, row, 0); + selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 0); + selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 1); + xsetsel(getsel()); + xclipcopy(); + } +} diff --git a/st.h b/st.h index fd3b0d8..6b27b55 100644 --- a/st.h +++ b/st.h @@ -81,12 +81,17 @@ void die(const char *, ...); void redraw(void); void draw(void); +void kscrolldown(const Arg *); +void kscrollup(const Arg *); void printscreen(const Arg *); +void opencopied(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); void toggleprinter(const Arg *); +void copyurl(const Arg *); int tattrset(int); +int tisaltscr(void); void tnew(int, int); void tresize(int, int); void tsetdirtattr(int); diff --git a/st.h.orig b/st.h.orig new file mode 100644 index 0000000..c3c31af --- /dev/null +++ b/st.h.orig @@ -0,0 +1,130 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); +void copyurl(const Arg *); + +int tattrset(int); +int tisaltscr(void); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; diff --git a/st.h.rej b/st.h.rej new file mode 100644 index 0000000..30a4152 --- /dev/null +++ b/st.h.rej @@ -0,0 +1,10 @@ +--- st.h ++++ st.h +@@ -81,6 +81,7 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void opencopied(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); diff --git a/st.o b/st.o new file mode 100644 index 0000000000000000000000000000000000000000..2f0ef5ef7ec49ce6906fb00f531e1c32386218f4 GIT binary patch literal 82896 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 YVes^e@;z4i`itVoU(Dz7pFd9jZ+2q`F8}}l literal 0 HcmV?d00001 diff --git a/x.c b/x.c index aa09997..4864c28 100644 --- a/x.c +++ b/x.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ typedef struct { void (*func)(const Arg *); const Arg arg; uint release; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ } MouseShortcut; typedef struct { @@ -455,6 +457,7 @@ mouseaction(XEvent *e, uint release) for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { if (ms->release == release && ms->button == e->xbutton.button && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && (match(ms->mod, state) || /* exact or forced */ match(ms->mod, state & ~forcemousemod))) { ms->func(&(ms->arg)); @@ -2097,3 +2100,23 @@ run: return 0; } + +void +opencopied(const Arg *arg) +{ + char * const clip = xsel.clipboard; + pid_t chpid; + + if(!clip) { + fprintf(stderr, "Warning: nothing copied to clipboard\n"); + return; + } + + if ((chpid = fork()) == 0) { + if (fork() == 0) + execlp(arg->v, arg->v, clip, NULL); + exit(1); + } + if (chpid > 0) + waitpid(chpid, NULL, 0); +} diff --git a/x.c.orig b/x.c.orig new file mode 100644 index 0000000..fba89f3 --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2101 @@ +/* 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; + int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */ +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_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 uint buttons; /* bit field of pressed buttons */ + +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, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == 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) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + 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); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/x.o b/x.o new file mode 100644 index 0000000000000000000000000000000000000000..e9b2d06d53ee09da0490c668e4a58a5329fdff23 GIT binary patch literal 76368 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 E1O6!pmH+?% literal 0 HcmV?d00001