diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ecc1c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +slstatus +*.o +*.orig +*.rej diff --git a/Makefile b/Makefile index 7a18274..86b05fa 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ include config.mk REQ = util COM =\ + components/backlight\ components/battery\ components/cat\ components/cpu\ @@ -14,9 +15,8 @@ COM =\ components/entropy\ components/hostname\ components/ip\ + components/kanji\ components/kernel_release\ - components/keyboard_indicators\ - components/keymap\ components/load_avg\ components/netspeeds\ components/num_files\ diff --git a/components/backlight.c b/components/backlight.c new file mode 100644 index 0000000..46240f6 --- /dev/null +++ b/components/backlight.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ + +#include + +#include "../util.h" + +#if defined(__linux__) + #include + + #define BRIGHTNESS_MAX "/sys/class/backlight/%s/max_brightness" + #define BRIGHTNESS_CUR "/sys/class/backlight/%s/brightness" + + const char * + backlight_perc(const char *card) + { + char path[PATH_MAX]; + int max, cur; + + if (esnprintf(path, sizeof (path), BRIGHTNESS_MAX, card) < 0 || + pscanf(path, "%d", &max) != 1) { + return NULL; + } + + if (esnprintf(path, sizeof (path), BRIGHTNESS_CUR, card) < 0 || + pscanf(path, "%d", &cur) != 1) { + return NULL; + } + + if (max == 0) { + return NULL; + } + + return bprintf("%d%%", cur * 100 / max); + } +#elif defined(__OpenBSD__) + #include + #include + #include + #include + + const char * + backlight_perc(const char *unused) + { + int fd, err; + struct wsdisplay_param wsd_param = { + .param = WSDISPLAYIO_PARAM_BRIGHTNESS + }; + + if ((fd = open("/dev/ttyC0", O_RDONLY)) < 0) { + warn("could not open /dev/ttyC0"); + return NULL; + } + if ((err = ioctl(fd, WSDISPLAYIO_GETPARAM, &wsd_param)) < 0) { + warn("ioctl 'WSDISPLAYIO_GETPARAM' failed"); + return NULL; + } + return bprintf("%d", wsd_param.curval * 100 / wsd_param.max); + } +#elif defined(__FreeBSD__) + #include + #include + #include + #include + + #define FBSD_BACKLIGHT_DEV "/dev/backlight/%s" + + const char * + backlight_perc(const char *card) + { + char buf[256]; + struct backlight_props props; + int fd; + + snprintf(buf, sizeof(buf), FBSD_BACKLIGHT_DEV, card); + if ((fd = open(buf, O_RDWR)) == -1) { + warn("could not open %s", card); + return NULL; + } + if (ioctl(fd, BACKLIGHTGETSTATUS, &props) == -1){ + warn("Cannot query the backlight device"); + return NULL; + } + + return bprintf("%d", props.brightness); + } +#endif diff --git a/components/battery.c b/components/battery.c index 1c753f9..7bfd874 100644 --- a/components/battery.c +++ b/components/battery.c @@ -1,10 +1,29 @@ /* See LICENSE file for copyright and license details. */ #include +#include #include #include "../slstatus.h" #include "../util.h" +const char * +battery_icon(const char *bat) +{ + unsigned long ul_perc; + const char *perc, *state; + static const char *icons[][11] = { + { "󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹" }, + { "󰢟", "󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅" }, + }; + + if (!(perc = battery_perc(bat)) || !(state = battery_state(bat))) + return NULL; + + ul_perc = strtoul(perc, NULL, 10); + + return bprintf("%s %d", icons[state[0] == '+'][ul_perc / 10], ul_perc); +} + #if defined(__linux__) /* * https://www.kernel.org/doc/html/latest/power/power_supply_class.html diff --git a/components/kanji.c b/components/kanji.c new file mode 100644 index 0000000..2dbcd9a --- /dev/null +++ b/components/kanji.c @@ -0,0 +1,30 @@ +/* Written by Madison Lynch */ +#include + +static const char *symbols[] = { + "日", // Sunday + "月", // Monday + "火", // Tuesday + "水", // Wednesday + "木", // Thursday + "金", // Friday + "土" // Saturday +}; + +/** +* Returns the appropriate Japanese Kanji character correlating with the current +* day of the week. +* +* @param unused (NULL) +* @return the appropriate Kanji character (char) +* @author Madison Lynch +*/ +const char * +kanji(const char *unused) { + const time_t current_time = time(NULL); + const unsigned int weekday = localtime( + ¤t_time + )->tm_wday; + + return (weekday < sizeof(symbols) / sizeof(char *)) ? symbols[weekday] : "?"; +} diff --git a/components/keyboard_indicators.c b/components/keyboard_indicators.c deleted file mode 100644 index 5f62bb7..0000000 --- a/components/keyboard_indicators.c +++ /dev/null @@ -1,50 +0,0 @@ -/* 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/components/keymap.c b/components/keymap.c deleted file mode 100644 index f8a2a47..0000000 --- a/components/keymap.c +++ /dev/null @@ -1,86 +0,0 @@ -/* 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/components/volume.c b/components/volume.c index 6cec556..9eb3c64 100644 --- a/components/volume.c +++ b/components/volume.c @@ -1,6 +1,7 @@ /* See LICENSE file for copyright and license details. */ #include #include +#include #include #include #include @@ -8,6 +9,22 @@ #include "../slstatus.h" #include "../util.h" +const char * +vol_icon(const char *arg) +{ + char *p; + const char *perc; + static const char *icons[] = { "󰕿", "󰖀", "󰕾" }; + unsigned long ul_perc; + + if (!(perc = vol_perc(arg))) + return NULL; + p = strrchr(perc, ' '); + ul_perc = strtoul(p ? p + 1 : perc, NULL, 10); + + return bprintf("%s %d", p ? "󰝟" : icons[ul_perc / 34], ul_perc); +} + #if defined(__OpenBSD__) | defined(__FreeBSD__) #include #include @@ -182,6 +199,68 @@ return bprintf("%d", value); } +#elif defined(ALSA) + #include + + static const char *devname = "default"; + const char * + vol_perc(const char *mixname) + { + snd_mixer_t *mixer = NULL; + snd_mixer_selem_id_t *mixid = NULL; + snd_mixer_elem_t *elem = NULL; + long min = 0, max = 0, volume = -1; + int err, sw1, sw2; + + if ((err = snd_mixer_open(&mixer, 0))) { + warn("snd_mixer_open: %d", err); + return NULL; + } + if ((err = snd_mixer_attach(mixer, devname))) { + warn("snd_mixer_attach(mixer, \"%s\"): %d", devname, err); + goto cleanup; + } + if ((err = snd_mixer_selem_register(mixer, NULL, NULL))) { + warn("snd_mixer_selem_register(mixer, NULL, NULL): %d", err); + goto cleanup; + } + if ((err = snd_mixer_load(mixer))) { + warn("snd_mixer_load(mixer): %d", err); + goto cleanup; + } + + snd_mixer_selem_id_alloca(&mixid); + snd_mixer_selem_id_set_name(mixid, mixname); + snd_mixer_selem_id_set_index(mixid, 0); + + elem = snd_mixer_find_selem(mixer, mixid); + if (!elem) { + warn("snd_mixer_find_selem(mixer, \"%s\") == NULL", mixname); + goto cleanup; + } + + if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max))) { + warn("snd_mixer_selem_get_playback_volume_range(): %d", err); + goto cleanup; + } + if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &volume))) { + warn("snd_mixer_selem_get_playback_volume(): %d", err); + } + if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &sw1))) { + warn("snd_mixer_selem_get_playback_switch(): %d", err); + } + if ((err = snd_mixer_selem_get_playback_switch(elem, 1, &sw2))) { + warn("snd_mixer_selem_get_playback_switch(): %d", err); + } + + cleanup: + snd_mixer_free(mixer); + snd_mixer_detach(mixer, devname); + snd_mixer_close(mixer); + + return volume == -1 ? NULL : bprintf("%s%.0f", + !(sw1 || sw2) ? "muted " : "", (volume-min)*100./(max-min)); + } #else #include diff --git a/config.def.h b/config.def.h index d805331..f5e2e8a 100644 --- a/config.def.h +++ b/config.def.h @@ -6,12 +6,17 @@ 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 +/* maximum command output length */ +#define CMDLEN 128 /* * function description argument (example) * + * backlight_perc backlight percentage device name + * (intel_backlight, numbered on FreeBSD) + * NULL on OpenBSD + * battery_icon battery_perc with an icon battery name (BAT0) + * NULL on OpenBSD/FreeBSD * battery_perc battery percentage battery name (BAT0) * NULL on OpenBSD/FreeBSD * battery_remaining battery remaining HH:MM battery name (BAT0) @@ -31,6 +36,7 @@ static const char unknown_str[] = "n/a"; * hostname hostname NULL * ipv4 IPv4 address interface name (eth0) * ipv6 IPv6 address interface name (eth0) + * kanji current day of week kanji NULL * kernel_release `uname -r` NULL * keyboard_indicators caps/num lock indicators format string (c?n?) * see keyboard_indicators.c @@ -58,12 +64,17 @@ static const char unknown_str[] = "n/a"; * uid UID of current user NULL * uptime system uptime NULL * username username of current user NULL + * vol_icon vol_perc with an icon mixer file (/dev/mixer) + * NULL on OpenBSD/FreeBSD * 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 */ - { datetime, "%s", "%F %T" }, + /* function format argument turn signal */ + { datetime, "%s", "%F %T", 1, -1 }, }; + +/* maximum output string length */ +#define MAXLEN CMDLEN * LEN(args) diff --git a/config.h b/config.h new file mode 100644 index 0000000..edb54ff --- /dev/null +++ b/config.h @@ -0,0 +1,89 @@ +/* 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 command output length */ +#define CMDLEN 128 + +/* + * function description argument (example) + * + * backlight_perc backlight percentage device name + * (intel_backlight, numbered on FreeBSD) + * NULL on OpenBSD + * battery_icon battery_perc with an icon battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_perc battery percentage battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_remaining battery remaining HH:MM battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * battery_state battery charging state battery name (BAT0) + * NULL on OpenBSD/FreeBSD + * cat read arbitrary file path + * cpu_freq cpu frequency in MHz NULL + * cpu_perc cpu usage in percent NULL + * datetime date and time format string (%F %T) + * disk_free free disk space in GB mountpoint path (/) + * disk_perc disk usage in percent mountpoint path (/) + * disk_total total disk space in GB mountpoint path (/) + * disk_used used disk space in GB mountpoint path (/) + * entropy available entropy NULL + * gid GID of current user NULL + * hostname hostname NULL + * ipv4 IPv4 address interface name (eth0) + * ipv6 IPv6 address interface name (eth0) + * kanji current day of week kanji NULL + * kernel_release `uname -r` NULL + * keyboard_indicators caps/num lock indicators format string (c?n?) + * see keyboard_indicators.c + * keymap layout (variant) of current NULL + * keymap + * load_avg load average NULL + * netspeed_rx receive network speed interface name (wlan0) + * netspeed_tx transfer network speed interface name (wlan0) + * num_files number of files in a directory path + * (/home/foo/Inbox/cur) + * ram_free free memory in GB NULL + * ram_perc memory usage in percent NULL + * ram_total total memory size in GB NULL + * ram_used used memory in GB NULL + * run_command custom shell command command (echo foo) + * swap_free free swap in GB NULL + * swap_perc swap usage in percent NULL + * swap_total total swap size in GB NULL + * swap_used used swap in GB NULL + * temp temperature in degree celsius sensor file + * (/sys/class/thermal/...) + * NULL on OpenBSD + * thermal zone on FreeBSD + * (tz0, tz1, etc.) + * uid UID of current user NULL + * uptime system uptime NULL + * username username of current user NULL + * vol_icon vol_perc with an icon mixer file (/dev/mixer) + * NULL on OpenBSD/FreeBSD + * 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 turn signal */ + { cpu_perc, " [  %s% ]", NULL, 1, -1, }, + { ram_perc, "[  %s% ]", NULL, 1, -1, }, + { backlight_perc, "[ 󰃠 %s ]", "intel_backlight", 1, 4, }, + { run_command, "[ 󰍬 %s ]", "getvol -m", 1, 3, }, + // { vol_perc, "[ 󰍬 %s ]", "Capture", 1, 3, }, + { vol_icon, "[ %s%% ]", "Master", 1, 2, }, + { battery_icon, "[ %s%% ]", "BAT1", 1, -1, }, + { datetime, "[ 󰥔 %s ]", "%I:%M %p", 1, -1, }, + { datetime, "[ 󰸗 %s ]", "%b %d", 1, -1, }, +}; + +/* maximum output string length */ +#define MAXLEN CMDLEN * LEN(args) diff --git a/config.mk b/config.mk index 07af883..7f74ae6 100644 --- a/config.mk +++ b/config.mk @@ -7,16 +7,13 @@ VERSION = 1.0 PREFIX = /usr/local MANPREFIX = $(PREFIX)/share/man -X11INC = /usr/X11R6/include -X11LIB = /usr/X11R6/lib - # flags -CPPFLAGS = -I$(X11INC) -D_DEFAULT_SOURCE -DVERSION=\"${VERSION}\" +CPPFLAGS = -D_DEFAULT_SOURCE -DALSA -DVERSION=\"${VERSION}\" CFLAGS = -std=c99 -pedantic -Wall -Wextra -Wno-unused-parameter -Os -LDFLAGS = -L$(X11LIB) -s +LDFLAGS = -s # OpenBSD: add -lsndio # FreeBSD: add -lkvm -lsndio -LDLIBS = -lX11 +LDLIBS = -lasound # compiler and linker CC = cc diff --git a/slstatus.c b/slstatus.c index fd31313..2f38873 100644 --- a/slstatus.c +++ b/slstatus.c @@ -5,7 +5,6 @@ #include #include #include -#include #include "arg.h" #include "slstatus.h" @@ -15,20 +14,17 @@ struct arg { const char *(*func)(const char *); const char *fmt; const char *args; + unsigned int turn; + int signal; }; char buf[1024]; -static volatile sig_atomic_t done; -static Display *dpy; +static volatile sig_atomic_t done, upsigno; #include "config.h" +#define MAXLEN CMDLEN * LEN(args) -static void -terminate(const int signo) -{ - if (signo != SIGUSR1) - done = 1; -} +static char statuses[LEN(args)][CMDLEN] = {0}; static void difftimespec(struct timespec *res, struct timespec *a, struct timespec *b) @@ -44,26 +40,58 @@ usage(void) die("usage: %s [-v] [-s] [-1]", argv0); } +static void +printstatus(unsigned int iter) +{ + size_t i; + char status[MAXLEN]; + const char *res; + + for (i = 0; i < LEN(args); i++) { + if (!((!iter && !upsigno) || upsigno == SIGUSR1 || + (!upsigno && args[i].turn > 0 && !(iter % args[i].turn)) || + (args[i].signal >= 0 && upsigno - SIGRTMIN == args[i].signal))) + continue; + + if (!(res = args[i].func(args[i].args))) + res = unknown_str; + + if (esnprintf(statuses[i], sizeof(statuses[i]), args[i].fmt, res) < 0) + break; + } + + status[0] = '\0'; + for (i = 0; i < LEN(args); i++) + strcat(status, statuses[i]); + status[strlen(status)] = '\0'; + + puts(status); + fflush(stdout); + if (ferror(stdout)) + die("puts:"); +} + + +static void +sighandler(const int signo) +{ + if ((signo <= SIGRTMAX && signo >= SIGRTMIN) || signo == SIGUSR1) + upsigno = signo; + else + done = 1; +} + 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; + unsigned int iter = 0; + int i, ret; - sflag = 0; ARGBEGIN { case 'v': die("slstatus-"VERSION); - case '1': - done = 1; - /* FALLTHROUGH */ - case 's': - sflag = 1; - break; default: usage(); } ARGEND @@ -72,41 +100,18 @@ main(int argc, char *argv[]) usage(); memset(&act, 0, sizeof(act)); - act.sa_handler = terminate; + act.sa_handler = sighandler; 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"); + for (i = SIGRTMIN; i <= SIGRTMAX; i++) + sigaction(i, &act, NULL); 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); - } + printstatus(iter++); if (!done) { if (clock_gettime(CLOCK_MONOTONIC, ¤t) < 0) @@ -117,18 +122,16 @@ main(int argc, char *argv[]) intspec.tv_nsec = (interval % 1000) * 1E6; difftimespec(&wait, &intspec, &diff); - if (wait.tv_sec >= 0 && - nanosleep(&wait, NULL) < 0 && - errno != EINTR) - die("nanosleep:"); + while(wait.tv_sec >= 0 && + (ret = nanosleep(&wait, &wait)) < 0 && + errno == EINTR && !done) { + printstatus(0); + errno = upsigno = 0; + } + if (ret < 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 index 8ef5874..58174e6 100644 --- a/slstatus.h +++ b/slstatus.h @@ -1,9 +1,13 @@ /* See LICENSE file for copyright and license details. */ +/* backlight */ +const char *backlight_perc(const char *); + /* battery */ const char *battery_perc(const char *); const char *battery_remaining(const char *); const char *battery_state(const char *); +const char *battery_icon(const char *); /* cat */ const char *cat(const char *path); @@ -31,6 +35,9 @@ const char *hostname(const char *unused); const char *ipv4(const char *interface); const char *ipv6(const char *interface); +/* kanji */ +const char *kanji(const char *unused); + /* kernel_release */ const char *kernel_release(const char *unused); @@ -77,6 +84,7 @@ const char *uid(const char *unused); const char *username(const char *unused); /* volume */ +const char *vol_icon(const char *card); const char *vol_perc(const char *card); /* wifi */