summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile53
-rw-r--r--arg.h33
-rw-r--r--components/datetime.c20
-rw-r--r--components/netspeeds.c74
-rw-r--r--components/ram.c89
-rw-r--r--components/run_command.c31
-rw-r--r--components/volume4
-rw-r--r--config.h21
-rw-r--r--config.mk22
-rw-r--r--slstatus.145
-rw-r--r--slstatus.c132
-rw-r--r--slstatus.h16
-rw-r--r--util.c141
-rw-r--r--util.h16
14 files changed, 697 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ef103c5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+# See LICENSE file for copyright and license details
+# slstatus - suckless status monitor
+.POSIX:
+
+include config.mk
+
+REQ = util
+COM =\
+ components/datetime\
+ components/netspeeds\
+ components/ram\
+ components/run_command\
+
+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)
+
+dist:
+ rm -rf "slstatus-$(VERSION)"
+ mkdir -p "slstatus-$(VERSION)/components"
+ cp -R LICENSE Makefile README config.mk config.def.h \
+ arg.h slstatus.c $(COM:=.c) $(REQ:=.c) $(REQ:=.h) \
+ slstatus.1 "slstatus-$(VERSION)"
+ 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"
+ cp -f components/volume "$(DESTDIR)$(PREFIX)/bin"
+ chmod 775 "$(DESTDIR)$(PREFIX)/bin/volume"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)/bin/slstatus"
+ rm -f "$(DESTDIR)$(MANPREFIX)/man1/slstatus.1"
+ rm -f "$(DESTDIR)$(PREFIX)/bin/volume"
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..5f1f408
--- /dev/null
+++ b/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/components/datetime.c b/components/datetime.c
new file mode 100644
index 0000000..5b10daf
--- /dev/null
+++ b/components/datetime.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <time.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+datetime(const char *fmt)
+{
+ time_t t;
+
+ t = time(NULL);
+ if (!strftime(buf, sizeof(buf), fmt, localtime(&t))) {
+ warn("strftime: Result string exceeds buffer size");
+ return NULL;
+ }
+
+ return buf;
+}
diff --git a/components/netspeeds.c b/components/netspeeds.c
new file mode 100644
index 0000000..b61b4c9
--- /dev/null
+++ b/components/netspeeds.c
@@ -0,0 +1,74 @@
+/* See LICENSE file for copyright and license details. */
+#include <limits.h>
+#include <stdio.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ #include <stdint.h>
+
+ #define NET_RX_BYTES "/sys/class/net/%s/statistics/rx_bytes"
+
+ 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);
+ }
+
+#elif defined(__OpenBSD__) | defined(__FreeBSD__)
+ #include <ifaddrs.h>
+ #include <net/if.h>
+ #include <string.h>
+ #include <sys/types.h>
+ #include <sys/socket.h>
+
+ const char *
+ netspeed_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/components/ram.c b/components/ram.c
new file mode 100644
index 0000000..da2021f
--- /dev/null
+++ b/components/ram.c
@@ -0,0 +1,89 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+#if defined(__linux__)
+ #include <stdint.h>
+
+ const char *
+ ram_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);
+ }
+#elif defined(__OpenBSD__)
+ #include <stdlib.h>
+ #include <sys/sysctl.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+
+ #define LOG1024 10
+ #define pagetok(size, pageshift) (size_t)(size << (pageshift - LOG1024))
+
+ inline int
+ load_uvmexp(struct uvmexp *uvmexp)
+ {
+ int uvmexp_mib[] = {CTL_VM, VM_UVMEXP};
+ size_t size;
+
+ size = sizeof(*uvmexp);
+
+ if (sysctl(uvmexp_mib, 2, uvmexp, &size, NULL, 0) >= 0)
+ return 1;
+
+ return 0;
+ }
+
+ const char *
+ ram_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);
+ }
+#elif defined(__FreeBSD__)
+ #include <sys/sysctl.h>
+ #include <sys/vmmeter.h>
+ #include <unistd.h>
+ #include <vm/vm_param.h>
+
+ 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);
+ }
+#endif
diff --git a/components/run_command.c b/components/run_command.c
new file mode 100644
index 0000000..93bf6da
--- /dev/null
+++ b/components/run_command.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#include "../slstatus.h"
+#include "../util.h"
+
+const char *
+run_command(const char *cmd)
+{
+ char *p;
+ FILE *fp;
+
+ if (!(fp = popen(cmd, "r"))) {
+ warn("popen '%s':", cmd);
+ return NULL;
+ }
+
+ p = fgets(buf, sizeof(buf) - 1, fp);
+ if (pclose(fp) < 0) {
+ warn("pclose '%s':", cmd);
+ return NULL;
+ }
+ if (!p)
+ return NULL;
+
+ if ((p = strrchr(buf, '\n')))
+ p[0] = '\0';
+
+ return buf[0] ? buf : NULL;
+}
diff --git a/components/volume b/components/volume
new file mode 100644
index 0000000..3249774
--- /dev/null
+++ b/components/volume
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+vol="$(pactl list sinks | grep '^[[:space:]]Volume:' | head -n $(( $SINK + 1 )) | tail -n 1 | sed -e 's,.* \([0-9][0-9]*\)%.*,\1,')"
+[ "$vol" = "0" ] && echo "ﱝ muted" || echo "墳 ${vol}%"
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..7ec4b55
--- /dev/null
+++ b/config.h
@@ -0,0 +1,21 @@
+/* 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
+
+static const struct arg args[] = {
+ /* function format argument */
+
+ /* WIFI { netspeed_rx, "%sB/s", "wlp3s0" }, */
+
+ { netspeed_rx, "[ %s B/s ]", "enp4s0" },
+ { run_command, "[ %s ]", "volume" },
+ { ram_perc, "[  %s%% ]", NULL },
+ { datetime, "  %s", "%a %b %d %r" },
+};
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..ead1859
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,22 @@
+# slstatus version
+VERSION = 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
+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.1 b/slstatus.1
new file mode 100644
index 0000000..bea2a8c
--- /dev/null
+++ b/slstatus.1
@@ -0,0 +1,45 @@
+.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 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.c b/slstatus.c
new file mode 100644
index 0000000..cb54f29
--- /dev/null
+++ b/slstatus.c
@@ -0,0 +1,132 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <X11/Xlib.h>
+
+#include "arg.h"
+#include "slstatus.h"
+#include "util.h"
+
+struct arg {
+ const char *(*func)(const char *);
+ const char *fmt;
+ const char *args;
+};
+
+char buf[1024];
+static volatile sig_atomic_t done;
+static Display *dpy;
+
+#include "config.h"
+
+static void
+terminate(const int signo)
+{
+ if (signo != SIGUSR1)
+ done = 1;
+}
+
+static void
+difftimespec(struct timespec *res, struct timespec *a, struct timespec *b)
+{
+ res->tv_sec = a->tv_sec - b->tv_sec - (a->tv_nsec < b->tv_nsec);
+ res->tv_nsec = a->tv_nsec - b->tv_nsec +
+ (a->tv_nsec < b->tv_nsec) * 1E9;
+}
+
+static void
+usage(void)
+{
+ die("usage: %s [-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 '1':
+ done = 1;
+ /* FALLTHROUGH */
+ case 's':
+ sflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if (argc)
+ usage();
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = terminate;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ act.sa_flags |= SA_RESTART;
+ sigaction(SIGUSR1, &act, NULL);
+
+ if (!sflag && !(dpy = XOpenDisplay(NULL)))
+ die("XOpenDisplay: Failed to open display");
+
+ do {
+ if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
+ die("clock_gettime:");
+
+ status[0] = '\0';
+ for (i = len = 0; i < LEN(args); i++) {
+ if (!(res = args[i].func(args[i].args)))
+ res = unknown_str;
+
+ if ((ret = esnprintf(status + len, sizeof(status) - len,
+ args[i].fmt, res)) < 0)
+ break;
+
+ len += ret;
+ }
+
+ if (sflag) {
+ puts(status);
+ fflush(stdout);
+ if (ferror(stdout))
+ die("puts:");
+ } else {
+ if (XStoreName(dpy, DefaultRootWindow(dpy), status) < 0)
+ die("XStoreName: Allocation failed");
+ XFlush(dpy);
+ }
+
+ if (!done) {
+ if (clock_gettime(CLOCK_MONOTONIC, &current) < 0)
+ die("clock_gettime:");
+ difftimespec(&diff, &current, &start);
+
+ intspec.tv_sec = interval / 1000;
+ intspec.tv_nsec = (interval % 1000) * 1E6;
+ difftimespec(&wait, &intspec, &diff);
+
+ if (wait.tv_sec >= 0 &&
+ nanosleep(&wait, NULL) < 0 &&
+ errno != EINTR)
+ die("nanosleep:");
+ }
+ } while (!done);
+
+ if (!sflag) {
+ XStoreName(dpy, DefaultRootWindow(dpy), NULL);
+ if (XCloseDisplay(dpy) < 0)
+ die("XCloseDisplay: Failed to close display");
+ }
+
+ return 0;
+}
diff --git a/slstatus.h b/slstatus.h
new file mode 100644
index 0000000..aa3f7a0
--- /dev/null
+++ b/slstatus.h
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+
+/* datetime */
+const char *datetime(const char *fmt);
+
+/* netspeeds */
+const char *netspeed_rx(const char *interface);
+
+/* num_files */
+const char *num_files(const char *path);
+
+/* ram */
+const char *ram_perc(const char *unused);
+
+/* run_command */
+const char *run_command(const char *cmd);
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..bca9b2e
--- /dev/null
+++ b/util.c
@@ -0,0 +1,141 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+char *argv0;
+
+static void
+verr(const char *fmt, va_list ap)
+{
+ vfprintf(stderr, fmt, ap);
+
+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+}
+
+void
+warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ verr(fmt, ap);
+ va_end(ap);
+}
+
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ verr(fmt, ap);
+ va_end(ap);
+
+ exit(1);
+}
+
+static int
+evsnprintf(char *str, size_t size, const char *fmt, va_list ap)
+{
+ int ret;
+
+ ret = vsnprintf(str, size, fmt, ap);
+
+ if (ret < 0) {
+ warn("vsnprintf:");
+ return -1;
+ } else if ((size_t)ret >= size) {
+ warn("vsnprintf: Output truncated");
+ return -1;
+ }
+
+ return ret;
+}
+
+int
+esnprintf(char *str, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = evsnprintf(str, size, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+const char *
+bprintf(const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = evsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ return (ret < 0) ? NULL : buf;
+}
+
+const char *
+fmt_human(uintmax_t num, int base)
+{
+ double scaled;
+ size_t i, prefixlen;
+ const char **prefix;
+ const char *prefix_1000[] = { "", "k", "M", "G", "T", "P", "E", "Z",
+ "Y" };
+ const char *prefix_1024[] = { "", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei",
+ "Zi", "Yi" };
+
+ switch (base) {
+ case 1000:
+ prefix = prefix_1000;
+ prefixlen = LEN(prefix_1000);
+ break;
+ case 1024:
+ prefix = prefix_1024;
+ prefixlen = LEN(prefix_1024);
+ break;
+ default:
+ warn("fmt_human: Invalid base");
+ return NULL;
+ }
+
+ scaled = num;
+ for (i = 0; i < prefixlen && scaled >= base; i++)
+ scaled /= base;
+
+ return bprintf("%.1f %s", scaled, prefix[i]);
+}
+
+int
+pscanf(const char *path, const char *fmt, ...)
+{
+ FILE *fp;
+ va_list ap;
+ int n;
+
+ if (!(fp = fopen(path, "r"))) {
+ warn("fopen '%s':", path);
+ return -1;
+ }
+ va_start(ap, fmt);
+ n = vfscanf(fp, fmt, ap);
+ va_end(ap);
+ fclose(fp);
+
+ return (n == EOF) ? -1 : n;
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..cf4b027
--- /dev/null
+++ b/util.h
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdint.h>
+
+extern char buf[1024];
+
+#define LEN(x) (sizeof(x) / sizeof((x)[0]))
+
+extern char *argv0;
+
+void warn(const char *, ...);
+void die(const char *, ...);
+
+int esnprintf(char *str, size_t size, const char *fmt, ...);
+const char *bprintf(const char *fmt, ...);
+const char *fmt_human(uintmax_t num, int base);
+int pscanf(const char *path, const char *fmt, ...);