Compare commits

...

10 Commits

Author SHA1 Message Date
lucy
774894486f add configs 2026-02-26 11:58:38 +01:00
drkhsh
614c275b42 license: bump copyright year 2026-02-12 22:54:11 +01:00
drkhsh
98769dfed7 fix buffer overflow in battery state parsing
"Not charging" is 12 characters; %12[a-zA-Z ] reads up to 12 chars
plus a NUL terminator (13 bytes) into a 12-byte buffer. Increase
state buffer to 13 in both battery_state and battery_remaining.
2026-02-12 22:52:21 +01:00
drkhsh
4f61bbbd8e wifi: always retry ioctl for ifindex
ifindex() caches the result of ioctl(SIOCGIFINDEX) and never
retries it, since the interface name strcmp succeeds after the
first call. if the interface does not exist when slstatus starts
(e.g. iwd with default config destroys/recreates interfaces
depending on service state), the ioctl fails and the stale
result is returned on all subsequent calls.

based on a patch by ryhpez, with input from Taha Medhous
2026-02-12 22:46:02 +01:00
Chris Billington
6fa36bad9f volume: avoid NULL dereference in onval() on sndio device switch 2026-02-12 22:45:28 +01:00
drkhsh
8723e8b8c6 more concise memory calculation on Linux
more flexible parsing for /proc/meminfo to take shared and reclaimable
memory into account. this matches the output with free(1).

additionally this could fix some corner cases, as the order of fields in
/proc/meminfo is not strictly defined.

slstatus:
percent 81% free 2.5 Gi total 23.4 Gi used 19.0 Gi

free(1):
               total        used        free      shared  buff/cache   available
Mem:            23Gi        19Gi       2.5Gi       1.3Gi       3.2Gi       3.6Gi
2025-07-24 22:41:25 +02:00
drkhsh
6eb7887853 fix name confusion in LICENSE
thanks for reporting, dsp
2025-07-16 18:21:43 +02:00
drkhsh
f5c4e634cd bump version to 1.1 2025-04-30 04:00:57 +02:00
drkhsh
3db023f13b refactor wifi on linux 2025-04-30 04:00:53 +02:00
drkhsh
a0f960c16f update LICENSE and README 2025-04-30 03:13:41 +02:00
37 changed files with 199 additions and 58 deletions

View File

@ -1,6 +1,6 @@
ISC License
Copyright 2016-2022 Aaron Marcher <me@drkhsh.at>
Copyright 2016-2026 Aaron Marcher <me@drkhsh.at>
Copyright 2016 Roy Freytag <rfreytag@hs-mittweida.de>
Copyright 2016 Vincent Loupmon <vincentloupmon@gmail.com>
@ -21,7 +21,7 @@ Copyright 2018 Ian Remmler <ian@remmler.org>
Copyright 2016-2019 Joerg Jung <jung@openbsd.org>
Copyright 2019 Ryan Kes <alrayyes@gmail.com>
Copyright 2019 Cem Keylan <cem@ckyln.com>
Copyright 2019 Dimitris Papastamos <dsp@2f30.org>
Copyright 2019 Spiros Thanasoulas <dsp@2f30.org>
Copyright 2019-2022 Ingo Feinerer <feinerer@logic.at>
Copyright 2020 Alexandre Ratchov <alex@caoua.org>
Copyright 2020 Mart Lubbers <mart@martlubbers.net>
@ -29,6 +29,9 @@ Copyright 2020 Daniel Moch <daniel@danielmoch.com>
Copyright 2022 Nickolas Raymond Kaczynski <nrk@disroot.org>
Copyright 2022 Patrick Iacob <iacobp@oregonstate.edu>
Copyright 2021-2022 Steven Ward <planet36@gmail.com>
Copyright 2025 Joakim Sindholt <opensource@zhasha.com>
Copyright 2025 Al <eirann@disroot.org>
Copyright 2025 sewn <sewn@disroot.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

2
README
View File

@ -18,7 +18,7 @@ Features
- Available entropy
- Username/GID/UID
- Hostname
- IP address (IPv4 and IPv6)
- IP address (IPv4 and IPv6), interface status
- Kernel version
- Keyboard indicators
- Keymap

View File

