diff options
| -rw-r--r-- | config.def.h | 479 | ||||
| -rw-r--r-- | config.h | 108 | ||||
| -rw-r--r-- | config.mk | 6 | ||||
| -rw-r--r-- | st.1 | 3 | ||||
| -rw-r--r-- | st.c | 211 | ||||
| -rw-r--r-- | st.h | 14 | ||||
| -rw-r--r-- | st.info | 37 | ||||
| -rw-r--r-- | win.h | 1 | ||||
| -rw-r--r-- | x.c | 343 |
9 files changed, 893 insertions, 309 deletions
diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..1984359 --- /dev/null +++ b/config.def.h @@ -0,0 +1,479 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "#3b4252", /* black */ + "#bf616a", /* red */ + "#a3be8c", /* green */ + "#ebcb8b", /* yellow */ + "#81a1c1", /* blue */ + "#b48ead", /* magenta */ + "#88c0d0", /* cyan */ + "#e5e9f0", /* white */ + + /* 8 bright colors */ + "#4c566a", /* black */ + "#bf616a", /* red */ + "#a3be8c", /* green */ + "#ebcb8b", /* yellow */ + "#81a1c1", /* blue */ + "#b48ead", /* magenta */ + "#8fbcbb", /* cyan */ + "#eceff4", /* white */ + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#2e3440", /* background */ + "#d8dee9", /* foreground */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 257; +unsigned int defaultbg = 256; +static unsigned int defaultcs = 257; +static unsigned int defaultrcs = 256; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; @@ -11,13 +11,15 @@ static int borderpx = 2; /* * What program is execed by st depends of these precedence rules: * 1: program passed with -e - * 2: utmp option + * 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 */ @@ -30,9 +32,9 @@ static float chscale = 1.0; /* * word delimiter string * - * More advanced example: " `'\"()[]{}" + * More advanced example: L" `'\"()[]{}" */ -char *worddelimiters = " "; +wchar_t *worddelimiters = L" "; /* selection timeouts (in milliseconds) */ static unsigned int doubleclicktimeout = 300; @@ -41,9 +43,18 @@ static unsigned int tripleclicktimeout = 600; /* alt screens */ int allowaltscreen = 1; -/* frames per second st should at maximum draw to the screen */ -static unsigned int xfps = 120; -static unsigned int actionfps = 30; +/* 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 @@ -63,7 +74,6 @@ static unsigned int cursorthickness = 2; static int bellvolume = 0; /* default TERM value */ -// char *termname = "st-256color"; char *termname = "xterm-256color"; /* @@ -89,28 +99,30 @@ float alpha = 0.95; /* 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 */ + "#3b4252", /* black */ + "#bf616a", /* red */ + "#a3be8c", /* green */ + "#ebcb8b", /* yellow */ + "#81a1c1", /* blue */ + "#b48ead", /* magenta */ + "#88c0d0", /* cyan */ + "#e5e9f0", /* 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 */ + "#4c566a", /* black */ + "#bf616a", /* red */ + "#a3be8c", /* green */ + "#ebcb8b", /* yellow */ + "#81a1c1", /* blue */ + "#b48ead", /* magenta */ + "#8fbcbb", /* cyan */ + "#eceff4", /* white */ + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#2e3440", /* background */ + "#d8dee9", /* foreground */ }; @@ -121,7 +133,7 @@ static const char *colorname[] = { unsigned int defaultfg = 257; unsigned int defaultbg = 256; static unsigned int defaultcs = 257; -static unsigned int defaultrcs = 257; +static unsigned int defaultrcs = 256; /* * Colors used, when the specific fg == defaultfg. So in reverse mode this @@ -130,6 +142,7 @@ static unsigned int defaultrcs = 257; */ unsigned int defaultitalic = 7; unsigned int defaultunderline = 7; + /* * Default shape of cursor * 2: Block ("█") @@ -160,19 +173,25 @@ static unsigned int mousebg = 0; 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[] = { - /* button mask string */ - { Button4, XK_NO_MOD, "\031" }, - { Button5, XK_NO_MOD, "\005" }, -}; - -MouseKey mkeys[] = { - /* button mask function argument */ - { Button4, ShiftMask, kscrollup, {.i = 1} }, - { Button5, ShiftMask, kscrolldown, {.i = 1} }, + /* mask button function argument release */ + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, }; /* Internal keyboard shortcuts. */ @@ -191,10 +210,10 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_C, clipcopy, {.i = 0} }, { TERMMOD, XK_V, clippaste, {.i = 0} }, { TERMMOD, XK_Y, selpaste, {.i = 0} }, - { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, - { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, { 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} }, }; /* @@ -212,10 +231,6 @@ static Shortcut shortcuts[] = { * * 0: no value * * > 0: cursor application mode enabled * * < 0: cursor application mode disabled - * crlf value - * * 0: no value - * * > 0: crlf mode is enabled - * * < 0: crlf mode is 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 @@ -235,13 +250,6 @@ static KeySym mappedkeys[] = { -1 }; static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; /* - * Override mouse-select 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 forceselmod = ShiftMask; - -/* * This is the huge key array which defines all compatibility to the Linux * world. Please decide about changes wisely. */ @@ -1,5 +1,5 @@ # st version -VERSION = 0.8.2 +VERSION = 0.8.4 # Customize below to fit your system @@ -28,8 +28,8 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) # OpenBSD: #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ -# `pkg-config --libs fontconfig` \ -# `pkg-config --libs freetype2` +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` # compiler and linker # CC = c99 @@ -170,7 +170,8 @@ See the LICENSE file for the terms of redistribution. .SH SEE ALSO .BR tabbed (1), .BR utmp (1), -.BR stty (1) +.BR stty (1), +.BR scroll (1) .SH BUGS See the TODO file in the distribution. @@ -39,10 +39,10 @@ /* macros */ #define IS_SET(flag) ((term.mode & (flag)) != 0) -#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') +#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) (utf8strchr(worddelimiters, u) != NULL) +#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]) @@ -55,7 +55,6 @@ enum term_mode { MODE_ECHO = 1 << 4, MODE_PRINT = 1 << 5, MODE_UTF8 = 1 << 6, - MODE_SIXEL = 1 << 7, }; enum cursor_movement { @@ -82,12 +81,11 @@ enum charset { enum escape_state { ESC_START = 1, ESC_CSI = 2, - ESC_STR = 4, /* OSC, PM, APC */ + 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, - ESC_DCS =128, }; typedef struct { @@ -136,13 +134,14 @@ typedef struct { 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 '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ typedef struct { char buf[ESC_BUF_SIZ]; /* raw string */ - int len; /* raw string length */ + size_t len; /* raw string length */ char priv; int arg[ESC_ARG_SIZ]; int narg; /* nb of args */ @@ -153,8 +152,9 @@ typedef struct { /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */ typedef struct { char type; /* ESC type ... */ - char buf[STR_BUF_SIZ]; /* raw string */ - int len; /* raw string length */ + 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; @@ -217,7 +217,6 @@ 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 char *utf8strchr(char *, Rune); static size_t utf8validate(Rune *, size_t); static char *base64dec(const char *); @@ -344,23 +343,6 @@ utf8encodebyte(Rune u, size_t i) return utfbyte[i] | (u & ~utfmask[i]); } -char * -utf8strchr(char *s, Rune u) -{ - Rune r; - size_t i, j, len; - - len = strlen(s); - for (i = 0, j = 0; i < len; i += j) { - if (!(j = utf8decode(&s[i], &r, len - i))) - break; - if (r == u) - return &(s[i]); - } - - return NULL; -} - size_t utf8validate(Rune *u, size_t i) { @@ -390,8 +372,9 @@ static const char base64_digits[] = { char base64dec_getc(const char **src) { - while (**src && !isprint(**src)) (*src)++; - return *((*src)++); + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ } char * @@ -409,6 +392,10 @@ base64dec(const char *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; @@ -483,7 +470,7 @@ selextend(int col, int row, int type, int done) selnormalize(); sel.type = type; - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.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; @@ -653,7 +640,8 @@ getsel(void) * st. * FIXME: Fix the computer world. */ - if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP)) + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) *ptr++ = '\n'; } *ptr = 0; @@ -684,7 +672,7 @@ die(const char *errstr, ...) void execsh(char *cmd, char **args) { - char *sh, *prog; + char *sh, *prog, *arg; const struct passwd *pw; errno = 0; @@ -698,13 +686,20 @@ execsh(char *cmd, char **args) if ((sh = getenv("SHELL")) == NULL) sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; - if (args) + if (args) { prog = args[0]; - else if (utmp) + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { prog = utmp; - else + arg = NULL; + } else { prog = sh; - DEFAULT(args, ((char *[]) {prog, NULL})); + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); unsetenv("COLUMNS"); unsetenv("LINES"); @@ -742,7 +737,7 @@ sigchld(int a) 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); + _exit(0); } void @@ -835,21 +830,25 @@ ttyread(void) { static char buf[BUFSIZ]; static int buflen = 0; - int written; - int ret; + int ret, written; /* append read bytes to unprocessed bytes */ - if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) - die("couldn't read from shell: %s\n", strerror(errno)); - buflen += ret; + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); - written = twrite(buf, buflen, 0); - buflen -= written; - /* keep any uncomplete utf8 char for the next call */ - if (buflen > 0) - memmove(buf, buf + written, buflen); - - return ret; + 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 @@ -1068,6 +1067,7 @@ tswapscreen(void) term.mode ^= MODE_ALTSCREEN; tfulldirt(); } + void kscrolldown(const Arg* a) { @@ -1090,6 +1090,7 @@ void kscrollup(const Arg* a) { int n = a->i; + if (n < 0) n = term.row + n; @@ -1124,7 +1125,8 @@ tscrolldown(int orig, int n, int copyhist) term.line[i-n] = temp; } - selscroll(orig, n); + if (term.scr == 0) + selscroll(orig, n); } void @@ -1154,7 +1156,8 @@ tscrollup(int orig, int n, int copyhist) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1163,27 +1166,17 @@ selscroll(int orig, int n) if (sel.ob.x == -1) return; - if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { - if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { + 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(); - return; - } - if (sel.type == SEL_RECTANGULAR) { - if (sel.ob.y < term.top) - sel.ob.y = term.top; - if (sel.oe.y > term.bot) - sel.oe.y = term.bot; } else { - if (sel.ob.y < term.top) { - sel.ob.y = term.top; - sel.ob.x = 0; - } - if (sel.oe.y > term.bot) { - sel.oe.y = term.bot; - sel.oe.x = term.col; - } + selnormalize(); } - selnormalize(); } } @@ -1633,6 +1626,7 @@ tsetmode(int priv, int set, int *args, int narg) 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", @@ -1714,6 +1708,12 @@ csihandle(void) if (csiescseq.arg[0] == 0) ttywrite(vtiden, strlen(vtiden), 0); break; + case 'b': /* REP -- if last char is printable print it <n> more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; case 'C': /* CUF -- Cursor <n> Forward */ case 'a': /* HPR -- Cursor <n> Forward */ DEFAULT(csiescseq.arg[0], 1); @@ -1837,7 +1837,7 @@ csihandle(void) break; case 'n': /* DSR – Device Status Report (cursor position) */ if (csiescseq.arg[0] == 6) { - len = snprintf(buf, sizeof(buf),"\033[%i;%iR", + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y+1, term.c.x+1); ttywrite(buf, len, 0); } @@ -1874,7 +1874,7 @@ csihandle(void) void csidump(void) { - int i; + size_t i; uint c; fprintf(stderr, "ESC["); @@ -1904,7 +1904,7 @@ csireset(void) void strhandle(void) { - char *p = NULL; + char *p = NULL, *dec; int j, narg, par; term.esc &= ~(ESC_STR_END|ESC_STR); @@ -1921,9 +1921,7 @@ strhandle(void) xsettitle(strescseq.args[1]); return; case 52: - if (narg > 2) { - char *dec; - + if (narg > 2 && allowwindowops) { dec = base64dec(strescseq.args[2]); if (dec) { xsetsel(dec); @@ -1941,7 +1939,10 @@ strhandle(void) case 104: /* color reset, here p = NULL */ j = (narg > 1) ? atoi(strescseq.args[1]) : -1; if (xsetcolorname(j, p)) { - fprintf(stderr, "erresc: invalid color %s\n", p); + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); } else { /* * TODO if defaultbg color is changed, borders @@ -1956,7 +1957,6 @@ strhandle(void) xsettitle(strescseq.args[0]); return; case 'P': /* DCS -- Device Control String */ - term.mode |= ESC_DCS; case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ return; @@ -1991,7 +1991,7 @@ strparse(void) void strdump(void) { - int i; + size_t i; uint c; fprintf(stderr, "ESC%c", strescseq.type); @@ -2018,7 +2018,10 @@ strdump(void) void strreset(void) { - memset(&strescseq, 0, sizeof(strescseq)); + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; } void @@ -2076,7 +2079,7 @@ tdumpline(int n) bp = &term.line[n][0]; end = &bp[MIN(tlinelen(n), term.col) - 1]; if (bp != end || bp->u != ' ') { - for ( ;bp <= end; ++bp) + for ( ; bp <= end; ++bp) tprinter(buf, utf8encode(bp->u, buf)); } tprinter("\n", 1); @@ -2147,12 +2150,9 @@ tdectest(char c) void tstrsequence(uchar c) { - strreset(); - switch (c) { case 0x90: /* DCS -- Device Control String */ c = 'P'; - term.esc |= ESC_DCS; break; case 0x9f: /* APC -- Application Program Command */ c = '_'; @@ -2164,6 +2164,7 @@ tstrsequence(uchar c) c = ']'; break; } + strreset(); strescseq.type = c; term.esc |= ESC_STR; } @@ -2206,6 +2207,7 @@ tcontrolcode(uchar ascii) return; case '\032': /* SUB */ tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ case '\030': /* CAN */ csireset(); break; @@ -2360,15 +2362,13 @@ tputc(Rune u) Glyph *gp; control = ISCONTROL(u); - if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { + if (u < 127 || !IS_SET(MODE_UTF8)) { c[0] = u; width = len = 1; } else { len = utf8encode(u, c); - if (!control && (width = wcwidth(u)) == -1) { - memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ + if (!control && (width = wcwidth(u)) == -1) width = 1; - } } if (IS_SET(MODE_PRINT)) @@ -2383,25 +2383,12 @@ tputc(Rune u) if (term.esc & ESC_STR) { if (u == '\a' || u == 030 || u == 032 || u == 033 || ISCONTROLC1(u)) { - term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); - if (IS_SET(MODE_SIXEL)) { - /* TODO: render sixel */; - term.mode &= ~MODE_SIXEL; - return; - } + term.esc &= ~(ESC_START|ESC_STR); term.esc |= ESC_STR_END; goto check_control_code; } - - if (IS_SET(MODE_SIXEL)) { - /* TODO: implement sixel mode */ - return; - } - if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') - term.mode |= MODE_SIXEL; - - if (strescseq.len+len >= sizeof(strescseq.buf)-1) { + 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 @@ -2415,7 +2402,10 @@ tputc(Rune u) * term.esc = 0; * strhandle(); */ - return; + 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); @@ -2434,6 +2424,8 @@ check_control_code: /* * control codes are not shown ever */ + if (!term.esc) + term.lastc = 0; return; } else if (term.esc & ESC_START) { if (term.esc & ESC_CSI) { @@ -2464,7 +2456,7 @@ check_control_code: */ return; } - if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y)) + if (selected(term.c.x, term.c.y)) selclear(); gp = &term.line[term.c.y][term.c.x]; @@ -2483,6 +2475,7 @@ check_control_code: } tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; if (width == 2) { gp->mode |= ATTR_WIDE; @@ -2506,7 +2499,7 @@ twrite(const char *buf, int buflen, int show_ctrl) int n; for (n = 0; n < buflen; n += charsize) { - if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { + if (IS_SET(MODE_UTF8)) { /* process a complete utf8 char */ charsize = utf8decode(buf + n, &u, buflen - n); if (charsize == 0) @@ -2630,6 +2623,7 @@ void drawregion(int x1, int y1, int x2, int y2) { int y; + for (y = y1; y < y2; y++) { if (!term.dirty[y]) continue; @@ -2642,7 +2636,7 @@ drawregion(int x1, int y1, int x2, int y2) void draw(void) { - int cx = term.c.x; + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; if (!xstartdraw()) return; @@ -2658,9 +2652,12 @@ draw(void) 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; + 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 @@ -74,15 +74,9 @@ typedef union { uint ui; float f; const void *v; + const char *s; } Arg; -typedef struct { - uint b; - uint mask; - void (*func)(const Arg *); - const Arg arg; -} MouseKey; - void die(const char *, ...); void redraw(void); void draw(void); @@ -121,12 +115,14 @@ char *xstrdup(char *); /* config.h globals */ extern char *utmp; +extern char *scroll; extern char *stty_args; extern char *vtiden; -extern char *worddelimiters; +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 MouseKey mkeys[]; +extern float alpha; @@ -1,4 +1,4 @@ -st| simpleterm, +st-mono| simpleterm monocolor, acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, am, bce, @@ -10,7 +10,7 @@ st| simpleterm, civis=\E[?25l, clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, - colors#8, + colors#2, cols#80, cr=^M, csr=\E[%i%p1%d;%p2%dr, @@ -158,6 +158,7 @@ st| simpleterm, rc=\E8, rev=\E[7m, ri=\EM, + rin=\E[%p1%dT, ritm=\E[23m, rmacs=\E(B, rmcup=\E[?1049l, @@ -168,13 +169,8 @@ st| simpleterm, rs1=\Ec, rs2=\E[4l\E>\E[?1034l, sc=\E7, - setab=\E[4%p1%dm, - setaf=\E[3%p1%dm, - setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, - setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, - sgr0=\E[0m, - sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, sitm=\E[3m, + sgr0=\E[0m, smacs=\E(0, smcup=\E[?1049h, smir=\E[4h, @@ -188,11 +184,22 @@ st| simpleterm, # XTerm extensions rmxx=\E[29m, smxx=\E[9m, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, # tmux extensions, see TERMINFO EXTENSIONS in tmux(1) - Se, - Ss, Tc, Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, st-256color| simpleterm with 256 colors, use=st, @@ -220,3 +227,13 @@ st-meta-256color| simpleterm with meta key and 256 colors, smm=\E[?1034h, rs2=\E[4l\E>\E[?1034h, is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, @@ -36,3 +36,4 @@ void xsetmode(int, unsigned int); void xsetpointermotion(int); void xsetsel(char *); int xstartdraw(void); +void xximspot(int, int); @@ -15,7 +15,7 @@ #include <X11/Xft/Xft.h> #include <X11/XKBlib.h> -static char *argv0; +char *argv0; #include "arg.h" #include "st.h" #include "win.h" @@ -29,9 +29,11 @@ typedef struct { } Shortcut; typedef struct { - uint b; - uint mask; - char *s; + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; } MouseShortcut; typedef struct { @@ -56,6 +58,7 @@ 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" @@ -91,8 +94,12 @@ typedef struct { Drawable buf; GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ Atom xembed, wmdeletewin, netwmname, netwmpid; - XIM xim; - XIC xic; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; Draw draw; Visual *vis; XSetWindowAttributes attrs; @@ -140,6 +147,10 @@ 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); @@ -161,6 +172,8 @@ 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 *); @@ -224,8 +237,9 @@ typedef struct { } Fontcache; /* Fontcache is an array now. A new font will be appended to the array. */ -static Fontcache frc[16]; +static Fontcache *frc = NULL; static int frclen = 0; +static int frccap = 0; static char *usedfont = NULL; static double usedfontsize = 0; static double defaultfontsize = 0; @@ -310,6 +324,12 @@ zoomreset(const Arg *arg) } } +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + int evcol(XEvent *e) { @@ -330,7 +350,7 @@ void mousesel(XEvent *e, int done) { int type, seltype = SEL_REGULAR; - uint state = e->xbutton.state & ~(Button1Mask | forceselmod); + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); for (type = 1; type < LEN(selmasks); ++type) { if (match(selmasks[type], state)) { @@ -406,34 +426,51 @@ mousereport(XEvent *e) 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 Button<N>mask for Button<N> - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + void bpress(XEvent *e) { struct timespec now; - MouseShortcut *ms; - MouseKey *mk; int snap; - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } - for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { - if (e->xbutton.button == ms->b - && match(ms->mask, e->xbutton.state)) { - ttywrite(ms->s, strlen(ms->s), 1); - return; - } - } - - for (mk = mkeys; mk < mkeys + LEN(mkeys); mk++) { - if (e->xbutton.button == mk->b - && match(mk->mask, e->xbutton.state)) { - mk->func(&mk->arg); - return; - } - } + if (mouseaction(e, 0)) + return; if (e->xbutton.button == Button1) { /* @@ -649,21 +686,21 @@ xsetsel(char *str) void brelease(XEvent *e) { - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } - if (e->xbutton.button == Button2) - selpaste(NULL); - else if (e->xbutton.button == Button1) + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) mousesel(e, 1); } void bmotion(XEvent *e) { - if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } @@ -777,7 +814,6 @@ xsetcolorname(int x, const char *name) if (!BETWEEN(x, 0, dc.collen)) return 1; - if (!xloadcolor(x, name, &ncolor)) return 1; @@ -1014,6 +1050,60 @@ xunloadfonts(void) 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) { @@ -1063,7 +1153,7 @@ xinit(int cols, int rows) 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 + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; xw.attrs.colormap = xw.cmap; @@ -1087,22 +1177,10 @@ xinit(int cols, int rows) xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); /* input methods */ - if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im=local"); - if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { - XSetLocaleModifiers("@im="); - if ((xw.xim = XOpenIM(xw.dpy, - NULL, NULL, NULL)) == NULL) { - die("XOpenIM failed. Could not open input" - " device.\n"); - } - } + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); } - xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing - | XIMStatusNothing, XNClientWindow, xw.win, - XNFocusWindow, xw.win, NULL); - if (xw.xic == NULL) - die("XCreateIC failed. Could not obtain input method.\n"); /* white cursor, black outline */ cursor = XCreateFontCursor(xw.dpy, mouseshape); @@ -1133,8 +1211,8 @@ xinit(int cols, int rows) win.mode = MODE_NUMLOCK; resettitle(); - XMapWindow(xw.dpy, xw.win); xhints(); + XMapWindow(xw.dpy, xw.win); XSync(xw.dpy, False); clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); @@ -1244,13 +1322,10 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x fontpattern = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres); - /* - * Overwrite or create the new cache entry. - */ - if (frclen >= LEN(frc)) { - frclen = LEN(frc) - 1; - XftFontClose(xw.dpy, frc[frclen].font); - frc[frclen].unicodep = 0; + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); } frc[frclen].font = XftFontOpenPattern(xw.dpy, @@ -1468,8 +1543,9 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) /* draw the new one */ if (IS_SET(MODE_FOCUSED)) { switch (win.cursor) { - case 7: /* st extension: snowman (U+2603) */ - g.u = 0x2603; + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ case 0: /* Blinking Block */ case 1: /* Blinking Block (Default) */ case 2: /* Steady Block */ @@ -1581,6 +1657,18 @@ xfinishdraw(void) } 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(); @@ -1619,8 +1707,7 @@ xsetmode(int set, unsigned int flags) int xsetcursor(int cursor) { - DEFAULT(cursor, 1); - if (!BETWEEN(cursor, 0, 6)) + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ return 1; win.cursor = cursor; return 0; @@ -1654,13 +1741,15 @@ focus(XEvent *ev) return; if (ev->type == FocusIn) { - XSetICFocus(xw.xic); + 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 { - XUnsetICFocus(xw.xic); + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); win.mode &= ~MODE_FOCUSED; if (IS_SET(MODE_FOCUS)) ttywrite("\033[O", 3, 0); @@ -1715,7 +1804,7 @@ kpress(XEvent *ev) { XKeyEvent *e = &ev->xkey; KeySym ksym; - char buf[32], *customkey; + char buf[64], *customkey; int len; Rune c; Status status; @@ -1724,7 +1813,10 @@ kpress(XEvent *ev) if (IS_SET(MODE_KBDLOCK)) return; - len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { if (ksym == bp->keysym && match(bp->mod, e->state)) { @@ -1757,7 +1849,6 @@ kpress(XEvent *ev) ttywrite(buf, len, 1); } - void cmessage(XEvent *e) { @@ -1793,10 +1884,9 @@ run(void) XEvent ev; int w = win.w, h = win.h; fd_set rfd; - int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; - int ttyfd; - struct timespec drawtimeout, *tv = NULL, now, last, lastblink; - long deltatime; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; /* Waiting for window mapping */ do { @@ -1817,82 +1907,77 @@ run(void) ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); cresize(w, h); - clock_gettime(CLOCK_MONOTONIC, &last); - lastblink = last; - - for (xev = actionfps;;) { + 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)); } - if (FD_ISSET(ttyfd, &rfd)) { - ttyread(); - if (blinktimeout) { - blinkset = tattrset(ATTR_BLINK); - if (!blinkset) - MODBIT(win.mode, 0, MODE_BLINK); - } - } + clock_gettime(CLOCK_MONOTONIC, &now); - if (FD_ISSET(xfd, &rfd)) - xev = actionfps; + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); - clock_gettime(CLOCK_MONOTONIC, &now); - drawtimeout.tv_sec = 0; - drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; - tv = &drawtimeout; - - dodraw = 0; - if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { - tsetdirtattr(ATTR_BLINK); - win.mode ^= MODE_BLINK; - lastblink = now; - dodraw = 1; - } - deltatime = TIMEDIFF(now, last); - if (deltatime > 1000 / (xev ? xfps : actionfps)) { - dodraw = 1; - last = now; + 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); } - if (dodraw) { - while (XPending(xw.dpy)) { - 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 */ + } - draw(); - XFlush(xw.dpy); - - if (xev && !FD_ISSET(xfd, &rfd)) - xev--; - if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) { - if (blinkset) { - if (TIMEDIFF(now, lastblink) \ - > blinktimeout) { - drawtimeout.tv_nsec = 1000; - } else { - drawtimeout.tv_nsec = (1E6 * \ - (blinktimeout - \ - TIMEDIFF(now, - lastblink))); - } - drawtimeout.tv_sec = \ - drawtimeout.tv_nsec / 1E9; - drawtimeout.tv_nsec %= (long)1E9; - } else { - tv = NULL; - } + /* 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; } } @@ -1914,7 +1999,7 @@ main(int argc, char *argv[]) { xw.l = xw.t = 0; xw.isfixed = False; - win.cursor = cursorshape; + xsetcursor(cursorshape); ARGBEGIN { case 'a': |