birdwm

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
M.gitignore | 2+-
Aextras/birdwm.patch | 6313+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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", &current_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, &current) < 0) ++ die("clock_gettime:"); ++ difftimespec(&diff, &current, &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, ...);