commit 0a6cf8fe09b18fc01727850b9b2fffc5a903f5bb
parent da3ef1f82465f4e27483d2b4a0e66ffbb13ac4a6
Author: grouse <bdmfegys@duck.com>
Date: Sat, 2 Dec 2023 23:44:05 -0500
removed patches line from .gitignore, and added extras folder with patch for vanilla dwm
Diffstat:
2 files changed, 6314 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
@@ -1,7 +1,7 @@
*.o
dwm
config.h
-*.patch
+#*.patch
*.orig
*.rej
patches
diff --git a/extras/birdwm.patch b/extras/birdwm.patch
@@ -0,0 +1,6313 @@
+commit 6f06d6d3c1d694fbae0f5587fff5ca761843429b
+Author: grouse <bdmfegys@duck.com>
+Date: Sat Dec 2 23:40:19 2023 -0500
+
+ birdwm patch adds multiple patches to vanilla 6.4 dwm
+
+diff --git a/Makefile b/Makefile
+index ffa69b4..4e8e249 100644
+--- a/Makefile
++++ b/Makefile
+@@ -6,7 +6,13 @@ include config.mk
+ SRC = drw.c dwm.c util.c
+ OBJ = ${SRC:.c=.o}
+
+-all: dwm
++all: options dwm
++
++options:
++ @echo dwm build options:
++ @echo "CFLAGS = ${CFLAGS}"
++ @echo "LDFLAGS = ${LDFLAGS}"
++ @echo "CC = ${CC}"
+
+ .c.o:
+ ${CC} -c ${CFLAGS} $<
+@@ -20,7 +26,7 @@ dwm: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+ clean:
+- rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz
++ rm -f config.h ./*.rej ./*.orig dwm ${OBJ} dwm-${VERSION}.tar.gz
+
+ dist: clean
+ mkdir -p dwm-${VERSION}
+@@ -37,9 +43,11 @@ install: all
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1
+ chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1
++ mkdir -p /usr/share/xsessions
++ cp -f dwm.desktop /usr/share/xsessions/dwm.desktop
+
+ uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/dwm\
+ ${DESTDIR}${MANPREFIX}/man1/dwm.1
+
+-.PHONY: all clean dist install uninstall
++.PHONY: all options clean dist install uninstall
+diff --git a/README b/README
+index 95d4fd0..cc076c4 100644
+--- a/README
++++ b/README
+@@ -1,36 +1,46 @@
+-dwm - dynamic window manager
++birdwm - dynamic window manager for birds
+ ============================
+-dwm is an extremely fast, small, and dynamic window manager for X.
++ _ _ _
++| |__ (_)_ __ __| |_ ___ __ ___
++| '_ \| | '__/ _` \ \ /\ / / '_ ` _ \
++| |_) | | | | (_| |\ V V /| | | | | |
++|_.__/|_|_| \__,_| \_/\_/ |_| |_| |_|
++
++This is my dwm fork.
++It includes my slstatus configs and it depends on some of my scripts included in my dotfiles.
++For more info see https://git.tetraonini.online/grouse/dotfiles
++
++birdwm is an extremely fast, small, and dynamic window manager for birds.
+
+
+ Requirements
+ ------------
+-In order to build dwm you need the Xlib header files.
++In order to build birdwm you need the Xlib header files.
+
+
+ Installation
+ ------------
+-Edit config.mk to match your local setup (dwm is installed into
++Edit config.mk to match your local setup (birdwm is installed into
+ the /usr/local namespace by default).
+
+-Afterwards enter the following command to build and install dwm (if
++Afterwards enter the following command to build and install birdwm (if
+ necessary as root):
+
+ make clean install
+
+
+-Running dwm
++Running birdwm
+ -----------
+-Add the following line to your .xinitrc to start dwm using startx:
++Add the following line to your .xinitrc to start birdwm using startx:
+
+ exec dwm
+
+-In order to connect dwm to a specific display, make sure that
++In order to connect birdwm 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.)
++(This will start birdwm 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:
+@@ -44,5 +54,5 @@ like this in your .xinitrc:
+
+ Configuration
+ -------------
+-The configuration of dwm is done by creating a custom config.h
++The configuration of birdwm is done by creating a custom config.h
+ and (re)compiling the source code.
+diff --git a/config.def.h b/config.def.h
+index 9efa774..7377bf7 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -1,40 +1,84 @@
+ /* See LICENSE file for copyright and license details. */
+-
++#include <X11/XF86keysym.h>
+ /* appearance */
+ static const unsigned int borderpx = 1; /* border pixel of windows */
++static const unsigned int gappx = 7; /* 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=10" };
+-static const char dmenufont[] = "monospace:size=10";
+-static const char col_gray1[] = "#222222";
+-static const char col_gray2[] = "#444444";
+-static const char col_gray3[] = "#bbbbbb";
+-static const char col_gray4[] = "#eeeeee";
+-static const char col_cyan[] = "#005577";
+-static const char *colors[][3] = {
+- /* fg bg border */
+- [SchemeNorm] = { col_gray3, col_gray1, col_gray2 },
+- [SchemeSel] = { col_gray4, col_cyan, col_cyan },
++#define ICONSIZE 16 /* icon size */
++#define ICONSPACING 5 /* space between icon and title */
++static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */
++static const unsigned int systrayspacing = 2; /* systray spacing */
++static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/
++static const int showsystray = 1; /* 0 means no systray */
++static const char *fonts[] = { "Inconsolata-ExpandedBold:pixelsize=22" };
++static const char dmenufont[] = "Inconsolata-ExpandedBold:pixelsize=22";
++static const unsigned int baralpha = 0x70;
++static const unsigned int borderalpha = OPAQUE;
++static char normbgcolor[] = "#222222";
++static char normbordercolor[] = "#444444";
++static char normfgcolor[] = "#bbbbbb";
++static char selfgcolor[] = "#eeeeee";
++static char selbordercolor[] = "#005577";
++static char selbgcolor[] = "#005577";
++static char *colors[][3] = {
++ /* fg bg border */
++ [SchemeNorm] = { normfgcolor, normbgcolor, normbordercolor },
++ [SchemeSel] = { selfgcolor, selbgcolor, selbordercolor },
++};
++typedef struct {
++ const char *name;
++ const void *cmd;
++} Sp;
++const char *spcmd1[] = {"st", "-n", "spterm", "-g", "144x41", NULL };
++const char *spcmd2[] = {"st", "-n", "spfm", "-g", "144x41", "-e", "ranger", NULL };
++const char *spcmd3[] = {"st", "-n", "upgrader", "-g", "144x41", "-e", "upgrader", NULL };
++static Sp scratchpads[] = {
++ /* name cmd */
++ {"spterm", spcmd1},
++ {"spranger", spcmd2},
++ {"upgrader", spcmd3},
+ };
+
+-/* tagging */
+-static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
++static const unsigned int alphas[][3] = {
++ /* fg bg border*/
++ [SchemeNorm] = { OPAQUE, baralpha, borderalpha },
++ [SchemeSel] = { OPAQUE, baralpha, borderalpha },
++};
+
++/* tagging */
++static const char *tags[] = { "", "", "", "", "", "", };
+ 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 },
++ { "Gimp", NULL, NULL, 0, 1, -1 }, // gimp menu floating rule
++/* scratchpad floating rules */
++ { NULL, "spterm", NULL, SPTAG(0), 1, -1 }, // scratchpad floating rule
++ { NULL, "spfm", NULL, SPTAG(1), 1, -1 }, // suckless file manager command
++ { NULL, "upgrader", NULL, SPTAG(2), 1, -1 }, // upgrade script command floating
++// { "Alacritty","Alacritty",NULL, 0, 1, -1 }, // alacritty
++/* end of scratchpad floating rules */
++ { "widget", NULL, NULL, 0, 1, -1 }, // used sometimes, for using sww widgets
++ { "python3", NULL, NULL, 0, 1, -1 }, // i cant remember what this is tbh
++ { NULL, "Toolkit", NULL, 0, 1, -1 }, // firefox floating media player floating rule
++ { "steamwebhelper","steamwebhelper",NULL,0, 1, -1 }, // just to make the steam menus float
++/* all rules below this start on the second monitor */
++ { "VencordDesktop", "vencorddesktop", NULL, 1 << 4, 0, 0 }, // starts discord on the discord icon tag
++ { "Cider", "cider", NULL, 1 << 3, 0, 0 }, // starts cider on the music tag
++ { "VSCodium","vscodium", NULL, 1 << 1, 0, 0 }, // starts vscodium by default on the cli/code tag
++ { "tutanota-desktop","tutanota-desktop",NULL,1 << 5,0, 0 }, // starts tutanota-desktop on the mail tag
++ { "Element", "element", NULL, 1 << 5, 0, 0 }, // starts element on the chat tag
++ { "Signal", "signal", NULL, 1 << 2, 0, 0 }, // starts element on the chat tag
+ };
+
+ /* 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 resizehints = 0; /* 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[] = {
+@@ -57,13 +101,31 @@ static const Layout layouts[] = {
+
+ /* 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 char *dmenucmd[] = { "dmenu_run", "-i", NULL };
++static const char *termcmd[] = { "kitty", NULL };
++//static const char *wallpaper[] = { "nsxiv", "-t", "/home/tetraonini/Pictures/wallpapers/*.png", NULL };
++//static const char *wallpapergif[] = { "nsxiv", "-t", "/home/tetraonini/Pictures/wallpapers/gif/*.gif", NULL };
++static const char *wallpaper[] = { "xwallpaper" };
++static const char *wallpapergif[] = { "xwallpapergif" };
++static const char *restarter[] = { "restarter", NULL};
++static const char *volup[] = { "volume.sh", "up", NULL};
++static const char *voldown[] = { "volume.sh", "down", NULL};
++static const char *volmute[] = { "volume.sh", "mute", NULL};
++static const char *firefox[] = { "librewolf", NULL};
++
+
++#include "exitdwm.c"
+ static const Key keys[] = {
+ /* modifier key function argument */
+ { MODKEY, XK_p, spawn, {.v = dmenucmd } },
+ { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } },
++ { MODKEY, XK_w, spawn, {.v = wallpaper } },
++ { MODKEY, XK_g, spawn, {.v = wallpapergif } },
++ { MODKEY|ShiftMask, XK_j, spawn, {.v = restarter } },
++ { MODKEY, XK_f, spawn, {.v = firefox } },
++ { 0, XF86XK_AudioRaiseVolume, spawn, {.v = volup} },
++ { 0, XF86XK_AudioLowerVolume, spawn, {.v = voldown} },
++ { 0, XF86XK_AudioMute, spawn, {.v = volmute} },
+ { MODKEY, XK_b, togglebar, {0} },
+ { MODKEY, XK_j, focusstack, {.i = +1 } },
+ { MODKEY, XK_k, focusstack, {.i = -1 } },
+@@ -75,7 +137,6 @@ static const Key keys[] = {
+ { 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} },
+@@ -85,6 +146,10 @@ 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_F5, xrdb, {.v = NULL } },
++ { MODKEY, XK_y, togglescratch, {.ui = 0 } },
++ { MODKEY, XK_u, togglescratch, {.ui = 1 } },
++ { MODKEY|ShiftMask, XK_u, togglescratch, {.ui = 2 } },
+ TAGKEYS( XK_1, 0)
+ TAGKEYS( XK_2, 1)
+ TAGKEYS( XK_3, 2)
+@@ -94,7 +159,10 @@ static const Key keys[] = {
+ TAGKEYS( XK_7, 6)
+ TAGKEYS( XK_8, 7)
+ TAGKEYS( XK_9, 8)
+- { MODKEY|ShiftMask, XK_q, quit, {0} },
++// following line is commented out due to exitmenu patch
++// { MODKEY|ShiftMask, XK_q, quit, {0} },
++ { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} },
++ { MODKEY|ShiftMask, XK_e, exitdwm, {0} },
+ };
+
+ /* button definitions */
+@@ -107,7 +175,7 @@ static const Button buttons[] = {
+ { ClkStatusText, 0, Button2, spawn, {.v = termcmd } },
+ { ClkClientWin, MODKEY, Button1, movemouse, {0} },
+ { ClkClientWin, MODKEY, Button2, togglefloating, {0} },
+- { ClkClientWin, MODKEY, Button3, resizemouse, {0} },
++ { ClkClientWin, MODKEY, Button1, resizemouse, {0} },
+ { ClkTagBar, 0, Button1, view, {0} },
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+diff --git a/config.mk b/config.mk
+index ba64d3d..886f060 100644
+--- a/config.mk
++++ b/config.mk
+@@ -23,7 +23,7 @@ FREETYPEINC = /usr/include/freetype2
+
+ # includes and libs
+ INCS = -I${X11INC} -I${FREETYPEINC}
+-LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS}
++LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2
+
+ # flags
+ CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
+diff --git a/drw.c b/drw.c
+index a58a2b4..effb137 100644
+--- a/drw.c
++++ b/drw.c
+@@ -4,6 +4,7 @@
+ #include <string.h>
+ #include <X11/Xlib.h>
+ #include <X11/Xft/Xft.h>
++#include <Imlib2.h>
+
+ #include "drw.h"
+ #include "util.h"
+@@ -61,7 +62,7 @@ utf8decode(const char *c, long *u, size_t clen)
+ }
+
+ Drw *
+-drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
++drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap)
+ {
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+@@ -70,8 +71,12 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
+ 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);
++ drw->visual = visual;
++ drw->depth = depth;
++ drw->cmap = cmap;
++ drw->drawable = XCreatePixmap(dpy, root, w, h, depth);
++ drw->picture = XRenderCreatePicture(dpy, drw->drawable, XRenderFindVisualFormat(dpy, visual), 0, NULL);
++ drw->gc = XCreateGC(dpy, drw->drawable, 0, NULL);
+ XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
+
+ return drw;
+@@ -85,14 +90,18 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h)
+
+ drw->w = w;
+ drw->h = h;
++ if (drw->picture)
++ XRenderFreePicture(drw->dpy, drw->picture);
+ if (drw->drawable)
+ XFreePixmap(drw->dpy, drw->drawable);
+- drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
++ drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, drw->depth);
++ drw->picture = XRenderCreatePicture(drw->dpy, drw->drawable, XRenderFindVisualFormat(drw->dpy, drw->visual), 0, NULL);
+ }
+
+ void
+ drw_free(Drw *drw)
+ {
++ XRenderFreePicture(drw->dpy, drw->picture);
+ XFreePixmap(drw->dpy, drw->drawable);
+ XFreeGC(drw->dpy, drw->gc);
+ drw_fontset_free(drw->fonts);
+@@ -181,21 +190,22 @@ drw_fontset_free(Fnt *font)
+ }
+
+ void
+-drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
++drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha)
+ {
+ if (!drw || !dest || !clrname)
+ return;
+
+- if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
+- DefaultColormap(drw->dpy, drw->screen),
++ if (!XftColorAllocName(drw->dpy, drw->visual, drw->cmap,
+ clrname, dest))
+ die("error, cannot allocate color '%s'", clrname);
++
++ dest->pixel = (dest->pixel & 0x00ffffffU) | (alpha << 24);
+ }
+
+ /* 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)
++drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount)
+ {
+ size_t i;
+ Clr *ret;
+@@ -205,7 +215,7 @@ drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+- drw_clr_create(drw, &ret[i], clrnames[i]);
++ drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]);
+ return ret;
+ }
+
+@@ -223,6 +233,67 @@ drw_setscheme(Drw *drw, Clr *scm)
+ drw->scheme = scm;
+ }
+
++Picture
++drw_picture_create_resized(Drw *drw, char *src, unsigned int srcw, unsigned int srch, unsigned int dstw, unsigned int dsth) {
++ Pixmap pm;
++ Picture pic;
++ GC gc;
++
++ if (srcw <= (dstw << 1u) && srch <= (dsth << 1u)) {
++ XImage img = {
++ srcw, srch, 0, ZPixmap, src,
++ ImageByteOrder(drw->dpy), BitmapUnit(drw->dpy), BitmapBitOrder(drw->dpy), 32,
++ 32, 0, 32,
++ 0, 0, 0
++ };
++ XInitImage(&img);
++
++ pm = XCreatePixmap(drw->dpy, drw->root, srcw, srch, 32);
++ gc = XCreateGC(drw->dpy, pm, 0, NULL);
++ XPutImage(drw->dpy, pm, gc, &img, 0, 0, 0, 0, srcw, srch);
++ XFreeGC(drw->dpy, gc);
++
++ pic = XRenderCreatePicture(drw->dpy, pm, XRenderFindStandardFormat(drw->dpy, PictStandardARGB32), 0, NULL);
++ XFreePixmap(drw->dpy, pm);
++
++ XRenderSetPictureFilter(drw->dpy, pic, FilterBilinear, NULL, 0);
++ XTransform xf;
++ xf.matrix[0][0] = (srcw << 16u) / dstw; xf.matrix[0][1] = 0; xf.matrix[0][2] = 0;
++ xf.matrix[1][0] = 0; xf.matrix[1][1] = (srch << 16u) / dsth; xf.matrix[1][2] = 0;
++ xf.matrix[2][0] = 0; xf.matrix[2][1] = 0; xf.matrix[2][2] = 65536;
++ XRenderSetPictureTransform(drw->dpy, pic, &xf);
++ } else {
++ Imlib_Image origin = imlib_create_image_using_data(srcw, srch, (DATA32 *)src);
++ if (!origin) return None;
++ imlib_context_set_image(origin);
++ imlib_image_set_has_alpha(1);
++ Imlib_Image scaled = imlib_create_cropped_scaled_image(0, 0, srcw, srch, dstw, dsth);
++ imlib_free_image_and_decache();
++ if (!scaled) return None;
++ imlib_context_set_image(scaled);
++ imlib_image_set_has_alpha(1);
++
++ XImage img = {
++ dstw, dsth, 0, ZPixmap, (char *)imlib_image_get_data_for_reading_only(),
++ ImageByteOrder(drw->dpy), BitmapUnit(drw->dpy), BitmapBitOrder(drw->dpy), 32,
++ 32, 0, 32,
++ 0, 0, 0
++ };
++ XInitImage(&img);
++
++ pm = XCreatePixmap(drw->dpy, drw->root, dstw, dsth, 32);
++ gc = XCreateGC(drw->dpy, pm, 0, NULL);
++ XPutImage(drw->dpy, pm, gc, &img, 0, 0, 0, 0, dstw, dsth);
++ imlib_free_image_and_decache();
++ XFreeGC(drw->dpy, gc);
++
++ pic = XRenderCreatePicture(drw->dpy, pm, XRenderFindStandardFormat(drw->dpy, PictStandardARGB32), 0, NULL);
++ XFreePixmap(drw->dpy, pm);
++ }
++
++ return pic;
++}
++
+ void
+ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
+ {
+@@ -263,9 +334,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
+ } 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));
++ d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap);
+ x += lpad;
+ w -= lpad;
+ }
+@@ -384,6 +453,14 @@ no_match:
+ return x + (render ? w : 0);
+ }
+
++void
++drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic)
++{
++ if (!drw)
++ return;
++ XRenderComposite(drw->dpy, PictOpOver, pic, None, drw->picture, 0, 0, 0, 0, x, y, w, h);
++}
++
+ void
+ drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+ {
+diff --git a/drw.h b/drw.h
+index 6471431..f32a93b 100644
+--- a/drw.h
++++ b/drw.h
+@@ -20,14 +20,18 @@ typedef struct {
+ Display *dpy;
+ int screen;
+ Window root;
++ Visual *visual;
++ unsigned int depth;
++ Colormap cmap;
+ Drawable drawable;
++ Picture picture;
+ GC gc;
+ Clr *scheme;
+ Fnt *fonts;
+ } Drw;
+
+ /* Drawable abstraction */
+-Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
++Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap);
+ void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+ void drw_free(Drw *drw);
+
+@@ -39,8 +43,8 @@ unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int
+ 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);
++void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha);
++Clr *drw_scm_create(Drw *drw, char *clrnames[], const unsigned int alphas[], size_t clrcount);
+
+ /* Cursor abstraction */
+ Cur *drw_cur_create(Drw *drw, int shape);
+@@ -50,9 +54,12 @@ void drw_cur_free(Drw *drw, Cur *cursor);
+ void drw_setfontset(Drw *drw, Fnt *set);
+ void drw_setscheme(Drw *drw, Clr *scm);
+
++Picture drw_picture_create_resized(Drw *drw, char *src, unsigned int src_w, unsigned int src_h, unsigned int dst_w, unsigned int dst_h);
++
+ /* 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);
++void drw_pic(Drw *drw, int x, int y, unsigned int w, unsigned int h, Picture pic);
+
+ /* Map functions */
+ void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
+diff --git a/dwm.1 b/dwm.1
+index ddc8321..02a415f 100644
+--- a/dwm.1
++++ b/dwm.1
+@@ -1,11 +1,11 @@
+-.TH DWM 1 dwm\-VERSION
++.TH DWM 1 birdwm\-VERSION
+ .SH NAME
+-dwm \- dynamic window manager
++birdwm \- dynamic window manager for birds
+ .SH SYNOPSIS
+ .B dwm
+ .RB [ \-v ]
+ .SH DESCRIPTION
+-dwm is a dynamic window manager for X. It manages windows in tiled, monocle
++birdwm is a dynamic window manager for birds. It manages windows in tiled, monocle
+ and floating layouts. Either layout can be applied dynamically, optimising the
+ environment for the application in use and the task performed.
+ .P
+@@ -29,7 +29,15 @@ color. The tags of the focused window are indicated with a filled square in the
+ top left corner. The tags which are applied to one or more windows are
+ indicated with an empty square in the top left corner.
+ .P
+-dwm draws a small border around windows to indicate the focus state.
++birdwm draws a small border around windows to indicate the focus state.
++.P
++On start, birdwm can start additional programs that may be specified in two special
++shell scripts (see the FILES section below), autostart_blocking.sh and
++autostart.sh. The former is executed first and birdwm will wait for its
++termination before starting. The latter is executed in the background before
++birdwm enters its handler loop.
++.P
++Either of these files may be omitted.
+ .SH OPTIONS
+ .TP
+ .B \-v
+@@ -141,7 +149,10 @@ View all windows with any tag.
+ Add/remove all windows with nth tag to/from the view.
+ .TP
+ .B Mod1\-Shift\-q
+-Quit dwm.
++Quit birdwm.
++.TP
++.B Mod1\-Control\-Shift\-q
++Restart birdwm.
+ .SS Mouse commands
+ .TP
+ .B Mod1\-Button1
+@@ -152,9 +163,31 @@ Toggles focused window between floating and tiled state.
+ .TP
+ .B Mod1\-Button3
+ Resize focused window while dragging. Tiled windows will be toggled to the floating state.
++.SH FILES
++The files containing programs to be started along with birdwm are searched for in
++the following directories:
++.IP "1. $XDG_DATA_HOME/birdwm"
++.IP "2. $HOME/.local/share/birdwm"
++.IP "3. $HOME/.birdwm"
++.P
++The first existing directory is scanned for any of the autostart files below.
++.TP 15
++autostart.sh
++This file is started as a shell background process before birdwm enters its handler
++loop.
++.TP 15
++autostart_blocking.sh
++This file is started before any autostart.sh; birdwm waits for its termination.
+ .SH CUSTOMIZATION
+-dwm is customized by creating a custom config.h and (re)compiling the source
++birdwm is customized by creating a custom config.h and (re)compiling the source
+ code. This keeps it fast, secure and simple.
++.SH SIGNALS
++.TP
++.B SIGHUP - 1
++Restart the dwm process.
++.TP
++.B SIGTERM - 15
++Cleanly terminate the dwm process.
+ .SH SEE ALSO
+ .BR dmenu (1),
+ .BR st (1)
+diff --git a/dwm.c b/dwm.c
+index f1d86b2..9ca208a 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -17,9 +17,9 @@
+ * client.
+ *
+ * Keys and tagging rules are organized as arrays and defined in config.h.
+- *
+ * To understand everything else, start reading main().
+ */
++#include <X11/XF86keysym.h>
+ #include <errno.h>
+ #include <locale.h>
+ #include <signal.h>
+@@ -28,13 +28,17 @@
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
++#include <limits.h>
++#include <stdint.h>
+ #include <sys/types.h>
++#include <sys/stat.h>
+ #include <sys/wait.h>
+ #include <X11/cursorfont.h>
+ #include <X11/keysym.h>
+ #include <X11/Xatom.h>
+ #include <X11/Xlib.h>
+ #include <X11/Xproto.h>
++#include <X11/Xresource.h>
+ #include <X11/Xutil.h>
+ #ifdef XINERAMA
+ #include <X11/extensions/Xinerama.h>
+@@ -52,17 +56,55 @@
+ #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 WIDTH(X) ((X)->w + 2 * (X)->bw + gappx)
++#define HEIGHT(X) ((X)->h + 2 * (X)->bw + gappx)
++#define NUMTAGS (LENGTH(tags) + LENGTH(scratchpads))
++#define TAGMASK ((1 << NUMTAGS) - 1)
++#define SPTAG(i) ((1 << LENGTH(tags)) << (i))
++#define SPTAGMASK (((1 << LENGTH(scratchpads))-1) << LENGTH(tags))
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
++#define XRDB_LOAD_COLOR(R,V) if (XrmGetResource(xrdb, R, NULL, &type, &value) == True) { \
++ if (value.addr != NULL && strnlen(value.addr, 8) == 7 && value.addr[0] == '#') { \
++ int i = 1; \
++ for (; i <= 6; i++) { \
++ if (value.addr[i] < 48) break; \
++ if (value.addr[i] > 57 && value.addr[i] < 65) break; \
++ if (value.addr[i] > 70 && value.addr[i] < 97) break; \
++ if (value.addr[i] > 102) break; \
++ } \
++ if (i == 7) { \
++ strncpy(V, value.addr, 7); \
++ V[7] = '\0'; \
++ } \
++ } \
++ }
++#define OPAQUE 0xffU
++
++#define SYSTEM_TRAY_REQUEST_DOCK 0
++#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
++
++/* XEMBED messages */
++#define XEMBED_EMBEDDED_NOTIFY 0
++#define XEMBED_WINDOW_ACTIVATE 1
++#define XEMBED_FOCUS_IN 4
++#define XEMBED_MODALITY_ON 10
++
++#define XEMBED_MAPPED (1 << 0)
++#define XEMBED_WINDOW_ACTIVATE 1
++#define XEMBED_WINDOW_DEACTIVATE 2
++
++#define VERSION_MAJOR 0
++#define VERSION_MINOR 0
++#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR
++
+ /* enums */
++enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */
+ 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 { NetSupported, NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayVisual,
++ NetWMName, NetWMIcon, NetWMState, NetWMFullscreen, NetActiveWindow, NetWMWindowType, NetWMWindowTypeDock,
++ NetSystemTrayOrientationHorz, NetWMWindowTypeDialog, NetClientList, NetWMCheck, NetLast }; /* EWMH atoms */
+ enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */
+ enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
+ ClkClientWin, ClkRootWin, ClkLast }; /* clicks */
+@@ -93,6 +135,7 @@ struct Client {
+ int bw, oldbw;
+ unsigned int tags;
+ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
++ unsigned int icw, ich; Picture icon;
+ Client *next;
+ Client *snext;
+ Monitor *mon;
+@@ -141,6 +184,12 @@ typedef struct {
+ int monitor;
+ } Rule;
+
++typedef struct Systray Systray;
++struct Systray {
++ Window win;
++ Client *icons;
++};
++
+ /* function declarations */
+ static void applyrules(Client *c);
+ static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact);
+@@ -170,14 +219,17 @@ 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 Picture geticonprop(Window w, unsigned int *icw, unsigned int *ich);
+ 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 unsigned int getsystraywidth();
+ 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 loadxrdb(void);
+ static void manage(Window w, XWindowAttributes *wa);
+ static void mappingnotify(XEvent *e);
+ static void maprequest(XEvent *e);
+@@ -192,10 +244,13 @@ 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 removesystrayicon(Client *i);
++static void resizerequest(XEvent *e);
+ static void restack(Monitor *m);
+ static void run(void);
++static void runautostart(void);
+ static void scan(void);
+-static int sendevent(Client *c, Atom proto);
++static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4);
+ static void sendmon(Client *c, Monitor *m);
+ static void setclientstate(Client *c, long state);
+ static void setfocus(Client *c);
+@@ -205,14 +260,19 @@ static void setmfact(const Arg *arg);
+ static void setup(void);
+ static void seturgent(Client *c, int urg);
+ static void showhide(Client *c);
++static void sighup(int unused);
++static void sigterm(int unused);
+ static void spawn(const Arg *arg);
++static Monitor *systraytomon(Monitor *m);
+ 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 togglescratch(const Arg *arg);
+ static void toggletag(const Arg *arg);
+ static void toggleview(const Arg *arg);
++static void freeicon(Client *c);
+ static void unfocus(Client *c, int setfocus);
+ static void unmanage(Client *c, int destroyed);
+ static void unmapnotify(XEvent *e);
+@@ -223,19 +283,30 @@ static int updategeom(void);
+ static void updatenumlockmask(void);
+ static void updatesizehints(Client *c);
+ static void updatestatus(void);
++static void updatesystray(int updatebar);
++static void updatesystrayicongeom(Client *i, int w, int h);
++static void updatesystrayiconstate(Client *i, XPropertyEvent *ev);
+ static void updatetitle(Client *c);
++static void updateicon(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 Client *wintosystrayicon(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 xinitvisual();
++static void xrdb(const Arg *arg);
+ static void zoom(const Arg *arg);
+
+ /* variables */
++static const char autostartblocksh[] = "autostart_blocking.sh";
++static const char autostartsh[] = "autostart.sh";
+ static const char broken[] = "broken";
++static const char dwmdir[] = "dwm";
++static const char localshare[] = ".local/share";
+ static char stext[256];
+ static int screen;
+ static int sw, sh; /* X display screen geometry width, height */
+@@ -257,9 +328,11 @@ static void (*handler[LASTEvent]) (XEvent *) = {
+ [MapRequest] = maprequest,
+ [MotionNotify] = motionnotify,
+ [PropertyNotify] = propertynotify,
++ [ResizeRequest] = resizerequest,
+ [UnmapNotify] = unmapnotify
+ };
+-static Atom wmatom[WMLast], netatom[NetLast];
++static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast];
++static int restart = 0;
+ static int running = 1;
+ static Cur *cursor[CurLast];
+ static Clr **scheme;
+@@ -268,6 +341,14 @@ static Drw *drw;
+ static Monitor *mons, *selmon;
+ static Window root, wmcheckwin;
+
++static Systray *systray = NULL;
++static unsigned long systrayorientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
++
++static int useargb = 0;
++static Visual *visual;
++static int depth;
++static Colormap cmap;
++
+ /* configuration, allows nested code to access above variables */
+ #include "config.h"
+
+@@ -299,6 +380,11 @@ applyrules(Client *c)
+ {
+ c->isfloating = r->isfloating;
+ c->tags |= r->tags;
++ if ((r->tags & SPTAGMASK) && r->isfloating) {
++ c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2);
++ c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2);
++ }
++
+ for (m = mons; m && m->num != r->monitor; m = m->next);
+ if (m)
+ c->mon = m;
+@@ -308,7 +394,7 @@ applyrules(Client *c)
+ 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];
++ c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : (c->mon->tagset[c->mon->seltags] & ~SPTAGMASK);
+ }
+
+ int
+@@ -441,7 +527,7 @@ buttonpress(XEvent *e)
+ arg.ui = 1 << i;
+ } else if (ev->x < x + TEXTW(selmon->ltsymbol))
+ click = ClkLtSymbol;
+- else if (ev->x > selmon->ww - (int)TEXTW(stext))
++ else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth())
+ click = ClkStatusText;
+ else
+ click = ClkWinTitle;
+@@ -484,6 +570,13 @@ cleanup(void)
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ while (mons)
+ cleanupmon(mons);
++ if (showsystray) {
++ while (systray->icons)
++ removesystrayicon(systray->icons);
++ XUnmapWindow(dpy, systray->win);
++ XDestroyWindow(dpy, systray->win);
++ free(systray);
++ }
+ for (i = 0; i < CurLast; i++)
+ drw_cur_free(drw, cursor[i]);
+ for (i = 0; i < LENGTH(colors); i++)
+@@ -515,9 +608,49 @@ cleanupmon(Monitor *mon)
+ void
+ clientmessage(XEvent *e)
+ {
++ XWindowAttributes wa;
++ XSetWindowAttributes swa;
+ XClientMessageEvent *cme = &e->xclient;
+ Client *c = wintoclient(cme->window);
+
++ if (showsystray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) {
++ /* add systray icons */
++ if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) {
++ if (!(c = (Client *)calloc(1, sizeof(Client))))
++ die("fatal: could not malloc() %u bytes\n", sizeof(Client));
++ if (!(c->win = cme->data.l[2])) {
++ free(c);
++ return;
++ }
++
++ c->mon = selmon;
++ c->next = systray->icons;
++ systray->icons = c;
++ XGetWindowAttributes(dpy, c->win, &wa);
++ c->x = c->oldx = c->y = c->oldy = 0;
++ c->w = c->oldw = wa.width;
++ c->h = c->oldh = wa.height;
++ c->oldbw = wa.border_width;
++ c->bw = 0;
++ c->isfloating = True;
++ /* reuse tags field as mapped status */
++ c->tags = 1;
++ updatesizehints(c);
++ updatesystrayicongeom(c, wa.width, wa.height);
++ XAddToSaveSet(dpy, c->win);
++ XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask);
++ XReparentWindow(dpy, c->win, systray->win, 0, 0);
++ /* use parents background color */
++ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
++ XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa);
++ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION);
++ XSync(dpy, False);
++ setclientstate(c, NormalState);
++ updatesystray(1);
++ }
++ return;
++ }
++
+ if (!c)
+ return;
+ if (cme->message_type == netatom[NetWMState]) {
+@@ -655,6 +788,10 @@ destroynotify(XEvent *e)
+
+ if ((c = wintoclient(ev->window)))
+ unmanage(c, 1);
++ else if (showsystray && (c = wintosystrayicon(ev->window))) {
++ removesystrayicon(c);
++ updatesystray(1);
++ }
+ }
+
+ void
+@@ -698,12 +835,18 @@ dirtomon(int dir)
+ void
+ drawbar(Monitor *m)
+ {
+- int x, w, tw = 0;
++ int x, w, tw = 0, stw = 0;
+ int boxs = drw->fonts->h / 9;
+ int boxw = drw->fonts->h / 6 + 2;
+ unsigned int i, occ = 0, urg = 0;
+ Client *c;
+
++ if (showsystray && m == systraytomon(m)) {
++ stw = getsystraywidth();
++ drw_setscheme(drw, scheme[SchemeNorm]);
++ drw_rect(drw, m->ww - stw, 0, stw, bh, 1, 1);
++ }
++
+ if (!m->showbar)
+ return;
+
+@@ -711,7 +854,7 @@ drawbar(Monitor *m)
+ 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);
++ drw_text(drw, m->ww - tw - stw, 0, tw, bh, 0, stext, 0);
+ }
+
+ for (c = m->clients; c; c = c->next) {
+@@ -734,10 +877,11 @@ drawbar(Monitor *m)
+ 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 ((w = m->ww - tw - stw - 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);
++ drw_text(drw, x, 0, w, bh, lrpad / 2 + (m->sel->icon ? m->sel->icw + ICONSPACING : 0), m->sel->name, 0);
++ if (m->sel->icon) drw_pic(drw, x + lrpad / 2, (bh - m->sel->ich) / 2, m->sel->icw, m->sel->ich, m->sel->icon);
+ if (m->sel->isfloating)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0);
+ } else {
+@@ -755,6 +899,9 @@ drawbars(void)
+
+ for (m = mons; m; m = m->next)
+ drawbar(m);
++
++ if (showsystray && !systraypinning)
++ updatesystray(0);
+ }
+
+ void
+@@ -782,8 +929,12 @@ expose(XEvent *e)
+ Monitor *m;
+ XExposeEvent *ev = &e->xexpose;
+
+- if (ev->count == 0 && (m = wintomon(ev->window)))
++ if (ev->count == 0 && (m = wintomon(ev->window))) {
+ drawbar(m);
++
++ if (showsystray && m == systraytomon(m))
++ updatesystray(0);
++ }
+ }
+
+ void
+@@ -833,6 +984,8 @@ focusmon(const Arg *arg)
+ unfocus(selmon->sel, 0);
+ selmon = m;
+ focus(NULL);
++ if (selmon->sel)
++ XWarpPointer(dpy, None, selmon->sel->win, 0, 0, 0, 0, selmon->sel->w/2, selmon->sel->h/2);
+ }
+
+ void
+@@ -858,6 +1011,7 @@ focusstack(const Arg *arg)
+ if (c) {
+ focus(c);
+ restack(selmon);
++ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w/2, c->h/2);
+ }
+ }
+
+@@ -869,14 +1023,83 @@ getatomprop(Client *c, Atom prop)
+ unsigned char *p = NULL;
+ Atom da, atom = None;
+
+- if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM,
++ /* FIXME getatomprop should return the number of items and a pointer to
++ * the stored data instead of this workaround */
++ Atom req = XA_ATOM;
++ if (prop == xatom[XembedInfo])
++ req = xatom[XembedInfo];
++
++ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req,
+ &da, &di, &dl, &dl, &p) == Success && p) {
+ atom = *(Atom *)p;
++ if (da == xatom[XembedInfo] && dl == 2)
++ atom = ((Atom *)p)[1];
+ XFree(p);
+ }
+ return atom;
+ }
+
++static uint32_t prealpha(uint32_t p) {
++ uint8_t a = p >> 24u;
++ uint32_t rb = (a * (p & 0xFF00FFu)) >> 8u;
++ uint32_t g = (a * (p & 0x00FF00u)) >> 8u;
++ return (rb & 0xFF00FFu) | (g & 0x00FF00u) | (a << 24u);
++}
++
++Picture
++geticonprop(Window win, unsigned int *picw, unsigned int *pich)
++{
++ int format;
++ unsigned long n, extra, *p = NULL;
++ Atom real;
++
++ if (XGetWindowProperty(dpy, win, netatom[NetWMIcon], 0L, LONG_MAX, False, AnyPropertyType,
++ &real, &format, &n, &extra, (unsigned char **)&p) != Success)
++ return None;
++ if (n == 0 || format != 32) { XFree(p); return None; }
++
++ unsigned long *bstp = NULL;
++ uint32_t w, h, sz;
++ {
++ unsigned long *i; const unsigned long *end = p + n;
++ uint32_t bstd = UINT32_MAX, d, m;
++ for (i = p; i < end - 1; i += sz) {
++ if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; }
++ if ((sz = w * h) > end - i) break;
++ if ((m = w > h ? w : h) >= ICONSIZE && (d = m - ICONSIZE) < bstd) { bstd = d; bstp = i; }
++ }
++ if (!bstp) {
++ for (i = p; i < end - 1; i += sz) {
++ if ((w = *i++) >= 16384 || (h = *i++) >= 16384) { XFree(p); return None; }
++ if ((sz = w * h) > end - i) break;
++ if ((d = ICONSIZE - (w > h ? w : h)) < bstd) { bstd = d; bstp = i; }
++ }
++ }
++ if (!bstp) { XFree(p); return None; }
++ }
++
++ if ((w = *(bstp - 2)) == 0 || (h = *(bstp - 1)) == 0) { XFree(p); return None; }
++
++ uint32_t icw, ich;
++ if (w <= h) {
++ ich = ICONSIZE; icw = w * ICONSIZE / h;
++ if (icw == 0) icw = 1;
++ }
++ else {
++ icw = ICONSIZE; ich = h * ICONSIZE / w;
++ if (ich == 0) ich = 1;
++ }
++ *picw = icw; *pich = ich;
++
++ uint32_t i, *bstp32 = (uint32_t *)bstp;
++ for (sz = w * h, i = 0; i < sz; ++i) bstp32[i] = prealpha(bstp[i]);
++
++ Picture ret = drw_picture_create_resized(drw, (char *)bstp, w, h, icw, ich);
++ XFree(p);
++
++ return ret;
++}
++
+ int
+ getrootptr(int *x, int *y)
+ {
+@@ -905,6 +1128,16 @@ getstate(Window w)
+ return result;
+ }
+
++unsigned int
++getsystraywidth()
++{
++ unsigned int w = 0;
++ Client *i;
++ if (showsystray)
++ for (i = systray->icons; i; w += i->w + systrayspacing, i = i->next);
++ return w ? w + systrayspacing : 0;
++}
++
+ int
+ gettextprop(Window w, Atom atom, char *text, unsigned int size)
+ {
+@@ -1017,7 +1250,7 @@ killclient(const Arg *arg)
+ {
+ if (!selmon->sel)
+ return;
+- if (!sendevent(selmon->sel, wmatom[WMDelete])) {
++ if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0, 0, 0)) {
+ XGrabServer(dpy);
+ XSetErrorHandler(xerrordummy);
+ XSetCloseDownMode(dpy, DestroyAll);
+@@ -1028,6 +1261,37 @@ killclient(const Arg *arg)
+ }
+ }
+
++void
++loadxrdb()
++{
++ Display *display;
++ char * resm;
++ XrmDatabase xrdb;
++ char *type;
++ XrmValue value;
++
++ display = XOpenDisplay(NULL);
++
++ if (display != NULL) {
++ resm = XResourceManagerString(display);
++
++ if (resm != NULL) {
++ xrdb = XrmGetStringDatabase(resm);
++
++ if (xrdb != NULL) {
++ XRDB_LOAD_COLOR("dwm.normbordercolor", normbordercolor);
++ XRDB_LOAD_COLOR("dwm.normbgcolor", normbgcolor);
++ XRDB_LOAD_COLOR("dwm.normfgcolor", normfgcolor);
++ XRDB_LOAD_COLOR("dwm.selbordercolor", selbordercolor);
++ XRDB_LOAD_COLOR("dwm.selbgcolor", selbgcolor);
++ XRDB_LOAD_COLOR("dwm.selfgcolor", selfgcolor);
++ }
++ }
++ }
++
++ XCloseDisplay(display);
++}
++
+ void
+ manage(Window w, XWindowAttributes *wa)
+ {
+@@ -1044,6 +1308,7 @@ manage(Window w, XWindowAttributes *wa)
+ c->h = c->oldh = wa->height;
+ c->oldbw = wa->border_width;
+
++ updateicon(c);
+ updatetitle(c);
+ if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) {
+ c->mon = t->mon;
+@@ -1104,6 +1369,12 @@ maprequest(XEvent *e)
+ static XWindowAttributes wa;
+ XMapRequestEvent *ev = &e->xmaprequest;
+
++ Client *i;
++ if (showsystray && (i = wintosystrayicon(ev->window))) {
++ sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION);
++ updatesystray(1);
++ }
++
+ if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect)
+ return;
+ if (!wintoclient(ev->window))
+@@ -1225,6 +1496,16 @@ propertynotify(XEvent *e)
+ Window trans;
+ XPropertyEvent *ev = &e->xproperty;
+
++ if (showsystray && (c = wintosystrayicon(ev->window))) {
++ if (ev->atom == XA_WM_NORMAL_HINTS) {
++ updatesizehints(c);
++ updatesystrayicongeom(c, c->w, c->h);
++ }
++ else
++ updatesystrayiconstate(c, ev);
++ updatesystray(1);
++ }
++
+ if ((ev->window == root) && (ev->atom == XA_WM_NAME))
+ updatestatus();
+ else if (ev->state == PropertyDelete)
+@@ -1250,6 +1531,11 @@ propertynotify(XEvent *e)
+ if (c == c->mon->sel)
+ drawbar(c->mon);
+ }
++ else if (ev->atom == netatom[NetWMIcon]) {
++ updateicon(c);
++ if (c == c->mon->sel)
++ drawbar(c->mon);
++ }
+ if (ev->atom == netatom[NetWMWindowType])
+ updatewindowtype(c);
+ }
+@@ -1258,6 +1544,7 @@ propertynotify(XEvent *e)
+ void
+ quit(const Arg *arg)
+ {
++ if(arg->i) restart = 1;
+ running = 0;
+ }
+
+@@ -1275,6 +1562,19 @@ recttomon(int x, int y, int w, int h)
+ return r;
+ }
+
++void
++removesystrayicon(Client *i)
++{
++ Client **ii;
++
++ if (!showsystray || !i)
++ return;
++ for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next);
++ if (ii)
++ *ii = i->next;
++ free(i);
++}
++
+ void
+ resize(Client *c, int x, int y, int w, int h, int interact)
+ {
+@@ -1286,12 +1586,36 @@ void
+ resizeclient(Client *c, int x, int y, int w, int h)
+ {
+ XWindowChanges wc;
++ unsigned int n;
++ unsigned int gapoffset;
++ unsigned int gapincr;
++ Client *nbc;
+
+- 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;
++
++ /* Get number of clients for the client's monitor */
++ for (n = 0, nbc = nexttiled(c->mon->clients); nbc; nbc = nexttiled(nbc->next), n++);
++
++ /* Do nothing if layout is floating */
++ if (c->isfloating || c->mon->lt[c->mon->sellt]->arrange == NULL) {
++ gapincr = gapoffset = 0;
++ } else {
++ /* Remove border and gap if layout is monocle or only one client */
++ if (c->mon->lt[c->mon->sellt]->arrange == monocle || n == 1) {
++ gapoffset = 0;
++ gapincr = -2 * borderpx;
++ wc.border_width = 0;
++ } else {
++ gapoffset = gappx;
++ gapincr = 2 * gappx;
++ }
++ }
++
++ c->oldx = c->x; c->x = wc.x = x + gapoffset;
++ c->oldy = c->y; c->y = wc.y = y + gapoffset;
++ c->oldw = c->w; c->w = wc.width = w - gapincr;
++ c->oldh = c->h; c->h = wc.height = h - gapincr;
++
+ XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
+ configure(c);
+ XSync(dpy, False);
+@@ -1354,6 +1678,18 @@ resizemouse(const Arg *arg)
+ }
+ }
+
++void
++resizerequest(XEvent *e)
++{
++ XResizeRequestEvent *ev = &e->xresizerequest;
++ Client *i;
++
++ if ((i = wintosystrayicon(ev->window))) {
++ updatesystrayicongeom(i, ev->width, ev->height);
++ updatesystray(1);
++ }
++}
++
+ void
+ restack(Monitor *m)
+ {
+@@ -1390,6 +1726,83 @@ run(void)
+ handler[ev.type](&ev); /* call handler */
+ }
+
++void
++runautostart(void)
++{
++ char *pathpfx;
++ char *path;
++ char *xdgdatahome;
++ char *home;
++ struct stat sb;
++
++ if ((home = getenv("HOME")) == NULL)
++ /* this is almost impossible */
++ return;
++
++ /* if $XDG_DATA_HOME is set and not empty, use $XDG_DATA_HOME/dwm,
++ * otherwise use ~/.local/share/dwm as autostart script directory
++ */
++ xdgdatahome = getenv("XDG_DATA_HOME");
++ if (xdgdatahome != NULL && *xdgdatahome != '\0') {
++ /* space for path segments, separators and nul */
++ pathpfx = ecalloc(1, strlen(xdgdatahome) + strlen(dwmdir) + 2);
++
++ if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) {
++ free(pathpfx);
++ return;
++ }
++ } else {
++ /* space for path segments, separators and nul */
++ pathpfx = ecalloc(1, strlen(home) + strlen(localshare)
++ + strlen(dwmdir) + 3);
++
++ if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) {
++ free(pathpfx);
++ return;
++ }
++ }
++
++ /* check if the autostart script directory exists */
++ if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) {
++ /* the XDG conformant path does not exist or is no directory
++ * so we try ~/.dwm instead
++ */
++ char *pathpfx_new = realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3);
++ if(pathpfx_new == NULL) {
++ free(pathpfx);
++ return;
++ }
++ pathpfx = pathpfx_new;
++
++ if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) {
++ free(pathpfx);
++ return;
++ }
++ }
++
++ /* try the blocking script first */
++ path = ecalloc(1, strlen(pathpfx) + strlen(autostartblocksh) + 2);
++ if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) {
++ free(path);
++ free(pathpfx);
++ }
++
++ if (access(path, X_OK) == 0)
++ system(path);
++
++ /* now the non-blocking script */
++ if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) {
++ free(path);
++ free(pathpfx);
++ }
++
++ if (access(path, X_OK) == 0)
++ system(strcat(path, " &"));
++
++ free(pathpfx);
++ free(path);
++}
++
+ void
+ scan(void)
+ {
+@@ -1443,26 +1856,35 @@ setclientstate(Client *c, long state)
+ }
+
+ int
+-sendevent(Client *c, Atom proto)
++sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4)
+ {
+ int n;
+- Atom *protocols;
++ Atom *protocols, mt;
+ int exists = 0;
+ XEvent ev;
+
+- if (XGetWMProtocols(dpy, c->win, &protocols, &n)) {
+- while (!exists && n--)
+- exists = protocols[n] == proto;
+- XFree(protocols);
++ if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) {
++ mt = wmatom[WMProtocols];
++ if (XGetWMProtocols(dpy, w, &protocols, &n)) {
++ while (!exists && n--)
++ exists = protocols[n] == proto;
++ XFree(protocols);
++ }
++ } else {
++ exists = True;
++ mt = proto;
+ }
+ if (exists) {
+ ev.type = ClientMessage;
+- ev.xclient.window = c->win;
+- ev.xclient.message_type = wmatom[WMProtocols];
++ ev.xclient.window = w;
++ ev.xclient.message_type = mt;
+ ev.xclient.format = 32;
+- ev.xclient.data.l[0] = proto;
+- ev.xclient.data.l[1] = CurrentTime;
+- XSendEvent(dpy, c->win, False, NoEventMask, &ev);
++ ev.xclient.data.l[0] = d0;
++ ev.xclient.data.l[1] = d1;
++ ev.xclient.data.l[2] = d2;
++ ev.xclient.data.l[3] = d3;
++ ev.xclient.data.l[4] = d4;
++ XSendEvent(dpy, w, False, mask, &ev);
+ }
+ return exists;
+ }
+@@ -1476,7 +1898,7 @@ setfocus(Client *c)
+ XA_WINDOW, 32, PropModeReplace,
+ (unsigned char *) &(c->win), 1);
+ }
+- sendevent(c, wmatom[WMTakeFocus]);
++ sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0);
+ }
+
+ void
+@@ -1553,12 +1975,16 @@ setup(void)
+ /* clean up any zombies (inherited from .xinitrc etc) immediately */
+ while (waitpid(-1, NULL, WNOHANG) > 0);
+
++ signal(SIGHUP, sighup);
++ signal(SIGTERM, sigterm);
++
+ /* 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);
++ xinitvisual();
++ drw = drw_create(dpy, screen, root, sw, sh, visual, depth, cmap);
+ if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+@@ -1572,13 +1998,23 @@ setup(void)
+ wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
+ netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
++ netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False);
++ netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
++ netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False);
++ netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False);
++ netatom[NetSystemTrayVisual] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_VISUAL", False);
+ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
++ netatom[NetWMIcon] = XInternAtom(dpy, "_NET_WM_ICON", 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[NetWMWindowTypeDock] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
+ netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
++ xatom[Manager] = XInternAtom(dpy, "MANAGER", False);
++ xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False);
++ xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False);
+ /* init cursors */
+ cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
+ cursor[CurResize] = drw_cur_create(drw, XC_sizing);
+@@ -1586,7 +2022,10 @@ setup(void)
+ /* init appearance */
+ scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
+ for (i = 0; i < LENGTH(colors); i++)
+- scheme[i] = drw_scm_create(drw, colors[i], 3);
++ scheme[i] = drw_scm_create(drw, colors[i], alphas[i], 3);
++ /* init system tray */
++ if (showsystray)
++ updatesystray(0);
+ /* init bars */
+ updatebars();
+ updatestatus();
+@@ -1632,6 +2071,10 @@ showhide(Client *c)
+ if (!c)
+ return;
+ if (ISVISIBLE(c)) {
++ if ((c->tags & SPTAGMASK) && c->isfloating) {
++ c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2);
++ c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2);
++ }
+ /* show clients top down */
+ XMoveWindow(dpy, c->win, c->x, c->y);
+ if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen)
+@@ -1644,6 +2087,20 @@ showhide(Client *c)
+ }
+ }
+
++void
++sighup(int unused)
++{
++ Arg a = {.i = 1};
++ quit(&a);
++}
++
++void
++sigterm(int unused)
++{
++ Arg a = {.i = 0};
++ quit(&a);
++}
++
+ void
+ spawn(const Arg *arg)
+ {
+@@ -1666,6 +2123,23 @@ spawn(const Arg *arg)
+ }
+ }
+
++Monitor *
++systraytomon(Monitor *m)
++{
++ Monitor *t;
++ int i, n;
++ if (!systraypinning) {
++ if (!m)
++ return selmon;
++ return m == selmon ? m : NULL;
++ }
++ for (n = 1, t = mons; t && t->next; n++, t = t->next);
++ for (i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next);
++ if (systraypinningfailfirst && n < systraypinning)
++ return mons;
++ return t;
++}
++
+ void
+ tag(const Arg *arg)
+ {
+@@ -1701,7 +2175,7 @@ tile(Monitor *m)
+ 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);
++ resize(c, m->wx, m->wy + my, mw - (2*c->bw) + (n > 1 ? gappx : 0), h - (2*c->bw), 0);
+ if (my + HEIGHT(c) < m->wh)
+ my += HEIGHT(c);
+ } else {
+@@ -1718,6 +2192,23 @@ togglebar(const Arg *arg)
+ selmon->showbar = !selmon->showbar;
+ updatebarpos(selmon);
+ XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh);
++ if (showsystray) {
++ XWindowChanges wc;
++ if (!selmon->showbar)
++ wc.y = -bh;
++ else if (selmon->showbar) {
++ #if BARPADDING_PATCH
++ wc.y = vp;
++ if (!selmon->topbar)
++ wc.y = selmon->mh - bh + vp;
++ #else
++ wc.y = 0;
++ if (!selmon->topbar)
++ wc.y = selmon->mh - bh;
++ #endif // BARPADDING_PATCH
++ }
++ XConfigureWindow(dpy, systray->win, CWY, &wc);
++ }
+ arrange(selmon);
+ }
+
+@@ -1735,6 +2226,32 @@ togglefloating(const Arg *arg)
+ arrange(selmon);
+ }
+
++void
++togglescratch(const Arg *arg)
++{
++ Client *c;
++ unsigned int found = 0;
++ unsigned int scratchtag = SPTAG(arg->ui);
++ Arg sparg = {.v = scratchpads[arg->ui].cmd};
++
++ for (c = selmon->clients; c && !(found = c->tags & scratchtag); c = c->next);
++ if (found) {
++ unsigned int newtagset = selmon->tagset[selmon->seltags] ^ scratchtag;
++ if (newtagset) {
++ selmon->tagset[selmon->seltags] = newtagset;
++ focus(NULL);
++ arrange(selmon);
++ }
++ if (ISVISIBLE(c)) {
++ focus(c);
++ restack(selmon);
++ }
++ } else {
++ selmon->tagset[selmon->seltags] |= scratchtag;
++ spawn(&sparg);
++ }
++}
++
+ void
+ toggletag(const Arg *arg)
+ {
+@@ -1762,6 +2279,15 @@ toggleview(const Arg *arg)
+ }
+ }
+
++void
++freeicon(Client *c)
++{
++ if (c->icon) {
++ XRenderFreePicture(dpy, c->icon);
++ c->icon = None;
++ }
++}
++
+ void
+ unfocus(Client *c, int setfocus)
+ {
+@@ -1783,6 +2309,7 @@ unmanage(Client *c, int destroyed)
+
+ detach(c);
+ detachstack(c);
++ freeicon(c);
+ if (!destroyed) {
+ wc.border_width = c->oldbw;
+ XGrabServer(dpy); /* avoid race conditions */
+@@ -1812,6 +2339,11 @@ unmapnotify(XEvent *e)
+ setclientstate(c, WithdrawnState);
+ else
+ unmanage(c, 0);
++ } else if (showsystray && (c = wintosystrayicon(ev->window))) {
++ /* KLUDGE! sometimes icons occasionally unmap their windows, but do
++ * _not_ destroy them. We map those windows back */
++ XMapRaised(dpy, c->win);
++ updatesystray(1);
+ }
+ }
+
+@@ -1821,17 +2353,21 @@ updatebars(void)
+ Monitor *m;
+ XSetWindowAttributes wa = {
+ .override_redirect = True,
+- .background_pixmap = ParentRelative,
++ .background_pixel = 0,
++ .border_pixel = 0,
++ .colormap = cmap,
+ .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);
++ m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, depth,
++ InputOutput, visual,
++ CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &wa);
+ XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor);
++ if (showsystray && m == systraytomon(m))
++ XMapRaised(dpy, systray->win);
+ XMapRaised(dpy, m->barwin);
+ XSetClassHint(dpy, m->barwin, &ch);
+ }
+@@ -2010,6 +2546,137 @@ updatestatus(void)
+ drawbar(selmon);
+ }
+
++void
++updatesystray(int updatebar)
++{
++ XSetWindowAttributes wa;
++ XWindowChanges wc;
++ Client *i;
++ Monitor *m = systraytomon(NULL);
++ unsigned int x = m->mx + m->mw;
++ unsigned int w = 1, xpad = 0, ypad = 0;
++ #if BARPADDING_PATCH
++ xpad = sp;
++ ypad = vp;
++ #endif // BARPADDING_PATCH
++
++ if (!showsystray)
++ return;
++ if (!systray) {
++ /* init systray */
++ if (!(systray = (Systray *)calloc(1, sizeof(Systray))))
++ die("fatal: could not malloc() %u bytes\n", sizeof(Systray));
++
++ wa.override_redirect = True;
++ wa.event_mask = ButtonPressMask|ExposureMask;
++ wa.background_pixel = 0;
++ wa.border_pixel = 0;
++ wa.colormap = cmap;
++ systray->win = XCreateWindow(dpy, root, x - xpad, m->by + ypad, w, bh, 0, depth,
++ InputOutput, visual,
++ CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &wa);
++ XSelectInput(dpy, systray->win, SubstructureNotifyMask);
++ XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation], XA_CARDINAL, 32,
++ PropModeReplace, (unsigned char *)&systrayorientation, 1);
++ XChangeProperty(dpy, systray->win, netatom[NetSystemTrayVisual], XA_VISUALID, 32,
++ PropModeReplace, (unsigned char *)&visual->visualid, 1);
++ XChangeProperty(dpy, systray->win, netatom[NetWMWindowType], XA_ATOM, 32,
++ PropModeReplace, (unsigned char *)&netatom[NetWMWindowTypeDock], 1);
++ XMapRaised(dpy, systray->win);
++ XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime);
++ if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) {
++ sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime, netatom[NetSystemTray], systray->win, 0, 0);
++ XSync(dpy, False);
++ }
++ else {
++ fprintf(stderr, "dwm: unable to obtain system tray.\n");
++ free(systray);
++ systray = NULL;
++ return;
++ }
++ }
++
++ for (w = 0, i = systray->icons; i; i = i->next) {
++ wa.background_pixel = 0;
++ XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa);
++ XMapRaised(dpy, i->win);
++ w += systrayspacing;
++ i->x = w;
++ XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h);
++ w += i->w;
++ if (i->mon != m)
++ i->mon = m;
++ }
++ w = w ? w + systrayspacing : 1;
++ x -= w;
++ XMoveResizeWindow(dpy, systray->win, x - xpad, m->by + ypad, w, bh);
++ wc.x = x - xpad;
++ wc.y = m->by + ypad;
++ wc.width = w;
++ wc.height = bh;
++ wc.stack_mode = Above; wc.sibling = m->barwin;
++ XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSibling|CWStackMode, &wc);
++ XMapWindow(dpy, systray->win);
++ XMapSubwindows(dpy, systray->win);
++ XSync(dpy, False);
++
++ if (updatebar)
++ drawbar(m);
++}
++
++void
++updatesystrayicongeom(Client *i, int w, int h)
++{
++ if (i) {
++ i->h = bh;
++ if (w == h)
++ i->w = bh;
++ else if (h == bh)
++ i->w = w;
++ else
++ i->w = (int) ((float)bh * ((float)w / (float)h));
++ applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False);
++ /* force icons into the systray dimensions if they don't want to */
++ if (i->h > bh) {
++ if (i->w == i->h)
++ i->w = bh;
++ else
++ i->w = (int) ((float)bh * ((float)i->w / (float)i->h));
++ i->h = bh;
++ }
++ if (i->w > 2*bh)
++ i->w = bh;
++ }
++}
++
++void
++updatesystrayiconstate(Client *i, XPropertyEvent *ev)
++{
++ long flags;
++ int code = 0;
++
++ if (!showsystray || !i || ev->atom != xatom[XembedInfo] ||
++ !(flags = getatomprop(i, xatom[XembedInfo])))
++ return;
++
++ if (flags & XEMBED_MAPPED && !i->tags) {
++ i->tags = 1;
++ code = XEMBED_WINDOW_ACTIVATE;
++ XMapRaised(dpy, i->win);
++ setclientstate(i, NormalState);
++ }
++ else if (!(flags & XEMBED_MAPPED) && i->tags) {
++ i->tags = 0;
++ code = XEMBED_WINDOW_DEACTIVATE;
++ XUnmapWindow(dpy, i->win);
++ setclientstate(i, WithdrawnState);
++ }
++ else
++ return;
++ sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0,
++ systray->win, XEMBED_EMBEDDED_VERSION);
++}
++
+ void
+ updatetitle(Client *c)
+ {
+@@ -2019,6 +2686,13 @@ updatetitle(Client *c)
+ strcpy(c->name, broken);
+ }
+
++void
++updateicon(Client *c)
++{
++ freeicon(c);
++ c->icon = geticonprop(c->win, &c->icw, &c->ich);
++}
++
+ void
+ updatewindowtype(Client *c)
+ {
+@@ -2092,6 +2766,16 @@ wintomon(Window w)
+ return selmon;
+ }
+
++Client *
++wintosystrayicon(Window w) {
++ Client *i = NULL;
++
++ if (!showsystray || !w)
++ return i;
++ for (i = systray->icons; i && i->win != w; i = i->next);
++ return i;
++}
++
+ /* 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. */
+@@ -2128,6 +2812,55 @@ xerrorstart(Display *dpy, XErrorEvent *ee)
+ return -1;
+ }
+
++void
++xinitvisual()
++{
++ XVisualInfo *infos;
++ XRenderPictFormat *fmt;
++ int nitems;
++ int i;
++
++ XVisualInfo tpl = {
++ .screen = screen,
++ .depth = 32,
++ .class = TrueColor
++ };
++ long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
++
++ infos = XGetVisualInfo(dpy, masks, &tpl, &nitems);
++ visual = NULL;
++ for(i = 0; i < nitems; i ++) {
++ fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
++ if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
++ visual = infos[i].visual;
++ depth = infos[i].depth;
++ cmap = XCreateColormap(dpy, root, visual, AllocNone);
++ useargb = 1;
++ break;
++ }
++ }
++
++ XFree(infos);
++
++ if (! visual) {
++ visual = DefaultVisual(dpy, screen);
++ depth = DefaultDepth(dpy, screen);
++ cmap = DefaultColormap(dpy, screen);
++ }
++}
++
++void
++xrdb(const Arg *arg)
++{
++ loadxrdb();
++ int i;
++ for (i = 0; i < LENGTH(colors); i++)
++ // scheme[i] = drw_scm_create(drw, colors[i], 3);
++ scheme[i] = drw_scm_create(drw, colors[i], alphas[i], 3);
++ focus(NULL);
++ arrange(NULL);
++}
++
+ void
+ zoom(const Arg *arg)
+ {
+@@ -2152,13 +2885,17 @@ main(int argc, char *argv[])
+ if (!(dpy = XOpenDisplay(NULL)))
+ die("dwm: cannot open display");
+ checkotherwm();
++ XrmInitialize();
++ loadxrdb();
+ setup();
+ #ifdef __OpenBSD__
+ if (pledge("stdio rpath proc exec", NULL) == -1)
+ die("pledge");
+ #endif /* __OpenBSD__ */
+ scan();
++ runautostart();
+ run();
++ if(restart) execvp(argv[0], argv);
+ cleanup();
+ XCloseDisplay(dpy);
+ return EXIT_SUCCESS;
+diff --git a/dwm.desktop b/dwm.desktop
+new file mode 100644
+index 0000000..be5db48
+--- /dev/null
++++ b/dwm.desktop
+@@ -0,0 +1,7 @@
++[Desktop Entry]
++Name=DWM
++Comment=Dynamic Window Manager
++TryExec=dwm
++Exec=dwm
++Type=Application
++Icon=dwm
+diff --git a/exitdwm.c b/exitdwm.c
+new file mode 100644
+index 0000000..b8b95bd
+--- /dev/null
++++ b/exitdwm.c
+@@ -0,0 +1,87 @@
++# include <stdio.h>
++# include <string.h>
++
++void exitdwm ()
++{
++# if \
++ defined S_LOCK || \
++ defined S_RESTART_DWM || \
++ defined S_OFFSCREEN || \
++ defined S_EXIT || \
++ defined S_REBOOT || \
++ defined S_SHUTDOWN || \
++ defined S_LOCK_ICON || \
++ defined S_RESTART_DWM_ICON || \
++ defined S_OFFSCREEN_ICON || \
++ defined S_EXIT_ICON || \
++ defined S_REBOOT_ICON || \
++ defined S_SHUTDOWN_ICON || \
++ defined S_FORMAT || \
++ defined S_FORMAT_CLEAR
++# error (conflicting macro names)
++# endif
++
++# define S_LOCK "Lock"
++# define S_RESTART_DWM "restart birdwm"
++# define S_OFFSCREEN "Off-screen"
++# define S_EXIT "Exit"
++# define S_REBOOT "Reboot"
++# define S_SHUTDOWN "Shutdown"
++# define S_LOCK_ICON "\uf023" // <= FontAwesome icons
++# define S_RESTART_DWM_ICON "\uf01e"
++# define S_OFFSCREEN_ICON "\uf108"
++# define S_EXIT_ICON "\uf2f5"
++# define S_REBOOT_ICON "\uf021"
++# define S_SHUTDOWN_ICON "\uf011"
++
++# define S_FORMAT(ACTION) S_##ACTION##_ICON " " S_##ACTION
++# define S_FORMAT_CLEAR "sed 's/^..//'"
++
++ FILE * exit_menu = popen (
++ "echo \""
++ S_FORMAT (LOCK) "\n"
++ S_FORMAT (RESTART_DWM) "\n"
++ S_FORMAT (OFFSCREEN) "\n"
++ S_FORMAT (EXIT) "\n"
++ S_FORMAT (REBOOT) "\n"
++ S_FORMAT (SHUTDOWN)
++ "\" | dmenu -i -p exit: | " S_FORMAT_CLEAR
++ ,
++ "r"
++ );
++
++ char exit_action [16];
++
++ if (
++ exit_menu == NULL ||
++ fscanf (exit_menu, "%15[a-zA-Z -]", exit_action) == EOF
++ ) {
++ fputs ("Error. Failure in exit_dwm.", stderr);
++ goto close_streams;
++ }
++
++ if (strcmp (exit_action, S_LOCK) == 0) system ("slock & sleep 10; xset dpms force off");
++ else if (strcmp (exit_action, S_RESTART_DWM) == 0) quit (& (const Arg) {1});
++ else if (strcmp (exit_action, S_OFFSCREEN) == 0) system ("sleep .5; xset dpms force off");
++ else if (strcmp (exit_action, S_EXIT) == 0) quit (& (const Arg) {0});
++ else if (strcmp (exit_action, S_REBOOT) == 0) system ("systemctl reboot");
++ else if (strcmp (exit_action, S_SHUTDOWN) == 0) system ("systemctl poweroff -i");
++
++close_streams:
++ pclose (exit_menu);
++
++# undef S_LOCK
++# undef S_RESTART_DWM
++# undef S_OFFSCREEN
++# undef S_EXIT
++# undef S_REBOOT
++# undef S_SHUTDOWN
++# undef S_LOCK_ICON
++# undef S_RESTART_DWM_ICON
++# undef S_OFFSCREEN_ICON
++# undef S_EXIT_ICON
++# undef S_REBOOT_ICON
++# undef S_SHUTDOWN_ICON
++# undef S_FORMAT
++# undef S_FORMAT_CLEAR
++}
+diff --git a/patches/dwm-exitmenu-6.3.diff b/patches/dwm-exitmenu-6.3.diff
+new file mode 100644
+index 0000000..c31d921
+--- /dev/null
++++ b/patches/dwm-exitmenu-6.3.diff
+@@ -0,0 +1,114 @@
++diff --git a/config.def.h b/config.def.h
++index a2ac963..92a6a81 100644
++--- a/config.def.h
+++++ b/config.def.h
++@@ -60,6 +60,7 @@ 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 };
++
+++#include "exitdwm.c"
++ static Key keys[] = {
++ /* modifier key function argument */
++ { MODKEY, XK_p, spawn, {.v = dmenucmd } },
++@@ -94,7 +95,7 @@ static Key keys[] = {
++ TAGKEYS( XK_7, 6)
++ TAGKEYS( XK_8, 7)
++ TAGKEYS( XK_9, 8)
++- { MODKEY|ShiftMask, XK_q, quit, {0} },
+++ { MODKEY|ShiftMask, XK_e, exitdwm, {0} },
++ };
++
++ /* button definitions */
++diff --git a/exitdwm.c b/exitdwm.c
++new file mode 100644
++index 0000000..74c514f
++--- /dev/null
+++++ b/exitdwm.c
++@@ -0,0 +1,87 @@
+++# include <stdio.h>
+++# include <string.h>
+++
+++void exitdwm ()
+++{
+++# if \
+++ defined S_LOCK || \
+++ defined S_RESTART_DWM || \
+++ defined S_OFFSCREEN || \
+++ defined S_EXIT || \
+++ defined S_REBOOT || \
+++ defined S_SHUTDOWN || \
+++ defined S_LOCK_ICON || \
+++ defined S_RESTART_DWM_ICON || \
+++ defined S_OFFSCREEN_ICON || \
+++ defined S_EXIT_ICON || \
+++ defined S_REBOOT_ICON || \
+++ defined S_SHUTDOWN_ICON || \
+++ defined S_FORMAT || \
+++ defined S_FORMAT_CLEAR
+++# error (conflicting macro names)
+++# endif
+++
+++# define S_LOCK "Lock"
+++# define S_RESTART_DWM "restart Dwm"
+++# define S_OFFSCREEN "Off-screen"
+++# define S_EXIT "Exit"
+++# define S_REBOOT "Reboot"
+++# define S_SHUTDOWN "Shutdown"
+++# define S_LOCK_ICON "\uf023" // <= FontAwesome icons
+++# define S_RESTART_DWM_ICON "\uf01e"
+++# define S_OFFSCREEN_ICON "\uf108"
+++# define S_EXIT_ICON "\uf2f5"
+++# define S_REBOOT_ICON "\uf021"
+++# define S_SHUTDOWN_ICON "\uf011"
+++
+++# define S_FORMAT(ACTION) S_##ACTION##_ICON " " S_##ACTION
+++# define S_FORMAT_CLEAR "sed 's/^..//'"
+++
+++ FILE * exit_menu = popen (
+++ "echo \""
+++ S_FORMAT (LOCK) "\n"
+++ S_FORMAT (RESTART_DWM) "\n"
+++ S_FORMAT (OFFSCREEN) "\n"
+++ S_FORMAT (EXIT) "\n"
+++ S_FORMAT (REBOOT) "\n"
+++ S_FORMAT (SHUTDOWN)
+++ "\" | dmenu -p exit: | " S_FORMAT_CLEAR
+++ ,
+++ "r"
+++ );
+++
+++ char exit_action [16];
+++
+++ if (
+++ exit_menu == NULL ||
+++ fscanf (exit_menu, "%15[a-zA-Z -]", exit_action) == EOF
+++ ) {
+++ fputs ("Error. Failure in exit_dwm.", stderr);
+++ goto close_streams;
+++ }
+++
+++ if (strcmp (exit_action, S_LOCK) == 0) system ("slock & sleep .5; xset dpms force off");
+++ else if (strcmp (exit_action, S_RESTART_DWM) == 0) quit (& (const Arg) {1});
+++ else if (strcmp (exit_action, S_OFFSCREEN) == 0) system ("sleep .5; xset dpms force off");
+++ else if (strcmp (exit_action, S_EXIT) == 0) quit (& (const Arg) {0});
+++ else if (strcmp (exit_action, S_REBOOT) == 0) system ("systemctl reboot");
+++ else if (strcmp (exit_action, S_SHUTDOWN) == 0) system ("systemctl poweroff -i");
+++
+++close_streams:
+++ pclose (exit_menu);
+++
+++# undef S_LOCK
+++# undef S_RESTART_DWM
+++# undef S_OFFSCREEN
+++# undef S_EXIT
+++# undef S_REBOOT
+++# undef S_SHUTDOWN
+++# undef S_LOCK_ICON
+++# undef S_RESTART_DWM_ICON
+++# undef S_OFFSCREEN_ICON
+++# undef S_EXIT_ICON
+++# undef S_REBOOT_ICON
+++# undef S_SHUTDOWN_ICON
+++# undef S_FORMAT
+++# undef S_FORMAT_CLEAR
+++}
+diff --git a/patches/used/dwm-restartsig-20180523-6.2.diff b/patches/used/dwm-restartsig-20180523-6.2.diff
+new file mode 100644
+index 0000000..f1f8680
+--- /dev/null
++++ b/patches/used/dwm-restartsig-20180523-6.2.diff
+@@ -0,0 +1,139 @@
++From 2991f37f0aaf44b9f9b11e7893ff0af8eb88f649 Mon Sep 17 00:00:00 2001
++From: Christopher Drelich <cd@cdrakka.com>
++Date: Wed, 23 May 2018 22:50:38 -0400
++Subject: [PATCH] Modifies quit to handle restarts and adds SIGHUP and SIGTERM
++ handlers.
++
++Modified quit() to restart if it receives arg .i = 1
++MOD+CTRL+SHIFT+Q was added to confid.def.h to do just that.
++
++Signal handlers were handled for SIGHUP and SIGTERM.
++If dwm receives these signals it calls quit() with
++arg .i = to 1 or 0, respectively.
++
++To restart dwm:
++MOD+CTRL+SHIFT+Q
++or
++kill -HUP dwmpid
++
++To quit dwm cleanly:
++MOD+SHIFT+Q
++or
++kill -TERM dwmpid
++---
++ config.def.h | 1 +
++ dwm.1 | 10 ++++++++++
++ dwm.c | 22 ++++++++++++++++++++++
++ 3 files changed, 33 insertions(+)
++
++diff --git a/config.def.h b/config.def.h
++index a9ac303..e559429 100644
++--- a/config.def.h
+++++ b/config.def.h
++@@ -94,6 +94,7 @@ static Key keys[] = {
++ TAGKEYS( XK_8, 7)
++ TAGKEYS( XK_9, 8)
++ { MODKEY|ShiftMask, XK_q, quit, {0} },
+++ { MODKEY|ControlMask|ShiftMask, XK_q, quit, {1} },
++ };
++
++ /* button definitions */
++diff --git a/dwm.1 b/dwm.1
++index 13b3729..36a331c 100644
++--- a/dwm.1
+++++ b/dwm.1
++@@ -142,6 +142,9 @@ Add/remove all windows with nth tag to/from the view.
++ .TP
++ .B Mod1\-Shift\-q
++ Quit dwm.
+++.TP
+++.B Mod1\-Control\-Shift\-q
+++Restart dwm.
++ .SS Mouse commands
++ .TP
++ .B Mod1\-Button1
++@@ -155,6 +158,13 @@ Resize focused window while dragging. Tiled windows will be toggled to the float
++ .SH CUSTOMIZATION
++ dwm is customized by creating a custom config.h and (re)compiling the source
++ code. This keeps it fast, secure and simple.
+++.SH SIGNALS
+++.TP
+++.B SIGHUP - 1
+++Restart the dwm process.
+++.TP
+++.B SIGTERM - 15
+++Cleanly terminate the dwm process.
++ .SH SEE ALSO
++ .BR dmenu (1),
++ .BR st (1)
++diff --git a/dwm.c b/dwm.c
++index bb95e26..286eecd 100644
++--- a/dwm.c
+++++ b/dwm.c
++@@ -205,6 +205,8 @@ static void setup(void);
++ static void seturgent(Client *c, int urg);
++ static void showhide(Client *c);
++ static void sigchld(int unused);
+++static void sighup(int unused);
+++static void sigterm(int unused);
++ static void spawn(const Arg *arg);
++ static void tag(const Arg *arg);
++ static void tagmon(const Arg *arg);
++@@ -260,6 +262,7 @@ static void (*handler[LASTEvent]) (XEvent *) = {
++ [UnmapNotify] = unmapnotify
++ };
++ static Atom wmatom[WMLast], netatom[NetLast];
+++static int restart = 0;
++ static int running = 1;
++ static Cur *cursor[CurLast];
++ static Clr **scheme;
++@@ -1248,6 +1251,7 @@ propertynotify(XEvent *e)
++ void
++ quit(const Arg *arg)
++ {
+++ if(arg->i) restart = 1;
++ running = 0;
++ }
++
++@@ -1536,6 +1540,9 @@ setup(void)
++ /* clean up any zombies immediately */
++ sigchld(0);
++
+++ signal(SIGHUP, sighup);
+++ signal(SIGTERM, sigterm);
+++
++ /* init screen */
++ screen = DefaultScreen(dpy);
++ sw = DisplayWidth(dpy, screen);
++@@ -1637,6 +1644,20 @@ sigchld(int unused)
++ }
++
++ void
+++sighup(int unused)
+++{
+++ Arg a = {.i = 1};
+++ quit(&a);
+++}
+++
+++void
+++sigterm(int unused)
+++{
+++ Arg a = {.i = 0};
+++ quit(&a);
+++}
+++
+++void
++ spawn(const Arg *arg)
++ {
++ if (arg->v == dmenucmd)
++@@ -2139,6 +2160,7 @@ main(int argc, char *argv[])
++ setup();
++ scan();
++ run();
+++ if(restart) execvp(argv[0], argv);
++ cleanup();
++ XCloseDisplay(dpy);
++ return EXIT_SUCCESS;
++--
++2.7.4
++
+diff --git a/slock/LICENSE b/slock/LICENSE
+new file mode 100644
+index 0000000..2e4419b
+--- /dev/null
++++ b/slock/LICENSE
+@@ -0,0 +1,24 @@
++MIT/X Consortium License
++
++© 2015-2016 Markus Teich <markus.teich@stusta.mhn.de>
++© 2014 Dimitris Papastamos <sin@2f30.org>
++© 2006-2014 Anselm R Garbe <anselm@garbe.us>
++© 2014-2016 Laslo Hunhold <dev@frign.de>
++
++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/slock/Makefile b/slock/Makefile
+new file mode 100644
+index 0000000..e730eb6
+--- /dev/null
++++ b/slock/Makefile
+@@ -0,0 +1,61 @@
++# slock - simple screen locker
++# See LICENSE file for copyright and license details.
++
++include config.mk
++
++SRC = slock.c ${COMPATSRC}
++OBJ = ${SRC:.c=.o}
++
++all: options slock
++
++options:
++ @echo slock build options:
++ @echo "CFLAGS = ${CFLAGS}"
++ @echo "LDFLAGS = ${LDFLAGS}"
++ @echo "CC = ${CC}"
++
++.c.o:
++ @echo CC $<
++ @${CC} -c ${CFLAGS} $<
++
++${OBJ}: config.h config.mk arg.h util.h
++
++config.h:
++ @echo creating $@ from config.def.h
++ @cp config.def.h $@
++
++slock: ${OBJ}
++ @echo CC -o $@
++ @${CC} -o $@ ${OBJ} ${LDFLAGS}
++
++clean:
++ @echo cleaning
++ @rm -f config.h slock ${OBJ} slock-${VERSION}.tar.gz
++
++dist: clean
++ @echo creating dist tarball
++ @mkdir -p slock-${VERSION}
++ @cp -R LICENSE Makefile README slock.1 config.mk \
++ ${SRC} explicit_bzero.c config.def.h arg.h util.h slock-${VERSION}
++ @tar -cf slock-${VERSION}.tar slock-${VERSION}
++ @gzip slock-${VERSION}.tar
++ @rm -rf slock-${VERSION}
++
++install: all
++ @echo installing executable file to ${DESTDIR}${PREFIX}/bin
++ @mkdir -p ${DESTDIR}${PREFIX}/bin
++ @cp -f slock ${DESTDIR}${PREFIX}/bin
++ @chmod 755 ${DESTDIR}${PREFIX}/bin/slock
++ @chmod u+s ${DESTDIR}${PREFIX}/bin/slock
++ @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
++ @mkdir -p ${DESTDIR}${MANPREFIX}/man1
++ @sed "s/VERSION/${VERSION}/g" <slock.1 >${DESTDIR}${MANPREFIX}/man1/slock.1
++ @chmod 644 ${DESTDIR}${MANPREFIX}/man1/slock.1
++
++uninstall:
++ @echo removing executable file from ${DESTDIR}${PREFIX}/bin
++ @rm -f ${DESTDIR}${PREFIX}/bin/slock
++ @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
++ @rm -f ${DESTDIR}${MANPREFIX}/man1/slock.1
++
++.PHONY: all options clean dist install uninstall
+diff --git a/slock/README b/slock/README
+new file mode 100644
+index 0000000..dcacd01
+--- /dev/null
++++ b/slock/README
+@@ -0,0 +1,24 @@
++slock - simple screen locker
++============================
++simple screen locker utility for X.
++
++
++Requirements
++------------
++In order to build slock you need the Xlib header files.
++
++
++Installation
++------------
++Edit config.mk to match your local setup (slock is installed into
++the /usr/local namespace by default).
++
++Afterwards enter the following command to build and install slock
++(if necessary as root):
++
++ make clean install
++
++
++Running slock
++-------------
++Simply invoke the 'slock' command. To get out of it, enter your password.
+diff --git a/slock/arg.h b/slock/arg.h
+new file mode 100644
+index 0000000..0b23c53
+--- /dev/null
++++ b/slock/arg.h
+@@ -0,0 +1,65 @@
++/*
++ * 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;\
++ }\
++ for (brk_ = 0, argv[0]++, argv_ = argv;\
++ argv[0][0] && !brk_;\
++ argv[0]++) {\
++ if (argv_ != argv)\
++ break;\
++ argc_ = argv[0][0];\
++ switch (argc_)
++
++/* Handles obsolete -NUM syntax */
++#define ARGNUM case '0':\
++ case '1':\
++ case '2':\
++ case '3':\
++ case '4':\
++ case '5':\
++ case '6':\
++ case '7':\
++ case '8':\
++ case '9'
++
++#define ARGEND }\
++ }
++
++#define ARGC() argc_
++
++#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX))
++
++#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
++ ((x), abort(), (char *)0) :\
++ (brk_ = 1, (argv[0][1] != '\0')?\
++ (&argv[0][1]) :\
++ (argc--, argv++, argv[0])))
++
++#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
++ (char *)0 :\
++ (brk_ = 1, (argv[0][1] != '\0')?\
++ (&argv[0][1]) :\
++ (argc--, argv++, argv[0])))
++
++#define LNGARG() &argv[0][0]
++
++#endif
+diff --git a/slock/config.def.h b/slock/config.def.h
+new file mode 100644
+index 0000000..730b31a
+--- /dev/null
++++ b/slock/config.def.h
+@@ -0,0 +1,33 @@
++/* user and group to drop privileges to */
++static const char *user = "nobody";
++static const char *group = "nobody";
++
++static const char *colorname[NUMCOLS] = {
++ [BACKGROUND] = "black", /* after initialization */
++ [INIT] = "#2d2d2d", /* after initialization */
++ [INPUT] = "#f4cdd4", /* during input */
++ [FAILED] = "#861a22", /* wrong password */
++ [CAPS] = "#ff00ff", /* CapsLock on */
++};
++
++/* treat a cleared input like a wrong password (color) */
++static const int failonclear = 1;
++
++/* insert grid pattern with scale 1:1, the size can be changed with logosize */
++static const int logosize = 75;
++static const int logow = 12; /* grid width and height for right center alignment*/
++static const int logoh = 6;
++
++static XRectangle rectangles[9] = {
++ /* x y w h */
++ { 0, 3, 1, 3 },
++ { 1, 3, 2, 1 },
++ { 0, 5, 8, 1 },
++ { 3, 0, 1, 5 },
++ { 5, 3, 1, 2 },
++ { 7, 3, 1, 2 },
++ { 8, 3, 4, 1 },
++ { 9, 4, 1, 2 },
++ { 11, 4, 1, 2 },
++
++};
+diff --git a/slock/config.mk b/slock/config.mk
+new file mode 100644
+index 0000000..08356e8
+--- /dev/null
++++ b/slock/config.mk
+@@ -0,0 +1,40 @@
++# slock version
++VERSION = 1.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 = -lXft
++FREETYPEINC = /usr/include/freetype2
++
++# includes and libs
++INCS = -I. -I/usr/include -I${X11INC} -I${FREETYPEINC}
++LIBS = -L/usr/lib -lc -lcrypt -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXext -lXrandr
++
++# flags
++CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -DHAVE_SHADOW_H ${XINERAMAFLAGS}
++CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
++LDFLAGS = -s ${LIBS}
++COMPATSRC = explicit_bzero.c
++
++# On OpenBSD and Darwin remove -lcrypt from LIBS
++#LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXext -lXrandr
++# On *BSD remove -DHAVE_SHADOW_H from CPPFLAGS
++# On NetBSD add -D_NETBSD_SOURCE to CPPFLAGS
++#CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_NETBSD_SOURCE
++# On OpenBSD set COMPATSRC to empty
++#COMPATSRC =
++
++# compiler and linker
++CC = cc
+diff --git a/slock/explicit_bzero.c b/slock/explicit_bzero.c
+new file mode 100644
+index 0000000..3e33ca8
+--- /dev/null
++++ b/slock/explicit_bzero.c
+@@ -0,0 +1,19 @@
++/* $OpenBSD: explicit_bzero.c,v 1.3 2014/06/21 02:34:26 matthew Exp $ */
++/*
++ * Public domain.
++ * Written by Matthew Dempsky.
++ */
++
++#include <string.h>
++
++__attribute__((weak)) void
++__explicit_bzero_hook(void *buf, size_t len)
++{
++}
++
++void
++explicit_bzero(void *buf, size_t len)
++{
++ memset(buf, 0, len);
++ __explicit_bzero_hook(buf, len);
++}
+diff --git a/slock/slock.1 b/slock/slock.1
+new file mode 100644
+index 0000000..82cdcd6
+--- /dev/null
++++ b/slock/slock.1
+@@ -0,0 +1,39 @@
++.Dd 2016-08-23
++.Dt SLOCK 1
++.Sh NAME
++.Nm slock
++.Nd simple X screen locker
++.Sh SYNOPSIS
++.Nm
++.Op Fl v
++.Op Ar cmd Op Ar arg ...
++.Sh DESCRIPTION
++.Nm
++is a simple X screen locker. If provided,
++.Ar cmd Op Ar arg ...
++is executed after the screen has been locked.
++.Sh OPTIONS
++.Bl -tag -width Ds
++.It Fl v
++Print version information to stdout and exit.
++.El
++.Sh SECURITY CONSIDERATIONS
++To make sure a locked screen can not be bypassed by switching VTs
++or killing the X server with Ctrl+Alt+Backspace, it is recommended
++to disable both in
++.Xr xorg.conf 5
++for maximum security:
++.Bd -literal -offset left
++Section "ServerFlags"
++ Option "DontVTSwitch" "True"
++ Option "DontZap" "True"
++EndSection
++.Ed
++.Sh EXAMPLES
++$
++.Nm
++/usr/sbin/s2ram
++.Sh CUSTOMIZATION
++.Nm
++can be customized by creating a custom config.h from config.def.h and
++(re)compiling the source code. This keeps it fast, secure and simple.
+diff --git a/slock/slock.c b/slock/slock.c
+new file mode 100644
+index 0000000..94b4a47
+--- /dev/null
++++ b/slock/slock.c
+@@ -0,0 +1,482 @@
++/* See LICENSE file for license details. */
++#define _XOPEN_SOURCE 500
++#define LENGTH(X) (sizeof X / sizeof X[0])
++#if HAVE_SHADOW_H
++#include <shadow.h>
++#endif
++
++#include <ctype.h>
++#include <errno.h>
++#include <grp.h>
++#include <pwd.h>
++#include <stdarg.h>
++#include <stdlib.h>
++#include <stdio.h>
++#include <string.h>
++#include <unistd.h>
++#include <sys/types.h>
++#include <X11/extensions/Xrandr.h>
++#ifdef XINERAMA
++#include <X11/extensions/Xinerama.h>
++#endif
++#include <X11/keysym.h>
++#include <X11/XF86keysym.h>
++#include <X11/Xlib.h>
++#include <X11/Xutil.h>
++#include <X11/Xft/Xft.h>
++#include <X11/XKBlib.h>
++
++#include "arg.h"
++#include "util.h"
++
++char *argv0;
++
++enum {
++ BACKGROUND,
++ INIT,
++ INPUT,
++ FAILED,
++ CAPS,
++ NUMCOLS
++};
++
++#include "config.h"
++
++struct lock {
++ int screen;
++ Window root, win;
++ Pixmap pmap;
++ unsigned long colors[NUMCOLS];
++ unsigned int x, y;
++ unsigned int xoff, yoff, mw, mh;
++ Drawable drawable;
++ GC gc;
++ XRectangle rectangles[LENGTH(rectangles)]
++};
++
++struct xrandr {
++ int active;
++ int evbase;
++ int errbase;
++};
++
++static void
++die(const char *errstr, ...)
++{
++ va_list ap;
++
++ va_start(ap, errstr);
++ vfprintf(stderr, errstr, ap);
++ va_end(ap);
++ exit(1);
++}
++
++#ifdef __linux__
++#include <fcntl.h>
++#include <linux/oom.h>
++
++static void
++dontkillme(void)
++{
++ FILE *f;
++ const char oomfile[] = "/proc/self/oom_score_adj";
++
++ if (!(f = fopen(oomfile, "w"))) {
++ if (errno == ENOENT)
++ return;
++ die("slock: fopen %s: %s\n", oomfile, strerror(errno));
++ }
++ fprintf(f, "%d", OOM_SCORE_ADJ_MIN);
++ if (fclose(f)) {
++ if (errno == EACCES)
++ die("slock: unable to disable OOM killer. "
++ "Make sure to suid or sgid slock.\n");
++ else
++ die("slock: fclose %s: %s\n", oomfile, strerror(errno));
++ }
++}
++#endif
++
++static const char *
++gethash(void)
++{
++ const char *hash;
++ struct passwd *pw;
++
++ /* Check if the current user has a password entry */
++ errno = 0;
++ if (!(pw = getpwuid(getuid()))) {
++ if (errno)
++ die("slock: getpwuid: %s\n", strerror(errno));
++ else
++ die("slock: cannot retrieve password entry\n");
++ }
++ hash = pw->pw_passwd;
++
++#if HAVE_SHADOW_H
++ if (!strcmp(hash, "x")) {
++ struct spwd *sp;
++ if (!(sp = getspnam(pw->pw_name)))
++ die("slock: getspnam: cannot retrieve shadow entry. "
++ "Make sure to suid or sgid slock.\n");
++ hash = sp->sp_pwdp;
++ }
++#else
++ if (!strcmp(hash, "*")) {
++#ifdef __OpenBSD__
++ if (!(pw = getpwuid_shadow(getuid())))
++ die("slock: getpwnam_shadow: cannot retrieve shadow entry. "
++ "Make sure to suid or sgid slock.\n");
++ hash = pw->pw_passwd;
++#else
++ die("slock: getpwuid: cannot retrieve shadow entry. "
++ "Make sure to suid or sgid slock.\n");
++#endif /* __OpenBSD__ */
++ }
++#endif /* HAVE_SHADOW_H */
++
++ return hash;
++}
++
++static void
++resizerectangles(struct lock *lock)
++{
++ int i;
++
++ for (i = 0; i < LENGTH(rectangles); i++){
++ lock->rectangles[i].x = (rectangles[i].x * logosize)
++ + lock->xoff + ((lock->mw) / 2) - (logow / 2 * logosize);
++ lock->rectangles[i].y = (rectangles[i].y * logosize)
++ + lock->yoff + ((lock->mh) / 2) - (logoh / 2 * logosize);
++ lock->rectangles[i].width = rectangles[i].width * logosize;
++ lock->rectangles[i].height = rectangles[i].height * logosize;
++ }
++}
++
++static void
++drawlogo(Display *dpy, struct lock *lock, int color)
++{
++ XSetForeground(dpy, lock->gc, lock->colors[BACKGROUND]);
++ XFillRectangle(dpy, lock->drawable, lock->gc, 0, 0, lock->x, lock->y);
++ XSetForeground(dpy, lock->gc, lock->colors[color]);
++ XFillRectangles(dpy, lock->drawable, lock->gc, lock->rectangles, LENGTH(rectangles));
++ XCopyArea(dpy, lock->drawable, lock->win, lock->gc, 0, 0, lock->x, lock->y, 0, 0);
++ XSync(dpy, False);
++}
++
++static void
++readpw(Display *dpy, struct xrandr *rr, struct lock **locks, int nscreens,
++ const char *hash)
++{
++ XRRScreenChangeNotifyEvent *rre;
++ char buf[32], passwd[256], *inputhash;
++ int caps, num, screen, running, failure, oldc;
++ unsigned int len, color, indicators;
++ KeySym ksym;
++ XEvent ev;
++
++ len = 0;
++ caps = 0;
++ running = 1;
++ failure = 0;
++ oldc = INIT;
++
++ if (!XkbGetIndicatorState(dpy, XkbUseCoreKbd, &indicators))
++ caps = indicators & 1;
++
++ while (running && !XNextEvent(dpy, &ev)) {
++ if (ev.type == KeyPress) {
++ explicit_bzero(&buf, sizeof(buf));
++ num = XLookupString(&ev.xkey, buf, sizeof(buf), &ksym, 0);
++ if (IsKeypadKey(ksym)) {
++ if (ksym == XK_KP_Enter)
++ ksym = XK_Return;
++ else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
++ ksym = (ksym - XK_KP_0) + XK_0;
++ }
++ if (IsFunctionKey(ksym) ||
++ IsKeypadKey(ksym) ||
++ IsMiscFunctionKey(ksym) ||
++ IsPFKey(ksym) ||
++ IsPrivateKeypadKey(ksym))
++ continue;
++ switch (ksym) {
++ case XK_Return:
++ passwd[len] = '\0';
++ errno = 0;
++ if (!(inputhash = crypt(passwd, hash)))
++ fprintf(stderr, "slock: crypt: %s\n", strerror(errno));
++ else
++ running = !!strcmp(inputhash, hash);
++ if (running) {
++ XBell(dpy, 100);
++ failure = 1;
++ }
++ explicit_bzero(&passwd, sizeof(passwd));
++ len = 0;
++ break;
++ case XK_Escape:
++ explicit_bzero(&passwd, sizeof(passwd));
++ len = 0;
++ break;
++ case XK_BackSpace:
++ if (len)
++ passwd[--len] = '\0';
++ break;
++ case XK_Caps_Lock:
++ caps = !caps;
++ break;
++ case XF86XK_AudioLowerVolume:
++ case XF86XK_AudioMute:
++ case XF86XK_AudioRaiseVolume:
++ case XF86XK_AudioPlay:
++ case XF86XK_AudioStop:
++ case XF86XK_AudioPrev:
++ case XF86XK_AudioNext:
++ XSendEvent(dpy, locks[0]->root, True, KeyPressMask, &ev);
++ break;
++ default:
++ if (num && !iscntrl((int)buf[0]) &&
++ (len + num < sizeof(passwd))) {
++ memcpy(passwd + len, buf, num);
++ len += num;
++ }
++ break;
++ }
++
++ color = len ? (caps ? CAPS : INPUT) : (failure || failonclear ? FAILED : INIT);
++ if (running && oldc != color) {
++ for (screen = 0; screen < nscreens; screen++) {
++ drawlogo(dpy, locks[screen], color);
++ }
++ oldc = color;
++ }
++ } else if (rr->active && ev.type == rr->evbase + RRScreenChangeNotify) {
++ rre = (XRRScreenChangeNotifyEvent*)&ev;
++ for (screen = 0; screen < nscreens; screen++) {
++ if (locks[screen]->win == rre->window) {
++ if (rre->rotation == RR_Rotate_90 ||
++ rre->rotation == RR_Rotate_270)
++ XResizeWindow(dpy, locks[screen]->win,
++ rre->height, rre->width);
++ else
++ XResizeWindow(dpy, locks[screen]->win,
++ rre->width, rre->height);
++ XClearWindow(dpy, locks[screen]->win);
++ break;
++ }
++ }
++ } else {
++ for (screen = 0; screen < nscreens; screen++)
++ XRaiseWindow(dpy, locks[screen]->win);
++ }
++ }
++}
++
++static struct lock *
++lockscreen(Display *dpy, struct xrandr *rr, int screen)
++{
++ char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
++ int i, ptgrab, kbgrab;
++ struct lock *lock;
++ XColor color, dummy;
++ XSetWindowAttributes wa;
++ Cursor invisible;
++#ifdef XINERAMA
++ XineramaScreenInfo *info;
++ int n;
++#endif
++
++ if (dpy == NULL || screen < 0 || !(lock = malloc(sizeof(struct lock))))
++ return NULL;
++
++ lock->screen = screen;
++ lock->root = RootWindow(dpy, lock->screen);
++
++ for (i = 0; i < NUMCOLS; i++) {
++ XAllocNamedColor(dpy, DefaultColormap(dpy, lock->screen),
++ colorname[i], &color, &dummy);
++ lock->colors[i] = color.pixel;
++ }
++
++ lock->x = DisplayWidth(dpy, lock->screen);
++ lock->y = DisplayHeight(dpy, lock->screen);
++#ifdef XINERAMA
++ if ((info = XineramaQueryScreens(dpy, &n))) {
++ lock->xoff = info[0].x_org;
++ lock->yoff = info[0].y_org;
++ lock->mw = info[0].width;
++ lock->mh = info[0].height;
++ } else
++#endif
++ {
++ lock->xoff = lock->yoff = 0;
++ lock->mw = lock->x;
++ lock->mh = lock->y;
++ }
++ lock->drawable = XCreatePixmap(dpy, lock->root,
++ lock->x, lock->y, DefaultDepth(dpy, screen));
++ lock->gc = XCreateGC(dpy, lock->root, 0, NULL);
++ XSetLineAttributes(dpy, lock->gc, 1, LineSolid, CapButt, JoinMiter);
++
++ /* init */
++ wa.override_redirect = 1;
++ wa.background_pixel = lock->colors[BACKGROUND];
++ lock->win = XCreateWindow(dpy, lock->root, 0, 0,
++ lock->x, lock->y,
++ 0, DefaultDepth(dpy, lock->screen),
++ CopyFromParent,
++ DefaultVisual(dpy, lock->screen),
++ CWOverrideRedirect | CWBackPixel, &wa);
++ lock->pmap = XCreateBitmapFromData(dpy, lock->win, curs, 8, 8);
++ invisible = XCreatePixmapCursor(dpy, lock->pmap, lock->pmap,
++ &color, &color, 0, 0);
++ XDefineCursor(dpy, lock->win, invisible);
++
++ resizerectangles(lock);
++
++ /* Try to grab mouse pointer *and* keyboard for 600ms, else fail the lock */
++ for (i = 0, ptgrab = kbgrab = -1; i < 6; i++) {
++ if (ptgrab != GrabSuccess) {
++ ptgrab = XGrabPointer(dpy, lock->root, False,
++ ButtonPressMask | ButtonReleaseMask |
++ PointerMotionMask, GrabModeAsync,
++ GrabModeAsync, None, invisible, CurrentTime);
++ }
++ if (kbgrab != GrabSuccess) {
++ kbgrab = XGrabKeyboard(dpy, lock->root, True,
++ GrabModeAsync, GrabModeAsync, CurrentTime);
++ }
++
++ /* input is grabbed: we can lock the screen */
++ if (ptgrab == GrabSuccess && kbgrab == GrabSuccess) {
++ XMapRaised(dpy, lock->win);
++ if (rr->active)
++ XRRSelectInput(dpy, lock->win, RRScreenChangeNotifyMask);
++
++ XSelectInput(dpy, lock->root, SubstructureNotifyMask);
++ drawlogo(dpy, lock, INIT);
++ return lock;
++ }
++
++ /* retry on AlreadyGrabbed but fail on other errors */
++ if ((ptgrab != AlreadyGrabbed && ptgrab != GrabSuccess) ||
++ (kbgrab != AlreadyGrabbed && kbgrab != GrabSuccess))
++ break;
++
++ usleep(100000);
++ }
++
++ /* we couldn't grab all input: fail out */
++ if (ptgrab != GrabSuccess)
++ fprintf(stderr, "slock: unable to grab mouse pointer for screen %d\n",
++ screen);
++ if (kbgrab != GrabSuccess)
++ fprintf(stderr, "slock: unable to grab keyboard for screen %d\n",
++ screen);
++ return NULL;
++}
++
++static void
++usage(void)
++{
++ die("usage: slock [-v] [cmd [arg ...]]\n");
++}
++
++int
++main(int argc, char **argv) {
++ struct xrandr rr;
++ struct lock **locks;
++ struct passwd *pwd;
++ struct group *grp;
++ uid_t duid;
++ gid_t dgid;
++ const char *hash;
++ Display *dpy;
++ int s, nlocks, nscreens;
++
++ ARGBEGIN {
++ case 'v':
++ fprintf(stderr, "slock-"VERSION"\n");
++ return 0;
++ default:
++ usage();
++ } ARGEND
++
++ /* validate drop-user and -group */
++ errno = 0;
++ if (!(pwd = getpwnam(user)))
++ die("slock: getpwnam %s: %s\n", user,
++ errno ? strerror(errno) : "user entry not found");
++ duid = pwd->pw_uid;
++ errno = 0;
++ if (!(grp = getgrnam(group)))
++ die("slock: getgrnam %s: %s\n", group,
++ errno ? strerror(errno) : "group entry not found");
++ dgid = grp->gr_gid;
++
++#ifdef __linux__
++ dontkillme();
++#endif
++
++ hash = gethash();
++ errno = 0;
++ if (!crypt("", hash))
++ die("slock: crypt: %s\n", strerror(errno));
++
++ if (!(dpy = XOpenDisplay(NULL)))
++ die("slock: cannot open display\n");
++
++ /* drop privileges */
++ if (setgroups(0, NULL) < 0)
++ die("slock: setgroups: %s\n", strerror(errno));
++ if (setgid(dgid) < 0)
++ die("slock: setgid: %s\n", strerror(errno));
++ if (setuid(duid) < 0)
++ die("slock: setuid: %s\n", strerror(errno));
++
++ /* check for Xrandr support */
++ rr.active = XRRQueryExtension(dpy, &rr.evbase, &rr.errbase);
++
++ /* get number of screens in display "dpy" and blank them */
++ nscreens = ScreenCount(dpy);
++ if (!(locks = calloc(nscreens, sizeof(struct lock *))))
++ die("slock: out of memory\n");
++ for (nlocks = 0, s = 0; s < nscreens; s++) {
++ if ((locks[s] = lockscreen(dpy, &rr, s)) != NULL)
++ nlocks++;
++ else
++ break;
++ }
++ XSync(dpy, 0);
++
++ /* did we manage to lock everything? */
++ if (nlocks != nscreens)
++ return 1;
++
++ /* run post-lock command */
++ if (argc > 0) {
++ switch (fork()) {
++ case -1:
++ die("slock: fork failed: %s\n", strerror(errno));
++ case 0:
++ if (close(ConnectionNumber(dpy)) < 0)
++ die("slock: close: %s\n", strerror(errno));
++ execvp(argv[0], argv);
++ fprintf(stderr, "slock: execvp %s: %s\n", argv[0], strerror(errno));
++ _exit(1);
++ }
++ }
++
++ /* everything is now blank. Wait for the correct password */
++ readpw(dpy, &rr, locks, nscreens, hash);
++
++ for (nlocks = 0, s = 0; s < nscreens; s++) {
++ XFreePixmap(dpy, locks[s]->drawable);
++ XFreeGC(dpy, locks[s]->gc);
++ }
++
++ XSync(dpy, 0);
++ XCloseDisplay(dpy);
++ return 0;
++}
+diff --git a/slock/util.h b/slock/util.h
+new file mode 100644
+index 0000000..6f748b8
+--- /dev/null
++++ b/slock/util.h
+@@ -0,0 +1,2 @@
++#undef explicit_bzero
++void explicit_bzero(void *, size_t);
+diff --git a/slstatus/LICENSE b/slstatus/LICENSE
+new file mode 100644
+index 0000000..8bee9c8
+--- /dev/null
++++ b/slstatus/LICENSE
+@@ -0,0 +1,43 @@
++ISC License
++
++Copyright 2016-2022 Aaron Marcher <me@drkhsh.at>
++
++Copyright 2016 Roy Freytag <rfreytag@hs-mittweida.de>
++Copyright 2016 Vincent Loupmon <vincentloupmon@gmail.com>
++Copyright 2016 Daniel Walter <d.walter@0x90.at>
++Copyright 2016-2018 Ali H. Fardan <raiz@firemail.cc>
++Copyright 2016 Jody Leonard <me@jodyleonard.com>
++Copyright 2016-2018 Quentin Rameau <quinq@fifth.space>
++Copyright 2016 Mike Coddington <mike@coddington.us>
++Copyright 2016-2018 Ivan J. <parazyd@dyne.org>
++Copyright 2017 Tobias Stoeckmann <tobias@stoeckmann.org>
++Copyright 2017-2018 Laslo Hunhold <dev@frign.de>
++Copyright 2018 Darron Anderson <darronanderson@protonmail.com>
++Copyright 2018 Josuah Demangeon <mail@josuah.net>
++Copyright 2018 Tobias Tschinkowitz <tobias@he4d.net>
++Copyright 2018 David Demelier <markand@malikania.fr>
++Copyright 2018-2012 Michael Buch <michaelbuch12@gmail.com>
++Copyright 2018 Ian Remmler <ian@remmler.org>
++Copyright 2016-2019 Joerg Jung <jung@openbsd.org>
++Copyright 2019 Ryan Kes <alrayyes@gmail.com>
++Copyright 2019 Cem Keylan <cem@ckyln.com>
++Copyright 2019 Dimitris Papastamos <dsp@2f30.org>
++Copyright 2019-2022 Ingo Feinerer <feinerer@logic.at>
++Copyright 2020 Alexandre Ratchov <alex@caoua.org>
++Copyright 2020 Mart Lubbers <mart@martlubbers.net>
++Copyright 2020 Daniel Moch <daniel@danielmoch.com>
++Copyright 2022 Nickolas Raymond Kaczynski <nrk@disroot.org>
++Copyright 2022 Patrick Iacob <iacobp@oregonstate.edu>
++Copyright 2021-2022 Steven Ward <planet36@gmail.com>
++
++Permission to use, copy, modify, and/or distribute this software for any
++purpose with or without fee is hereby granted, provided that the above
++copyright notice and this permission notice appear in all copies.
++
++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
++WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
++MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
++ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
++WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
++ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
++OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+diff --git a/slstatus/Makefile b/slstatus/Makefile
+new file mode 100644
+index 0000000..43b8a3d
+--- /dev/null
++++ b/slstatus/Makefile
+@@ -0,0 +1,70 @@
++# See LICENSE file for copyright and license details
++# slstatus - suckless status monitor
++.POSIX:
++
++include config.mk
++
++REQ = util
++COM =\
++ components/battery\
++ components/cat\
++ components/cpu\
++ components/datetime\
++ components/disk\
++ components/entropy\
++ components/hostname\
++ components/ip\
++ components/kanji\
++ components/kernel_release\
++ components/keyboard_indicators\
++ components/keymap\
++ components/load_avg\
++ components/netspeeds\
++ components/num_files\
++ components/ram\
++ components/run_command\
++ components/swap\
++ components/temperature\
++ components/uptime\
++ components/user\
++ components/volume\
++ components/wifi
++
++all: slstatus
++
++$(COM:=.o): config.mk $(REQ:=.h) slstatus.h
++slstatus.o: slstatus.c slstatus.h arg.h config.h config.mk $(REQ:=.h)
++
++.c.o:
++ $(CC) -o $@ -c $(CPPFLAGS) $(CFLAGS) $<
++
++config.h:
++ cp config.def.h $@
++
++slstatus: slstatus.o $(COM:=.o) $(REQ:=.o)
++ $(CC) -o $@ $(LDFLAGS) $(COM:=.o) $(REQ:=.o) slstatus.o $(LDLIBS)
++
++clean:
++ rm -f config.h slstatus slstatus.o $(COM:=.o) $(REQ:=.o) slstatus-${VERSION}.tar.gz
++
++dist:
++ rm -rf "slstatus-$(VERSION)"
++ mkdir -p "slstatus-$(VERSION)/components"
++ cp -R LICENSE Makefile README config.mk config.def.h \
++ arg.h slstatus.h slstatus.c $(REQ:=.c) $(REQ:=.h) \
++ slstatus.1 "slstatus-$(VERSION)"
++ cp -R $(COM:=.c) "slstatus-$(VERSION)/components"
++ tar -cf - "slstatus-$(VERSION)" | gzip -c > "slstatus-$(VERSION).tar.gz"
++ rm -rf "slstatus-$(VERSION)"
++
++install: all
++ mkdir -p "$(DESTDIR)$(PREFIX)/bin"
++ cp -f slstatus "$(DESTDIR)$(PREFIX)/bin"
++ chmod 755 "$(DESTDIR)$(PREFIX)/bin/slstatus"
++ mkdir -p "$(DESTDIR)$(MANPREFIX)/man1"
++ cp -f slstatus.1 "$(DESTDIR)$(MANPREFIX)/man1"
++ chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1"
++
++uninstall:
++ rm -f "$(DESTDIR)$(PREFIX)/bin/slstatus"
++ rm -f "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1"
+diff --git a/slstatus/README b/slstatus/README
+new file mode 100644
+index 0000000..12d38bf
+--- /dev/null
++++ b/slstatus/README
+@@ -0,0 +1,65 @@
++slstatus - suckless status
++==========================
++slstatus is a small tool for providing system status information to other
++programs over the EWMH property of the root window (used by dwm(1)) or
++standard input/output. It is designed to be as efficient as possible by
++only issuing the minimum of system calls required.
++
++
++Features
++--------
++- Battery percentage/state/time left
++- Cat (read file)
++- CPU usage
++- CPU frequency
++- Custom shell commands
++- Date and time
++- Disk status (free storage, percentage, total storage and used storage)
++- Available entropy
++- Username/GID/UID
++- Hostname
++- IP address (IPv4 and IPv6)
++- Kernel version
++- Keyboard indicators
++- Keymap
++- Load average
++- Network speeds (RX and TX)
++- Number of files in a directory (hint: Maildir)
++- Memory status (free memory, percentage, total memory and used memory)
++- Swap status (free swap, percentage, total swap and used swap)
++- Temperature
++- Uptime
++- Volume percentage
++- WiFi signal percentage and ESSID
++
++
++Requirements
++------------
++Currently slstatus works on FreeBSD, Linux and OpenBSD.
++In order to build slstatus you need the Xlib header files.
++
++- For volume percentage on Linux the kernel module `snd-mixer-oss` must be
++ loaded.
++- For volume percentage on FreeBSD, `sndio` must be installed.
++
++
++Installation
++------------
++Edit config.mk to match your local setup (slstatus is installed into the
++/usr/local namespace by default).
++
++Afterwards enter the following command to build and install slstatus (if
++necessary as root):
++
++ make clean install
++
++
++Running slstatus
++----------------
++See the man page for details.
++
++
++Configuration
++-------------
++slstatus can be customized by creating a custom config.h and (re)compiling the
++source code. This keeps it fast, secure and simple.
+diff --git a/slstatus/arg.h b/slstatus/arg.h
+new file mode 100644
+index 0000000..5f1f408
+--- /dev/null
++++ b/slstatus/arg.h
+@@ -0,0 +1,33 @@
++/* See LICENSE file for copyright and license details. */
++#ifndef ARG_H
++#define ARG_H
++
++extern char *argv0;
++
++/* int main(int argc, char *argv[]) */
++#define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \
++ *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \
++ int i_, argused_; \
++ if ((*argv)[1] == '-' && !(*argv)[2]) { \
++ argc--, argv++; \
++ break; \
++ } \
++ for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \
++ switch ((*argv)[i_])
++#define ARGEND if (argused_) { \
++ if ((*argv)[i_ + 1]) { \
++ break; \
++ } else { \
++ argc--, argv++; \
++ break; \
++ } \
++ } \
++ } \
++ }
++#define ARGC() ((*argv)[i_])
++#define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \
++ (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x))
++#define EARGF(x) ARGF_(((x), exit(1), (char *)0))
++#define ARGF() ARGF_((char *)0)
++
++#endif
+diff --git a/slstatus/components/battery.c b/slstatus/components/battery.c
+new file mode 100644
+index 0000000..1c753f9
+--- /dev/null
++++ b/slstatus/components/battery.c
+@@ -0,0 +1,247 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <string.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(__linux__)
++/*
++ * https://www.kernel.org/doc/html/latest/power/power_supply_class.html
++ */
++ #include <limits.h>
++ #include <stdint.h>
++ #include <unistd.h>
++
++ #define POWER_SUPPLY_CAPACITY "/sys/class/power_supply/%s/capacity"
++ #define POWER_SUPPLY_STATUS "/sys/class/power_supply/%s/status"
++ #define POWER_SUPPLY_CHARGE "/sys/class/power_supply/%s/charge_now"
++ #define POWER_SUPPLY_ENERGY "/sys/class/power_supply/%s/energy_now"
++ #define POWER_SUPPLY_CURRENT "/sys/class/power_supply/%s/current_now"
++ #define POWER_SUPPLY_POWER "/sys/class/power_supply/%s/power_now"
++
++ static const char *
++ pick(const char *bat, const char *f1, const char *f2, char *path,
++ size_t length)
++ {
++ if (esnprintf(path, length, f1, bat) > 0 &&
++ access(path, R_OK) == 0)
++ return f1;
++
++ if (esnprintf(path, length, f2, bat) > 0 &&
++ access(path, R_OK) == 0)
++ return f2;
++
++ return NULL;
++ }
++
++ const char *
++ battery_perc(const char *bat)
++ {
++ int cap_perc;
++ char path[PATH_MAX];
++
++ if (esnprintf(path, sizeof(path), POWER_SUPPLY_CAPACITY, bat) < 0)
++ return NULL;
++ if (pscanf(path, "%d", &cap_perc) != 1)
++ return NULL;
++
++ return bprintf("%d", cap_perc);
++ }
++
++ const char *
++ battery_state(const char *bat)
++ {
++ static struct {
++ char *state;
++ char *symbol;
++ } map[] = {
++ { "Charging", "+" },
++ { "Discharging", "-" },
++ { "Full", "o" },
++ { "Not charging", "o" },
++ };
++ size_t i;
++ char path[PATH_MAX], state[12];
++
++ if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
++ return NULL;
++ if (pscanf(path, "%12[a-zA-Z ]", state) != 1)
++ return NULL;
++
++ for (i = 0; i < LEN(map); i++)
++ if (!strcmp(map[i].state, state))
++ break;
++
++ return (i == LEN(map)) ? "?" : map[i].symbol;
++ }
++
++ const char *
++ battery_remaining(const char *bat)
++ {
++ uintmax_t charge_now, current_now, m, h;
++ double timeleft;
++ char path[PATH_MAX], state[12];
++
++ if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
++ return NULL;
++ if (pscanf(path, "%12[a-zA-Z ]", state) != 1)
++ return NULL;
++
++ if (!pick(bat, POWER_SUPPLY_CHARGE, POWER_SUPPLY_ENERGY, path,
++ sizeof(path)) ||
++ pscanf(path, "%ju", &charge_now) < 0)
++ return NULL;
++
++ if (!strcmp(state, "Discharging")) {
++ if (!pick(bat, POWER_SUPPLY_CURRENT, POWER_SUPPLY_POWER, path,
++ sizeof(path)) ||
++ pscanf(path, "%ju", ¤t_now) < 0)
++ return NULL;
++
++ if (current_now == 0)
++ return NULL;
++
++ timeleft = (double)charge_now / (double)current_now;
++ h = timeleft;
++ m = (timeleft - (double)h) * 60;
++
++ return bprintf("%juh %jum", h, m);
++ }
++
++ return "";
++ }
++#elif defined(__OpenBSD__)
++ #include <fcntl.h>
++ #include <machine/apmvar.h>
++ #include <sys/ioctl.h>
++ #include <unistd.h>
++
++ static int
++ load_apm_power_info(struct apm_power_info *apm_info)
++ {
++ int fd;
++
++ fd = open("/dev/apm", O_RDONLY);
++ if (fd < 0) {
++ warn("open '/dev/apm':");
++ return 0;
++ }
++
++ memset(apm_info, 0, sizeof(struct apm_power_info));
++ if (ioctl(fd, APM_IOC_GETPOWER, apm_info) < 0) {
++ warn("ioctl 'APM_IOC_GETPOWER':");
++ close(fd);
++ return 0;
++ }
++ return close(fd), 1;
++ }
++
++ const char *
++ battery_perc(const char *unused)
++ {
++ struct apm_power_info apm_info;
++
++ if (load_apm_power_info(&apm_info))
++ return bprintf("%d", apm_info.battery_life);
++
++ return NULL;
++ }
++
++ const char *
++ battery_state(const char *unused)
++ {
++ struct {
++ unsigned int state;
++ char *symbol;
++ } map[] = {
++ { APM_AC_ON, "+" },
++ { APM_AC_OFF, "-" },
++ };
++ struct apm_power_info apm_info;
++ size_t i;
++
++ if (load_apm_power_info(&apm_info)) {
++ for (i = 0; i < LEN(map); i++)
++ if (map[i].state == apm_info.ac_state)
++ break;
++
++ return (i == LEN(map)) ? "?" : map[i].symbol;
++ }
++
++ return NULL;
++ }
++
++ const char *
++ battery_remaining(const char *unused)
++ {
++ struct apm_power_info apm_info;
++ unsigned int h, m;
++
++ if (load_apm_power_info(&apm_info)) {
++ if (apm_info.ac_state != APM_AC_ON) {
++ h = apm_info.minutes_left / 60;
++ m = apm_info.minutes_left % 60;
++ return bprintf("%uh %02um", h, m);
++ } else {
++ return "";
++ }
++ }
++
++ return NULL;
++ }
++#elif defined(__FreeBSD__)
++ #include <sys/sysctl.h>
++
++ #define BATTERY_LIFE "hw.acpi.battery.life"
++ #define BATTERY_STATE "hw.acpi.battery.state"
++ #define BATTERY_TIME "hw.acpi.battery.time"
++
++ const char *
++ battery_perc(const char *unused)
++ {
++ int cap_perc;
++ size_t len;
++
++ len = sizeof(cap_perc);
++ if (sysctlbyname(BATTERY_LIFE, &cap_perc, &len, NULL, 0) < 0 || !len)
++ return NULL;
++
++ return bprintf("%d", cap_perc);
++ }
++
++ const char *
++ battery_state(const char *unused)
++ {
++ int state;
++ size_t len;
++
++ len = sizeof(state);
++ if (sysctlbyname(BATTERY_STATE, &state, &len, NULL, 0) < 0 || !len)
++ return NULL;
++
++ switch (state) {
++ case 0: /* FALLTHROUGH */
++ case 2:
++ return "+";
++ case 1:
++ return "-";
++ default:
++ return "?";
++ }
++ }
++
++ const char *
++ battery_remaining(const char *unused)
++ {
++ int rem;
++ size_t len;
++
++ len = sizeof(rem);
++ if (sysctlbyname(BATTERY_TIME, &rem, &len, NULL, 0) < 0 || !len
++ || rem < 0)
++ return NULL;
++
++ return bprintf("%uh %02um", rem / 60, rem % 60);
++ }
++#endif
+diff --git a/slstatus/components/battery.o b/slstatus/components/battery.o
+new file mode 100644
+index 0000000..4c63c60
+Binary files /dev/null and b/slstatus/components/battery.o differ
+diff --git a/slstatus/components/cat.c b/slstatus/components/cat.c
+new file mode 100644
+index 0000000..07944cc
+--- /dev/null
++++ b/slstatus/components/cat.c
+@@ -0,0 +1,32 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <string.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++cat(const char *path)
++{
++ char *f;
++ FILE *fp;
++
++ if (!(fp = fopen(path, "r"))) {
++ warn("fopen '%s':", path);
++ return NULL;
++ }
++
++ f = fgets(buf, sizeof(buf) - 1, fp);
++ if (fclose(fp) < 0) {
++ warn("fclose '%s':", path);
++ return NULL;
++ }
++ if (!f)
++ return NULL;
++
++ if ((f = strrchr(buf, '\n')))
++ f[0] = '\0';
++
++ return buf[0] ? buf : NULL;
++}
++
+diff --git a/slstatus/components/cat.o b/slstatus/components/cat.o
+new file mode 100644
+index 0000000..3b50ebc
+Binary files /dev/null and b/slstatus/components/cat.o differ
+diff --git a/slstatus/components/cpu.c b/slstatus/components/cpu.c
+new file mode 100644
+index 0000000..d0d03c7
+--- /dev/null
++++ b/slstatus/components/cpu.c
+@@ -0,0 +1,157 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdint.h>
++#include <stdio.h>
++#include <string.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(__linux__)
++ #define CPU_FREQ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"
++
++ const char *
++ cpu_freq(const char *unused)
++ {
++ uintmax_t freq;
++
++ /* in kHz */
++ if (pscanf(CPU_FREQ, "%ju", &freq) != 1)
++ return NULL;
++
++ return fmt_human(freq * 1000, 1000);
++ }
++
++ const char *
++ cpu_perc(const char *unused)
++ {
++ static long double a[7];
++ long double b[7], sum;
++
++ memcpy(b, a, sizeof(b));
++ /* cpu user nice system idle iowait irq softirq */
++ if (pscanf("/proc/stat", "%*s %Lf %Lf %Lf %Lf %Lf %Lf %Lf",
++ &a[0], &a[1], &a[2], &a[3], &a[4], &a[5], &a[6])
++ != 7)
++ return NULL;
++
++ if (b[0] == 0)
++ return NULL;
++
++ sum = (b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6]) -
++ (a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6]);
++
++ if (sum == 0)
++ return NULL;
++
++ return bprintf("%d", (int)(100 *
++ ((b[0] + b[1] + b[2] + b[5] + b[6]) -
++ (a[0] + a[1] + a[2] + a[5] + a[6])) / sum));
++ }
++#elif defined(__OpenBSD__)
++ #include <sys/param.h>
++ #include <sys/sched.h>
++ #include <sys/sysctl.h>
++
++ const char *
++ cpu_freq(const char *unused)
++ {
++ int freq, mib[2];
++ size_t size;
++
++ mib[0] = CTL_HW;
++ mib[1] = HW_CPUSPEED;
++
++ size = sizeof(freq);
++
++ /* in MHz */
++ if (sysctl(mib, 2, &freq, &size, NULL, 0) < 0) {
++ warn("sysctl 'HW_CPUSPEED':");
++ return NULL;
++ }
++
++ return fmt_human(freq * 1E6, 1000);
++ }
++
++ const char *
++ cpu_perc(const char *unused)
++ {
++ int mib[2];
++ static uintmax_t a[CPUSTATES];
++ uintmax_t b[CPUSTATES], sum;
++ size_t size;
++
++ mib[0] = CTL_KERN;
++ mib[1] = KERN_CPTIME;
++
++ size = sizeof(a);
++
++ memcpy(b, a, sizeof(b));
++ if (sysctl(mib, 2, &a, &size, NULL, 0) < 0) {
++ warn("sysctl 'KERN_CPTIME':");
++ return NULL;
++ }
++ if (b[0] == 0)
++ return NULL;
++
++ sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) -
++ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]);
++
++ if (sum == 0)
++ return NULL;
++
++ return bprintf("%d", 100 *
++ ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] +
++ a[CP_INTR]) -
++ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] +
++ b[CP_INTR])) / sum);
++ }
++#elif defined(__FreeBSD__)
++ #include <devstat.h>
++ #include <sys/param.h>
++ #include <sys/sysctl.h>
++
++ const char *
++ cpu_freq(const char *unused)
++ {
++ int freq;
++ size_t size;
++
++ size = sizeof(freq);
++ /* in MHz */
++ if (sysctlbyname("hw.clockrate", &freq, &size, NULL, 0) < 0 || !size) {
++ warn("sysctlbyname 'hw.clockrate':");
++ return NULL;
++ }
++
++ return fmt_human(freq * 1E6, 1000);
++ }
++
++ const char *
++ cpu_perc(const char *unused)
++ {
++ size_t size;
++ static long a[CPUSTATES];
++ long b[CPUSTATES], sum;
++
++ size = sizeof(a);
++ memcpy(b, a, sizeof(b));
++ if (sysctlbyname("kern.cp_time", &a, &size, NULL, 0) < 0 || !size) {
++ warn("sysctlbyname 'kern.cp_time':");
++ return NULL;
++ }
++ if (b[0] == 0)
++ return NULL;
++
++ sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) -
++ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]);
++
++ if (sum == 0)
++ return NULL;
++
++ return bprintf("%d", 100 *
++ ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] +
++ a[CP_INTR]) -
++ (b[CP_USER] + b[CP_NICE] + b[CP_SYS] +
++ b[CP_INTR])) / sum);
++ }
++#endif
+diff --git a/slstatus/components/cpu.o b/slstatus/components/cpu.o
+new file mode 100644
+index 0000000..969d09c
+Binary files /dev/null and b/slstatus/components/cpu.o differ
+diff --git a/slstatus/components/datetime.c b/slstatus/components/datetime.c
+new file mode 100644
+index 0000000..5b10daf
+--- /dev/null
++++ b/slstatus/components/datetime.c
+@@ -0,0 +1,20 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <time.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++datetime(const char *fmt)
++{
++ time_t t;
++
++ t = time(NULL);
++ if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) {
++ warn("strftime: Result string exceeds buffer size");
++ return NULL;
++ }
++
++ return buf;
++}
+diff --git a/slstatus/components/datetime.o b/slstatus/components/datetime.o
+new file mode 100644
+index 0000000..0353444
+Binary files /dev/null and b/slstatus/components/datetime.o differ
+diff --git a/slstatus/components/disk.c b/slstatus/components/disk.c
+new file mode 100644
+index 0000000..e19a693
+--- /dev/null
++++ b/slstatus/components/disk.c
+@@ -0,0 +1,59 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <sys/statvfs.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++disk_free(const char *path)
++{
++ struct statvfs fs;
++
++ if (statvfs(path, &fs) < 0) {
++ warn("statvfs '%s':", path);
++ return NULL;
++ }
++
++ return fmt_human(fs.f_frsize * fs.f_bavail, 1024);
++}
++
++const char *
++disk_perc(const char *path)
++{
++ struct statvfs fs;
++
++ if (statvfs(path, &fs) < 0) {
++ warn("statvfs '%s':", path);
++ return NULL;
++ }
++
++ return bprintf("%d", (int)(100 *
++ (1 - ((double)fs.f_bavail / (double)fs.f_blocks))));
++}
++
++const char *
++disk_total(const char *path)
++{
++ struct statvfs fs;
++
++ if (statvfs(path, &fs) < 0) {
++ warn("statvfs '%s':", path);
++ return NULL;
++ }
++
++ return fmt_human(fs.f_frsize * fs.f_blocks, 1024);
++}
++
++const char *
++disk_used(const char *path)
++{
++ struct statvfs fs;
++
++ if (statvfs(path, &fs) < 0) {
++ warn("statvfs '%s':", path);
++ return NULL;
++ }
++
++ return fmt_human(fs.f_frsize * (fs.f_blocks - fs.f_bfree), 1024);
++}
+diff --git a/slstatus/components/disk.o b/slstatus/components/disk.o
+new file mode 100644
+index 0000000..5ec5c0a
+Binary files /dev/null and b/slstatus/components/disk.o differ
+diff --git a/slstatus/components/entropy.c b/slstatus/components/entropy.c
+new file mode 100644
+index 0000000..65010b0
+--- /dev/null
++++ b/slstatus/components/entropy.c
+@@ -0,0 +1,29 @@
++/* See LICENSE file for copyright and license details. */
++#include "../slstatus.h"
++#if defined(__linux__)
++ #include <stdint.h>
++ #include <stdio.h>
++
++ #include "../util.h"
++
++ #define ENTROPY_AVAIL "/proc/sys/kernel/random/entropy_avail"
++
++ const char *
++ entropy(const char *unused)
++ {
++ uintmax_t num;
++
++ if (pscanf(ENTROPY_AVAIL, "%ju", &num) != 1)
++ return NULL;
++
++ return bprintf("%ju", num);
++ }
++#elif defined(__OpenBSD__) | defined(__FreeBSD__)
++ const char *
++ entropy(const char *unused)
++ {
++ // https://www.unicode.org/charts/PDF/U2200.pdf
++ /* Unicode Character 'INFINITY' (U+221E) */
++ return "\u221E";
++ }
++#endif
+diff --git a/slstatus/components/entropy.o b/slstatus/components/entropy.o
+new file mode 100644
+index 0000000..6af57ba
+Binary files /dev/null and b/slstatus/components/entropy.o differ
+diff --git a/slstatus/components/hostname.c b/slstatus/components/hostname.c
+new file mode 100644
+index 0000000..dab8b63
+--- /dev/null
++++ b/slstatus/components/hostname.c
+@@ -0,0 +1,17 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <unistd.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++hostname(const char *unused)
++{
++ if (gethostname(buf, sizeof(buf)) < 0) {
++ warn("gethostbyname:");
++ return NULL;
++ }
++
++ return buf;
++}
+diff --git a/slstatus/components/hostname.o b/slstatus/components/hostname.o
+new file mode 100644
+index 0000000..4384f7c
+Binary files /dev/null and b/slstatus/components/hostname.o differ
+diff --git a/slstatus/components/ip.c b/slstatus/components/ip.c
+new file mode 100644
+index 0000000..9476549
+--- /dev/null
++++ b/slstatus/components/ip.c
+@@ -0,0 +1,61 @@
++/* See LICENSE file for copyright and license details. */
++#include <ifaddrs.h>
++#include <netdb.h>
++#include <stdio.h>
++#include <string.h>
++#if defined(__OpenBSD__)
++ #include <sys/socket.h>
++ #include <sys/types.h>
++#elif defined(__FreeBSD__)
++ #include <netinet/in.h>
++ #include <sys/socket.h>
++#endif
++
++#include "../slstatus.h"
++#include "../util.h"
++
++static const char *
++ip(const char *interface, unsigned short sa_family)
++{
++ struct ifaddrs *ifaddr, *ifa;
++ int s;
++ char host[NI_MAXHOST];
++
++ if (getifaddrs(&ifaddr) < 0) {
++ warn("getifaddrs:");
++ return NULL;
++ }
++
++ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
++ if (!ifa->ifa_addr)
++ continue;
++
++ s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6),
++ host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
++ if (!strcmp(ifa->ifa_name, interface) &&
++ (ifa->ifa_addr->sa_family == sa_family)) {
++ freeifaddrs(ifaddr);
++ if (s != 0) {
++ warn("getnameinfo: %s", gai_strerror(s));
++ return NULL;
++ }
++ return bprintf("%s", host);
++ }
++ }
++
++ freeifaddrs(ifaddr);
++
++ return NULL;
++}
++
++const char *
++ipv4(const char *interface)
++{
++ return ip(interface, AF_INET);
++}
++
++const char *
++ipv6(const char *interface)
++{
++ return ip(interface, AF_INET6);
++}
+diff --git a/slstatus/components/ip.o b/slstatus/components/ip.o
+new file mode 100644
+index 0000000..22f306d
+Binary files /dev/null and b/slstatus/components/ip.o differ
+diff --git a/slstatus/components/kanji.c b/slstatus/components/kanji.c
+new file mode 100644
+index 0000000..3d156e4
+--- /dev/null
++++ b/slstatus/components/kanji.c
+@@ -0,0 +1,14 @@
++/* Written by Madison Lynch <madi@mxdi.xyz> */
++#include <time.h>
++
++const char *
++kanji(const char *unused) {
++ char *kanji[] = {"日", "月", "火", "水", "木", "金", "土"};
++ time_t t=time(NULL);
++ struct tm tm=*localtime(&t);
++ int map[]={0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4},
++ m=tm.tm_mon+1,
++ y=tm.tm_year+1900-(m<3),
++ wd=(y+y/4-y/100+y/400+map[m-1]+tm.tm_mday)%7;
++ return kanji[wd];
++}
+diff --git a/slstatus/components/kanji.o b/slstatus/components/kanji.o
+new file mode 100644
+index 0000000..304e0e5
+Binary files /dev/null and b/slstatus/components/kanji.o differ
+diff --git a/slstatus/components/kernel_release.c b/slstatus/components/kernel_release.c
+new file mode 100644
+index 0000000..36a6a44
+--- /dev/null
++++ b/slstatus/components/kernel_release.c
+@@ -0,0 +1,19 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <sys/utsname.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++kernel_release(const char *unused)
++{
++ struct utsname udata;
++
++ if (uname(&udata) < 0) {
++ warn("uname:");
++ return NULL;
++ }
++
++ return bprintf("%s", udata.release);
++}
+diff --git a/slstatus/components/kernel_release.o b/slstatus/components/kernel_release.o
+new file mode 100644
+index 0000000..be0be90
+Binary files /dev/null and b/slstatus/components/kernel_release.o differ
+diff --git a/slstatus/components/keyboard_indicators.c b/slstatus/components/keyboard_indicators.c
+new file mode 100644
+index 0000000..5f62bb7
+--- /dev/null
++++ b/slstatus/components/keyboard_indicators.c
+@@ -0,0 +1,50 @@
++/* See LICENSE file for copyright and license details. */
++#include <ctype.h>
++#include <stdio.h>
++#include <string.h>
++#include <X11/Xlib.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++/*
++ * fmt consists of uppercase or lowercase 'c' for caps lock and/or 'n' for num
++ * lock, each optionally followed by '?', in the order of indicators desired.
++ * If followed by '?', the letter with case preserved is included in the output
++ * if the corresponding indicator is on. Otherwise, the letter is always
++ * included, lowercase when off and uppercase when on.
++ */
++const char *
++keyboard_indicators(const char *fmt)
++{
++ Display *dpy;
++ XKeyboardState state;
++ size_t fmtlen, i, n;
++ int togglecase, isset;
++ char key;
++
++ if (!(dpy = XOpenDisplay(NULL))) {
++ warn("XOpenDisplay: Failed to open display");
++ return NULL;
++ }
++ XGetKeyboardControl(dpy, &state);
++ XCloseDisplay(dpy);
++
++ fmtlen = strnlen(fmt, 4);
++ for (i = n = 0; i < fmtlen; i++) {
++ key = tolower(fmt[i]);
++ if (key != 'c' && key != 'n')
++ continue;
++
++ togglecase = (i + 1 >= fmtlen || fmt[i + 1] != '?');
++ isset = (state.led_mask & (1 << (key == 'n')));
++
++ if (togglecase)
++ buf[n++] = isset ? toupper(key) : key;
++ else if (isset)
++ buf[n++] = fmt[i];
++ }
++
++ buf[n] = 0;
++ return buf;
++}
+diff --git a/slstatus/components/keyboard_indicators.o b/slstatus/components/keyboard_indicators.o
+new file mode 100644
+index 0000000..001b37f
+Binary files /dev/null and b/slstatus/components/keyboard_indicators.o differ
+diff --git a/slstatus/components/keymap.c b/slstatus/components/keymap.c
+new file mode 100644
+index 0000000..f8a2a47
+--- /dev/null
++++ b/slstatus/components/keymap.c
+@@ -0,0 +1,86 @@
++/* See LICENSE file for copyright and license details. */
++#include <ctype.h>
++#include <stdlib.h>
++#include <string.h>
++#include <X11/XKBlib.h>
++#include <X11/Xlib.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++static int
++valid_layout_or_variant(char *sym)
++{
++ size_t i;
++ /* invalid symbols from xkb rules config */
++ static const char *invalid[] = { "evdev", "inet", "pc", "base" };
++
++ for (i = 0; i < LEN(invalid); i++)
++ if (!strncmp(sym, invalid[i], strlen(invalid[i])))
++ return 0;
++
++ return 1;
++}
++
++static char *
++get_layout(char *syms, int grp_num)
++{
++ char *tok, *layout;
++ int grp;
++
++ layout = NULL;
++ tok = strtok(syms, "+:");
++ for (grp = 0; tok && grp <= grp_num; tok = strtok(NULL, "+:")) {
++ if (!valid_layout_or_variant(tok)) {
++ continue;
++ } else if (strlen(tok) == 1 && isdigit(tok[0])) {
++ /* ignore :2, :3, :4 (additional layout groups) */
++ continue;
++ }
++ layout = tok;
++ grp++;
++ }
++
++ return layout;
++}
++
++const char *
++keymap(const char *unused)
++{
++ Display *dpy;
++ XkbDescRec *desc;
++ XkbStateRec state;
++ char *symbols;
++ const char *layout;
++
++ layout = NULL;
++
++ if (!(dpy = XOpenDisplay(NULL))) {
++ warn("XOpenDisplay: Failed to open display");
++ return NULL;
++ }
++ if (!(desc = XkbAllocKeyboard())) {
++ warn("XkbAllocKeyboard: Failed to allocate keyboard");
++ goto end;
++ }
++ if (XkbGetNames(dpy, XkbSymbolsNameMask, desc)) {
++ warn("XkbGetNames: Failed to retrieve key symbols");
++ goto end;
++ }
++ if (XkbGetState(dpy, XkbUseCoreKbd, &state)) {
++ warn("XkbGetState: Failed to retrieve keyboard state");
++ goto end;
++ }
++ if (!(symbols = XGetAtomName(dpy, desc->names->symbols))) {
++ warn("XGetAtomName: Failed to get atom name");
++ goto end;
++ }
++ layout = bprintf("%s", get_layout(symbols, state.group));
++ XFree(symbols);
++end:
++ XkbFreeKeyboard(desc, XkbSymbolsNameMask, 1);
++ if (XCloseDisplay(dpy))
++ warn("XCloseDisplay: Failed to close display");
++
++ return layout;
++}
+diff --git a/slstatus/components/keymap.o b/slstatus/components/keymap.o
+new file mode 100644
+index 0000000..b9c0bca
+Binary files /dev/null and b/slstatus/components/keymap.o differ
+diff --git a/slstatus/components/load_avg.c b/slstatus/components/load_avg.c
+new file mode 100644
+index 0000000..f278a40
+--- /dev/null
++++ b/slstatus/components/load_avg.c
+@@ -0,0 +1,19 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <stdlib.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++load_avg(const char *unused)
++{
++ double avgs[3];
++
++ if (getloadavg(avgs, 3) < 0) {
++ warn("getloadavg: Failed to obtain load average");
++ return NULL;
++ }
++
++ return bprintf("%.2f %.2f %.2f", avgs[0], avgs[1], avgs[2]);
++}
+diff --git a/slstatus/components/load_avg.o b/slstatus/components/load_avg.o
+new file mode 100644
+index 0000000..0861fb7
+Binary files /dev/null and b/slstatus/components/load_avg.o differ
+diff --git a/slstatus/components/netspeeds.c b/slstatus/components/netspeeds.c
+new file mode 100644
+index 0000000..cde6fa9
+--- /dev/null
++++ b/slstatus/components/netspeeds.c
+@@ -0,0 +1,129 @@
++/* See LICENSE file for copyright and license details. */
++#include <limits.h>
++#include <stdio.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(__linux__)
++ #include <stdint.h>
++
++ #define NET_RX_BYTES "/sys/class/net/%s/statistics/rx_bytes"
++ #define NET_TX_BYTES "/sys/class/net/%s/statistics/tx_bytes"
++
++ const char *
++ netspeed_rx(const char *interface)
++ {
++ uintmax_t oldrxbytes;
++ static uintmax_t rxbytes;
++ extern const unsigned int interval;
++ char path[PATH_MAX];
++
++ oldrxbytes = rxbytes;
++
++ if (esnprintf(path, sizeof(path), NET_RX_BYTES, interface) < 0)
++ return NULL;
++ if (pscanf(path, "%ju", &rxbytes) != 1)
++ return NULL;
++ if (oldrxbytes == 0)
++ return NULL;
++
++ return fmt_human((rxbytes - oldrxbytes) * 1000 / interval,
++ 1024);
++ }
++
++ const char *
++ netspeed_tx(const char *interface)
++ {
++ uintmax_t oldtxbytes;
++ static uintmax_t txbytes;
++ extern const unsigned int interval;
++ char path[PATH_MAX];
++
++ oldtxbytes = txbytes;
++
++ if (esnprintf(path, sizeof(path), NET_TX_BYTES, interface) < 0)
++ return NULL;
++ if (pscanf(path, "%ju", &txbytes) != 1)
++ return NULL;
++ if (oldtxbytes == 0)
++ return NULL;
++
++ return fmt_human((txbytes - oldtxbytes) * 1000 / interval,
++ 1024);
++ }
++#elif defined(__OpenBSD__) | defined(__FreeBSD__)
++ #include <ifaddrs.h>
++ #include <net/if.h>
++ #include <string.h>
++ #include <sys/types.h>
++ #include <sys/socket.h>
++
++ const char *
++ netspeed_rx(const char *interface)
++ {
++ struct ifaddrs *ifal, *ifa;
++ struct if_data *ifd;
++ uintmax_t oldrxbytes;
++ static uintmax_t rxbytes;
++ extern const unsigned int interval;
++ int if_ok = 0;
++
++ oldrxbytes = rxbytes;
++
++ if (getifaddrs(&ifal) < 0) {
++ warn("getifaddrs failed");
++ return NULL;
++ }
++ rxbytes = 0;
++ for (ifa = ifal; ifa; ifa = ifa->ifa_next)
++ if (!strcmp(ifa->ifa_name, interface) &&
++ (ifd = (struct if_data *)ifa->ifa_data))
++ rxbytes += ifd->ifi_ibytes, if_ok = 1;
++
++ freeifaddrs(ifal);
++ if (!if_ok) {
++ warn("reading 'if_data' failed");
++ return NULL;
++ }
++ if (oldrxbytes == 0)
++ return NULL;
++
++ return fmt_human((rxbytes - oldrxbytes) * 1000 / interval,
++ 1024);
++ }
++
++ const char *
++ netspeed_tx(const char *interface)
++ {
++ struct ifaddrs *ifal, *ifa;
++ struct if_data *ifd;
++ uintmax_t oldtxbytes;
++ static uintmax_t txbytes;
++ extern const unsigned int interval;
++ int if_ok = 0;
++
++ oldtxbytes = txbytes;
++
++ if (getifaddrs(&ifal) < 0) {
++ warn("getifaddrs failed");
++ return NULL;
++ }
++ txbytes = 0;
++ for (ifa = ifal; ifa; ifa = ifa->ifa_next)
++ if (!strcmp(ifa->ifa_name, interface) &&
++ (ifd = (struct if_data *)ifa->ifa_data))
++ txbytes += ifd->ifi_obytes, if_ok = 1;
++
++ freeifaddrs(ifal);
++ if (!if_ok) {
++ warn("reading 'if_data' failed");
++ return NULL;
++ }
++ if (oldtxbytes == 0)
++ return NULL;
++
++ return fmt_human((txbytes - oldtxbytes) * 1000 / interval,
++ 1024);
++ }
++#endif
+diff --git a/slstatus/components/netspeeds.o b/slstatus/components/netspeeds.o
+new file mode 100644
+index 0000000..fe9aca1
+Binary files /dev/null and b/slstatus/components/netspeeds.o differ
+diff --git a/slstatus/components/num_files.c b/slstatus/components/num_files.c
+new file mode 100644
+index 0000000..df0acd1
+--- /dev/null
++++ b/slstatus/components/num_files.c
+@@ -0,0 +1,32 @@
++/* See LICENSE file for copyright and license details. */
++#include <dirent.h>
++#include <stdio.h>
++#include <string.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++num_files(const char *path)
++{
++ struct dirent *dp;
++ DIR *dir;
++ int num;
++
++ if (!(dir = opendir(path))) {
++ warn("opendir '%s':", path);
++ return NULL;
++ }
++
++ num = 0;
++ while ((dp = readdir(dir))) {
++ if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
++ continue; /* skip self and parent */
++
++ num++;
++ }
++
++ closedir(dir);
++
++ return bprintf("%d", num);
++}
+diff --git a/slstatus/components/num_files.o b/slstatus/components/num_files.o
+new file mode 100644
+index 0000000..15d1365
+Binary files /dev/null and b/slstatus/components/num_files.o differ
+diff --git a/slstatus/components/ram.c b/slstatus/components/ram.c
+new file mode 100644
+index 0000000..15c4b74
+--- /dev/null
++++ b/slstatus/components/ram.c
+@@ -0,0 +1,212 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(__linux__)
++ #include <stdint.h>
++
++ const char *
++ ram_free(const char *unused)
++ {
++ uintmax_t free;
++
++ if (pscanf("/proc/meminfo",
++ "MemTotal: %ju kB\n"
++ "MemFree: %ju kB\n"
++ "MemAvailable: %ju kB\n",
++ &free, &free, &free) != 3)
++ return NULL;
++
++ return fmt_human(free * 1024, 1024);
++ }
++
++ const char *
++ ram_perc(const char *unused)
++ {
++ uintmax_t total, free, buffers, cached;
++ int percent;
++
++ if (pscanf("/proc/meminfo",
++ "MemTotal: %ju kB\n"
++ "MemFree: %ju kB\n"
++ "MemAvailable: %ju kB\n"
++ "Buffers: %ju kB\n"
++ "Cached: %ju kB\n",
++ &total, &free, &buffers, &buffers, &cached) != 5)
++ return NULL;
++
++ if (total == 0)
++ return NULL;
++
++ percent = 100 * ((total - free) - (buffers + cached)) / total;
++ return bprintf("%d", percent);
++ }
++
++ const char *
++ ram_total(const char *unused)
++ {
++ uintmax_t total;
++
++ if (pscanf("/proc/meminfo", "MemTotal: %ju kB\n", &total)
++ != 1)
++ return NULL;
++
++ return fmt_human(total * 1024, 1024);
++ }
++
++ const char *
++ ram_used(const char *unused)
++ {
++ uintmax_t total, free, buffers, cached, used;
++
++ if (pscanf("/proc/meminfo",
++ "MemTotal: %ju kB\n"
++ "MemFree: %ju kB\n"
++ "MemAvailable: %ju kB\n"
++ "Buffers: %ju kB\n"
++ "Cached: %ju kB\n",
++ &total, &free, &buffers, &buffers, &cached) != 5)
++ return NULL;
++
++ used = (total - free - buffers - cached);
++ return fmt_human(used * 1024, 1024);
++ }
++#elif defined(__OpenBSD__)
++ #include <stdlib.h>
++ #include <sys/sysctl.h>
++ #include <sys/types.h>
++ #include <unistd.h>
++
++ #define LOG1024 10
++ #define pagetok(size, pageshift) (size_t)(size << (pageshift - LOG1024))
++
++ inline int
++ load_uvmexp(struct uvmexp *uvmexp)
++ {
++ int uvmexp_mib[] = {CTL_VM, VM_UVMEXP};
++ size_t size;
++
++ size = sizeof(*uvmexp);
++
++ if (sysctl(uvmexp_mib, 2, uvmexp, &size, NULL, 0) >= 0)
++ return 1;
++
++ return 0;
++ }
++
++ const char *
++ ram_free(const char *unused)
++ {
++ struct uvmexp uvmexp;
++ int free_pages;
++
++ if (!load_uvmexp(&uvmexp))
++ return NULL;
++
++ free_pages = uvmexp.npages - uvmexp.active;
++ return fmt_human(pagetok(free_pages, uvmexp.pageshift) *
++ 1024, 1024);
++ }
++
++ const char *
++ ram_perc(const char *unused)
++ {
++ struct uvmexp uvmexp;
++ int percent;
++
++ if (!load_uvmexp(&uvmexp))
++ return NULL;
++
++ percent = uvmexp.active * 100 / uvmexp.npages;
++ return bprintf("%d", percent);
++ }
++
++ const char *
++ ram_total(const char *unused)
++ {
++ struct uvmexp uvmexp;
++
++ if (!load_uvmexp(&uvmexp))
++ return NULL;
++
++ return fmt_human(pagetok(uvmexp.npages,
++ uvmexp.pageshift) * 1024, 1024);
++ }
++
++ const char *
++ ram_used(const char *unused)
++ {
++ struct uvmexp uvmexp;
++
++ if (!load_uvmexp(&uvmexp))
++ return NULL;
++
++ return fmt_human(pagetok(uvmexp.active,
++ uvmexp.pageshift) * 1024, 1024);
++ }
++#elif defined(__FreeBSD__)
++ #include <sys/sysctl.h>
++ #include <sys/vmmeter.h>
++ #include <unistd.h>
++ #include <vm/vm_param.h>
++
++ const char *
++ ram_free(const char *unused) {
++ struct vmtotal vm_stats;
++ int mib[] = {CTL_VM, VM_TOTAL};
++ size_t len;
++
++ len = sizeof(struct vmtotal);
++ if (sysctl(mib, 2, &vm_stats, &len, NULL, 0) < 0
++ || !len)
++ return NULL;
++
++ return fmt_human(vm_stats.t_free * getpagesize(), 1024);
++ }
++
++ const char *
++ ram_total(const char *unused) {
++ unsigned int npages;
++ size_t len;
++
++ len = sizeof(npages);
++ if (sysctlbyname("vm.stats.vm.v_page_count",
++ &npages, &len, NULL, 0) < 0 || !len)
++ return NULL;
++
++ return fmt_human(npages * getpagesize(), 1024);
++ }
++
++ const char *
++ ram_perc(const char *unused) {
++ unsigned int npages;
++ unsigned int active;
++ size_t len;
++
++ len = sizeof(npages);
++ if (sysctlbyname("vm.stats.vm.v_page_count",
++ &npages, &len, NULL, 0) < 0 || !len)
++ return NULL;
++
++ if (sysctlbyname("vm.stats.vm.v_active_count",
++ &active, &len, NULL, 0) < 0 || !len)
++ return NULL;
++
++ return bprintf("%d", active * 100 / npages);
++ }
++
++ const char *
++ ram_used(const char *unused) {
++ unsigned int active;
++ size_t len;
++
++ len = sizeof(active);
++ if (sysctlbyname("vm.stats.vm.v_active_count",
++ &active, &len, NULL, 0) < 0 || !len)
++ return NULL;
++
++ return fmt_human(active * getpagesize(), 1024);
++ }
++#endif
+diff --git a/slstatus/components/ram.o b/slstatus/components/ram.o
+new file mode 100644
+index 0000000..2dbd845
+Binary files /dev/null and b/slstatus/components/ram.o differ
+diff --git a/slstatus/components/run_command.c b/slstatus/components/run_command.c
+new file mode 100644
+index 0000000..93bf6da
+--- /dev/null
++++ b/slstatus/components/run_command.c
+@@ -0,0 +1,31 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdio.h>
++#include <string.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++run_command(const char *cmd)
++{
++ char *p;
++ FILE *fp;
++
++ if (!(fp = popen(cmd, "r"))) {
++ warn("popen '%s':", cmd);
++ return NULL;
++ }
++
++ p = fgets(buf, sizeof(buf) - 1, fp);
++ if (pclose(fp) < 0) {
++ warn("pclose '%s':", cmd);
++ return NULL;
++ }
++ if (!p)
++ return NULL;
++
++ if ((p = strrchr(buf, '\n')))
++ p[0] = '\0';
++
++ return buf[0] ? buf : NULL;
++}
+diff --git a/slstatus/components/run_command.o b/slstatus/components/run_command.o
+new file mode 100644
+index 0000000..7addb38
+Binary files /dev/null and b/slstatus/components/run_command.o differ
+diff --git a/slstatus/components/swap.c b/slstatus/components/swap.c
+new file mode 100644
+index 0000000..f270d93
+--- /dev/null
++++ b/slstatus/components/swap.c
+@@ -0,0 +1,274 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(__linux__)
++ static int
++ get_swap_info(long *s_total, long *s_free, long *s_cached)
++ {
++ FILE *fp;
++ struct {
++ const char *name;
++ const size_t len;
++ long *var;
++ } ent[] = {
++ { "SwapTotal", sizeof("SwapTotal") - 1, s_total },
++ { "SwapFree", sizeof("SwapFree") - 1, s_free },
++ { "SwapCached", sizeof("SwapCached") - 1, s_cached },
++ };
++ size_t line_len = 0, i, left;
++ char *line = NULL;
++
++ /* get number of fields we want to extract */
++ for (i = 0, left = 0; i < LEN(ent); i++)
++ if (ent[i].var)
++ left++;
++
++ if (!(fp = fopen("/proc/meminfo", "r"))) {
++ warn("fopen '/proc/meminfo':");
++ return 1;
++ }
++
++ /* read file line by line and extract field information */
++ while (left > 0 && getline(&line, &line_len, fp) >= 0) {
++ for (i = 0; i < LEN(ent); i++) {
++ if (ent[i].var &&
++ !strncmp(line, ent[i].name, ent[i].len)) {
++ sscanf(line + ent[i].len + 1,
++ "%ld kB\n", ent[i].var);
++ left--;
++ break;
++ }
++ }
++ }
++ free(line);
++ if (ferror(fp)) {
++ warn("getline '/proc/meminfo':");
++ return 1;
++ }
++
++ fclose(fp);
++ return 0;
++ }
++
++ const char *
++ swap_free(const char *unused)
++ {
++ long free;
++
++ if (get_swap_info(NULL, &free, NULL))
++ return NULL;
++
++ return fmt_human(free * 1024, 1024);
++ }
++
++ const char *
++ swap_perc(const char *unused)
++ {
++ long total, free, cached;
++
++ if (get_swap_info(&total, &free, &cached) || total == 0)
++ return NULL;
++
++ return bprintf("%d", 100 * (total - free - cached) / total);
++ }
++
++ const char *
++ swap_total(const char *unused)
++ {
++ long total;
++
++ if (get_swap_info(&total, NULL, NULL))
++ return NULL;
++
++ return fmt_human(total * 1024, 1024);
++ }
++
++ const char *
++ swap_used(const char *unused)
++ {
++ long total, free, cached;
++
++ if (get_swap_info(&total, &free, &cached))
++ return NULL;
++
++ return fmt_human((total - free - cached) * 1024, 1024);
++ }
++#elif defined(__OpenBSD__)
++ #include <stdlib.h>
++ #include <sys/swap.h>
++ #include <sys/types.h>
++ #include <unistd.h>
++
++ static int
++ getstats(int *total, int *used)
++ {
++ struct swapent *sep, *fsep;
++ int rnswap, nswap, i;
++
++ if ((nswap = swapctl(SWAP_NSWAP, 0, 0)) < 1) {
++ warn("swaptctl 'SWAP_NSWAP':");
++ return 1;
++ }
++ if (!(fsep = sep = calloc(nswap, sizeof(*sep)))) {
++ warn("calloc 'nswap':");
++ return 1;
++ }
++ if ((rnswap = swapctl(SWAP_STATS, (void *)sep, nswap)) < 0) {
++ warn("swapctl 'SWAP_STATA':");
++ return 1;
++ }
++ if (nswap != rnswap) {
++ warn("getstats: SWAP_STATS != SWAP_NSWAP");
++ return 1;
++ }
++
++ *total = 0;
++ *used = 0;
++
++ for (i = 0; i < rnswap; i++) {
++ *total += sep->se_nblks >> 1;
++ *used += sep->se_inuse >> 1;
++ }
++
++ free(fsep);
++
++ return 0;
++ }
++
++ const char *
++ swap_free(const char *unused)
++ {
++ int total, used;
++
++ if (getstats(&total, &used))
++ return NULL;
++
++ return fmt_human((total - used) * 1024, 1024);
++ }
++
++ const char *
++ swap_perc(const char *unused)
++ {
++ int total, used;
++
++ if (getstats(&total, &used))
++ return NULL;
++
++ if (total == 0)
++ return NULL;
++
++ return bprintf("%d", 100 * used / total);
++ }
++
++ const char *
++ swap_total(const char *unused)
++ {
++ int total, used;
++
++ if (getstats(&total, &used))
++ return NULL;
++
++ return fmt_human(total * 1024, 1024);
++ }
++
++ const char *
++ swap_used(const char *unused)
++ {
++ int total, used;
++
++ if (getstats(&total, &used))
++ return NULL;
++
++ return fmt_human(used * 1024, 1024);
++ }
++#elif defined(__FreeBSD__)
++ #include <fcntl.h>
++ #include <kvm.h>
++ #include <stdlib.h>
++ #include <sys/types.h>
++ #include <unistd.h>
++
++ static int getswapinfo(struct kvm_swap *swap_info, size_t size)
++ {
++ kvm_t *kd;
++
++ kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, NULL);
++ if (kd == NULL) {
++ warn("kvm_openfiles '/dev/null':");
++ return 0;
++ }
++
++ if (kvm_getswapinfo(kd, swap_info, size, 0 /* Unused flags */) < 0) {
++ warn("kvm_getswapinfo:");
++ kvm_close(kd);
++ return 0;
++ }
++
++ kvm_close(kd);
++ return 1;
++ }
++
++ const char *
++ swap_free(const char *unused)
++ {
++ struct kvm_swap swap_info[1];
++ long used, total;
++
++ if (!getswapinfo(swap_info, 1))
++ return NULL;
++
++ total = swap_info[0].ksw_total;
++ used = swap_info[0].ksw_used;
++
++ return fmt_human((total - used) * getpagesize(), 1024);
++ }
++
++ const char *
++ swap_perc(const char *unused)
++ {
++ struct kvm_swap swap_info[1];
++ long used, total;
++
++ if (!getswapinfo(swap_info, 1))
++ return NULL;
++
++ total = swap_info[0].ksw_total;
++ used = swap_info[0].ksw_used;
++
++ return bprintf("%d", used * 100 / total);
++ }
++
++ const char *
++ swap_total(const char *unused)
++ {
++ struct kvm_swap swap_info[1];
++ long total;
++
++ if (!getswapinfo(swap_info, 1))
++ return NULL;
++
++ total = swap_info[0].ksw_total;
++
++ return fmt_human(total * getpagesize(), 1024);
++ }
++
++ const char *
++ swap_used(const char *unused)
++ {
++ struct kvm_swap swap_info[1];
++ long used;
++
++ if (!getswapinfo(swap_info, 1))
++ return NULL;
++
++ used = swap_info[0].ksw_used;
++
++ return fmt_human(used * getpagesize(), 1024);
++ }
++#endif
+diff --git a/slstatus/components/swap.o b/slstatus/components/swap.o
+new file mode 100644
+index 0000000..eff037e
+Binary files /dev/null and b/slstatus/components/swap.o differ
+diff --git a/slstatus/components/temperature.c b/slstatus/components/temperature.c
+new file mode 100644
+index 0000000..7cf1394
+--- /dev/null
++++ b/slstatus/components/temperature.c
+@@ -0,0 +1,73 @@
++/* See LICENSE file for copyright and license details. */
++#include <stddef.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++
++#if defined(__linux__)
++ #include <stdint.h>
++
++ const char *
++ temp(const char *file)
++ {
++ uintmax_t temp;
++
++ if (pscanf(file, "%ju", &temp) != 1)
++ return NULL;
++
++ return bprintf("%ju", temp / 1000);
++ }
++#elif defined(__OpenBSD__)
++ #include <stdio.h>
++ #include <sys/time.h> /* before <sys/sensors.h> for struct timeval */
++ #include <sys/sensors.h>
++ #include <sys/sysctl.h>
++
++ const char *
++ temp(const char *unused)
++ {
++ int mib[5];
++ size_t size;
++ struct sensor temp;
++
++ mib[0] = CTL_HW;
++ mib[1] = HW_SENSORS;
++ mib[2] = 0; /* cpu0 */
++ mib[3] = SENSOR_TEMP;
++ mib[4] = 0; /* temp0 */
++
++ size = sizeof(temp);
++
++ if (sysctl(mib, 5, &temp, &size, NULL, 0) < 0) {
++ warn("sysctl 'SENSOR_TEMP':");
++ return NULL;
++ }
++
++ /* kelvin to celsius */
++ return bprintf("%d", (int)((float)(temp.value-273150000) / 1E6));
++ }
++#elif defined(__FreeBSD__)
++ #include <stdio.h>
++ #include <stdlib.h>
++ #include <sys/sysctl.h>
++
++ #define ACPI_TEMP "hw.acpi.thermal.%s.temperature"
++
++ const char *
++ temp(const char *zone)
++ {
++ char buf[256];
++ int temp;
++ size_t len;
++
++ len = sizeof(temp);
++ snprintf(buf, sizeof(buf), ACPI_TEMP, zone);
++ if (sysctlbyname(buf, &temp, &len, NULL, 0) < 0
++ || !len)
++ return NULL;
++
++ /* kelvin to decimal celcius */
++ return bprintf("%d.%d", (temp - 2731) / 10, abs((temp - 2731) % 10));
++ }
++#endif
+diff --git a/slstatus/components/temperature.o b/slstatus/components/temperature.o
+new file mode 100644
+index 0000000..0856c7b
+Binary files /dev/null and b/slstatus/components/temperature.o differ
+diff --git a/slstatus/components/uptime.c b/slstatus/components/uptime.c
+new file mode 100644
+index 0000000..6227f73
+--- /dev/null
++++ b/slstatus/components/uptime.c
+@@ -0,0 +1,34 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdint.h>
++#include <stdio.h>
++#include <time.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(CLOCK_BOOTTIME)
++ #define UPTIME_FLAG CLOCK_BOOTTIME
++#elif defined(CLOCK_UPTIME)
++ #define UPTIME_FLAG CLOCK_UPTIME
++#else
++ #define UPTIME_FLAG CLOCK_MONOTONIC
++#endif
++
++const char *
++uptime(const char *unused)
++{
++ char warn_buf[256];
++ uintmax_t h, m;
++ struct timespec uptime;
++
++ if (clock_gettime(UPTIME_FLAG, &uptime) < 0) {
++ snprintf(warn_buf, sizeof(warn_buf), "clock_gettime %d", UPTIME_FLAG);
++ warn(warn_buf);
++ return NULL;
++ }
++
++ h = uptime.tv_sec / 3600;
++ m = uptime.tv_sec % 3600 / 60;
++
++ return bprintf("%juh %jum", h, m);
++}
+diff --git a/slstatus/components/uptime.o b/slstatus/components/uptime.o
+new file mode 100644
+index 0000000..ee47012
+Binary files /dev/null and b/slstatus/components/uptime.o differ
+diff --git a/slstatus/components/user.c b/slstatus/components/user.c
+new file mode 100644
+index 0000000..3517495
+--- /dev/null
++++ b/slstatus/components/user.c
+@@ -0,0 +1,33 @@
++/* See LICENSE file for copyright and license details. */
++#include <pwd.h>
++#include <stdio.h>
++#include <sys/types.h>
++#include <unistd.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++const char *
++gid(const char *unused)
++{
++ return bprintf("%d", getgid());
++}
++
++const char *
++username(const char *unused)
++{
++ struct passwd *pw;
++
++ if (!(pw = getpwuid(geteuid()))) {
++ warn("getpwuid '%d':", geteuid());
++ return NULL;
++ }
++
++ return bprintf("%s", pw->pw_name);
++}
++
++const char *
++uid(const char *unused)
++{
++ return bprintf("%d", geteuid());
++}
+diff --git a/slstatus/components/user.o b/slstatus/components/user.o
+new file mode 100644
+index 0000000..83bb561
+Binary files /dev/null and b/slstatus/components/user.o differ
+diff --git a/slstatus/components/volume.c b/slstatus/components/volume.c
+new file mode 100644
+index 0000000..6cec556
+--- /dev/null
++++ b/slstatus/components/volume.c
+@@ -0,0 +1,219 @@
++/* See LICENSE file for copyright and license details. */
++#include <fcntl.h>
++#include <stdio.h>
++#include <string.h>
++#include <sys/ioctl.h>
++#include <unistd.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#if defined(__OpenBSD__) | defined(__FreeBSD__)
++ #include <poll.h>
++ #include <sndio.h>
++ #include <stdlib.h>
++ #include <sys/queue.h>
++
++ struct control {
++ LIST_ENTRY(control) next;
++ unsigned int addr;
++ #define CTRL_NONE 0
++ #define CTRL_LEVEL 1
++ #define CTRL_MUTE 2
++ unsigned int type;
++ unsigned int maxval;
++ unsigned int val;
++ };
++
++ static LIST_HEAD(, control) controls = LIST_HEAD_INITIALIZER(controls);
++ static struct pollfd *pfds;
++ static struct sioctl_hdl *hdl;
++ static int initialized;
++
++ /*
++ * Call-back to obtain the description of all audio controls.
++ */
++ static void
++ ondesc(void *unused, struct sioctl_desc *desc, int val)
++ {
++ struct control *c, *ctmp;
++ unsigned int type = CTRL_NONE;
++
++ if (desc == NULL)
++ return;
++
++ /* Delete existing audio control with the same address. */
++ LIST_FOREACH_SAFE(c, &controls, next, ctmp) {
++ if (desc->addr == c->addr) {
++ LIST_REMOVE(c, next);
++ free(c);
++ break;
++ }
++ }
++
++ /* Only match output.level and output.mute audio controls. */
++ if (desc->group[0] != 0 ||
++ strcmp(desc->node0.name, "output") != 0)
++ return;
++ if (desc->type == SIOCTL_NUM &&
++ strcmp(desc->func, "level") == 0)
++ type = CTRL_LEVEL;
++ else if (desc->type == SIOCTL_SW &&
++ strcmp(desc->func, "mute") == 0)
++ type = CTRL_MUTE;
++ else
++ return;
++
++ c = malloc(sizeof(struct control));
++ if (c == NULL) {
++ warn("sndio: failed to allocate audio control\n");
++ return;
++ }
++
++ c->addr = desc->addr;
++ c->type = type;
++ c->maxval = desc->maxval;
++ c->val = val;
++ LIST_INSERT_HEAD(&controls, c, next);
++ }
++
++ /*
++ * Call-back invoked whenever an audio control changes.
++ */
++ static void
++ onval(void *unused, unsigned int addr, unsigned int val)
++ {
++ struct control *c;
++
++ LIST_FOREACH(c, &controls, next) {
++ if (c->addr == addr)
++ break;
++ }
++ c->val = val;
++ }
++
++ static void
++ cleanup(void)
++ {
++ struct control *c;
++
++ if (hdl) {
++ sioctl_close(hdl);
++ hdl = NULL;
++ }
++
++ free(pfds);
++ pfds = NULL;
++
++ while (!LIST_EMPTY(&controls)) {
++ c = LIST_FIRST(&controls);
++ LIST_REMOVE(c, next);
++ free(c);
++ }
++ }
++
++ static int
++ init(void)
++ {
++ hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0);
++ if (hdl == NULL) {
++ warn("sndio: cannot open device");
++ goto failed;
++ }
++
++ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
++ warn("sndio: cannot set control description call-back");
++ goto failed;
++ }
++
++ if (!sioctl_onval(hdl, onval, NULL)) {
++ warn("sndio: cannot set control values call-back");
++ goto failed;
++ }
++
++ pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd));
++ if (pfds == NULL) {
++ warn("sndio: cannot allocate pollfd structures");
++ goto failed;
++ }
++
++ return 1;
++ failed:
++ cleanup();
++ return 0;
++ }
++
++ const char *
++ vol_perc(const char *unused)
++ {
++ struct control *c;
++ int n, v, value;
++
++ if (!initialized)
++ initialized = init();
++
++ if (hdl == NULL)
++ return NULL;
++
++ n = sioctl_pollfd(hdl, pfds, POLLIN);
++ if (n > 0) {
++ n = poll(pfds, n, 0);
++ if (n > 0) {
++ if (sioctl_revents(hdl, pfds) & POLLHUP) {
++ warn("sndio: disconnected");
++ cleanup();
++ initialized = 0;
++ return NULL;
++ }
++ }
++ }
++
++ value = 100;
++ LIST_FOREACH(c, &controls, next) {
++ if (c->type == CTRL_MUTE && c->val == 1)
++ value = 0;
++ else if (c->type == CTRL_LEVEL) {
++ v = (c->val * 100 + c->maxval / 2) / c->maxval;
++ /* For multiple channels return the minimum. */
++ if (v < value)
++ value = v;
++ }
++ }
++
++ return bprintf("%d", value);
++ }
++#else
++ #include <sys/soundcard.h>
++
++ const char *
++ vol_perc(const char *card)
++ {
++ size_t i;
++ int v, afd, devmask;
++ char *vnames[] = SOUND_DEVICE_NAMES;
++
++ if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) {
++ warn("open '%s':", card);
++ return NULL;
++ }
++
++ if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
++ warn("ioctl 'SOUND_MIXER_READ_DEVMASK':");
++ close(afd);
++ return NULL;
++ }
++ for (i = 0; i < LEN(vnames); i++) {
++ if (devmask & (1 << i) && !strcmp("vol", vnames[i])) {
++ if (ioctl(afd, MIXER_READ(i), &v) < 0) {
++ warn("ioctl 'MIXER_READ(%ld)':", i);
++ close(afd);
++ return NULL;
++ }
++ }
++ }
++
++ close(afd);
++
++ return bprintf("%d", v & 0xff);
++ }
++#endif
+diff --git a/slstatus/components/volume.o b/slstatus/components/volume.o
+new file mode 100644
+index 0000000..73b3122
+Binary files /dev/null and b/slstatus/components/volume.o differ
+diff --git a/slstatus/components/wifi.c b/slstatus/components/wifi.c
+new file mode 100644
+index 0000000..4543d32
+--- /dev/null
++++ b/slstatus/components/wifi.c
+@@ -0,0 +1,267 @@
++/* See LICENSE file for copyright and license details. */
++#include <ifaddrs.h>
++#include <stdio.h>
++#include <string.h>
++#include <sys/ioctl.h>
++#include <sys/socket.h>
++#include <unistd.h>
++
++#include "../slstatus.h"
++#include "../util.h"
++
++#define RSSI_TO_PERC(rssi) \
++ rssi >= -50 ? 100 : \
++ (rssi <= -100 ? 0 : \
++ (2 * (rssi + 100)))
++
++#if defined(__linux__)
++ #include <limits.h>
++ #include <linux/wireless.h>
++
++ #define NET_OPERSTATE "/sys/class/net/%s/operstate"
++
++ const char *
++ wifi_perc(const char *interface)
++ {
++ int cur;
++ size_t i;
++ char *p, *datastart;
++ char path[PATH_MAX];
++ char status[5];
++ FILE *fp;
++
++ if (esnprintf(path, sizeof(path), NET_OPERSTATE, interface) < 0)
++ return NULL;
++ if (!(fp = fopen(path, "r"))) {
++ warn("fopen '%s':", path);
++ return NULL;
++ }
++ p = fgets(status, 5, fp);
++ fclose(fp);
++ if (!p || strcmp(status, "up\n") != 0)
++ return NULL;
++
++ if (!(fp = fopen("/proc/net/wireless", "r"))) {
++ warn("fopen '/proc/net/wireless':");
++ return NULL;
++ }
++
++ for (i = 0; i < 3; i++)
++ if (!(p = fgets(buf, sizeof(buf) - 1, fp)))
++ break;
++
++ fclose(fp);
++ if (i < 2 || !p)
++ return NULL;
++
++ if (!(datastart = strstr(buf, interface)))
++ return NULL;
++
++ datastart = (datastart+(strlen(interface)+1));
++ sscanf(datastart + 1, " %*d %d %*d %*d\t\t %*d\t "
++ "%*d\t\t%*d\t\t %*d\t %*d\t\t %*d", &cur);
++
++ /* 70 is the max of /proc/net/wireless */
++ return bprintf("%d", (int)((float)cur / 70 * 100));
++ }
++
++ const char *
++ wifi_essid(const char *interface)
++ {
++ static char id[IW_ESSID_MAX_SIZE+1];
++ int sockfd;
++ struct iwreq wreq;
++
++ memset(&wreq, 0, sizeof(struct iwreq));
++ wreq.u.essid.length = IW_ESSID_MAX_SIZE+1;
++ if (esnprintf(wreq.ifr_name, sizeof(wreq.ifr_name), "%s",
++ interface) < 0)
++ return NULL;
++
++ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
++ warn("socket 'AF_INET':");
++ return NULL;
++ }
++ wreq.u.essid.pointer = id;
++ if (ioctl(sockfd,SIOCGIWESSID, &wreq) < 0) {
++ warn("ioctl 'SIOCGIWESSID':");
++ close(sockfd);
++ return NULL;
++ }
++
++ close(sockfd);
++
++ if (!strcmp(id, ""))
++ return NULL;
++
++ return id;
++ }
++#elif defined(__OpenBSD__)
++ #include <net/if.h>
++ #include <net/if_media.h>
++ #include <net80211/ieee80211.h>
++ #include <sys/select.h> /* before <sys/ieee80211_ioctl.h> for NBBY */
++ #include <net80211/ieee80211_ioctl.h>
++ #include <stdlib.h>
++ #include <sys/types.h>
++
++ static int
++ load_ieee80211_nodereq(const char *interface, struct ieee80211_nodereq *nr)
++ {
++ struct ieee80211_bssid bssid;
++ int sockfd;
++ uint8_t zero_bssid[IEEE80211_ADDR_LEN];
++
++ memset(&bssid, 0, sizeof(bssid));
++ memset(nr, 0, sizeof(struct ieee80211_nodereq));
++ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
++ warn("socket 'AF_INET':");
++ return 0;
++ }
++ strlcpy(bssid.i_name, interface, sizeof(bssid.i_name));
++ if ((ioctl(sockfd, SIOCG80211BSSID, &bssid)) < 0) {
++ warn("ioctl 'SIOCG80211BSSID':");
++ close(sockfd);
++ return 0;
++ }
++ memset(&zero_bssid, 0, sizeof(zero_bssid));
++ if (memcmp(bssid.i_bssid, zero_bssid,
++ IEEE80211_ADDR_LEN) == 0) {
++ close(sockfd);
++ return 0;
++ }
++ strlcpy(nr->nr_ifname, interface, sizeof(nr->nr_ifname));
++ memcpy(&nr->nr_macaddr, bssid.i_bssid, sizeof(nr->nr_macaddr));
++ if ((ioctl(sockfd, SIOCG80211NODE, nr)) < 0 && nr->nr_rssi) {
++ warn("ioctl 'SIOCG80211NODE':");
++ close(sockfd);
++ return 0;
++ }
++
++ return close(sockfd), 1;
++ }
++
++ const char *
++ wifi_perc(const char *interface)
++ {
++ struct ieee80211_nodereq nr;
++ int q;
++
++ if (load_ieee80211_nodereq(interface, &nr)) {
++ if (nr.nr_max_rssi)
++ q = IEEE80211_NODEREQ_RSSI(&nr);
++ else
++ q = RSSI_TO_PERC(nr.nr_rssi);
++
++ return bprintf("%d", q);
++ }
++
++ return NULL;
++ }
++
++ const char *
++ wifi_essid(const char *interface)
++ {
++ struct ieee80211_nodereq nr;
++
++ if (load_ieee80211_nodereq(interface, &nr))
++ return bprintf("%s", nr.nr_nwid);
++
++ return NULL;
++ }
++#elif defined(__FreeBSD__)
++ #include <net/if.h>
++ #include <net80211/ieee80211_ioctl.h>
++
++ int
++ load_ieee80211req(int sock, const char *interface, void *data, int type, size_t *len)
++ {
++ char warn_buf[256];
++ struct ieee80211req ireq;
++ memset(&ireq, 0, sizeof(ireq));
++ ireq.i_type = type;
++ ireq.i_data = (caddr_t) data;
++ ireq.i_len = *len;
++
++ strlcpy(ireq.i_name, interface, sizeof(ireq.i_name));
++ if (ioctl(sock, SIOCG80211, &ireq) < 0) {
++ snprintf(warn_buf, sizeof(warn_buf),
++ "ioctl: 'SIOCG80211': %d", type);
++ warn(warn_buf);
++ return 0;
++ }
++
++ *len = ireq.i_len;
++ return 1;
++ }
++
++ const char *
++ wifi_perc(const char *interface)
++ {
++ union {
++ struct ieee80211req_sta_req sta;
++ uint8_t buf[24 * 1024];
++ } info;
++ uint8_t bssid[IEEE80211_ADDR_LEN];
++ int rssi_dbm;
++ int sockfd;
++ size_t len;
++ const char *fmt;
++
++ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
++ warn("socket 'AF_INET':");
++ return NULL;
++ }
++
++ /* Retreive MAC address of interface */
++ len = IEEE80211_ADDR_LEN;
++ fmt = NULL;
++ if (load_ieee80211req(sockfd, interface, &bssid, IEEE80211_IOC_BSSID, &len))
++ {
++ /* Retrieve info on station with above BSSID */
++ memset(&info, 0, sizeof(info));
++ memcpy(info.sta.is_u.macaddr, bssid, sizeof(bssid));
++
++ len = sizeof(info);
++ if (load_ieee80211req(sockfd, interface, &info, IEEE80211_IOC_STA_INFO, &len)) {
++ rssi_dbm = info.sta.info[0].isi_noise +
++ info.sta.info[0].isi_rssi / 2;
++
++ fmt = bprintf("%d", RSSI_TO_PERC(rssi_dbm));
++ }
++ }
++
++ close(sockfd);
++ return fmt;
++ }
++
++ const char *
++ wifi_essid(const char *interface)
++ {
++ char ssid[IEEE80211_NWID_LEN + 1];
++ size_t len;
++ int sockfd;
++ const char *fmt;
++
++ if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
++ warn("socket 'AF_INET':");
++ return NULL;
++ }
++
++ fmt = NULL;
++ len = sizeof(ssid);
++ memset(&ssid, 0, len);
++ if (load_ieee80211req(sockfd, interface, &ssid, IEEE80211_IOC_SSID, &len)) {
++ if (len < sizeof(ssid))
++ len += 1;
++ else
++ len = sizeof(ssid);
++
++ ssid[len - 1] = '\0';
++ fmt = bprintf("%s", ssid);
++ }
++
++ close(sockfd);
++ return fmt;
++ }
++#endif
+diff --git a/slstatus/components/wifi.o b/slstatus/components/wifi.o
+new file mode 100644
+index 0000000..91e1508
+Binary files /dev/null and b/slstatus/components/wifi.o differ
+diff --git a/slstatus/config.def.h b/slstatus/config.def.h
+new file mode 100644
+index 0000000..aded4d7
+--- /dev/null
++++ b/slstatus/config.def.h
+@@ -0,0 +1,78 @@
++/* See LICENSE file for copyright and license details. */
++
++/* interval between updates (in ms) */
++const unsigned int interval = 1000;
++
++/* text to show if no value can be retrieved */
++static const char unknown_str[] = "n/a";
++
++/* maximum output string length */
++#define MAXLEN 2048
++
++/*
++ * function description argument (example)
++ *
++ * battery_perc battery percentage battery name (BAT0)
++ * NULL on OpenBSD/FreeBSD
++ * battery_remaining battery remaining HH:MM battery name (BAT0)
++ * NULL on OpenBSD/FreeBSD
++ * battery_state battery charging state battery name (BAT0)
++ * NULL on OpenBSD/FreeBSD
++ * cat read arbitrary file path
++ * cpu_freq cpu frequency in MHz NULL
++ * cpu_perc cpu usage in percent NULL
++ * datetime date and time format string (%F %T)
++ * disk_free free disk space in GB mountpoint path (/)
++ * disk_perc disk usage in percent mountpoint path (/)
++ * disk_total total disk space in GB mountpoint path (/)
++ * disk_used used disk space in GB mountpoint path (/)
++ * entropy available entropy NULL
++ * gid GID of current user NULL
++ * hostname hostname NULL
++ * ipv4 IPv4 address interface name (eth0)
++ * ipv6 IPv6 address interface name (eth0)
++ * kanji japanese day of week kanji NULL
++ * kernel_release `uname -r` NULL
++ * keyboard_indicators caps/num lock indicators format string (c?n?)
++ * see keyboard_indicators.c
++ * keymap layout (variant) of current NULL
++ * keymap
++ * load_avg load average NULL
++ * netspeed_rx receive network speed interface name (wlan0)
++ * netspeed_tx transfer network speed interface name (wlan0)
++ * num_files number of files in a directory path
++ * (/home/foo/Inbox/cur)
++ * ram_free free memory in GB NULL
++ * ram_perc memory usage in percent NULL
++ * ram_total total memory size in GB NULL
++ * ram_used used memory in GB NULL
++ * run_command custom shell command command (echo foo)
++ * swap_free free swap in GB NULL
++ * swap_perc swap usage in percent NULL
++ * swap_total total swap size in GB NULL
++ * swap_used used swap in GB NULL
++ * temp temperature in degree celsius sensor file
++ * (/sys/class/thermal/...)
++ * NULL on OpenBSD
++ * thermal zone on FreeBSD
++ * (tz0, tz1, etc.)
++ * uid UID of current user NULL
++ * uptime system uptime NULL
++ * username username of current user NULL
++ * vol_perc OSS/ALSA volume in percent mixer file (/dev/mixer)
++ * NULL on OpenBSD/FreeBSD
++ * wifi_essid WiFi ESSID interface name (wlan0)
++ * wifi_perc WiFi signal in percent interface name (wlan0)
++ */
++static const struct arg args[] = {
++ /* function format argument */
++ { ram_used, "[RAM %s]", NULL },
++ { cpu_perc, "[CPU %s%%]", NULL },
++ { disk_free, "[ %s free]" , "/" },
++ { netspeed_rx, "[ %s]" , "wlan0"},
++ { temp, "[%s C°]", "/sys/class/thermal/thermal_zone0/temp" },
++ { run_command, "[%s% ]", "getvolume"},
++ { datetime, "%s", "%r"},
++ { kernel_release, " %s", },
++ { kanji, "| %s |", },
++};
+diff --git a/slstatus/config.mk b/slstatus/config.mk
+new file mode 100644
+index 0000000..07af883
+--- /dev/null
++++ b/slstatus/config.mk
+@@ -0,0 +1,22 @@
++# slstatus version
++VERSION = 1.0
++
++# customize below to fit your system
++
++# paths
++PREFIX = /usr/local
++MANPREFIX = $(PREFIX)/share/man
++
++X11INC = /usr/X11R6/include
++X11LIB = /usr/X11R6/lib
++
++# flags
++CPPFLAGS = -I$(X11INC) -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\"
++CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter -Os
++LDFLAGS = -L$(X11LIB) -s
++# OpenBSD: add -lsndio
++# FreeBSD: add -lkvm -lsndio
++LDLIBS = -lX11
++
++# compiler and linker
++CC = cc
+diff --git a/slstatus/slstatus.1 b/slstatus/slstatus.1
+new file mode 100644
+index 0000000..73e7a60
+--- /dev/null
++++ b/slstatus/slstatus.1
+@@ -0,0 +1,47 @@
++.Dd 2023-04-23
++.Dt SLSTATUS 1
++.Os
++.Sh NAME
++.Nm slstatus
++.Nd suckless status
++.Sh SYNOPSIS
++.Nm
++.Op Fl s
++.Op Fl 1
++.Sh DESCRIPTION
++.Nm
++is a small tool for providing system status information to other programs
++over the EWMH
++.Em WM_NAME
++property of the root window (used by
++.Xr dwm 1 ) or standard input/output. It is designed to be as efficient as possible by
++only issuing the minimum of system calls required.
++.P
++By default,
++.Nm
++outputs to WM_NAME.
++.Sh OPTIONS
++.Bl -tag -width Ds
++.It Fl v
++Print version information to stderr, then exit.
++.It Fl s
++Write to stdout instead of WM_NAME.
++.It Fl 1
++Write once to stdout and quit.
++.El
++.Sh CUSTOMIZATION
++.Nm
++can be customized by creating a custom config.h and (re)compiling the source
++code. This keeps it fast, secure and simple.
++.Sh SIGNALS
++.Nm
++responds to the following signals:
++.Pp
++.Bl -tag -width TERM -compact
++.It USR1
++Triggers an instant redraw.
++.El
++.Sh AUTHORS
++See the LICENSE file for the authors.
++.Sh SEE ALSO
++.Xr dwm 1
+diff --git a/slstatus/slstatus.c b/slstatus/slstatus.c
+new file mode 100644
+index 0000000..fd31313
+--- /dev/null
++++ b/slstatus/slstatus.c
+@@ -0,0 +1,134 @@
++/* See LICENSE file for copyright and license details. */
++#include <errno.h>
++#include <signal.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <time.h>
++#include <X11/Xlib.h>
++
++#include "arg.h"
++#include "slstatus.h"
++#include "util.h"
++
++struct arg {
++ const char *(*func)(const char *);
++ const char *fmt;
++ const char *args;
++};
++
++char buf[1024];
++static volatile sig_atomic_t done;
++static Display *dpy;
++
++#include "config.h"
++
++static void
++terminate(const int signo)
++{
++ if (signo != SIGUSR1)
++ done = 1;
++}
++
++static void
++difftimespec(struct timespec *res, struct timespec *a, struct timespec *b)
++{
++ res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec);
++ res->tv_nsec = a->tv_nsec - b->tv_nsec +
++ (a->tv_nsec < b->tv_nsec) * 1E9;
++}
++
++static void
++usage(void)
++{
++ die("usage: %s [-v] [-s] [-1]", argv0);
++}
++
++int
++main(int argc, char *argv[])
++{
++ struct sigaction act;
++ struct timespec start, current, diff, intspec, wait;
++ size_t i, len;
++ int sflag, ret;
++ char status[MAXLEN];
++ const char *res;
++
++ sflag = 0;
++ ARGBEGIN {
++ case 'v':
++ die("slstatus-"VERSION);
++ case '1':
++ done = 1;
++ /* FALLTHROUGH */
++ case 's':
++ sflag = 1;
++ break;
++ default:
++ usage();
++ } ARGEND
++
++ if (argc)
++ usage();
++
++ memset(&act, 0, sizeof(act));
++ act.sa_handler = terminate;
++ sigaction(SIGINT, &act, NULL);
++ sigaction(SIGTERM, &act, NULL);
++ act.sa_flags |= SA_RESTART;
++ sigaction(SIGUSR1, &act, NULL);
++
++ if (!sflag && !(dpy = XOpenDisplay(NULL)))
++ die("XOpenDisplay: Failed to open display");
++
++ do {
++ if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
++ die("clock_gettime:");
++
++ status[0] = '\0';
++ for (i = len = 0; i < LEN(args); i++) {
++ if (!(res = args[i].func(args[i].args)))
++ res = unknown_str;
++
++ if ((ret = esnprintf(status + len, sizeof(status) - len,
++ args[i].fmt, res)) < 0)
++ break;
++
++ len += ret;
++ }
++
++ if (sflag) {
++ puts(status);
++ fflush(stdout);
++ if (ferror(stdout))
++ die("puts:");
++ } else {
++ if (XStoreName(dpy, DefaultRootWindow(dpy), status) < 0)
++ die("XStoreName: Allocation failed");
++ XFlush(dpy);
++ }
++
++ if (!done) {
++ if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0)
++ die("clock_gettime:");
++ difftimespec(&diff, ¤t, &start);
++
++ intspec.tv_sec = interval / 1000;
++ intspec.tv_nsec = (interval % 1000) * 1E6;
++ difftimespec(&wait, &intspec, &diff);
++
++ if (wait.tv_sec >= 0 &&
++ nanosleep(&wait, NULL) < 0 &&
++ errno != EINTR)
++ die("nanosleep:");
++ }
++ } while (!done);
++
++ if (!sflag) {
++ XStoreName(dpy, DefaultRootWindow(dpy), NULL);
++ if (XCloseDisplay(dpy) < 0)
++ die("XCloseDisplay: Failed to close display");
++ }
++
++ return 0;
++}
+diff --git a/slstatus/slstatus.h b/slstatus/slstatus.h
+new file mode 100644
+index 0000000..dc927eb
+--- /dev/null
++++ b/slstatus/slstatus.h
+@@ -0,0 +1,87 @@
++/* See LICENSE file for copyright and license details. */
++
++/* battery */
++const char *battery_perc(const char *);
++const char *battery_remaining(const char *);
++const char *battery_state(const char *);
++
++/* cat */
++const char *cat(const char *path);
++
++/* cpu */
++const char *cpu_freq(const char *unused);
++const char *cpu_perc(const char *unused);
++
++/* datetime */
++const char *datetime(const char *fmt);
++
++/* disk */
++const char *disk_free(const char *path);
++const char *disk_perc(const char *path);
++const char *disk_total(const char *path);
++const char *disk_used(const char *path);
++
++/* entropy */
++const char *entropy(const char *unused);
++
++/* hostname */
++const char *hostname(const char *unused);
++
++/* ip */
++const char *ipv4(const char *interface);
++const char *ipv6(const char *interface);
++
++/* kanji */
++const char *kanji(const char *unused);
++
++/* kernel_release */
++const char *kernel_release(const char *unused);
++
++/* keyboard_indicators */
++const char *keyboard_indicators(const char *fmt);
++
++/* keymap */
++const char *keymap(const char *unused);
++
++/* load_avg */
++const char *load_avg(const char *unused);
++
++/* netspeeds */
++const char *netspeed_rx(const char *interface);
++const char *netspeed_tx(const char *interface);
++
++/* num_files */
++const char *num_files(const char *path);
++
++/* ram */
++const char *ram_free(const char *unused);
++const char *ram_perc(const char *unused);
++const char *ram_total(const char *unused);
++const char *ram_used(const char *unused);
++
++/* run_command */
++const char *run_command(const char *cmd);
++
++/* swap */
++const char *swap_free(const char *unused);
++const char *swap_perc(const char *unused);
++const char *swap_total(const char *unused);
++const char *swap_used(const char *unused);
++
++/* temperature */
++const char *temp(const char *);
++
++/* uptime */
++const char *uptime(const char *unused);
++
++/* user */
++const char *gid(const char *unused);
++const char *uid(const char *unused);
++const char *username(const char *unused);
++
++/* volume */
++const char *vol_perc(const char *card);
++
++/* wifi */
++const char *wifi_essid(const char *interface);
++const char *wifi_perc(const char *interface);
+diff --git a/slstatus/util.c b/slstatus/util.c
+new file mode 100644
+index 0000000..bca9b2e
+--- /dev/null
++++ b/slstatus/util.c
+@@ -0,0 +1,141 @@
++/* See LICENSE file for copyright and license details. */
++#include <errno.h>
++#include <stdarg.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include "util.h"
++
++char *argv0;
++
++static void
++verr(const char *fmt, va_list ap)
++{
++ vfprintf(stderr, fmt, ap);
++
++ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
++ fputc(' ', stderr);
++ perror(NULL);
++ } else {
++ fputc('\n', stderr);
++ }
++}
++
++void
++warn(const char *fmt, ...)
++{
++ va_list ap;
++
++ va_start(ap, fmt);
++ verr(fmt, ap);
++ va_end(ap);
++}
++
++void
++die(const char *fmt, ...)
++{
++ va_list ap;
++
++ va_start(ap, fmt);
++ verr(fmt, ap);
++ va_end(ap);
++
++ exit(1);
++}
++
++static int
++evsnprintf(char *str, size_t size, const char *fmt, va_list ap)
++{
++ int ret;
++
++ ret = vsnprintf(str, size, fmt, ap);
++
++ if (ret < 0) {
++ warn("vsnprintf:");
++ return -1;
++ } else if ((size_t)ret >= size) {
++ warn("vsnprintf: Output truncated");
++ return -1;
++ }
++
++ return ret;
++}
++
++int
++esnprintf(char *str, size_t size, const char *fmt, ...)
++{
++ va_list ap;
++ int ret;
++
++ va_start(ap, fmt);
++ ret = evsnprintf(str, size, fmt, ap);
++ va_end(ap);
++
++ return ret;
++}
++
++const char *
++bprintf(const char *fmt, ...)
++{
++ va_list ap;
++ int ret;
++
++ va_start(ap, fmt);
++ ret = evsnprintf(buf, sizeof(buf), fmt, ap);
++ va_end(ap);
++
++ return (ret < 0) ? NULL : buf;
++}
++
++const char *
++fmt_human(uintmax_t num, int base)
++{
++ double scaled;
++ size_t i, prefixlen;
++ const char **prefix;
++ const char *prefix_1000[] = { "", "k", "M", "G", "T", "P", "E", "Z",
++ "Y" };
++ const char *prefix_1024[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei",
++ "Zi", "Yi" };
++
++ switch (base) {
++ case 1000:
++ prefix = prefix_1000;
++ prefixlen = LEN(prefix_1000);
++ break;
++ case 1024:
++ prefix = prefix_1024;
++ prefixlen = LEN(prefix_1024);
++ break;
++ default:
++ warn("fmt_human: Invalid base");
++ return NULL;
++ }
++
++ scaled = num;
++ for (i = 0; i < prefixlen && scaled >= base; i++)
++ scaled /= base;
++
++ return bprintf("%.1f %s", scaled, prefix[i]);
++}
++
++int
++pscanf(const char *path, const char *fmt, ...)
++{
++ FILE *fp;
++ va_list ap;
++ int n;
++
++ if (!(fp = fopen(path, "r"))) {
++ warn("fopen '%s':", path);
++ return -1;
++ }
++ va_start(ap, fmt);
++ n = vfscanf(fp, fmt, ap);
++ va_end(ap);
++ fclose(fp);
++
++ return (n == EOF) ? -1 : n;
++}
+diff --git a/slstatus/util.h b/slstatus/util.h
+new file mode 100644
+index 0000000..cf4b027
+--- /dev/null
++++ b/slstatus/util.h
+@@ -0,0 +1,16 @@
++/* See LICENSE file for copyright and license details. */
++#include <stdint.h>
++
++extern char buf[1024];
++
++#define LEN(x) (sizeof(x) / sizeof((x)[0]))
++
++extern char *argv0;
++
++void warn(const char *, ...);
++void die(const char *, ...);
++
++int esnprintf(char *str, size_t size, const char *fmt, ...);
++const char *bprintf(const char *fmt, ...);
++const char *fmt_human(uintmax_t num, int base);
++int pscanf(const char *path, const char *fmt, ...);