From 3266e89eb410b2e6267452e711d5d30ca4fb7908 Mon Sep 17 00:00:00 2001 From: lucy Date: Thu, 26 Feb 2026 11:54:02 +0100 Subject: [PATCH] add patches --- Makefile | 5 +- config.def.h | 7 + config.h | 467 ++++++++ config.mk | 11 +- config.mk.orig | 36 + hb.c | 128 +++ hb.h | 14 + hb.o | Bin 0 -> 4360 bytes st | Bin 0 -> 109736 bytes st.c | 126 ++- st.c.orig | 2776 ++++++++++++++++++++++++++++++++++++++++++++++++ st.h | 5 +- st.o | Bin 0 -> 82064 bytes win.h | 2 +- x.c | 305 +++--- x.c.orig | 2134 +++++++++++++++++++++++++++++++++++++ x.o | Bin 0 -> 77552 bytes 17 files changed, 5851 insertions(+), 165 deletions(-) create mode 100644 config.h create mode 100644 config.mk.orig create mode 100644 hb.c create mode 100644 hb.h create mode 100644 hb.o create mode 100755 st create mode 100644 st.c.orig create mode 100644 st.o create mode 100644 x.c.orig create mode 100644 x.o diff --git a/Makefile b/Makefile index 15db421..dfcea0f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c +SRC = st.c x.c hb.c OBJ = $(SRC:.c=.o) all: st @@ -16,7 +16,8 @@ config.h: $(CC) $(STCFLAGS) -c $< st.o: config.h st.h win.h -x.o: arg.h config.h st.h win.h +x.o: arg.h config.h st.h win.h hb.h +hb.o: st.h $(OBJ): config.h config.mk diff --git a/config.def.h b/config.def.h index 2cd740a..fda28aa 100644 --- a/config.def.h +++ b/config.def.h @@ -93,6 +93,9 @@ char *termname = "st-256color"; */ unsigned int tabspaces = 8; +/* bg opacity */ +float alpha = 0.8; + /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { /* 8 normal colors */ @@ -176,6 +179,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"} }, @@ -201,6 +206,8 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Y, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, }; /* diff --git a/config.h b/config.h new file mode 100644 index 0000000..5211708 --- /dev/null +++ b/config.h @@ -0,0 +1,467 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "FiraCode Nerd Font Mono:pixelsize=12:antialias=true:autohint=true"; +static int borderpx = 14; + +/* + * 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 = 2; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* bg opacity */ +float alpha = 0.8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 4; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + {ShiftMask, Button4, kscrollup, {.i = 1}}, {ShiftMask, Button5, kscrolldown, {.i = 1}}, {XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1}, {ShiftMask, Button4, ttysend, {.s = "\033[5;2~"}}, + {XK_ANY_MOD, Button4, ttysend, {.s = "\031"}}, {ShiftMask, Button5, ttysend, {.s = "\033[6;2~"}}, {XK_ANY_MOD, Button5, ttysend, {.s = "\005"}}, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask | ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + {XK_ANY_MOD, XK_Break, sendbreak, {.i = 0}}, {ControlMask, XK_Print, toggleprinter, {.i = 0}}, + {ShiftMask, XK_Print, printscreen, {.i = 0}}, {XK_ANY_MOD, XK_Print, printsel, {.i = 0}}, + {TERMMOD, XK_Prior, zoom, {.f = +1}}, {TERMMOD, XK_Next, zoom, {.f = -1}}, + {TERMMOD, XK_Home, zoomreset, {.f = 0}}, {TERMMOD, XK_C, clipcopy, {.i = 0}}, + {TERMMOD, XK_V, clippaste, {.i = 0}}, {TERMMOD, XK_Y, selpaste, {.i = 0}}, + {ShiftMask, XK_Insert, selpaste, {.i = 0}}, {TERMMOD, XK_Num_Lock, numlock, {.i = 0}}, + {ShiftMask, XK_Page_Up, kscrollup, {.i = -1}}, {ShiftMask, XK_Page_Down, kscrolldown, {.i = -1}}, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = {-1}; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask | XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + {XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + {XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + {XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + {XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + {XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + {XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + {XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + {XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + {XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + {XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + {XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + {XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + {XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + {XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + {XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + {XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + {XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + {XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + {XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + {XK_KP_End, ControlMask, "\033[J", -1, 0}, + {XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + {XK_KP_End, ShiftMask, "\033[K", -1, 0}, + {XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + {XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + {XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + {XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + {XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + {XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + {XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + {XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + {XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + {XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + {XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + {XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + {XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + {XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + {XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + {XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + {XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + {XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + {XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + {XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + {XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + {XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + {XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + {XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + {XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + {XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + {XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + {XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + {XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + {XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + {XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + {XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + {XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + {XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + {XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + {XK_Up, ShiftMask | Mod1Mask, "\033[1;4A", 0, 0}, + {XK_Up, ControlMask, "\033[1;5A", 0, 0}, + {XK_Up, ShiftMask | ControlMask, "\033[1;6A", 0, 0}, + {XK_Up, ControlMask | Mod1Mask, "\033[1;7A", 0, 0}, + {XK_Up, ShiftMask | ControlMask | Mod1Mask, "\033[1;8A", 0, 0}, + {XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + {XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + {XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + {XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + {XK_Down, ShiftMask | Mod1Mask, "\033[1;4B", 0, 0}, + {XK_Down, ControlMask, "\033[1;5B", 0, 0}, + {XK_Down, ShiftMask | ControlMask, "\033[1;6B", 0, 0}, + {XK_Down, ControlMask | Mod1Mask, "\033[1;7B", 0, 0}, + {XK_Down, ShiftMask | ControlMask | Mod1Mask, "\033[1;8B", 0, 0}, + {XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + {XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + {XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + {XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + {XK_Left, ShiftMask | Mod1Mask, "\033[1;4D", 0, 0}, + {XK_Left, ControlMask, "\033[1;5D", 0, 0}, + {XK_Left, ShiftMask | ControlMask, "\033[1;6D", 0, 0}, + {XK_Left, ControlMask | Mod1Mask, "\033[1;7D", 0, 0}, + {XK_Left, ShiftMask | ControlMask | Mod1Mask, "\033[1;8D", 0, 0}, + {XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + {XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + {XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + {XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + {XK_Right, ShiftMask | Mod1Mask, "\033[1;4C", 0, 0}, + {XK_Right, ControlMask, "\033[1;5C", 0, 0}, + {XK_Right, ShiftMask | ControlMask, "\033[1;6C", 0, 0}, + {XK_Right, ControlMask | Mod1Mask, "\033[1;7C", 0, 0}, + {XK_Right, ShiftMask | ControlMask | Mod1Mask, "\033[1;8C", 0, 0}, + {XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + {XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + {XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + {XK_Return, Mod1Mask, "\033\r", 0, 0}, + {XK_Return, XK_ANY_MOD, "\r", 0, 0}, + {XK_Insert, ShiftMask, "\033[4l", -1, 0}, + {XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + {XK_Insert, ControlMask, "\033[L", -1, 0}, + {XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + {XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + {XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + {XK_Delete, ControlMask, "\033[M", -1, 0}, + {XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + {XK_Delete, ShiftMask, "\033[2K", -1, 0}, + {XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + {XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + {XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + {XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + {XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + {XK_Home, ShiftMask, "\033[2J", 0, -1}, + {XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + {XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + {XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + {XK_End, ControlMask, "\033[J", -1, 0}, + {XK_End, ControlMask, "\033[1;5F", +1, 0}, + {XK_End, ShiftMask, "\033[K", -1, 0}, + {XK_End, ShiftMask, "\033[1;2F", +1, 0}, + {XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + {XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + {XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + {XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + {XK_Next, ControlMask, "\033[6;5~", 0, 0}, + {XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + {XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + {XK_F1, XK_NO_MOD, "\033OP", 0, 0}, + {XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + {XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + {XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + {XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + {XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + {XK_F2, XK_NO_MOD, "\033OQ", 0, 0}, + {XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + {XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + {XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + {XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + {XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + {XK_F3, XK_NO_MOD, "\033OR", 0, 0}, + {XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + {XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + {XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + {XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + {XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + {XK_F4, XK_NO_MOD, "\033OS", 0, 0}, + {XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + {XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + {XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + {XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + {XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + {XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + {XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + {XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + {XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + {XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + {XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + {XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + {XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + {XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + {XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + {XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + {XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + {XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + {XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + {XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + {XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + {XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + {XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + {XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + {XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + {XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + {XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + {XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + {XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + {XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + {XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + {XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + {XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + {XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + {XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + {XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + {XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + {XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + {XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + {XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + {XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + {XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + {XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + {XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + {XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + {XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + {XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + {XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + {XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + {XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + {XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + {XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + {XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + {XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + {XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + {XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + {XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + {XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + {XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + {XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + {XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + {XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + {XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + {XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + {XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + {XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + {XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/config.mk b/config.mk index 2fc854e..0908429 100644 --- a/config.mk +++ b/config.mk @@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config # includes and libs INCS = -I$(X11INC) \ `$(PKG_CONFIG) --cflags fontconfig` \ - `$(PKG_CONFIG) --cflags freetype2` -LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --cflags freetype2` \ + `$(PKG_CONFIG) --cflags harfbuzz` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \ `$(PKG_CONFIG) --libs fontconfig` \ - `$(PKG_CONFIG) --libs freetype2` + `$(PKG_CONFIG) --libs freetype2` \ + `$(PKG_CONFIG) --libs harfbuzz` # flags STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 @@ -29,7 +31,8 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) #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 freetype2` \ +# `$(PKG_CONFIG) --libs harfbuzz` #MANPREFIX = ${PREFIX}/man # compiler and linker diff --git a/config.mk.orig b/config.mk.orig new file mode 100644 index 0000000..2fc854e --- /dev/null +++ b/config.mk.orig @@ -0,0 +1,36 @@ +# st version +VERSION = 0.9.3 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` +#MANPREFIX = ${PREFIX}/man + +# compiler and linker +# CC = c99 diff --git a/hb.c b/hb.c new file mode 100644 index 0000000..e146e54 --- /dev/null +++ b/hb.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "hb.h" + +#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } +#define BUFFER_STEP 256 + +hb_font_t *hbfindfont(XftFont *match); + +typedef struct { + XftFont *match; + hb_font_t *font; +} HbFontMatch; + +typedef struct { + size_t capacity; + HbFontMatch *fonts; +} HbFontCache; + +static HbFontCache hbfontcache = { 0, NULL }; + +typedef struct { + size_t capacity; + Rune *runes; +} RuneBuffer; + +static RuneBuffer hbrunebuffer = { 0, NULL }; + +/* + * Poplulate the array with a list of font features, wrapped in FEATURE macro, + * e. g. + * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') + */ +hb_feature_t features[] = { + FEATURE('c', 'a', 'l', 't'), + FEATURE('d', 'l', 'i', 'g') +}; + +void +hbunloadfonts() +{ + for (int i = 0; i < hbfontcache.capacity; i++) { + hb_font_destroy(hbfontcache.fonts[i].font); + XftUnlockFace(hbfontcache.fonts[i].match); + } + + if (hbfontcache.fonts != NULL) { + free(hbfontcache.fonts); + hbfontcache.fonts = NULL; + } + hbfontcache.capacity = 0; +} + +hb_font_t * +hbfindfont(XftFont *match) +{ + for (int i = 0; i < hbfontcache.capacity; i++) { + if (hbfontcache.fonts[i].match == match) + return hbfontcache.fonts[i].font; + } + + /* Font not found in cache, caching it now. */ + hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1)); + FT_Face face = XftLockFace(match); + hb_font_t *font = hb_ft_font_create(face, NULL); + if (font == NULL) + die("Failed to load Harfbuzz font."); + + hbfontcache.fonts[hbfontcache.capacity].match = match; + hbfontcache.fonts[hbfontcache.capacity].font = font; + hbfontcache.capacity += 1; + + return font; +} + +void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) { + ushort mode = USHRT_MAX; + unsigned int glyph_count; + int rune_idx, glyph_idx, end = start + length; + + hb_font_t *font = hbfindfont(xfont); + if (font == NULL) + return; + + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); + + /* Resize the buffer if required length is larger. */ + if (hbrunebuffer.capacity < length) { + hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP; + hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune)); + } + + /* Fill buffer with codepoints. */ + for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) { + hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u; + mode = glyphs[glyph_idx].mode; + if (mode & ATTR_WDUMMY) + hbrunebuffer.runes[rune_idx] = 0x0020; + } + hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length); + + /* Shape the segment. */ + hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t)); + + /* Get new glyph info. */ + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count); + hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); + + /* Fill the output. */ + data->buffer = buffer; + data->glyphs = info; + data->positions = pos; + data->count = glyph_count; +} + +void hbcleanup(HbTransformData *data) { + hb_buffer_destroy(data->buffer); + memset(data, 0, sizeof(HbTransformData)); +} diff --git a/hb.h b/hb.h new file mode 100644 index 0000000..3b0ef44 --- /dev/null +++ b/hb.h @@ -0,0 +1,14 @@ +#include +#include +#include + +typedef struct { + hb_buffer_t *buffer; + hb_glyph_info_t *glyphs; + hb_glyph_position_t *positions; + unsigned int count; +} HbTransformData; + +void hbunloadfonts(); +void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int); +void hbcleanup(HbTransformData *); diff --git a/hb.o b/hb.o new file mode 100644 index 0000000000000000000000000000000000000000..801d455075a6351f5059482e7fbdfc275becaf03 GIT binary patch literal 4360 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 BukC8n7gc$FeSI=@g1y$i~*= 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 literal 0 HcmV?d00001 diff --git a/st.c b/st.c index 6f40e35..3315e45 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 */ @@ -185,8 +192,8 @@ static void tnewline(int); static void tputtab(int); static void tputc(Rune); static void treset(void); -static void tscrollup(int, int); -static void tscrolldown(int, int); +static void tscrollup(int, int, int); +static void tscrolldown(int, int, int); static void tsetattr(const int *, int); static void tsetchar(Rune, const Glyph *, int, int); static void tsetdirt(int, int); @@ -409,10 +416,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; @@ -521,7 +528,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 +543,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 +571,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 +609,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; @@ -844,6 +851,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); @@ -1058,13 +1068,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); @@ -1074,17 +1124,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); @@ -1094,7 +1155,8 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1123,7 +1185,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 @@ -1742,11 +1804,11 @@ csihandle(void) case 'S': /* SU -- Scroll line up */ if (csiescseq.priv) break; 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); @@ -2335,7 +2397,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); } @@ -2348,7 +2410,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); } @@ -2572,7 +2634,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; @@ -2609,6 +2671,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)); @@ -2667,7 +2737,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); } } @@ -2688,8 +2758,10 @@ 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.line[term.ocy], term.col); term.ocx = cx; term.ocy = term.c.y; xfinishdraw(); diff --git a/st.c.orig b/st.c.orig new file mode 100644 index 0000000..1295c37 --- /dev/null +++ b/st.c.orig @@ -0,0 +1,2776 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ +#define HISTSIZE 2000 + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + Line hist[HISTSIZE]; /* history buffer */ + int histi; /* history index */ + int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static 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 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; + + if (term.row <= 0) + return; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +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 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + int sep = ';'; /* colon or semi-colon, but not both */ + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (sep == ';' && *p == ':') + sep = ':'; /* allow override to colon once */ + if (*p != sep || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n, 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: /* set foreground color to default */ + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: /* set background color to default */ + term.c.attr.bg = defaultbg; + break; + case 58: + /* This starts a sequence to change the color of + * "underline" pixels. We don't support that and + * instead eat up a following "5;n" or "2;r;g;b". */ + tdefcolor(attr, &i, l); + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: /* 1034: enable 8-bit mode for keyboard input */ + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen buffer */ + case 1047: /* swap screen buffer */ + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: /* save/restore cursor (like DECSC/DECRC) */ + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + LIMIT(csiescseq.arg[0], 1, 65535); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 0) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0], 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) */ + if (csiescseq.priv) { + goto unknown; + } else { + tcursor(CURSOR_LOAD); + } + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: /* manipulate selection data */ + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: /* set dynamic VT100 text foreground color */ + case 11: /* set dynamic VT100 text background color */ + case 12: /* set dynamic text cursor color */ + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, p); + } else { + tfulldirt(); + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + return; /* color reset without parameter */ + } + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + case 110: /* reset dynamic VT100 text foreground color */ + case 111: /* reset dynamic VT100 text background color */ + case 112: /* reset dynamic text cursor color */ + if (narg != 1) + break; + if ((j = par - 110) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + if (xsetcolorname(osc_table[j].idx, NULL)) { + fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str); + } else { + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1, 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(); + xsetmode(0, MODE_HIDE); + xsetmode(0, MODE_BRCKTPASTE); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + for (i = 0; i < HISTSIZE; i++) { + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); + for (j = mincol; j < col; j++) { + term.hist[i][j] = term.c.attr; + term.hist[i][j].u = ' '; + } + } + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(TLINE(y), x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + if (term.scr == 0) + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/st.h b/st.h index fd3b0d8..4e584b6 100644 --- a/st.h +++ b/st.h @@ -11,7 +11,8 @@ #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 || \ +#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \ + (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) @@ -81,6 +82,8 @@ void die(const char *, ...); void redraw(void); void draw(void); +void kscrolldown(const Arg *); +void kscrollup(const Arg *); void printscreen(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); diff --git a/st.o b/st.o new file mode 100644 index 0000000000000000000000000000000000000000..7c9e94bf6bca408428e5d59307653b7e2cdda788 GIT binary patch literal 82064 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 literal 0 HcmV?d00001 diff --git a/win.h b/win.h index 6de960d..94679e4 100644 --- a/win.h +++ b/win.h @@ -25,7 +25,7 @@ enum win_mode { void xbell(void); void xclipcopy(void); -void xdrawcursor(int, int, Glyph, int, int, Glyph); +void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int); void xdrawline(Line, int, int, int); void xfinishdraw(void); void xloadcols(void); diff --git a/x.c b/x.c index d73152b..5e5e4f4 100644 --- a/x.c +++ b/x.c @@ -19,6 +19,7 @@ char *argv0; #include "arg.h" #include "st.h" #include "win.h" +#include "hb.h" /* types used in config.h */ typedef struct { @@ -105,6 +106,7 @@ typedef struct { XSetWindowAttributes attrs; int scr; int isfixed; /* is fixed geometry? */ + int depth; /* bit depth */ int l, t; /* left and top offset */ int gm; /* geometry mask */ } XWindow; @@ -141,8 +143,9 @@ typedef struct { } DC; static inline ushort sixd_to_16bit(int); +static void xresetfontsettings(ushort mode, Font **font, int *frcflags); static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); -static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int); static void xdrawglyph(Glyph, int, int); static void xclear(int, int, int, int); static int xgeommasktogravity(int); @@ -752,12 +755,12 @@ xresize(int col, int row) XFreePixmap(xw.dpy, xw.buf); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); + xw.depth); XftDrawChange(xw.draw, xw.buf); xclear(0, 0, win.w, win.h); /* resize to new width */ - xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); } ushort @@ -812,6 +815,10 @@ xloadcols(void) else die("could not allocate color %d\n", i); } + + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; loaded = 1; } @@ -842,6 +849,12 @@ xsetcolorname(int x, const char *name) XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); dc.col[x] = ncolor; + if (x == defaultbg) { + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + return 0; } @@ -1062,6 +1075,9 @@ xunloadfont(Font *f) void xunloadfonts(void) { + /* Clear Harfbuzz font cache. */ + hbunloadfonts(); + /* Free the loaded fonts in the font cache. */ while (frclen > 0) XftFontClose(xw.dpy, frc[--frclen].font); @@ -1134,11 +1150,25 @@ xinit(int cols, int rows) Window parent, root; pid_t thispid = getpid(); XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; if (!(xw.dpy = XOpenDisplay(NULL))) die("can't open display\n"); xw.scr = XDefaultScreen(xw.dpy); - xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + + if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { + xw.vis = vis.visual; + xw.depth = vis.depth; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.vis = attr.visual; + xw.depth = attr.depth; + } /* font */ if (!FcInit()) @@ -1148,7 +1178,7 @@ xinit(int cols, int rows) xloadfonts(usedfont, 0); /* colors */ - xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); xloadcols(); /* adjust fixed window geometry */ @@ -1168,11 +1198,8 @@ xinit(int cols, int rows) | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; xw.attrs.colormap = xw.cmap; - root = XRootWindow(xw.dpy, xw.scr); - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) - parent = root; - xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, - win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); if (parent != root) @@ -1183,12 +1210,12 @@ xinit(int cols, int rows) dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, - DefaultDepth(xw.dpy, xw.scr)); + xw.depth); 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)); + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); /* Xft rendering context */ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); @@ -1242,142 +1269,153 @@ xinit(int cols, int rows) xsel.xtarget = XA_STRING; } +void +xresetfontsettings(ushort mode, Font **font, int *frcflags) +{ + *font = &dc.font; + 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; + } +} + 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; + ushort mode = glyphs[0].mode & ~ATTR_WRAP; Font *font = &dc.font; int frcflags = FRC_NORMAL; - float runewidth = win.cw; + float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f); Rune rune; FT_UInt glyphidx; FcResult fcres; FcPattern *fcpattern, *fontpattern; FcFontSet *fcsets[] = { NULL }; FcCharSet *fccharset; - int i, f, numspecs = 0; + int f, code_idx, numspecs = 0; + float cluster_xp = xp, cluster_yp = yp; + HbTransformData shaped = { 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; + /* Initial values. */ + xresetfontsettings(mode, &font, &frcflags); - /* Skip dummy wide-character spacing. */ - if (mode == ATTR_WDUMMY) + /* Shape the segment. */ + hbtransform(&shaped, font->match, glyphs, 0, len); + xp = winx; yp = winy + font->ascent; + cluster_xp = xp; cluster_yp = yp; + + for (code_idx = 0; code_idx < shaped.count; code_idx++) { + int idx = shaped.glyphs[code_idx].cluster; + + if (glyphs[idx].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; + /* Advance the drawing cursor if we've moved to a new cluster */ + if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) { + xp += runewidth; + cluster_xp = xp; + cluster_yp = yp; } - /* Lookup character index with default font. */ - glyphidx = XftCharIndex(xw.dpy, font->match, rune); - if (glyphidx) { + if (shaped.glyphs[code_idx].codepoint != 0) { + /* If symbol is found, put it into the specs. */ specs[numspecs].font = font->match; + specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint; + specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.); + specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.); + cluster_xp += shaped.positions[code_idx].x_advance / 64.; + cluster_yp += shaped.positions[code_idx].y_advance / 64.; + numspecs++; + } else { + /* If it's not found, try to fetch it through the font cache. */ + rune = glyphs[idx].u; + 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++; - 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++; } + /* Cleanup and get ready for next segment. */ + hbcleanup(&shaped); return numspecs; } void -xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) { - 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; @@ -1513,21 +1551,24 @@ void xdrawglyph(Glyph g, int x, int y) { int numspecs; - XftGlyphFontSpec spec; + XftGlyphFontSpec *specs = xw.specbuf; - numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); - xdrawglyphfontspecs(&spec, g, numspecs, x, y); + numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); + xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); } void -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) { Color drawcol; /* remove the old cursor */ if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; - xdrawglyph(og, ox, oy); + + /* Redraw the line where cursor was previously. + * It will restore the ligatures broken by the cursor. */ + xdrawline(line, 0, oy, len); if (IS_SET(MODE_HIDE)) return; @@ -1661,18 +1702,16 @@ xdrawline(Line line, int x1, int y1, int x2) 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++) { + for (x = x1; x < x2; 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; + if ((i > 0) && ATTRCMP(base, new)) { + numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1); + xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox); i = 0; } if (i == 0) { @@ -1681,8 +1720,10 @@ xdrawline(Line line, int x1, int y1, int x2) } i++; } - if (i > 0) - xdrawglyphfontspecs(specs, base, i, ox, y1); + if (i > 0) { + numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); + xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); + } } void @@ -2047,6 +2088,10 @@ main(int argc, char *argv[]) case 'a': allowaltscreen = 0; break; + case 'A': + alpha = strtof(EARGF(usage()), NULL); + LIMIT(alpha, 0.0, 1.0); + break; case 'c': opt_class = EARGF(usage()); break; diff --git a/x.c.orig b/x.c.orig new file mode 100644 index 0000000..f32fd6c --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2134 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|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 depth; /* bit depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(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 && + (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, + xw.depth); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + 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 - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + if (x == defaultbg) { + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + + 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, root; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + + if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { + xw.vis = vis.visual; + xw.depth = vis.depth; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.vis = attr.visual; + xw.depth = attr.depth; + } + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); + 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 (p[0] == '\0') + 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 (p[0] == '\0') + 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 'A': + alpha = strtof(EARGF(usage()), NULL); + LIMIT(alpha, 0.0, 1.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..2166f93fe07b8c9526b38bd5bbf2b74422c613b8 GIT binary patch literal 77552 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 JOOq7q{{f&4y?Fot literal 0 HcmV?d00001