From 2377e085237dca45c5f0a08bea16074b40076e1d Mon Sep 17 00:00:00 2001 From: cptbbVM Date: Mon, 11 Dec 2023 20:36:00 +0100 Subject: [PATCH] mv st std dwm dwmd --- dwm | 1 - dwmd/LICENSE | 38 + dwmd/Makefile | 45 + dwmd/README | 48 + dwmd/config.def.h | 120 ++ dwmd/config.h | 120 ++ dwmd/config.mk | 39 + dwmd/drw.c | 450 ++++++ dwmd/drw.h | 58 + dwmd/drw.o | Bin 0 -> 11072 bytes dwmd/dwm | Bin 0 -> 67392 bytes dwmd/dwm.1 | 176 +++ dwmd/dwm.c | 2178 +++++++++++++++++++++++++++++ dwmd/dwm.o | Bin 0 -> 58104 bytes dwmd/dwm.png | Bin 0 -> 373 bytes dwmd/patches/azerty.diff | 43 + dwmd/patches/fullgaps.diff | 94 ++ dwmd/q | 116 ++ dwmd/transient.c | 42 + dwmd/util.c | 36 + dwmd/util.h | 8 + dwmd/util.o | Bin 0 -> 2224 bytes st | 1 - std/FAQ | 253 ++++ std/LEGACY | 17 + std/LICENSE | 34 + std/Makefile | 51 + std/README | 34 + std/TODO | 28 + std/arg.h | 50 + std/config.def.h | 474 +++++++ std/config.h | 474 +++++++ std/config.mk | 36 + std/st | Bin 0 -> 105544 bytes std/st.1 | 177 +++ std/st.c | 2674 ++++++++++++++++++++++++++++++++++++ std/st.h | 126 ++ std/st.info | 243 ++++ std/st.o | Bin 0 -> 78304 bytes std/win.h | 41 + std/x.c | 2099 ++++++++++++++++++++++++++++ std/x.o | Bin 0 -> 74800 bytes 42 files changed, 10422 insertions(+), 2 deletions(-) delete mode 160000 dwm create mode 100644 dwmd/LICENSE create mode 100644 dwmd/Makefile create mode 100644 dwmd/README create mode 100644 dwmd/config.def.h create mode 100644 dwmd/config.h create mode 100644 dwmd/config.mk create mode 100644 dwmd/drw.c create mode 100644 dwmd/drw.h create mode 100644 dwmd/drw.o create mode 100755 dwmd/dwm create mode 100644 dwmd/dwm.1 create mode 100644 dwmd/dwm.c create mode 100644 dwmd/dwm.o create mode 100644 dwmd/dwm.png create mode 100644 dwmd/patches/azerty.diff create mode 100644 dwmd/patches/fullgaps.diff create mode 100644 dwmd/q create mode 100644 dwmd/transient.c create mode 100644 dwmd/util.c create mode 100644 dwmd/util.h create mode 100644 dwmd/util.o delete mode 160000 st create mode 100644 std/FAQ create mode 100644 std/LEGACY create mode 100644 std/LICENSE create mode 100644 std/Makefile create mode 100644 std/README create mode 100644 std/TODO create mode 100644 std/arg.h create mode 100644 std/config.def.h create mode 100644 std/config.h create mode 100644 std/config.mk create mode 100755 std/st create mode 100644 std/st.1 create mode 100644 std/st.c create mode 100644 std/st.h create mode 100644 std/st.info create mode 100644 std/st.o create mode 100644 std/win.h create mode 100644 std/x.c create mode 100644 std/x.o diff --git a/dwm b/dwm deleted file mode 160000 index 9f88553..0000000 --- a/dwm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9f8855343c881bdc01b9fff5b956537ba1106b76 diff --git a/dwmd/LICENSE b/dwmd/LICENSE new file mode 100644 index 0000000..995172f --- /dev/null +++ b/dwmd/LICENSE @@ -0,0 +1,38 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich +© 2020-2022 Chris Down + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dwmd/Makefile b/dwmd/Makefile new file mode 100644 index 0000000..ffa69b4 --- /dev/null +++ b/dwmd/Makefile @@ -0,0 +1,45 @@ +# dwm - dynamic window manager +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dwm.c util.c +OBJ = ${SRC:.c=.o} + +all: dwm + +.c.o: + ${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + cp config.def.h $@ + +dwm: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz + +dist: clean + mkdir -p dwm-${VERSION} + cp -R LICENSE Makefile README config.def.h config.mk\ + dwm.1 drw.h util.h ${SRC} dwm.png transient.c dwm-${VERSION} + tar -cf dwm-${VERSION}.tar dwm-${VERSION} + gzip dwm-${VERSION}.tar + rm -rf dwm-${VERSION} + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f dwm ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwm\ + ${DESTDIR}${MANPREFIX}/man1/dwm.1 + +.PHONY: all clean dist install uninstall diff --git a/dwmd/README b/dwmd/README new file mode 100644 index 0000000..95d4fd0 --- /dev/null +++ b/dwmd/README @@ -0,0 +1,48 @@ +dwm - dynamic window manager +============================ +dwm is an extremely fast, small, and dynamic window manager for X. + + +Requirements +------------ +In order to build dwm you need the Xlib header files. + + +Installation +------------ +Edit config.mk to match your local setup (dwm is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dwm (if +necessary as root): + + make clean install + + +Running dwm +----------- +Add the following line to your .xinitrc to start dwm using startx: + + exec dwm + +In order to connect dwm to a specific display, make sure that +the DISPLAY environment variable is set correctly, e.g.: + + DISPLAY=foo.bar:1 exec dwm + +(This will start dwm on display :1 of the host foo.bar.) + +In order to display status info in the bar, you can do something +like this in your .xinitrc: + + while xsetroot -name "`date` `uptime | sed 's/.*,//'`" + do + sleep 1 + done & + exec dwm + + +Configuration +------------- +The configuration of dwm is done by creating a custom config.h +and (re)compiling the source code. diff --git a/dwmd/config.def.h b/dwmd/config.def.h new file mode 100644 index 0000000..564de19 --- /dev/null +++ b/dwmd/config.def.h @@ -0,0 +1,120 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const unsigned int borderpx = 3; /* border pixel of windows */ +static const unsigned int gappx = 10; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "monospace:size=12" }; +static const char dmenufont[] = "monospace:size=12"; +static const char col_gray1[] = "#119822"; +static const char col_gray2[] = "#2A7221"; +static const char col_gray3[] = "#1E441E"; +static const char col_gray4[] = "#152614"; +static const char col_cyan[] = "#31CB00"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY, XK_s, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_minus, setgaps, {.i = -5 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = +5 } }, + { MODKEY, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwmd/config.h b/dwmd/config.h new file mode 100644 index 0000000..1216706 --- /dev/null +++ b/dwmd/config.h @@ -0,0 +1,120 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const unsigned int borderpx = 3; /* border pixel of windows */ +static const unsigned int gappx = 10; /* gaps between windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "monospace:size=12" }; +static const char dmenufont[] = "monospace:size=12"; +static const char col_gray1[] = "#119822"; +static const char col_gray2[] = "#2A7221"; +static const char col_gray3[] = "#1E441E"; +static const char col_gray4[] = "#152614"; +static const char col_cyan[] = "#31CB00"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY, XK_s, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_agrave, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_agrave, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_semicolon, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_semicolon, tagmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_minus, setgaps, {.i = -5 } }, + { MODKEY|ShiftMask, XK_equal, setgaps, {.i = +5 } }, + { MODKEY, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_ampersand, 0) + TAGKEYS( XK_eacute, 1) + TAGKEYS( XK_quotedbl, 2) + TAGKEYS( XK_apostrophe, 3) + TAGKEYS( XK_parenleft, 4) + TAGKEYS( XK_minus, 5) + TAGKEYS( XK_egrave, 6) + TAGKEYS( XK_underscore, 7) + TAGKEYS( XK_ccedilla, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwmd/config.mk b/dwmd/config.mk new file mode 100644 index 0000000..ba64d3d --- /dev/null +++ b/dwmd/config.mk @@ -0,0 +1,39 @@ +# dwm version +VERSION = 6.4 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = ${X11INC}/freetype2 +#MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = -I${X11INC} -I${FREETYPEINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} +LDFLAGS = ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/dwmd/drw.c b/dwmd/drw.c new file mode 100644 index 0000000..a58a2b4 --- /dev/null +++ b/dwmd/drw.c @@ -0,0 +1,450 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + int i, ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/dwmd/drw.h b/dwmd/drw.h new file mode 100644 index 0000000..6471431 --- /dev/null +++ b/dwmd/drw.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/dwmd/drw.o b/dwmd/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..eb6ee1a9c9f5e11a38ee2dc8ca0d4fbe2252e873 GIT binary patch literal 11072 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 A8~^|S literal 0 HcmV?d00001 diff --git a/dwmd/dwm b/dwmd/dwm new file mode 100755 index 0000000000000000000000000000000000000000..c52410c589ad066cee14e614965d1f4e205314bd GIT binary patch literal 67392 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << LENGTH(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + Client *next; + Client *snext; + Monitor *mon; + Window win; +}; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + const char *symbol; + void (*arrange)(Monitor *); +} Layout; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + const Layout *lt[2]; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int monitor; +} Rule; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void movemouse(const Arg *arg); +static Client *nexttiled(Client *c); +static void pop(Client *c); +static void propertynotify(XEvent *e); +static void quit(const Arg *arg); +static Monitor *recttomon(int x, int y, int w, int h); +static void resize(Client *c, int x, int y, int w, int h, int interact); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void restack(Monitor *m); +static void run(void); +static void scan(void); +static int sendevent(Client *c, Atom proto); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void setgaps(const Arg *arg); +static void setlayout(const Arg *arg); +static void setmfact(const Arg *arg); +static void setup(void); +static void seturgent(Client *c, int urg); +static void showhide(Client *c); +static void spawn(const Arg *arg); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +static void tile(Monitor *m); +static void togglebar(const Arg *arg); +static void togglefloating(const Arg *arg); +static void toggletag(const Arg *arg); +static void toggleview(const Arg *arg); +static void unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +/* variables */ +static const char broken[] = "broken"; +static char stext[256]; +static int screen; +static int sw, sh; /* X display screen geometry width, height */ +static int bh; /* bar height */ +static int lrpad; /* sum of left and right padding for text */ +static int (*xerrorxlib)(Display *, XErrorEvent *); +static unsigned int numlockmask = 0; +static void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureRequest] = configurerequest, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MappingNotify] = mappingnotify, + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast]; +static int running = 1; +static Cur *cursor[CurLast]; +static Clr **scheme; +static Display *dpy; +static Drw *drw; +static Monitor *mons, *selmon; +static Window root, wmcheckwin; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +/* compile-time check if all tags fit into an unsigned int bit array. */ +struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +applyrules(Client *c) +{ + const char *class, *instance; + unsigned int i; + const Rule *r; + Monitor *m; + XClassHint ch = { NULL, NULL }; + + /* rule matching */ + c->isfloating = 0; + c->tags = 0; + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < LENGTH(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next); + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + if (!c->hintsvalid) + updatesizehints(c); + /* see last two sentences in ICCCM 4.1.2.3 */ + baseismin = c->basew == c->minw && c->baseh == c->minh; + if (!baseismin) { /* temporarily remove base dimensions */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for aspect limits */ + if (c->mina > 0 && c->maxa > 0) { + if (c->maxa < (float)*w / *h) + *w = *h * c->maxa + 0.5; + else if (c->mina < (float)*h / *w) + *h = *w * c->mina + 0.5; + } + if (baseismin) { /* increment calculation requires this */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for increment value */ + if (c->incw) + *w -= *w % c->incw; + if (c->inch) + *h -= *h % c->inch; + /* restore base dimensions */ + *w = MAX(*w + c->basew, c->minw); + *h = MAX(*h + c->baseh, c->minh); + if (c->maxw) + *w = MIN(*w, c->maxw); + if (c->maxh) + *h = MIN(*h, c->maxh); + } + return *x != c->x || *y != c->y || *w != c->w || *h != c->h; +} + +void +arrange(Monitor *m) +{ + if (m) + showhide(m->stack); + else for (m = mons; m; m = m->next) + showhide(m->stack); + if (m) { + arrangemon(m); + restack(m); + } else for (m = mons; m; m = m->next) + arrangemon(m); +} + +void +arrangemon(Monitor *m) +{ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); +} + +void +attach(Client *c) +{ + c->next = c->mon->clients; + c->mon->clients = c; +} + +void +attachstack(Client *c) +{ + c->snext = c->mon->stack; + c->mon->stack = c; +} + +void +buttonpress(XEvent *e) +{ + unsigned int i, x, click; + Arg arg = {0}; + Client *c; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + if (ev->window == selmon->barwin) { + i = x = 0; + do + x += TEXTW(tags[i]); + while (ev->x >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (ev->x < x + TEXTW(selmon->ltsymbol)) + click = ClkLtSymbol; + else if (ev->x > selmon->ww - (int)TEXTW(stext)) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors); i++) + free(scheme[i]); + free(scheme); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); +} + +void +cleanupmon(Monitor *mon) +{ + Monitor *m; + + if (mon == mons) + mons = mons->next; + else { + for (m = mons; m && m->next != mon; m = m->next); + m->next = mon->next; + } + XUnmapWindow(dpy, mon->barwin); + XDestroyWindow(dpy, mon->barwin); + free(mon); +} + +void +clientmessage(XEvent *e) +{ + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drw_setscheme(drw, scheme[SchemeNorm]); + tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ + drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + } + + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) + drawbar(m); +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j, k; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + int start, end, skip; + KeySym *syms; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + XDisplayKeycodes(dpy, &start, &end); + syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip); + if (!syms) + return; + for (k = start; k <= end; k++) + for (i = 0; i < LENGTH(keys); i++) + /* skip modifier codes, we do that ourselves */ + if (keys[i].keysym == syms[(k - start) * skip]) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, k, + keys[i].mod | modifiers[j], + root, True, + GrabModeAsync, GrabModeAsync); + XFree(syms); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + if (!sendevent(selmon->sel, wmatom[WMDelete])) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + } + + if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww) + c->x = c->mon->wx + c->mon->ww - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->wy + c->mon->wh) + c->y = c->mon->wy + c->mon->wh - HEIGHT(c); + c->x = MAX(c->x, c->mon->wx); + c->y = MAX(c->y, c->mon->wy); + c->bw = borderpx; + + wc.border_width = c->bw; + XConfigureWindow(dpy, w, CWBorderWidth, &wc); + XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel); + configure(c); /* propagates border_width, if size doesn't change */ + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + attach(c); + attachstack(c); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + c->hintsvalid = 0; + break; + case XA_WM_HINTS: + updatewmhints(c); + drawbars(); + break; + } + if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +quit(const Arg *arg) +{ + running = 0; +} + +Monitor * +recttomon(int x, int y, int w, int h) +{ + Monitor *m, *r = selmon; + int a, area = 0; + + for (m = mons; m; m = m->next) + if ((a = INTERSECT(x, y, w, h, m)) > area) { + area = a; + r = m; + } + return r; +} + +void +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +resizemouse(const Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(NULL); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +scan(void) +{ + unsigned int i, num; + Window d1, d2, *wins = NULL; + XWindowAttributes wa; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) + manage(wins[i], &wa); + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) + manage(wins[i], &wa); + } + if (wins) + XFree(wins); + } +} + +void +sendmon(Client *c, Monitor *m) +{ + if (c->mon == m) + return; + unfocus(c, 1); + detach(c); + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + attach(c); + attachstack(c); + focus(NULL); + arrange(NULL); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *)data, 2); +} + +int +sendevent(Client *c, Atom proto) +{ + int n; + Atom *protocols; + int exists = 0; + XEvent ev; + + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c, wmatom[WMTakeFocus]); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setgaps(const Arg *arg) +{ + if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) + selmon->gappx = 0; + else + selmon->gappx += arg->i; + arrange(selmon); +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(const Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + struct sigaction sa; + + /* do not transform children into zombies when they terminate */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + + /* clean up any zombies (inherited from .xinitrc etc) immediately */ + while (waitpid(-1, NULL, WNOHANG) > 0); + + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); + sh = DisplayHeight(dpy, screen); + root = RootWindow(dpy, screen); + drw = drw_create(dpy, screen, root, sw, sh); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + bh = drw->fonts->h + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ + scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +spawn(const Arg *arg) +{ + struct sigaction sa; + + if (arg->v == dmenucmd) + dmenumon[0] = '0' + selmon->num; + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]); + } +} + +void +tag(const Arg *arg) +{ + if (selmon->sel && arg->ui & TAGMASK) { + selmon->sel->tags = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww - m->gappx; + for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; + resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); + if (my + HEIGHT(c) + m->gappx < m->wh) + my += HEIGHT(c) + m->gappx; + } else { + h = (m->wh - ty) / (n - i) - m->gappx; + resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); + if (ty + HEIGHT(c) + m->gappx < m->wh) + ty += HEIGHT(c) + m->gappx; + } +} + +void +togglebar(const Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +toggletag(const Arg *arg) +{ + unsigned int newtags; + + if (!selmon->sel) + return; + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + focus(NULL); + arrange(selmon); + } +} + +void +toggleview(const Arg *arg) +{ + unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XSelectInput(dpy, c->win, NoEventMask); + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + setclientstate(c, WithdrawnState); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } + free(c); + focus(NULL); + updateclientlist(); + arrange(m); +} + +void +unmapnotify(XEvent *e) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if ((c = wintoclient(ev->window))) { + if (ev->send_event) + setclientstate(c, WithdrawnState); + else + unmanage(c, 0); + } +} + +void +updatebars(void) +{ + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + + /* new monitors if nn > n */ + for (i = n; i < nn; i++) { + for (m = mons; m && m->next; m = m->next); + if (m) + m->next = createmon(); + else + mons = createmon(); + } + for (i = 0, m = mons; i < nn && m; m = m->next, i++) + if (i >= n + || unique[i].x_org != m->mx || unique[i].y_org != m->my + || unique[i].width != m->mw || unique[i].height != m->mh) + { + dirty = 1; + m->num = i; + m->mx = m->wx = unique[i].x_org; + m->my = m->wy = unique[i].y_org; + m->mw = m->ww = unique[i].width; + m->mh = m->wh = unique[i].height; + updatebarpos(m); + } + /* removed monitors if n > nn */ + for (i = nn; i < n; i++) { + for (m = mons; m && m->next; m = m->next); + while ((c = m->clients)) { + dirty = 1; + m->clients = c->next; + detachstack(c); + c->mon = mons; + attach(c); + attachstack(c); + } + if (m == selmon) + selmon = mons; + cleanupmon(m); + } + free(unique); + } else +#endif /* XINERAMA */ + { /* default monitor setup */ + if (!mons) + mons = createmon(); + if (mons->mw != sw || mons->mh != sh) { + dirty = 1; + mons->mw = mons->ww = sw; + mons->mh = mons->wh = sh; + updatebarpos(mons); + } + } + if (dirty) { + selmon = mons; + selmon = wintomon(root); + } + return dirty; +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) + for (j = 0; j < modmap->max_keypermod; j++) + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +void +updatesizehints(Client *c) +{ + long msize; + XSizeHints size; + + if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if (size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } else if (size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else + c->basew = c->baseh = 0; + if (size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } else + c->incw = c->inch = 0; + if (size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else + c->maxw = c->maxh = 0; + if (size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } else if (size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else + c->minw = c->minh = 0; + if (size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else + c->maxa = c->mina = 0.0; + c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); + c->hintsvalid = 1; +} + +void +updatestatus(void) +{ + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange || !c || c->isfloating) + return; + if (c == nexttiled(selmon->clients) && !(c = nexttiled(c->next))) + return; + pop(c); +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + die("dwm-"VERSION); + else if (argc != 1) + die("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); + checkotherwm(); + setup(); +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec", NULL) == -1) + die("pledge"); +#endif /* __OpenBSD__ */ + scan(); + run(); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/dwmd/dwm.o b/dwmd/dwm.o new file mode 100644 index 0000000000000000000000000000000000000000..be09fab1d335abadbd78d879d77d3bb77996fc96 GIT binary patch literal 58104 zcmeIbdtg-6wLg9)i5d`RVv99xu?CHrC}M)BiK1p;LQZsm2tfj9A(B9lKw>f@!6!OI zI3A<)YOC$Fz4mb{t$3x?S`;6_fS}cis1+YowAM}xDn1JM%3qv^{gul> z?{?M9b^VIDZu!2%5pHL9BG2tw-AWn$ays+K`jqSX3} zkn1EUTdC6P`m0sx^J0GOjqzhwQ|ZK8J*C~=K~%GzsQ2_V*FTKTD@$K<{iEnC)-~+N zXy{|N^Qpu|{skp|v@GUF$A-4W%C|-%Q#O`=9*MtPyzOBkjkr(v7mSOAo`{uiN8a{m z`R-UG{$#B9iRgODk4D^Wk^7#AmiJtBP|VxWm+g9ga{cO#sMpi?U#_=fHY{`m5xSk* z6C+XSxm1yFxaI3tk*0?m6`9)6_iAZ3RPU*AyUwOErQK;dq_+0Y>WMr!xnTKc*^w11 zflPP3e@=_m_!kz;zwYxP=S6Po#xeKAipXtG4%tno!!oBsM+}^f%$yE6hfXVUcPCF< z{_YpVWPIWz*ZU&!;B+X-!riXZNW9Wvrt5v+dT-9Yxc@l9W|el&zL->XeP!oqDC_z& zI?(>fBV13_xbH~UcY(Z;80vakQSIc1eW$u!O^55>zZ^VRj$(fx2-12!& zp*4@)f#c)k0=kq|(0!N?`nV50`^>O&)tOflce}31gS^_(?XKtMZK*R`_k`6;O@YoTLROj1+QX;B z66|XW3yA$wL|0qry56l7nhpk1r)!ocPW&WtV!W^ne`AH?K8bvPqq>fL5;;6YS}MGP z@V>*gC2aQMGs2tYC@dJ1zG7+Z1VkVt^Lr87uHmJJ6*4~umnZ&P~;)U$#S;j z6n=|@059`zj{|nmre3TtA4ml`2<$-zW%Wd1krp4IQC9`{?7kb-N*NIM#)<(T&Dm&kq9*azVyRx&J z930_po9o}QoW#DQjTYKo3Gd-}TK>`1!~DmU#(xgIT>Q0;vgI3rZcw^z1-dWVHEe8U z>HDGQyW$5?wESo2`BZA`TaC@xG4v3&;A) z5PCoKJ}cjH?S4!{&qvGuOiU4v_k^B~mcN9Pw048?%{YygZvwJ2wSipBNus~KnV#?^ z+2__GAncUBPZy9qim>u05(h_%pNSQ3{v?v8+&eO3d*qZSF24}hK6!TJl&3GR{v;YX z@pW{Cmm)=fLtl71a>`qmANfgS=F^`?GG;m*^LGAt@7K}d7ybLkLL#6;yfl#Fmvldhh2ARP7>RH7 zQ!!*mL#SytYRX*@UGE@dPeC^Kze801l zi=*G~J@%{q{&Jt%A^CQ!JVou8d_{75Fz$R+`qZ?ku3x3bv14WUZNu>UdmesMpNDu< zOTE(Hw*w9{-0e~$!ec79dLOc5UOhK`>^`)I*F%wYw4Wlc_9vA=pUTpwVt(Z~fBM)^ zx1TI2-WVu+cURo~`1j6cyC(F@6UD@)&*&LKJ9^{<_qer9iff3^1{vBe0x zo=QafH>LjoxbHoYBx=HNe^EuOd}n(;JQTF~XwnG?eKRNIeG*6O^c@oO7nMbe z-!JWsdLL5voz%G}vf>lCQ`CQuw2k?*EBy0I{At6>YYT^UjXT|+FfQhwKX!cA*l)Mb zEKbMD-;PGAUi6+vyd75jeChVRAH}@h@m;ltVLI|utoW7U&tki>v1NBtYf`2P77T{$&f!;3>toHcD+`@GQB(B{62iCfHjD^|QO7WzQ5_0KPx&^0`w zxbCP7y$0Uk7%T3L7Vj(m4E)oF1u!4qHN0GNKPa>{8ro9rKSl;iuiVq$-;X-Oki9uL zbyJ)2#S@})C@L3Kd;6-rj}A2a^alT;W5|-cf-q{nbCw z;^jO^A$N$l<##McHbz>%qN8sY1%+9tC=JFscJNqvJ;gmzF82V+<<0Jy{?K>8W}185 zE}E0@#L`f8Md2#oWQD6Rkasl~=F{j;!Qc7h9=A(H07B;GEXObyD0l+|3y6DMYq+Jt$;ldhLqu6S#ud>&4MXK6I4VNJLz&+h0wk6gsp)!uKsxo#6# z30*dIZLJeoaYi;AeE-f{B-JZd({x0cGQTM|iEyJdi+7z2Ca^&XOI3al_dZaUetX%a z)TyG=V_kEzV!8;1bY0b0@w@0SiZEIiory`4KVfcVX}@uPK{%lQt;!wpT#iK7!j@Es{8T65vALs-bTe>=Jz|B3-c0j7|7MuMZ_)~p8OW< z&_o6}3^~WHsfEVT$PYJyqqOk!=+qc4hf_aASSyM#$GTZShgJ7K84Yc&PJKNj(%C;S z`Eb2A9wn8H3X8h%H|}WMj{53SOmRNIj6jU@W2WEx)j%bpv!b)3-e!1xtaxXvI9;xY zqu!=S*B>CvOV z>M)>DrjU!yyls^-Yez1Y+^G$E?R|1;Lo>=a$wFVv6Z~6=6Zw_*rmrR#m`u^omA(_f z*Y*Fi1LwU9RCL%t=jj^PS5xT>C%NELK|rIqU~R+DkIpc(fztz`yh8vj3pyt zmHg{3NRo2o$cml7xUrI__|s62+g{N1xB~S!iYf>SgxmsXfROul*Pq?0)&hO!x&Aua zApQihL{G*N$;Vi~(mih1MZ8kzyHL!7rIo5NnkU)U_hQDJW*fXd*zT^^`|w<-LY1LL zQyNgX=(}J1z@L(uzK`58>m9w zo3mzLtn{F{AUx!2ivH^EDcJiYE}g`YekvCyUMe4d{b)%j?I|GUR~7DklI(@4Q;K@Amr~2n=-5~Y z3v@#EKAA)LN9dmFrKrzfzsyrhwY^W~awhe6FQwL|(Q%&Y{@k~x6m=q~`J?~_mLAMx zqoIw_^5?HPrEAveyS^{SZnJ~8KvWjzkw9P ztThzcQvUKaqlr&3^HJlZ=JT1iMX~wJ+gSYB_^zCgR0wkqCvjNn-C=r3__Ghm{s)MZ zNgbDTyyPB>now1FscQC%zKIlzF95T?acDLd`-*Ta%zvZUXBl3IYpiR~r~POR?F(V3 z&h0)Zjj7INFNB9muA+XuNQknwBe3 zaOa%T*ZQs@XU=oyVKn!85E71XyK4H~^5!l35(gm|^u4zMHFo;9Ld9Xa+-r%g?16Fg zoO~L$s{IEFRkY9@ji$OINhK7jpk9_JECc2iD6F{(sO;}I&PPWo;1j+6(`0Lwf;b4cu47^)r+n#0I?EJBQP&fu-~{w zO<`0~+H>}=z}%=G+qM2Js&eISLE@m^R*YXgwC+^l)$KZpBu|YzxD-2$@veJz9zsnq zFrrp_o8sm3a+AkvmHg~eD{E3uXYJiS@`>edf0c}2;8Ii2gWawx^ZU2Csh)y(d0+BR zh-$Pb$0`?L?ZW6|RDC72^nF@^D*>L`Y0!4`dMdqtdc9Z-M_>56pN;L0_A!zBHoN|` z+{do%&*&5j?Zmq9#&KDyRJ3eM@{pLPCe~OY_igFlocby^`G-1qvo-UO2|EU3#4^LZ zR47ePhgH_ACel+F438Uk%4YJz&T?uy@_!Y%a8{7oN>gSvjG^6Vt%&v9Fw=5a&ke)0 z6B+SBY$B=}7k^w4d2mnv6S~e6Gdp1yzZoUHp19r+F2_{2ZDG9YIOI5SEWq-;^hxmE z$bFc{x_(YibvXil@=VO%qyF&135O29ug3d;oJN_mx_`5tyRpcqs+JNsYi4!otDNLh z)u~Mdlgi&s-rc{s?+4WJNfK>g9%vLmXVtqaST)l`MiHm3ED9wLp56OvcpPoTW&Fwe z1m-OmWL_+$(|fS_!RGBEUp%mRb2H}6)8-wngkdgnO7x*&&=EVysx465SA4jRmpb~N zt_W(!ZXHLP>B^WlnOl zIQIi&D_ZvFSj+wbu@&YEsxdnIuR-^_paaYD%`;M1ce)-lU=D?WPSAd{9`Dq`# zKwlx}Q$(*GjByw_XxXW|pI0^3n=&pj(qCFv{5*C@aM8IZnG@^E!3J&B2yC`xg|>mH zXYZ@tj1jt508zL(ua-RL<(qy3xR&<@wj_0P-o+}!>r-P9Aq`tFTmkAxgF<~0}QM7{T8egl@O zx;~ZVyRI2cK`tMo2_`78V}J6~^bg*p9kf_sN8hk?C(d1cKMex ze#P9lr*D9I>h>m()@rX~uIsJrK-=Q9weMl+>y@4FC*9KSzDwPdatkLpEPXK)VLP#L zIpV93m#O*m1`{@Mcs#j}xhD%betQFcZJFAlI{xOw7#QQT* z&^@|2A7jq*OCR@_lqDeY(Cn%TxqIqWRrEbZh5ePyup;fB{u~a2z}(>KyC z4WaQcW~%}CA)0yWk(ya6C-ze`3#P8}=9Uz(#DB~^n{d!O$XgVFMr&~3$(?t992YUa zVwB%n;JZbBO=0>pc=nvZi?L{(zUxRM%MZbs=Nf8jNohpkP3gD4YNg%&tu%p#YMxTf z*J}A{)_i|HvOKj~_LPd4MXDKdinR39V%k#+Xg%kT>3MW|HMy0Z)6beYyLX8iFfK&5 zS1tY1w~z~)FQpq=ezeFvOZigrY>7JM(F!c;&WgIpTI83hX@r~F7fKuf8>aSUB@Zjz zlX{#C>CiplnY~k}-!T`A-`X|HJDL*1Q;*Xy;3N-KOlI}o0avI)pl0vwP?M!bW|q7t z^*FT@Z5=#br%q0v>Z^NOXyh#`DUVQJ-39a2O|zJI~Zi+S+#^Wv#?t$j1 zI2Ac%>*a@h5~-`o>&y8ha?!?*BYDbia1Utf{KzTKFCC7*igM{t+&8Vt>%A3%)Uyt{ zUZvZ8CsZ}uuNn?#bf565hWB0u3iJ(&LG2n1YPoA*(9=-VN&oy4+E7m}8dS3D?D{wR`%NyHMGCV~Y9Zh@ z@ixkVXuHlHV{w_K*YG`=U7j^i!5C&&6w==BjfPYsPrQ2yyf+re=7e0PCxMM3;?_6tAADbUou05H#($8aHXO9Pb z+J7Of_M5?6w`mPvtj@?!TBm#eEZsxFFItoSHr-{Au3~Px(Z+~hqgQ{O?+2H#Eeu-^HC9bzdU02z9*|T<<+q zX_)UN+Uh8A63^1H;OHb~&WXbOhq02N0H>(>s@OvYR+g|TGw3)G<%T)e9P46sj)ia% z&%o@VZuq9+p{QKG8Te$1*uoB&tE-I6h=+y7e)oo4wcn%d!}HX~PEcySn*JQo3w{vc zhgU>X)^m36mH+7P#}=ycb2Ns2^rZwU6vz#RM$=+nHdWcX@ zCKT~#fS9HSgh9r~%w4a3;dx)v`UiHMr!|G2Ar?WBfO^*i}FF#H-t+zROm>zuKJbkzm>z1EP8Ofmo12)ZM z^oF9IabG)l9faw&dzLa^@_09Ob{Ru`xWT%X)7s zOQDyb6~+~&k0j>c{6qa{3yp0F+*(l^ap_0U^ZTH?ryh1;xhLQuHXdZZLqeO>JWv&I z%jYyD4@y6;reHd>mRA<(-KuM7a2{0Wfm^1S2vXRAcdKzG@omg>mqQb{x$k2Cc~$(_ zm2?jv37?(SO7{<7KE3h9+3FSnJ$$FOu=}u2tv5i9bIadMf}J8}wG-#_T(@sL#(~3V z2Oi5zJYuK2*8gGjQfBOHOl?pzi^NbhuS=iXOKtUL`e!dwZIx8*I(|(n9#!g4m8xq^ zZ{AJ$+C<#1q@60@&Ky*U(<5q^Ra1I(O|yIdfTJ&#io#p~a5M}T9kjvU zs#K(BgWga>#g-L?w8f5H#5}rxtM1!sl0B*C`Uke6I=ioZUpo7|l{?AfyeIY4-nd~4 zCG&hYAJfti{Mek{(uwgR0LApJ>`XkvV@s+UN+x_Ri5)%FCo~||s3`L*ay2F0o76={ ztNFg7Mum*W`&iKo=z5!(-C*KDvbl+c(f{V@9mV}^X|$KN*<&%~4*TOB0r*%8e=HZk z0`-Wsl+-XYUl zW7gIY2C9cff?sAA7LKpSMuaBiPlHs_E5J*kg$i2`pdtvk;KrCpTTd0%E{V-4%G1;& zLy;?qgQaM4W{;p|vscTxjDUiS08J=?jP}B`)|wx8Fz>C09T#RE6z*s$N88*9XBHYJ zR@lTvbfvMC=`y{-)4zSMzrVCU?S4)c)`2H8lu1~>ZW9G4itN&}T`aSnN|g9V`qxo3 zzwYgR{4L*>_+II@3-LgM0@I&jJs?%9>pc^35)71~{D?m>cHLW4$W3+U`L#C;D}OpM zqV(w*D2$8rLxYO;+I9EXVyyQ z%%OBUyMzXDCrJC2dhVFBd^P>lc>+Dyd7t^U6eU7mY zYC5R%TQuhy?AwFP$MtIb zJ@Ec`iJJEDjz&@XPcSmA=L=HHaHADVaO_p7r{zoc%-=A zCtLtK!CQD|;$)Sby0!)#bV@<;V62q0Ft0gFt%sBKxN-B`v*cO^HKiVUhuVx*AhG)|s39~o0F#Wq7C0M&f9Y#{ljVGW?7L>O$Na?=`q8FPPqh56ZDHIBz^4C@^nRC_MfqF`v^%v!;yL?rq3R5?C2x3|CbKE0oA!LY=4YKJnh$dh~hgF zvGQY+<6QrIEODPooa$HBmQTn{o}`V7yA`KSufg4rz1v1^Tk}k=d#Zbmf*y`|>vsw1P3evBIw_)6~j(#W2O|(a6s> zRYo3sCz{%n)jL5gl*~noMEcaGJmcmE8@~p@etV+OKjb(H*op66fFON6h5ht5&=c4& z-|wA?kpwNP+#3VKWidS}oK^d~c;a~1+dU(-JLE-UV3p(5UKd9+cmZ2HT)eLLJG!_j zGqU1UR6O;tioKD}XX)Zx=NfE%JxuYI7L4##_2nO*zkG8$=@sd`1*BM3tEjvK9!9(F zkrg*7RINwkfh4^cfx$Dh6?5V1cm^BWj{AQp$cV`KDHXwr_G+nam3iVt8P$Vbm==BSdwe^_3og+T8)RPJfMRIW=Lm_d^PWy{F<28>4g&Jux}FFs_#x)m|r2 zOzK)%Gw_v(YW=)W)O(bczc>&twz*Ouj@wQU+)U5IY7=`KCw0D49jSZ<1HBuGrCs`- zM76&Z{diB}FxW*K3mP_7`)3e{u>jk68F(^?vFQt9*-4`6ltulj5ip0+!>`JZ4(Zgo zXsMw1RuscRxtoujarGE5SExF*rD#(5*LaQwy%ee?a^=<;i7g;sI!MWm`if((=Iy|b{t*yjAudLEn%^d0uZ3%r84eg7TZ@p$n@n=!5OSS%a zWBn_}MYC1AhMx02kNS_26R4+MhTw~5-Vf;#jER~JtmY5VElL(m|I2sdTWV-|XhBpU zrZVMQBk_%{KdcZiU1P3VLe$!x?Dx8tpscwuNo|pM2D>>pevYx6j(eWwYZ@?hDX4J? z;;MGI(mg~q5;rKKDdiJ*j)ra;doQeU?v`t#$} zzH&W|(t1KN=uXT-BdPcKHFM#{ttgAr*0|S1mmTU-%~yf6R?F;0Z^5xa^$aQMcaecu z-AW%%8)5A7JE;)|9#K?wtM(sV1ys)u-oxGzrmo7-W>%LgU6Y4$7)fJW1Ju-Y;$BNr z`j={lNA*+|q!=PPpGE-(+dSCH-R~AT<`_-*t*4hXeoM_;;2}`)sK-I zl<4GF7Nr+&QC&VsGnNl9V>tsGH#B2mou~OVuItw|;h{->_>=ByAcVh`m@S)Oc=)7a zZo277Fe>k%ZDIA5jPwiw^lS<{g>R(kj7q_PWjui;a_dk1kAo9m?qk?V}A0q)@jReDMFcK`s!Z+@1fr*~%orh46 z`KF25F(S*dk&Os14_U?i1z?M>n0$`ON;LIIVKY?4u-J#o^rM*~K4P89S`2A9;n;keDW@5o4Jg_1L_Fxo$S3y&Zd(c@k_+~PF0So0z`d=C_& zGrpWhi?vwcMP0+`iRwB$)m^+T;%=-g-d*kAR7lMnE#B7E6k1P|{<4x-#O-DahCcLL z(Ys5c{Xibry$S@be|c{C%aNOC4w79{hSOVCsB&Zd8a&j9=LN4?nq4!l{N-y~scC3{ zaFUG}K$6Fhk*F1-XblRPUN2(5Zjto%t=Pex@4Xj2={99FZ`dHRYxKF^u-Z(r>`ysDgMrLp8hVU*?0=D>I7dVaN6UO5yOJNM{0z7%>z#?o-mZ z+>}$LY5!%Lazsbd%4!iqIdd5FT=74musPL)*phdUF|<)0QASCxeF6@wSNZ7ciEqP3 zbf4@T+)W%u%9_ntpVeKTPIyQY-(rO=aZLr}&RTBakp7^qkzJ^Y;bfs9Y9 z;@;o63)P_uaXYc=>j!xKTggt^OZ*MwH0kY+8=k!xQ)1lhgJ(BjH;g>{WNO*;PjT1a z|J<`tq68msSE*s?;xh~Cm$W3B8ruq%E?m;kvb11v{gV0xxLnv?P~Y6vSl_U$pe?y% z$-*TI97R6AK2hIX(Ad`2(spJ+TjOQP#`Z+Pyq1Q>vyW>SrE$3&>dZc_;bJPLi#B#N z&bz#|;DqDaPbipQzp%Nn;Y`Xn`Sj6aoh2;=^IMiA+EF&y+)!{yV?lFEeM4izXlHuu z#IiZlYNu9Du5xBfno~1%O6`;hQ{vO;B32o%tgW0gqk3{|$_xUg)#A&SbW$5VzjDsR zDHEnobLLF0teq2`P+NUrhEhS(rq|R=nOa*J(-bH)IXbB_bD>$8l+a)fs3y3T6V4w@ zHfQ4WczoJ~sg;$JGZ|W`Ikhuu26!6SoLF@NPl#7nPOhC3ubx)xoP4>HY)2EE zSpeG<%s%<@iydcaeH*pFnFTOYbIZK?=Ej2dWNT|nTVkj-?Y#OWXvTt;*2X0T4GY^_ zo9mZ3?Fpx0apRKY9JHTv@?z)Y`8X`O1fTiN$?Z6{&qvfd+c{_4InL}!7dur87q>bS z7q&IdZ|QJKoiWa-&RFL(=XB=`r_3pL7Pl;EX>YBc*LY_8!YdljE*+Ek!kn6e(9CAg z_wlK-555bf1<=EzdcBJqu6cM8w>k5zGAYS-#s|@cA4nuSPSr--Nv|r}^ zRU*3gIlg$8mde&*qHu?*Ezu?`DKA4QPfH|6D^3VIS7qUjaGD-c0d<_7tr(|S^&P5h zmfDw{c)P!+Z$(B04EIWf1>RrdO7@4$UsS;DXD}NOYO42tFZkwxbu6>QU01!1Py%Ja zaL#&6ec=^*<6Z+SMVlA2pNLIdT@gLFlHp;-qsT%ux$$mN3x~ckx9iTrHRL2b{m5)X zB=2_Tt>CQKW^5&OCvHNt?cdhdNgKwr*1%H?HOsmCCMv7Bjh$w-tEDTpUSjZwfNc#XZ?2^}{-P zDX3jWt2Suc-7L@ND+;Oq9$>CS{hWSlC9~oqt^o<8D#_6%6P#B32TFotl5@Z$xEyUF z+OMs^N8|+M|GV;{hh1K-gZNASzban=qJLGM@`K7#9;H@&*za!zBklX;Xh8BW`8WBO zTKS@{lwV}!Q!gb*@)uh9cKHS?-!9)|256Tz$LHQzQjtJ)Caz2XupnQ=ZlrQpw@+(-*KlqqA zg!Gxn56Tz$LHQy-C|}B7mJ$5_-w9Pw;&`A`w!^=r3uN%W8UJg6|91-xw86DFKlP*gQ}dT~MNsiVh?E-e^SGUn8hvXW9K2jvRzIS3yRV0;dB z6UN;GM}0j67^UI*;DoN2>V!J-Lf;*BNbYJ7)3`E%^XLIeN`#fDGsls6B4HAHN;!|P zIzAUU`M8$3@AZ@y3+JV>vaSy2YQC$OUicG_I~kKT1?jquF{O7joHrE=%7ATU{B}A{ z4(HvF6$|I5vd4!DvYLm6^Q(HodC@1sxf2hKhVv)jJe(Wtc@m^l#~e1!Vk+w@y)GOY z&aFHYmn4TC6`-VIGh}969L}Y3;}}1YGJ&VE8c}RKMC3XJfYjD5joIfTD7vsZuH)mCc^Sh|NH|11? zi*7i`4VR=2&fXmw8ZL^43&w}@K{TE!HFYq6;)VF+5+5YG`9tz<$eKv@L(!}i;d}t7 zl#8qqrn{QtgXJw*V0|;m?aCP+F1qQUSh(bdgQMZH)FD;jn(Xg~!e!BLNiGLw9R?EEXH`x()(Eef)3Q#z6FZQ}ehNT&ha ztHSF-SvQ5(hbqI}q4UC9L)GDJp;&lFXnc5QsD?6|rDl$E7m5yJM;lMa_`j*!+^lH0 zsWRMJA8r~So}2Y-xM@nb^}=vdG(2}1L42MyWoWnw?K)SJO$s+n47WC@i|LAN>Y=sa zrg*p&IVf5WbM?SpPr_i&z+%s$-TsV*dyZ^DeP|u)cofdHEoM`evePr1_i`pL>lrwt zOAc8CY!~O#vnkq6=nSds^TMmMvI|24wit`-{2XH9I1A|y+W98xPd8-8!Ud_Etf}Zx zu(NU}u+XPEHq>yrl~hi(h3bdoN0;kTP^OZ4Au@4F{e13~KuUIWhR$`B}moS;YSxrhT9ITXm@lFVD()DO@mN zpc=|I^C^HLsr>@*Pi3`)^WAX4C5rBRWpj#A6-+1VDzaTIV`?t~^17&x+(dophMZWq zC`ElUOLs%q0R%8)4CQX(a;tEz`xb0>b2g-M-$GBi;ou3>x1tCJ@D+3#t83Mw=!?v! zGN_L#KB|vW#Mu_^h=rGj=7l?8^U=e|)oRc?l;{h>`R4(; zQu}m9n*x-7gv=5^W{SGK*pgkL^ZO(PMid%I@T{?+um{L&$zh#8BJi z@#b9&{ior^IL=GL}wleM6>`OfVfi_neVK?gov(8^~ znUkrEnvc;ikgM7}e<%rcoO>yD4zv%()U51L+2QqB)5G29-dpk6hN;K8tn+|f0PG@Q zF0gsP;=rZ>o6cA}u&nDv1wH>^IZ+bU7|y#8^2x96U|j9D)ra$Hfs-$+WBhHRpPKQT ziSUI-|CKLLyYAw$*He9!J=K_|>`9ZA!$aY^$>F(k!gX%AW?{Gh9*LSO6FUVcGKwaI zNMq>_avGFNPslhn`AQAb9L+Q{GP!tn0I*ihzmoE2k^Y(SaT4^81^F`h=vpqT`XuJK zm_NfT(VhzAZRNbzxNbKS-~D1Q1-^};1B6#)^I^ll2ua<%gl^NhE$~HTl~jzqD4d7s zuc9Bv_=l96^?EpOlI{<+jE^UL3hB9MC<391Nzop?Ht1lQ6!{n0;uh-D<~&l3QPnio z#lx#Z*=w@HcZ8zhyFyu0aN+{WnsK0^iu&4%%=YFyBtmd~%X$6IKe{ug?j2kz4?J+jZuIuLs&4*s=a{6RW6)3*PD|GMn#bauFG z!hSOu%_Hb9xp|KBQU8JRvoovR!MWK1cc-$!$xbvpV(pxPRiDQdRgG}DRxuygcNqT( zj+A6AT!?d`|C2%;heNJYjO!5e5y^6-azy_k(_ha=ii06gck?w5Yfc5@>n*$r*Tm;S zt^)N7O4INm{07E(Nuctw)vkd$&HS0e^d8d>=N#$>#OJ4st93W9Hhf6WRg8k?6Pawb0*$YvTWNrYAip(GNL?oLr}Z4Jq}awIR_<6Q4-LNWZ~{@DsVB zYAp_o9-bup4mP}6e*;5!Qus+chu(~9l^~DkcQdZm-N+!lN&h>!zS52_FfQwO!T-Ye zZ5I72z={7cYydTH0`?Z;zhhjjyMevS_-G!}>Tyl!BYa4ob?OT98b0p9k@(-j^lB{) zj5-kEC-eBB*3iIcolku3vh*B^Yr=Peh7yfSlnw_@d>T0Rsx>t5Z!`T?MKz$$tFR;S z*-w8&csoa#&YlnT^e%msg#$$M}8fiF?O+pXq(Z@BEeq)Odtb@+abtKPriR$S+CHL)4&WsHBD@%-;;;5f!dGJZDW;%~)_&thDy^-$&v#@8OL8TibnlJPSNG;lfNwT$1( zxLUU%@B54&!-_XB{Q|~sU|j0e#`xU~oX7OE7e{vZh;fNOD;ZyYj3#`R>AQf3oS{wu zU$^p+9;&BtO%g6u`Y)Ui|4GN{YpK`WjQ2499@EqJ^NIf6LJiRTozgRm_cC6sVP^;M zkTc8~&)0|W@lB>5!wn_l^**LSBOLYf3bs%y*VE{(HvReVv;bf1dFxng351 zkMo2`{PS+$Dqb<2)azcRU&r)nJp-CJt+G5!Hp zXguSK882bRYF&c7XMt1vSE=eiasaPFIF-A-ocDjQ`HUf6Dk{7XC}dpR@4a zFfQ{w$-a;AcLm_%9~l44!Z$FU%l<9d8yP>s!nZI^^G7p1%edUble`xgpKj5=!Z^+2 z%=9MXD=hr)jQ`NW`xuvXf$;i>@ii9xUdDf8;a@Sn*}~~p1Z*E68HWVm;Q;(w0r;^2 z_=|{-2a-D`fd0$?e0%^-_f!tVzcB!B4ZyDpz*h#~s{`<#1>kD~@P`BN%>noe0r*=1 z_?`f~KLF1~=s%D>^8)Y@0eEo$ULJr~2H+P2;4=d7O9Sw$0`Qvx@c#_J?+L&k3Bb1n z;BN)s9|z!H1>j*!Xb;q0#{}Rf1>h3`@CyR)iv#c_0eC6^za;>_D**p}0RC72{!9S= zd;tD(0RBb*{$2q7X#oC306q)@`+?eVL;!wr0A3Y<&j`T3AAq+9;CNfi0rk8g0RM3S z{@(%k0|9tX0RD0S{$T+AWdMFiz&JHL06!)GFAl)R2H@2J_=N%Zya0Sj0Dd*_Zy57= zwfBy3|M~!WF95$a0RM3S{+j^&!2o{z?G;b^uP_F&sV5X=qH;&uijCJI)t5 zOBy>83Hm0db6Ij>!jW%^wl&U6BwFw|9(`-D-Dz)ZrgMD5G|@7zd12#{gwvtELE4a9 zym%R(gJGNC40SXwyu_K;+*rRP*-8u=T9-NV@SW5|BVFM;jxEVVyHnrRR=;F{f|9Kb z$fWNlHbGXqGp}XI{DljWZH-QwLRonGQm4Jixui*brxE-es)DniG11o2l4wn|Ids(O zk|53XlKM6EGEwcOmZeP#8-|Z*9fbNbyb4h4Ysw_>v^r2`$vHuuZpD zrmn3m!fHVy#58sQXcSwIZbe&-E|E*pYC(M~HFRQrvbnh(jfAHzwY3*RDSVyPNwh3j z(A+p5U+YcKmx)2D8<5Tx&##}Ca276^w*(3&8rztdOuT4ebF(yCYyDC<0ez=+-eTC9 zPE`9W(I$ZzsV!RDT3Sb=<=gdH30s6L47=$HCKe{p;{0?d6wne>BaB|y-d2ygfg2fE zabt*EO?^>1v8=Vxq0Gj%#4@%h7$sZLBHHzuTi`HD7td>IoVN(B&vk;M@Y&@H8j;wL(u-r(~_R zWOJi(1#zyW^`lFjD_YRVBYJuRqLwqiwGF{#zEd}ynrMdd2B$8j+lVh4=hZi(|Dj0Rl6i<)bybat z8IvZrv@Nc0c2#JaGe@<{oOw-)=Fpenow{kTex>TL#d=D5SB*`3sjcPSAm>Zc=^g)cS?(2+-i4n9y9`-cIrxY; zwvCB7@cX3;8xn}~AW^Z6PAH-)1XXtGCcz6to?LRJC=5N&bvUd_EBY zLK{VyuZH`P<~&vTov>6aK!Bm6rMX>Iqk2heGBHtw!MX|X%ms}D49;&{M!!Z-+k%t! zWs8-NNi7Wv=Pzt*gE1)nIdwWu(GamyNBRhrs^Rn{6g5GImet>_lvYIh*0`%F7g6J$Z2IqUh60m1k=&80b!@3jV`= z2Lb~5t#WRYGqqx--_EFSYt393okq8pYkuQQ;D5LNK=4+ugu^{Jbte+8mF zuF<+F_yGv^SEo)nP_61|lhFpcONyD`rQGio!36RMDJlDmxDA8--GTHPpAt@6WsmUn z-!1$BzMjeZXf}P9fv*59eyc|%^ZxxW44m#8Qli5z@e%rO8hEFH(~sp)A{~E)kIUDkqPWSsL3H>wr(pkzl@%V3iL~dsQ{f!3wuMPTp4SbD(KVa~= z0{A%0*C`#rjYM*PgOA7^$2b+EJkfKeLH}EW{$?UVqQgD-2%pCpxA`1~eK1Oty%ry# zKgPgKKEG$&=Ci?|zt`aNbO8PH2L1mS^zRzDDfcAo|4R_-sNRrJF2V+UrgWe^HY=zqIg2I42K{pu zy|mX33l~0rGx+?`;4_hL4N|*M+|^jZ?eLyOFYWsczU3)?EBsGnob0e3A8E&`0D5`G zl;};n&9dmFUiB6(au->+*sYy$;&0k*nME)BZ?JH&&krnI-JeMJAUN1!m0;3Bu2agw_cACcP;K!2SjDEe`^7>7f7a0dLB>TNk^7{Di=F>s@cFaBXP-qc zd=B~!h>%GC=kO8z^B5<&WH-U%1}=3*wlh6|&(#KQ*7pY%AL(EJZQ(K>_@4m$Een_V z#>nrIR{O{8(-|k-x8Wo8stlmNz@UHLpr38gi=I~)^yWUyUk%*!hmQjI96bWUkfhy( z|1T|E?6x)l@3C;xX9f=N+4BPxbT@AfY(~M?4QiCaM?e(%EEzgBT^oF7=Y@cF61 zr^n#)D~n#@)jbw2{C{uZ!vCuPJhxCQMs6;9C;xG2>V4Sc`_=sQKV&JA7?hoMeotSXV0_nQCzQ?7B1s&n}v^M`nxS$+VS`kl$x3NFZLPDIO+d4 ze8fKO7QM8~k1SmDe8}Kq*7r{qy|nv@MT7Mb{L}#4wQ#A|6bqMn&1RhH)nnA_5{q8q zz(NZb{;d`+{F4U%Hw^v{S@aV3x-DGzylC(-{r)YxY%cvh08qu77Leo{%wqt{(m?0xyzuZaaQ#Ct$|aSP54l{ z&*1YeuElRR81y$Ac(+0S3j^O`(3|{UwD6nQ4u7?9nZNZ~xaj|}f!|`t-OD)1{D*;m zZO~Jm@Hyl}Tp&?{8)4k$^DTqkrw;T9v2LCGzocJf0|Fr@9 zZ?Ncv|1AdoLxca144n8&T>8%d{=cy3h5tPU{v(6`?+u*ze~AyJKL+rB%%T_mTMYbm zlovnzvw;(T882Q4;Qy*cFZ};z;CC4O-!pLHKaB1FVF3R<7QOKAH}D@D{0}|}7f2Eh zhco{}87G-$KmCS9FZ{o2;6FhbvClCEPP}9sKOunsXp3I>pJCwtY4AVCz=^+%%M$|l zpJ&kv|7iw(r@?=gffIk3Z_EkcKi{I?gff)cEPNc>d8xrC4LY&gH5R?_xzWOD4MXXc z06ssm=!MTOEc_kK#<@Fy&w7h~7t=p#(60d|`fsx6Mea5Or}2(#P3ctw&%w3KAKoCu{xW~~(!fprp-~`0BFyAJjB)7~zfx3=bCiXzWxUwJpJBX$apM0T zKBE70i(dNOd zi(cyWkcF?+Y@BB-{29idXWZt$!=nF^>E8;#do6q|)9;F zpM-^P($#U6F;0Hd7*i+~yGEVxK`i(l4mlMUNOn8sS@!ll0tJ?+4JMh4&u7$^If z^YcGj^x{9S8T979=#i%%Snlx#PJVUO5KTA3z)807zld?dKEy}-aIrye?t9;6(C;?r z?+L&+2H<})_>*k0XZ{(uKqAcC$37tdU(dL-qtxpQgWeq1vdR?uOh0;2Uphl9T=?WO zPWqVpqUQ$C#|`>jC?ob<5&~Gs4@3ZLFvYt;^xagU-a2a>LF!*mW_~({uhGI9F zFO9Zv;eRUQWCwFT6SwFeU_Co5T==Xo_@pApReX$u$r&l!9^Hu%3{ z(cj8^oHO-_*g^E~3&6jyaPiwy&N?ukOD+5zmir$TzLxR3EL`e!H{)dgPw)}HddZ-t z{yuJ~X7rAMQyJl-UNnGf!al`E_~ebl1(Mi7=tnY6^#8<1=+|2GVz>IUiOT>V!SAzh z;r}Azwmz=~(7$WY?|~fA=c54nFAREWE1^G$Z{$gRMbB6OUe7q`Y5J!hK>w13??O3B z?^*a-4LkcRTd9e!H@LL}QhuU05H zgA4z+7$^Q_yBri9OfUSu7l5C|IPo##zhU5`jQhE78#qtrm7RZT;5;2r z_1$OS+#MBtmw`{hIVCdR&+rlZ)Dnn9_WT7tg3l%piRk|eAHf?5L?Zgn@Dcnn0+ERR zm-qKSdxC(VB9%DRcl|!F>7{C)!>3 z2p<}oDA|0-wv>q8TrcA%J5>@rf6Uhh@zK_Em_cvqDZis^^UpWvNyhs{T3)e%Q<=3I zcI5YNZT-g^`g{disc)mjN80N$gWepkZwa8k-Jt)Q!C%%TVxLjmQRw%pDGB}%_wx@8 z+_cZ927gnZ9AdzHO!_>=2{ZLM&7h}K(Px~6i#|05A2Tk=?|)OaDR-{H$Bh4t1|QRZ z78&%};Kkjj|0V;c(??FlA0&ylgppi{+w{B5lmw?SmC^LjMDcUijZ((3}4DUjg*;zB;N`kD<>W1Lz;KaJe77*}}z7o@Si*o8#>B7QN{I z7lS@z)b|4eH|HTdtx(~Z{O8^R{6UiOn!<;Pm#JL(EoMDWo6GC*8UrVK`5ow13zy$n zm+_SNT*WkWSk31mSAN%dorRxKpn+``F25VS+rs7dv-5~ClJJ+`5ihZD`5o>W3zy#` zZ?*7Dy$qb{RY1a!RvS3kN9bjpO4+6z)>-s-k0dVmZiR)*?|<(Oz$KnjHp!LW%a=G| z;}W+8m*3rQrNT%yzS_d2-Pc*T*ngXae>76_-EHCGhx-hi#szsVN*?>O=p*#`22S)f z?4KnDPArAK%)p60TVqX2*;^_AaEFA2cq zJ(WT)?`>$c=mlRLfUmP~sn<3OmwvR{!lk}>2MyL=`ca963;!Am7yhjlF8o(pxbR3_)JdI|p;3m5*a7B2i(TX;r)3m5*|EL`~Sws7H}mpfQb;a_6m!oSACg@3Ds3;)#? zF8tS7xbWX*;lh8ng$w^Yjzjy|-@^Z-tC@*&L{D1+SsVvli(cNFBympY4`zCaXM)S` zH_7`NMIU)D({h79$qh4~H5M+v_q5Z(zsvM#3qOkS-4=c{O$CQ4GiBnQydTE3aCy&xa}Av6^Z2^Sz={5Irf)THqL*aOhl&1creDqkbV>9aUYxru zT;#5?@B+SGXW{vLz23rwf47D6W#&EfqL1L)40_W4Wa(dw(wV>ynkT;5;zh=ohsOIx_S zr)~t#4@IuLr!HpU63^=_T*i^r7B26X+hO7Iez_8!*NI$-t2Gub@0VL=;qo52O%^Wi zk=tS6@*cV2JRcLeG9Hb!aCwhhjfKm5s87B269qu-^cByweZcX&P^xV-;ugoS@pq|2|caG5{fY2h+Ye!#+I z9wPG-kt^?QE8+Qu;4;stwQ!jiB`jR#UF$4d=4sDZxXkPFhUxl=T$#@oS-7kVDlA;~ zXX-3m*85jkxV)du{9QhhE%UcwhYi+8_FG0-xV&$z&cbEipw+@<-)x_S%l=zkctGwl zeX9SC#xFZ3>MwCd<9BiOu}RL_8k_4!tDmhNjduamJEJebZ{m+`YoQAwFU60YkIoPQ z!7th4N82@|h>!{T(dvhGZCpUbF2ixO2kj>#W!={4IE{J9zb;84Y2K+A0 zXeg@4Ao^r|r#@~yQy+)pKxH|+WZqkQJmWmYP%iCe#^Tx#T`6u zko`!8*hA*Wb-;+HU4O>~h%}euhQ4x~0y*L1G#n2kKYyzxXyQ0X@=3NWpXRp*l0TOi zBkj4->&obvB5|_sbb4t4va{wW^yWO0U}lFL*{Q@=1iZg!hy>3 zZOTmUYA!E!mJE?E#~%iiU&H0C=2vwkUV{IE%a;&nq_tdrgn`j1nMRPR~-1i`n**^0r=5Zz)gr>JMc9eJuYjj>|%Cm;W;|4%Ge~FX|FI4gaS$lJe3n z2a-)C@0h2{moVWGxFlQKVgLXD literal 0 HcmV?d00001 diff --git a/dwmd/dwm.png b/dwmd/dwm.png new file mode 100644 index 0000000000000000000000000000000000000000..b1f9ba7e5f4cc7350ee2392ebcea5fcbe00fb49b GIT binary patch literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^2Y@($g9*gC@m3f}u_bxCyDx`7I;J! zGca%iWx0hJ8D`Cq01C2~c>21sUt<^MF=V?Ztt9{yk}YwKC~?lu%}vcKVQ?-=O)N=G zQ7F$W$xsN%NL6t6^bL5QqM8R(c+=CxF{I+w+q;fj4F)_6j>`Z3pZ>_($QEQ&92OXP z%lpEKGwG8$G-U1H{@Y%;mx-mNK|p|siBVAj$Z~Mt-~h6K0!}~{PyozQ07(f5fTdVi zm=-zT`NweeJ#%S&{fequZGmkDDC*%x$$Sa*fAP=$`nJkhx1Y~k<8b2;Hq)FOdV=P$ q&oWzoxz_&nv&n0)xBzV8k*jsxheTIy&cCY600f?{elF{r5}E*x)opSB literal 0 HcmV?d00001 diff --git a/dwmd/patches/azerty.diff b/dwmd/patches/azerty.diff new file mode 100644 index 0000000..6566ad3 --- /dev/null +++ b/dwmd/patches/azerty.diff @@ -0,0 +1,43 @@ +diff -up dwm-6.2/config.def.h dwm-6.2-azerty/config.def.h +--- dwm-6.2/config.def.h Sat Feb 2 13:55:28 2019 ++++ dwm-6.2-azerty/config.def.h Sat Apr 24 22:28:13 2021 +@@ -78,21 +78,21 @@ static Key keys[] = { + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, +- { MODKEY, XK_0, view, {.ui = ~0 } }, +- { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, ++ { MODKEY, XK_agrave, view, {.ui = ~0 } }, ++ { MODKEY|ShiftMask, XK_agrave, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, +- { MODKEY, XK_period, focusmon, {.i = +1 } }, ++ { MODKEY, XK_semicolon, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, +- { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, +- TAGKEYS( XK_1, 0) +- TAGKEYS( XK_2, 1) +- TAGKEYS( XK_3, 2) +- TAGKEYS( XK_4, 3) +- TAGKEYS( XK_5, 4) +- TAGKEYS( XK_6, 5) +- TAGKEYS( XK_7, 6) +- TAGKEYS( XK_8, 7) +- TAGKEYS( XK_9, 8) ++ { MODKEY|ShiftMask, XK_semicolon, tagmon, {.i = +1 } }, ++ TAGKEYS( XK_ampersand, 0) ++ TAGKEYS( XK_eacute, 1) ++ TAGKEYS( XK_quotedbl, 2) ++ TAGKEYS( XK_apostrophe, 3) ++ TAGKEYS( XK_parenleft, 4) ++ TAGKEYS( XK_minus, 5) ++ TAGKEYS( XK_egrave, 6) ++ TAGKEYS( XK_underscore, 7) ++ TAGKEYS( XK_ccedilla, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, + }; + +Only in dwm-6.2-azerty: config.h +Only in dwm-6.2-azerty: drw.o +Only in dwm-6.2-azerty: dwm +Only in dwm-6.2-azerty: dwm.o +Only in dwm-6.2-azerty: util.o diff --git a/dwmd/patches/fullgaps.diff b/dwmd/patches/fullgaps.diff new file mode 100644 index 0000000..dc52139 --- /dev/null +++ b/dwmd/patches/fullgaps.diff @@ -0,0 +1,94 @@ +diff -up a/config.def.h b/config.def.h +--- a/config.def.h ++++ b/config.def.h +@@ -2,6 +2,7 @@ + + /* appearance */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappx = 5; /* gaps between windows */ + static const unsigned int snap = 32; /* snap pixel */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ +@@ -85,6 +86,9 @@ static const Key keys[] = { + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, ++ { MODKEY, XK_minus, setgaps, {.i = -1 } }, ++ { MODKEY, XK_equal, setgaps, {.i = +1 } }, ++ { MODKEY|ShiftMask, XK_equal, setgaps, {.i = 0 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) +diff -up a/dwm.c b/dwm.c +--- a/dwm.c 2023-04-30 ++++ b/dwm.c 2023-04-30 +@@ -119,6 +119,7 @@ struct Monitor { + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ ++ int gappx; /* gaps between windows */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; +@@ -200,6 +201,7 @@ static void sendmon(Client *c, Monitor * + static void setclientstate(Client *c, long state); + static void setfocus(Client *c); + static void setfullscreen(Client *c, int fullscreen); ++static void setgaps(const Arg *arg); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setup(void); +@@ -641,6 +643,7 @@ createmon(void) + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; ++ m->gappx = gappx; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % LENGTH(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); +@@ -1508,6 +1511,16 @@ setfullscreen(Client *c, int fullscreen) + } + + void ++setgaps(const Arg *arg) ++{ ++ if ((arg->i == 0) || (selmon->gappx + arg->i < 0)) ++ selmon->gappx = 0; ++ else ++ selmon->gappx += arg->i; ++ arrange(selmon); ++} ++ ++void + setlayout(const Arg *arg) + { + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) +@@ -1697,18 +1710,18 @@ tile(Monitor *m) + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else +- mw = m->ww; +- for (i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) +- if (i < m->nmaster) { +- h = (m->wh - my) / (MIN(n, m->nmaster) - i); +- resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), 0); +- if (my + HEIGHT(c) < m->wh) +- my += HEIGHT(c); ++ mw = m->ww - m->gappx; ++ for (i = 0, my = ty = m->gappx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) ++ if (i < m->nmaster) { ++ h = (m->wh - my) / (MIN(n, m->nmaster) - i) - m->gappx; ++ resize(c, m->wx + m->gappx, m->wy + my, mw - (2*c->bw) - m->gappx, h - (2*c->bw), 0); ++ if (my + HEIGHT(c) + m->gappx < m->wh) ++ my += HEIGHT(c) + m->gappx; + } else { +- h = (m->wh - ty) / (n - i); +- resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0); +- if (ty + HEIGHT(c) < m->wh) +- ty += HEIGHT(c); ++ h = (m->wh - ty) / (n - i) - m->gappx; ++ resize(c, m->wx + mw + m->gappx, m->wy + ty, m->ww - mw - (2*c->bw) - 2*m->gappx, h - (2*c->bw), 0); ++ if (ty + HEIGHT(c) + m->gappx < m->wh) ++ ty += HEIGHT(c) + m->gappx; + } + } diff --git a/dwmd/q b/dwmd/q new file mode 100644 index 0000000..fc5dc63 --- /dev/null +++ b/dwmd/q @@ -0,0 +1,116 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const unsigned int borderpx = 10; /* border pixel of windows */ +static const unsigned int snap = 32; /* snap pixel */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "monospace:size=10" }; +static const char dmenufont[] = "monospace:size=10"; +static const char col_gray1[] = "#119822"; +static const char col_gray2[] = "#2A7221"; +static const char col_gray3[] = "#1E441E"; +static const char col_gray4[] = "#152614"; +static const char col_cyan[] = "#31CB00"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating monitor */ + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static const int nmaster = 1; /* number of clients in master area */ +static const int resizehints = 1; /* 1 means respect size hints in tiled resizals */ +static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen window */ + +static const Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *termcmd[] = { "st", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_p, spawn, {.v = dmenucmd } }, + { MODKEY, XK_s, spawn, {.v = termcmd } }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_j, focusstack, {.i = +1 } }, + { MODKEY, XK_k, focusstack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_d, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, setmfact, {.f = +0.05} }, + { MODKEY, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_q, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static const Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwmd/transient.c b/dwmd/transient.c new file mode 100644 index 0000000..040adb5 --- /dev/null +++ b/dwmd/transient.c @@ -0,0 +1,42 @@ +/* cc transient.c -o transient -lX11 */ + +#include +#include +#include +#include + +int main(void) { + Display *d; + Window r, f, t = None; + XSizeHints h; + XEvent e; + + d = XOpenDisplay(NULL); + if (!d) + exit(1); + r = DefaultRootWindow(d); + + f = XCreateSimpleWindow(d, r, 100, 100, 400, 400, 0, 0, 0); + h.min_width = h.max_width = h.min_height = h.max_height = 400; + h.flags = PMinSize | PMaxSize; + XSetWMNormalHints(d, f, &h); + XStoreName(d, f, "floating"); + XMapWindow(d, f); + + XSelectInput(d, f, ExposureMask); + while (1) { + XNextEvent(d, &e); + + if (t == None) { + sleep(5); + t = XCreateSimpleWindow(d, r, 50, 50, 100, 100, 0, 0, 0); + XSetTransientForHint(d, t, f); + XStoreName(d, t, "transient"); + XMapWindow(d, t); + XSelectInput(d, t, ExposureMask); + } + } + + XCloseDisplay(d); + exit(0); +} diff --git a/dwmd/util.c b/dwmd/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/dwmd/util.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/dwmd/util.h b/dwmd/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/dwmd/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dwmd/util.o b/dwmd/util.o new file mode 100644 index 0000000000000000000000000000000000000000..8e55fa18d6ada0bfba70d02362b40ffaf2d35ebf GIT binary patch literal 2224 zcmbtUO=uHQ5T0$TjWtbT?V(Z+5mrf{E}Ik~q$<%yyGoE+D{3#ACR-cKpPNldRWM>b zgb?V#qX!Qj6g(94C^YE7t4B}Pqfig^ASe~$%?e!*H_wR z+#gQLxBTIhT=s{b%5|js!_VYZOTUnxTl%%UX6d(b+tTmkPpGq6v+rmK4Gjy1B5kdC z3LW&mXss1_iv>os)~p=RT1i>`n3Mw^gi#NxEFAoX2Gl1&Yn~v>9Mc*b)Ud}2Xic>0 z4|+TC>cRCO#w|3`vDD_k`T&R4>1-_YvYq&(UG-`$t0N1T`;bZJ^QBB2U}|zQ9t=%g zx*85f#+9fN2}afExEfO#KRRyF-kt7tqjojnaLGz| z^@i91Tmi-r6@7;rexwT?>Vhj>aGJVXeVmikC-2WXC&VVY@S$qv@=6Aj)T9d8oDLN; zs~ZL^EtHL1(Of{^$m>N|C|Au4lrdB?K(FOY(Cv$XbxEqW!nxab;7`YvR}81SuooA{ z-!RcV5AE=@9h6{5Yy=>Xttsx zm@~xAiN_o+jADS!FTn{+*XXcOJS<^9+GN`A)BaCT{20R-oghnQoEtIR36CL8HUBlA z{(V$4ic1u}Kkt{v`2JicFhDl_*hQIR1pY7NF`mcu1q^VTb4V|_fiBz|h|xO}?<%@b zWBJa$oZoX7xkPp5zhwE-R7?, so it is mandatory that +applications which want to test against keypad keys send these +sequences. + +But buggy applications (like bash and irssi, for example) don't do this. A fast +solution for them is to use the following command: + + $ printf '\033[?1h\033=' >/dev/tty + +or + $ tput smkx + +In the case of bash, readline is used. Readline has a different note in its +manpage about this issue: + + enable-keypad (Off) + When set to On, readline will try to enable the + application keypad when it is called. Some systems + need this to enable arrow keys. + +Adding this option to your .inputrc will fix the keypad problem for all +applications using readline. + +If you are using zsh, then read the zsh FAQ +: + + It should be noted that the O / [ confusion can occur with other keys + such as Home and End. Some systems let you query the key sequences + sent by these keys from the system's terminal database, terminfo. + Unfortunately, the key sequences given there typically apply to the + mode that is not the one zsh uses by default (it's the "application" + mode rather than the "raw" mode). Explaining the use of terminfo is + outside of the scope of this FAQ, but if you wish to use the key + sequences given there you can tell the line editor to turn on + "application" mode when it starts and turn it off when it stops: + + function zle-line-init () { echoti smkx } + function zle-line-finish () { echoti rmkx } + zle -N zle-line-init + zle -N zle-line-finish + +Putting these lines into your .zshrc will fix the problems. + + +## How can I use meta in 8bit mode? + +St supports meta in 8bit mode, but the default terminfo entry doesn't +use this capability. If you want it, you have to use the 'st-meta' value +in TERM. + + +## I cannot compile st in OpenBSD + +OpenBSD lacks librt, despite it being mandatory in POSIX +. +If you want to compile st for OpenBSD you have to remove -lrt from config.mk, and +st will compile without any loss of functionality, because all the functions are +included in libc on this platform. + + +## The Backspace Case + +St is emulating the Linux way of handling backspace being delete and delete being +backspace. + +This is an issue that was discussed in suckless mailing list +. Here is why some old grumpy +terminal users wants its backspace to be how he feels it: + + Well, I am going to comment why I want to change the behaviour + of this key. When ASCII was defined in 1968, communication + with computers was done using punched cards, or hardcopy + terminals (basically a typewriter machine connected with the + computer using a serial port). ASCII defines DELETE as 7F, + because, in punched-card terms, it means all the holes of the + card punched; it is thus a kind of 'physical delete'. In the + same way, the BACKSPACE key was a non-destructive backspace, + as on a typewriter. So, if you wanted to delete a character, + you had to BACKSPACE and then DELETE. Another use of BACKSPACE + was to type accented characters, for example 'a BACKSPACE `'. + The VT100 had no BACKSPACE key; it was generated using the + CONTROL key as another control character (CONTROL key sets to + 0 b7 b6 b5, so it converts H (code 0x48) into BACKSPACE (code + 0x08)), but it had a DELETE key in a similar position where + the BACKSPACE key is located today on common PC keyboards. + All the terminal emulators emulated the difference between + these keys correctly: the backspace key generated a BACKSPACE + (^H) and delete key generated a DELETE (^?). + + But a problem arose when Linus Torvalds wrote Linux. Unlike + earlier terminals, the Linux virtual terminal (the terminal + emulator integrated in the kernel) returned a DELETE when + backspace was pressed, due to the VT100 having a DELETE key in + the same position. This created a lot of problems (see [1] + and [2]). Since Linux has become the king, a lot of terminal + emulators today generate a DELETE when the backspace key is + pressed in order to avoid problems with Linux. The result is + that the only way of generating a BACKSPACE on these systems + is by using CONTROL + H. (I also think that emacs had an + important point here because the CONTROL + H prefix is used + in emacs in some commands (help commands).) + + From point of view of the kernel, you can change the key + for deleting a previous character with stty erase. When you + connect a real terminal into a machine you describe the type + of terminal, so getty configures the correct value of stty + erase for this terminal. In the case of terminal emulators, + however, you don't have any getty that can set the correct + value of stty erase, so you always get the default value. + For this reason, it is necessary to add 'stty erase ^H' to your + profile if you have changed the value of the backspace key. + Of course, another solution is for st itself to modify the + value of stty erase. I usually have the inverse problem: + when I connect to non-Unix machines, I have to press CONTROL + + h to get a BACKSPACE. The inverse problem occurs when a user + connects to my Unix machines from a different system with a + correct backspace key. + + [1] http://www.ibb.net/~anne/keyboard.html + [2] http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-5.html + + +## But I really want the old grumpy behaviour of my terminal + +Apply [1]. + +[1] https://st.suckless.org/patches/delkey + + +## Why do images not work in st using the w3m image hack? + +w3mimg uses a hack that draws an image on top of the terminal emulator Drawable +window. The hack relies on the terminal to use a single buffer to draw its +contents directly. + +st uses double-buffered drawing so the image is quickly replaced and may show a +short flicker effect. + +Below is a patch example to change st double-buffering to a single Drawable +buffer. + +diff --git a/x.c b/x.c +--- a/x.c ++++ b/x.c +@@ -732,10 +732,6 @@ xresize(int col, int row) + win.tw = col * win.cw; + win.th = row * win.ch; + +- XFreePixmap(xw.dpy, xw.buf); +- xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, +- DefaultDepth(xw.dpy, xw.scr)); +- XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ +@@ -1148,8 +1144,7 @@ xinit(int cols, int rows) + 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 = xw.win; + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + +@@ -1632,8 +1627,6 @@ xdrawline(Line line, int x1, int y1, int x2) + void + xfinishdraw(void) + { +- XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, +- win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); + + +## BadLength X error in Xft when trying to render emoji + +Xft makes st crash when rendering color emojis with the following error: + +"X Error of failed request: BadLength (poly request too large or internal Xlib length error)" + Major opcode of failed request: 139 (RENDER) + Minor opcode of failed request: 20 (RenderAddGlyphs) + Serial number of failed request: 1595 + Current serial number in output stream: 1818" + +This is a known bug in Xft (not st) which happens on some platforms and +combination of particular fonts and fontconfig settings. + +See also: +https://gitlab.freedesktop.org/xorg/lib/libxft/issues/6 +https://bugs.freedesktop.org/show_bug.cgi?id=107534 +https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + +The solution is to remove color emoji fonts or disable this in the fontconfig +XML configuration. As an ugly workaround (which may work only on newer +fontconfig versions (FC_COLOR)), the following code can be used to mask color +fonts: + + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + +Please don't bother reporting this bug to st, but notify the upstream Xft +developers about fixing this bug. + +As of 2022-09-05 this now seems to be finally fixed in libXft 2.3.5: +https://gitlab.freedesktop.org/xorg/lib/libxft/-/blob/libXft-2.3.5/NEWS diff --git a/std/LEGACY b/std/LEGACY new file mode 100644 index 0000000..bf28b1e --- /dev/null +++ b/std/LEGACY @@ -0,0 +1,17 @@ +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/std/LICENSE b/std/LICENSE new file mode 100644 index 0000000..3cbf420 --- /dev/null +++ b/std/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2022 Hiltjo Posthuma +© 2018 Devin J. Pohly +© 2014-2017 Quentin Rameau +© 2009-2012 Aurélien APTEL +© 2008-2017 Anselm R Garbe +© 2012-2017 Roberto E. Vargas Caballero +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013-2014 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/std/Makefile b/std/Makefile new file mode 100644 index 0000000..15db421 --- /dev/null +++ b/std/Makefile @@ -0,0 +1,51 @@ +# st - simple terminal +# See LICENSE file for copyright and license details. +.POSIX: + +include config.mk + +SRC = st.c x.c +OBJ = $(SRC:.c=.o) + +all: st + +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 + +$(OBJ): config.h config.mk + +st: $(OBJ) + $(CC) -o $@ $(OBJ) $(STLDFLAGS) + +clean: + rm -f st $(OBJ) st-$(VERSION).tar.gz + +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)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) + +install: st + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f st $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/st + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 + tic -sx st.info + @echo Please see the README file regarding the terminfo entry of st. + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/st + rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + +.PHONY: all clean dist install uninstall diff --git a/std/README b/std/README new file mode 100644 index 0000000..6a846ed --- /dev/null +++ b/std/README @@ -0,0 +1,34 @@ +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 bt source code. + diff --git a/std/TODO b/std/TODO new file mode 100644 index 0000000..5f74cd5 --- /dev/null +++ b/std/TODO @@ -0,0 +1,28 @@ +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/std/arg.h b/std/arg.h new file mode 100644 index 0000000..a22e019 --- /dev/null +++ b/std/arg.h @@ -0,0 +1,50 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/std/config.def.h b/std/config.def.h new file mode 100644 index 0000000..76d4eb9 --- /dev/null +++ b/std/config.def.h @@ -0,0 +1,474 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=17:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +/* + * 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/std/config.h b/std/config.h new file mode 100644 index 0000000..76d4eb9 --- /dev/null +++ b/std/config.h @@ -0,0 +1,474 @@ +/* See LICENSE file for copyright and license details. */ + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "Liberation Mono:pixelsize=17:antialias=true:autohint=true"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/sh"; +char *utmp = NULL; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = NULL; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static unsigned int doubleclicktimeout = 300; +static unsigned int tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static unsigned int blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static unsigned int cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "st-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +unsigned int tabspaces = 8; + +/* Terminal colors (16 first used in escape sequence) */ +static const char *colorname[] = { + /* 8 normal colors */ + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + + /* 8 bright colors */ + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#cccccc", + "#555555", + "gray90", /* default foreground colour */ + "black", /* default background colour */ +}; + + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +unsigned int defaultfg = 258; +unsigned int defaultbg = 259; +unsigned int defaultcs = 256; +static unsigned int defaultrcs = 257; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static unsigned int cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static unsigned int cols = 80; +static unsigned int rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static unsigned int mouseshape = XC_xterm; +static unsigned int mousefg = 7; +static unsigned int mousebg = 0; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static unsigned int defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +/* + * 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/std/config.mk b/std/config.mk new file mode 100644 index 0000000..1e306f8 --- /dev/null +++ b/std/config.mk @@ -0,0 +1,36 @@ +# st version +VERSION = 0.9 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# includes and libs +INCS = -I$(X11INC) \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# flags +STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +STLDFLAGS = $(LIBS) $(LDFLAGS) + +# OpenBSD: +#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE +#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ +# `$(PKG_CONFIG) --libs fontconfig` \ +# `$(PKG_CONFIG) --libs freetype2` +#MANPREFIX = ${PREFIX}/man + +# compiler and linker +# CC = c99 diff --git a/std/st b/std/st new file mode 100755 index 0000000000000000000000000000000000000000..2b1bcea65fd2814b811305caf02e72326f864184 GIT binary patch literal 105544 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][{+-}{+-}]. See +.BR XParseGeometry (3) +for further details. +.TP +.B \-i +will fixate the position given with the -g option. +.TP +.BI \-n " name" +defines the window instance name (default $TERM). +.TP +.BI \-o " iofile" +writes all the I/O to +.I iofile. +This feature is useful when recording st sessions. A value of "-" means +standard output. +.TP +.BI \-T " title" +defines the window title (default 'st'). +.TP +.BI \-t " title" +defines the window title (default 'st'). +.TP +.BI \-w " windowid" +embeds st within the window identified by +.I windowid +.TP +.BI \-l " line" +use a tty +.I line +instead of a pseudo terminal. +.I line +should be a (pseudo-)serial device (e.g. /dev/ttyS0 on Linux for serial port +0). +When this flag is given +remaining arguments are used as flags for +.BR stty(1). +By default st initializes the serial line to 8 bits, no parity, 1 stop bit +and a 38400 baud rate. The speed is set by appending it as last argument +(e.g. 'st -l /dev/ttyS0 115200'). Arguments before the last one are +.BR stty(1) +flags. If you want to set odd parity on 115200 baud use for example 'st -l +/dev/ttyS0 parenb parodd 115200'. Set the number of bits by using for +example 'st -l /dev/ttyS0 cs7 115200'. See +.BR stty(1) +for more arguments and cases. +.TP +.B \-v +prints version information to stderr, then exits. +.TP +.BI \-e " command " [ " arguments " "... ]" +st executes +.I command +instead of the shell. If this is used it +.B must be the last option +on the command line, as in xterm / rxvt. +This option is only intended for compatibility, +and all the remaining arguments are used as a command +even without it. +.SH SHORTCUTS +.TP +.B Break +Send a break in the serial line. +Break key is obtained in PC keyboards +pressing at the same time control and pause. +.TP +.B Ctrl-Print Screen +Toggle if st should print to the +.I iofile. +.TP +.B Shift-Print Screen +Print the full screen to the +.I iofile. +.TP +.B Print Screen +Print the selection to the +.I iofile. +.TP +.B Ctrl-Shift-Page Up +Increase font size. +.TP +.B Ctrl-Shift-Page Down +Decrease font size. +.TP +.B Ctrl-Shift-Home +Reset to default font size. +.TP +.B Ctrl-Shift-y +Paste from primary selection (middle mouse button). +.TP +.B Ctrl-Shift-c +Copy the selected text to the clipboard selection. +.TP +.B Ctrl-Shift-v +Paste from the clipboard selection. +.SH CUSTOMIZATION +.B st +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR tabbed (1), +.BR utmp (1), +.BR stty (1), +.BR scroll (1) +.SH BUGS +See the TODO file in the distribution. + diff --git a/std/st.c b/std/st.c new file mode 100644 index 0000000..d6478f5 --- /dev/null +++ b/std/st.c @@ -0,0 +1,2674 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "st.h" +#include "win.h" + +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* Arbitrary sizes */ +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, +}; + +enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +}; + +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; + +enum charset { + CS_GRAPHIC0, + CS_GRAPHIC1, + CS_UK, + CS_USA, + CS_MULTI, + CS_GER, + CS_FIN +}; + +enum escape_state { + ESC_START = 1, + ESC_CSI = 2, + ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_ALTCHARSET = 8, + ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, +}; + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + size_t len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(const char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static void osc_color_response(int, int, int); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(int, int, int, int); +static void tcursor(int); +static void tdeletechar(int); +static void tdeleteline(int); +static void tinsertblank(int); +static void tinsertblankline(int); +static int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(Rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(const int *, int); +static void tsetchar(Rune, const Glyph *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, const int *, int); +static int twrite(const char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32_t tdefcolor(const int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); + +static char *base64dec(const char *); +static char base64dec_getc(const char **); + +static ssize_t xwrite(int, const char *, size_t); + +/* Globals */ +static Term term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +ssize_t +xwrite(int fd, const char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(const char *s) +{ + char *p; + + if ((p = strdup(s)) == NULL) + die("strdup: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +char +base64dec_getc(const char **src) +{ + while (**src && !isprint((unsigned char)**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(const char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + static const char base64_digits[256] = { + [43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, + 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SEL_IDLE; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & ATTR_WRAP) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SEL_EMPTY; + sel.type = SEL_REGULAR; + sel.alt = IS_SET(MODE_ALTSCREEN); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SEL_READY; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || + sel.alt != IS_SET(MODE_ALTSCREEN)) + return 0; + + if (sel.type == SEL_RECTANGULAR) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + const Glyph *gp, *prevgp; + + switch (sel.snap) { + case SNAP_WORD: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SNAP_LINE: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*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 + & ATTR_WRAP)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + const Glyph *gp, *last; + + if (sel.ob.x == -1) + return NULL; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + } else { + gp = &term.line[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)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & ATTR_WDUMMY) + continue; + + ptr += utf8encode(gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SEL_IDLE; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + const struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == NULL) { + if (errno) + die("getpwuid: %s\n", strerror(errno)); + else + die("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == NULL) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = NULL; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = NULL; + } else { + prog = sh; + arg = NULL; + } + DEFAULT(args, ((char *[]) {prog, arg, NULL})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + die("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + die("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + die("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(const char *line, char *cmd, const char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= MODE_PRINT; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + die("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, NULL, NULL, NULL) < 0) + die("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + die("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + close(m); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, NULL) < 0) + die("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + if (s > 2) + close(s); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", NULL) == -1) + die("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + die("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if (buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(const char *s, size_t n, int may_echo) +{ + const char *next; + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + + if (!IS_SET(MODE_CRLF)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(const char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup(void) +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static TCursor c[2]; + int alt = IS_SET(MODE_ALTSCREEN); + + if (mode == CURSOR_SAVE) { + c[alt] = term.c; + } else if (mode == CURSOR_LOAD) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (TCursor){{ + .mode = ATTR_NULL, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CURSOR_SAVE); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Line *tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = NULL; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(Rune u, const Glyph *attr, int x, int y) +{ + static const char *vt100_0[62] = { /* 0x41 - 0x7e */ + "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ + 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ + 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * The table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CS_GRAPHIC0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + + if (term.line[y][x].mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Glyph *gp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for (x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if (selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Glyph *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Glyph)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(const int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n", + r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, + "erresc(38): Incorrect number of parameters (%d)\n", + *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, + "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(const int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + ATTR_BOLD | + ATTR_FAINT | + ATTR_ITALIC | + ATTR_UNDERLINE | + ATTR_BLINK | + ATTR_REVERSE | + ATTR_INVISIBLE | + ATTR_STRUCK ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= ATTR_BOLD; + break; + case 2: + term.c.attr.mode |= ATTR_FAINT; + break; + case 3: + term.c.attr.mode |= ATTR_ITALIC; + break; + case 4: + term.c.attr.mode |= ATTR_UNDERLINE; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= ATTR_BLINK; + break; + case 7: + term.c.attr.mode |= ATTR_REVERSE; + break; + case 8: + term.c.attr.mode |= ATTR_INVISIBLE; + break; + case 9: + term.c.attr.mode |= ATTR_STRUCK; + break; + case 22: + term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT); + break; + case 23: + term.c.attr.mode &= ~ATTR_ITALIC; + break; + case 24: + term.c.attr.mode &= ~ATTR_UNDERLINE; + break; + case 25: + term.c.attr.mode &= ~ATTR_BLINK; + break; + case 27: + term.c.attr.mode &= ~ATTR_REVERSE; + break; + case 28: + term.c.attr.mode &= ~ATTR_INVISIBLE; + break; + case 29: + term.c.attr.mode &= ~ATTR_STRUCK; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, const int *args, int narg) +{ + int alt; const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, MODE_APPCURSOR); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, MODE_REVERSE); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, MODE_WRAP); + break; + case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ + case 12: /* att610 -- Start blinking cursor (IGNORED) */ + break; + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, MODE_HIDE); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEX10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEBTN); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMOTION); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, MODE_MOUSE); + xsetmode(set, MODE_MOUSEMANY); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, MODE_FOCUS); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, MODE_MOUSESGR); + break; + case 1034: + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(MODE_ALTSCREEN); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, MODE_KBDLOCK); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, MODE_INSERT); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, MODE_CRLF); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* die(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); + break; + case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); + break; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~MODE_PRINT; + break; + case 5: + term.mode |= MODE_PRINT; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1); + } + break; + case 1: /* above */ + if (term.c.y > 1) + tclearregion(0, 0, term.col-1, term.c.y-1); + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(csiescseq.arg[0], 1); + tmoveato(term.c.x, csiescseq.arg[0]-1); + break; + case 'h': /* SM -- Set terminal mode */ + tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR -- Device Status Report */ + switch (csiescseq.arg[0]) { + case 5: /* Status Report "OK" `0n` */ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + break; + default: + goto unknown; + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CURSOR_SAVE); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CURSOR_LOAD); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +osc_color_response(int num, int index, int is_osc4) +{ + int n; + char buf[32]; + unsigned char r, g, b; + + if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) { + fprintf(stderr, "erresc: failed to fetch %s color %d\n", + is_osc4 ? "osc4" : "osc", + is_osc4 ? num : index); + return; + } + + n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007", + is_osc4 ? "4;" : "", num, r, r, g, g, b, b); + if (n < 0 || n >= sizeof(buf)) { + fprintf(stderr, "error: %s while printing %s response\n", + n < 0 ? "snprintf failed" : "truncation occurred", + is_osc4 ? "osc4" : "osc"); + } else { + ttywrite(buf, n, 1); + } +} + +void +strhandle(void) +{ + char *p = NULL, *dec; + int j, narg, par; + const struct { int idx; char *str; } osc_table[] = { + { defaultfg, "foreground" }, + { defaultbg, "background" }, + { defaultcs, "cursor" } + }; + + term.esc &= ~(ESC_STR_END|ESC_STR); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + if (narg > 1) { + xsettitle(strescseq.args[1]); + xseticontitle(strescseq.args[1]); + } + return; + case 1: + if (narg > 1) + xseticontitle(strescseq.args[1]); + return; + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 10: + case 11: + case 12: + if (narg < 2) + break; + p = strescseq.args[1]; + if ((j = par - 10) < 0 || j >= LEN(osc_table)) + break; /* shouldn't be possible */ + + if (!strcmp(p, "?")) { + osc_color_response(par, osc_table[j].idx, 0); + } else if (xsetcolorname(osc_table[j].idx, p)) { + fprintf(stderr, "erresc: invalid %s color: %s\n", + osc_table[j].str, p); + } else { + tfulldirt(); + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + + if (p && !strcmp(p, "?")) { + osc_color_response(j, 0, 1); + } else if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) { + xloadcols(); + return; /* color reset without parameter */ + } + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + tfulldirt(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(const Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(const Arg *arg) +{ + term.mode ^= MODE_PRINT; +} + +void +printscreen(const Arg *arg) +{ + tdump(); +} + +void +printsel(const Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTF_SIZ]; + const Glyph *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8encode(bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CS_GRAPHIC0, CS_USA}; + char *p; + + if ((p = strchr(cs, ascii)) == NULL) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strreset(); + strescseq.type = c; + term.esc |= ESC_STR; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(MODE_CRLF)); + return; + case '\a': /* BEL */ + if (term.esc & ESC_STR_END) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST); + term.esc |= ESC_START; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(ESC_STR_END|ESC_STR); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= ESC_CSI; + return 0; + case '#': + term.esc |= ESC_TEST; + return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= ESC_ALTCHARSET; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + xsetmode(0, MODE_HIDE); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, MODE_APPKEYPAD); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, MODE_APPKEYPAD); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CURSOR_SAVE); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CURSOR_LOAD); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & ESC_STR_END) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(Rune u) +{ + char c[UTF_SIZ]; + int control; + int width, len; + Glyph *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(MODE_UTF8)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) + width = 1; + } + + if (IS_SET(MODE_PRINT)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if (term.esc & ESC_STR) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(ESC_START|ESC_STR); + term.esc |= ESC_STR_END; + goto check_control_code; + } + + if (strescseq.len+len >= strescseq.siz) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ + if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if (control) { + /* in UTF-8 mode ignore handling C1 control characters */ + if (IS_SET(MODE_UTF8) && ISCONTROLC1(u)) + return; + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if (term.esc & ESC_START) { + if (term.esc & ESC_CSI) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); + } else if (term.esc & ESC_ALTCHARSET) { + tdeftran(u); + } else if (term.esc & ESC_TEST) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + if (selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { + gp->mode |= ATTR_WRAP; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph)); + gp->mode &= ~ATTR_WIDE; + } + + if (term.c.x+width > term.col) { + if (IS_SET(MODE_WRAP)) + tnewline(1); + else + tmoveto(term.col - width, term.c.y); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if (width == 2) { + gp->mode |= ATTR_WIDE; + if (term.c.x+1 < term.col) { + if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) { + gp[2].u = ' '; + gp[2].mode &= ~ATTR_WDUMMY; + } + gp[1].u = '\0'; + gp[1].mode = ATTR_WDUMMY; + } + } + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { + term.c.state |= CURSOR_WRAPNEXT; + } +} + +int +twrite(const char *buf, int buflen, int show_ctrl) +{ + int charsize; + Rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if (IS_SET(MODE_UTF8)) { + /* process a complete utf8 char */ + charsize = utf8decode(buf + n, &u, buflen - n); + if (charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if (show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + TCursor c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not NULL */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Line)); + memmove(term.alt, term.alt + i, row * sizeof(Line)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Line)); + term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Glyph)); + term.alt[i] = xmalloc(col * sizeof(Glyph)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + term.col = col; + term.row = row; + /* reset scrolling region */ + tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CURSOR_LOAD); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(NULL); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) + term.ocx--; + if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx]); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/std/st.h b/std/st.h new file mode 100644 index 0000000..fd3b0d8 --- /dev/null +++ b/std/st.h @@ -0,0 +1,126 @@ +/* See LICENSE for license details. */ + +#include +#include + +/* macros */ +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) + +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_BLINK = 1 << 4, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +enum selection_mode { + SEL_IDLE = 0, + SEL_EMPTY = 1, + SEL_READY = 2 +}; + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +enum selection_snap { + SNAP_WORD = 1, + SNAP_LINE = 2 +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ +typedef struct { + Rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + +typedef Glyph *Line; + +typedef union { + int i; + uint ui; + float f; + const void *v; + const char *s; +} Arg; + +void die(const char *, ...); +void redraw(void); +void draw(void); + +void printscreen(const Arg *); +void printsel(const Arg *); +void sendbreak(const Arg *); +void toggleprinter(const Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(const char *, char *, const char *, char **); +size_t ttyread(void); +void ttyresize(int, int); +void ttywrite(const char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +size_t utf8encode(Rune, char *); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar_t *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern unsigned int tabspaces; +extern unsigned int defaultfg; +extern unsigned int defaultbg; +extern unsigned int defaultcs; diff --git a/std/st.info b/std/st.info new file mode 100644 index 0000000..efab2cf --- /dev/null +++ b/std/st.info @@ -0,0 +1,243 @@ +st-mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#8, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, + BE=\E[?2004h, + BD=\E[?2004l, + PS=\E[200~, + PE=\E[201~, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +st| simpleterm, + use=st-mono, + colors#8, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +st-256color| simpleterm with 256 colors, + use=st, + ccc, + colors#256, + oc=\E]104\007, + pairs#32767, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +st-meta| simpleterm with meta key, + use=st, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-meta-256color| simpleterm with meta key and 256 colors, + use=st-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +st-bs| simpleterm with backspace as backspace, + use=st, + kbs=\010, + kdch1=\177, + +st-bs-256color| simpleterm with backspace as backspace and 256colors, + use=st-256color, + kbs=\010, + kdch1=\177, diff --git a/std/st.o b/std/st.o new file mode 100644 index 0000000000000000000000000000000000000000..6a8f452ee8f8be2b21521eeedfa951bd1ac4f51a GIT binary patch literal 78304 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 KQK|l`to{dJ>BF}G literal 0 HcmV?d00001 diff --git a/std/win.h b/std/win.h new file mode 100644 index 0000000..6de960d --- /dev/null +++ b/std/win.h @@ -0,0 +1,41 @@ +/* See LICENSE for license details. */ + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 13, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, +}; + +void xbell(void); +void xclipcopy(void); +void xdrawcursor(int, int, Glyph, int, int, Glyph); +void xdrawline(Line, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +int xsetcolorname(int, const char *); +int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); +void xseticontitle(char *); +void xsettitle(char *); +int xsetcursor(int); +void xsetmode(int, unsigned int); +void xsetpointermotion(int); +void xsetsel(char *); +int xstartdraw(void); +void xximspot(int, int); diff --git a/std/x.c b/std/x.c new file mode 100644 index 0000000..b36fb8c --- /dev/null +++ b/std/x.c @@ -0,0 +1,2099 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +static void xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; + } else { + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { + /* MODE_MOUSEX10: no button release reporting */ + if (IS_SET(MODE_MOUSEX10)) + return; + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) + return; + } + code = 0; + } + + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + + if (!IS_SET(MODE_MOUSEX10)) { + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + code, x+1, y+1, + e->type == ButtonRelease ? 'm' : 'M'); + } else if (x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; + sizeh->height_inc = win.ch; + sizeh->width_inc = win.cw; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = XRootWindow(xw.dpy, xw.scr); + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +{ + float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + rune = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ + if (mode == ATTR_WDUMMY) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); + if (glyphidx) { + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for (f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); + /* Everything correct. */ + if (glyphidx && frc[f].flags == frcflags) + break; + /* We got a default font for a not found glyph. */ + if (!glyphidx && frc[f].flags == frcflags + && frc[f].unicodep == rune) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if (f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + * + * Xft and fontconfig are design failures. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, rune); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + die("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = rune; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +{ + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + win.ch + + ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + } + if (winx + width >= borderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, borderpx); + if (winy + win.ch >= borderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0; + r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & ATTR_UNDERLINE) { + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, + width, 1); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) +{ + Color drawcol; + + /* remove the old cursor */ + if (selected(ox, oy)) + og.mode ^= ATTR_REVERSE; + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* FALLTHROUGH */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xseticontitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop) != Success) + return; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +xdrawline(Line line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Glyph base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == ATTR_WDUMMY) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + } + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && tattrset(ATTR_BLINK)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +} diff --git a/std/x.o b/std/x.o new file mode 100644 index 0000000000000000000000000000000000000000..8012ab90a2732dd276fc7f094247ba878b0fe1f3 GIT binary patch literal 74800 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