diff --git a/.gitignore b/.gitignore index ab80e36..6f257bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.tar.* +*.diff +*/src +!dwm/dwm-restartsig.diff diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..995172f --- /dev/null +++ b/LICENSE @@ -0,0 +1,38 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich +© 2020-2022 Chris Down + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/dmenu/Makefile b/dmenu/Makefile new file mode 100644 index 0000000..e731a2c --- /dev/null +++ b/dmenu/Makefile @@ -0,0 +1,64 @@ +# dmenu - dynamic menu +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = drw.c dmenu.c stest.c util.c +OBJ = $(SRC:.c=.o) + +all: options dmenu stest + +options: + @echo dmenu build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + @echo "CC = $(CC)" + +.c.o: + $(CC) -c $(CFLAGS) $< + +config.h: + cp config.def.h $@ + +$(OBJ): arg.h config.h config.mk drw.h + +dmenu: dmenu.o drw.o util.o + $(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) + +stest: stest.o + $(CC) -o $@ stest.o $(LDFLAGS) + +clean: + rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz *.orig *.rej config.h + +dist: clean + mkdir -p dmenu-$(VERSION) + cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ + drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ + dmenu-$(VERSION) + tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) + gzip dmenu-$(VERSION).tar + rm -rf dmenu-$(VERSION) + +install: all + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path + chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run + chmod 755 $(DESTDIR)$(PREFIX)/bin/stest + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ + $(DESTDIR)$(PREFIX)/bin/dmenu_path\ + $(DESTDIR)$(PREFIX)/bin/dmenu_run\ + $(DESTDIR)$(PREFIX)/bin/stest\ + $(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ + $(DESTDIR)$(MANPREFIX)/man1/stest.1 + +.PHONY: all options clean dist install uninstall diff --git a/dmenu/PKGBUILD b/dmenu/PKGBUILD deleted file mode 100644 index 5eea39c..0000000 --- a/dmenu/PKGBUILD +++ /dev/null @@ -1,52 +0,0 @@ -# my custom dmenu!! - -pkgname=dmenu -pkgver=5.2 -pkgrel=1 -pkgdesc='Generic menu for X' -url='http://git.vern.cc/vitorg/suckless' -arch=('x86_64') -license=('MIT') -options=(zipman) -depends=('sh' 'glibc' 'coreutils' 'libx11' 'libxinerama' 'libxft' 'freetype2' 'fontconfig' 'libfontconfig.so') -_patchname=("center" "gruvbox" "border") -source=(https://dl.suckless.org/tools/$pkgname-${pkgver}.tar.gz - center.diff::https://tools.suckless.org/$pkgname/patches/center/$pkgname-center-$pkgver.diff - gruvbox.diff::https://tools.suckless.org/$pkgname/patches/gruvbox/$pkgname-gruvbox-20210329-9ae8ea5.diff - border.diff::https://tools.suckless.org/$pkgname/patches/border/$pkgname-border-$pkgver.diff - config.h) -sha512sums=('7be9bb76b11225ec86a30e643e9b884f6d13af581526085212cb7401383d448a72fe4dffc3ce84ffb8319dbf36ca8384597a89070cd27d39d5618f239a2da6e3' - 'b98695c14063b9555c6964e675512bcdc3a4dc97ece8a882fabe9261b9081bd31dbd884885d9ad0b2c1305bdcc50170bb0641659e076333c7a1e29aed516a476' - '8a860d57a550bef787de1d7be28ce9531340c01af296f0c6da6485ec0bb6e16293dd2ba13806280dd26d4a07c3d0f067fd84ed2abb9a8c66546d90c04489e234' - 'b241c668bb18a2bdcc4bc76492ff34ab24cc35d7af49a0f84069ab9fa3d4ce81bcc30bf8b14c02f1403e52370c050e6b210330752245301ba479073aa0e40d61' - 'a9f3f35ed258ff430584d7b21493cbb1f76bae40d2c58b0bcf30fdeb14fc1616f92d4e0c5696f9f30117e659f1c231e037b5a1517d71052848a069e17e013911') - -prepare() { - cd ${pkgname}-${pkgver} - echo "CPPFLAGS+=${CPPFLAGS}" >> config.mk - echo "CFLAGS+=${CFLAGS}" >> config.mk - echo "LDFLAGS+=${LDFLAGS}" >> config.mk - if [[ -f "${srcdir}/config.h" ]]; then - cp -fv "${srcdir}/config.h" config.h - fi - for p in ${_patchname[@]}; do - printf "\npatching %s\n\n" "$p" - patch --forward --strip=1 --input="${srcdir}/$p.diff" - done -} - -build() { - cd ${pkgname}-${pkgver} - make \ - X11INC=/usr/include/X11 \ - X11LIB=/usr/lib/X11 \ - FREETYPEINC=/usr/include/freetype2 -} - -package() { - cd ${pkgname}-${pkgver} - make PREFIX=/usr DESTDIR="${pkgdir}" install - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} - -# vim: ts=2 sw=2 et: diff --git a/dmenu/arg.h b/dmenu/arg.h new file mode 100644 index 0000000..e94e02b --- /dev/null +++ b/dmenu/arg.h @@ -0,0 +1,49 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/dmenu/config.h b/dmenu/config.def.h similarity index 100% rename from dmenu/config.h rename to dmenu/config.def.h diff --git a/dmenu/config.mk b/dmenu/config.mk new file mode 100644 index 0000000..566348b --- /dev/null +++ b/dmenu/config.mk @@ -0,0 +1,32 @@ +# dmenu version +VERSION = 5.2 + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# Xinerama, comment if you don't want it +XINERAMALIBS = -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# freetype +FREETYPELIBS = -lfontconfig -lXft +FREETYPEINC = /usr/include/freetype2 +# OpenBSD (uncomment) +#FREETYPEINC = $(X11INC)/freetype2 +#MANPREFIX = ${PREFIX}/man + +# includes and libs +INCS = -I$(X11INC) -I$(FREETYPEINC) +LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) +CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) +LDFLAGS = $(LIBS) + +# compiler and linker +CC = cc diff --git a/dmenu/dmenu.1 b/dmenu/dmenu.1 new file mode 100644 index 0000000..c036baa --- /dev/null +++ b/dmenu/dmenu.1 @@ -0,0 +1,197 @@ +.TH DMENU 1 dmenu\-VERSION +.SH NAME +dmenu \- dynamic menu +.SH SYNOPSIS +.B dmenu +.RB [ \-bfiv ] +.RB [ \-l +.IR lines ] +.RB [ \-m +.IR monitor ] +.RB [ \-p +.IR prompt ] +.RB [ \-fn +.IR font ] +.RB [ \-nb +.IR color ] +.RB [ \-nf +.IR color ] +.RB [ \-sb +.IR color ] +.RB [ \-sf +.IR color ] +.RB [ \-w +.IR windowid ] +.P +.BR dmenu_run " ..." +.SH DESCRIPTION +.B dmenu +is a dynamic menu for X, which reads a list of newline\-separated items from +stdin. When the user selects an item and presses Return, their choice is printed +to stdout and dmenu terminates. Entering text will narrow the items to those +matching the tokens in the input. +.P +.B dmenu_run +is a script used by +.IR dwm (1) +which lists programs in the user's $PATH and runs the result in their $SHELL. +.SH OPTIONS +.TP +.B \-b +dmenu appears at the bottom of the screen. +.TP +.B \-c +dmenu appears centered on the screen. +.TP +.B \-f +dmenu grabs the keyboard before reading stdin if not reading from a tty. This +is faster, but will lock up X until stdin reaches end\-of\-file. +.TP +.B \-i +dmenu matches menu items case insensitively. +.TP +.BI \-l " lines" +dmenu lists items vertically, with the given number of lines. +.TP +.BI \-m " monitor" +dmenu is displayed on the monitor number supplied. Monitor numbers are starting +from 0. +.TP +.BI \-p " prompt" +defines the prompt to be displayed to the left of the input field. +.TP +.BI \-fn " font" +defines the font or font set used. +.TP +.BI \-nb " color" +defines the normal background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-nf " color" +defines the normal foreground color. +.TP +.BI \-sb " color" +defines the selected background color. +.TP +.BI \-sf " color" +defines the selected foreground color. +.TP +.B \-v +prints version information to stdout, then exits. +.TP +.BI \-w " windowid" +embed into windowid. +.SH USAGE +dmenu is completely controlled by the keyboard. Items are selected using the +arrow keys, page up, page down, home, and end. +.TP +.B Tab +Copy the selected item to the input field. +.TP +.B Return +Confirm selection. Prints the selected item to stdout and exits, returning +success. +.TP +.B Ctrl-Return +Confirm selection. Prints the selected item to stdout and continues. +.TP +.B Shift\-Return +Confirm input. Prints the input text to stdout and exits, returning success. +.TP +.B Escape +Exit without selecting an item, returning failure. +.TP +.B Ctrl-Left +Move cursor to the start of the current word +.TP +.B Ctrl-Right +Move cursor to the end of the current word +.TP +.B C\-a +Home +.TP +.B C\-b +Left +.TP +.B C\-c +Escape +.TP +.B C\-d +Delete +.TP +.B C\-e +End +.TP +.B C\-f +Right +.TP +.B C\-g +Escape +.TP +.B C\-h +Backspace +.TP +.B C\-i +Tab +.TP +.B C\-j +Return +.TP +.B C\-J +Shift-Return +.TP +.B C\-k +Delete line right +.TP +.B C\-m +Return +.TP +.B C\-M +Shift-Return +.TP +.B C\-n +Down +.TP +.B C\-p +Up +.TP +.B C\-u +Delete line left +.TP +.B C\-w +Delete word left +.TP +.B C\-y +Paste from primary X selection +.TP +.B C\-Y +Paste from X clipboard +.TP +.B M\-b +Move cursor to the start of the current word +.TP +.B M\-f +Move cursor to the end of the current word +.TP +.B M\-g +Home +.TP +.B M\-G +End +.TP +.B M\-h +Up +.TP +.B M\-j +Page down +.TP +.B M\-k +Page up +.TP +.B M\-l +Down +.SH SEE ALSO +.IR dwm (1), +.IR stest (1) diff --git a/dmenu/dmenu.c b/dmenu/dmenu.c new file mode 100644 index 0000000..ba4132c --- /dev/null +++ b/dmenu/dmenu.c @@ -0,0 +1,821 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef XINERAMA +#include +#endif +#include + +#include "drw.h" +#include "util.h" + +/* macros */ +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define LENGTH(X) (sizeof X / sizeof X[0]) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + +/* enums */ +enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = NULL; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static unsigned int +textw_clamp(const char *str, unsigned int n) +{ + unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad; + return MIN(w, n); +} + +static void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = NULL; + *last = item; +} + +static void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) + break; +} + +static int +max_textw(void) +{ + int len = 0; + for (struct item *item = items; item && item->text; item++) + len = MAX(TEXTW(item->text), len); + return len; +} + +static void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + for (i = 0; items && items[i].text; ++i) + free(items[i].text); + free(items); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static char * +cistrstr(const char *h, const char *n) +{ + size_t i; + + if (!n[0]) + return (char *)h; + + for (; *h; ++h) { + for (i = 0; n[i] && tolower((unsigned char)n[i]) == + tolower((unsigned char)h[i]); ++i) + ; + if (n[i] == '\0') + return (char *)h; + } + return NULL; +} + +static int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} + +static void +drawmenu(void) +{ + unsigned int curpos; + struct item *item; + int x = 0, y = 0, w; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, NULL); + } + die("cannot grab focus"); +} + +static void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, NULL); + } + die("cannot grab keyboard"); +} + +static void +match(void) +{ + static char **tokv = NULL; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + die("cannot realloc %zu bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); +} + +static void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); +} + +static size_t +nextrune(int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +static void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } +} + +static void +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(NULL, 0 - cursor); + break; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(NULL, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + case XK_KP_Left: + movewordedge(-1); + goto draw; + case XK_Right: + case XK_KP_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl((unsigned char)*buf)) + insert(buf, len); + break; + case XK_Delete: + case XK_KP_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(NULL, nextrune(-1) - cursor); + break; + case XK_End: + case XK_KP_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + cleanup(); + exit(1); + case XK_Home: + case XK_KP_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + case XK_KP_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + case XK_KP_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + case XK_KP_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + case XK_KP_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + case XK_KP_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + case XK_KP_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + cursor = strnlen(sel->text, sizeof text - 1); + memcpy(text, sel->text, cursor); + text[cursor] = '\0'; + match(); + break; + } + +draw: + drawmenu(); +} + +static void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static void +readstdin(void) +{ + char *line = NULL; + size_t i, junk, size = 0; + ssize_t len; + + /* read each line from stdin and add it to the item list */ + for (i = 0; (len = getline(&line, &junk, stdin)) != -1; i++, line = NULL) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + die("cannot realloc %zu bytes:", size); + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + items[i].text = line; + items[i].out = 0; + } + if (items) + items[i].text = NULL; + lines = MIN(lines, i); +} + +static void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static void +setup(void) +{ + int x, y, i, j; + unsigned int du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"dmenu", "dmenu"}; +#ifdef XINERAMA + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; +#endif + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; +#ifdef XINERAMA + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i]) != 0) + break; + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width); + x = info[i].x_org + ((info[i].width - mw) / 2); + y = info[i].y_org + ((info[i].height - mh) / 2); + } else { + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + } + + XFree(info); + } else +#endif + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + + if (centered) { + mw = MIN(MAX(max_textw() + promptw, min_width), wa.width); + x = (wa.width - mw) / 2; + y = (wa.height - mh) / 2; + } else { + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = mw / 3; /* input width: ~33% of monitor width */ + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + if (border_width) + XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) + die("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, NULL); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static void +usage(void) +{ + die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("dmenu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */ + centered = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else if (!strcmp(argv[i], "-bw")) + border_width = atoi(argv[++i]); /* border width */ + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, NULL, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + die("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", NULL) == -1) + die("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/dmenu/dmenu_path b/dmenu/dmenu_path new file mode 100755 index 0000000..3a7cda7 --- /dev/null +++ b/dmenu/dmenu_path @@ -0,0 +1,13 @@ +#!/bin/sh + +cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" +cache="$cachedir/dmenu_run" + +[ ! -e "$cachedir" ] && mkdir -p "$cachedir" + +IFS=: +if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" +else + cat "$cache" +fi diff --git a/dmenu/dmenu_run b/dmenu/dmenu_run new file mode 100755 index 0000000..834ede5 --- /dev/null +++ b/dmenu/dmenu_run @@ -0,0 +1,2 @@ +#!/bin/sh +dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} & diff --git a/dmenu/drw.c b/dmenu/drw.c new file mode 100644 index 0000000..a58a2b4 --- /dev/null +++ b/dmenu/drw.c @@ -0,0 +1,450 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + int i, ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/dmenu/drw.h b/dmenu/drw.h new file mode 100644 index 0000000..fd7631b --- /dev/null +++ b/dmenu/drw.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/dmenu/stest.1 b/dmenu/stest.1 new file mode 100644 index 0000000..2667d8a --- /dev/null +++ b/dmenu/stest.1 @@ -0,0 +1,90 @@ +.TH STEST 1 dmenu\-VERSION +.SH NAME +stest \- filter a list of files by properties +.SH SYNOPSIS +.B stest +.RB [ -abcdefghlpqrsuwx ] +.RB [ -n +.IR file ] +.RB [ -o +.IR file ] +.RI [ file ...] +.SH DESCRIPTION +.B stest +takes a list of files and filters by the files' properties, analogous to +.IR test (1). +Files which pass all tests are printed to stdout. If no files are given, stest +reads files from stdin. +.SH OPTIONS +.TP +.B \-a +Test hidden files. +.TP +.B \-b +Test that files are block specials. +.TP +.B \-c +Test that files are character specials. +.TP +.B \-d +Test that files are directories. +.TP +.B \-e +Test that files exist. +.TP +.B \-f +Test that files are regular files. +.TP +.B \-g +Test that files have their set-group-ID flag set. +.TP +.B \-h +Test that files are symbolic links. +.TP +.B \-l +Test the contents of a directory given as an argument. +.TP +.BI \-n " file" +Test that files are newer than +.IR file . +.TP +.BI \-o " file" +Test that files are older than +.IR file . +.TP +.B \-p +Test that files are named pipes. +.TP +.B \-q +No files are printed, only the exit status is returned. +.TP +.B \-r +Test that files are readable. +.TP +.B \-s +Test that files are not empty. +.TP +.B \-u +Test that files have their set-user-ID flag set. +.TP +.B \-v +Invert the sense of tests, only failing files pass. +.TP +.B \-w +Test that files are writable. +.TP +.B \-x +Test that files are executable. +.SH EXIT STATUS +.TP +.B 0 +At least one file passed all tests. +.TP +.B 1 +No files passed all tests. +.TP +.B 2 +An error occurred. +.SH SEE ALSO +.IR dmenu (1), +.IR test (1) diff --git a/dmenu/stest.c b/dmenu/stest.c new file mode 100644 index 0000000..e27d3a5 --- /dev/null +++ b/dmenu/stest.c @@ -0,0 +1,109 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include +#include +#include +#include +#include +#include + +#include "arg.h" +char *argv0; + +#define FLAG(x) (flag[(x)-'a']) + +static void test(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static void +test(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (line[n - 1] == '\n') + line[n - 1] = '\0'; + test(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* test directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + test(path, d->d_name); + } + closedir(dir); + } else { + test(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/dmenu/util.c b/dmenu/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/dmenu/util.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/dmenu/util.h b/dmenu/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/dmenu/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dwm/Makefile b/dwm/Makefile new file mode 100644 index 0000000..c479ecf --- /dev/null +++ b/dwm/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 dwm ${OBJ} dwm-${VERSION}.tar.gz config.h *.orig *.rej + +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/dwm/PKGBUILD b/dwm/PKGBUILD deleted file mode 100644 index 8a9051c..0000000 --- a/dwm/PKGBUILD +++ /dev/null @@ -1,49 +0,0 @@ -# my custom dwm!! - -pkgname=dwm -pkgver=6.4 -pkgrel=1 -pkgdesc="A dynamic window manager for X" -url="http://git.vern.cc/vitorg/suckless" -arch=('x86_64') -license=('MIT') -options=(zipman) -depends=('libx11' 'libxinerama' 'libxft' 'freetype2') -install=dwm.install -source=(https://dl.suckless.org/dwm/dwm-$pkgver.tar.gz - https://dwm.suckless.org/patches/swallow/dwm-swallow-6.3.diff - https://dwm.suckless.org/patches/cool_autostart/dwm-cool-autostart-6.2.diff - https://dwm.suckless.org/patches/pertag/dwm-pertag-20200914-61bb8b2.diff - https://dwm.suckless.org/patches/uselessgap/dwm-uselessgap-20211119-58414bee958f2.diff - dwm-restartsig.diff - config.h - dwm.desktop - dwm-loop) -sha256sums=('fa9c0d69a584485076cfc18809fd705e5c2080dafb13d5e729a3646ca7703a6e' - 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP') - -prepare() { - cd "$srcdir/$pkgname-$pkgver" - if [[ -f "$srcdir/config.h" ]]; then - cp -fv "$srcdir/config.h" config.h - fi - # patch --forward --strip=1 --input="${srcdir}/dwm-restartsig.diff" - patch --forward --strip=1 --input="${srcdir}/dwm-swallow-6.3.diff" - patch --forward --strip=1 --input="${srcdir}/dwm-cool-autostart-6.2.diff" - patch --forward --strip=1 --input="${srcdir}/dwm-pertag-20200914-61bb8b2.diff" - patch --forward --strip=1 --input="${srcdir}/dwm-uselessgap-20211119-58414bee958f2.diff" -} - -build() { - cd "$srcdir/$pkgname-$pkgver" - make X11INC=/usr/include/X11 X11LIB=/usr/lib/X11 FREETYPEINC=/usr/include/freetype2 -} - -package() { - cd "$srcdir/$pkgname-$pkgver" - make PREFIX=/usr DESTDIR="$pkgdir" install - install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" - install -Dm644 README "$pkgdir/usr/share/doc/$pkgname/README" - install -Dm644 "$srcdir/dwm.desktop" "$pkgdir/usr/share/xsessions/dwm.desktop" - install -m755 "$srcdir/dwm-loop" "$pkgdir/usr/bin/dwm-loop" -} diff --git a/dwm/config.def.h b/dwm/config.def.h new file mode 100644 index 0000000..ffda70c --- /dev/null +++ b/dwm/config.def.h @@ -0,0 +1,142 @@ +/* See LICENSE file for copyright and license details. */ + +#define SESSION_FILE "/tmp/dwm-session" + +/* appearance */ +static const unsigned int borderpx = 1; /* border pixel of windows */ +static const unsigned int gappx = 4; /* gaps between windows*/ +static const unsigned int snap = 32; /* snap pixel */ +static const int swallowfloating = 0; /* 1 means swallow floating windows by default */ +static const int showbar = 1; /* 0 means no bar */ +static const int topbar = 1; /* 0 means bottom bar */ +static const char *fonts[] = { "monospace:size=10" }; +static const char col_gray1[] = "#282828"; +static const char col_gray2[] = "#504945"; +static const char col_gray3[] = "#bdae93"; +static const char col_gray4[] = "#ebdbb2"; +static const char col_cyan[] = "#cc241d"; +static const char *colors[][3] = { + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray1 }, + [SchemeSel] = { col_gray4, col_gray2, col_gray2 }, +}; + +typedef struct { + const char *name; + const void *cmd; +} Sp; +const char *spcmd1[] = {"st", "-n", "spterm", "-g", "120x34", NULL }; +const char *spcmd2[] = {"st", "-n", "spfm", "-g", "144x41", "-e", "nnn", NULL }; +const char *spcmd3[] = {"st", "-n", "spmtrx", "-g", "144x41", "-e", "gomuks", NULL }; +const char *spcmd4[] = {"st", "-n", "sprss", "-g", "144x41", "-e", "newsboat", NULL }; +static Sp scratchpads[] = { + /* name cmd */ + {"spterm", spcmd1}, + {"spfm", spcmd2}, + {"spmtrx", spcmd3}, + {"sprss", spcmd4}, +}; + +/* tagging */ +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const int momentaryalttags = 0; /* 1 means alttags will show only when key is held down*/ +static const Rule rules[] = { + /* class instance title tags mask isfloating isterminal noswallow monitor */ + { "Gimp", NULL, NULL, 0, 1, 0, 0, -1 }, + { "Cromite", NULL, NULL, 1<<1, 0, 0, 0, -1 }, + { "st", NULL, NULL, 0, 0, 1, 0, -1 }, + { NULL, NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ + { NULL, "spterm", NULL, SPTAG(0), 1, 0, 0, -1 }, + { NULL, "spfm", NULL, SPTAG(1), 1, 0, 0, -1 }, + { NULL, "spmtrx", NULL, SPTAG(2), 1, 0, 0, -1 }, + { NULL, "sprss", NULL, SPTAG(3), 1, 0, 0, -1 }, +}; + +/* layout(s) */ +static const float mfact = 0.5; /* 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 */ + { "[M]", monocle }, + { "><>", NULL }, /* no layout function means floating behavior */ +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* helper for spawning shell commands in the pre dwm-5.0 fashion */ +#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } + +/* commands */ +static const char *menucmd[] = { "dmenu_run", "-c", "-l", "10", "-bw", "2", NULL }; +static const char *passcmd[] = { "passmenu", "-c", "-l", "10", "-bw", "2", NULL }; +static const char *termcmd[] = { "st", NULL }; +static const char *wwwcmd[] = { "cromite", NULL }; +static const char *sscmd[] = { "ss", NULL }; + +static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_r, spawn, {.v = menucmd } }, + { MODKEY, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_b, spawn, {.v = wwwcmd } }, + { MODKEY, XK_p, spawn, {.v = sscmd } }, + { MODKEY|ShiftMask, XK_p, spawn, {.v = passcmd } }, + { MODKEY|ShiftMask, 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|ShiftMask, XK_Return, zoom, {0} }, + { MODKEY, XK_w, killclient, {0} }, + { 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_c, togglescratch, {.ui = 0 } }, + { MODKEY, XK_e, togglescratch, {.ui = 1 } }, + { MODKEY, XK_g, togglescratch, {.ui = 2 } }, + { MODKEY, XK_n, togglescratch, {.ui = 3 } }, + 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} }, + { MODKEY|ShiftMask, XK_c, quit, {1} }, +}; + +/* 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} }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/dwm/config.h b/dwm/config.h index 61ef190..ffda70c 100644 --- a/dwm/config.h +++ b/dwm/config.h @@ -1,5 +1,7 @@ /* See LICENSE file for copyright and license details. */ +#define SESSION_FILE "/tmp/dwm-session" + /* appearance */ static const unsigned int borderpx = 1; /* border pixel of windows */ static const unsigned int gappx = 4; /* gaps between windows*/ @@ -8,35 +10,46 @@ static const int swallowfloating = 0; /* 1 means swallow floating wind static const int showbar = 1; /* 0 means no bar */ static const int topbar = 1; /* 0 means bottom bar */ static const char *fonts[] = { "monospace:size=10" }; -static const char col_gray1[] = "#222222"; -static const char col_gray2[] = "#444444"; -static const char col_gray3[] = "#bbbbbb"; -static const char col_gray4[] = "#eeeeee"; -static const char col_cyan[] = "#005577"; +static const char col_gray1[] = "#282828"; +static const char col_gray2[] = "#504945"; +static const char col_gray3[] = "#bdae93"; +static const char col_gray4[] = "#ebdbb2"; +static const char col_cyan[] = "#cc241d"; static const char *colors[][3] = { /* fg bg border */ - [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, - [SchemeSel] = { col_gray4, col_cyan, col_cyan }, + [SchemeNorm] = { col_gray3, col_gray1, col_gray1 }, + [SchemeSel] = { col_gray4, col_gray2, col_gray2 }, }; -static const char *autostart[] = { - "xwallpaper", "--maximize", "/usr/share/wallpapers/arch-gruvbox-dark.png", NULL, - "cromite", "--no-startup-window", NULL, - "picom", "--backend", "glx", NULL, - "slstatus", NULL, - NULL +typedef struct { + const char *name; + const void *cmd; +} Sp; +const char *spcmd1[] = {"st", "-n", "spterm", "-g", "120x34", NULL }; +const char *spcmd2[] = {"st", "-n", "spfm", "-g", "144x41", "-e", "nnn", NULL }; +const char *spcmd3[] = {"st", "-n", "spmtrx", "-g", "144x41", "-e", "gomuks", NULL }; +const char *spcmd4[] = {"st", "-n", "sprss", "-g", "144x41", "-e", "newsboat", NULL }; +static Sp scratchpads[] = { + /* name cmd */ + {"spterm", spcmd1}, + {"spfm", spcmd2}, + {"spmtrx", spcmd3}, + {"sprss", spcmd4}, }; + /* tagging */ static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; - +static const int momentaryalttags = 0; /* 1 means alttags will show only when key is held down*/ static const Rule rules[] = { - /* xprop(1): - * WM_CLASS(STRING) = instance, class - * WM_NAME(STRING) = title - */ - /* class instance title tags mask isfloating monitor */ - { "Gimp", NULL, NULL, 0, 1, -1 }, - { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, + /* class instance title tags mask isfloating isterminal noswallow monitor */ + { "Gimp", NULL, NULL, 0, 1, 0, 0, -1 }, + { "Cromite", NULL, NULL, 1<<1, 0, 0, 0, -1 }, + { "st", NULL, NULL, 0, 0, 1, 0, -1 }, + { NULL, NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ + { NULL, "spterm", NULL, SPTAG(0), 1, 0, 0, -1 }, + { NULL, "spfm", NULL, SPTAG(1), 1, 0, 0, -1 }, + { NULL, "spmtrx", NULL, SPTAG(2), 1, 0, 0, -1 }, + { NULL, "sprss", NULL, SPTAG(3), 1, 0, 0, -1 }, }; /* layout(s) */ @@ -48,8 +61,8 @@ static const int lockfullscreen = 1; /* 1 will force focus on the fullscreen win static const Layout layouts[] = { /* symbol arrange function */ { "[]=", tile }, /* first entry is default */ - { "><>", NULL }, /* no layout function means floating behavior */ { "[M]", monocle }, + { "><>", NULL }, /* no layout function means floating behavior */ }; /* key definitions */ @@ -64,7 +77,8 @@ static const Layout layouts[] = { #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } /* commands */ -static const char *menucmd[] = { "dmenu_run", NULL }; +static const char *menucmd[] = { "dmenu_run", "-c", "-l", "10", "-bw", "2", NULL }; +static const char *passcmd[] = { "passmenu", "-c", "-l", "10", "-bw", "2", NULL }; static const char *termcmd[] = { "st", NULL }; static const char *wwwcmd[] = { "cromite", NULL }; static const char *sscmd[] = { "ss", NULL }; @@ -75,6 +89,7 @@ static const Key keys[] = { { MODKEY, XK_Return, spawn, {.v = termcmd } }, { MODKEY, XK_b, spawn, {.v = wwwcmd } }, { MODKEY, XK_p, spawn, {.v = sscmd } }, + { MODKEY|ShiftMask, XK_p, spawn, {.v = passcmd } }, { MODKEY|ShiftMask, XK_b, togglebar, {0} }, { MODKEY, XK_j, focusstack, {.i = +1 } }, { MODKEY, XK_k, focusstack, {.i = -1 } }, @@ -82,12 +97,8 @@ static const Key keys[] = { { MODKEY, XK_d, incnmaster, {.i = -1 } }, { MODKEY, XK_h, setmfact, {.f = -0.05} }, { MODKEY, XK_l, setmfact, {.f = +0.05} }, - { MODKEY|ShiftMask, XK_Return, zoom, {0} }, - { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_Return, zoom, {0} }, { MODKEY, XK_w, killclient, {0} }, - { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, - { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, - { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, { MODKEY, XK_space, setlayout, {0} }, { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, { MODKEY, XK_0, view, {.ui = ~0 } }, @@ -96,6 +107,10 @@ static const Key keys[] = { { MODKEY, XK_period, focusmon, {.i = +1 } }, { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + { MODKEY, XK_c, togglescratch, {.ui = 0 } }, + { MODKEY, XK_e, togglescratch, {.ui = 1 } }, + { MODKEY, XK_g, togglescratch, {.ui = 2 } }, + { MODKEY, XK_n, togglescratch, {.ui = 3 } }, TAGKEYS( XK_1, 0) TAGKEYS( XK_2, 1) TAGKEYS( XK_3, 2) @@ -106,6 +121,7 @@ static const Key keys[] = { TAGKEYS( XK_8, 7) TAGKEYS( XK_9, 8) { MODKEY|ShiftMask, XK_q, quit, {0} }, + { MODKEY|ShiftMask, XK_c, quit, {1} }, }; /* button definitions */ @@ -115,7 +131,6 @@ static const Button buttons[] = { { ClkLtSymbol, 0, Button1, setlayout, {0} }, { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, { ClkWinTitle, 0, Button2, zoom, {0} }, - { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, { ClkClientWin, MODKEY, Button1, movemouse, {0} }, { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, diff --git a/dwm/config.mk b/dwm/config.mk new file mode 100644 index 0000000..c1434f2 --- /dev/null +++ b/dwm/config.mk @@ -0,0 +1,40 @@ +# 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 +#KVMLIB = -lkvm + +# includes and libs +INCS = -I${X11INC} -I${FREETYPEINC} +LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res ${KVMLIB} + +# flags +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -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/dwm/drw.c b/dwm/drw.c new file mode 100644 index 0000000..a58a2b4 --- /dev/null +++ b/dwm/drw.c @@ -0,0 +1,450 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "drw.h" +#include "util.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + drw_fontset_free(drw->fonts); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + die("no font specified."); + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + die("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + int i, ty, ellipsis_x = 0; + unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0, overflow = 0; + /* keep track of a couple codepoints for which we have no match. */ + enum { nomatches_len = 64 }; + static struct { long codepoint[nomatches_len]; unsigned int idx; } nomatches; + static unsigned int ellipsis_width = 0; + + if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) + return 0; + + if (!render) { + w = invert ? invert : ~invert; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + if (!ellipsis_width && render) + ellipsis_width = drw_fontset_getwidth(drw, "..."); + while (1) { + ew = ellipsis_len = utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); + if (ew + ellipsis_width <= w) { + /* keep track where the ellipsis still fits */ + ellipsis_x = x + ew; + ellipsis_w = w - ew; + ellipsis_len = utf8strlen; + } + + if (ew + tmpw > w) { + overflow = 1; + /* called from drw_fontset_getwidth_clamp(): + * it wants the width AFTER the overflow + */ + if (!render) + x += tmpw; + else + utf8strlen = ellipsis_len; + } else if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + ew += tmpw; + } else { + nextfont = curfont; + } + break; + } + } + + if (overflow || !charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); + } + x += ew; + w -= ew; + } + if (render && overflow) + drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert); + + if (!*text || overflow) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + for (i = 0; i < nomatches_len; ++i) { + /* avoid calling XftFontMatch if we know we won't find a match */ + if (utf8codepoint == nomatches.codepoint[i]) + goto no_match; + } + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + die("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint; +no_match: + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +unsigned int +drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n) +{ + unsigned int tmp = 0; + if (drw && drw->fonts && text && n) + tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n); + return MIN(n, tmp); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/dwm/drw.h b/dwm/drw.h new file mode 100644 index 0000000..6471431 --- /dev/null +++ b/dwm/drw.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/dwm/drw.o b/dwm/drw.o new file mode 100644 index 0000000..eb6ee1a Binary files /dev/null and b/dwm/drw.o differ diff --git a/dwm/dwm b/dwm/dwm new file mode 100755 index 0000000..0db4b64 Binary files /dev/null and b/dwm/dwm differ diff --git a/dwm/dwm-loop b/dwm/dwm-loop deleted file mode 100644 index 9074a16..0000000 --- a/dwm/dwm-loop +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -while true - do - /usr/bin/dwm 2> /dev/null -done diff --git a/dwm/dwm.1 b/dwm/dwm.1 new file mode 100644 index 0000000..0f3a6ba --- /dev/null +++ b/dwm/dwm.1 @@ -0,0 +1,209 @@ +.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. +.TP +.B Mod1\-Control\-Shift\-q +Restart 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 SIGNALS +.TP +.B SIGHUP - 1 +Restart the dwm process. +.TP +.B SIGTERM - 15 +Cleanly terminate the dwm process. +.SH SEE ALSO +.BR dmenu (1), +.BR st (1) +.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/dwm.c b/dwm/dwm.c new file mode 100644 index 0000000..cc9e53b --- /dev/null +++ b/dwm/dwm.c @@ -0,0 +1,2668 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XINERAMA +#include +#endif /* XINERAMA */ +#include +#include +#include +#ifdef __OpenBSD__ +#include +#include +#endif /* __OpenBSD */ + +#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 NUMTAGS (LENGTH(tags) + LENGTH(scratchpads)) +#define WIDTH(X) ((X)->w + 2 * (X)->bw + gappx) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw + gappx) +#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) + +/* enums */ +enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { SchemeNorm, SchemeSel }; /* color schemes */ +enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, + NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ +enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int click; + unsigned int mask; + unsigned int button; + void (*func)(const Arg *arg); + const Arg arg; +} Button; + +typedef struct Monitor Monitor; +typedef struct Client Client; +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; + int bw, oldbw; + unsigned int tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, isterminal, noswallow; + pid_t pid; + Client *next; + Client *snext; + Client *swallowing; + 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; + +typedef struct Pertag Pertag; +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]; + Pertag *pertag; +}; + +typedef struct { + const char *class; + const char *instance; + const char *title; + unsigned int tags; + int isfloating; + int isterminal; + int noswallow; + int monitor; +} Rule; + +/* function declarations */ +static void applyrules(Client *c); +static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +static void arrange(Monitor *m); +static void arrangemon(Monitor *m); +static void attach(Client *c); +static void attachstack(Client *c); +static void buttonpress(XEvent *e); +static void checkotherwm(void); +static void cleanup(void); +static void cleanupmon(Monitor *mon); +static void clientmessage(XEvent *e); +static void configure(Client *c); +static void configurenotify(XEvent *e); +static void configurerequest(XEvent *e); +static Monitor *createmon(void); +static void destroynotify(XEvent *e); +static void detach(Client *c); +static void detachstack(Client *c); +static Monitor *dirtomon(int dir); +static void drawbar(Monitor *m); +static void drawbars(void); +static void enternotify(XEvent *e); +static void expose(XEvent *e); +static void focus(Client *c); +static void focusin(XEvent *e); +static void focusmon(const Arg *arg); +static void focusstack(const Arg *arg); +static Atom getatomprop(Client *c, Atom prop); +static int getrootptr(int *x, int *y); +static long getstate(Window w); +static int gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void grabbuttons(Client *c, int focused); +static void grabkeys(void); +static void incnmaster(const Arg *arg); +static void keypress(XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window w, XWindowAttributes *wa); +static void mappingnotify(XEvent *e); +static void maprequest(XEvent *e); +static void monocle(Monitor *m); +static void motionnotify(XEvent *e); +static void movemouse(const Arg *arg); +static Client *nexttiled(Client *c); +static void pop(Client *c); +static void propertynotify(XEvent *e); +static void quit(const Arg *arg); +static Monitor *recttomon(int x, int y, int w, int h); +static void resize(Client *c, int x, int y, int w, int h, int interact); +static void resizeclient(Client *c, int x, int y, int w, int h); +static void resizemouse(const Arg *arg); +static void restack(Monitor *m); +static void run(void); +static void runautostart(void); +static void scan(void); +static int sendevent(Client *c, Atom proto); +static void sendmon(Client *c, Monitor *m); +static void setclientstate(Client *c, long state); +static void setfocus(Client *c); +static void setfullscreen(Client *c, int fullscreen); +static void 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 sigchld(int unused); +static void sighup(int unused); +static void sigterm(int unused); +static void spawn(const Arg *arg); +static void tag(const Arg *arg); +static void tagmon(const Arg *arg); +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 unfocus(Client *c, int setfocus); +static void unmanage(Client *c, int destroyed); +static void unmapnotify(XEvent *e); +static void updatebarpos(Monitor *m); +static void updatebars(void); +static void updateclientlist(void); +static int updategeom(void); +static void updatenumlockmask(void); +static void updatesizehints(Client *c); +static void updatestatus(void); +static void updatetitle(Client *c); +static void updatewindowtype(Client *c); +static void updatewmhints(Client *c); +static void view(const Arg *arg); +static Client *wintoclient(Window w); +static Monitor *wintomon(Window w); +static int xerror(Display *dpy, XErrorEvent *ee); +static int xerrordummy(Display *dpy, XErrorEvent *ee); +static int xerrorstart(Display *dpy, XErrorEvent *ee); +static void zoom(const Arg *arg); + +static pid_t getparentprocess(pid_t p); +static int isdescprocess(pid_t p, pid_t c); +static Client *swallowingclient(Window w); +static Client *termforwin(const Client *c); +static pid_t winpid(Window w); + +/* 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, + [UnmapNotify] = unmapnotify +}; +static Atom wmatom[WMLast], netatom[NetLast]; +static int restart = 0; +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 xcb_connection_t *xcon; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +struct Pertag { + unsigned int curtag, prevtag; /* current and previous tag */ + int nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ + float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ + unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */ + const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and layouts indexes */ + int showbars[LENGTH(tags) + 1]; /* display bar for the current tag */ +}; + +/* 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->isterminal = r->isterminal; + c->noswallow = r->noswallow; + 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 +swallow(Client *p, Client *c) +{ + + if (c->noswallow || c->isterminal) + return; + if (c->noswallow && !swallowfloating && c->isfloating) + return; + + detach(c); + detachstack(c); + + setclientstate(c, WithdrawnState); + XUnmapWindow(dpy, p->win); + + p->swallowing = c; + c->mon = p->mon; + + Window w = p->win; + p->win = c->win; + c->win = w; + updatetitle(p); + XMoveResizeWindow(dpy, p->win, p->x, p->y, p->w, p->h); + arrange(p->mon); + configure(p); + updateclientlist(); +} + +void +unswallow(Client *c) +{ + c->win = c->swallowing->win; + + free(c->swallowing); + c->swallowing = NULL; + + /* unfullscreen the client */ + setfullscreen(c, 0); + updatetitle(c); + arrange(c->mon); + XMapWindow(dpy, c->win); + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + setclientstate(c, NormalState); + focus(NULL); + arrange(c->mon); +} + +void +buttonpress(XEvent *e) +{ + unsigned int i, x, click; + Arg arg = {0}; + Client *c; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + if (ev->window == selmon->barwin) { + i = x = 0; + do + x += TEXTW(tags[i]); + while (ev->x >= x && ++i < LENGTH(tags)); + if (i < LENGTH(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (ev->x < x + TEXTW(selmon->ltsymbol)) + click = ClkLtSymbol; + else if (ev->x > selmon->ww - (int)TEXTW(stext)) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < LENGTH(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", NULL }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + for (i = 0; i < CurLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < LENGTH(colors); i++) + free(scheme[i]); + free(scheme); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); +} + +void +cleanupmon(Monitor *mon) +{ + Monitor *m; + + if (mon == mons) + mons = mons->next; + else { + for (m = mons; m && m->next != mon; m = m->next); + m->next = mon->next; + } + XUnmapWindow(dpy, mon->barwin); + XDestroyWindow(dpy, mon->barwin); + free(mon); +} + +void +clientmessage(XEvent *e) +{ + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } + focus(NULL); + arrange(NULL); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + unsigned int i; + + 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); + m->pertag = ecalloc(1, sizeof(Pertag)); + m->pertag->curtag = m->pertag->prevtag = 1; + + for (i = 0; i <= LENGTH(tags); i++) { + m->pertag->nmasters[i] = m->nmaster; + m->pertag->mfacts[i] = m->mfact; + + m->pertag->ltidxs[i][0] = m->lt[0]; + m->pertag->ltidxs[i][1] = m->lt[1]; + m->pertag->sellts[i] = m->sellt; + + m->pertag->showbars[i] = m->showbar; + } + + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); + + else if ((c = swallowingclient(ev->window))) + unmanage(c->swallowing, 1); +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + c->mon->sel = t; + } +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = NULL; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + unsigned int i, occ = 0, urg = 0; + Client *c; + + if (!m->showbar) + return; + + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drw_setscheme(drw, scheme[SchemeNorm]); + tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ + drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + } + + for (c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for (i = 0; i < LENGTH(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) + drawbar(m); +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +void +focusmon(const Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(NULL); +} + +void +focusstack(const Arg *arg) +{ + Client *c = NULL, *i; + + if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen)) + return; + if (arg->i > 0) { + for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if (!c) + for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for (i = selmon->clients; i != selmon->sel; i = i->next) + if (ISVISIBLE(i)) + c = i; + if (!c) + for (; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if (c) { + focus(c); + restack(selmon); + } +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + unsigned char *p = NULL; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +int +getrootptr(int *x, int *y) +{ + int di; + unsigned int dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +int +gettextprop(Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < LENGTH(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + unsigned int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < LENGTH(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < LENGTH(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +void +incnmaster(const Arg *arg) +{ + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +#ifdef XINERAMA +static int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} +#endif /* XINERAMA */ + +void +keypress(XEvent *e) +{ + unsigned int i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); + for (i = 0; i < LENGTH(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +killclient(const Arg *arg) +{ + if (!selmon->sel) + return; + if (!sendevent(selmon->sel, wmatom[WMDelete])) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = NULL, *term = NULL; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + c->pid = winpid(w); + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + term = termforwin(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); + if (term) + swallow(term, c); + focus(NULL); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + unsigned int n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = NULL; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(NULL); + } + mon = m; +} + +void +movemouse(const Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(NULL); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendmon(c, m); + selmon = m; + focus(NULL); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != NULL)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + c->hintsvalid = 0; + break; + case XA_WM_HINTS: + updatewmhints(c); + drawbars(); + break; + } + if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +void +saveSession(void) +{ + FILE *fw = fopen(SESSION_FILE, "w"); + for (Client *c = selmon->clients; c != NULL; c = c->next) { // get all the clients with their tags and write them to the file + fprintf(fw, "%lu %u\n", c->win, c->tags); + } + fclose(fw); +} + +void +restoreSession(void) +{ + // restore session + FILE *fr = fopen(SESSION_FILE, "r"); + if (!fr) + return; + + char *str = malloc(23 * sizeof(char)); // allocate enough space for excepted input from text file + while (fscanf(fr, "%[^\n] ", str) != EOF) { // read file till the end + long unsigned int winId; + unsigned int tagsForWin; + int check = sscanf(str, "%lu %u", &winId, &tagsForWin); // get data + if (check != 2) // break loop if data wasn't read correctly + break; + + for (Client *c = selmon->clients; c ; c = c->next) { // add tags to every window by winId + if (c->win == winId) { + c->tags = tagsForWin; + break; + } + } + } + + for (Client *c = selmon->clients; c ; c = c->next) { // refocus on windows + focus(c); + restack(c->mon); + } + + for (Monitor *m = selmon; m; m = m->next) // rearrange all monitors + arrange(m); + + free(str); + fclose(fr); + + // delete a file + remove(SESSION_FILE); +} + +void +quit(const Arg *arg) +{ + if(arg->i) restart = 1; + running = 0; + + if (restart == 1) + saveSession(); +} + +Monitor * +recttomon(int x, int y, int w, int h) +{ + Monitor *m, *r = selmon; + int a, area = 0; + + for (m = mons; m; m = m->next) + if ((a = INTERSECT(x, y, w, h, m)) > area) { + area = a; + r = m; + } + return r; +} + +void +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + 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 +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(Client *c, Atom proto) +{ + int n; + Atom *protocols; + int exists = 0; + XEvent ev; + + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(c->win), 1); + } + sendevent(c, wmatom[WMTakeFocus]); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (unsigned char*)0, 0); + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +setlayout(const Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][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 = selmon->pertag->mfacts[selmon->pertag->curtag] = f; + arrange(selmon); +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + signal(SIGHUP, sighup); + signal(SIGTERM, sigterm); + + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); + sh = DisplayHeight(dpy, screen); + root = RootWindow(dpy, screen); + drw = drw_create(dpy, screen, root, sw, sh); + if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) + die("no fonts could be loaded."); + lrpad = drw->fonts->h; + bh = drw->fonts->h + 2; + updategeom(); + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + /* init cursors */ + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); + /* init appearance */ + scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); + for (i = 0; i < LENGTH(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + /* init bars */ + updatebars(); + updatestatus(); + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (unsigned char *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + /* select events */ + wa.cursor = cursor[CurNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(NULL); +} + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + 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 +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + die("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, NULL, WNOHANG)); +} + +void +sighup(int unused) +{ + Arg a = {.i = 1}; + quit(&a); +} + +void +sigterm(int unused) +{ + Arg a = {.i = 0}; + quit(&a); +} + +void +spawn(const Arg *arg) +{ + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]); + } +} + +void +tag(const Arg *arg) +{ + if (selmon->sel && arg->ui & TAGMASK) { + selmon->sel->tags = arg->ui & TAGMASK; + focus(NULL); + arrange(selmon); + } +} + +void +tagmon(const Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendmon(selmon->sel, dirtomon(arg->i)); +} + +void +tile(Monitor *m) +{ + unsigned int i, n, h, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? m->ww * m->mfact : 0; + else + mw = m->ww; + 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->pertag->showbars[selmon->pertag->curtag] = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +} + +void +togglefloating(const Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +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); + int i; + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + + if (newtagset == ~0) { + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = 0; + } + + /* test if the user did not select the same tag */ + if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) { + selmon->pertag->prevtag = selmon->pertag->curtag; + for (i = 0; !(newtagset & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + + /* apply settings for this view */ + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + + focus(NULL); + arrange(selmon); + } +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Monitor *m = c->mon; + XWindowChanges wc; + + if (c->swallowing) { + unswallow(c); + return; + } + + Client *s = swallowingclient(c->win); + if (s) { + free(s->swallowing); + s->swallowing = NULL; + arrange(m); + focus(NULL); + return; + } + + detach(c); + detachstack(c); + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XSelectInput(dpy, c->win, NoEventMask); + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + setclientstate(c, WithdrawnState); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } + free(c); + + if (!s) { + arrange(m); + focus(NULL); + updateclientlist(); + } +} + +void +unmapnotify(XEvent *e) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if ((c = wintoclient(ev->window))) { + if (ev->send_event) + setclientstate(c, WithdrawnState); + else + unmanage(c, 0); + } +} + +void +updatebars(void) +{ + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + +#ifdef XINERAMA + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = NULL; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + + /* new monitors if nn > n */ + for (i = n; i < nn; i++) { + for (m = mons; m && m->next; m = m->next); + if (m) + m->next = createmon(); + else + mons = createmon(); + } + for (i = 0, m = mons; i < nn && m; m = m->next, i++) + if (i >= n + || unique[i].x_org != m->mx || unique[i].y_org != m->my + || unique[i].width != m->mw || unique[i].height != m->mh) + { + dirty = 1; + m->num = i; + m->mx = m->wx = unique[i].x_org; + m->my = m->wy = unique[i].y_org; + m->mw = m->ww = unique[i].width; + m->mh = m->wh = unique[i].height; + updatebarpos(m); + } + /* removed monitors if n > nn */ + for (i = nn; i < n; i++) { + for (m = mons; m && m->next; m = m->next); + while ((c = m->clients)) { + dirty = 1; + m->clients = c->next; + detachstack(c); + c->mon = mons; + attach(c); + attachstack(c); + } + if (m == selmon) + selmon = mons; + cleanupmon(m); + } + free(unique); + } else +#endif /* XINERAMA */ + { /* default monitor setup */ + if (!mons) + mons = createmon(); + if (mons->mw != sw || mons->mh != sh) { + dirty = 1; + mons->mw = mons->ww = sw; + mons->mh = mons->wh = sh; + updatebarpos(mons); + } + } + if (dirty) { + selmon = mons; + selmon = wintomon(root); + } + return dirty; +} + +void +updatenumlockmask(void) +{ + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) + for (j = 0; j < modmap->max_keypermod; j++) + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +void +updatesizehints(Client *c) +{ + long msize; + XSizeHints size; + + if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if (size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } else if (size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else + c->basew = c->baseh = 0; + if (size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } else + c->incw = c->inch = 0; + if (size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else + c->maxw = c->maxh = 0; + if (size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } else if (size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else + c->minw = c->minh = 0; + if (size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else + c->maxa = c->mina = 0.0; + c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); + c->hintsvalid = 1; +} + +void +updatestatus(void) +{ + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} + +void +view(const Arg *arg) +{ + int i; + unsigned int tmptag; + + 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; + selmon->pertag->prevtag = selmon->pertag->curtag; + + if (arg->ui == ~0) + selmon->pertag->curtag = 0; + else { + for (i = 0; !(arg->ui & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + } else { + tmptag = selmon->pertag->prevtag; + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = tmptag; + } + + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + + focus(NULL); + arrange(selmon); +} + +pid_t +winpid(Window w) +{ + + pid_t result = 0; + +#ifdef __linux__ + xcb_res_client_id_spec_t spec = {0}; + spec.client = w; + spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; + + xcb_generic_error_t *e = NULL; + xcb_res_query_client_ids_cookie_t c = xcb_res_query_client_ids(xcon, 1, &spec); + xcb_res_query_client_ids_reply_t *r = xcb_res_query_client_ids_reply(xcon, c, &e); + + if (!r) + return (pid_t)0; + + xcb_res_client_id_value_iterator_t i = xcb_res_query_client_ids_ids_iterator(r); + for (; i.rem; xcb_res_client_id_value_next(&i)) { + spec = i.data->spec; + if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { + uint32_t *t = xcb_res_client_id_value_value(i.data); + result = *t; + break; + } + } + + free(r); + + if (result == (pid_t)-1) + result = 0; + +#endif /* __linux__ */ + +#ifdef __OpenBSD__ + Atom type; + int format; + unsigned long len, bytes; + unsigned char *prop; + pid_t ret; + + if (XGetWindowProperty(dpy, w, XInternAtom(dpy, "_NET_WM_PID", 0), 0, 1, False, AnyPropertyType, &type, &format, &len, &bytes, &prop) != Success || !prop) + return 0; + + ret = *(pid_t*)prop; + XFree(prop); + result = ret; + +#endif /* __OpenBSD__ */ + return result; +} + +pid_t +getparentprocess(pid_t p) +{ + unsigned int v = 0; + +#ifdef __linux__ + FILE *f; + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p); + + if (!(f = fopen(buf, "r"))) + return 0; + + fscanf(f, "%*u %*s %*c %u", &v); + fclose(f); +#endif /* __linux__*/ + +#ifdef __OpenBSD__ + int n; + kvm_t *kd; + struct kinfo_proc *kp; + + kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL); + if (!kd) + return 0; + + kp = kvm_getprocs(kd, KERN_PROC_PID, p, sizeof(*kp), &n); + v = kp->p_ppid; +#endif /* __OpenBSD__ */ + + return (pid_t)v; +} + +int +isdescprocess(pid_t p, pid_t c) +{ + while (p != c && c != 0) + c = getparentprocess(c); + + return (int)c; +} + +Client * +termforwin(const Client *w) +{ + Client *c; + Monitor *m; + + if (!w->pid || w->isterminal) + return NULL; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->isterminal && !c->swallowing && c->pid && isdescprocess(c->pid, w->pid)) + return c; + } + } + + return NULL; +} + +Client * +swallowingclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->swallowing && c->swallowing->win == w) + return c; + } + } + + return NULL; +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return NULL; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + die("dwm: another window manager is already running"); + return -1; +} + +void +zoom(const Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange || !c || c->isfloating) + return; + if (c == nexttiled(selmon->clients) && !(c = nexttiled(c->next))) + return; + pop(c); +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + die("dwm-"VERSION); + else if (argc != 1) + die("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(NULL))) + die("dwm: cannot open display"); + if (!(xcon = XGetXCBConnection(dpy))) + die("dwm: cannot get xcb connection\n"); + checkotherwm(); + setup(); +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec ps", NULL) == -1) + die("pledge"); +#endif /* __OpenBSD__ */ + scan(); + runautostart(); + restoreSession(); + run(); + if(restart) execvp(argv[0], argv); + cleanup(); + XCloseDisplay(dpy); + return EXIT_SUCCESS; +} diff --git a/dwm/dwm.desktop b/dwm/dwm.desktop deleted file mode 100644 index e44ff32..0000000 --- a/dwm/dwm.desktop +++ /dev/null @@ -1,7 +0,0 @@ -[Desktop Entry] -Encoding=UTF-8 -Name=Dwm -Comment=Dynamic window manager -Exec=dwm-loop -Icon=dwm -Type=XSession diff --git a/dwm/dwm.install b/dwm/dwm.install deleted file mode 100644 index 782c02a..0000000 --- a/dwm/dwm.install +++ /dev/null @@ -1,5 +0,0 @@ -post_install() { - echo "-- Edit config.h in dwm's PKGBUILD directory" - echo "-- and rebuild package if you want to change settings." - rm $srcdir/dwm-6.4* dwm-swallow* -} diff --git a/dwm/dwm.o b/dwm/dwm.o new file mode 100644 index 0000000..6ebd43a Binary files /dev/null and b/dwm/dwm.o differ diff --git a/dwm/transient.c b/dwm/transient.c new file mode 100644 index 0000000..040adb5 --- /dev/null +++ b/dwm/transient.c @@ -0,0 +1,42 @@ +/* cc transient.c -o transient -lX11 */ + +#include +#include +#include +#include + +int main(void) { + Display *d; + Window r, f, t = None; + XSizeHints h; + XEvent e; + + d = XOpenDisplay(NULL); + if (!d) + exit(1); + r = DefaultRootWindow(d); + + f = XCreateSimpleWindow(d, r, 100, 100, 400, 400, 0, 0, 0); + h.min_width = h.max_width = h.min_height = h.max_height = 400; + h.flags = PMinSize | PMaxSize; + XSetWMNormalHints(d, f, &h); + XStoreName(d, f, "floating"); + XMapWindow(d, f); + + XSelectInput(d, f, ExposureMask); + while (1) { + XNextEvent(d, &e); + + if (t == None) { + sleep(5); + t = XCreateSimpleWindow(d, r, 50, 50, 100, 100, 0, 0, 0); + XSetTransientForHint(d, t, f); + XStoreName(d, t, "transient"); + XMapWindow(d, t); + XSelectInput(d, t, ExposureMask); + } + } + + XCloseDisplay(d); + exit(0); +} diff --git a/dwm/util.c b/dwm/util.c new file mode 100644 index 0000000..96b82c9 --- /dev/null +++ b/dwm/util.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "util.h" + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc:"); + return p; +} diff --git a/dwm/util.h b/dwm/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/dwm/util.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + +void die(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/dwm/util.o b/dwm/util.o new file mode 100644 index 0000000..8e55fa1 Binary files /dev/null and b/dwm/util.o differ diff --git a/slstatus/LICENSE b/slstatus/LICENSE new file mode 100644 index 0000000..8bee9c8 --- /dev/null +++ b/slstatus/LICENSE @@ -0,0 +1,43 @@ +ISC License + +Copyright 2016-2022 Aaron Marcher + +Copyright 2016 Roy Freytag +Copyright 2016 Vincent Loupmon +Copyright 2016 Daniel Walter +Copyright 2016-2018 Ali H. Fardan +Copyright 2016 Jody Leonard +Copyright 2016-2018 Quentin Rameau +Copyright 2016 Mike Coddington +Copyright 2016-2018 Ivan J. +Copyright 2017 Tobias Stoeckmann +Copyright 2017-2018 Laslo Hunhold +Copyright 2018 Darron Anderson +Copyright 2018 Josuah Demangeon +Copyright 2018 Tobias Tschinkowitz +Copyright 2018 David Demelier +Copyright 2018-2012 Michael Buch +Copyright 2018 Ian Remmler +Copyright 2016-2019 Joerg Jung +Copyright 2019 Ryan Kes +Copyright 2019 Cem Keylan +Copyright 2019 Dimitris Papastamos +Copyright 2019-2022 Ingo Feinerer +Copyright 2020 Alexandre Ratchov +Copyright 2020 Mart Lubbers +Copyright 2020 Daniel Moch +Copyright 2022 Nickolas Raymond Kaczynski +Copyright 2022 Patrick Iacob +Copyright 2021-2022 Steven Ward + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/slstatus/Makefile b/slstatus/Makefile new file mode 100644 index 0000000..1893d38 --- /dev/null +++ b/slstatus/Makefile @@ -0,0 +1,69 @@ +# 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/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 slstatus slstatus.o $(COM:=.o) $(REQ:=.o) slstatus-${VERSION}.tar.gz *.rej *.orig config.h + +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/PKGBUILD b/slstatus/PKGBUILD deleted file mode 100644 index 73e6e27..0000000 --- a/slstatus/PKGBUILD +++ /dev/null @@ -1,41 +0,0 @@ -# my cool slstatus!! - -pkgname=slstatus -pkgver=1.0 -pkgrel=1 -pkgdesc="A suckless status monitor for dwm" -url="http://git.vern.cc/vitorg/suckless" -arch=('x86_64') -license=('MIT') -options=(zipman) -depends=() -install= -provides=("$pkgname") -conflicts=("$pkgname") -source=(http://dl.suckless.org/tools/slstatus-1.0.tar.gz - config.h) -sha256sums=('6d6d0a16c08dd9d211172c30c4720701267a3f40cdc938db3f386f6a2b6cff54' - '6a2df67ebdb5480644ec2b9176bfcf771aabc99c1329c8d16c3ef2cc0f56dad1') - -prepare() { - cd "${pkgname}-${pkgver}" || exit 1 - echo "CPPFLAGS+=${CPPFLAGS}" >> config.mk - echo "CFLAGS+=${CFLAGS}" >> config.mk - echo "LDFLAGS+=${LDFLAGS}" >> config.mk - if [[ -f "${srcdir}/config.h" ]]; then - cp -fv "${srcdir}/config.h" config.h - fi -} - -build() { - cd ${pkgname}-${pkgver} - make \ - X11INC=/usr/include/X11 \ - X11LIB=/usr/lib/X11 -} - -package() { - cd ${pkgname}-${pkgver} - make PREFIX=/usr DESTDIR="${pkgdir}" install - install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" -} diff --git a/slstatus/README b/slstatus/README new file mode 100644 index 0000000..12d38bf --- /dev/null +++ b/slstatus/README @@ -0,0 +1,65 @@ +slstatus - suckless status +========================== +slstatus is a small tool for providing system status information to other +programs over the EWMH property of the root window (used by dwm(1)) or +standard input/output. It is designed to be as efficient as possible by +only issuing the minimum of system calls required. + + +Features +-------- +- Battery percentage/state/time left +- Cat (read file) +- CPU usage +- CPU frequency +- Custom shell commands +- Date and time +- Disk status (free storage, percentage, total storage and used storage) +- Available entropy +- Username/GID/UID +- Hostname +- IP address (IPv4 and IPv6) +- Kernel version +- Keyboard indicators +- Keymap +- Load average +- Network speeds (RX and TX) +- Number of files in a directory (hint: Maildir) +- Memory status (free memory, percentage, total memory and used memory) +- Swap status (free swap, percentage, total swap and used swap) +- Temperature +- Uptime +- Volume percentage +- WiFi signal percentage and ESSID + + +Requirements +------------ +Currently slstatus works on FreeBSD, Linux and OpenBSD. +In order to build slstatus you need the Xlib header files. + +- For volume percentage on Linux the kernel module `snd-mixer-oss` must be + loaded. +- For volume percentage on FreeBSD, `sndio` must be installed. + + +Installation +------------ +Edit config.mk to match your local setup (slstatus is installed into the +/usr/local namespace by default). + +Afterwards enter the following command to build and install slstatus (if +necessary as root): + + make clean install + + +Running slstatus +---------------- +See the man page for details. + + +Configuration +------------- +slstatus can be customized by creating a custom config.h and (re)compiling the +source code. This keeps it fast, secure and simple. diff --git a/slstatus/arg.h b/slstatus/arg.h new file mode 100644 index 0000000..5f1f408 --- /dev/null +++ b/slstatus/arg.h @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef ARG_H +#define ARG_H + +extern char *argv0; + +/* int main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \ + *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \ + int i_, argused_; \ + if ((*argv)[1] == '-' && !(*argv)[2]) { \ + argc--, argv++; \ + break; \ + } \ + for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \ + switch ((*argv)[i_]) +#define ARGEND if (argused_) { \ + if ((*argv)[i_ + 1]) { \ + break; \ + } else { \ + argc--, argv++; \ + break; \ + } \ + } \ + } \ + } +#define ARGC() ((*argv)[i_]) +#define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \ + (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x)) +#define EARGF(x) ARGF_(((x), exit(1), (char *)0)) +#define ARGF() ARGF_((char *)0) + +#endif diff --git a/slstatus/components/battery.c b/slstatus/components/battery.c new file mode 100644 index 0000000..1c753f9 --- /dev/null +++ b/slstatus/components/battery.c @@ -0,0 +1,247 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) +/* + * https://www.kernel.org/doc/html/latest/power/power_supply_class.html + */ + #include + #include + #include + + #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 + #include + #include + #include + + 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 + + #define BATTERY_LIFE "hw.acpi.battery.life" + #define BATTERY_STATE "hw.acpi.battery.state" + #define BATTERY_TIME "hw.acpi.battery.time" + + const char * + battery_perc(const char *unused) + { + int cap_perc; + size_t len; + + len = sizeof(cap_perc); + if (sysctlbyname(BATTERY_LIFE, &cap_perc, &len, NULL, 0) < 0 || !len) + return NULL; + + return bprintf("%d", cap_perc); + } + + const char * + battery_state(const char *unused) + { + int state; + size_t len; + + len = sizeof(state); + if (sysctlbyname(BATTERY_STATE, &state, &len, NULL, 0) < 0 || !len) + return NULL; + + switch (state) { + case 0: /* FALLTHROUGH */ + case 2: + return "+"; + case 1: + return "-"; + default: + return "?"; + } + } + + const char * + battery_remaining(const char *unused) + { + int rem; + size_t len; + + len = sizeof(rem); + if (sysctlbyname(BATTERY_TIME, &rem, &len, NULL, 0) < 0 || !len + || rem < 0) + return NULL; + + return bprintf("%uh %02um", rem / 60, rem % 60); + } +#endif diff --git a/slstatus/components/battery.o b/slstatus/components/battery.o new file mode 100644 index 0000000..4c63c60 Binary files /dev/null and b/slstatus/components/battery.o differ diff --git a/slstatus/components/cat.c b/slstatus/components/cat.c new file mode 100644 index 0000000..07944cc --- /dev/null +++ b/slstatus/components/cat.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +cat(const char *path) +{ + char *f; + FILE *fp; + + if (!(fp = fopen(path, "r"))) { + warn("fopen '%s':", path); + return NULL; + } + + f = fgets(buf, sizeof(buf) - 1, fp); + if (fclose(fp) < 0) { + warn("fclose '%s':", path); + return NULL; + } + if (!f) + return NULL; + + if ((f = strrchr(buf, '\n'))) + f[0] = '\0'; + + return buf[0] ? buf : NULL; +} + diff --git a/slstatus/components/cat.o b/slstatus/components/cat.o new file mode 100644 index 0000000..3b50ebc Binary files /dev/null and b/slstatus/components/cat.o differ diff --git a/slstatus/components/cpu.c b/slstatus/components/cpu.c new file mode 100644 index 0000000..d0d03c7 --- /dev/null +++ b/slstatus/components/cpu.c @@ -0,0 +1,157 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#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 + #include + #include + + 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 + #include + #include + + const char * + cpu_freq(const char *unused) + { + int freq; + size_t size; + + size = sizeof(freq); + /* in MHz */ + if (sysctlbyname("hw.clockrate", &freq, &size, NULL, 0) < 0 || !size) { + warn("sysctlbyname 'hw.clockrate':"); + return NULL; + } + + return fmt_human(freq * 1E6, 1000); + } + + const char * + cpu_perc(const char *unused) + { + size_t size; + static long a[CPUSTATES]; + long b[CPUSTATES], sum; + + size = sizeof(a); + memcpy(b, a, sizeof(b)); + if (sysctlbyname("kern.cp_time", &a, &size, NULL, 0) < 0 || !size) { + warn("sysctlbyname 'kern.cp_time':"); + return NULL; + } + if (b[0] == 0) + return NULL; + + sum = (a[CP_USER] + a[CP_NICE] + a[CP_SYS] + a[CP_INTR] + a[CP_IDLE]) - + (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + b[CP_INTR] + b[CP_IDLE]); + + if (sum == 0) + return NULL; + + return bprintf("%d", 100 * + ((a[CP_USER] + a[CP_NICE] + a[CP_SYS] + + a[CP_INTR]) - + (b[CP_USER] + b[CP_NICE] + b[CP_SYS] + + b[CP_INTR])) / sum); + } +#endif diff --git a/slstatus/components/cpu.o b/slstatus/components/cpu.o new file mode 100644 index 0000000..969d09c Binary files /dev/null and b/slstatus/components/cpu.o differ diff --git a/slstatus/components/datetime.c b/slstatus/components/datetime.c new file mode 100644 index 0000000..5b10daf --- /dev/null +++ b/slstatus/components/datetime.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +datetime(const char *fmt) +{ + time_t t; + + t = time(NULL); + if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) { + warn("strftime: Result string exceeds buffer size"); + return NULL; + } + + return buf; +} diff --git a/slstatus/components/datetime.o b/slstatus/components/datetime.o new file mode 100644 index 0000000..0353444 Binary files /dev/null and b/slstatus/components/datetime.o differ diff --git a/slstatus/components/disk.c b/slstatus/components/disk.c new file mode 100644 index 0000000..e19a693 --- /dev/null +++ b/slstatus/components/disk.c @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +disk_free(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return fmt_human(fs.f_frsize * fs.f_bavail, 1024); +} + +const char * +disk_perc(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return bprintf("%d", (int)(100 * + (1 - ((double)fs.f_bavail / (double)fs.f_blocks)))); +} + +const char * +disk_total(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return fmt_human(fs.f_frsize * fs.f_blocks, 1024); +} + +const char * +disk_used(const char *path) +{ + struct statvfs fs; + + if (statvfs(path, &fs) < 0) { + warn("statvfs '%s':", path); + return NULL; + } + + return fmt_human(fs.f_frsize * (fs.f_blocks - fs.f_bfree), 1024); +} diff --git a/slstatus/components/disk.o b/slstatus/components/disk.o new file mode 100644 index 0000000..5ec5c0a Binary files /dev/null and b/slstatus/components/disk.o differ diff --git a/slstatus/components/entropy.c b/slstatus/components/entropy.c new file mode 100644 index 0000000..65010b0 --- /dev/null +++ b/slstatus/components/entropy.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include "../slstatus.h" +#if defined(__linux__) + #include + #include + + #include "../util.h" + + #define ENTROPY_AVAIL "/proc/sys/kernel/random/entropy_avail" + + const char * + entropy(const char *unused) + { + uintmax_t num; + + if (pscanf(ENTROPY_AVAIL, "%ju", &num) != 1) + return NULL; + + return bprintf("%ju", num); + } +#elif defined(__OpenBSD__) | defined(__FreeBSD__) + const char * + entropy(const char *unused) + { + // https://www.unicode.org/charts/PDF/U2200.pdf + /* Unicode Character 'INFINITY' (U+221E) */ + return "\u221E"; + } +#endif diff --git a/slstatus/components/entropy.o b/slstatus/components/entropy.o new file mode 100644 index 0000000..6af57ba Binary files /dev/null and b/slstatus/components/entropy.o differ diff --git a/slstatus/components/hostname.c b/slstatus/components/hostname.c new file mode 100644 index 0000000..dab8b63 --- /dev/null +++ b/slstatus/components/hostname.c @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +hostname(const char *unused) +{ + if (gethostname(buf, sizeof(buf)) < 0) { + warn("gethostbyname:"); + return NULL; + } + + return buf; +} diff --git a/slstatus/components/hostname.o b/slstatus/components/hostname.o new file mode 100644 index 0000000..4384f7c Binary files /dev/null and b/slstatus/components/hostname.o differ diff --git a/slstatus/components/ip.c b/slstatus/components/ip.c new file mode 100644 index 0000000..9476549 --- /dev/null +++ b/slstatus/components/ip.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#if defined(__OpenBSD__) + #include + #include +#elif defined(__FreeBSD__) + #include + #include +#endif + +#include "../slstatus.h" +#include "../util.h" + +static const char * +ip(const char *interface, unsigned short sa_family) +{ + struct ifaddrs *ifaddr, *ifa; + int s; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr) < 0) { + warn("getifaddrs:"); + return NULL; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + + s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + if (!strcmp(ifa->ifa_name, interface) && + (ifa->ifa_addr->sa_family == sa_family)) { + freeifaddrs(ifaddr); + if (s != 0) { + warn("getnameinfo: %s", gai_strerror(s)); + return NULL; + } + return bprintf("%s", host); + } + } + + freeifaddrs(ifaddr); + + return NULL; +} + +const char * +ipv4(const char *interface) +{ + return ip(interface, AF_INET); +} + +const char * +ipv6(const char *interface) +{ + return ip(interface, AF_INET6); +} diff --git a/slstatus/components/ip.o b/slstatus/components/ip.o new file mode 100644 index 0000000..22f306d Binary files /dev/null and b/slstatus/components/ip.o differ diff --git a/slstatus/components/kernel_release.c b/slstatus/components/kernel_release.c new file mode 100644 index 0000000..36a6a44 --- /dev/null +++ b/slstatus/components/kernel_release.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +kernel_release(const char *unused) +{ + struct utsname udata; + + if (uname(&udata) < 0) { + warn("uname:"); + return NULL; + } + + return bprintf("%s", udata.release); +} diff --git a/slstatus/components/kernel_release.o b/slstatus/components/kernel_release.o new file mode 100644 index 0000000..be0be90 Binary files /dev/null and b/slstatus/components/kernel_release.o differ diff --git a/slstatus/components/keyboard_indicators.c b/slstatus/components/keyboard_indicators.c new file mode 100644 index 0000000..5f62bb7 --- /dev/null +++ b/slstatus/components/keyboard_indicators.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +/* + * fmt consists of uppercase or lowercase 'c' for caps lock and/or 'n' for num + * lock, each optionally followed by '?', in the order of indicators desired. + * If followed by '?', the letter with case preserved is included in the output + * if the corresponding indicator is on. Otherwise, the letter is always + * included, lowercase when off and uppercase when on. + */ +const char * +keyboard_indicators(const char *fmt) +{ + Display *dpy; + XKeyboardState state; + size_t fmtlen, i, n; + int togglecase, isset; + char key; + + if (!(dpy = XOpenDisplay(NULL))) { + warn("XOpenDisplay: Failed to open display"); + return NULL; + } + XGetKeyboardControl(dpy, &state); + XCloseDisplay(dpy); + + fmtlen = strnlen(fmt, 4); + for (i = n = 0; i < fmtlen; i++) { + key = tolower(fmt[i]); + if (key != 'c' && key != 'n') + continue; + + togglecase = (i + 1 >= fmtlen || fmt[i + 1] != '?'); + isset = (state.led_mask & (1 << (key == 'n'))); + + if (togglecase) + buf[n++] = isset ? toupper(key) : key; + else if (isset) + buf[n++] = fmt[i]; + } + + buf[n] = 0; + return buf; +} diff --git a/slstatus/components/keyboard_indicators.o b/slstatus/components/keyboard_indicators.o new file mode 100644 index 0000000..001b37f Binary files /dev/null and b/slstatus/components/keyboard_indicators.o differ diff --git a/slstatus/components/keymap.c b/slstatus/components/keymap.c new file mode 100644 index 0000000..f8a2a47 --- /dev/null +++ b/slstatus/components/keymap.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +static int +valid_layout_or_variant(char *sym) +{ + size_t i; + /* invalid symbols from xkb rules config */ + static const char *invalid[] = { "evdev", "inet", "pc", "base" }; + + for (i = 0; i < LEN(invalid); i++) + if (!strncmp(sym, invalid[i], strlen(invalid[i]))) + return 0; + + return 1; +} + +static char * +get_layout(char *syms, int grp_num) +{ + char *tok, *layout; + int grp; + + layout = NULL; + tok = strtok(syms, "+:"); + for (grp = 0; tok && grp <= grp_num; tok = strtok(NULL, "+:")) { + if (!valid_layout_or_variant(tok)) { + continue; + } else if (strlen(tok) == 1 && isdigit(tok[0])) { + /* ignore :2, :3, :4 (additional layout groups) */ + continue; + } + layout = tok; + grp++; + } + + return layout; +} + +const char * +keymap(const char *unused) +{ + Display *dpy; + XkbDescRec *desc; + XkbStateRec state; + char *symbols; + const char *layout; + + layout = NULL; + + if (!(dpy = XOpenDisplay(NULL))) { + warn("XOpenDisplay: Failed to open display"); + return NULL; + } + if (!(desc = XkbAllocKeyboard())) { + warn("XkbAllocKeyboard: Failed to allocate keyboard"); + goto end; + } + if (XkbGetNames(dpy, XkbSymbolsNameMask, desc)) { + warn("XkbGetNames: Failed to retrieve key symbols"); + goto end; + } + if (XkbGetState(dpy, XkbUseCoreKbd, &state)) { + warn("XkbGetState: Failed to retrieve keyboard state"); + goto end; + } + if (!(symbols = XGetAtomName(dpy, desc->names->symbols))) { + warn("XGetAtomName: Failed to get atom name"); + goto end; + } + layout = bprintf("%s", get_layout(symbols, state.group)); + XFree(symbols); +end: + XkbFreeKeyboard(desc, XkbSymbolsNameMask, 1); + if (XCloseDisplay(dpy)) + warn("XCloseDisplay: Failed to close display"); + + return layout; +} diff --git a/slstatus/components/keymap.o b/slstatus/components/keymap.o new file mode 100644 index 0000000..b9c0bca Binary files /dev/null and b/slstatus/components/keymap.o differ diff --git a/slstatus/components/load_avg.c b/slstatus/components/load_avg.c new file mode 100644 index 0000000..f278a40 --- /dev/null +++ b/slstatus/components/load_avg.c @@ -0,0 +1,19 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +load_avg(const char *unused) +{ + double avgs[3]; + + if (getloadavg(avgs, 3) < 0) { + warn("getloadavg: Failed to obtain load average"); + return NULL; + } + + return bprintf("%.2f %.2f %.2f", avgs[0], avgs[1], avgs[2]); +} diff --git a/slstatus/components/load_avg.o b/slstatus/components/load_avg.o new file mode 100644 index 0000000..0861fb7 Binary files /dev/null and b/slstatus/components/load_avg.o differ diff --git a/slstatus/components/netspeeds.c b/slstatus/components/netspeeds.c new file mode 100644 index 0000000..cde6fa9 --- /dev/null +++ b/slstatus/components/netspeeds.c @@ -0,0 +1,129 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + #include + + #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 + #include + #include + #include + #include + + const char * + netspeed_rx(const char *interface) + { + struct ifaddrs *ifal, *ifa; + struct if_data *ifd; + uintmax_t oldrxbytes; + static uintmax_t rxbytes; + extern const unsigned int interval; + int if_ok = 0; + + oldrxbytes = rxbytes; + + if (getifaddrs(&ifal) < 0) { + warn("getifaddrs failed"); + return NULL; + } + rxbytes = 0; + for (ifa = ifal; ifa; ifa = ifa->ifa_next) + if (!strcmp(ifa->ifa_name, interface) && + (ifd = (struct if_data *)ifa->ifa_data)) + rxbytes += ifd->ifi_ibytes, if_ok = 1; + + freeifaddrs(ifal); + if (!if_ok) { + warn("reading 'if_data' failed"); + return NULL; + } + if (oldrxbytes == 0) + return NULL; + + return fmt_human((rxbytes - oldrxbytes) * 1000 / interval, + 1024); + } + + const char * + netspeed_tx(const char *interface) + { + struct ifaddrs *ifal, *ifa; + struct if_data *ifd; + uintmax_t oldtxbytes; + static uintmax_t txbytes; + extern const unsigned int interval; + int if_ok = 0; + + oldtxbytes = txbytes; + + if (getifaddrs(&ifal) < 0) { + warn("getifaddrs failed"); + return NULL; + } + txbytes = 0; + for (ifa = ifal; ifa; ifa = ifa->ifa_next) + if (!strcmp(ifa->ifa_name, interface) && + (ifd = (struct if_data *)ifa->ifa_data)) + txbytes += ifd->ifi_obytes, if_ok = 1; + + freeifaddrs(ifal); + if (!if_ok) { + warn("reading 'if_data' failed"); + return NULL; + } + if (oldtxbytes == 0) + return NULL; + + return fmt_human((txbytes - oldtxbytes) * 1000 / interval, + 1024); + } +#endif diff --git a/slstatus/components/netspeeds.o b/slstatus/components/netspeeds.o new file mode 100644 index 0000000..fe9aca1 Binary files /dev/null and b/slstatus/components/netspeeds.o differ diff --git a/slstatus/components/num_files.c b/slstatus/components/num_files.c new file mode 100644 index 0000000..df0acd1 --- /dev/null +++ b/slstatus/components/num_files.c @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +num_files(const char *path) +{ + struct dirent *dp; + DIR *dir; + int num; + + if (!(dir = opendir(path))) { + warn("opendir '%s':", path); + return NULL; + } + + num = 0; + while ((dp = readdir(dir))) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; /* skip self and parent */ + + num++; + } + + closedir(dir); + + return bprintf("%d", num); +} diff --git a/slstatus/components/num_files.o b/slstatus/components/num_files.o new file mode 100644 index 0000000..15d1365 Binary files /dev/null and b/slstatus/components/num_files.o differ diff --git a/slstatus/components/ram.c b/slstatus/components/ram.c new file mode 100644 index 0000000..15c4b74 --- /dev/null +++ b/slstatus/components/ram.c @@ -0,0 +1,212 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__linux__) + #include + + 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 + #include + #include + #include + + #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 + #include + #include + #include + + const char * + ram_free(const char *unused) { + struct vmtotal vm_stats; + int mib[] = {CTL_VM, VM_TOTAL}; + size_t len; + + len = sizeof(struct vmtotal); + if (sysctl(mib, 2, &vm_stats, &len, NULL, 0) < 0 + || !len) + return NULL; + + return fmt_human(vm_stats.t_free * getpagesize(), 1024); + } + + const char * + ram_total(const char *unused) { + unsigned int npages; + size_t len; + + len = sizeof(npages); + if (sysctlbyname("vm.stats.vm.v_page_count", + &npages, &len, NULL, 0) < 0 || !len) + return NULL; + + return fmt_human(npages * getpagesize(), 1024); + } + + const char * + ram_perc(const char *unused) { + unsigned int npages; + unsigned int active; + size_t len; + + len = sizeof(npages); + if (sysctlbyname("vm.stats.vm.v_page_count", + &npages, &len, NULL, 0) < 0 || !len) + return NULL; + + if (sysctlbyname("vm.stats.vm.v_active_count", + &active, &len, NULL, 0) < 0 || !len) + return NULL; + + return bprintf("%d", active * 100 / npages); + } + + const char * + ram_used(const char *unused) { + unsigned int active; + size_t len; + + len = sizeof(active); + if (sysctlbyname("vm.stats.vm.v_active_count", + &active, &len, NULL, 0) < 0 || !len) + return NULL; + + return fmt_human(active * getpagesize(), 1024); + } +#endif diff --git a/slstatus/components/ram.o b/slstatus/components/ram.o new file mode 100644 index 0000000..2dbd845 Binary files /dev/null and b/slstatus/components/ram.o differ diff --git a/slstatus/components/run_command.c b/slstatus/components/run_command.c new file mode 100644 index 0000000..93bf6da --- /dev/null +++ b/slstatus/components/run_command.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +run_command(const char *cmd) +{ + char *p; + FILE *fp; + + if (!(fp = popen(cmd, "r"))) { + warn("popen '%s':", cmd); + return NULL; + } + + p = fgets(buf, sizeof(buf) - 1, fp); + if (pclose(fp) < 0) { + warn("pclose '%s':", cmd); + return NULL; + } + if (!p) + return NULL; + + if ((p = strrchr(buf, '\n'))) + p[0] = '\0'; + + return buf[0] ? buf : NULL; +} diff --git a/slstatus/components/run_command.o b/slstatus/components/run_command.o new file mode 100644 index 0000000..7addb38 Binary files /dev/null and b/slstatus/components/run_command.o differ diff --git a/slstatus/components/swap.c b/slstatus/components/swap.c new file mode 100644 index 0000000..f270d93 --- /dev/null +++ b/slstatus/components/swap.c @@ -0,0 +1,274 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#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 + #include + #include + #include + + 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 + #include + #include + #include + #include + + static int getswapinfo(struct kvm_swap *swap_info, size_t size) + { + kvm_t *kd; + + kd = kvm_openfiles(NULL, "/dev/null", NULL, 0, NULL); + if (kd == NULL) { + warn("kvm_openfiles '/dev/null':"); + return 0; + } + + if (kvm_getswapinfo(kd, swap_info, size, 0 /* Unused flags */) < 0) { + warn("kvm_getswapinfo:"); + kvm_close(kd); + return 0; + } + + kvm_close(kd); + return 1; + } + + const char * + swap_free(const char *unused) + { + struct kvm_swap swap_info[1]; + long used, total; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + total = swap_info[0].ksw_total; + used = swap_info[0].ksw_used; + + return fmt_human((total - used) * getpagesize(), 1024); + } + + const char * + swap_perc(const char *unused) + { + struct kvm_swap swap_info[1]; + long used, total; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + total = swap_info[0].ksw_total; + used = swap_info[0].ksw_used; + + return bprintf("%d", used * 100 / total); + } + + const char * + swap_total(const char *unused) + { + struct kvm_swap swap_info[1]; + long total; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + total = swap_info[0].ksw_total; + + return fmt_human(total * getpagesize(), 1024); + } + + const char * + swap_used(const char *unused) + { + struct kvm_swap swap_info[1]; + long used; + + if (!getswapinfo(swap_info, 1)) + return NULL; + + used = swap_info[0].ksw_used; + + return fmt_human(used * getpagesize(), 1024); + } +#endif diff --git a/slstatus/components/swap.o b/slstatus/components/swap.o new file mode 100644 index 0000000..eff037e Binary files /dev/null and b/slstatus/components/swap.o differ diff --git a/slstatus/components/temperature.c b/slstatus/components/temperature.c new file mode 100644 index 0000000..7cf1394 --- /dev/null +++ b/slstatus/components/temperature.c @@ -0,0 +1,73 @@ +/* See LICENSE file for copyright and license details. */ +#include + +#include "../slstatus.h" +#include "../util.h" + + +#if defined(__linux__) + #include + + 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 + #include /* before for struct timeval */ + #include + #include + + 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 + #include + #include + + #define ACPI_TEMP "hw.acpi.thermal.%s.temperature" + + const char * + temp(const char *zone) + { + char buf[256]; + int temp; + size_t len; + + len = sizeof(temp); + snprintf(buf, sizeof(buf), ACPI_TEMP, zone); + if (sysctlbyname(buf, &temp, &len, NULL, 0) < 0 + || !len) + return NULL; + + /* kelvin to decimal celcius */ + return bprintf("%d.%d", (temp - 2731) / 10, abs((temp - 2731) % 10)); + } +#endif diff --git a/slstatus/components/temperature.o b/slstatus/components/temperature.o new file mode 100644 index 0000000..0856c7b Binary files /dev/null and b/slstatus/components/temperature.o differ diff --git a/slstatus/components/uptime.c b/slstatus/components/uptime.c new file mode 100644 index 0000000..6227f73 --- /dev/null +++ b/slstatus/components/uptime.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(CLOCK_BOOTTIME) + #define UPTIME_FLAG CLOCK_BOOTTIME +#elif defined(CLOCK_UPTIME) + #define UPTIME_FLAG CLOCK_UPTIME +#else + #define UPTIME_FLAG CLOCK_MONOTONIC +#endif + +const char * +uptime(const char *unused) +{ + char warn_buf[256]; + uintmax_t h, m; + struct timespec uptime; + + if (clock_gettime(UPTIME_FLAG, &uptime) < 0) { + snprintf(warn_buf, sizeof(warn_buf), "clock_gettime %d", UPTIME_FLAG); + warn(warn_buf); + return NULL; + } + + h = uptime.tv_sec / 3600; + m = uptime.tv_sec % 3600 / 60; + + return bprintf("%juh %jum", h, m); +} diff --git a/slstatus/components/uptime.o b/slstatus/components/uptime.o new file mode 100644 index 0000000..ee47012 Binary files /dev/null and b/slstatus/components/uptime.o differ diff --git a/slstatus/components/user.c b/slstatus/components/user.c new file mode 100644 index 0000000..3517495 --- /dev/null +++ b/slstatus/components/user.c @@ -0,0 +1,33 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +const char * +gid(const char *unused) +{ + return bprintf("%d", getgid()); +} + +const char * +username(const char *unused) +{ + struct passwd *pw; + + if (!(pw = getpwuid(geteuid()))) { + warn("getpwuid '%d':", geteuid()); + return NULL; + } + + return bprintf("%s", pw->pw_name); +} + +const char * +uid(const char *unused) +{ + return bprintf("%d", geteuid()); +} diff --git a/slstatus/components/user.o b/slstatus/components/user.o new file mode 100644 index 0000000..83bb561 Binary files /dev/null and b/slstatus/components/user.o differ diff --git a/slstatus/components/volume.c b/slstatus/components/volume.c new file mode 100644 index 0000000..6cec556 --- /dev/null +++ b/slstatus/components/volume.c @@ -0,0 +1,219 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#if defined(__OpenBSD__) | defined(__FreeBSD__) + #include + #include + #include + #include + + 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 + + const char * + vol_perc(const char *card) + { + size_t i; + int v, afd, devmask; + char *vnames[] = SOUND_DEVICE_NAMES; + + if ((afd = open(card, O_RDONLY | O_NONBLOCK)) < 0) { + warn("open '%s':", card); + return NULL; + } + + if (ioctl(afd, (int)SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + warn("ioctl 'SOUND_MIXER_READ_DEVMASK':"); + close(afd); + return NULL; + } + for (i = 0; i < LEN(vnames); i++) { + if (devmask & (1 << i) && !strcmp("vol", vnames[i])) { + if (ioctl(afd, MIXER_READ(i), &v) < 0) { + warn("ioctl 'MIXER_READ(%ld)':", i); + close(afd); + return NULL; + } + } + } + + close(afd); + + return bprintf("%d", v & 0xff); + } +#endif diff --git a/slstatus/components/volume.o b/slstatus/components/volume.o new file mode 100644 index 0000000..73b3122 Binary files /dev/null and b/slstatus/components/volume.o differ diff --git a/slstatus/components/wifi.c b/slstatus/components/wifi.c new file mode 100644 index 0000000..4543d32 --- /dev/null +++ b/slstatus/components/wifi.c @@ -0,0 +1,267 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#include "../slstatus.h" +#include "../util.h" + +#define RSSI_TO_PERC(rssi) \ + rssi >= -50 ? 100 : \ + (rssi <= -100 ? 0 : \ + (2 * (rssi + 100))) + +#if defined(__linux__) + #include + #include + + #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 + #include + #include + #include /* before for NBBY */ + #include + #include + #include + + 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 + #include + + int + load_ieee80211req(int sock, const char *interface, void *data, int type, size_t *len) + { + char warn_buf[256]; + struct ieee80211req ireq; + memset(&ireq, 0, sizeof(ireq)); + ireq.i_type = type; + ireq.i_data = (caddr_t) data; + ireq.i_len = *len; + + strlcpy(ireq.i_name, interface, sizeof(ireq.i_name)); + if (ioctl(sock, SIOCG80211, &ireq) < 0) { + snprintf(warn_buf, sizeof(warn_buf), + "ioctl: 'SIOCG80211': %d", type); + warn(warn_buf); + return 0; + } + + *len = ireq.i_len; + return 1; + } + + const char * + wifi_perc(const char *interface) + { + union { + struct ieee80211req_sta_req sta; + uint8_t buf[24 * 1024]; + } info; + uint8_t bssid[IEEE80211_ADDR_LEN]; + int rssi_dbm; + int sockfd; + size_t len; + const char *fmt; + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + warn("socket 'AF_INET':"); + return NULL; + } + + /* Retreive MAC address of interface */ + len = IEEE80211_ADDR_LEN; + fmt = NULL; + if (load_ieee80211req(sockfd, interface, &bssid, IEEE80211_IOC_BSSID, &len)) + { + /* Retrieve info on station with above BSSID */ + memset(&info, 0, sizeof(info)); + memcpy(info.sta.is_u.macaddr, bssid, sizeof(bssid)); + + len = sizeof(info); + if (load_ieee80211req(sockfd, interface, &info, IEEE80211_IOC_STA_INFO, &len)) { + rssi_dbm = info.sta.info[0].isi_noise + + info.sta.info[0].isi_rssi / 2; + + fmt = bprintf("%d", RSSI_TO_PERC(rssi_dbm)); + } + } + + close(sockfd); + return fmt; + } + + const char * + wifi_essid(const char *interface) + { + char ssid[IEEE80211_NWID_LEN + 1]; + size_t len; + int sockfd; + const char *fmt; + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + warn("socket 'AF_INET':"); + return NULL; + } + + fmt = NULL; + len = sizeof(ssid); + memset(&ssid, 0, len); + if (load_ieee80211req(sockfd, interface, &ssid, IEEE80211_IOC_SSID, &len)) { + if (len < sizeof(ssid)) + len += 1; + else + len = sizeof(ssid); + + ssid[len - 1] = '\0'; + fmt = bprintf("%s", ssid); + } + + close(sockfd); + return fmt; + } +#endif diff --git a/slstatus/components/wifi.o b/slstatus/components/wifi.o new file mode 100644 index 0000000..91e1508 Binary files /dev/null and b/slstatus/components/wifi.o differ diff --git a/slstatus/config.def.h b/slstatus/config.def.h new file mode 100644 index 0000000..4cb1ad4 --- /dev/null +++ b/slstatus/config.def.h @@ -0,0 +1,71 @@ +/* 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) + * 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 */ + { uptime, "upt %s | ", NULL }, + { ram_perc, "%s%% | ", NULL }, + { datetime, "%s", "%F %T" }, +}; diff --git a/slstatus/config.mk b/slstatus/config.mk new file mode 100644 index 0000000..07af883 --- /dev/null +++ b/slstatus/config.mk @@ -0,0 +1,22 @@ +# slstatus version +VERSION = 1.0 + +# customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# flags +CPPFLAGS = -I$(X11INC) -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" +CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter -Os +LDFLAGS = -L$(X11LIB) -s +# OpenBSD: add -lsndio +# FreeBSD: add -lkvm -lsndio +LDLIBS = -lX11 + +# compiler and linker +CC = cc diff --git a/slstatus/slstatus b/slstatus/slstatus new file mode 100755 index 0000000..a456030 Binary files /dev/null and b/slstatus/slstatus differ diff --git a/slstatus/slstatus.1 b/slstatus/slstatus.1 new file mode 100644 index 0000000..73e7a60 --- /dev/null +++ b/slstatus/slstatus.1 @@ -0,0 +1,47 @@ +.Dd 2023-04-23 +.Dt SLSTATUS 1 +.Os +.Sh NAME +.Nm slstatus +.Nd suckless status +.Sh SYNOPSIS +.Nm +.Op Fl s +.Op Fl 1 +.Sh DESCRIPTION +.Nm +is a small tool for providing system status information to other programs +over the EWMH +.Em WM_NAME +property of the root window (used by +.Xr dwm 1 ) or standard input/output. It is designed to be as efficient as possible by +only issuing the minimum of system calls required. +.P +By default, +.Nm +outputs to WM_NAME. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl v +Print version information to stderr, then exit. +.It Fl s +Write to stdout instead of WM_NAME. +.It Fl 1 +Write once to stdout and quit. +.El +.Sh CUSTOMIZATION +.Nm +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.Sh SIGNALS +.Nm +responds to the following signals: +.Pp +.Bl -tag -width TERM -compact +.It USR1 +Triggers an instant redraw. +.El +.Sh AUTHORS +See the LICENSE file for the authors. +.Sh SEE ALSO +.Xr dwm 1 diff --git a/slstatus/slstatus.c b/slstatus/slstatus.c new file mode 100644 index 0000000..fd31313 --- /dev/null +++ b/slstatus/slstatus.c @@ -0,0 +1,134 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" +#include "slstatus.h" +#include "util.h" + +struct arg { + const char *(*func)(const char *); + const char *fmt; + const char *args; +}; + +char buf[1024]; +static volatile sig_atomic_t done; +static Display *dpy; + +#include "config.h" + +static void +terminate(const int signo) +{ + if (signo != SIGUSR1) + done = 1; +} + +static void +difftimespec(struct timespec *res, struct timespec *a, struct timespec *b) +{ + res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec); + res->tv_nsec = a->tv_nsec - b->tv_nsec + + (a->tv_nsec < b->tv_nsec) * 1E9; +} + +static void +usage(void) +{ + die("usage: %s [-v] [-s] [-1]", argv0); +} + +int +main(int argc, char *argv[]) +{ + struct sigaction act; + struct timespec start, current, diff, intspec, wait; + size_t i, len; + int sflag, ret; + char status[MAXLEN]; + const char *res; + + sflag = 0; + ARGBEGIN { + case 'v': + die("slstatus-"VERSION); + case '1': + done = 1; + /* FALLTHROUGH */ + case 's': + sflag = 1; + break; + default: + usage(); + } ARGEND + + if (argc) + usage(); + + memset(&act, 0, sizeof(act)); + act.sa_handler = terminate; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + act.sa_flags |= SA_RESTART; + sigaction(SIGUSR1, &act, NULL); + + if (!sflag && !(dpy = XOpenDisplay(NULL))) + die("XOpenDisplay: Failed to open display"); + + do { + if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) + die("clock_gettime:"); + + status[0] = '\0'; + for (i = len = 0; i < LEN(args); i++) { + if (!(res = args[i].func(args[i].args))) + res = unknown_str; + + if ((ret = esnprintf(status + len, sizeof(status) - len, + args[i].fmt, res)) < 0) + break; + + len += ret; + } + + if (sflag) { + puts(status); + fflush(stdout); + if (ferror(stdout)) + die("puts:"); + } else { + if (XStoreName(dpy, DefaultRootWindow(dpy), status) < 0) + die("XStoreName: Allocation failed"); + XFlush(dpy); + } + + if (!done) { + if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0) + die("clock_gettime:"); + difftimespec(&diff, ¤t, &start); + + intspec.tv_sec = interval / 1000; + intspec.tv_nsec = (interval % 1000) * 1E6; + difftimespec(&wait, &intspec, &diff); + + if (wait.tv_sec >= 0 && + nanosleep(&wait, NULL) < 0 && + errno != EINTR) + die("nanosleep:"); + } + } while (!done); + + if (!sflag) { + XStoreName(dpy, DefaultRootWindow(dpy), NULL); + if (XCloseDisplay(dpy) < 0) + die("XCloseDisplay: Failed to close display"); + } + + return 0; +} diff --git a/slstatus/slstatus.h b/slstatus/slstatus.h new file mode 100644 index 0000000..8ef5874 --- /dev/null +++ b/slstatus/slstatus.h @@ -0,0 +1,84 @@ +/* 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); + +/* 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/slstatus.o b/slstatus/slstatus.o new file mode 100644 index 0000000..e8cd9a3 Binary files /dev/null and b/slstatus/slstatus.o differ diff --git a/slstatus/util.c b/slstatus/util.c new file mode 100644 index 0000000..bca9b2e --- /dev/null +++ b/slstatus/util.c @@ -0,0 +1,141 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include + +#include "util.h" + +char *argv0; + +static void +verr(const char *fmt, va_list ap) +{ + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } +} + +void +warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verr(fmt, ap); + va_end(ap); +} + +void +die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verr(fmt, ap); + va_end(ap); + + exit(1); +} + +static int +evsnprintf(char *str, size_t size, const char *fmt, va_list ap) +{ + int ret; + + ret = vsnprintf(str, size, fmt, ap); + + if (ret < 0) { + warn("vsnprintf:"); + return -1; + } else if ((size_t)ret >= size) { + warn("vsnprintf: Output truncated"); + return -1; + } + + return ret; +} + +int +esnprintf(char *str, size_t size, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = evsnprintf(str, size, fmt, ap); + va_end(ap); + + return ret; +} + +const char * +bprintf(const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = evsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + return (ret < 0) ? NULL : buf; +} + +const char * +fmt_human(uintmax_t num, int base) +{ + double scaled; + size_t i, prefixlen; + const char **prefix; + const char *prefix_1000[] = { "", "k", "M", "G", "T", "P", "E", "Z", + "Y" }; + const char *prefix_1024[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", + "Zi", "Yi" }; + + switch (base) { + case 1000: + prefix = prefix_1000; + prefixlen = LEN(prefix_1000); + break; + case 1024: + prefix = prefix_1024; + prefixlen = LEN(prefix_1024); + break; + default: + warn("fmt_human: Invalid base"); + return NULL; + } + + scaled = num; + for (i = 0; i < prefixlen && scaled >= base; i++) + scaled /= base; + + return bprintf("%.1f %s", scaled, prefix[i]); +} + +int +pscanf(const char *path, const char *fmt, ...) +{ + FILE *fp; + va_list ap; + int n; + + if (!(fp = fopen(path, "r"))) { + warn("fopen '%s':", path); + return -1; + } + va_start(ap, fmt); + n = vfscanf(fp, fmt, ap); + va_end(ap); + fclose(fp); + + return (n == EOF) ? -1 : n; +} diff --git a/slstatus/util.h b/slstatus/util.h new file mode 100644 index 0000000..cf4b027 --- /dev/null +++ b/slstatus/util.h @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ +#include + +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/slstatus/util.o b/slstatus/util.o new file mode 100644 index 0000000..361d63f Binary files /dev/null and b/slstatus/util.o differ