@ -62,7 +62,7 @@
{ "Not charging", "o" },
};
size_t i;
char path[PATH_MAX], state[12];
char path[PATH_MAX], state[13];
if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
return NULL;
@ -81,7 +81,7 @@
{
uintmax_t charge_now, current_now, m, h;
double timeleft;
char path[PATH_MAX], state[12];
char path[PATH_MAX], state[13];
if (esnprintf(path, sizeof(path), POWER_SUPPLY_STATUS, bat) < 0)
return NULL;

BIN
components/battery.o Normal file

Binary file not shown.

BIN
components/cat.o Normal file

Binary file not shown.

BIN
components/cpu.o Normal file

Binary file not shown.

BIN
components/datetime.o Normal file

Binary file not shown.

BIN
components/disk.o Normal file

Binary file not shown.

BIN
components/entropy.o Normal file

Binary file not shown.

BIN
components/hostname.o Normal file

Binary file not shown.

BIN
components/ip.o Normal file

Binary file not shown.

BIN
components/kernel_release.o Normal file

Binary file not shown.

Binary file not shown.

BIN
components/keymap.o Normal file

Binary file not shown.

BIN
components/load_avg.o Normal file

Binary file not shown.

BIN
components/netspeeds.o Normal file

Binary file not shown.

BIN
components/num_files.o Normal file

Binary file not shown.

View File

@ -11,36 +11,45 @@
ram_free(const char *unused)
{
uintmax_t free;
FILE *fp;
if (pscanf("/proc/meminfo",
"MemTotal: %ju kB\n"
"MemFree: %ju kB\n"
"MemAvailable: %ju kB\n",
&free, &free, &free) != 3)
if (!(fp = fopen("/proc/meminfo", "r")))
return NULL;
if (lscanf(fp, "MemFree:", "%ju kB", &free) != 1) {
fclose(fp);
return NULL;
}
fclose(fp);
return fmt_human(free * 1024, 1024);
}
const char *
ram_perc(const char *unused)
{
uintmax_t total, free, buffers, cached;
uintmax_t total, free, buffers, cached, shmem, sreclaimable;
int percent;
FILE *fp;
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)
if (!(fp = fopen("/proc/meminfo", "r")))
return NULL;
if (lscanf(fp, "MemTotal:", "%ju kB", &total) != 1 ||
lscanf(fp, "MemFree:", "%ju kB", &free) != 1 ||
lscanf(fp, "Buffers:", "%ju kB", &buffers) != 1 ||
lscanf(fp, "Cached:", "%ju kB", &cached) != 1 ||
lscanf(fp, "Shmem:", "%ju kB", &shmem) != 1 ||
lscanf(fp, "SReclaimable:", "%ju kB", &sreclaimable) != 1) {
fclose(fp);
return NULL;
}
fclose(fp);
if (total == 0)
return NULL;
percent = 100 * ((total - free) - (buffers + cached)) / total;
percent = 100 * (total - free - buffers - cached - sreclaimable + shmem) / total;
return bprintf("%d", percent);
}
@ -59,18 +68,24 @@
const char *
ram_used(const char *unused)
{
uintmax_t total, free, buffers, cached, used;
uintmax_t total, free, buffers, cached, used, shmem, sreclaimable;
FILE *fp;
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)
if (!(fp = fopen("/proc/meminfo", "r")))
return NULL;
used = (total - free - buffers - cached);
if (lscanf(fp, "MemTotal:", "%ju kB", &total) != 1 ||
lscanf(fp, "MemFree:", "%ju kB", &free) != 1 ||
lscanf(fp, "Buffers:", "%ju kB", &buffers) != 1 ||
lscanf(fp, "Cached:", "%ju kB", &cached) != 1 ||
lscanf(fp, "Shmem:", "%ju kB", &shmem) != 1 ||
lscanf(fp, "SReclaimable:", "%ju kB", &sreclaimable) != 1) {
fclose(fp);
return NULL;
}
fclose(fp);
used = total - free - buffers - cached - sreclaimable + shmem;
return fmt_human(used * 1024, 1024);
}
#elif defined(__OpenBSD__)

BIN
components/ram.o Normal file

Binary file not shown.

BIN
components/run_command.o Normal file

Binary file not shown.

BIN
components/swap.o Normal file

Binary file not shown.

BIN
components/temperature.o Normal file

Binary file not shown.

