336 lines
9.3 KiB
C
336 lines
9.3 KiB
C
/* beastie - minimal system info */
|
|
#define VERSION "dev"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <pwd.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/types.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/statvfs.h>
|
|
|
|
/* module IDs - reference these in config.h modules[] */
|
|
enum module {
|
|
MOD_SEP, /* blank separator line */
|
|
MOD_USERATHOST, /* user@hostname */
|
|
MOD_OS, /* os name + release */
|
|
MOD_KERNEL, /* kern.ident */
|
|
MOD_CPU, /* hw.model */
|
|
MOD_UPTIME, /* uptime */
|
|
MOD_MEM, /* used / total memory */
|
|
MOD_SHELL, /* $SHELL basename */
|
|
MOD_TERM, /* $TERM */
|
|
MOD_WM, /* wm (via xdpyinfo) */
|
|
MOD_DISK, /* used / total on / */
|
|
MOD_PKGS, /* pkg count */
|
|
MOD_RES, /* screen resolution */
|
|
MOD_COLORS, /* 16-color palette */
|
|
};
|
|
|
|
#include "config.h"
|
|
|
|
#define ART_H (int)(sizeof(ART)/sizeof(ART[0]))
|
|
#define NMOD (int)(sizeof(modules)/sizeof(modules[0]))
|
|
#define MAX_INFO 32
|
|
|
|
/* format uptime as Xd Xh Xm from boot time */
|
|
static void
|
|
uptime_str(char *buf, size_t len)
|
|
{
|
|
long up = 0;
|
|
struct timeval bt;
|
|
size_t sz = sizeof(bt);
|
|
if (sysctlbyname("kern.boottime", &bt, &sz, NULL, 0) == 0)
|
|
up = (long)(time(NULL) - bt.tv_sec);
|
|
if (!up) { snprintf(buf, len, "unknown"); return; }
|
|
long d = up / 86400, h = (up % 86400) / 3600, m = (up % 3600) / 60;
|
|
if (d) snprintf(buf, len, "%ldd %ldh %ldm", d, h, m);
|
|
else if (h) snprintf(buf, len, "%ldh %ldm", h, m);
|
|
else snprintf(buf, len, "%ldm", m);
|
|
}
|
|
|
|
/* format memory as used / total in MB */
|
|
static void
|
|
mem_str(char *buf, size_t len)
|
|
{
|
|
unsigned long total_mb = 0, used_mb = 0;
|
|
unsigned long physmem;
|
|
unsigned int free_c = 0, inactive_c = 0;
|
|
int pgsz = 0;
|
|
size_t sz;
|
|
sz = sizeof(physmem);
|
|
if (sysctlbyname("hw.physmem", &physmem, &sz, NULL, 0) == -1) {
|
|
snprintf(buf, len, "unknown"); return;
|
|
}
|
|
/* available = free + inactive pages */
|
|
sz = sizeof(free_c); sysctlbyname("vm.stats.vm.v_free_count", &free_c, &sz, NULL, 0);
|
|
sz = sizeof(inactive_c); sysctlbyname("vm.stats.vm.v_inactive_count", &inactive_c, &sz, NULL, 0);
|
|
sz = sizeof(pgsz); sysctlbyname("hw.pagesize", &pgsz, &sz, NULL, 0);
|
|
unsigned long avail = (unsigned long)(free_c + inactive_c) * pgsz;
|
|
total_mb = physmem >> 20;
|
|
used_mb = (physmem - avail) >> 20;
|
|
if (!total_mb) { snprintf(buf, len, "unknown"); return; }
|
|
snprintf(buf, len, "%luM / %luM", used_mb, total_mb);
|
|
}
|
|
|
|
/* read cpu model string */
|
|
static void
|
|
cpu_str(char *buf, size_t len)
|
|
{
|
|
size_t sz = len;
|
|
if (sysctlbyname("hw.model", buf, &sz, NULL, 0) == 0) return;
|
|
snprintf(buf, len, "unknown");
|
|
}
|
|
|
|
/* disk usage on / in GB used / total */
|
|
static void
|
|
disk_str(char *buf, size_t len)
|
|
{
|
|
struct statvfs s;
|
|
if (statvfs("/", &s) == -1) { snprintf(buf, len, "unknown"); return; }
|
|
unsigned long long total = (unsigned long long)s.f_blocks * s.f_frsize;
|
|
unsigned long long avail = (unsigned long long)s.f_bavail * s.f_frsize;
|
|
unsigned long long used = total - avail;
|
|
snprintf(buf, len, "%.1fG / %.1fG",
|
|
(double)used / (1024*1024*1024),
|
|
(double)total / (1024*1024*1024));
|
|
}
|
|
|
|
/* installed pkg count via pkg info */
|
|
static void
|
|
pkgs_str(char *buf, size_t len)
|
|
{
|
|
FILE *f = popen("pkg info 2>/dev/null | wc -l", "r");
|
|
if (f) {
|
|
int n = 0;
|
|
fscanf(f, "%d", &n);
|
|
pclose(f);
|
|
if (n > 0) { snprintf(buf, len, "%d (pkg)", n); return; }
|
|
}
|
|
snprintf(buf, len, "unknown");
|
|
}
|
|
|
|
/* screen resolution via xdpyinfo */
|
|
static void
|
|
res_str(char *buf, size_t len)
|
|
{
|
|
FILE *f = popen("xdpyinfo 2>/dev/null | grep dimensions | awk '{print $2}'", "r");
|
|
if (f) {
|
|
char res[32] = {0};
|
|
fgets(res, sizeof(res), f);
|
|
pclose(f);
|
|
size_t rl = strlen(res);
|
|
if (rl && res[rl-1] == '\n') res[rl-1] = '\0';
|
|
if (res[0]) { snprintf(buf, len, "%s", res); return; }
|
|
}
|
|
snprintf(buf, len, "unknown");
|
|
}
|
|
|
|
/* read custom kernel name via kern.ident sysctl */
|
|
static void
|
|
kernel_ident(char *buf, size_t len)
|
|
{
|
|
size_t sz = len;
|
|
if (sysctlbyname("kern.ident", buf, &sz, NULL, 0) == 0) return;
|
|
snprintf(buf, len, "unknown");
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
|
|
printf("Beastie version " VERSION "\n");
|
|
printf("Beastie is a minimal fetch tool for BSD systems.\n");
|
|
printf("Compiled with "
|
|
#if defined(__clang__)
|
|
"clang " __clang_version__
|
|
#elif defined(__GNUC__)
|
|
"gcc " __VERSION__
|
|
#else
|
|
"unknown compiler"
|
|
#endif
|
|
"\n");
|
|
return 0;
|
|
}
|
|
struct utsname uts;
|
|
char cpu[256] = {0};
|
|
char kid[64] = {0};
|
|
char upt[64] = {0};
|
|
char mem[64] = {0};
|
|
char dsk[64] = {0};
|
|
char pkg[64] = {0};
|
|
char res[64] = {0};
|
|
|
|
uname(&uts);
|
|
|
|
/* prefer $USER env, fall back to passwd db */
|
|
const char *user = getenv("USER");
|
|
if (!user) {
|
|
struct passwd *pw = getpwuid(getuid());
|
|
user = pw ? pw->pw_name : "?";
|
|
}
|
|
|
|
cpu_str(cpu, sizeof(cpu));
|
|
kernel_ident(kid, sizeof(kid));
|
|
uptime_str(upt, sizeof(upt));
|
|
mem_str(mem, sizeof(mem));
|
|
disk_str(dsk, sizeof(dsk));
|
|
pkgs_str(pkg, sizeof(pkg));
|
|
res_str(res, sizeof(res));
|
|
|
|
/* strip path prefix from $SHELL */
|
|
const char *shell = getenv("SHELL");
|
|
if (shell) { const char *s = strrchr(shell, '/'); if (s) shell = s + 1; }
|
|
else shell = "unknown";
|
|
|
|
/* normalize $TERM - strip -256color/-color suffix, map xterm generically */
|
|
char termbuf[64] = "unknown";
|
|
const char *termenv = getenv("TERM");
|
|
if (termenv) {
|
|
if (strncmp(termenv, "xterm", 5) == 0)
|
|
snprintf(termbuf, sizeof(termbuf), "generic terminal emulator");
|
|
else {
|
|
char *p;
|
|
snprintf(termbuf, sizeof(termbuf), "%s", termenv);
|
|
if ((p = strstr(termbuf, "-256color"))) *p = '\0';
|
|
else if ((p = strstr(termbuf, "-color"))) *p = '\0';
|
|
}
|
|
}
|
|
const char *term = termbuf;
|
|
|
|
/* detect x server vendor via xdpyinfo to avoid linking against libX11 */
|
|
char wmbuf[64] = "none";
|
|
if (getenv("DISPLAY")) {
|
|
FILE *xdpy = popen("xdpyinfo 2>/dev/null | grep 'vendor string' | awk '{print $3}'", "r");
|
|
if (xdpy) {
|
|
char vendor[64] = {0};
|
|
fgets(vendor, sizeof(vendor), xdpy);
|
|
pclose(xdpy);
|
|
size_t vl = strlen(vendor);
|
|
if (vl && vendor[vl-1] == '\n') vendor[vl-1] = '\0';
|
|
if (vendor[0])
|
|
snprintf(wmbuf, sizeof(wmbuf), "dwm (on %s)", vendor);
|
|
else
|
|
snprintf(wmbuf, sizeof(wmbuf), "dwm");
|
|
} else {
|
|
snprintf(wmbuf, sizeof(wmbuf), "dwm");
|
|
}
|
|
}
|
|
const char *wm = wmbuf;
|
|
|
|
/* build info lines from modules[] */
|
|
char ibufs[MAX_INFO][512];
|
|
const char *ilines[MAX_INFO];
|
|
int ni = 0;
|
|
|
|
for (int j = 0; j < NMOD && ni < MAX_INFO; j++) {
|
|
switch (modules[j]) {
|
|
case MOD_SEP:
|
|
ilines[ni++] = "";
|
|
break;
|
|
case MOD_USERATHOST:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_BOLD COLOR_USER "%s" COLOR_RESET
|
|
"@"
|
|
COLOR_BOLD COLOR_USER "%s" COLOR_RESET,
|
|
user, uts.nodename);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_OS:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "os " COLOR_RESET " %s %s",
|
|
uts.sysname, uts.release);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_KERNEL:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "kernel" COLOR_RESET " %s", kid);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_CPU:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "cpu " COLOR_RESET " %s", cpu);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_UPTIME:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "uptime" COLOR_RESET " %s", upt);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_MEM:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "mem " COLOR_RESET " %s", mem);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_SHELL:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "shell " COLOR_RESET " %s", shell);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_TERM:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "term " COLOR_RESET " %s", term);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_WM:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "wm " COLOR_RESET " %s", wm);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_DISK:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "disk " COLOR_RESET " %s", dsk);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_PKGS:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "pkgs " COLOR_RESET " %s", pkg);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_RES:
|
|
snprintf(ibufs[ni], sizeof(ibufs[0]),
|
|
COLOR_LABEL "res " COLOR_RESET " %s", res);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
case MOD_COLORS: {
|
|
/* render two rows of 8 color swatches each */
|
|
char *p = ibufs[ni];
|
|
size_t rem = sizeof(ibufs[0]);
|
|
int n;
|
|
for (int k = 0; k < 8; k++) {
|
|
n = snprintf(p, rem, "\033[4%dm ", k);
|
|
p += n; rem -= n;
|
|
}
|
|
n = snprintf(p, rem, COLOR_RESET " ");
|
|
p += n; rem -= n;
|
|
for (int k = 8; k < 16; k++) {
|
|
n = snprintf(p, rem, "\033[48;5;%dm ", k);
|
|
p += n; rem -= n;
|
|
}
|
|
snprintf(p, rem, COLOR_RESET);
|
|
ilines[ni] = ibufs[ni]; ni++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* print art + info side by side, padding whichever is shorter */
|
|
int total = ni > ART_H ? ni : ART_H;
|
|
|
|
printf("\n");
|
|
for (int i = 0; i < total; i++) {
|
|
printf(" ");
|
|
if (i < ART_H)
|
|
printf(COLOR_ART "%s" COLOR_RESET, ART[i]);
|
|
else
|
|
printf("%*s", ART_W, "");
|
|
printf(" ");
|
|
if (i < ni)
|
|
printf("%s", ilines[i]);
|
|
printf("\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|