Files
energonfetch/main.go
T
2025-12-31 02:34:15 +01:00

448 lines
14 KiB
Go

package main
import (
"bytes"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"syscall"
)
var cube = []string{
" +------+",
" / /|",
" +------+ |",
" | | +",
" | |/",
" +------+",
}
func main() {
info := collectAll()
values := make([]string, 0, len(info))
keys := make([]string, 0, len(info))
for _, kv := range info {
values = append(values, strings.ToLower(kv.v))
keys = append(keys, strings.ToLower(kv.k))
}
printAligned(values, keys, cube)
}
type pair struct{ k, v string }
func collectAll() []pair {
var out []pair
distro := readPrettyOSRelease("/etc/os-release")
if distro == "" {
distro = runtime.GOOS
}
out = append(out, pair{"os", distro})
out = append(out, pair{"arch", runtime.GOARCH})
out = append(out, pair{"kernel", getUname()})
cpuModel, cpuHz := detectCPU()
if cpuHz > 0 {
if cpuHz >= 1e9 {
ghz := float64(cpuHz) / 1e9
out = append(out, pair{"cpu", fmt.Sprintf("%s-%.2f ghz", cpuModel, ghz)})
} else {
out = append(out, pair{"cpu", fmt.Sprintf("%s-%d mhz", cpuModel, cpuHz/1e6)})
}
} else {
out = append(out, pair{"cpu", cpuModel})
}
memTotal, memAvail, swapTotal, swapFree := parseMemInfo()
if memTotal > 0 {
memUsed := memTotal - memAvail
out = append(out, pair{"memory", fmt.Sprintf("%s / %s", hr(memUsed), hr(memTotal))})
}
if swapTotal > 0 {
swapUsed := swapTotal - swapFree
out = append(out, pair{"swap", fmt.Sprintf("%s / %s", hr(swapUsed), hr(swapTotal))})
}
out = append(out, pair{"desktop", detectDesktop()})
used, total, fstype := getStorageAndType("/")
out = append(out, pair{"storage", fmt.Sprintf("%s / %s %s", hr(used), hr(total), fstype)})
return out
}
func readPrettyOSRelease(path string) string {
if b, err := os.ReadFile(path); err == nil {
for _, ln := range bytes.Split(b, []byte{'\n'}) {
if bytes.HasPrefix(ln, []byte("PRETTY_NAME=")) {
val := strings.Trim(string(bytes.TrimPrefix(ln, []byte("PRETTY_NAME="))), `"`)
return strings.TrimSpace(val)
}
}
name := parseKey(bytesToLower(b), "name=")
version := parseKey(bytesToLower(b), "version=")
if name != "" && version != "" {
return strings.TrimSpace(name + " " + version)
}
if name != "" {
return name
}
}
return ""
}
func parseKey(b []byte, key string) string {
for _, ln := range bytes.Split(b, []byte{'\n'}) {
if bytes.HasPrefix(ln, []byte(key)) {
val := bytes.TrimPrefix(ln, []byte(key))
val = bytes.Trim(val, `"' `)
return string(val)
}
}
return ""
}
func bytesToLower(b []byte) []byte { return bytes.ToLower(b) }
func getUname() string {
var u syscall.Utsname
if err := syscall.Uname(&u); err == nil {
return fromUts(u.Release[:])
}
return "unknown"
}
func fromUts(a []int8) string {
var b []byte
for _, c := range a {
if c == 0 {
break
}
b = append(b, byte(c))
}
return string(b)
}
func detectCPU() (model string, hz uint64) {
kPaths := []string{
"/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq",
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq",
}
for _, p := range kPaths {
if b, err := os.ReadFile(p); err == nil {
if n, err := strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64); err == nil && n > 0 {
hz = n * 1000
model = readCPUModelFromProc()
if model == "" {
model = "unknown"
}
return model, hz
}
}
}
if b, err := os.ReadFile("/proc/cpuinfo"); err == nil {
lower := bytes.ToLower(b)
if m := findFirstField(lower, "model name"); m != "" {
model = trimField(m)
} else if m := findFirstField(lower, "hardware"); m != "" {
model = trimField(m)
} else if m := findFirstField(lower, "processor"); m != "" {
model = trimField(m)
} else if m := findFirstField(lower, "model"); m != "" {
model = trimField(m)
} else {
model = "unknown"
}
if mhz := findFirstField(lower, "cpu mhz"); mhz != "" {
if f, err := strconv.ParseFloat(strings.TrimSpace(mhz), 64); err == nil && f > 0 {
hz = uint64(f * 1e6)
}
}
return model, hz
}
return "unknown", 0
}
func readCPUModelFromProc() string {
if b, err := os.ReadFile("/proc/cpuinfo"); err == nil {
lower := bytes.ToLower(b)
if m := findFirstField(lower, "model name"); m != "" {
return trimField(m)
}
if m := findFirstField(lower, "hardware"); m != "" {
return trimField(m)
}
}
return ""
}
func findFirstField(b []byte, key string) string {
t := []byte(key + ":")
if i := bytes.Index(b, t); i != -1 {
if j := bytes.IndexByte(b[i:], '\n'); j != -1 {
line := b[i : i+j]
parts := bytes.SplitN(line, []byte{':'}, 2)
if len(parts) == 2 {
return string(parts[1])
}
} else {
parts := bytes.SplitN(b[i:], []byte{':'}, 2)
if len(parts) == 2 {
return string(parts[1])
}
}
}
t2 := []byte(key + "\t:")
if i := bytes.Index(b, t2); i != -1 {
if j := bytes.IndexByte(b[i:], '\n'); j != -1 {
line := b[i : i+j]
parts := bytes.SplitN(line, []byte{':'}, 2)
if len(parts) == 2 {
return string(parts[1])
}
}
}
t3 := []byte(key + "=")
if i := bytes.Index(b, t3); i != -1 {
if j := bytes.IndexByte(b[i:], '\n'); j != -1 {
line := b[i : i+j]
parts := bytes.SplitN(line, []byte{'='}, 2)
if len(parts) == 2 {
return string(parts[1])
}
}
}
return ""
}
func trimField(s string) string {
return strings.TrimSpace(strings.Trim(s, `"`))
}
func parseMemInfo() (memTotal, memAvailable, swapTotal, swapFree uint64) {
if b, err := os.ReadFile("/proc/meminfo"); err == nil {
for _, ln := range bytes.Split(b, []byte{'\n'}) {
if len(ln) == 0 {
continue
}
l := bytes.ToLower(ln)
fields := bytes.Fields(l)
if len(fields) < 2 {
continue
}
key := string(fields[0])
valStr := string(fields[1])
n, err := strconv.ParseUint(valStr, 10, 64)
if err != nil {
continue
}
switch key {
case "memtotal:":
memTotal = n * 1024
case "memavailable:":
memAvailable = n * 1024
case "swaptotal:":
swapTotal = n * 1024
case "swapfree:":
swapFree = n * 1024
}
}
}
return
}
func getStorageAndType(path string) (used, total uint64, fstype string) {
var st syscall.Statfs_t
if syscall.Statfs(path, &st) == nil {
total = st.Blocks * uint64(st.Bsize)
avail := st.Bavail * uint64(st.Bsize)
used = total - avail
}
if b, err := os.ReadFile("/proc/mounts"); err == nil {
lines := bytes.Split(b, []byte{'\n'})
bestLen := -1
for _, ln := range lines {
if len(ln) == 0 {
continue
}
parts := bytes.Fields(ln)
if len(parts) < 3 {
continue
}
mp := string(parts[1])
if strings.HasPrefix(path, mp) && len(mp) > bestLen {
bestLen = len(mp)
fstype = string(parts[2])
}
}
}
if fstype == "" {
fstype = "fs"
}
return used, total, strings.ToLower(fstype)
}
func detectDesktop() string {
envs := []string{
"XDG_CURRENT_DESKTOP",
"DESKTOP_SESSION",
"XDG_SESSION_DESKTOP",
"GDMSESSION",
"SWAYSOCK",
}
for _, e := range envs {
if v := os.Getenv(e); v != "" {
return strings.ToLower(strings.Split(v, ":")[0])
}
}
if os.Getenv("WAYLAND_DISPLAY") != "" {
return "wayland"
}
if os.Getenv("DISPLAY") != "" {
return "x11"
}
return "tty"
}
func printAligned(values, keys []string, cube []string) {
n := len(values)
cubeH := len(cube)
valW := 0
keyW := 0
for _, v := range values {
if visibleLen(v) > valW {
valW = visibleLen(v)
}
}
for _, k := range keys {
if visibleLen(k) > keyW {
keyW = visibleLen(k)
}
}
cubeCol := valW + 2 + 3 + keyW + 2
lines := n
if cubeH > lines {
lines = cubeH
}
cubeStart := 0
if n > cubeH {
cubeStart = (n - cubeH) / 2
}
for i := 0; i < lines; i++ {
left := ""
mid := ""
if i < n {
left = values[i]
mid = "<- " + bold(keys[i])
}
line := ""
if left != "" || mid != "" {
if left != "" {
line += padRight(left, valW)
} else {
line += strings.Repeat(" ", valW)
}
line += " "
if mid != "" {
key := keys[i]
paddedKey := padRight(key, keyW)
mid = "<- " + bold(paddedKey)
line += mid
}
}
vis := visibleLen(line)
if pad := cubeCol - vis; pad > 0 {
line += strings.Repeat(" ", pad)
}
cIdx := i - cubeStart
if cIdx >= 0 && cIdx < cubeH {
line += cube[cIdx]
}
fmt.Println(line)
}
}
func bold(s string) string { return "\033[1m" + s + "\033[0m" }
func padRight(s string, w int) string {
if visibleLen(s) >= w {
return s
}
return s + strings.Repeat(" ", w-visibleLen(s))
}
func visibleLen(s string) int {
n := 0
esc := false
for i := 0; i < len(s); i++ {
c := s[i]
if c == '\033' {
esc = true
continue
}
if esc {
if c == 'm' {
esc = false
}
continue
}
n++
}
return n
}
func hr(b uint64) string {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
if b >= GB {
f := float64(b) / float64(GB)
return fmt.Sprintf("%.2f gb", f)
}
if b >= MB {
return fmt.Sprintf("%.0f mb", float64(b)/float64(MB))
}
return fmt.Sprintf("%d kb", b/KB)
}