commit dd077feccde0ac4568fd526338eb450b7b3fbf78
Author: grouse <bdmfegys@duck.com>
Date: Sun, 22 Oct 2023 21:50:02 -0400
first commit
Diffstat:
73 files changed, 6899 insertions(+), 0 deletions(-)
diff --git a/.dwm/autostart.sh b/.dwm/autostart.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+#xrandr --output DP-0 --mode 1920x1080 --rate 140 --right-of HDMI-0 --output HDMI-0 --mode 1920x1080 --rate 165
+xrandr --auto
+slstatus &
+feh --bg-fill $HOME/.config/wall.png
+picom & disown
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,38 @@
+MIT/X Consortium License
+
+© 2006-2019 Anselm R Garbe <anselm@garbe.ca>
+© 2006-2009 Jukka Salmi <jukka at salmi dot ch>
+© 2006-2007 Sander van Dijk <a dot h dot vandijk at gmail dot com>
+© 2007-2011 Peter Hartlich <sgkkr at hartlich dot com>
+© 2007-2009 Szabolcs Nagy <nszabolcs at gmail dot com>
+© 2007-2009 Christof Musik <christof at sendfax dot de>
+© 2007-2009 Premysl Hruby <dfenze at gmail dot com>
+© 2007-2008 Enno Gottox Boland <gottox at s01 dot de>
+© 2008 Martin Hurton <martin dot hurton at gmail dot com>
+© 2008 Neale Pickett <neale dot woozle dot org>
+© 2009 Mate Nagy <mnagy at port70 dot net>
+© 2010-2016 Hiltjo Posthuma <hiltjo@codemadness.org>
+© 2010-2012 Connor Lane Smith <cls@lubutu.com>
+© 2011 Christoph Lohmann <20h@r-36.net>
+© 2015-2016 Quentin Rameau <quinq@fifth.space>
+© 2015-2016 Eric Pruitt <eric.pruitt@gmail.com>
+© 2016-2017 Markus Teich <markus.teich@stusta.mhn.de>
+© 2020-2022 Chris Down <chris@chrisdown.name>
+
+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/Makefile b/Makefile
@@ -0,0 +1,51 @@
+# dwm - dynamic window manager
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = drw.c dwm.c util.c
+OBJ = ${SRC:.c=.o}
+
+all: options dwm
+
+options:
+ @echo dwm build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.h config.mk
+
+config.h:
+ cp config.def.h $@
+
+dwm: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ rm -f config.h dwm ${OBJ} dwm-${VERSION}.tar.gz
+
+dist: clean
+ mkdir -p dwm-${VERSION}
+ cp -R LICENSE Makefile README config.def.h config.mk\
+ dwm.1 drw.h util.h ${SRC} dwm.png transient.c dwm-${VERSION}
+ tar -cf dwm-${VERSION}.tar dwm-${VERSION}
+ gzip dwm-${VERSION}.tar
+ rm -rf dwm-${VERSION}
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f dwm ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/dwm
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1
+ chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/dwm\
+ ${DESTDIR}${MANPREFIX}/man1/dwm.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/README b/README
@@ -0,0 +1,48 @@
+dwm - dynamic window manager
+============================
+dwm is an extremely fast, small, and dynamic window manager for X.
+
+
+Requirements
+------------
+In order to build dwm you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (dwm is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install dwm (if
+necessary as root):
+
+ make clean install
+
+
+Running dwm
+-----------
+Add the following line to your .xinitrc to start dwm using startx:
+
+ exec dwm
+
+In order to connect dwm to a specific display, make sure that
+the DISPLAY environment variable is set correctly, e.g.:
+
+ DISPLAY=foo.bar:1 exec dwm
+
+(This will start dwm on display :1 of the host foo.bar.)
+
+In order to display status info in the bar, you can do something
+like this in your .xinitrc:
+
+ while xsetroot -name "`date` `uptime | sed 's/.*,//'`"
+ do
+ sleep 1
+ done &
+ exec dwm
+
+
+Configuration
+-------------
+The configuration of dwm is done by creating a custom config.h
+and (re)compiling the source code.
diff --git a/README.md b/README.md
@@ -0,0 +1,3 @@
+# dwm
+
+My dwm fork.
+\ No newline at end of file
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,164 @@
+/* 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 */
+#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=18" };
+static const char dmenufont[] = "Inconsolata-ExpandedBold:pixelsize=18";
+static const char col_gray1[] = "#C5C8C6";
+static const char col_gray2[] = "#444444";
+static const char col_gray3[] = "#1d1d1d";
+static const char col_gray4[] = "#C5C8C6";
+static const char col_cyan[] = "#1d1d1d";
+static const unsigned int baralpha = 0x70;
+static const unsigned int borderalpha = OPAQUE;
+static const char *colors[][3] = {
+ /* fg bg border */
+ [SchemeNorm] = { col_gray3, col_gray1, col_gray2 },
+ [SchemeSel] = { col_gray4, col_cyan, col_cyan },
+};
+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},
+};
+
+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 },
+ { NULL, "spterm", NULL, SPTAG(0), 1, -1 },
+ { NULL, "spfm", NULL, SPTAG(1), 1, -1 },
+ { NULL, "upgrader", NULL, SPTAG(2), 1, -1 },
+ { "widget", NULL, NULL, 0, 1, -1 },
+ { "python3", NULL, NULL, 0, 1, -1 },
+ { NULL, "Toolkit", NULL, 0, 1, -1 },
+};
+
+/* layout(s) */
+static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */
+static const int nmaster = 1; /* number of clients in master area */
+static const int resizehints = 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[] = {
+ /* symbol arrange function */
+ { "[]=", tile }, /* first entry is default */
+ { "><>", NULL }, /* no layout function means floating behavior */
+ { "[M]", monocle },
+};
+
+/* key definitions */
+#define MODKEY Mod1Mask
+#define TAGKEYS(KEY,TAG) \
+ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \
+ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \
+ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} },
+
+/* helper for spawning shell commands in the pre dwm-5.0 fashion */
+#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
+
+/* commands */
+static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */
+static const char *dmenucmd[] = { "dmenu_run", "-i", NULL };
+static const char *termcmd[] = { "st", NULL };
+static const char *wallpaper[] = { "wallpaper" };
+static const char *wallpapergif[] = { "wallpapergif" };
+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.AppImage", NULL};
+
+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 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+ { MODKEY, XK_d, incnmaster, {.i = -1 } },
+ { MODKEY, XK_h, setmfact, {.f = -0.05} },
+ { MODKEY, XK_l, setmfact, {.f = +0.05} },
+ { MODKEY, XK_Return, zoom, {0} },
+ { MODKEY, XK_Tab, view, {0} },
+ { MODKEY|ShiftMask, XK_c, killclient, {0} },
+ { MODKEY, XK_t, setlayout, {.v = &layouts[0]} },
+ { MODKEY, XK_m, setlayout, {.v = &layouts[2]} },
+ { MODKEY, XK_space, setlayout, {0} },
+ { MODKEY|ShiftMask, XK_space, togglefloating, {0} },
+ { MODKEY, XK_0, view, {.ui = ~0 } },
+ { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } },
+ { MODKEY, XK_comma, focusmon, {.i = -1 } },
+ { MODKEY, XK_period, focusmon, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } },
+ { MODKEY, 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)
+ TAGKEYS( XK_4, 3)
+ TAGKEYS( XK_5, 4)
+ TAGKEYS( XK_6, 5)
+ TAGKEYS( XK_7, 6)
+ TAGKEYS( XK_8, 7)
+ TAGKEYS( XK_9, 8)
+ { MODKEY|ShiftMask, XK_q, quit, {0} },
+};
+
+/* button definitions */
+/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */
+static const Button buttons[] = {
+ /* click event mask button function argument */
+ { ClkLtSymbol, 0, Button1, setlayout, {0} },
+ { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} },
+ { ClkWinTitle, 0, Button2, zoom, {0} },
+ { ClkStatusText, 0, Button2, spawn, {.v = termcmd } },
+ { ClkClientWin, MODKEY, Button1, movemouse, {0} },
+ { ClkClientWin, MODKEY, Button2, togglefloating, {0} },
+ { ClkClientWin, MODKEY, Button1, resizemouse, {0} },
+ { ClkTagBar, 0, Button1, view, {0} },
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+ { ClkTagBar, MODKEY, Button3, toggletag, {0} },
+};
+
diff --git a/config.mk b/config.mk
@@ -0,0 +1,39 @@
+# dwm version
+VERSION = 6.4
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# Xinerama, comment if you don't want it
+XINERAMALIBS = -lXinerama
+XINERAMAFLAGS = -DXINERAMA
+
+# freetype
+FREETYPELIBS = -lfontconfig -lXft
+FREETYPEINC = /usr/include/freetype2
+# OpenBSD (uncomment)
+#FREETYPEINC = ${X11INC}/freetype2
+#MANPREFIX = ${PREFIX}/man
+
+# includes and libs
+INCS = -I${X11INC} -I${FREETYPEINC}
+LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lXrender -lImlib2
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
+#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
+CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS}
+LDFLAGS = ${LIBS}
+
+# Solaris
+#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
+#LDFLAGS = ${LIBS}
+
+# compiler and linker
+CC = cc
diff --git a/drw.c b/drw.c
@@ -0,0 +1,527 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+#include <Imlib2.h>
+
+#include "drw.h"
+#include "util.h"
+
+#define UTF_INVALID 0xFFFD
+#define UTF_SIZ 4
+
+static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
+static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+
+static long
+utf8decodebyte(const char c, size_t *i)
+{
+ for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
+ if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
+ return (unsigned char)c & ~utfmask[*i];
+ return 0;
+}
+
+static size_t
+utf8validate(long *u, size_t i)
+{
+ if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
+ *u = UTF_INVALID;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static size_t
+utf8decode(const char *c, long *u, size_t clen)
+{
+ size_t i, j, len, type;
+ long udecoded;
+
+ *u = UTF_INVALID;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (!BETWEEN(len, 1, UTF_SIZ))
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+
+ return len;
+}
+
+Drw *
+drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h, Visual *visual, unsigned int depth, Colormap cmap)
+{
+ Drw *drw = ecalloc(1, sizeof(Drw));
+
+ drw->dpy = dpy;
+ drw->screen = screen;
+ drw->root = root;
+ drw->w = w;
+ drw->h = h;
+ 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;
+}
+
+void
+drw_resize(Drw *drw, unsigned int w, unsigned int h)
+{
+ if (!drw)
+ return;
+
+ 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, 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);
+ free(drw);
+}
+
+/* This function is an implementation detail. Library users should use
+ * drw_fontset_create instead.
+ */
+static Fnt *
+xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
+{
+ Fnt *font;
+ XftFont *xfont = NULL;
+ FcPattern *pattern = NULL;
+
+ if (fontname) {
+ /* Using the pattern found at font->xfont->pattern does not yield the
+ * same substitution results as using the pattern returned by
+ * FcNameParse; using the latter results in the desired fallback
+ * behaviour whereas the former just results in missing-character
+ * rectangles being drawn, at least with some fonts. */
+ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
+ fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
+ return NULL;
+ }
+ if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
+ fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
+ XftFontClose(drw->dpy, xfont);
+ return NULL;
+ }
+ } else if (fontpattern) {
+ if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
+ fprintf(stderr, "error, cannot load font from pattern.\n");
+ return NULL;
+ }
+ } else {
+ die("no font specified.");
+ }
+
+ font = ecalloc(1, sizeof(Fnt));
+ font->xfont = xfont;
+ font->pattern = pattern;
+ font->h = xfont->ascent + xfont->descent;
+ font->dpy = drw->dpy;
+
+ return font;
+}
+
+static void
+xfont_free(Fnt *font)
+{
+ if (!font)
+ return;
+ if (font->pattern)
+ FcPatternDestroy(font->pattern);
+ XftFontClose(font->dpy, font->xfont);
+ free(font);
+}
+
+Fnt*
+drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
+{
+ Fnt *cur, *ret = NULL;
+ size_t i;
+
+ if (!drw || !fonts)
+ return NULL;
+
+ for (i = 1; i <= fontcount; i++) {
+ if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
+ cur->next = ret;
+ ret = cur;
+ }
+ }
+ return (drw->fonts = ret);
+}
+
+void
+drw_fontset_free(Fnt *font)
+{
+ if (font) {
+ drw_fontset_free(font->next);
+ xfont_free(font);
+ }
+}
+
+void
+drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha)
+{
+ if (!drw || !dest || !clrname)
+ return;
+
+ 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[], const unsigned int alphas[], size_t clrcount)
+{
+ size_t i;
+ Clr *ret;
+
+ /* need at least two colors for a scheme */
+ if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
+ return NULL;
+
+ for (i = 0; i < clrcount; i++)
+ drw_clr_create(drw, &ret[i], clrnames[i], alphas[i]);
+ return ret;
+}
+
+void
+drw_setfontset(Drw *drw, Fnt *set)
+{
+ if (drw)
+ drw->fonts = set;
+}
+
+void
+drw_setscheme(Drw *drw, Clr *scm)
+{
+ if (drw)
+ drw->scheme = scm;
+}
+
+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)
+{
+ if (!drw || !drw->scheme)
+ return;
+ XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
+ if (filled)
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ else
+ XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
+}
+
+int
+drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
+{
+ int i, ty, ellipsis_x = 0;
+ unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ long utf8codepoint = 0;
+ const char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0, overflow = 0;
+ /* keep track of a couple codepoints for which we have no match. */
+ enum { nomatches_len = 64 };
+ static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches;
+ static unsigned int ellipsis_width = 0;
+
+ if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
+ return 0;
+
+ if (!render) {
+ w = invert ? invert : ~invert;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ d = XftDrawCreate(drw->dpy, drw->drawable, drw->visual, drw->cmap);
+ x += lpad;
+ w -= lpad;
+ }
+
+ usedfont = drw->fonts;
+ if (!ellipsis_width && render)
+ ellipsis_width = drw_fontset_getwidth(drw, "...");
+ while (1) {
+ ew = ellipsis_len = utf8strlen = 0;
+ utf8str = text;
+ nextfont = NULL;
+ while (*text) {
+ utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
+ if (ew + ellipsis_width <= w) {
+ /* keep track where the ellipsis still fits */
+ ellipsis_x = x + ew;
+ ellipsis_w = w - ew;
+ ellipsis_len = utf8strlen;
+ }
+
+ if (ew + tmpw > w) {
+ overflow = 1;
+ /* called from drw_fontset_getwidth_clamp():
+ * it wants the width AFTER the overflow
+ */
+ if (!render)
+ x += tmpw;
+ else
+ utf8strlen = ellipsis_len;
+ } else if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ text += utf8charlen;
+ ew += tmpw;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (overflow || !charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (utf8strlen) {
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
+ usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen);
+ }
+ x += ew;
+ w -= ew;
+ }
+ if (render && overflow)
+ drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
+
+ if (!*text || overflow) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn. */
+ charexists = 1;
+
+ for (i = 0; i < nomatches_len; ++i) {
+ /* avoid calling XftFontMatch if we know we won't find a match */
+ if (utf8codepoint == nomatches.codepoint[i])
+ goto no_match;
+ }
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ die("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
+no_match:
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x + (render ? w : 0);
+}
+
+void
+drw_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)
+{
+ if (!drw)
+ return;
+
+ XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
+ XSync(drw->dpy, False);
+}
+
+unsigned int
+drw_fontset_getwidth(Drw *drw, const char *text)
+{
+ if (!drw || !drw->fonts || !text)
+ return 0;
+ return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
+}
+
+unsigned int
+drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
+{
+ unsigned int tmp = 0;
+ if (drw && drw->fonts && text && n)
+ tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
+ return MIN(n, tmp);
+}
+
+void
+drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
+{
+ XGlyphInfo ext;
+
+ if (!font || !text)
+ return;
+
+ XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
+ if (w)
+ *w = ext.xOff;
+ if (h)
+ *h = font->h;
+}
+
+Cur *
+drw_cur_create(Drw *drw, int shape)
+{
+ Cur *cur;
+
+ if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
+ return NULL;
+
+ cur->cursor = XCreateFontCursor(drw->dpy, shape);
+
+ return cur;
+}
+
+void
+drw_cur_free(Drw *drw, Cur *cursor)
+{
+ if (!cursor)
+ return;
+
+ XFreeCursor(drw->dpy, cursor->cursor);
+ free(cursor);
+}
diff --git a/drw.h b/drw.h
@@ -0,0 +1,65 @@
+/* See LICENSE file for copyright and license details. */
+
+typedef struct {
+ Cursor cursor;
+} Cur;
+
+typedef struct Fnt {
+ Display *dpy;
+ unsigned int h;
+ XftFont *xfont;
+ FcPattern *pattern;
+ struct Fnt *next;
+} Fnt;
+
+enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */
+typedef XftColor Clr;
+
+typedef struct {
+ unsigned int w, h;
+ Display *dpy;
+ int screen;
+ Window root;
+ 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, Visual *visual, unsigned int depth, Colormap cmap);
+void drw_resize(Drw *drw, unsigned int w, unsigned int h);
+void drw_free(Drw *drw);
+
+/* Fnt abstraction */
+Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
+void drw_fontset_free(Fnt* set);
+unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
+unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
+void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
+
+/* Colorscheme abstraction */
+void drw_clr_create(Drw *drw, Clr *dest, const char *clrname, unsigned int alpha);
+Clr *drw_scm_create(Drw *drw, const char *clrnames[], const unsigned int alphas[], size_t clrcount);
+
+/* Cursor abstraction */
+Cur *drw_cur_create(Drw *drw, int shape);
+void drw_cur_free(Drw *drw, Cur *cursor);
+
+/* Drawing context manipulation */
+void drw_setfontset(Drw *drw, Fnt *set);
+void drw_setscheme(Drw *drw, Clr *scm);
+
+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
@@ -0,0 +1,199 @@
+.TH DWM 1 dwm\-VERSION
+.SH NAME
+dwm \- dynamic window manager
+.SH SYNOPSIS
+.B dwm
+.RB [ \-v ]
+.SH DESCRIPTION
+dwm is a dynamic window manager for X. 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
+In tiled layouts windows are managed in a master and stacking area. The master
+area on the left contains one window by default, and the stacking area on the
+right contains all other windows. The number of master area windows can be
+adjusted from zero to an arbitrary number. In monocle layout all windows are
+maximised to the screen size. In floating layout windows can be resized and
+moved freely. Dialog windows are always managed floating, regardless of the
+layout applied.
+.P
+Windows are grouped by tags. Each window can be tagged with one or multiple
+tags. Selecting certain tags displays all windows with these tags.
+.P
+Each screen contains a small status bar which displays all available tags, the
+layout, the title of the focused window, and the text read from the root window
+name property, if the screen is focused. A floating window is indicated with an
+empty square and a maximised floating window is indicated with a filled square
+before the windows title. The selected tags are indicated with a different
+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.
+.P
+On start, dwm 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 dwm will wait for its
+termination before starting. The latter is executed in the background before
+dwm enters its handler loop.
+.P
+Either of these files may be omitted.
+.SH OPTIONS
+.TP
+.B \-v
+prints version information to stderr, then exits.
+.SH USAGE
+.SS Status bar
+.TP
+.B X root window name
+is read and displayed in the status text area. It can be set with the
+.BR xsetroot (1)
+command.
+.TP
+.B Button1
+click on a tag label to display all windows with that tag, click on the layout
+label toggles between tiled and floating layout.
+.TP
+.B Button3
+click on a tag label adds/removes all windows with that tag to/from the view.
+.TP
+.B Mod1\-Button1
+click on a tag label applies that tag to the focused window.
+.TP
+.B Mod1\-Button3
+click on a tag label adds/removes that tag to/from the focused window.
+.SS Keyboard commands
+.TP
+.B Mod1\-Shift\-Return
+Start
+.BR st(1).
+.TP
+.B Mod1\-p
+Spawn
+.BR dmenu(1)
+for launching other programs.
+.TP
+.B Mod1\-,
+Focus previous screen, if any.
+.TP
+.B Mod1\-.
+Focus next screen, if any.
+.TP
+.B Mod1\-Shift\-,
+Send focused window to previous screen, if any.
+.TP
+.B Mod1\-Shift\-.
+Send focused window to next screen, if any.
+.TP
+.B Mod1\-b
+Toggles bar on and off.
+.TP
+.B Mod1\-t
+Sets tiled layout.
+.TP
+.B Mod1\-f
+Sets floating layout.
+.TP
+.B Mod1\-m
+Sets monocle layout.
+.TP
+.B Mod1\-space
+Toggles between current and previous layout.
+.TP
+.B Mod1\-j
+Focus next window.
+.TP
+.B Mod1\-k
+Focus previous window.
+.TP
+.B Mod1\-i
+Increase number of windows in master area.
+.TP
+.B Mod1\-d
+Decrease number of windows in master area.
+.TP
+.B Mod1\-l
+Increase master area size.
+.TP
+.B Mod1\-h
+Decrease master area size.
+.TP
+.B Mod1\-Return
+Zooms/cycles focused window to/from master area (tiled layouts only).
+.TP
+.B Mod1\-Shift\-c
+Close focused window.
+.TP
+.B Mod1\-Shift\-space
+Toggle focused window between tiled and floating state.
+.TP
+.B Mod1\-Tab
+Toggles to the previously selected tags.
+.TP
+.B Mod1\-Shift\-[1..n]
+Apply nth tag to focused window.
+.TP
+.B Mod1\-Shift\-0
+Apply all tags to focused window.
+.TP
+.B Mod1\-Control\-Shift\-[1..n]
+Add/remove nth tag to/from focused window.
+.TP
+.B Mod1\-[1..n]
+View all windows with nth tag.
+.TP
+.B Mod1\-0
+View all windows with any tag.
+.TP
+.B Mod1\-Control\-[1..n]
+Add/remove all windows with nth tag to/from the view.
+.TP
+.B Mod1\-Shift\-q
+Quit dwm.
+.SS Mouse commands
+.TP
+.B Mod1\-Button1
+Move focused window while dragging. Tiled windows will be toggled to the floating state.
+.TP
+.B Mod1\-Button2
+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 dwm are searched for in
+the following directories:
+.IP "1. $XDG_DATA_HOME/dwm"
+.IP "2. $HOME/.local/share/dwm"
+.IP "3. $HOME/.dwm"
+.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 dwm enters its handler
+loop.
+.TP 15
+autostart_blocking.sh
+This file is started before any autostart.sh; dwm waits for its termination.
+.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 SEE ALSO
+.BR dmenu (1),
+.BR st (1)
+.SH ISSUES
+Java applications which use the XToolkit/XAWT backend may draw grey windows
+only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early
+JDK 1.6 versions, because it assumes a reparenting window manager. Possible workarounds
+are using JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or setting the
+environment variable
+.BR AWT_TOOLKIT=MToolkit
+(to use the older Motif backend instead) or running
+.B xprop -root -f _NET_WM_NAME 32a -set _NET_WM_NAME LG3D
+or
+.B wmname LG3D
+(to pretend that a non-reparenting window manager is running that the
+XToolkit/XAWT backend can recognize) or when using OpenJDK setting the environment variable
+.BR _JAVA_AWT_WM_NONREPARENTING=1 .
+.SH BUGS
+Send all bug reports with a patch to hackers@suckless.org.
diff --git a/dwm.c b/dwm.c
@@ -0,0 +1,2816 @@
+/* See LICENSE file for copyright and license details.
+ *
+ * dynamic window manager is designed like any other X client as well. It is
+ * driven through handling X events. In contrast to other X clients, a window
+ * manager selects for SubstructureRedirectMask on the root window, to receive
+ * events about window (dis-)appearance. Only one X connection at a time is
+ * allowed to select for this event mask.
+ *
+ * The event handlers of dwm are organized in an array which is accessed
+ * whenever a new event has been fetched. This allows event dispatching
+ * in O(1) time.
+ *
+ * Each child of the root window is called a client, except windows which have
+ * set the override_redirect flag. Clients are organized in a linked client
+ * list on each monitor, the focus history is remembered through a stack list
+ * on each monitor. Each client contains a bit array to indicate the tags of a
+ * 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>
+#include <stdarg.h>
+#include <stdio.h>
+#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/Xutil.h>
+#ifdef XINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif /* XINERAMA */
+#include <X11/Xft/Xft.h>
+
+#include "drw.h"
+#include "util.h"
+
+/* macros */
+#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask)
+#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))
+#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \
+ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy)))
+#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]))
+#define LENGTH(X) (sizeof X / sizeof X[0])
+#define MOUSEMASK (BUTTONMASK|PointerMotionMask)
+#define WIDTH(X) ((X)->w + 2 * (X)->bw + 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 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, 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 */
+
+typedef union {
+ int i;
+ unsigned int ui;
+ float f;
+ const void *v;
+} Arg;
+
+typedef struct {
+ unsigned int click;
+ unsigned int mask;
+ unsigned int button;
+ void (*func)(const Arg *arg);
+ const Arg arg;
+} Button;
+
+typedef struct Monitor Monitor;
+typedef struct Client Client;
+struct Client {
+ char name[256];
+ float mina, maxa;
+ int x, y, w, h;
+ int oldx, oldy, oldw, oldh;
+ int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;
+ int bw, oldbw;
+ unsigned int tags;
+ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
+ unsigned int icw, ich; Picture icon;
+ Client *next;
+ Client *snext;
+ Monitor *mon;
+ Window win;
+};
+
+typedef struct {
+ unsigned int mod;
+ KeySym keysym;
+ void (*func)(const Arg *);
+ const Arg arg;
+} Key;
+
+typedef struct {
+ const char *symbol;
+ void (*arrange)(Monitor *);
+} Layout;
+
+struct Monitor {
+ char ltsymbol[16];
+ float mfact;
+ int nmaster;
+ int num;
+ int by; /* bar geometry */
+ int mx, my, mw, mh; /* screen size */
+ int wx, wy, ww, wh; /* window area */
+ unsigned int seltags;
+ unsigned int sellt;
+ unsigned int tagset[2];
+ int showbar;
+ int topbar;
+ Client *clients;
+ Client *sel;
+ Client *stack;
+ Monitor *next;
+ Window barwin;
+ const Layout *lt[2];
+};
+
+typedef struct {
+ const char *class;
+ const char *instance;
+ const char *title;
+ unsigned int tags;
+ int isfloating;
+ int monitor;
+} Rule;
+
+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);
+static void arrange(Monitor *m);
+static void arrangemon(Monitor *m);
+static void attach(Client *c);
+static void attachstack(Client *c);
+static void buttonpress(XEvent *e);
+static void checkotherwm(void);
+static void cleanup(void);
+static void cleanupmon(Monitor *mon);
+static void clientmessage(XEvent *e);
+static void configure(Client *c);
+static void configurenotify(XEvent *e);
+static void configurerequest(XEvent *e);
+static Monitor *createmon(void);
+static void destroynotify(XEvent *e);
+static void detach(Client *c);
+static void detachstack(Client *c);
+static Monitor *dirtomon(int dir);
+static void drawbar(Monitor *m);
+static void drawbars(void);
+static void enternotify(XEvent *e);
+static void expose(XEvent *e);
+static void focus(Client *c);
+static void focusin(XEvent *e);
+static void focusmon(const Arg *arg);
+static void focusstack(const Arg *arg);
+static Atom getatomprop(Client *c, Atom prop);
+static 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 manage(Window w, XWindowAttributes *wa);
+static void mappingnotify(XEvent *e);
+static void maprequest(XEvent *e);
+static void monocle(Monitor *m);
+static void motionnotify(XEvent *e);
+static void movemouse(const Arg *arg);
+static Client *nexttiled(Client *c);
+static void pop(Client *c);
+static void propertynotify(XEvent *e);
+static void quit(const Arg *arg);
+static Monitor *recttomon(int x, int y, int w, int h);
+static void resize(Client *c, int x, int y, int w, int h, int interact);
+static void resizeclient(Client *c, int x, int y, int w, int h);
+static void resizemouse(const Arg *arg);
+static void 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(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);
+static void setfullscreen(Client *c, int fullscreen);
+static void setlayout(const Arg *arg);
+static void setmfact(const Arg *arg);
+static void setup(void);
+static void seturgent(Client *c, int urg);
+static void showhide(Client *c);
+static void spawn(const Arg *arg);
+static 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);
+static void updatebarpos(Monitor *m);
+static void updatebars(void);
+static void updateclientlist(void);
+static int updategeom(void);
+static void updatenumlockmask(void);
+static void updatesizehints(Client *c);
+static void updatestatus(void);
+static void 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 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 */
+static int bh; /* bar height */
+static int lrpad; /* sum of left and right padding for text */
+static int (*xerrorxlib)(Display *, XErrorEvent *);
+static unsigned int numlockmask = 0;
+static void (*handler[LASTEvent]) (XEvent *) = {
+ [ButtonPress] = buttonpress,
+ [ClientMessage] = clientmessage,
+ [ConfigureRequest] = configurerequest,
+ [ConfigureNotify] = configurenotify,
+ [DestroyNotify] = destroynotify,
+ [EnterNotify] = enternotify,
+ [Expose] = expose,
+ [FocusIn] = focusin,
+ [KeyPress] = keypress,
+ [MappingNotify] = mappingnotify,
+ [MapRequest] = maprequest,
+ [MotionNotify] = motionnotify,
+ [PropertyNotify] = propertynotify,
+ [ResizeRequest] = resizerequest,
+ [UnmapNotify] = unmapnotify
+};
+static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast];
+static int running = 1;
+static Cur *cursor[CurLast];
+static Clr **scheme;
+static Display *dpy;
+static Drw *drw;
+static Monitor *mons, *selmon;
+static Window root, wmcheckwin;
+
+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"
+
+/* compile-time check if all tags fit into an unsigned int bit array. */
+struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; };
+
+/* function implementations */
+void
+applyrules(Client *c)
+{
+ const char *class, *instance;
+ unsigned int i;
+ const Rule *r;
+ Monitor *m;
+ XClassHint ch = { NULL, NULL };
+
+ /* rule matching */
+ c->isfloating = 0;
+ c->tags = 0;
+ XGetClassHint(dpy, c->win, &ch);
+ class = ch.res_class ? ch.res_class : broken;
+ instance = ch.res_name ? ch.res_name : broken;
+
+ for (i = 0; i < LENGTH(rules); i++) {
+ r = &rules[i];
+ if ((!r->title || strstr(c->name, r->title))
+ && (!r->class || strstr(class, r->class))
+ && (!r->instance || strstr(instance, r->instance)))
+ {
+ c->isfloating = r->isfloating;
+ c->tags |= r->tags;
+ 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;
+ }
+ }
+ if (ch.res_class)
+ XFree(ch.res_class);
+ if (ch.res_name)
+ XFree(ch.res_name);
+ c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : (c->mon->tagset[c->mon->seltags] & ~SPTAGMASK);
+}
+
+int
+applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact)
+{
+ int baseismin;
+ Monitor *m = c->mon;
+
+ /* set minimum possible */
+ *w = MAX(1, *w);
+ *h = MAX(1, *h);
+ if (interact) {
+ if (*x > sw)
+ *x = sw - WIDTH(c);
+ if (*y > sh)
+ *y = sh - HEIGHT(c);
+ if (*x + *w + 2 * c->bw < 0)
+ *x = 0;
+ if (*y + *h + 2 * c->bw < 0)
+ *y = 0;
+ } else {
+ if (*x >= m->wx + m->ww)
+ *x = m->wx + m->ww - WIDTH(c);
+ if (*y >= m->wy + m->wh)
+ *y = m->wy + m->wh - HEIGHT(c);
+ if (*x + *w + 2 * c->bw <= m->wx)
+ *x = m->wx;
+ if (*y + *h + 2 * c->bw <= m->wy)
+ *y = m->wy;
+ }
+ if (*h < bh)
+ *h = bh;
+ if (*w < bh)
+ *w = bh;
+ if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) {
+ if (!c->hintsvalid)
+ updatesizehints(c);
+ /* see last two sentences in ICCCM 4.1.2.3 */
+ baseismin = c->basew == c->minw && c->baseh == c->minh;
+ if (!baseismin) { /* temporarily remove base dimensions */
+ *w -= c->basew;
+ *h -= c->baseh;
+ }
+ /* adjust for aspect limits */
+ if (c->mina > 0 && c->maxa > 0) {
+ if (c->maxa < (float)*w / *h)
+ *w = *h * c->maxa + 0.5;
+ else if (c->mina < (float)*h / *w)
+ *h = *w * c->mina + 0.5;
+ }
+ if (baseismin) { /* increment calculation requires this */
+ *w -= c->basew;
+ *h -= c->baseh;
+ }
+ /* adjust for increment value */
+ if (c->incw)
+ *w -= *w % c->incw;
+ if (c->inch)
+ *h -= *h % c->inch;
+ /* restore base dimensions */
+ *w = MAX(*w + c->basew, c->minw);
+ *h = MAX(*h + c->baseh, c->minh);
+ if (c->maxw)
+ *w = MIN(*w, c->maxw);
+ if (c->maxh)
+ *h = MIN(*h, c->maxh);
+ }
+ return *x != c->x || *y != c->y || *w != c->w || *h != c->h;
+}
+
+void
+arrange(Monitor *m)
+{
+ if (m)
+ showhide(m->stack);
+ else for (m = mons; m; m = m->next)
+ showhide(m->stack);
+ if (m) {
+ arrangemon(m);
+ restack(m);
+ } else for (m = mons; m; m = m->next)
+ arrangemon(m);
+}
+
+void
+arrangemon(Monitor *m)
+{
+ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol);
+ if (m->lt[m->sellt]->arrange)
+ m->lt[m->sellt]->arrange(m);
+}
+
+void
+attach(Client *c)
+{
+ c->next = c->mon->clients;
+ c->mon->clients = c;
+}
+
+void
+attachstack(Client *c)
+{
+ c->snext = c->mon->stack;
+ c->mon->stack = c;
+}
+
+void
+buttonpress(XEvent *e)
+{
+ unsigned int i, x, click;
+ Arg arg = {0};
+ Client *c;
+ Monitor *m;
+ XButtonPressedEvent *ev = &e->xbutton;
+
+ click = ClkRootWin;
+ /* focus monitor if necessary */
+ if ((m = wintomon(ev->window)) && m != selmon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ focus(NULL);
+ }
+ if (ev->window == selmon->barwin) {
+ i = x = 0;
+ do
+ x += TEXTW(tags[i]);
+ while (ev->x >= x && ++i < LENGTH(tags));
+ if (i < LENGTH(tags)) {
+ click = ClkTagBar;
+ arg.ui = 1 << i;
+ } else if (ev->x < x + TEXTW(selmon->ltsymbol))
+ click = ClkLtSymbol;
+ else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth())
+ click = ClkStatusText;
+ else
+ click = ClkWinTitle;
+ } else if ((c = wintoclient(ev->window))) {
+ focus(c);
+ restack(selmon);
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+ click = ClkClientWin;
+ }
+ for (i = 0; i < LENGTH(buttons); i++)
+ if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button
+ && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state))
+ buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
+}
+
+void
+checkotherwm(void)
+{
+ xerrorxlib = XSetErrorHandler(xerrorstart);
+ /* this causes an error if some other window manager is running */
+ XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XSync(dpy, False);
+}
+
+void
+cleanup(void)
+{
+ Arg a = {.ui = ~0};
+ Layout foo = { "", NULL };
+ Monitor *m;
+ size_t i;
+
+ view(&a);
+ selmon->lt[selmon->sellt] = &foo;
+ for (m = mons; m; m = m->next)
+ while (m->stack)
+ unmanage(m->stack, 0);
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ while (mons)
+ cleanupmon(mons);
+ 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++)
+ free(scheme[i]);
+ free(scheme);
+ XDestroyWindow(dpy, wmcheckwin);
+ drw_free(drw);
+ XSync(dpy, False);
+ XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+}
+
+void
+cleanupmon(Monitor *mon)
+{
+ Monitor *m;
+
+ if (mon == mons)
+ mons = mons->next;
+ else {
+ for (m = mons; m && m->next != mon; m = m->next);
+ m->next = mon->next;
+ }
+ XUnmapWindow(dpy, mon->barwin);
+ XDestroyWindow(dpy, mon->barwin);
+ free(mon);
+}
+
+void
+clientmessage(XEvent *e)
+{
+ 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]) {
+ if (cme->data.l[1] == netatom[NetWMFullscreen]
+ || cme->data.l[2] == netatom[NetWMFullscreen])
+ setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */
+ || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen)));
+ } else if (cme->message_type == netatom[NetActiveWindow]) {
+ if (c != selmon->sel && !c->isurgent)
+ seturgent(c, 1);
+ }
+}
+
+void
+configure(Client *c)
+{
+ XConfigureEvent ce;
+
+ ce.type = ConfigureNotify;
+ ce.display = dpy;
+ ce.event = c->win;
+ ce.window = c->win;
+ ce.x = c->x;
+ ce.y = c->y;
+ ce.width = c->w;
+ ce.height = c->h;
+ ce.border_width = c->bw;
+ ce.above = None;
+ ce.override_redirect = False;
+ XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
+}
+
+void
+configurenotify(XEvent *e)
+{
+ Monitor *m;
+ Client *c;
+ XConfigureEvent *ev = &e->xconfigure;
+ int dirty;
+
+ /* TODO: updategeom handling sucks, needs to be simplified */
+ if (ev->window == root) {
+ dirty = (sw != ev->width || sh != ev->height);
+ sw = ev->width;
+ sh = ev->height;
+ if (updategeom() || dirty) {
+ drw_resize(drw, sw, bh);
+ updatebars();
+ for (m = mons; m; m = m->next) {
+ for (c = m->clients; c; c = c->next)
+ if (c->isfullscreen)
+ resizeclient(c, m->mx, m->my, m->mw, m->mh);
+ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh);
+ }
+ focus(NULL);
+ arrange(NULL);
+ }
+ }
+}
+
+void
+configurerequest(XEvent *e)
+{
+ Client *c;
+ Monitor *m;
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+ XWindowChanges wc;
+
+ if ((c = wintoclient(ev->window))) {
+ if (ev->value_mask & CWBorderWidth)
+ c->bw = ev->border_width;
+ else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
+ m = c->mon;
+ if (ev->value_mask & CWX) {
+ c->oldx = c->x;
+ c->x = m->mx + ev->x;
+ }
+ if (ev->value_mask & CWY) {
+ c->oldy = c->y;
+ c->y = m->my + ev->y;
+ }
+ if (ev->value_mask & CWWidth) {
+ c->oldw = c->w;
+ c->w = ev->width;
+ }
+ if (ev->value_mask & CWHeight) {
+ c->oldh = c->h;
+ c->h = ev->height;
+ }
+ if ((c->x + c->w) > m->mx + m->mw && c->isfloating)
+ c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */
+ if ((c->y + c->h) > m->my + m->mh && c->isfloating)
+ c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */
+ if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight)))
+ configure(c);
+ if (ISVISIBLE(c))
+ XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
+ } else
+ configure(c);
+ } else {
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
+ }
+ XSync(dpy, False);
+}
+
+Monitor *
+createmon(void)
+{
+ Monitor *m;
+
+ m = ecalloc(1, sizeof(Monitor));
+ m->tagset[0] = m->tagset[1] = 1;
+ m->mfact = mfact;
+ m->nmaster = nmaster;
+ m->showbar = showbar;
+ m->topbar = topbar;
+ m->lt[0] = &layouts[0];
+ m->lt[1] = &layouts[1 % LENGTH(layouts)];
+ strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
+ return m;
+}
+
+void
+destroynotify(XEvent *e)
+{
+ Client *c;
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+
+ if ((c = wintoclient(ev->window)))
+ unmanage(c, 1);
+ else if (showsystray && (c = wintosystrayicon(ev->window))) {
+ removesystrayicon(c);
+ updatesystray(1);
+ }
+}
+
+void
+detach(Client *c)
+{
+ Client **tc;
+
+ for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next);
+ *tc = c->next;
+}
+
+void
+detachstack(Client *c)
+{
+ Client **tc, *t;
+
+ for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext);
+ *tc = c->snext;
+
+ if (c == c->mon->sel) {
+ for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext);
+ c->mon->sel = t;
+ }
+}
+
+Monitor *
+dirtomon(int dir)
+{
+ Monitor *m = NULL;
+
+ if (dir > 0) {
+ if (!(m = selmon->next))
+ m = mons;
+ } else if (selmon == mons)
+ for (m = mons; m->next; m = m->next);
+ else
+ for (m = mons; m->next != selmon; m = m->next);
+ return m;
+}
+
+void
+drawbar(Monitor *m)
+{
+ int x, w, tw = 0, 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;
+
+ /* draw status first so it can be overdrawn by tags later */
+ if (m == selmon) { /* status is only drawn on selected monitor */
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */
+ drw_text(drw, m->ww - tw - stw, 0, tw, bh, 0, stext, 0);
+ }
+
+ for (c = m->clients; c; c = c->next) {
+ occ |= c->tags;
+ if (c->isurgent)
+ urg |= c->tags;
+ }
+ x = 0;
+ for (i = 0; i < LENGTH(tags); i++) {
+ w = TEXTW(tags[i]);
+ drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]);
+ drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i);
+ if (occ & 1 << i)
+ drw_rect(drw, x + boxs, boxs, boxw, boxw,
+ m == selmon && selmon->sel && selmon->sel->tags & 1 << i,
+ urg & 1 << i);
+ x += w;
+ }
+ w = TEXTW(m->ltsymbol);
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
+
+ if ((w = m->ww - tw - 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->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 {
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, x, 0, w, bh, 1, 1);
+ }
+ }
+ drw_map(drw, m->barwin, 0, 0, m->ww, bh);
+}
+
+void
+drawbars(void)
+{
+ Monitor *m;
+
+ for (m = mons; m; m = m->next)
+ drawbar(m);
+
+ if (showsystray && !systraypinning)
+ updatesystray(0);
+}
+
+void
+enternotify(XEvent *e)
+{
+ Client *c;
+ Monitor *m;
+ XCrossingEvent *ev = &e->xcrossing;
+
+ if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root)
+ return;
+ c = wintoclient(ev->window);
+ m = c ? c->mon : wintomon(ev->window);
+ if (m != selmon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ } else if (!c || c == selmon->sel)
+ return;
+ focus(c);
+}
+
+void
+expose(XEvent *e)
+{
+ Monitor *m;
+ XExposeEvent *ev = &e->xexpose;
+
+ if (ev->count == 0 && (m = wintomon(ev->window))) {
+ drawbar(m);
+
+ if (showsystray && m == systraytomon(m))
+ updatesystray(0);
+ }
+}
+
+void
+focus(Client *c)
+{
+ if (!c || !ISVISIBLE(c))
+ for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
+ if (selmon->sel && selmon->sel != c)
+ unfocus(selmon->sel, 0);
+ if (c) {
+ if (c->mon != selmon)
+ selmon = c->mon;
+ if (c->isurgent)
+ seturgent(c, 0);
+ detachstack(c);
+ attachstack(c);
+ grabbuttons(c, 1);
+ XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel);
+ setfocus(c);
+ } else {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+ selmon->sel = c;
+ drawbars();
+}
+
+/* there are some broken focus acquiring clients needing extra handling */
+void
+focusin(XEvent *e)
+{
+ XFocusChangeEvent *ev = &e->xfocus;
+
+ if (selmon->sel && ev->window != selmon->sel->win)
+ setfocus(selmon->sel);
+}
+
+void
+focusmon(const Arg *arg)
+{
+ Monitor *m;
+
+ if (!mons->next)
+ return;
+ if ((m = dirtomon(arg->i)) == selmon)
+ return;
+ unfocus(selmon->sel, 0);
+ selmon = m;
+ focus(NULL);
+ if (selmon->sel)
+ XWarpPointer(dpy, None, selmon->sel->win, 0, 0, 0, 0, selmon->sel->w/2, selmon->sel->h/2);
+}
+
+void
+focusstack(const Arg *arg)
+{
+ Client *c = NULL, *i;
+
+ if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen))
+ return;
+ if (arg->i > 0) {
+ for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next);
+ if (!c)
+ for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next);
+ } else {
+ for (i = selmon->clients; i != selmon->sel; i = i->next)
+ if (ISVISIBLE(i))
+ c = i;
+ if (!c)
+ for (; i; i = i->next)
+ if (ISVISIBLE(i))
+ c = i;
+ }
+ if (c) {
+ focus(c);
+ restack(selmon);
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w/2, c->h/2);
+ }
+}
+
+Atom
+getatomprop(Client *c, Atom prop)
+{
+ int di;
+ unsigned long dl;
+ unsigned char *p = NULL;
+ Atom da, atom = None;
+
+ /* 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)
+{
+ int di;
+ unsigned int dui;
+ Window dummy;
+
+ return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui);
+}
+
+long
+getstate(Window w)
+{
+ int format;
+ long result = -1;
+ unsigned char *p = NULL;
+ unsigned long n, extra;
+ Atom real;
+
+ if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState],
+ &real, &format, &n, &extra, (unsigned char **)&p) != Success)
+ return -1;
+ if (n != 0)
+ result = *p;
+ XFree(p);
+ return result;
+}
+
+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)
+{
+ char **list = NULL;
+ int n;
+ XTextProperty name;
+
+ if (!text || size == 0)
+ return 0;
+ text[0] = '\0';
+ if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems)
+ return 0;
+ if (name.encoding == XA_STRING) {
+ strncpy(text, (char *)name.value, size - 1);
+ } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) {
+ strncpy(text, *list, size - 1);
+ XFreeStringList(list);
+ }
+ text[size - 1] = '\0';
+ XFree(name.value);
+ return 1;
+}
+
+void
+grabbuttons(Client *c, int focused)
+{
+ updatenumlockmask();
+ {
+ unsigned int i, j;
+ unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ if (!focused)
+ XGrabButton(dpy, AnyButton, AnyModifier, c->win, False,
+ BUTTONMASK, GrabModeSync, GrabModeSync, None, None);
+ for (i = 0; i < LENGTH(buttons); i++)
+ if (buttons[i].click == ClkClientWin)
+ for (j = 0; j < LENGTH(modifiers); j++)
+ XGrabButton(dpy, buttons[i].button,
+ buttons[i].mask | modifiers[j],
+ c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ }
+}
+
+void
+grabkeys(void)
+{
+ updatenumlockmask();
+ {
+ unsigned int i, j, k;
+ unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
+ int start, end, skip;
+ KeySym *syms;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ XDisplayKeycodes(dpy, &start, &end);
+ syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
+ if (!syms)
+ return;
+ for (k = start; k <= end; k++)
+ for (i = 0; i < LENGTH(keys); i++)
+ /* skip modifier codes, we do that ourselves */
+ if (keys[i].keysym == syms[(k - start) * skip])
+ for (j = 0; j < LENGTH(modifiers); j++)
+ XGrabKey(dpy, k,
+ keys[i].mod | modifiers[j],
+ root, True,
+ GrabModeAsync, GrabModeAsync);
+ XFree(syms);
+ }
+}
+
+void
+incnmaster(const Arg *arg)
+{
+ selmon->nmaster = MAX(selmon->nmaster + arg->i, 0);
+ arrange(selmon);
+}
+
+#ifdef XINERAMA
+static int
+isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info)
+{
+ while (n--)
+ if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org
+ && unique[n].width == info->width && unique[n].height == info->height)
+ return 0;
+ return 1;
+}
+#endif /* XINERAMA */
+
+void
+keypress(XEvent *e)
+{
+ unsigned int i;
+ KeySym keysym;
+ XKeyEvent *ev;
+
+ ev = &e->xkey;
+ keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0);
+ for (i = 0; i < LENGTH(keys); i++)
+ if (keysym == keys[i].keysym
+ && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
+ && keys[i].func)
+ keys[i].func(&(keys[i].arg));
+}
+
+void
+killclient(const Arg *arg)
+{
+ if (!selmon->sel)
+ return;
+ if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0, 0, 0)) {
+ XGrabServer(dpy);
+ XSetErrorHandler(xerrordummy);
+ XSetCloseDownMode(dpy, DestroyAll);
+ XKillClient(dpy, selmon->sel->win);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XUngrabServer(dpy);
+ }
+}
+
+void
+manage(Window w, XWindowAttributes *wa)
+{
+ Client *c, *t = NULL;
+ Window trans = None;
+ XWindowChanges wc;
+
+ c = ecalloc(1, sizeof(Client));
+ c->win = w;
+ /* geometry */
+ c->x = c->oldx = wa->x;
+ c->y = c->oldy = wa->y;
+ c->w = c->oldw = wa->width;
+ c->h = c->oldh = wa->height;
+ c->oldbw = wa->border_width;
+
+ updateicon(c);
+ updatetitle(c);
+ if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) {
+ c->mon = t->mon;
+ c->tags = t->tags;
+ } else {
+ c->mon = selmon;
+ applyrules(c);
+ }
+
+ if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww)
+ c->x = c->mon->wx + c->mon->ww - WIDTH(c);
+ if (c->y + HEIGHT(c) > c->mon->wy + c->mon->wh)
+ c->y = c->mon->wy + c->mon->wh - HEIGHT(c);
+ c->x = MAX(c->x, c->mon->wx);
+ c->y = MAX(c->y, c->mon->wy);
+ c->bw = borderpx;
+
+ wc.border_width = c->bw;
+ XConfigureWindow(dpy, w, CWBorderWidth, &wc);
+ XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel);
+ configure(c); /* propagates border_width, if size doesn't change */
+ updatewindowtype(c);
+ updatesizehints(c);
+ updatewmhints(c);
+ XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
+ grabbuttons(c, 0);
+ if (!c->isfloating)
+ c->isfloating = c->oldstate = trans != None || c->isfixed;
+ if (c->isfloating)
+ XRaiseWindow(dpy, c->win);
+ attach(c);
+ attachstack(c);
+ XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) &(c->win), 1);
+ XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */
+ setclientstate(c, NormalState);
+ if (c->mon == selmon)
+ unfocus(selmon->sel, 0);
+ c->mon->sel = c;
+ arrange(c->mon);
+ XMapWindow(dpy, c->win);
+ focus(NULL);
+}
+
+void
+mappingnotify(XEvent *e)
+{
+ XMappingEvent *ev = &e->xmapping;
+
+ XRefreshKeyboardMapping(ev);
+ if (ev->request == MappingKeyboard)
+ grabkeys();
+}
+
+void
+maprequest(XEvent *e)
+{
+ static XWindowAttributes wa;
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ 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))
+ manage(ev->window, &wa);
+}
+
+void
+monocle(Monitor *m)
+{
+ unsigned int n = 0;
+ Client *c;
+
+ for (c = m->clients; c; c = c->next)
+ if (ISVISIBLE(c))
+ n++;
+ if (n > 0) /* override layout symbol */
+ snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n);
+ for (c = nexttiled(m->clients); c; c = nexttiled(c->next))
+ resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0);
+}
+
+void
+motionnotify(XEvent *e)
+{
+ static Monitor *mon = NULL;
+ Monitor *m;
+ XMotionEvent *ev = &e->xmotion;
+
+ if (ev->window != root)
+ return;
+ if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) {
+ unfocus(selmon->sel, 1);
+ selmon = m;
+ focus(NULL);
+ }
+ mon = m;
+}
+
+void
+movemouse(const Arg *arg)
+{
+ int x, y, ocx, ocy, nx, ny;
+ Client *c;
+ Monitor *m;
+ XEvent ev;
+ Time lasttime = 0;
+
+ if (!(c = selmon->sel))
+ return;
+ if (c->isfullscreen) /* no support moving fullscreen windows by mouse */
+ return;
+ restack(selmon);
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess)
+ return;
+ if (!getrootptr(&x, &y))
+ return;
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+
+ nx = ocx + (ev.xmotion.x - x);
+ ny = ocy + (ev.xmotion.y - y);
+ if (abs(selmon->wx - nx) < snap)
+ nx = selmon->wx;
+ else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap)
+ nx = selmon->wx + selmon->ww - WIDTH(c);
+ if (abs(selmon->wy - ny) < snap)
+ ny = selmon->wy;
+ else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap)
+ ny = selmon->wy + selmon->wh - HEIGHT(c);
+ if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
+ && (abs(nx - c->x) > snap || abs(ny - c->y) > snap))
+ togglefloating(NULL);
+ if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
+ resize(c, nx, ny, c->w, c->h, 1);
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XUngrabPointer(dpy, CurrentTime);
+ if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
+ sendmon(c, m);
+ selmon = m;
+ focus(NULL);
+ }
+}
+
+Client *
+nexttiled(Client *c)
+{
+ for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next);
+ return c;
+}
+
+void
+pop(Client *c)
+{
+ detach(c);
+ attach(c);
+ focus(c);
+ arrange(c->mon);
+}
+
+void
+propertynotify(XEvent *e)
+{
+ Client *c;
+ Window trans;
+ XPropertyEvent *ev = &e->xproperty;
+
+ if (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)
+ return; /* ignore */
+ else if ((c = wintoclient(ev->window))) {
+ switch(ev->atom) {
+ default: break;
+ case XA_WM_TRANSIENT_FOR:
+ if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) &&
+ (c->isfloating = (wintoclient(trans)) != NULL))
+ arrange(c->mon);
+ break;
+ case XA_WM_NORMAL_HINTS:
+ c->hintsvalid = 0;
+ break;
+ case XA_WM_HINTS:
+ updatewmhints(c);
+ drawbars();
+ break;
+ }
+ if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
+ updatetitle(c);
+ if (c == c->mon->sel)
+ drawbar(c->mon);
+ }
+ else if (ev->atom == netatom[NetWMIcon]) {
+ updateicon(c);
+ if (c == c->mon->sel)
+ drawbar(c->mon);
+ }
+ if (ev->atom == netatom[NetWMWindowType])
+ updatewindowtype(c);
+ }
+}
+
+void
+quit(const Arg *arg)
+{
+ running = 0;
+}
+
+Monitor *
+recttomon(int x, int y, int w, int h)
+{
+ Monitor *m, *r = selmon;
+ int a, area = 0;
+
+ for (m = mons; m; m = m->next)
+ if ((a = INTERSECT(x, y, w, h, m)) > area) {
+ area = a;
+ r = m;
+ }
+ return r;
+}
+
+void
+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)
+{
+ if (applysizehints(c, &x, &y, &w, &h, interact))
+ resizeclient(c, x, y, w, h);
+}
+
+void
+resizeclient(Client *c, int x, int y, int w, int h)
+{
+ XWindowChanges wc;
+ unsigned int n;
+ unsigned int gapoffset;
+ unsigned int gapincr;
+ Client *nbc;
+
+ 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);
+}
+
+void
+resizemouse(const Arg *arg)
+{
+ int ocx, ocy, nw, nh;
+ Client *c;
+ Monitor *m;
+ XEvent ev;
+ Time lasttime = 0;
+
+ if (!(c = selmon->sel))
+ return;
+ if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */
+ return;
+ restack(selmon);
+ ocx = c->x;
+ ocy = c->y;
+ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess)
+ return;
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
+ do {
+ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
+ switch(ev.type) {
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+ continue;
+ lasttime = ev.xmotion.time;
+
+ nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1);
+ nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1);
+ if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww
+ && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh)
+ {
+ if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
+ && (abs(nw - c->w) > snap || abs(nh - c->h) > snap))
+ togglefloating(NULL);
+ }
+ if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
+ resize(c, c->x, c->y, nw, nh, 1);
+ break;
+ }
+ } while (ev.type != ButtonRelease);
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
+ XUngrabPointer(dpy, CurrentTime);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+ if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
+ sendmon(c, m);
+ selmon = m;
+ focus(NULL);
+ }
+}
+
+void
+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)
+{
+ Client *c;
+ XEvent ev;
+ XWindowChanges wc;
+
+ drawbar(m);
+ if (!m->sel)
+ return;
+ if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
+ XRaiseWindow(dpy, m->sel->win);
+ if (m->lt[m->sellt]->arrange) {
+ wc.stack_mode = Below;
+ wc.sibling = m->barwin;
+ for (c = m->stack; c; c = c->snext)
+ if (!c->isfloating && ISVISIBLE(c)) {
+ XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
+ wc.sibling = c->win;
+ }
+ }
+ XSync(dpy, False);
+ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+}
+
+void
+run(void)
+{
+ XEvent ev;
+ /* main event loop */
+ XSync(dpy, False);
+ while (running && !XNextEvent(dpy, &ev))
+ if (handler[ev.type])
+ handler[ev.type](&ev); /* call handler */
+}
+
+void
+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)
+{
+ unsigned int i, num;
+ Window d1, d2, *wins = NULL;
+ XWindowAttributes wa;
+
+ if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
+ for (i = 0; i < num; i++) {
+ if (!XGetWindowAttributes(dpy, wins[i], &wa)
+ || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
+ continue;
+ if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)
+ manage(wins[i], &wa);
+ }
+ for (i = 0; i < num; i++) { /* now the transients */
+ if (!XGetWindowAttributes(dpy, wins[i], &wa))
+ continue;
+ if (XGetTransientForHint(dpy, wins[i], &d1)
+ && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState))
+ manage(wins[i], &wa);
+ }
+ if (wins)
+ XFree(wins);
+ }
+}
+
+void
+sendmon(Client *c, Monitor *m)
+{
+ if (c->mon == m)
+ return;
+ unfocus(c, 1);
+ detach(c);
+ detachstack(c);
+ c->mon = m;
+ c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
+ attach(c);
+ attachstack(c);
+ focus(NULL);
+ arrange(NULL);
+}
+
+void
+setclientstate(Client *c, long state)
+{
+ long data[] = { state, None };
+
+ XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
+ PropModeReplace, (unsigned char *)data, 2);
+}
+
+int
+sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4)
+{
+ int n;
+ Atom *protocols, mt;
+ int exists = 0;
+ XEvent ev;
+
+ 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 = w;
+ ev.xclient.message_type = mt;
+ ev.xclient.format = 32;
+ 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;
+}
+
+void
+setfocus(Client *c)
+{
+ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+ XChangeProperty(dpy, root, netatom[NetActiveWindow],
+ XA_WINDOW, 32, PropModeReplace,
+ (unsigned char *) &(c->win), 1);
+ }
+ sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0);
+}
+
+void
+setfullscreen(Client *c, int fullscreen)
+{
+ if (fullscreen && !c->isfullscreen) {
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1);
+ c->isfullscreen = 1;
+ c->oldstate = c->isfloating;
+ c->oldbw = c->bw;
+ c->bw = 0;
+ c->isfloating = 1;
+ resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
+ XRaiseWindow(dpy, c->win);
+ } else if (!fullscreen && c->isfullscreen){
+ XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
+ PropModeReplace, (unsigned char*)0, 0);
+ c->isfullscreen = 0;
+ c->isfloating = c->oldstate;
+ c->bw = c->oldbw;
+ c->x = c->oldx;
+ c->y = c->oldy;
+ c->w = c->oldw;
+ c->h = c->oldh;
+ resizeclient(c, c->x, c->y, c->w, c->h);
+ arrange(c->mon);
+ }
+}
+
+void
+setlayout(const Arg *arg)
+{
+ if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt])
+ selmon->sellt ^= 1;
+ if (arg && arg->v)
+ selmon->lt[selmon->sellt] = (Layout *)arg->v;
+ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol);
+ if (selmon->sel)
+ arrange(selmon);
+ else
+ drawbar(selmon);
+}
+
+/* arg > 1.0 will set mfact absolutely */
+void
+setmfact(const Arg *arg)
+{
+ float f;
+
+ if (!arg || !selmon->lt[selmon->sellt]->arrange)
+ return;
+ f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0;
+ if (f < 0.05 || f > 0.95)
+ return;
+ selmon->mfact = f;
+ arrange(selmon);
+}
+
+void
+setup(void)
+{
+ int i;
+ XSetWindowAttributes wa;
+ Atom utf8string;
+ struct sigaction sa;
+
+ /* do not transform children into zombies when they terminate */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART;
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* clean up any zombies (inherited from .xinitrc etc) immediately */
+ while (waitpid(-1, NULL, WNOHANG) > 0);
+
+ /* init screen */
+ screen = DefaultScreen(dpy);
+ sw = DisplayWidth(dpy, screen);
+ sh = DisplayHeight(dpy, screen);
+ root = RootWindow(dpy, screen);
+ 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;
+ bh = drw->fonts->h + 2;
+ updategeom();
+ /* init atoms */
+ utf8string = XInternAtom(dpy, "UTF8_STRING", False);
+ wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
+ wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
+ wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
+ netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
+ netatom[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);
+ cursor[CurMove] = drw_cur_create(drw, XC_fleur);
+ /* init appearance */
+ scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
+ for (i = 0; i < LENGTH(colors); i++)
+ scheme[i] = drw_scm_create(drw, colors[i], alphas[i], 3);
+ /* init system tray */
+ if (showsystray)
+ updatesystray(0);
+ /* init bars */
+ updatebars();
+ updatestatus();
+ /* supporting window for NetWMCheck */
+ wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+ XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
+ PropModeReplace, (unsigned char *) "dwm", 3);
+ XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
+ PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+ /* EWMH support per view */
+ XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) netatom, NetLast);
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ /* select events */
+ wa.cursor = cursor[CurNormal]->cursor;
+ wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
+ |ButtonPressMask|PointerMotionMask|EnterWindowMask
+ |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
+ XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
+ XSelectInput(dpy, root, wa.event_mask);
+ grabkeys();
+ focus(NULL);
+}
+
+void
+seturgent(Client *c, int urg)
+{
+ XWMHints *wmh;
+
+ c->isurgent = urg;
+ if (!(wmh = XGetWMHints(dpy, c->win)))
+ return;
+ wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint);
+ XSetWMHints(dpy, c->win, wmh);
+ XFree(wmh);
+}
+
+void
+showhide(Client *c)
+{
+ if (!c)
+ return;
+ if (ISVISIBLE(c)) {
+ 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)
+ resize(c, c->x, c->y, c->w, c->h, 0);
+ showhide(c->snext);
+ } else {
+ /* hide clients bottom up */
+ showhide(c->snext);
+ XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y);
+ }
+}
+
+void
+spawn(const Arg *arg)
+{
+ struct sigaction sa;
+
+ if (arg->v == dmenucmd)
+ dmenumon[0] = '0' + selmon->num;
+ if (fork() == 0) {
+ if (dpy)
+ close(ConnectionNumber(dpy));
+ setsid();
+
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ execvp(((char **)arg->v)[0], (char **)arg->v);
+ die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]);
+ }
+}
+
+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)
+{
+ if (selmon->sel && arg->ui & TAGMASK) {
+ selmon->sel->tags = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+tagmon(const Arg *arg)
+{
+ if (!selmon->sel || !mons->next)
+ return;
+ sendmon(selmon->sel, dirtomon(arg->i));
+}
+
+void
+tile(Monitor *m)
+{
+ unsigned int i, n, h, mw, my, ty;
+ Client *c;
+
+ for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
+ if (n == 0)
+ return;
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? m->ww * m->mfact : 0;
+ else
+ mw = m->ww;
+ 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) + (n > 1 ? gappx : 0), h - (2*c->bw), 0);
+ if (my + HEIGHT(c) < m->wh)
+ my += HEIGHT(c);
+ } else {
+ h = (m->wh - ty) / (n - i);
+ resize(c, m->wx + mw, m->wy + ty, m->ww - mw - (2*c->bw), h - (2*c->bw), 0);
+ if (ty + HEIGHT(c) < m->wh)
+ ty += HEIGHT(c);
+ }
+}
+
+void
+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);
+}
+
+void
+togglefloating(const Arg *arg)
+{
+ if (!selmon->sel)
+ return;
+ if (selmon->sel->isfullscreen) /* no support for fullscreen windows */
+ return;
+ selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed;
+ if (selmon->sel->isfloating)
+ resize(selmon->sel, selmon->sel->x, selmon->sel->y,
+ selmon->sel->w, selmon->sel->h, 0);
+ arrange(selmon);
+}
+
+void
+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)
+{
+ unsigned int newtags;
+
+ if (!selmon->sel)
+ return;
+ newtags = selmon->sel->tags ^ (arg->ui & TAGMASK);
+ if (newtags) {
+ selmon->sel->tags = newtags;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+toggleview(const Arg *arg)
+{
+ unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK);
+
+ if (newtagset) {
+ selmon->tagset[selmon->seltags] = newtagset;
+ focus(NULL);
+ arrange(selmon);
+ }
+}
+
+void
+freeicon(Client *c)
+{
+ if (c->icon) {
+ XRenderFreePicture(dpy, c->icon);
+ c->icon = None;
+ }
+}
+
+void
+unfocus(Client *c, int setfocus)
+{
+ if (!c)
+ return;
+ grabbuttons(c, 0);
+ XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
+ if (setfocus) {
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+ XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+}
+
+void
+unmanage(Client *c, int destroyed)
+{
+ Monitor *m = c->mon;
+ XWindowChanges wc;
+
+ detach(c);
+ detachstack(c);
+ freeicon(c);
+ if (!destroyed) {
+ wc.border_width = c->oldbw;
+ XGrabServer(dpy); /* avoid race conditions */
+ XSetErrorHandler(xerrordummy);
+ XSelectInput(dpy, c->win, NoEventMask);
+ XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ setclientstate(c, WithdrawnState);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XUngrabServer(dpy);
+ }
+ free(c);
+ focus(NULL);
+ updateclientlist();
+ arrange(m);
+}
+
+void
+unmapnotify(XEvent *e)
+{
+ Client *c;
+ XUnmapEvent *ev = &e->xunmap;
+
+ if ((c = wintoclient(ev->window))) {
+ if (ev->send_event)
+ setclientstate(c, WithdrawnState);
+ else
+ unmanage(c, 0);
+ } 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);
+ }
+}
+
+void
+updatebars(void)
+{
+ Monitor *m;
+ XSetWindowAttributes wa = {
+ .override_redirect = True,
+ .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, 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);
+ }
+}
+
+void
+updatebarpos(Monitor *m)
+{
+ m->wy = m->my;
+ m->wh = m->mh;
+ if (m->showbar) {
+ m->wh -= bh;
+ m->by = m->topbar ? m->wy : m->wy + m->wh;
+ m->wy = m->topbar ? m->wy + bh : m->wy;
+ } else
+ m->by = -bh;
+}
+
+void
+updateclientlist()
+{
+ Client *c;
+ Monitor *m;
+
+ XDeleteProperty(dpy, root, netatom[NetClientList]);
+ for (m = mons; m; m = m->next)
+ for (c = m->clients; c; c = c->next)
+ XChangeProperty(dpy, root, netatom[NetClientList],
+ XA_WINDOW, 32, PropModeAppend,
+ (unsigned char *) &(c->win), 1);
+}
+
+int
+updategeom(void)
+{
+ int dirty = 0;
+
+#ifdef XINERAMA
+ if (XineramaIsActive(dpy)) {
+ int i, j, n, nn;
+ Client *c;
+ Monitor *m;
+ XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn);
+ XineramaScreenInfo *unique = NULL;
+
+ for (n = 0, m = mons; m; m = m->next, n++);
+ /* only consider unique geometries as separate screens */
+ unique = ecalloc(nn, sizeof(XineramaScreenInfo));
+ for (i = 0, j = 0; i < nn; i++)
+ if (isuniquegeom(unique, j, &info[i]))
+ memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo));
+ XFree(info);
+ nn = j;
+
+ /* new monitors if nn > n */
+ for (i = n; i < nn; i++) {
+ for (m = mons; m && m->next; m = m->next);
+ if (m)
+ m->next = createmon();
+ else
+ mons = createmon();
+ }
+ for (i = 0, m = mons; i < nn && m; m = m->next, i++)
+ if (i >= n
+ || unique[i].x_org != m->mx || unique[i].y_org != m->my
+ || unique[i].width != m->mw || unique[i].height != m->mh)
+ {
+ dirty = 1;
+ m->num = i;
+ m->mx = m->wx = unique[i].x_org;
+ m->my = m->wy = unique[i].y_org;
+ m->mw = m->ww = unique[i].width;
+ m->mh = m->wh = unique[i].height;
+ updatebarpos(m);
+ }
+ /* removed monitors if n > nn */
+ for (i = nn; i < n; i++) {
+ for (m = mons; m && m->next; m = m->next);
+ while ((c = m->clients)) {
+ dirty = 1;
+ m->clients = c->next;
+ detachstack(c);
+ c->mon = mons;
+ attach(c);
+ attachstack(c);
+ }
+ if (m == selmon)
+ selmon = mons;
+ cleanupmon(m);
+ }
+ free(unique);
+ } else
+#endif /* XINERAMA */
+ { /* default monitor setup */
+ if (!mons)
+ mons = createmon();
+ if (mons->mw != sw || mons->mh != sh) {
+ dirty = 1;
+ mons->mw = mons->ww = sw;
+ mons->mh = mons->wh = sh;
+ updatebarpos(mons);
+ }
+ }
+ if (dirty) {
+ selmon = mons;
+ selmon = wintomon(root);
+ }
+ return dirty;
+}
+
+void
+updatenumlockmask(void)
+{
+ unsigned int i, j;
+ XModifierKeymap *modmap;
+
+ numlockmask = 0;
+ modmap = XGetModifierMapping(dpy);
+ for (i = 0; i < 8; i++)
+ for (j = 0; j < modmap->max_keypermod; j++)
+ if (modmap->modifiermap[i * modmap->max_keypermod + j]
+ == XKeysymToKeycode(dpy, XK_Num_Lock))
+ numlockmask = (1 << i);
+ XFreeModifiermap(modmap);
+}
+
+void
+updatesizehints(Client *c)
+{
+ long msize;
+ XSizeHints size;
+
+ if (!XGetWMNormalHints(dpy, c->win, &size, &msize))
+ /* size is uninitialized, ensure that size.flags aren't used */
+ size.flags = PSize;
+ if (size.flags & PBaseSize) {
+ c->basew = size.base_width;
+ c->baseh = size.base_height;
+ } else if (size.flags & PMinSize) {
+ c->basew = size.min_width;
+ c->baseh = size.min_height;
+ } else
+ c->basew = c->baseh = 0;
+ if (size.flags & PResizeInc) {
+ c->incw = size.width_inc;
+ c->inch = size.height_inc;
+ } else
+ c->incw = c->inch = 0;
+ if (size.flags & PMaxSize) {
+ c->maxw = size.max_width;
+ c->maxh = size.max_height;
+ } else
+ c->maxw = c->maxh = 0;
+ if (size.flags & PMinSize) {
+ c->minw = size.min_width;
+ c->minh = size.min_height;
+ } else if (size.flags & PBaseSize) {
+ c->minw = size.base_width;
+ c->minh = size.base_height;
+ } else
+ c->minw = c->minh = 0;
+ if (size.flags & PAspect) {
+ c->mina = (float)size.min_aspect.y / size.min_aspect.x;
+ c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
+ } else
+ c->maxa = c->mina = 0.0;
+ c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh);
+ c->hintsvalid = 1;
+}
+
+void
+updatestatus(void)
+{
+ if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext)))
+ strcpy(stext, "dwm-"VERSION);
+ drawbar(selmon);
+}
+
+void
+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)
+{
+ if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
+ gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
+ if (c->name[0] == '\0') /* hack to mark broken clients */
+ strcpy(c->name, broken);
+}
+
+void
+updateicon(Client *c)
+{
+ freeicon(c);
+ c->icon = geticonprop(c->win, &c->icw, &c->ich);
+}
+
+void
+updatewindowtype(Client *c)
+{
+ Atom state = getatomprop(c, netatom[NetWMState]);
+ Atom wtype = getatomprop(c, netatom[NetWMWindowType]);
+
+ if (state == netatom[NetWMFullscreen])
+ setfullscreen(c, 1);
+ if (wtype == netatom[NetWMWindowTypeDialog])
+ c->isfloating = 1;
+}
+
+void
+updatewmhints(Client *c)
+{
+ XWMHints *wmh;
+
+ if ((wmh = XGetWMHints(dpy, c->win))) {
+ if (c == selmon->sel && wmh->flags & XUrgencyHint) {
+ wmh->flags &= ~XUrgencyHint;
+ XSetWMHints(dpy, c->win, wmh);
+ } else
+ c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0;
+ if (wmh->flags & InputHint)
+ c->neverfocus = !wmh->input;
+ else
+ c->neverfocus = 0;
+ XFree(wmh);
+ }
+}
+
+void
+view(const Arg *arg)
+{
+ if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
+ return;
+ selmon->seltags ^= 1; /* toggle sel tagset */
+ if (arg->ui & TAGMASK)
+ selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+}
+
+Client *
+wintoclient(Window w)
+{
+ Client *c;
+ Monitor *m;
+
+ for (m = mons; m; m = m->next)
+ for (c = m->clients; c; c = c->next)
+ if (c->win == w)
+ return c;
+ return NULL;
+}
+
+Monitor *
+wintomon(Window w)
+{
+ int x, y;
+ Client *c;
+ Monitor *m;
+
+ if (w == root && getrootptr(&x, &y))
+ return recttomon(x, y, 1, 1);
+ for (m = mons; m; m = m->next)
+ if (w == m->barwin)
+ return m;
+ if ((c = wintoclient(w)))
+ return c->mon;
+ return selmon;
+}
+
+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. */
+int
+xerror(Display *dpy, XErrorEvent *ee)
+{
+ if (ee->error_code == BadWindow
+ || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
+ || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
+ || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
+ || (ee->request_code == X_GrabButton && ee->error_code == BadAccess)
+ || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
+ || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
+ return 0;
+ fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
+ ee->request_code, ee->error_code);
+ return xerrorxlib(dpy, ee); /* may call exit */
+}
+
+int
+xerrordummy(Display *dpy, XErrorEvent *ee)
+{
+ return 0;
+}
+
+/* Startup Error handler to check if another window manager
+ * is already running. */
+int
+xerrorstart(Display *dpy, XErrorEvent *ee)
+{
+ die("dwm: another window manager is already running");
+ return -1;
+}
+
+void
+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
+zoom(const Arg *arg)
+{
+ Client *c = selmon->sel;
+
+ if (!selmon->lt[selmon->sellt]->arrange || !c || c->isfloating)
+ return;
+ if (c == nexttiled(selmon->clients) && !(c = nexttiled(c->next)))
+ return;
+ pop(c);
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc == 2 && !strcmp("-v", argv[1]))
+ die("dwm-"VERSION);
+ else if (argc != 1)
+ die("usage: dwm [-v]");
+ if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
+ fputs("warning: no locale support\n", stderr);
+ if (!(dpy = XOpenDisplay(NULL)))
+ die("dwm: cannot open display");
+ checkotherwm();
+ setup();
+#ifdef __OpenBSD__
+ if (pledge("stdio rpath proc exec", NULL) == -1)
+ die("pledge");
+#endif /* __OpenBSD__ */
+ scan();
+ runautostart();
+ run();
+ cleanup();
+ XCloseDisplay(dpy);
+ return EXIT_SUCCESS;
+}
diff --git a/dwm.desktop b/dwm.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Name=DWM
+Comment=Dynamic Window Manager
+TryExec=dwm
+Exec=dwm
+Type=Application
diff --git a/dwm.png b/dwm.png
Binary files differ.
diff --git a/slstatus/LICENSE 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,247 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+/*
+ * https://www.kernel.org/doc/html/latest/power/power_supply_class.html
+ */
+ #include <limits.h>
+ #include <stdint.h>
+ #include <unistd.h>
+
+ #define POWER_SUPPLY_CAPACITY "/sys/class/power_supply/%s/capacity"
+ #define POWER_SUPPLY_STATUS "/sys/class/power_supply/%s/status"
+ #define POWER_SUPPLY_CHARGE "/sys/class/power_supply/%s/charge_now"
+ #define POWER_SUPPLY_ENERGY "/sys/class/power_supply/%s/energy_now"
+ #define POWER_SUPPLY_CURRENT "/sys/class/power_supply/%s/current_now"
+ #define POWER_SUPPLY_POWER "/sys/class/power_supply/%s/power_now"
+
+ static const char *
+ pick(const char *bat, const char *f1, const char *f2, char *path,
+ size_t length)
+ {
+ if (esnprintf(path, length, f1, bat) > 0 &&
+ access(path, R_OK) == 0)
+ return f1;
+
+ if (esnprintf(path, length, f2, bat) > 0 &&
+ access(path, R_OK) == 0)
+ return f2;
+
+ return NULL;
+ }
+
+ const char *
+ battery_perc(const char *bat)
+ {
+ int cap_perc;
+ char path[PATH_MAX];
+
+ if (esnprintf(path, sizeof(path), POWER_SUPPLY_CAPACITY, bat) < 0)
+ return NULL;
+ if (pscanf(path, "%d", &cap_perc) != 1)
+ return NULL;
+
+ return bprintf("%d", cap_perc);
+ }
+
+ const char *
+ battery_state(const char *bat)
+ {
+ static struct {
+ char *state;
+ char *symbol;
+ } map[] = {
+ { "Charging", "+" },
+ { "Discharging", "-" },
+ { "Full", "o" },
+ { "Not charging", "o" },
+ };
+ size_t i;
+ char path[PATH_MAX], state[12];
+
+ if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
+ return NULL;
+ if (pscanf(path, "%12[a-zA-Z ]", state) != 1)
+ return NULL;
+
+ for (i = 0; i < LEN(map); i++)
+ if (!strcmp(map[i].state, state))
+ break;
+
+ return (i == LEN(map)) ? "?" : map[i].symbol;
+ }
+
+ const char *
+ battery_remaining(const char *bat)
+ {
+ uintmax_t charge_now, current_now, m, h;
+ double timeleft;
+ char path[PATH_MAX], state[12];
+
+ if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
+ return NULL;
+ if (pscanf(path, "%12[a-zA-Z ]", state) != 1)
+ return NULL;
+
+ if (!pick(bat, POWER_SUPPLY_CHARGE, POWER_SUPPLY_ENERGY, path,
+ sizeof(path)) ||
+ pscanf(path, "%ju", &charge_now) < 0)
+ return NULL;
+
+ if (!strcmp(state, "Discharging")) {
+ if (!pick(bat, POWER_SUPPLY_CURRENT, POWER_SUPPLY_POWER, path,
+ sizeof(path)) ||
+ pscanf(path, "%ju", ¤t_now) < 0)
+ return NULL;
+
+ if (current_now == 0)
+ return NULL;
+
+ timeleft = (double)charge_now / (double)current_now;
+ h = timeleft;
+ m = (timeleft - (double)h) * 60;
+
+ return bprintf("%juh %jum", h, m);
+ }
+
+ return "";
+ }
+#elif defined(__OpenBSD__)
+ #include <fcntl.h>
+ #include <machine/apmvar.h>
+ #include <sys/ioctl.h>
+ #include <unistd.h>
+
+ static int
+ load_apm_power_info(struct apm_power_info *apm_info)
+ {
+ int fd;
+
+ fd = open("/dev/apm", O_RDONLY);
+ if (fd < 0) {
+ warn("open '/dev/apm':");
+ return 0;
+ }
+
+ memset(apm_info, 0, sizeof(struct apm_power_info));
+ if (ioctl(fd, APM_IOC_GETPOWER, apm_info) < 0) {
+ warn("ioctl 'APM_IOC_GETPOWER':");
+ close(fd);
+ return 0;
+ }
+ return close(fd), 1;
+ }
+
+ const char *
+ battery_perc(const char *unused)
+ {
+ struct apm_power_info apm_info;
+
+ if (load_apm_power_info(&apm_info))
+ return bprintf("%d", apm_info.battery_life);
+
+ return NULL;
+ }
+
+ const char *
+ battery_state(const char *unused)
+ {
+ struct {
+ unsigned int state;
+ char *symbol;
+ } map[] = {
+ { APM_AC_ON, "+" },
+ { APM_AC_OFF, "-" },
+ };
+ struct apm_power_info apm_info;
+ size_t i;
+
+ if (load_apm_power_info(&apm_info)) {
+ for (i = 0; i < LEN(map); i++)
+ if (map[i].state == apm_info.ac_state)
+ break;
+
+ return (i == LEN(map)) ? "?" : map[i].symbol;
+ }
+
+ return NULL;
+ }
+
+ const char *
+ battery_remaining(const char *unused)
+ {
+ struct apm_power_info apm_info;
+ unsigned int h, m;
+
+ if (load_apm_power_info(&apm_info)) {
+ if (apm_info.ac_state != APM_AC_ON) {
+ h = apm_info.minutes_left / 60;
+ m = apm_info.minutes_left % 60;
+ return bprintf("%uh %02um", h, m);
+ } else {
+ return "";
+ }
+ }
+
+ return NULL;
+ }
+#elif defined(__FreeBSD__)
+ #include <sys/sysctl.h>
+
+ #define BATTERY_LIFE "hw.acpi.battery.life"
+ #define BATTERY_STATE "hw.acpi.battery.state"
+ #define BATTERY_TIME "hw.acpi.battery.time"
+
+ const char *
+ battery_perc(const char *unused)
+ {
+ int cap_perc;
+ size_t len;
+
+ len = sizeof(cap_perc);
+ if (sysctlbyname(BATTERY_LIFE, &cap_perc, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ return bprintf("%d", cap_perc);
+ }
+
+ const char *
+ battery_state(const char *unused)
+ {
+ int state;
+ size_t len;
+
+ len = sizeof(state);
+ if (sysctlbyname(BATTERY_STATE, &state, &len, NULL, 0) < 0 || !len)
+ return NULL;
+
+ switch (state) {
+ case 0: /* FALLTHROUGH */
+ case 2:
+ return "+";
+ case 1:
+ return "-";
+ default:
+ return "?";
+ }
+ }
+
+ const char *
+ battery_remaining(const char *unused)
+ {
+ int rem;
+ size_t len;
+
+ len = sizeof(rem);
+ if (sysctlbyname(BATTERY_TIME, &rem, &len, NULL, 0) < 0 || !len
+ || rem < 0)
+ return NULL;
+
+ return bprintf("%uh %02um", rem / 60, rem % 60);
+ }
+#endif
diff --git a/slstatus/components/battery.o b/slstatus/components/battery.o
Binary files differ.
diff --git a/slstatus/components/cat.c 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
Binary files differ.
diff --git a/slstatus/components/cpu.c 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
Binary files differ.
diff --git a/slstatus/components/datetime.c 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
Binary files differ.
diff --git a/slstatus/components/disk.c 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
Binary files differ.
diff --git a/slstatus/components/entropy.c 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
Binary files differ.
diff --git a/slstatus/components/hostname.c 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
Binary files differ.
diff --git a/slstatus/components/ip.c 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
Binary files differ.
diff --git a/slstatus/components/kanji.c 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
Binary files differ.
diff --git a/slstatus/components/kernel_release.c 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
Binary files differ.
diff --git a/slstatus/components/keyboard_indicators.c 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
Binary files differ.
diff --git a/slstatus/components/keymap.c 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
Binary files differ.
diff --git a/slstatus/components/load_avg.c 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
Binary files differ.
diff --git a/slstatus/components/netspeeds.c 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
Binary files differ.
diff --git a/slstatus/components/num_files.c 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
Binary files differ.
diff --git a/slstatus/components/ram.c 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
Binary files differ.
diff --git a/slstatus/components/run_command.c 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
Binary files differ.
diff --git a/slstatus/components/swap.c 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
Binary files differ.
diff --git a/slstatus/components/temperature.c 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
Binary files differ.
diff --git a/slstatus/components/uptime.c 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
Binary files differ.
diff --git a/slstatus/components/user.c 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
Binary files differ.
diff --git a/slstatus/components/volume.c 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
Binary files differ.
diff --git a/slstatus/components/wifi.c 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
Binary files differ.
diff --git a/slstatus/config.def.h 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
@@ -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
@@ -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
@@ -0,0 +1,134 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <X11/Xlib.h>
+
+#include "arg.h"
+#include "slstatus.h"
+#include "util.h"
+
+struct arg {
+ const char *(*func)(const char *);
+ const char *fmt;
+ const char *args;
+};
+
+char buf[1024];
+static volatile sig_atomic_t done;
+static Display *dpy;
+
+#include "config.h"
+
+static void
+terminate(const int signo)
+{
+ if (signo != SIGUSR1)
+ done = 1;
+}
+
+static void
+difftimespec(struct timespec *res, struct timespec *a, struct timespec *b)
+{
+ res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec);
+ res->tv_nsec = a->tv_nsec - b->tv_nsec +
+ (a->tv_nsec < b->tv_nsec) * 1E9;
+}
+
+static void
+usage(void)
+{
+ die("usage: %s [-v] [-s] [-1]", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction act;
+ struct timespec start, current, diff, intspec, wait;
+ size_t i, len;
+ int sflag, ret;
+ char status[MAXLEN];
+ const char *res;
+
+ sflag = 0;
+ ARGBEGIN {
+ case 'v':
+ die("slstatus-"VERSION);
+ case '1':
+ done = 1;
+ /* FALLTHROUGH */
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if (argc)
+ usage();
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = terminate;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ act.sa_flags |= SA_RESTART;
+ sigaction(SIGUSR1, &act, NULL);
+
+ if (!sflag && !(dpy = XOpenDisplay(NULL)))
+ die("XOpenDisplay: Failed to open display");
+
+ do {
+ if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
+ die("clock_gettime:");
+
+ status[0] = '\0';
+ for (i = len = 0; i < LEN(args); i++) {
+ if (!(res = args[i].func(args[i].args)))
+ res = unknown_str;
+
+ if ((ret = esnprintf(status + len, sizeof(status) - len,
+ args[i].fmt, res)) < 0)
+ break;
+
+ len += ret;
+ }
+
+ if (sflag) {
+ puts(status);
+ fflush(stdout);
+ if (ferror(stdout))
+ die("puts:");
+ } else {
+ if (XStoreName(dpy, DefaultRootWindow(dpy), status) < 0)
+ die("XStoreName: Allocation failed");
+ XFlush(dpy);
+ }
+
+ if (!done) {
+ if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0)
+ die("clock_gettime:");
+ difftimespec(&diff, ¤t, &start);
+
+ intspec.tv_sec = interval / 1000;
+ intspec.tv_nsec = (interval % 1000) * 1E6;
+ difftimespec(&wait, &intspec, &diff);
+
+ if (wait.tv_sec >= 0 &&
+ nanosleep(&wait, NULL) < 0 &&
+ errno != EINTR)
+ die("nanosleep:");
+ }
+ } while (!done);
+
+ if (!sflag) {
+ XStoreName(dpy, DefaultRootWindow(dpy), NULL);
+ if (XCloseDisplay(dpy) < 0)
+ die("XCloseDisplay: Failed to close display");
+ }
+
+ return 0;
+}
diff --git a/slstatus/slstatus.h b/slstatus/slstatus.h
@@ -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
@@ -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
@@ -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, ...);
diff --git a/transient.c b/transient.c
@@ -0,0 +1,42 @@
+/* cc transient.c -o transient -lX11 */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+int main(void) {
+ Display *d;
+ Window r, f, t = None;
+ XSizeHints h;
+ XEvent e;
+
+ d = XOpenDisplay(NULL);
+ if (!d)
+ exit(1);
+ r = DefaultRootWindow(d);
+
+ f = XCreateSimpleWindow(d, r, 100, 100, 400, 400, 0, 0, 0);
+ h.min_width = h.max_width = h.min_height = h.max_height = 400;
+ h.flags = PMinSize | PMaxSize;
+ XSetWMNormalHints(d, f, &h);
+ XStoreName(d, f, "floating");
+ XMapWindow(d, f);
+
+ XSelectInput(d, f, ExposureMask);
+ while (1) {
+ XNextEvent(d, &e);
+
+ if (t == None) {
+ sleep(5);
+ t = XCreateSimpleWindow(d, r, 50, 50, 100, 100, 0, 0, 0);
+ XSetTransientForHint(d, t, f);
+ XStoreName(d, t, "transient");
+ XMapWindow(d, t);
+ XSelectInput(d, t, ExposureMask);
+ }
+ }
+
+ XCloseDisplay(d);
+ exit(0);
+}
diff --git a/util.c b/util.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ if (!(p = calloc(nmemb, size)))
+ die("calloc:");
+ return p;
+}
diff --git a/util.h b/util.h
@@ -0,0 +1,8 @@
+/* See LICENSE file for copyright and license details. */
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+
+void die(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);