BIN
components/uptime.o Normal file

Binary file not shown.

BIN
components/user.o Normal file

Binary file not shown.

View File

@ -89,6 +89,8 @@
if (c->addr == addr)
break;
}
if (c == NULL)
return;
c->val = val;
}
@ -217,3 +219,33 @@
return bprintf("%d", v & 0xff);
}
#endif
const char *
vol_perc_pw(const char *sink)
{
FILE *fp;
char buf[64], cmd[64];
double v;
if (esnprintf(cmd, sizeof(cmd), "wpctl get-volume %s", sink) < 0)
return NULL;
if (!(fp = popen(cmd, "r"))) {
warn("popen '%s':", cmd);
return NULL;
}
if (!fgets(buf, sizeof(buf), fp)) {
pclose(fp);
return NULL;
}
pclose(fp);
if (strstr(buf, "[MUTED]"))
return "mute";
if (sscanf(buf, "Volume: %lf", &v) != 1)
return NULL;
return bprintf("%d%%", (int)(v * 100 + 0.5));
}

BIN
components/volume.o Normal file

Binary file not shown.

View File

@ -26,24 +26,16 @@
static char resp[4096];
static char *
findattr(int attr, char *p, char *e, size_t *len)
findattr(int attr, const char *p, const char *e, size_t *len)
{
struct nlattr nla;
size_t alen;
while ((size_t)(e-p) >= sizeof(nla)) {
while (p < e) {
struct nlattr nla;
memcpy(&nla, p, sizeof(nla));
if (nla.nla_len < NLA_HDRLEN)
return NULL;
if (nla.nla_type == attr) {
p += NLA_HDRLEN;
*len = nla.nla_len-NLA_HDRLEN;
return (size_t)(e-p)>=*len?p:NULL;
*len = nla.nla_len - NLA_HDRLEN;
return (char *)(p + NLA_HDRLEN);
}
alen = NLA_ALIGN(nla.nla_len);
if ((size_t)(e-p) < alen)
return NULL;
p += alen;
p += NLA_ALIGN(nla.nla_len);
}
return NULL;
}
@ -97,9 +89,10 @@
}
if ((size_t)r <= sizeof(ctrl))
return 0;
p = findattr(CTRL_ATTR_FAMILY_ID, resp+sizeof(ctrl), resp+r, &len);
p = findattr(CTRL_ATTR_FAMILY_ID, resp + sizeof(ctrl), resp + r, &len);
if (p && len == 2)
memcpy(&id, p, 2);
return id;
}
@ -117,10 +110,10 @@
}
if (strcmp(ifr.ifr_name, interface) != 0) {
strcpy(ifr.ifr_name, interface);
if (ioctl(ifsock, SIOCGIFINDEX, &ifr) != 0) {
warn("ioctl 'SIOCGIFINDEX':");
return -1;
}
}
if (ioctl(ifsock, SIOCGIFINDEX, &ifr) != 0) {
warn("ioctl 'SIOCGIFINDEX':");
return -1;
}
return ifr.ifr_ifindex;
}
@ -172,11 +165,12 @@
return NULL;
}
if ((size_t)r <= NLMSG_HDRLEN+GENL_HDRLEN)
if ((size_t)r <= NLMSG_HDRLEN + GENL_HDRLEN)
return NULL;
p = findattr(NL80211_ATTR_SSID, resp+NLMSG_HDRLEN+GENL_HDRLEN, resp+r, &len);
p = findattr(NL80211_ATTR_SSID, resp + NLMSG_HDRLEN + GENL_HDRLEN, resp + r, &len);
if (p)
p[len] = 0;
return p;
}
@ -188,8 +182,9 @@
uint16_t fam = nl80211fam();
ssize_t r;
size_t len;
char req[NLMSG_HDRLEN+GENL_HDRLEN+NLA_HDRLEN+NLA_ALIGN(4)] = {0}, *p = req, *e;
char req[NLMSG_HDRLEN + GENL_HDRLEN + NLA_HDRLEN + NLA_ALIGN(4)] = {0}, *p = req, *e;
int idx = ifindex(interface);
if (idx < 0) {
fprintf(stderr, "interface %s not found\n", interface);
return NULL;
@ -209,16 +204,17 @@
}, sizeof(struct genlmsghdr));
p += GENL_HDRLEN;
memcpy(p, &(struct nlattr){
.nla_len = NLA_HDRLEN+4,
.nla_len = NLA_HDRLEN + 4,
.nla_type = NL80211_ATTR_IFINDEX,
}, sizeof(struct nlattr));
p += NLA_HDRLEN;
memcpy(p, &(uint32_t){idx}, 4);
memcpy(p, &idx, 4);
if (send(nlsock, req, sizeof(req), 0) != sizeof(req)) {
warn("send 'AF_NETLINK':");
return NULL;
}
*strength = 0;
while (1) {
r = recv(nlsock, resp, sizeof(resp), 0);
@ -228,9 +224,11 @@
}
if ((size_t)r < sizeof(hdr))
return NULL;
for (p = resp; p != resp+r && (size_t)(resp+r-p) >= sizeof(hdr); p = e) {
for (p = resp; p != resp + r && (size_t)(resp + r-p) >= sizeof(hdr); p = e) {
memcpy(&hdr, p, sizeof(hdr));
e = resp+r-p<hdr.nlmsg_len?resp+r:p+hdr.nlmsg_len;
e = resp + r - p < hdr.nlmsg_len ? resp + r : p + hdr.nlmsg_len;
if (!*strength && hdr.nlmsg_len > NLMSG_HDRLEN+GENL_HDRLEN) {
p += NLMSG_HDRLEN+GENL_HDRLEN;
p = findattr(NL80211_ATTR_STA_INFO, p, e, &len);
@ -240,7 +238,7 @@
snprintf(strength, sizeof(strength), "%d", RSSI_TO_PERC(*p));
}
if (hdr.nlmsg_type == NLMSG_DONE)
return *strength?strength:NULL;
return *strength ? strength : NULL;
}
}
}

