commit 240504f287c0945aedcf1f7c6fe956c81f5a0f45 Author: Librebooter Date: Mon Mar 16 02:41:26 2026 -0500 initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2c619a9 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +# beastie - minimal system info +include config.mk + +beastie: beastie.c config.h + ${CC} ${CFLAGS} -o $@ beastie.c ${LDFLAGS} + +install: beastie + install -m 755 beastie ${DESTDIR}${PREFIX}/bin/beastie + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/beastie + +clean: + rm -f beastie + +.PHONY: install uninstall clean diff --git a/README b/README new file mode 100644 index 0000000..17ca90c --- /dev/null +++ b/README @@ -0,0 +1,59 @@ +beastie - minimal system fetch +================================ + +Minimal system info tool for BSD. Displays info side-by-side with +ASCII art. Named after the BSD daemon mascot. + +Reads hardware/OS info via sysctlbyname(3). +WM detection uses xdpyinfo(1) at runtime if MOD_WM is enabled. + + +EXAMPLE OUTPUT +-------------- + + %@ realdaemon@daemon-box + %# @## + %#@ @%%%%%%@%%##@ os FreeBSD 15.0-RELEASE-p4 + @%%##@%%%%%@######%## kernel MinKern + %%%###%%%%# @@ @# cpu Intel(R) Core(TM) i7-4910MQ CPU @ 2.90GHz + %%%%%%%%## @ @#@ uptime 3h 45m + @%%%%%## @@#@@# mem 2689M / 16237M + %%%%#### @@##### shell sh + %%%%%%#####%##@## *: term st + @%%%#@######%#@ : :@ wm dwm (on XLibre) + %%%%%%###@ @# @:@ @: + %%###### %#@ + @%%@##@###@%%##@ + %%%##%####@%@@ + @%%%%%%%%%@ + @%@::%@@%##@ + @%:@%%%####@ + @%%%@@%@:@%:# + @%%%%%@ @+ :-:@@:::@@@ + %##@@ %::::::# . @ + %%@ %@ @ @ + @@%#### + + +CONFIGURATION +------------- +Edit config.h and recompile. Configurable: + + COLOR_ART ANSI color for the ASCII art + COLOR_USER ANSI color for the user@host header + COLOR_LABEL ANSI color for info labels + ART_W Visual width of the widest art line (must be accurate) + ART[] The ASCII art, one line per string + + +BUILD AND INSTALL +----------------- + make + doas make install + +Installs to /usr/local/bin/beastie. Override PREFIX to change. + + +REQUIREMENTS +------------ + BSD: base system, optionally xdpyinfo(1) for wm vendor detection diff --git a/beastie b/beastie new file mode 100755 index 0000000..2e6f4a8 Binary files /dev/null and b/beastie differ diff --git a/beastie.c b/beastie.c new file mode 100644 index 0000000..746e19c --- /dev/null +++ b/beastie.c @@ -0,0 +1,336 @@ +/* beastie - minimal system info */ +#define VERSION "dev" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..139eab2 --- /dev/null +++ b/config.h @@ -0,0 +1,54 @@ +/* beastie - configuration, edit and recompile */ + +/* colors */ +#define COLOR_RESET "\033[0m" +#define COLOR_BOLD "\033[1m" +#define COLOR_ART "\033[1;31m" /* bold red - art */ +#define COLOR_USER "\033[1;34m" /* bold blue - user@host */ +#define COLOR_LABEL "\033[1;35m" /* bold magenta - info labels */ + +/* modules to display, in order - comment out or reorder to taste */ +static const enum module modules[] = { + MOD_USERATHOST, + MOD_SEP, + MOD_OS, + MOD_KERNEL, + MOD_CPU, + MOD_UPTIME, + MOD_MEM, + MOD_SHELL, + MOD_TERM, + MOD_WM, + MOD_DISK, + MOD_PKGS, + MOD_RES, + MOD_SEP, + /* MOD_COLORS, */ +}; + +/* ascii art - ART_W must equal the visual width of the longest line */ +#define ART_W 38 +static const char *ART[] = { + " %@ ", + " %# @## ", + " %#@ @%%%%%%@%%##@ ", + " @%%##@%%%%%@######%## ", + " %%%###%%%%# @@ @# ", + " %%%%%%%%## @ @#@ ", + " @%%%%%## @@#@@# ", + " %%%%#### @@##### ", + " %%%%%%#####%##@## *: ", + " @%%%#@######%#@ : :@ ", + " %%%%%%###@ @# @:@ @: ", + " %%###### %#@ ", + " @%%@##@###@%%##@ ", + " %%%##%####@%@@ ", + " @%%%%%%%%%@ ", + " @%@::%@@%##@ ", + " @%:@%%%####@ ", + " @%%%@@%@:@%:# ", + " @%%%%%@ @+ :-:@@:::@@@ ", + " %##@@ %::::::# . @ ", + "%%@ %@ @ @ ", + " @@%#### ", +}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..b9296f0 --- /dev/null +++ b/config.mk @@ -0,0 +1,7 @@ +# beastie - build configuration, edit and recompile + +PREFIX = /usr/local + +CC = cc +CFLAGS = -O2 -Wall -Wextra -std=c99 -pedantic +LDFLAGS =