From f7b397d0cb34ae7bd921793d76bce154a2c30b43 Mon Sep 17 00:00:00 2001 From: doryan Date: Sat, 14 Sep 2024 22:59:56 +0400 Subject: [PATCH] first commit: new st config --- .gitignore | 1 + Makefile | 5 +- config.def.h | 85 +++++---- config.h | 473 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.mk | 9 +- hb.c | 125 ++++++++++++++ hb.h | 14 ++ st | Bin 0 -> 115480 bytes st.c | 123 +++++++++++--- st.h | 5 +- win.h | 2 +- x.c | 335 ++++++++++++++++++++++-------------- 12 files changed, 971 insertions(+), 206 deletions(-) create mode 100644 .gitignore create mode 100644 config.h create mode 100644 hb.c create mode 100644 hb.h create mode 100755 st diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5761abc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.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..dfdc59e 100644 --- a/config.def.h +++ b/config.def.h @@ -5,8 +5,8 @@ * * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html */ -static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; -static int borderpx = 2; +static char *font = "FiraCode Nerd Font Propo:pixelsize=13"; +static int borderpx = 5; /* * What program is execed by st depends of these precedence rules: @@ -96,52 +96,52 @@ unsigned int tabspaces = 8; /* Terminal colors (16 first used in escape sequence) */ static const char *colorname[] = { /* 8 normal colors */ - "black", - "red3", - "green3", - "yellow3", - "blue2", - "magenta3", - "cyan3", - "gray90", - - /* 8 bright colors */ - "gray50", - "red", - "green", - "yellow", - "#5c5cff", - "magenta", - "cyan", - "white", - + "#323437", + "#ff5454", + "#8cc85f", + "#e3c78a", + "#80a0ff", + "#d183e8", + "#79dac8", + "#a1aab8", + "#7c8f8f", + "#ff5189", + "#36c692", + "#bfbf97", + "#74b2ff", + "#ae81ff", + "#85dc85", + "#e2637f", [255] = 0, /* more colors can be added after 255 to use with DefaultXX */ - "#cccccc", - "#555555", - "gray90", /* default foreground colour */ - "black", /* default background colour */ + "#272727", + "#f8f8f2", + "#080808", + "#eeeeee", }; - - /* * Default colors (colorname index) * foreground, background, cursor, reverse cursor */ -unsigned int defaultfg = 258; -unsigned int defaultbg = 259; -unsigned int defaultcs = 256; +unsigned int defaultfg = 259; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; static unsigned int defaultrcs = 257; -/* - * Default shape of cursor - * 2: Block ("█") - * 4: Underline ("_") - * 6: Bar ("|") - * 7: Snowman ("☃") +/* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81 + * Default style of cursor + * 0: blinking block + * 1: blinking block (default) + * 2: steady block ("█") + * 3: blinking underline + * 4: steady underline ("_") + * 5: blinking bar + * 6: steady bar ("|") + * 7: blinking st cursor + * 8: steady st cursor */ -static unsigned int cursorshape = 2; +static unsigned int cursorshape = 0; /* * Default columns and rows numbers @@ -177,10 +177,8 @@ static uint forcemousemod = ShiftMask; static MouseShortcut mshortcuts[] = { /* mask button function argument release */ { 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"} }, + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, }; /* Internal keyboard shortcuts. */ @@ -193,8 +191,8 @@ static Shortcut shortcuts[] = { { 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} }, + { ControlMask, XK_KP_Add, zoom, {.f = +1} }, + { ControlMask, XK_minus, zoom, {.f = -1} }, { TERMMOD, XK_Home, zoomreset, {.f = 0} }, { TERMMOD, XK_C, clipcopy, {.i = 0} }, { TERMMOD, XK_V, clippaste, {.i = 0} }, @@ -203,6 +201,7 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, }; + /* * Special keys (change & recompile st.info accordingly) * diff --git a/config.h b/config.h new file mode 100644 index 0000000..dfdc59e --- /dev/null +++ b/config.h @@ -0,0 +1,473 @@ +/* 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 Propo:pixelsize=13"; +static int borderpx = 5; + +/* + * 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; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "#323437", + "#ff5454", + "#8cc85f", + "#e3c78a", + "#80a0ff", + "#d183e8", + "#79dac8", + "#a1aab8", + "#7c8f8f", + "#ff5189", + "#36c692", + "#bfbf97", + "#74b2ff", + "#ae81ff", + "#85dc85", + "#e2637f", + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#272727", + "#f8f8f2", + "#080808", + "#eeeeee", +}; +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 259; +unsigned int defaultbg = 256; +unsigned int defaultcs = 257; +static unsigned int defaultrcs = 257; + +/* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-Ps-SP-q.1D81 + * Default style of cursor + * 0: blinking block + * 1: blinking block (default) + * 2: steady block ("█") + * 3: blinking underline + * 4: steady underline ("_") + * 5: blinking bar + * 6: steady bar ("|") + * 7: blinking st cursor + * 8: steady st cursor + */ +static unsigned int cursorshape = 0; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, kscrollup, {.i = 1} }, + { ShiftMask, Button5, kscrolldown, {.i = 1} }, +}; + +/* 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} }, + { ControlMask, XK_KP_Add, zoom, {.f = +1} }, + { ControlMask, XK_minus, 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} }, +}; + + +/* + * 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 fdc29a7..07fedba 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # st version -VERSION = 0.9.2 +VERSION = 0.9 # Customize below to fit your system @@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config # includes and libs INCS = -I$(X11INC) \ `$(PKG_CONFIG) --cflags fontconfig` \ - `$(PKG_CONFIG) --cflags freetype2` + `$(PKG_CONFIG) --cflags freetype2` \ + `$(PKG_CONFIG) --cflags harfbuzz` LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ `$(PKG_CONFIG) --libs fontconfig` \ - `$(PKG_CONFIG) --libs freetype2` + `$(PKG_CONFIG) --libs freetype2` \ + `$(PKG_CONFIG) --libs harfbuzz` # flags STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 @@ -34,3 +36,4 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) # compiler and linker # CC = c99 + diff --git a/hb.c b/hb.c new file mode 100644 index 0000000..99412c8 --- /dev/null +++ b/hb.c @@ -0,0 +1,125 @@ +#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[] = { }; + +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/st b/st new file mode 100755 index 0000000000000000000000000000000000000000..09477cfe06bb98a5e2a993ca7d079b0232cddf5f GIT binary patch literal 115480 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 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); @@ -1055,13 +1065,53 @@ tswapscreen(void) } void -tscrolldown(int orig, int n) +kscrolldown(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (n > term.scr) + n = term.scr; + + if (term.scr > 0) { + term.scr -= n; + selscroll(0, -n); + tfulldirt(); + } +} + +void +kscrollup(const Arg* a) +{ + int n = a->i; + + if (n < 0) + n = term.row + n; + + if (term.scr <= HISTSIZE-n) { + term.scr += n; + selscroll(0, n); + tfulldirt(); + } +} + +void +tscrolldown(int orig, int n, int copyhist) { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[term.bot]; + term.line[term.bot] = temp; + } + tsetdirt(orig, term.bot-n); tclearregion(0, term.bot-n+1, term.col-1, term.bot); @@ -1071,17 +1121,28 @@ tscrolldown(int orig, int n) term.line[i-n] = temp; } - selscroll(orig, n); + if (term.scr == 0) + selscroll(orig, n); } void -tscrollup(int orig, int n) +tscrollup(int orig, int n, int copyhist) { int i; Line temp; LIMIT(n, 0, term.bot-orig+1); + if (copyhist) { + term.histi = (term.histi + 1) % HISTSIZE; + temp = term.hist[term.histi]; + term.hist[term.histi] = term.line[orig]; + term.line[orig] = temp; + } + + if (term.scr > 0 && term.scr < HISTSIZE) + term.scr = MIN(term.scr + n, HISTSIZE-1); + tclearregion(0, orig, term.col-1, orig+n-1); tsetdirt(orig+n, term.bot); @@ -1091,7 +1152,8 @@ tscrollup(int orig, int n) term.line[i+n] = temp; } - selscroll(orig, -n); + if (term.scr == 0) + selscroll(orig, -n); } void @@ -1120,7 +1182,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++; } @@ -1288,14 +1350,14 @@ void tinsertblankline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrolldown(term.c.y, n); + tscrolldown(term.c.y, n, 0); } void tdeleteline(int n) { if (BETWEEN(term.c.y, term.top, term.bot)) - tscrollup(term.c.y, n); + tscrollup(term.c.y, n, 0); } int32_t @@ -1733,11 +1795,11 @@ csihandle(void) 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); @@ -2309,7 +2371,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); } @@ -2322,7 +2384,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); } @@ -2545,7 +2607,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; @@ -2582,6 +2644,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)); @@ -2640,7 +2710,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); } } @@ -2662,7 +2732,8 @@ draw(void) drawregion(0, 0, term.col, term.row); xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx, 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.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/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..0b2bcf1 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 { @@ -141,8 +142,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); @@ -253,6 +255,7 @@ static char *opt_name = NULL; static char *opt_title = NULL; static uint buttons; /* bit field of pressed buttons */ +static int cursorblinks = 0; void clipcopy(const Arg *dummy) @@ -757,7 +760,7 @@ xresize(int col, int row) 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 @@ -1062,6 +1065,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); @@ -1188,7 +1194,7 @@ xinit(int cols, int rows) 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,6 +1248,22 @@ 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) { @@ -1256,128 +1278,156 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x FcPattern *fcpattern, *fontpattern; FcFontSet *fcsets[] = { NULL }; FcCharSet *fccharset; - int i, f, numspecs = 0; + int i, f, length = 0, start = 0, numspecs = 0; + float cluster_xp = xp, cluster_yp = yp; + HbTransformData shaped = { 0 }; + + /* Initial values. */ + mode = prevmode = glyphs[0].mode & ~ATTR_WRAP; + xresetfontsettings(mode, &font, &frcflags); 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; + mode = glyphs[i].mode & ~ATTR_WRAP; /* Skip dummy wide-character spacing. */ - if (mode == ATTR_WDUMMY) + if (mode & ATTR_WDUMMY && i < (len - 1)) 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)); + if ( + prevmode != mode + || ATTRCMP(glyphs[start], glyphs[i]) + || selected(x + i, y) != selected(x + start, y) + || i == (len - 1) + ) { + /* Handle 1-character wide segments and end of line */ + length = i - start; + if (i == start) { + length = 1; + } else if (i == (len - 1)) { + length = (i - start + 1); } - 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; + /* Shape the segment. */ + hbtransform(&shaped, font->match, glyphs, start, length); + runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f); + cluster_xp = xp; cluster_yp = yp; + for (int code_idx = 0; code_idx < shaped.count; code_idx++) { + int idx = shaped.glyphs[code_idx].cluster; - glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + if (glyphs[start + idx].mode & ATTR_WDUMMY) + continue; - f = frclen; - frclen++; + /* 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; + runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f); + } - FcPatternDestroy(fcpattern); - FcCharSetDestroy(fccharset); + 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[start + 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; + numspecs++; + } + } + + /* Cleanup and get ready for next segment. */ + hbcleanup(&shaped); + start = i; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + xresetfontsettings(mode, &font, &frcflags); + yp = winy + font->ascent; + } } - - 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) +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 +1563,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; @@ -1561,29 +1614,44 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) /* draw the new one */ if (IS_SET(MODE_FOCUSED)) { switch (win.cursor) { - case 7: /* st extension */ - g.u = 0x2603; /* snowman (U+2603) */ + default: + case 0: /* blinking block */ + case 1: /* blinking block (default) */ + if (IS_SET(MODE_BLINK)) + break; /* FALLTHROUGH */ - case 0: /* Blinking Block */ - case 1: /* Blinking Block (Default) */ - case 2: /* Steady Block */ + case 2: /* steady block */ xdrawglyph(g, cx, cy); break; - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ + case 3: /* blinking underline */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + 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 */ + case 5: /* blinking bar */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 6: /* steady bar */ XftDrawRect(xw.draw, &drawcol, borderpx + cx * win.cw, borderpx + cy * win.ch, cursorthickness, win.ch); break; + case 7: /* blinking st cursor */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 8: /* steady st cursor */ + g.u = xsetcursor; + xdrawglyph(g, cx, cy); + break; } } else { XftDrawRect(xw.draw, &drawcol, @@ -1661,18 +1729,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 +1747,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 @@ -1746,9 +1814,12 @@ xsetmode(int set, unsigned int flags) int xsetcursor(int cursor) { - if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */ return 1; win.cursor = cursor; + cursorblinks = win.cursor == 0 || win.cursor == 1 || + win.cursor == 3 || win.cursor == 5 || + win.cursor == 7; return 0; } @@ -1995,6 +2066,10 @@ run(void) if (FD_ISSET(ttyfd, &rfd) || xev) { if (!drawing) { trigger = now; + if (IS_SET(MODE_BLINK)) { + win.mode ^= MODE_BLINK; + } + lastblink = now; drawing = 1; } timeout = (maxlatency - TIMEDIFF(now, trigger)) \ @@ -2005,7 +2080,7 @@ run(void) /* idle detected or maxlatency exhausted -> draw */ timeout = -1; - if (blinktimeout && tattrset(ATTR_BLINK)) { + if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) { timeout = blinktimeout - TIMEDIFF(now, lastblink); if (timeout <= 0) { if (-timeout > blinktimeout) /* start visible */