BIN
components/wifi.o Normal file

Binary file not shown.

View File

@ -65,6 +65,9 @@ static const char unknown_str[] = "n/a";
* wifi_perc WiFi signal in percent interface name (wlan0)
*/
static const struct arg args[] = {
/* function format argument */
{ datetime, "%s", "%F %T" },
/* function format argument */
{ wifi_perc, " wifi %s%% |", "wlan0" },
{ battery_perc, " bat %s%% |", "BAT0" },
{ vol_perc_pw, " vol %s |", "@DEFAULT_AUDIO_SINK@" },
{ datetime, " %s", "%F %T" },
};

70
config.h Normal file
View File

@ -0,0 +1,70 @@
/* 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
* up interface is running interface name (eth0)
* 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 */
{wifi_essid, "%s", "wlan0"}, {wifi_perc, " %s%% |", "wlan0"}, {battery_perc, " bat %s%%", "BAT0"}, {battery_state, "%s |", "BAT0"}, {vol_perc_pw, " vol %s |", "@DEFAULT_AUDIO_SINK@"}, {datetime, " %s", "%F %T"},
};

View File

@ -1,5 +1,5 @@
# slstatus version
VERSION = 1.0
VERSION = 1.1
# customize below to fit your system

BIN
slstatus Executable file

Binary file not shown.

View File

@ -79,6 +79,7 @@ const char *username(const char *unused);
/* volume */
const char *vol_perc(const char *card);
const char *vol_perc_pw(const char *sink);
/* wifi */
const char *wifi_essid(const char *interface);

BIN
slstatus.o Normal file

Binary file not shown.

17
util.c
View File

@ -139,3 +139,20 @@ pscanf(const char *path, const char *fmt, ...)
return (n == EOF) ? -1 : n;
}
int
lscanf(FILE *fp, const char *key, const char *fmt, void *res)
{
int n;
char line[256];
n = -1;
while (fgets(line, sizeof(line), fp))
if (strncmp(line, key, strlen(key)) == 0) {
n = sscanf(line + strlen(key), fmt, res);
break;
}
rewind(fp);
return (n == 1) ? 1 : -1;
}

2
util.h
View File

@ -1,5 +1,6 @@
/* See LICENSE file for copyright and license details. */
#include <stdint.h>
#include <stdio.h>
extern char buf[1024];
@ -14,3 +15,4 @@ 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, ...);
int lscanf(FILE *fp, const char *key, const char *fmt, void *res);

BIN
util.o Normal file

Binary file not shown.