slstatus 是 suckless 风格的轻量状态栏工具,默认通过 EWMH 属性或 stdout 输出系统信息,常配合 dwm 使用。它本身已经很精简高效,但真正好玩的地方在于自己写组件。虽然纯 C 没有 obj,anw这个功能还是用纯 c 写的。
本文重点不谈官方自带的 battery、cpu、wifi 等常规模块,而是分享我添加的三个个人化组件:
- 实时年龄计算(
age_cal) - 重要日子精确天数计数(
days_since) - 蓝牙连接设备名 + 电量(
bluetooth_conn/bluetooth_batt)
这些组件让状态栏变成了真正“属于我”的东西。
1. 实时年龄显示(精确到小数点后 8 位) 链接到标题
状态栏里最显眼的一项是:
{ age_cal, "me@[%s] ‖ ", "20100318" },
效果类似:me@[16.43210987] ‖
实现非常直接(components/age.c):
#define YEAR 31557600.0 /* 平均年长度(考虑闰年) */
const char *
age_cal(const char *birth)
{
static char age_buf[16];
int year, month, day;
sscanf(birth, "%4d%2d%2d", &year, &month, &day);
struct tm birth_time = {0};
birth_time.tm_year = year - 1900;
birth_time.tm_mon = month - 1;
birth_time.tm_mday = day;
time_t birth_timestamp = mktime(&birth_time);
time_t current_timestamp = time(NULL);
double seconds_difference = difftime(current_timestamp, birth_timestamp);
double super_age = seconds_difference / YEAR;
snprintf(age_buf, sizeof(age_buf), "%.8lf", super_age);
return age_buf;
}
设计要点:
- 使用
31557600.0(365.25 × 86400)作为年平均秒数,能较好地处理闰年。 - 输出精度到小数点后 8 位,状态栏每 300ms 刷新一次时,数字会缓慢跳动,有种“生命在流逝”的感觉。
- 参数直接写出生日期
YYYYMMDD,无需额外配置文件。
2. 重要日子精确天数计数 链接到标题
我最喜欢的组件:
{ days_since, "📅 %s ‖ ", "2026-07-01" },
输出类似:📅 51.23456d ‖
对应代码(components/days.c):
const char *
days_since(const char *date_str)
{
struct tm target_tm = { 0 };
time_t now, target;
double diff_sec, days;
if (sscanf(date_str, "%d-%d-%d",
&target_tm.tm_year, &target_tm.tm_mon, &target_tm.tm_mday) != 3)
return NULL;
target_tm.tm_year -= 1900;
target_tm.tm_mon -= 1;
target_tm.tm_isdst = -1; /* 让 mktime 自动处理夏令时 */
target = mktime(&target_tm);
if (target == -1)
return NULL;
time(&now);
diff_sec = difftime(now, target);
days = diff_sec / (60 * 60 * 24);
return bprintf("%.5fd", days);
}
亮点:
- 支持任意
YYYY-MM-DD日期,可以轻松换成纪念日、项目启动日、考试日等。 - 精确到小数点后 5 位(约 0.86 秒分辨率),配合 300ms 刷新,视觉上是实时跳动的。
- 使用
difftime+mktime正确处理时区和夏令时。
想换日期只需要改 config.h 里的字符串,重新 make 就行。
3. 蓝牙设备名 + 电量显示 链接到标题
蓝牙相关我写了三个函数(目前用了两个):
{ bluetooth_conn, "B[%s]", "" },
{ bluetooth_batt, "[%s] ‖ ", "" },
典型输出:B[AirPods Pro][87%] ‖ 或 B[WH-1000XM5, Keyboard][62%, 100%] ‖
核心实现依赖 bluetoothctl(components/bluetooth.c):
bluetooth_conn:执行bluetoothctl devices Connected,解析设备名称,多个设备用,分隔。bluetooth_batt:对每个已连接设备再执行bluetoothctl info <MAC>,用正则提取Battery Percentage: 0x?? (NN),同样用逗号拼接。
关键解析逻辑示例:
/* 提取设备名 */
if (strncmp(buf, "Device ", 7) != 0) continue;
name = strchr(buf + 7, ' ');
if (!name) continue;
name++; /* 跳过 MAC 后的空格 */
/* 提取电量 */
sscanf(buf, " Battery Percentage: %*x (%d)", &perc)
优点:
- 无需额外 daemon,直接调用系统自带的
bluetoothctl。 - 支持多设备同时显示名称和电量。
- 没有连接设备时返回
NULL,状态栏会干净地跳过该项。
最终状态栏配置片段 链接到标题
static const struct arg args[] = {
{ hostname, " @%s ‖ ", "" },
/* 音量、亮度、电池等常规项... */
{ bluetooth_conn, "B[%s]", "" },
{ bluetooth_batt, "[%s] ‖ ", "" },
{ wifi_essid, "W[%s]", "wlp0s20f3" },
{ wifi_perc, "[%s] ‖ ", "wlp0s20f3" },
{ age_cal, "me@[%s] ‖ ", "20100318" },
{ days_since, "📅 %s ‖ ", "2026-07-01" },
{ datetime, "[ %s ] ", "%F %T" },
};
刷新间隔设为 300ms,既保证数字跳动流畅,又不会带来明显的 CPU 开销。
小结 链接到标题
通过三个很小的组件,状态栏从“系统信息显示器”变成了个人时间胶囊:
- 实时年龄让人时刻意识到时间流逝
- 日子计数让我感恩和她走过的一路
- 蓝牙信息则解决了耳机/键盘电量需要频繁查看手机的痛点
这正是 suckless 哲学最迷人的地方——代码足够小,你完全可以按照自己的生活节奏去扩展它。
如果你也想加这些功能,直接把 age.c、days.c、bluetooth.c 丢进 components/,在 slstatus.h 声明函数,改好 config.h 后 make clean install 即可。