summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--FUNDING.yml2
-rw-r--r--LEGACY17
-rw-r--r--LICENSE4
-rw-r--r--Makefile19
-rw-r--r--PKGBUILD45
-rw-r--r--README34
-rw-r--r--README.md99
-rw-r--r--TODO28
-rw-r--r--Xdefaults128
-rw-r--r--boxdraw.c194
-rw-r--r--boxdraw_data.h214
-rw-r--r--config.h558
-rw-r--r--config.mk9
-rw-r--r--hb.c154
-rw-r--r--hb.h7
-rwxr-xr-xst-copyout13
-rwxr-xr-xst-urlhandler19
-rw-r--r--st.136
-rw-r--r--st.c232
-rw-r--r--st.h22
-rw-r--r--win.h3
-rw-r--r--x.c380
23 files changed, 2027 insertions, 195 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0438df2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+patches/
+.gitignore
+*.o
+st
+*.orig
diff --git a/FUNDING.yml b/FUNDING.yml
new file mode 100644
index 0000000..c7c9a22
--- /dev/null
+++ b/FUNDING.yml
@@ -0,0 +1,2 @@
+custom: ["https://lukesmith.xyz/donate.html"]
+github: lukesmithxyz
diff --git a/LEGACY b/LEGACY
deleted file mode 100644
index bf28b1e..0000000
--- a/LEGACY
+++ /dev/null
@@ -1,17 +0,0 @@
-A STATEMENT ON LEGACY SUPPORT
-
-In the terminal world there is much cruft that comes from old and unsup‐
-ported terminals that inherit incompatible modes and escape sequences
-which noone is able to know, except when he/she comes from that time and
-developed a graphical vt100 emulator at that time.
-
-One goal of st is to only support what is really needed. When you en‐
-counter a sequence which you really need, implement it. But while you
-are at it, do not add the other cruft you might encounter while sneek‐
-ing at other terminal emulators. History has bloated them and there is
-no real evidence that most of the sequences are used today.
-
-
-Christoph Lohmann <20h@r-36.net>
-2012-09-13T07:00:36.081271045+02:00
-
diff --git a/LICENSE b/LICENSE
index 3cbf420..a5cd534 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,10 @@
MIT/X Consortium License
+<<<<<<< HEAD
+© 2014-2020 Hiltjo Posthuma <hiltjo at codemadness dot org>
+=======
© 2014-2022 Hiltjo Posthuma <hiltjo at codemadness dot org>
+>>>>>>> suckless-master
© 2018 Devin J. Pohly <djpohly at gmail dot com>
© 2014-2017 Quentin Rameau <quinq at fifth dot space>
© 2009-2012 Aurélien APTEL <aurelien dot aptel at gmail dot com>
diff --git a/Makefile b/Makefile
index 470ac86..02045f0 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
include config.mk
-SRC = st.c x.c
+SRC = st.c x.c boxdraw.c hb.c
OBJ = $(SRC:.c=.o)
all: options st
@@ -15,14 +15,13 @@ options:
@echo "LDFLAGS = $(STLDFLAGS)"
@echo "CC = $(CC)"
-config.h:
- cp config.def.h config.h
-
.c.o:
$(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
+boxdraw.o: config.h st.h boxdraw_data.h
$(OBJ): config.h config.mk
@@ -30,12 +29,12 @@ st: $(OBJ)
$(CC) -o $@ $(OBJ) $(STLDFLAGS)
clean:
- rm -f st $(OBJ) st-$(VERSION).tar.gz
+ rm -f st $(OBJ) st-$(VERSION).tar.gz *.rej *.orig *.o
dist: clean
mkdir -p st-$(VERSION)
cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\
- config.def.h st.info st.1 arg.h st.h win.h $(SRC)\
+ config.h st.info st.1 arg.h st.h win.h $(SRC)\
st-$(VERSION)
tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz
rm -rf st-$(VERSION)
@@ -43,7 +42,11 @@ dist: clean
install: st
mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f st $(DESTDIR)$(PREFIX)/bin
+ cp -f st-copyout $(DESTDIR)$(PREFIX)/bin
+ cp -f st-urlhandler $(DESTDIR)$(PREFIX)/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/st
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/st-copyout
+ chmod 755 $(DESTDIR)$(PREFIX)/bin/st-urlhandler
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
@@ -52,6 +55,8 @@ install: st
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st
+ rm -f $(DESTDIR)$(PREFIX)/bin/st-copyout
+ rm -f $(DESTDIR)$(PREFIX)/bin/st-urlhandler
rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
.PHONY: all options clean dist install uninstall
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 0000000..66dd05e
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,45 @@
+# Maintainer:
+
+pkgname=st-luke-git
+_pkgname=st
+pkgver=0.8.2.r1062.2087ab9
+pkgrel=1
+epoch=1
+pkgdesc="Luke's simple (suckless) terminal with vim-bindings, transparency, xresources, etc. "
+url='https://github.com/LukeSmithxyz/st'
+arch=('i686' 'x86_64')
+license=('MIT')
+options=('zipman')
+depends=('libxft')
+makedepends=('ncurses' 'libxext' 'git')
+optdepends=('dmenu: feed urls to dmenu')
+source=(git+https://github.com/LukeSmithxyz/st)
+sha1sums=('SKIP')
+
+provides=("${_pkgname}")
+conflicts=("${_pkgname}")
+
+pkgver() {
+ cd "${_pkgname}"
+ printf "%s.r%s.%s" "$(awk '/^VERSION =/ {print $3}' config.mk)" \
+ "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
+}
+
+prepare() {
+ cd $srcdir/${_pkgname}
+ # skip terminfo which conflicts with ncurses
+ sed -i '/tic /d' Makefile
+}
+
+build() {
+ cd "${_pkgname}"
+ make X11INC=/usr/include/X11 X11LIB=/usr/lib/X11
+}
+
+package() {
+ cd "${_pkgname}"
+ make PREFIX=/usr DESTDIR="${pkgdir}" install
+ install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
+ install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
+ install -Dm644 Xdefaults "${pkgdir}/usr/share/doc/${pkgname}/Xdefaults.example"
+}
diff --git a/README b/README
deleted file mode 100644
index 6a846ed..0000000
--- a/README
+++ /dev/null
@@ -1,34 +0,0 @@
-st - simple terminal
---------------------
-st is a simple terminal emulator for X which sucks less.
-
-
-Requirements
-------------
-In order to build st you need the Xlib header files.
-
-
-Installation
-------------
-Edit config.mk to match your local setup (st is installed into
-the /usr/local namespace by default).
-
-Afterwards enter the following command to build and install st (if
-necessary as root):
-
- make clean install
-
-
-Running st
-----------
-If you did not install st with make clean install, you must compile
-the st terminfo entry with the following command:
-
- tic -sx st.info
-
-See the man page for additional details.
-
-Credits
--------
-Based on Aurélien APTEL <aurelien dot aptel at gmail dot com> bt source code.
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c410e4b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,99 @@
+# Luke's build of st - the simple (suckless) terminal
+
+The [suckless terminal (st)](https://st.suckless.org/) with some additional
+features that make it literally the best terminal emulator ever:
+
+## Unique features (using dmenu)
+
++ **follow urls** by pressing `alt-l`
++ **copy urls** in the same way with `alt-y`
++ **copy the output of commands** with `alt-o`
+
+## Bindings for
+
++ **scrollback** with `alt-↑/↓` or `alt-pageup/down` or `shift` while scrolling the
+ mouse.
++ OR **vim-bindings**: scroll up/down in history with `alt-k` and `alt-j`.
+ Faster with `alt-u`/`alt-d`.
++ **zoom/change font size**: same bindings as above, but holding down shift as
+ well. `alt-home` returns to default
++ **copy text** with `alt-c`, **paste** is `alt-v` or `shift-insert`
+
+## Pretty stuff
+
++ Compatibility with `Xresources` and `pywal` for dynamic colors.
++ Default [gruvbox](https://github.com/morhetz/gruvbox) colors otherwise.
++ Transparency/alpha, which is also adjustable from your `Xresources`.
++ Default font is system "mono" at 14pt, meaning the font will match your
+ system font.
+
+## Other st patches
+
++ Boxdraw
++ Ligatures
++ font2
++ updated to latest version 0.8.4
+
+## Installation for newbs
+
+You should have xlib header files and libharfbuzz build files installed.
+
+```
+git clone https://github.com/LukeSmithxyz/st
+cd st
+sudo make install
+```
+
+Obviously, `make` is required to build. `fontconfig` is required for the
+default build, since it asks `fontconfig` for your system monospace font. It
+might be obvious, but `libX11` and `libXft` are required as well. Chances are,
+you have all of this installed already.
+
+On OpenBSD, be sure to edit `config.mk` first and remove `-lrt` from the
+`$LIBS` before compiling.
+
+Be sure to have a composite manager (`xcompmgr`, `picom`, etc.) running if you
+want transparency.
+
+## How to configure dynamically with Xresources
+
+For many key variables, this build of `st` will look for X settings set in
+either `~/.Xdefaults` or `~/.Xresources`. You must run `xrdb` on one of these
+files to load the settings.
+
+For example, you can define your desired fonts, transparency or colors:
+
+```
+*.font: Liberation Mono:pixelsize=12:antialias=true:autohint=true;
+*.alpha: 0.9
+*.color0: #111
+...
+```
+
+The `alpha` value (for transparency) goes from `0` (transparent) to `1`
+(opaque). There is an example `Xdefaults` file in this respository.
+
+### Colors
+
+To be clear about the color settings:
+
+- This build will use gruvbox colors by default and as a fallback.
+- If there are Xresources colors defined, those will take priority.
+- But if `wal` has run in your session, its colors will take priority.
+
+Note that when you run `wal`, it will negate the transparency of existing windows, but new windows will continue with the previously defined transparency.
+
+## Notes on Emojis and Special Characters
+
+If st crashes when viewing emojis, install
+[libxft-bgra](https://aur.archlinux.org/packages/libxft-bgra/) from the AUR.
+
+Note that some special characters may appear truncated if too wide. You might
+want to manually set your prefered emoji/special character font to a lower size
+in the `config.h` file to avoid this. By default, JoyPixels is used at a
+smaller size than the usual text.
+
+## Contact
+
+- Luke Smith <luke@lukesmith.xyz>
+- [https://lukesmith.xyz](https://lukesmith.xyz)
diff --git a/TODO b/TODO
deleted file mode 100644
index 5f74cd5..0000000
--- a/TODO
+++ /dev/null
@@ -1,28 +0,0 @@
-vt emulation
-------------
-
-* double-height support
-
-code & interface
-----------------
-
-* add a simple way to do multiplexing
-
-drawing
--------
-* add diacritics support to xdraws()
- * switch to a suckless font drawing library
-* make the font cache simpler
-* add better support for brightening of the upper colors
-
-bugs
-----
-
-* fix shift up/down (shift selection in emacs)
-* remove DEC test sequence when appropriate
-
-misc
-----
-
- $ grep -nE 'XXX|TODO' st.c
-
diff --git a/Xdefaults b/Xdefaults
new file mode 100644
index 0000000..040c772
--- /dev/null
+++ b/Xdefaults
@@ -0,0 +1,128 @@
+!! Transparency (0-1):
+st.alpha: 0.92
+st.alphaOffset: 0.3
+
+!! Set a default font and font size as below:
+st.font: Monospace-11;
+
+! st.termname: st-256color
+! st.borderpx: 2
+
+!! Set the background, foreground and cursor colors as below:
+
+!! gruvbox:
+*.color0: #1d2021
+*.color1: #cc241d
+*.color2: #98971a
+*.color3: #d79921
+*.color4: #458588
+*.color5: #b16286
+*.color6: #689d6a
+*.color7: #a89984
+*.color8: #928374
+*.color9: #fb4934
+*.color10: #b8bb26
+*.color11: #fabd2f
+*.color12: #83a598
+*.color13: #d3869b
+*.color14: #8ec07c
+*.color15: #ebdbb2
+*.background: #282828
+*.foreground: white
+*.cursorColor: white
+
+/* /1* !! gruvbox light: *1/ */
+/* *.color0: #fbf1c7 */
+/* *.color1: #cc241d */
+/* *.color2: #98971a */
+/* *.color3: #d79921 */
+/* *.color4: #458588 */
+/* *.color5: #b16286 */
+/* *.color6: #689d6a */
+/* *.color7: #7c6f64 */
+/* *.color8: #928374 */
+/* *.color9: #9d0006 */
+/* *.color10: #79740e */
+/* *.color11: #b57614 */
+/* *.color12: #076678 */
+/* *.color13: #8f3f71 */
+/* *.color14: #427b58 */
+/* *.color15: #3c3836 */
+/* *.background: #fbf1c7 */
+/* *.foreground: #282828 */
+/* *.cursorColor: #282828 */
+
+/* !! brogrammer: */
+/* *.foreground: #d6dbe5 */
+/* *.background: #131313 */
+/* *.color0: #1f1f1f */
+/* *.color8: #d6dbe5 */
+/* *.color1: #f81118 */
+/* *.color9: #de352e */
+/* *.color2: #2dc55e */
+/* *.color10: #1dd361 */
+/* *.color3: #ecba0f */
+/* *.color11: #f3bd09 */
+/* *.color4: #2a84d2 */
+/* *.color12: #1081d6 */
+/* *.color5: #4e5ab7 */
+/* *.color13: #5350b9 */
+/* *.color6: #1081d6 */
+/* *.color14: #0f7ddb */
+/* *.color7: #d6dbe5 */
+/* *.color15: #ffffff */
+/* *.colorBD: #d6dbe5 */
+
+/* ! base16 */
+/* *.color0: #181818 */
+/* *.color1: #ab4642 */
+/* *.color2: #a1b56c */
+/* *.color3: #f7ca88 */
+/* *.color4: #7cafc2 */
+/* *.color5: #ba8baf */
+/* *.color6: #86c1b9 */
+/* *.color7: #d8d8d8 */
+/* *.color8: #585858 */
+/* *.color9: #ab4642 */
+/* *.color10: #a1b56c */
+/* *.color11: #f7ca88 */
+/* *.color12: #7cafc2 */
+/* *.color13: #ba8baf */
+/* *.color14: #86c1b9 */
+/* *.color15: #f8f8f8 */
+
+/* !! solarized */
+/* *.color0: #073642 */
+/* *.color1: #dc322f */
+/* *.color2: #859900 */
+/* *.color3: #b58900 */
+/* *.color4: #268bd2 */
+/* *.color5: #d33682 */
+/* *.color6: #2aa198 */
+/* *.color7: #eee8d5 */
+/* *.color9: #cb4b16 */
+/* *.color8: #fdf6e3 */
+/* *.color10: #586e75 */
+/* *.color11: #657b83 */
+/* *.color12: #839496 */
+/* *.color13: #6c71c4 */
+/* *.color14: #93a1a1 */
+/* *.color15: #fdf6e3 */
+
+/* !! xterm */
+/* *.color0: #000000 */
+/* *.color1: #cd0000 */
+/* *.color2: #00cd00 */
+/* *.color3: #cdcd00 */
+/* *.color4: #0000cd */
+/* *.color5: #cd00cd */
+/* *.color6: #00cdcd */
+/* *.color7: #e5e5e5 */
+/* *.color8: #4d4d4d */
+/* *.color9: #ff0000 */
+/* *.color10: #00ff00 */
+/* *.color11: #ffff00 */
+/* *.color12: #0000ff */
+/* *.color13: #ff00ff */
+/* *.color14: #00ffff */
+/* *.color15: #aabac8 */
diff --git a/boxdraw.c b/boxdraw.c
new file mode 100644
index 0000000..28a92d0
--- /dev/null
+++ b/boxdraw.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
+ * MIT/X Consortium License
+ */
+
+#include <X11/Xft/Xft.h>
+#include "st.h"
+#include "boxdraw_data.h"
+
+/* Rounded non-negative integers division of n / d */
+#define DIV(n, d) (((n) + (d) / 2) / (d))
+
+static Display *xdpy;
+static Colormap xcmap;
+static XftDraw *xd;
+static Visual *xvis;
+
+static void drawbox(int, int, int, int, XftColor *, XftColor *, ushort);
+static void drawboxlines(int, int, int, int, XftColor *, ushort);
+
+/* public API */
+
+void
+boxdraw_xinit(Display *dpy, Colormap cmap, XftDraw *draw, Visual *vis)
+{
+ xdpy = dpy; xcmap = cmap; xd = draw, xvis = vis;
+}
+
+int
+isboxdraw(Rune u)
+{
+ Rune block = u & ~0xff;
+ return (boxdraw && block == 0x2500 && boxdata[(uint8_t)u]) ||
+ (boxdraw_braille && block == 0x2800);
+}
+
+/* the "index" is actually the entire shape data encoded as ushort */
+ushort
+boxdrawindex(const Glyph *g)
+{
+ if (boxdraw_braille && (g->u & ~0xff) == 0x2800)
+ return BRL | (uint8_t)g->u;
+ if (boxdraw_bold && (g->mode & ATTR_BOLD))
+ return BDB | boxdata[(uint8_t)g->u];
+ return boxdata[(uint8_t)g->u];
+}
+
+void
+drawboxes(int x, int y, int cw, int ch, XftColor *fg, XftColor *bg,
+ const XftGlyphFontSpec *specs, int len)
+{
+ for ( ; len-- > 0; x += cw, specs++)
+ drawbox(x, y, cw, ch, fg, bg, (ushort)specs->glyph);
+}
+
+/* implementation */
+
+void
+drawbox(int x, int y, int w, int h, XftColor *fg, XftColor *bg, ushort bd)
+{
+ ushort cat = bd & ~(BDB | 0xff); /* mask out bold and data */
+ if (bd & (BDL | BDA)) {
+ /* lines (light/double/heavy/arcs) */
+ drawboxlines(x, y, w, h, fg, bd);
+
+ } else if (cat == BBD) {
+ /* lower (8-X)/8 block */
+ int d = DIV((uint8_t)bd * h, 8);
+ XftDrawRect(xd, fg, x, y + d, w, h - d);
+
+ } else if (cat == BBU) {
+ /* upper X/8 block */
+ XftDrawRect(xd, fg, x, y, w, DIV((uint8_t)bd * h, 8));
+
+ } else if (cat == BBL) {
+ /* left X/8 block */
+ XftDrawRect(xd, fg, x, y, DIV((uint8_t)bd * w, 8), h);
+
+ } else if (cat == BBR) {
+ /* right (8-X)/8 block */
+ int d = DIV((uint8_t)bd * w, 8);
+ XftDrawRect(xd, fg, x + d, y, w - d, h);
+
+ } else if (cat == BBQ) {
+ /* Quadrants */
+ int w2 = DIV(w, 2), h2 = DIV(h, 2);
+ if (bd & TL)
+ XftDrawRect(xd, fg, x, y, w2, h2);
+ if (bd & TR)
+ XftDrawRect(xd, fg, x + w2, y, w - w2, h2);
+ if (bd & BL)
+ XftDrawRect(xd, fg, x, y + h2, w2, h - h2);
+ if (bd & BR)
+ XftDrawRect(xd, fg, x + w2, y + h2, w - w2, h - h2);
+
+ } else if (bd & BBS) {
+ /* Shades - data is 1/2/3 for 25%/50%/75% alpha, respectively */
+ int d = (uint8_t)bd;
+ XftColor xfc;
+ XRenderColor xrc = { .alpha = 0xffff };
+
+ xrc.red = DIV(fg->color.red * d + bg->color.red * (4 - d), 4);
+ xrc.green = DIV(fg->color.green * d + bg->color.green * (4 - d), 4);
+ xrc.blue = DIV(fg->color.blue * d + bg->color.blue * (4 - d), 4);
+
+ XftColorAllocValue(xdpy, xvis, xcmap, &xrc, &xfc);
+ XftDrawRect(xd, &xfc, x, y, w, h);
+ XftColorFree(xdpy, xvis, xcmap, &xfc);
+
+ } else if (cat == BRL) {
+ /* braille, each data bit corresponds to one dot at 2x4 grid */
+ int w1 = DIV(w, 2);
+ int h1 = DIV(h, 4), h2 = DIV(h, 2), h3 = DIV(3 * h, 4);
+
+ if (bd & 1) XftDrawRect(xd, fg, x, y, w1, h1);
+ if (bd & 2) XftDrawRect(xd, fg, x, y + h1, w1, h2 - h1);
+ if (bd & 4) XftDrawRect(xd, fg, x, y + h2, w1, h3 - h2);
+ if (bd & 8) XftDrawRect(xd, fg, x + w1, y, w - w1, h1);
+ if (bd & 16) XftDrawRect(xd, fg, x + w1, y + h1, w - w1, h2 - h1);
+ if (bd & 32) XftDrawRect(xd, fg, x + w1, y + h2, w - w1, h3 - h2);
+ if (bd & 64) XftDrawRect(xd, fg, x, y + h3, w1, h - h3);
+ if (bd & 128) XftDrawRect(xd, fg, x + w1, y + h3, w - w1, h - h3);
+
+ }
+}
+
+void
+drawboxlines(int x, int y, int w, int h, XftColor *fg, ushort bd)
+{
+ /* s: stem thickness. width/8 roughly matches underscore thickness. */
+ /* We draw bold as 1.5 * normal-stem and at least 1px thicker. */
+ /* doubles draw at least 3px, even when w or h < 3. bold needs 6px. */
+ int mwh = MIN(w, h);
+ int base_s = MAX(1, DIV(mwh, 8));
+ int bold = (bd & BDB) && mwh >= 6; /* possibly ignore boldness */
+ int s = bold ? MAX(base_s + 1, DIV(3 * base_s, 2)) : base_s;
+ int w2 = DIV(w - s, 2), h2 = DIV(h - s, 2);
+ /* the s-by-s square (x + w2, y + h2, s, s) is the center texel. */
+ /* The base length (per direction till edge) includes this square. */
+
+ int light = bd & (LL | LU | LR | LD);
+ int double_ = bd & (DL | DU | DR | DD);
+
+ if (light) {
+ /* d: additional (negative) length to not-draw the center */
+ /* texel - at arcs and avoid drawing inside (some) doubles */
+ int arc = bd & BDA;
+ int multi_light = light & (light - 1);
+ int multi_double = double_ & (double_ - 1);
+ /* light crosses double only at DH+LV, DV+LH (ref. shapes) */
+ int d = arc || (multi_double && !multi_light) ? -s : 0;
+
+ if (bd & LL)
+ XftDrawRect(xd, fg, x, y + h2, w2 + s + d, s);
+ if (bd & LU)
+ XftDrawRect(xd, fg, x + w2, y, s, h2 + s + d);
+ if (bd & LR)
+ XftDrawRect(xd, fg, x + w2 - d, y + h2, w - w2 + d, s);
+ if (bd & LD)
+ XftDrawRect(xd, fg, x + w2, y + h2 - d, s, h - h2 + d);
+ }
+
+ /* double lines - also align with light to form heavy when combined */
+ if (double_) {
+ /*
+ * going clockwise, for each double-ray: p is additional length
+ * to the single-ray nearer to the previous direction, and n to
+ * the next. p and n adjust from the base length to lengths
+ * which consider other doubles - shorter to avoid intersections
+ * (p, n), or longer to draw the far-corner texel (n).
+ */
+ int dl = bd & DL, du = bd & DU, dr = bd & DR, dd = bd & DD;
+ if (dl) {
+ int p = dd ? -s : 0, n = du ? -s : dd ? s : 0;
+ XftDrawRect(xd, fg, x, y + h2 + s, w2 + s + p, s);
+ XftDrawRect(xd, fg, x, y + h2 - s, w2 + s + n, s);
+ }
+ if (du) {
+ int p = dl ? -s : 0, n = dr ? -s : dl ? s : 0;
+ XftDrawRect(xd, fg, x + w2 - s, y, s, h2 + s + p);
+ XftDrawRect(xd, fg, x + w2 + s, y, s, h2 + s + n);
+ }
+ if (dr) {
+ int p = du ? -s : 0, n = dd ? -s : du ? s : 0;
+ XftDrawRect(xd, fg, x + w2 - p, y + h2 - s, w - w2 + p, s);
+ XftDrawRect(xd, fg, x + w2 - n, y + h2 + s, w - w2 + n, s);
+ }
+ if (dd) {
+ int p = dr ? -s : 0, n = dl ? -s : dr ? s : 0;
+ XftDrawRect(xd, fg, x + w2 + s, y + h2 - p, s, h - h2 + p);
+ XftDrawRect(xd, fg, x + w2 - s, y + h2 - n, s, h - h2 + n);
+ }
+ }
+}
diff --git a/boxdraw_data.h b/boxdraw_data.h
new file mode 100644
index 0000000..7890500
--- /dev/null
+++ b/boxdraw_data.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2018 Avi Halachmi (:avih) avihpit@yahoo.com https://github.com/avih
+ * MIT/X Consortium License
+ */
+
+/*
+ * U+25XX codepoints data
+ *
+ * References:
+ * http://www.unicode.org/charts/PDF/U2500.pdf
+ * http://www.unicode.org/charts/PDF/U2580.pdf
+ *
+ * Test page:
+ * https://github.com/GNOME/vte/blob/master/doc/boxes.txt
+ */
+
+/* Each shape is encoded as 16-bits. Higher bits are category, lower are data */
+/* Categories (mutually exclusive except BDB): */
+/* For convenience, BDL/BDA/BBS/BDB are 1 bit each, the rest are enums */
+#define BDL (1<<8) /* Box Draw Lines (light/double/heavy) */
+#define BDA (1<<9) /* Box Draw Arc (light) */
+
+#define BBD (1<<10) /* Box Block Down (lower) X/8 */
+#define BBL (2<<10) /* Box Block Left X/8 */
+#define BBU (3<<10) /* Box Block Upper X/8 */
+#define BBR (4<<10) /* Box Block Right X/8 */
+#define BBQ (5<<10) /* Box Block Quadrants */
+#define BRL (6<<10) /* Box Braille (data is lower byte of U28XX) */
+
+#define BBS (1<<14) /* Box Block Shades */
+#define BDB (1<<15) /* Box Draw is Bold */
+
+/* (BDL/BDA) Light/Double/Heavy x Left/Up/Right/Down/Horizontal/Vertical */
+/* Heavy is light+double (literally drawing light+double align to form heavy) */
+#define LL (1<<0)
+#define LU (1<<1)
+#define LR (1<<2)
+#define LD (1<<3)
+#define LH (LL+LR)
+#define LV (LU+LD)
+
+#define DL (1<<4)
+#define DU (1<<5)
+#define DR (1<<6)
+#define DD (1<<7)
+#define DH (DL+DR)
+#define DV (DU+DD)
+
+#define HL (LL+DL)
+#define HU (LU+DU)
+#define HR (LR+DR)
+#define HD (LD+DD)
+#define HH (HL+HR)
+#define HV (HU+HD)
+
+/* (BBQ) Quadrants Top/Bottom x Left/Right */
+#define TL (1<<0)
+#define TR (1<<1)
+#define BL (1<<2)
+#define BR (1<<3)
+
+/* Data for U+2500 - U+259F except dashes/diagonals */
+static const unsigned short boxdata[256] = {
+ /* light lines */
+ [0x00] = BDL + LH, /* light horizontal */
+ [0x02] = BDL + LV, /* light vertical */
+ [0x0c] = BDL + LD + LR, /* light down and right */
+ [0x10] = BDL + LD + LL, /* light down and left */
+ [0x14] = BDL + LU + LR, /* light up and right */
+ [0x18] = BDL + LU + LL, /* light up and left */
+ [0x1c] = BDL + LV + LR, /* light vertical and right */
+ [0x24] = BDL + LV + LL, /* light vertical and left */
+ [0x2c] = BDL + LH + LD, /* light horizontal and down */
+ [0x34] = BDL + LH + LU, /* light horizontal and up */
+ [0x3c] = BDL + LV + LH, /* light vertical and horizontal */
+ [0x74] = BDL + LL, /* light left */
+ [0x75] = BDL + LU, /* light up */
+ [0x76] = BDL + LR, /* light right */
+ [0x77] = BDL + LD, /* light down */
+
+ /* heavy [+light] lines */
+ [0x01] = BDL + HH,
+ [0x03] = BDL + HV,
+ [0x0d] = BDL + HR + LD,
+ [0x0e] = BDL + HD + LR,
+ [0x0f] = BDL + HD + HR,
+ [0x11] = BDL + HL + LD,
+ [0x12] = BDL + HD + LL,
+ [0x13] = BDL + HD + HL,
+ [0x15] = BDL + HR + LU,
+ [0x16] = BDL + HU + LR,
+ [0x17] = BDL + HU + HR,
+ [0x19] = BDL + HL + LU,
+ [0x1a] = BDL + HU + LL,
+ [0x1b] = BDL + HU + HL,
+ [0x1d] = BDL + HR + LV,
+ [0x1e] = BDL + HU + LD + LR,
+ [0x1f] = BDL + HD + LR + LU,
+ [0x20] = BDL + HV + LR,
+ [0x21] = BDL + HU + HR + LD,
+ [0x22] = BDL + HD + HR + LU,
+ [0x23] = BDL + HV + HR,
+ [0x25] = BDL + HL + LV,
+ [0x26] = BDL + HU + LD + LL,
+ [0x27] = BDL + HD + LU + LL,
+ [0x28] = BDL + HV + LL,
+ [0x29] = BDL + HU + HL + LD,
+ [0x2a] = BDL + HD + HL + LU,
+ [0x2b] = BDL + HV + HL,
+ [0x2d] = BDL + HL + LD + LR,
+ [0x2e] = BDL + HR + LL + LD,
+ [0x2f] = BDL + HH + LD,
+ [0x30] = BDL + HD + LH,
+ [0x31] = BDL + HD + HL + LR,
+ [0x32] = BDL + HR + HD + LL,
+ [0x33] = BDL + HH + HD,
+ [0x35] = BDL + HL + LU + LR,
+ [0x36] = BDL + HR + LU + LL,
+ [0x37] = BDL + HH + LU,
+ [0x38] = BDL + HU + LH,
+ [0x39] = BDL + HU + HL + LR,
+ [0x3a] = BDL + HU + HR + LL,
+ [0x3b] = BDL + HH + HU,
+ [0x3d] = BDL + HL + LV + LR,
+ [0x3e] = BDL + HR + LV + LL,
+ [0x3f] = BDL + HH + LV,
+ [0x40] = BDL + HU + LH + LD,
+ [0x41] = BDL + HD + LH + LU,
+ [0x42] = BDL + HV + LH,
+ [0x43] = BDL + HU + HL + LD + LR,
+ [0x44] = BDL + HU + HR + LD + LL,
+ [0x45] = BDL + HD + HL + LU + LR,
+ [0x46] = BDL + HD + HR + LU + LL,
+ [0x47] = BDL + HH + HU + LD,
+ [0x48] = BDL + HH + HD + LU,
+ [0x49] = BDL + HV + HL + LR,
+ [0x4a] = BDL + HV + HR + LL,
+ [0x4b] = BDL + HV + HH,
+ [0x78] = BDL + HL,
+ [0x79] = BDL + HU,
+ [0x7a] = BDL + HR,
+ [0x7b] = BDL + HD,
+ [0x7c] = BDL + HR + LL,
+ [0x7d] = BDL + HD + LU,
+ [0x7e] = BDL + HL + LR,
+ [0x7f] = BDL + HU + LD,
+
+ /* double [+light] lines */
+ [0x50] = BDL + DH,
+ [0x51] = BDL + DV,
+ [0x52] = BDL + DR + LD,
+ [0x53] = BDL + DD + LR,
+ [0x54] = BDL + DR + DD,
+ [0x55] = BDL + DL + LD,
+ [0x56] = BDL + DD + LL,
+ [0x57] = BDL + DL + DD,
+ [0x58] = BDL + DR + LU,
+ [0x59] = BDL + DU + LR,
+ [0x5a] = BDL + DU + DR,
+ [0x5b] = BDL + DL + LU,
+ [0x5c] = BDL + DU + LL,
+ [0x5d] = BDL + DL + DU,
+ [0x5e] = BDL + DR + LV,
+ [0x5f] = BDL + DV + LR,
+ [0x60] = BDL + DV + DR,
+ [0x61] = BDL + DL + LV,
+ [0x62] = BDL + DV + LL,
+ [0x63] = BDL + DV + DL,
+ [0x64] = BDL + DH + LD,
+ [0x65] = BDL + DD + LH,
+ [0x66] = BDL + DD + DH,
+ [0x67] = BDL + DH + LU,
+ [0x68] = BDL + DU + LH,
+ [0x69] = BDL + DH + DU,
+ [0x6a] = BDL + DH + LV,
+ [0x6b] = BDL + DV + LH,
+ [0x6c] = BDL + DH + DV,
+
+ /* (light) arcs */
+ [0x6d] = BDA + LD + LR,
+ [0x6e] = BDA + LD + LL,
+ [0x6f] = BDA + LU + LL,
+ [0x70] = BDA + LU + LR,
+
+ /* Lower (Down) X/8 block (data is 8 - X) */
+ [0x81] = BBD + 7, [0x82] = BBD + 6, [0x83] = BBD + 5, [0x84] = BBD + 4,
+ [0x85] = BBD + 3, [0x86] = BBD + 2, [0x87] = BBD + 1, [0x88] = BBD + 0,
+
+ /* Left X/8 block (data is X) */
+ [0x89] = BBL + 7, [0x8a] = BBL + 6, [0x8b] = BBL + 5, [0x8c] = BBL + 4,
+ [0x8d] = BBL + 3, [0x8e] = BBL + 2, [0x8f] = BBL + 1,
+
+ /* upper 1/2 (4/8), 1/8 block (X), right 1/2, 1/8 block (8-X) */
+ [0x80] = BBU + 4, [0x94] = BBU + 1,
+ [0x90] = BBR + 4, [0x95] = BBR + 7,
+
+ /* Quadrants */
+ [0x96] = BBQ + BL,
+ [0x97] = BBQ + BR,
+ [0x98] = BBQ + TL,
+ [0x99] = BBQ + TL + BL + BR,
+ [0x9a] = BBQ + TL + BR,
+ [0x9b] = BBQ + TL + TR + BL,
+ [0x9c] = BBQ + TL + TR + BR,
+ [0x9d] = BBQ + TR,
+ [0x9e] = BBQ + BL + TR,
+ [0x9f] = BBQ + BL + TR + BR,
+
+ /* Shades, data is an alpha value in 25% units (1/4, 1/2, 3/4) */
+ [0x91] = BBS + 1, [0x92] = BBS + 2, [0x93] = BBS + 3,
+
+ /* U+2504 - U+250B, U+254C - U+254F: unsupported (dashes) */
+ /* U+2571 - U+2573: unsupported (diagonals) */
+};
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..18a1507
--- /dev/null
+++ b/config.h
@@ -0,0 +1,558 @@
+/* See LICENSE file for copyright and license details. */
+
+/*
+ * appearance
+ *
+ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ */
+static char *font = "mono:pixelsize=12:antialias=true:autohint=true";
+static char *font2[] = { "NotoColorEmoji:pixelsize=10:antialias=true:autohint=true" };
+static int borderpx = 2;
+
+/*
+ * What program is execed by st depends of these precedence rules:
+ * 1: program passed with -e
+ * 2: scroll and/or utmp
+ * 3: SHELL environment variable
+ * 4: value of shell in /etc/passwd
+ * 5: value of shell in config.h
+ */
+static char *shell = "/bin/sh";
+char *utmp = NULL;
+/* scroll program: to enable use a string like "scroll" */
+char *scroll = NULL;
+char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
+
+/* identification sequence returned in DA and DECID */
+char *vtiden = "\033[?6c";
+
+/* Kerning / character bounding-box multipliers */
+static float cwscale = 1.0;
+static float chscale = 1.0;
+
+/*
+ * word delimiter string
+ *
+ * More advanced example: L" `'\"()[]{}"
+ */
+wchar_t *worddelimiters = L" ";
+
+/* selection timeouts (in milliseconds) */
+static unsigned int doubleclicktimeout = 300;
+static unsigned int tripleclicktimeout = 600;
+
+/* alt screens */
+int allowaltscreen = 1;
+
+/* allow certain non-interactive (insecure) window operations such as:
+ setting the clipboard text */
+int allowwindowops = 0;
+
+/*
+ * draw latency range in ms - from new content/keypress/etc until drawing.
+ * within this range, st draws when content stops arriving (idle). mostly it's
+ * near minlatency, but it waits longer for slow updates to avoid partial draw.
+ * low minlatency will tear/flicker more, as it can "detect" idle too early.
+ */
+static double minlatency = 8;
+static double maxlatency = 33;
+
+/*
+ * blinking timeout (set to 0 to disable blinking) for the terminal blinking
+ * attribute.
+ */
+static unsigned int blinktimeout = 800;
+
+/*
+ * thickness of underline and bar cursors
+ */
+static unsigned int cursorthickness = 2;
+
+/*
+ * 1: render most of the lines/blocks characters without using the font for
+ * perfect alignment between cells (U2500 - U259F except dashes/diagonals).
+ * Bold affects lines thickness if boxdraw_bold is not 0. Italic is ignored.
+ * 0: disable (render all U25XX glyphs normally from the font).
+ */
+const int boxdraw = 1;
+const int boxdraw_bold = 0;
+
+/* braille (U28XX): 1: render as adjacent "pixels", 0: use font */
+const int boxdraw_braille = 0;
+
+/*
+ * 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;
+float alphaOffset = 0.0;
+float alphaUnfocus;
+
+/* Terminal colors (16 first used in escape sequence) */
+static const char *colorname[] = {
+ "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */
+ "#cc241d",
+ "#98971a",
+ "#d79921",
+ "#458588",
+ "#b16286",
+ "#689d6a",
+ "#a89984",
+ "#928374",
+ "#fb4934",
+ "#b8bb26",
+ "#fabd2f",
+ "#83a598",
+ "#d3869b",
+ "#8ec07c",
+ "#ebdbb2",
+ [255] = 0,
+ /* more colors can be added after 255 to use with DefaultXX */
+ "#add8e6", /* 256 -> cursor */
+ "#555555", /* 257 -> rev cursor*/
+ "#282828", /* 258 -> bg */
+ "#ebdbb2", /* 259 -> fg */
+};
+
+
+/*
+ * Default colors (colorname index)
+ * foreground, background, cursor, reverse cursor
+ */
+unsigned int defaultfg = 259;
+unsigned int defaultbg = 258;
+unsigned int defaultcs = 256;
+unsigned int defaultrcs = 257;
+unsigned int background = 258;
+
+/*
+ * Default shape of cursor
+ * 2: Block ("█")
+ * 4: Underline ("_")
+ * 6: Bar ("|")
+ * 7: Snowman ("☃")
+ */
+static unsigned int cursorshape = 2;
+
+/*
+ * Default columns and rows numbers
+ */
+
+static unsigned int cols = 80;
+static unsigned int rows = 24;
+
+/*
+ * Default colour and shape of the mouse cursor
+ */
+static unsigned int mouseshape = XC_xterm;
+static unsigned int mousefg = 7;
+static unsigned int mousebg = 0;
+
+/*
+ * Color used to display font attributes when fontconfig selected a font which
+ * doesn't match the ones requested.
+ */
+static unsigned int defaultattr = 11;
+
+/*
+ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set).
+ * Note that if you want to use ShiftMask with selmasks, set this to an other
+ * modifier, set to 0 to not use it.
+ */
+static uint forcemousemod = ShiftMask;
+
+/*
+ * Xresources preferences to load at startup
+ */
+ResourcePref resources[] = {
+ { "font", STRING, &font },
+ { "fontalt0", STRING, &font2[0] },
+ { "color0", STRING, &colorname[0] },
+ { "color1", STRING, &colorname[1] },
+ { "color2", STRING, &colorname[2] },
+ { "color3", STRING, &colorname[3] },
+ { "color4", STRING, &colorname[4] },
+ { "color5", STRING, &colorname[5] },
+ { "color6", STRING, &colorname[6] },
+ { "color7", STRING, &colorname[7] },
+ { "color8", STRING, &colorname[8] },
+ { "color9", STRING, &colorname[9] },
+ { "color10", STRING, &colorname[10] },
+ { "color11", STRING, &colorname[11] },
+ { "color12", STRING, &colorname[12] },
+ { "color13", STRING, &colorname[13] },
+ { "color14", STRING, &colorname[14] },
+ { "color15", STRING, &colorname[15] },
+ { "background", STRING, &colorname[258] },
+ { "foreground", STRING, &colorname[259] },
+ { "cursorColor", STRING, &colorname[256] },
+ { "termname", STRING, &termname },
+ { "shell", STRING, &shell },
+ { "minlatency", INTEGER, &minlatency },
+ { "maxlatency", INTEGER, &maxlatency },
+ { "blinktimeout", INTEGER, &blinktimeout },
+ { "bellvolume", INTEGER, &bellvolume },
+ { "tabspaces", INTEGER, &tabspaces },
+ { "borderpx", INTEGER, &borderpx },
+ { "cwscale", FLOAT, &cwscale },
+ { "chscale", FLOAT, &chscale },
+ { "alpha", FLOAT, &alpha },
+ { "alphaOffset", FLOAT, &alphaOffset },
+};
+
+/*
+ * Internal mouse shortcuts.
+ * Beware that overloading Button1 will disable the selection.
+ */
+static MouseShortcut mshortcuts[] = {
+ /* mask button function argument release */
+ { XK_NO_MOD, Button4, kscrollup, {.i = 1} },
+ { XK_NO_MOD, 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 (Mod1Mask|ShiftMask)
+
+static char *openurlcmd[] = { "/bin/sh", "-c", "st-urlhandler -o", "externalpipe", NULL };
+static char *copyurlcmd[] = { "/bin/sh", "-c", "st-urlhandler -c", "externalpipe", NULL };
+static char *copyoutput[] = { "/bin/sh", "-c", "st-copyout", "externalpipe", NULL };
+
+
+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} },
+ { MODKEY, XK_c, clipcopy, {.i = 0} },
+ { ShiftMask, XK_Insert, clippaste, {.i = 0} },
+ { MODKEY, XK_v, clippaste, {.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} },
+ { MODKEY, XK_Page_Up, kscrollup, {.i = -1} },
+ { MODKEY, XK_Page_Down, kscrolldown, {.i = -1} },
+ { MODKEY, XK_k, kscrollup, {.i = 1} },
+ { MODKEY, XK_j, kscrolldown, {.i = 1} },
+ { MODKEY, XK_Up, kscrollup, {.i = 1} },
+ { MODKEY, XK_Down, kscrolldown, {.i = 1} },
+ { MODKEY, XK_u, kscrollup, {.i = -1} },
+ { MODKEY, XK_d, kscrolldown, {.i = -1} },
+ { MODKEY, XK_s, changealpha, {.f = -0.05} },
+ { MODKEY, XK_a, changealpha, {.f = +0.05} },
+ { TERMMOD, XK_Up, zoom, {.f = +1} },
+ { TERMMOD, XK_Down, zoom, {.f = -1} },
+ { TERMMOD, XK_K, zoom, {.f = +1} },
+ { TERMMOD, XK_J, zoom, {.f = -1} },
+ { TERMMOD, XK_U, zoom, {.f = +2} },
+ { TERMMOD, XK_D, zoom, {.f = -2} },
+ { MODKEY, XK_l, externalpipe, {.v = openurlcmd } },
+ { MODKEY, XK_y, externalpipe, {.v = copyurlcmd } },
+ { MODKEY, XK_o, externalpipe, {.v = copyoutput } },
+};
+
+/*
+ * 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 ddf278a..ef6de39 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
@@ -30,7 +32,6 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
#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..8000afa
--- /dev/null
+++ b/hb.c
@@ -0,0 +1,154 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <X11/Xft/Xft.h>
+#include <X11/cursorfont.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+#include "st.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 }
+
+/*
+ * Replace 0 with a list of font features, wrapped in FEATURE macro, e.g.
+ * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
+ *
+ * Uncomment either one of the 2 lines below. Uncomment the prior to disable (any) font features. Uncomment the
+ * latter to enable the (selected) font features.
+ */
+
+hb_feature_t features[] = { 0 };
+//hb_feature_t features[] = { FEATURE('s','s','0','1'), FEATURE('s','s','0','2'), FEATURE('s','s','0','3'), FEATURE('s','s','0','5'), FEATURE('s','s','0','6'), FEATURE('s','s','0','7'), FEATURE('s','s','0','8'), FEATURE('z','e','r','o') };
+
+void hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length);
+hb_font_t *hbfindfont(XftFont *match);
+
+typedef struct {
+ XftFont *match;
+ hb_font_t *font;
+} HbFontMatch;
+
+static int hbfontslen = 0;
+static HbFontMatch *hbfontcache = NULL;
+
+void
+hbunloadfonts()
+{
+ for (int i = 0; i < hbfontslen; i++) {
+ hb_font_destroy(hbfontcache[i].font);
+ XftUnlockFace(hbfontcache[i].match);
+ }
+
+ if (hbfontcache != NULL) {
+ free(hbfontcache);
+ hbfontcache = NULL;
+ }
+ hbfontslen = 0;
+}
+
+hb_font_t *
+hbfindfont(XftFont *match)
+{
+ for (int i = 0; i < hbfontslen; i++) {
+ if (hbfontcache[i].match == match)
+ return hbfontcache[i].font;
+ }
+
+ /* Font not found in cache, caching it now. */
+ hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 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[hbfontslen].match = match;
+ hbfontcache[hbfontslen].font = font;
+ hbfontslen += 1;
+
+ return font;
+}
+
+void
+hbtransform(XftGlyphFontSpec *specs, const Glyph *glyphs, size_t len, int x, int y)
+{
+ int start = 0, length = 1, gstart = 0;
+ hb_codepoint_t *codepoints = calloc((unsigned int)len, sizeof(hb_codepoint_t));
+
+ for (int idx = 1, specidx = 1; idx < len; idx++) {
+ if (glyphs[idx].mode & ATTR_WDUMMY) {
+ length += 1;
+ continue;
+ }
+
+ if (specs[specidx].font != specs[start].font || ATTRCMP(glyphs[gstart], glyphs[idx]) || selected(x + idx, y) != selected(x + gstart, y)) {
+ hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
+
+ /* Reset the sequence. */
+ length = 1;
+ start = specidx;
+ gstart = idx;
+ } else {
+ length += 1;
+ }
+
+ specidx++;
+ }
+
+ /* EOL. */
+ hbtransformsegment(specs[start].font, glyphs, codepoints, gstart, length);
+
+ /* Apply the transformation to glyph specs. */
+ for (int i = 0, specidx = 0; i < len; i++) {
+ if (glyphs[i].mode & ATTR_WDUMMY)
+ continue;
+ if (glyphs[i].mode & ATTR_BOXDRAW) {
+ specidx++;
+ continue;
+ }
+
+ if (codepoints[i] != specs[specidx].glyph)
+ ((Glyph *)glyphs)[i].mode |= ATTR_LIGA;
+
+ specs[specidx++].glyph = codepoints[i];
+ }
+
+ free(codepoints);
+}
+
+void
+hbtransformsegment(XftFont *xfont, const Glyph *string, hb_codepoint_t *codepoints, int start, int length)
+{
+ hb_font_t *font = hbfindfont(xfont);
+ if (font == NULL)
+ return;
+
+ Rune rune;
+ ushort mode = USHRT_MAX;
+ hb_buffer_t *buffer = hb_buffer_create();
+ hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
+
+ /* Fill buffer with codepoints. */
+ for (int i = start; i < (start+length); i++) {
+ rune = string[i].u;
+ mode = string[i].mode;
+ if (mode & ATTR_WDUMMY)
+ rune = 0x0020;
+ hb_buffer_add_codepoints(buffer, &rune, 1, 0, 1);
+ }
+
+ /* Shape the segment. */
+ hb_shape(font, buffer, features, sizeof(features));
+
+ /* Get new glyph info. */
+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL);
+
+ /* Write new codepoints. */
+ for (int i = 0; i < length; i++) {
+ hb_codepoint_t gid = info[i].codepoint;
+ codepoints[start+i] = gid;
+ }
+
+ /* Cleanup. */
+ hb_buffer_destroy(buffer);
+}
diff --git a/hb.h b/hb.h
new file mode 100644
index 0000000..b3e02d0
--- /dev/null
+++ b/hb.h
@@ -0,0 +1,7 @@
+#include <X11/Xft/Xft.h>
+#include <hb.h>
+#include <hb-ft.h>
+
+void hbunloadfonts();
+void hbtransform(XftGlyphFontSpec *, const Glyph *, size_t, int, int);
+
diff --git a/st-copyout b/st-copyout
new file mode 100755
index 0000000..0d19e5a
--- /dev/null
+++ b/st-copyout
@@ -0,0 +1,13 @@
+#!/bin/sh
+# Using external pipe with st, give a dmenu prompt of recent commands,
+# allowing the user to copy the output of one.
+# xclip required for this script.
+# By Jaywalker and Luke
+tmpfile=$(mktemp /tmp/st-cmd-output.XXXXXX)
+trap 'rm "$tmpfile"' 0 1 15
+sed -n "w $tmpfile"
+sed -i 's/\x0//g' "$tmpfile"
+ps1="$(grep "\S" "$tmpfile" | tail -n 1 | sed 's/^\s*//' | cut -d' ' -f1)"
+chosen="$(grep -F "$ps1" "$tmpfile" | sed '$ d' | tac | dmenu -p "Copy which command's output?" -i -l 10 | sed 's/[^^]/[&]/g; s/\^/\\^/g')"
+eps1="$(echo "$ps1" | sed 's/[^^]/[&]/g; s/\^/\\^/g')"
+awk "/^$chosen$/{p=1;print;next} p&&/$eps1/{p=0};p" "$tmpfile" | xclip -selection clipboard
diff --git a/st-urlhandler b/st-urlhandler
new file mode 100755
index 0000000..0eb4586
--- /dev/null
+++ b/st-urlhandler
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+urlregex="(((http|https|gopher|gemini|ftp|ftps|git)://|www\\.)[a-zA-Z0-9.]*[:;a-zA-Z0-9./+@$&%?$\#=_~-]*)|((magnet:\\?xt=urn:btih:)[a-zA-Z0-9]*)"
+
+urls="$(sed 's/.*│//g' | tr -d '\n' | # First remove linebreaks and mutt sidebars:
+ grep -aEo "$urlregex" | # grep only urls as defined above.
+ uniq | # Ignore neighboring duplicates.
+ sed "s/\(\.\|,\|;\|\!\\|\?\)$//;
+ s/^www./http:\/\/www\./")" # xdg-open will not detect url without http
+
+[ -z "$urls" ] && exit 1
+
+while getopts "hoc" o; do case "${o}" in
+ h) printf "Optional arguments for custom use:\\n -c: copy\\n -o: xdg-open\\n -h: Show this message\\n" && exit 1 ;;
+ o) chosen="$(echo "$urls" | dmenu -i -p 'Follow which url?' -l 10)"
+ setsid xdg-open "$chosen" >/dev/null 2>&1 & ;;
+ c) echo "$urls" | dmenu -i -p 'Copy which url?' -l 10 | tr -d '\n' | xclip -selection clipboard ;;
+ *) printf "Invalid option: -%s\\n" "$OPTARG" && exit 1 ;;
+esac done
diff --git a/st.1 b/st.1
index 39120b4..1728e24 100644
--- a/st.1
+++ b/st.1
@@ -125,6 +125,41 @@ and all the remaining arguments are used as a command
even without it.
.SH SHORTCUTS
.TP
+.B Alt-j/k or Alt-Up/Down or Alt-Mouse Wheel
+Scroll up/down one line at a time.
+.TP
+.B Alt-u/d or Alt-Page Up/Page Down
+Scroll up/down one screen at a time.
+.TP
+.B Alt-Shift-k/j or Alt-Shift-Page Up/Page Down or Alt-Shift-Mouse Wheel
+Increase or decrease font size.
+.TP
+.B Alt-Home
+Reset to default font size.
+.TP
+.B Shift-Insert or Alt-v
+Paste from clipboard.
+.TP
+.B Alt-c
+Copy to clipboard.
+.TP
+.B Alt-p
+Paste/input primary selection.
+.TP
+.B Alt-l
+Show dmenu menu of all URLs on screen and choose one to open.
+.TP
+.B Alt-y
+Show dmenu menu of all URLs on screen and choose one to copy.
+.TP
+.B Alt-o
+Show dmenu menu of all recently run commands and copy the output of the chosen command to the clipboard.
+.I xclip
+required.
+.TP
+.B Alt-a/s
+Increase or decrease opacity/alpha value (make window more or less transparent).
+.TP
.B Break
Send a break in the serial line.
Break key is obtained in PC keyboards
@@ -174,4 +209,3 @@ See the LICENSE file for the terms of redistribution.
.BR scroll (1)
.SH BUGS
See the TODO file in the distribution.
-
diff --git a/st.c b/st.c
index 6ba467d..78cb9b7 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,10 @@
#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])
+#define TLINE_HIST(y) ((y) <= HISTSIZE-term.row+2 ? term.hist[(y)] : term.line[(y-HISTSIZE+term.row-3)])
enum term_mode {
MODE_WRAP = 1 << 0,
@@ -113,8 +118,12 @@ typedef struct {
typedef struct {
int row; /* nb row */
int col; /* nb col */
+ int maxcol;
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 +194,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);
@@ -194,7 +203,6 @@ 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);
@@ -272,6 +280,8 @@ xrealloc(void *p, size_t len)
char *
xstrdup(const char *s)
{
+ if ((s = strdup(s)) == NULL)
+ die("strdup: %s\n", strerror(errno));
char *p;
if ((p = strdup(s)) == NULL)
@@ -409,10 +419,24 @@ 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 && TLINE(y)[i - 1].u == ' ')
+ --i;
+
+ return i;
+}
+
+int
+tlinehistlen(int y)
+{
+ int i = term.col;
+
+ if (TLINE_HIST(y)[i - 1].mode & ATTR_WRAP)
return i;
- while (i > 0 && term.line[y][i - 1].u == ' ')
+ while (i > 0 && TLINE_HIST(y)[i - 1].u == ' ')
--i;
return i;
@@ -464,6 +488,7 @@ selextend(int col, int row, int type, int done)
sel.mode = done ? SEL_IDLE : SEL_READY;
}
+
void
selnormalize(void)
{
@@ -521,7 +546,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 +561,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 +589,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 +627,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 +869,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 +1083,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 +1139,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 +1170,8 @@ tscrollup(int orig, int n)
term.line[i+n] = temp;
}
- selscroll(orig, -n);
+ if (term.scr == 0)
+ selscroll(orig, -n);
}
void
@@ -1120,7 +1200,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++;
}
@@ -1215,6 +1295,9 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
term.dirty[y] = 1;
term.line[y][x] = *attr;
term.line[y][x].u = u;
+
+ if (isboxdraw(u))
+ term.line[y][x].mode |= ATTR_BOXDRAW;
}
void
@@ -1228,8 +1311,8 @@ tclearregion(int x1, int y1, int x2, int y2)
if (y1 > y2)
temp = y1, y1 = y2, y2 = temp;
- LIMIT(x1, 0, term.col-1);
- LIMIT(x2, 0, term.col-1);
+ LIMIT(x1, 0, term.maxcol-1);
+ LIMIT(x2, 0, term.maxcol-1);
LIMIT(y1, 0, term.row-1);
LIMIT(y2, 0, term.row-1);
@@ -1285,14 +1368,14 @@ void
tinsertblankline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
- tscrolldown(term.c.y, n);
+ tscrolldown(term.c.y, n, 0);
}
void
tdeleteline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
- tscrollup(term.c.y, n);
+ tscrollup(term.c.y, n, 0);
}
int32_t
@@ -1729,11 +1812,11 @@ csihandle(void)
break;
case 'S': /* SU -- Scroll <n> line up */
DEFAULT(csiescseq.arg[0], 1);
- tscrollup(term.top, csiescseq.arg[0]);
+ tscrollup(term.top, csiescseq.arg[0], 0);
break;
case 'T': /* SD -- Scroll <n> 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 <n> blank lines */
DEFAULT(csiescseq.arg[0], 1);
@@ -1982,6 +2065,61 @@ strparse(void)
}
void
+externalpipe(const Arg *arg)
+{
+ int to[2];
+ char buf[UTF_SIZ];
+ void (*oldsigpipe)(int);
+ Glyph *bp, *end;
+ int lastpos, n, newline;
+
+ if (pipe(to) == -1)
+ return;
+
+ switch (fork()) {
+ case -1:
+ close(to[0]);
+ close(to[1]);
+ return;
+ case 0:
+ dup2(to[0], STDIN_FILENO);
+ close(to[0]);
+ close(to[1]);
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]);
+ perror("failed");
+ exit(0);
+ }
+
+ close(to[0]);
+ /* ignore sigpipe for now, in case child exists early */
+ oldsigpipe = signal(SIGPIPE, SIG_IGN);
+ newline = 0;
+ for (n = 0; n <= HISTSIZE + 2; n++) {
+ bp = TLINE_HIST(n);
+ lastpos = MIN(tlinehistlen(n) + 1, term.col) - 1;
+ if (lastpos < 0)
+ break;
+ if (lastpos == 0)
+ continue;
+ end = &bp[lastpos + 1];
+ for (; bp < end; ++bp)
+ if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
+ break;
+ if ((newline = TLINE_HIST(n)[lastpos].mode & ATTR_WRAP))
+ continue;
+ if (xwrite(to[1], "\n", 1) < 0)
+ break;
+ newline = 0;
+ }
+ if (newline)
+ (void)xwrite(to[1], "\n", 1);
+ close(to[1]);
+ /* restore */
+ signal(SIGPIPE, oldsigpipe);
+}
+
+void
strdump(void)
{
size_t i;
@@ -2296,7 +2434,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);
}
@@ -2309,7 +2447,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);
}
@@ -2523,12 +2661,19 @@ twrite(const char *buf, int buflen, int show_ctrl)
void
tresize(int col, int row)
{
- int i;
- int minrow = MIN(row, term.row);
- int mincol = MIN(col, term.col);
+ int i, j;
+ int tmp;
+ int minrow, mincol;
int *bp;
TCursor c;
+ tmp = col;
+ if (!term.maxcol)
+ term.maxcol = term.col;
+ col = MAX(col, term.maxcol);
+ minrow = MIN(row, term.row);
+ mincol = MIN(col, term.maxcol);
+
if (col < 1 || row < 1) {
fprintf(stderr,
"tresize: error resizing to %dx%d\n", col, row);
@@ -2560,6 +2705,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));
@@ -2571,17 +2724,18 @@ tresize(int col, int row)
term.line[i] = xmalloc(col * sizeof(Glyph));
term.alt[i] = xmalloc(col * sizeof(Glyph));
}
- if (col > term.col) {
- bp = term.tabs + term.col;
+ if (col > term.maxcol) {
+ bp = term.tabs + term.maxcol;
- memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
+ memset(bp, 0, sizeof(*term.tabs) * (col - term.maxcol));
while (--bp > term.tabs && !*bp)
/* nothing */ ;
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
*bp = 1;
}
/* update terminal size */
- term.col = col;
+ term.col = tmp;
+ term.maxcol = col;
term.row = row;
/* reset scrolling region */
tsetscroll(0, row-1);
@@ -2618,7 +2772,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);
}
}
@@ -2639,8 +2793,13 @@ 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);
+ /* 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();
@@ -2654,3 +2813,4 @@ redraw(void)
tfulldirt();
draw();
}
+
diff --git a/st.h b/st.h
index fd3b0d8..60d973a 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) & (~ATTR_LIGA)) != ((b).mode & (~ATTR_WRAP) & (~ATTR_LIGA)) || \
+ (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)
@@ -33,6 +34,8 @@ enum glyph_attribute {
ATTR_WRAP = 1 << 8,
ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10,
+ ATTR_BOXDRAW = 1 << 11,
+ ATTR_LIGA = 1 << 12,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
};
@@ -79,8 +82,13 @@ typedef union {
void die(const char *, ...);
void redraw(void);
+void tfulldirt(void);
void draw(void);
+void externalpipe(const Arg *);
+void kscrolldown(const Arg *);
+void kscrollup(const Arg *);
+
void printscreen(const Arg *);
void printsel(const Arg *);
void sendbreak(const Arg *);
@@ -111,6 +119,14 @@ void *xmalloc(size_t);
void *xrealloc(void *, size_t);
char *xstrdup(const char *);
+int isboxdraw(Rune);
+ushort boxdrawindex(const Glyph *);
+#ifdef XFT_VERSION
+/* only exposed to x.c, otherwise we'll need Xft.h for the types */
+void boxdraw_xinit(Display *, Colormap, XftDraw *, Visual *);
+void drawboxes(int, int, int, int, XftColor *, XftColor *, const XftGlyphFontSpec *, int);
+#endif
+
/* config.h globals */
extern char *utmp;
extern char *scroll;
@@ -123,4 +139,8 @@ extern char *termname;
extern unsigned int tabspaces;
extern unsigned int defaultfg;
extern unsigned int defaultbg;
+extern float alpha;
+extern float alphaUnfocus;
+extern const int boxdraw, boxdraw_bold, boxdraw_braille;
extern unsigned int defaultcs;
+
diff --git a/win.h b/win.h
index 6de960d..8839e31 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);
@@ -39,3 +39,4 @@ void xsetpointermotion(int);
void xsetsel(char *);
int xstartdraw(void);
void xximspot(int, int);
+
diff --git a/x.c b/x.c
index 2a3bd38..017238f 100644
--- a/x.c
+++ b/x.c
@@ -14,11 +14,13 @@
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <X11/XKBlib.h>
+#include <X11/Xresource.h>
char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
+#include "hb.h"
/* types used in config.h */
typedef struct {
@@ -45,6 +47,19 @@ typedef struct {
signed char appcursor; /* application cursor */
} Key;
+/* Xresources preferences */
+enum resource_type {
+ STRING = 0,
+ INTEGER = 1,
+ FLOAT = 2
+};
+
+typedef struct {
+ char *name;
+ enum resource_type type;
+ void *dst;
+} ResourcePref;
+
/* X modifiers */
#define XK_ANY_MOD UINT_MAX
#define XK_NO_MOD 0
@@ -55,6 +70,7 @@ static void clipcopy(const Arg *);
static void clippaste(const Arg *);
static void numlock(const Arg *);
static void selpaste(const Arg *);
+static void changealpha(const Arg *);
static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
@@ -105,6 +121,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;
@@ -156,6 +173,8 @@ static void xresize(int, int);
static void xhints(void);
static int xloadcolor(int, const char *, Color *);
static int xloadfont(Font *, FcPattern *);
+static int xloadsparefont(FcPattern *, int);
+static void xloadsparefonts(void);
static void xloadfonts(const char *, double);
static void xunloadfont(Font *);
static void xunloadfonts(void);
@@ -163,6 +182,7 @@ static void xsetenv(void);
static void xseturgency(int);
static int evcol(XEvent *);
static int evrow(XEvent *);
+static float clamp(float, float, float);
static void expose(XEvent *);
static void visibility(XEvent *);
@@ -243,6 +263,7 @@ static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
+static char *opt_alpha = NULL;
static char *opt_class = NULL;
static char **opt_cmd = NULL;
static char *opt_embed = NULL;
@@ -252,6 +273,9 @@ static char *opt_line = NULL;
static char *opt_name = NULL;
static char *opt_title = NULL;
+static int focused = 0;
+
+static int oldbutton = 3; /* button event on startup: 3 = release */
static uint buttons; /* bit field of pressed buttons */
void
@@ -293,6 +317,18 @@ numlock(const Arg *dummy)
}
void
+changealpha(const Arg *arg)
+{
+ if((alpha > 0 && arg->f < 0) || (alpha < 1 && arg->f > 0))
+ alpha += arg->f;
+ alpha = clamp(alpha, 0.0, 1.0);
+ alphaUnfocus = clamp(alpha-alphaOffset, 0.0, 1.0);
+
+ xloadcols();
+ redraw();
+}
+
+void
zoom(const Arg *arg)
{
Arg larg;
@@ -306,6 +342,7 @@ zoomabs(const Arg *arg)
{
xunloadfonts();
xloadfonts(usedfont, arg->f);
+ xloadsparefonts();
cresize(0, 0);
redraw();
xhints();
@@ -344,6 +381,15 @@ evrow(XEvent *e)
return y / win.ch;
}
+float
+clamp(float value, float lower, float upper) {
+ if(value < lower)
+ return lower;
+ if(value > upper)
+ return upper;
+ return value;
+}
+
void
mousesel(XEvent *e, int done)
{
@@ -364,59 +410,51 @@ mousesel(XEvent *e, int done)
void
mousereport(XEvent *e)
{
- int len, btn, code;
- int x = evcol(e), y = evrow(e);
- int state = e->xbutton.state;
+ int code;
+ int len, x = evcol(e), y = evrow(e),
+ button = e->xbutton.button, state = e->xbutton.state;
char buf[40];
static int ox, oy;
- if (e->type == MotionNotify) {
+ /* from urxvt */
+ if (e->xbutton.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)
+ /* MOUSE_MOTION: no reporting if no button is pressed */
+ if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
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;
+
+ button = oldbutton + 32;
+ ox = x;
+ oy = y;
} else {
- btn = e->xbutton.button;
- /* Only buttons 1 through 11 can be encoded */
- if (btn < 1 || btn > 11)
- return;
- if (e->type == ButtonRelease) {
+ if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {
+ button = 3;
+ } else {
+ button -= Button1;
+ if (button >= 3)
+ button += 64 - 3;
+ }
+ if (e->xbutton.type == ButtonPress) {
+ oldbutton = button;
+ ox = x;
+ oy = y;
+ } else if (e->xbutton.type == ButtonRelease) {
+ oldbutton = 3;
/* MODE_MOUSEX10: no button release reporting */
if (IS_SET(MODE_MOUSEX10))
return;
- /* Don't send release events for the scroll wheel */
- if (btn == 4 || btn == 5)
+ if (button == 64 || button == 65)
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);
+ button += ((state & ShiftMask ) ? 4 : 0)
+ + ((state & Mod4Mask ) ? 8 : 0)
+ + ((state & ControlMask) ? 16 : 0);
}
if (IS_SET(MODE_MOUSESGR)) {
@@ -752,7 +790,7 @@ xresize(int col, int row)
XFreePixmap(xw.dpy, xw.buf);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
- DefaultDepth(xw.dpy, xw.scr));
+ xw.depth);
XftDrawChange(xw.draw, xw.buf);
xclear(0, 0, win.w, win.h);
@@ -791,27 +829,39 @@ xloadcolor(int i, const char *name, Color *ncolor)
}
void
+xloadalpha(void)
+{
+ float const usedAlpha = focused ? alpha : alphaUnfocus;
+ if (opt_alpha) alpha = strtof(opt_alpha, NULL);
+ dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * usedAlpha);
+ dc.col[defaultbg].pixel &= 0x00FFFFFF;
+ dc.col[defaultbg].pixel |= (unsigned char)(0xff * usedAlpha) << 24;
+}
+
+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);
+ if (!loaded) {
+ dc.collen = 1 + (defaultbg = MAX(LEN(colorname), 256));
dc.col = xmalloc(dc.collen * sizeof(Color));
}
- for (i = 0; i < dc.collen; i++)
+ for (i = 0; i+1 < 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);
}
+
+ if (dc.collen) // cannot die, as the color is already loaded.
+ xloadcolor(background, NULL, &dc.col[defaultbg]);
+
+ xloadalpha();
loaded = 1;
}
@@ -859,8 +909,8 @@ xclear(int x1, int y1, int x2, int y2)
void
xhints(void)
{
- XClassHint class = {opt_name ? opt_name : termname,
- opt_class ? opt_class : termname};
+ XClassHint class = {opt_name ? opt_name : "st",
+ opt_class ? opt_class : "St"};
XWMHints wm = {.flags = InputHint, .input = 1};
XSizeHints *sizeh;
@@ -1050,6 +1100,101 @@ xloadfonts(const char *fontstr, double fontsize)
FcPatternDestroy(pattern);
}
+int
+xloadsparefont(FcPattern *pattern, int flags)
+{
+ FcPattern *match;
+ FcResult result;
+
+ match = FcFontMatch(NULL, pattern, &result);
+ if (!match) {
+ return 1;
+ }
+
+ if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) {
+ FcPatternDestroy(match);
+ return 1;
+ }
+
+ frc[frclen].flags = flags;
+ /* Believe U+0000 glyph will present in each default font */
+ frc[frclen].unicodep = 0;
+ frclen++;
+
+ return 0;
+}
+
+void
+xloadsparefonts(void)
+{
+ FcPattern *pattern;
+ double sizeshift, fontval;
+ int fc;
+ char **fp;
+
+ if (frclen != 0)
+ die("can't embed spare fonts. cache isn't empty");
+
+ /* Calculate count of spare fonts */
+ fc = sizeof(font2) / sizeof(*font2);
+ if (fc == 0)
+ return;
+
+ /* Allocate memory for cache entries. */
+ if (frccap < 4 * fc) {
+ frccap += 4 * fc - frccap;
+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ }
+
+ for (fp = font2; fp - font2 < fc; ++fp) {
+
+ if (**fp == '-')
+ pattern = XftXlfdParse(*fp, False, False);
+ else
+ pattern = FcNameParse((FcChar8 *)*fp);
+
+ if (!pattern)
+ die("can't open spare font %s\n", *fp);
+
+ if (defaultfontsize > 0) {
+ sizeshift = usedfontsize - defaultfontsize;
+ if (sizeshift != 0 &&
+ FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
+ FcResultMatch) {
+ fontval += sizeshift;
+ FcPatternDel(pattern, FC_PIXEL_SIZE);
+ FcPatternDel(pattern, FC_SIZE);
+ FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval);
+ }
+ }
+
+ FcPatternAddBool(pattern, FC_SCALABLE, 1);
+
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
+ XftDefaultSubstitute(xw.dpy, xw.scr, pattern);
+
+ if (xloadsparefont(pattern, FRC_NORMAL))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
+ if (xloadsparefont(pattern, FRC_ITALIC))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDel(pattern, FC_WEIGHT);
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
+ if (xloadsparefont(pattern, FRC_ITALICBOLD))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
+ if (xloadsparefont(pattern, FRC_BOLD))
+ die("can't open spare font %s\n", *fp);
+
+ FcPatternDestroy(pattern);
+ }
+}
+
void
xunloadfont(Font *f)
{
@@ -1062,6 +1207,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 +1282,21 @@ xinit(int cols, int rows)
Window parent;
pid_t thispid = getpid();
XColor xmousefg, xmousebg;
+ XWindowAttributes attr;
+ XVisualInfo vis;
- if (!(xw.dpy = XOpenDisplay(NULL)))
- die("can't open display\n");
xw.scr = XDefaultScreen(xw.dpy);
- xw.vis = XDefaultVisual(xw.dpy, xw.scr);
+
+ if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) {
+ parent = XRootWindow(xw.dpy, xw.scr);
+ xw.depth = 32;
+ } else {
+ XGetWindowAttributes(xw.dpy, parent, &attr);
+ xw.depth = attr.depth;
+ }
+
+ XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis);
+ xw.vis = vis.visual;
/* font */
if (!FcInit())
@@ -1147,8 +1305,11 @@ xinit(int cols, int rows)
usedfont = (opt_font == NULL)? font : opt_font;
xloadfonts(usedfont, 0);
+ /* spare fonts */
+ xloadsparefonts();
+
/* colors */
- xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
+ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
xloadcols();
/* adjust fixed window geometry */
@@ -1168,19 +1329,15 @@ xinit(int cols, int rows)
| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
xw.attrs.colormap = xw.cmap;
- if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
- parent = XRootWindow(xw.dpy, xw.scr);
xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
- win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
+ win.w, win.h, 0, xw.depth, InputOutput,
xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
| CWEventMask | CWColormap, &xw.attrs);
memset(&gcvalues, 0, sizeof(gcvalues));
gcvalues.graphics_exposures = False;
- dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
- &gcvalues);
- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
- DefaultDepth(xw.dpy, xw.scr));
+ xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
+ dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues);
XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
@@ -1237,6 +1394,8 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
+
+ boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
}
int
@@ -1261,7 +1420,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
mode = glyphs[i].mode;
/* Skip dummy wide-character spacing. */
- if (mode == ATTR_WDUMMY)
+ if (mode & ATTR_WDUMMY)
continue;
/* Determine font for glyph if different from previous glyph. */
@@ -1283,8 +1442,13 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
yp = winy + font->ascent;
}
- /* Lookup character index with default font. */
- glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+ if (mode & ATTR_BOXDRAW) {
+ /* minor shoehorning: boxdraw uses only this ushort */
+ glyphidx = boxdrawindex(&glyphs[i]);
+ } else {
+ /* Lookup character index with default font. */
+ glyphidx = XftCharIndex(xw.dpy, font->match, rune);
+ }
if (glyphidx) {
specs[numspecs].font = font->match;
specs[numspecs].glyph = glyphidx;
@@ -1368,6 +1532,9 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
numspecs++;
}
+ /* Harfbuzz transformation for ligatures. */
+ hbtransform(specs, glyphs, len, x, y);
+
return numspecs;
}
@@ -1488,13 +1655,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
r.width = width;
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
- /* Render the glyphs. */
- XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
+ if (base.mode & ATTR_BOXDRAW) {
+ drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
+ } else {
+ /* 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);
+ XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
+ width, 1);
}
if (base.mode & ATTR_STRUCK) {
@@ -1517,14 +1688,17 @@ xdrawglyph(Glyph g, int x, int y)
}
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;
@@ -1532,7 +1706,7 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
/*
* Select the right color for the right mode.
*/
- g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;
+ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
if (IS_SET(MODE_REVERSE)) {
g.mode |= ATTR_REVERSE;
@@ -1777,12 +1951,22 @@ focus(XEvent *ev)
xseturgency(0);
if (IS_SET(MODE_FOCUS))
ttywrite("\033[I", 3, 0);
+ if (!focused) {
+ focused = 1;
+ xloadcols();
+ tfulldirt();
+ }
} else {
if (xw.ime.xic)
XUnsetICFocus(xw.ime.xic);
win.mode &= ~MODE_FOCUSED;
if (IS_SET(MODE_FOCUS))
ttywrite("\033[O", 3, 0);
+ if (focused) {
+ focused = 0;
+ xloadcols();
+ tfulldirt();
+ }
}
}
@@ -2011,6 +2195,59 @@ run(void)
}
}
+int
+resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
+{
+ char **sdst = dst;
+ int *idst = dst;
+ float *fdst = dst;
+
+ char fullname[256];
+ char fullclass[256];
+ char *type;
+ XrmValue ret;
+
+ snprintf(fullname, sizeof(fullname), "%s.%s",
+ opt_name ? opt_name : "st", name);
+ snprintf(fullclass, sizeof(fullclass), "%s.%s",
+ opt_class ? opt_class : "St", name);
+ fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
+
+ XrmGetResource(db, fullname, fullclass, &type, &ret);
+ if (ret.addr == NULL || strncmp("String", type, 64))
+ return 1;
+
+ switch (rtype) {
+ case STRING:
+ *sdst = ret.addr;
+ break;
+ case INTEGER:
+ *idst = strtoul(ret.addr, NULL, 10);
+ break;
+ case FLOAT:
+ *fdst = strtof(ret.addr, NULL);
+ break;
+ }
+ return 0;
+}
+
+void
+config_init(void)
+{
+ char *resm;
+ XrmDatabase db;
+ ResourcePref *p;
+
+ XrmInitialize();
+ resm = XResourceManagerString(xw.dpy);
+ if (!resm)
+ return;
+
+ db = XrmGetStringDatabase(resm);
+ for (p = resources; p < resources + LEN(resources); p++)
+ resource_load(db, p->name, p->type, p->dst);
+}
+
void
usage(void)
{
@@ -2035,6 +2272,9 @@ main(int argc, char *argv[])
case 'a':
allowaltscreen = 0;
break;
+ case 'A':
+ opt_alpha = EARGF(usage());
+ break;
case 'c':
opt_class = EARGF(usage());
break;
@@ -2084,8 +2324,15 @@ run:
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
+
+ if(!(xw.dpy = XOpenDisplay(NULL)))
+ die("Can't open display\n");
+
+ config_init();
cols = MAX(cols, 1);
rows = MAX(rows, 1);
+ defaultbg = MAX(LEN(colorname), 256);
+ alphaUnfocus = alpha-alphaOffset;
tnew(cols, rows);
xinit(cols, rows);
xsetenv();
@@ -2094,3 +2341,4 @@ run:
return 0;
}
+