25 Commits
Author SHA1 Message Date
EnumDev 1439a22378 Simplify color map system 2025-08-23 17:43:51 +03:00
EnumDev 74eb84e706 Simplify print statements 2025-08-23 16:39:21 +03:00
EnumDev 5ab8a6ee68 Add Bedrock Linux support 2025-08-23 16:32:14 +03:00
EnumDev 1ee2691b44 Update LICENSE 2025-04-01 17:44:16 +03:00
EnumDev b6dba0061d Remove developers section from README.md 2025-03-21 17:12:44 +02:00
EnumDev d41c00f972 Remove dependency warnings 2025-03-21 16:11:49 +02:00
EnumDev d51cfdf363 Improved GetGPUModels function 2025-03-21 16:03:15 +02:00
EnumDev 0f46e313e6 Remove compress goal from Makefile 2025-03-21 14:57:11 +02:00
EnumDev a76295c455 Moved go files to src/ folder 2025-03-21 14:56:58 +02:00
EnumDev 47fd83f8cd Improve GetMountedPartitions function and added 'hidden_partitions' and 'hidden_filesystems' config options 2025-03-21 14:11:33 +02:00
EnumDev ca17578cac Improve GetLibc function 2025-03-21 12:31:29 +02:00
EnumDev 7d121c330d Move package count detection to go code 2025-03-21 09:57:36 +02:00
EnumDev 6185af37d5 Add i3-with-shmlog detection 2025-03-20 13:27:46 +02:00
EnumDev cd0faaefe5 Add Enit init system detection 2025-03-10 22:43:06 +02:00
EnumDev 4b9bc59810 Change init system checking code 2025-03-10 22:42:18 +02:00
EnumDev 0ed976a06e Fixed bug where the getDistroAsciiArt function would ignore the systemConfigDir variable 2025-02-06 21:18:55 +02:00
EnumDev ce9813a2af Made fatal errors clearer and fixed bug in fetch script 2025-02-06 21:18:55 +02:00
EnumDev 0d3d3fafb2 Added motherboard information 2025-02-06 21:18:55 +02:00
EnumDev a99defd401 Makefile will now always use bash 2025-02-06 21:18:55 +02:00
EnumDev f34537eb5a Updated README.md 2025-01-24 18:10:17 +00:00
EnumDev 120a1cf8e4 Fix for Xfce 2024-11-04 13:37:57 +02:00
EnumDev 76974ee789 Added i3, swayfx and hyprland WM detection 2024-10-23 17:14:23 +03:00
EnumDev 20cf7fe869 Fixed error when attempting to use stormfetch without being connected to the internet and simplified fetch script 2024-09-06 19:07:45 +03:00
EnumDev c5e02c07b5 Added wayland monitor resolution support 2024-08-23 15:13:09 +03:00
EnumDev e8a95e5313 Added Local IPv4 address support 2024-08-23 14:32:26 +03:00
20 changed files with 793 additions and 674 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 CapCreeperGR Copyright (c) 2024 EnumDev
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+3 -9
View File
@@ -1,3 +1,5 @@
SHELL := /bin/bash
ifeq ($(PREFIX),) ifeq ($(PREFIX),)
PREFIX := /usr/local PREFIX := /usr/local
endif endif
@@ -13,7 +15,7 @@ endif
build: build:
mkdir -p build mkdir -p build
$(GO) build -ldflags "-w -X 'main.systemConfigDir=$(SYSCONFDIR)'" -o build/stormfetch stormfetch cd src; $(GO) build -ldflags "-w -X 'main.systemConfigDir=$(SYSCONFDIR)'" -o ../build/stormfetch stormfetch
install: build/stormfetch config/ install: build/stormfetch config/
mkdir -p $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(BINDIR)
@@ -21,14 +23,6 @@ install: build/stormfetch config/
cp build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch cp build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch
cp -r config/. $(DESTDIR)$(SYSCONFDIR)/stormfetch/ cp -r config/. $(DESTDIR)$(SYSCONFDIR)/stormfetch/
compress: build/stormfetch config/
mkdir -p stormfetch/$(BINDIR)
mkdir -p stormfetch/$(SYSCONFDIR)/stormfetch/
cp build/stormfetch stormfetch/$(BINDIR)/stormfetch
cp -r config/. stormfetch/$(SYSCONFDIR)/stormfetch/
tar --owner=root --group=root -czf stormfetch.tar.gz stormfetch
rm -r stormfetch
run: build/stormfetch run: build/stormfetch
build/stormfetch build/stormfetch
-3
View File
@@ -2,9 +2,6 @@
# Stormfetch # Stormfetch
## A simple linux fetch program written in go and bash ## A simple linux fetch program written in go and bash
### Developers:
- [CapCreeperGR ](https://gitlab.com/CapCreeperGR)
### Project Information ### Project Information
Stormfetch is a program that can read your system's information and display it in the terminal along with the ASCII art of the Linux distribution you are running. Stormfetch is a program that can read your system's information and display it in the terminal along with the ASCII art of the Linux distribution you are running.
Stormfetch is still in beta, so distro compatibility is limited. If you would like to contribute ASCII art or add other compatibility features feel free to create a pull request or notify me through GitLab Issues. Stormfetch is still in beta, so distro compatibility is limited. If you would like to contribute ASCII art or add other compatibility features feel free to create a pull request or notify me through GitLab Issues.
+12
View File
@@ -0,0 +1,12 @@
#/7;8;8;7
${C1} \\\\\\\\\\\\
\\\ \\\
\\\ \\\
\\\ \\\\\\\\\\\\\\\\\
\\\ \\\
\\\ \\\
\\\ ${C2}______ \\\
\\\ ///
\\\ ///
\\\ ///
\\\////////////////
+3 -3
View File
@@ -1,7 +1,7 @@
distro_ascii: auto distro_ascii: auto
fetch_script: auto fetch_script: auto
ansii_colors: []
force_config_ansii: false
dependency_warning: true
show_fs_type: true show_fs_type: true
hidden_partitions: []
# Hiding squashfs prevents snaps from showing up
hidden_filesystems: ["squashfs"]
hidden_gpus: [] hidden_gpus: []
+30 -28
View File
@@ -1,44 +1,46 @@
source fetch_script_functions.sh
echo -e "${C3}Distribution: ${C4}${DISTRO_LONG_NAME} ($(uname -m))" echo -e "${C3}Distribution: ${C4}${DISTRO_LONG_NAME} ($(uname -m))"
echo -e "${C3}Hostname: ${C4}$(cat /etc/hostname)" echo -e "${C3}Hostname: ${C4}$(cat /etc/hostname)"
echo -e "${C3}Kernel: ${C4}$(uname -s) $(uname -r)" echo -e "${C3}Kernel: ${C4}$(uname -s) $(uname -r)"
echo -e "${C3}Packages: ${C4}$(get_packages)" echo -e "${C3}Packages: ${C4}${PACKAGES}"
echo -e "${C3}Shell: ${C4}${USER_SHELL}" echo -e "${C3}Shell: ${C4}${USER_SHELL}"
echo -e "${C3}Init: ${C4}${INIT_SYSTEM}" echo -e "${C3}Init: ${C4}${INIT_SYSTEM}"
echo -e "${C3}Libc: ${C4}${LIBC}" echo -e "${C3}Libc: ${C4}${LIBC}"
if [ ! -z "$CPU_MODEL" ]; then echo -e "${C3}CPU: ${C4}${CPU_MODEL} (${CPU_THREADS} threads)"; fi [ -n "$MOTHERBOARD" ] && echo -e "${C3}Motherboard: ${C4}${MOTHERBOARD}"
for i in $(seq ${CONNECTED_GPUS}); do [ -n "$CPU_MODEL" ] && echo -e "${C3}CPU: ${C4}${CPU_MODEL} (${CPU_THREADS} threads)"
for i in $(seq "${CONNECTED_GPUS}"); do
gpu="GPU$i" gpu="GPU$i"
echo -e "${C3}GPU: ${C4}${!gpu}" echo -e "${C3}GPU: ${C4}${!gpu}"
done done
if [ ! -z "$MEM_TOTAL" ] && [ ! -z "$MEM_USED" ]; then echo -e "${C3}Memory: ${C4}${MEM_USED} MiB / ${MEM_TOTAL} MiB"; fi [ -n "$MEM_TOTAL" ] && [ -n "$MEM_USED" ] && echo -e "${C3}Memory: ${C4}${MEM_USED} MiB / ${MEM_TOTAL} MiB"
for i in $(seq ${MOUNTED_PARTITIONS}); do for i in $(seq "${MOUNTED_PARTITIONS}"); do
device="PARTITION${i}_DEVICE" mountpoint="PARTITION${i}_MOUNTPOINT"
mountpoint="PARTITION${i}_MOUNTPOINT" label="PARTITION${i}_LABEL"
label="PARTITION${i}_LABEL" type="PARTITION${i}_TYPE"
type="PARTITION${i}_TYPE" total="PARTITION${i}_TOTAL_SIZE"
total="PARTITION${i}_TOTAL_SIZE" used="PARTITION${i}_USED_SIZE"
used="PARTITION${i}_USED_SIZE" if [ -z "${!type}" ]; then
if [ -z "${!type}" ]; then if [ -z "${!label}" ]; then
if [ -z "${!label}" ]; then echo -e "${C3}Partition ${!mountpoint}: ${C4}${!used}/${!total}"
echo -e "${C3}Partition ${!mountpoint}: ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label}: ${C4}${!used}/${!total}"
fi
else else
if [ -z "${!label}" ]; then echo -e "${C3}Partition ${!label}: ${C4}${!used}/${!total}"
echo -e "${C3}Partition ${!mountpoint} (${!type}): ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label} (${!type}): ${C4}${!used}/${!total}"
fi
fi fi
done else
if [ ! -z "$DISPLAY_PROTOCOL" ]; then if [ -z "${!label}" ]; then
echo -e "${C3}Partition ${!mountpoint} (${!type}): ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label} (${!type}): ${C4}${!used}/${!total}"
fi
fi
done
[ -n "$LOCAL_IPV4" ] && echo -e "${C3}Local IPv4 Address: ${C4}${LOCAL_IPV4}"
if [ -n "$DISPLAY_PROTOCOL" ]; then
echo -e "${C3}Display Protocol: ${C4}${DISPLAY_PROTOCOL}" echo -e "${C3}Display Protocol: ${C4}${DISPLAY_PROTOCOL}"
for i in $(seq ${CONNECTED_MONITORS}); do for i in $(seq "${CONNECTED_MONITORS}"); do
monitor="MONITOR$i" monitor="MONITOR$i"
echo -e "${C3}Screen $i: ${C4}${!monitor}" echo -e "${C3}Screen $i: ${C4}${!monitor}"
done done
fi fi
if [ ! -z "$DE_WM" ]; then echo -e "${C3}DE/WM: ${C4}${DE_WM}"; fi [ -n "$DE_WM" ] && echo -e "${C3}DE/WM: ${C4}${DE_WM}"
# Exiting with error code 0 in case the condition above returns 1
exit 0
-40
View File
@@ -1,40 +0,0 @@
command_exists() {
if [ -z "$1" ]; then
return 1
fi
if command -v "$1" &> /dev/null; then
return 0
else
return 1
fi
}
get_packages() {
ARRAY=()
if command_exists dpkg; then
ARRAY+=("$(dpkg-query -f '.\n' -W | wc -l) (dpkg)")
fi
if command_exists pacman; then
ARRAY+=("$(pacman -Q | wc -l) (pacman)")
fi
if command_exists rpm; then
ARRAY+=("$(rpm -qa | wc -l) (rpm)")
fi
if command_exists xbps-query; then
ARRAY+=("$(xbps-query -l | wc -l) (xbps)")
fi
if command_exists bpm; then
ARRAY+=("$(bpm list -c) (bpm)")
fi
if command_exists emerge; then
ARRAY+=("$(ls -l /var/db/pkg/* | wc -l) (emerge)")
fi
if command_exists flatpak; then
ARRAY+=("$(flatpak list | wc -l) (flatpak)")
fi
if command_exists snap; then
ARRAY+=("$(snap list | wc -l) (snap)")
fi
echo "${ARRAY[@]}"
unset ARRAY
}
+23
View File
@@ -0,0 +1,23 @@
package main
import (
"fmt"
"strconv"
"strings"
)
func setupColorMap(asciiArt string) map[string]string {
colorMap := make(map[string]string)
// Read colors from ascii art
if strings.HasPrefix(asciiArt, "#/") {
firstLine := strings.Split(asciiArt, "\n")[0]
ansiColors := strings.Split(strings.TrimPrefix(firstLine, "#/"), ";")
for i, ansiColor := range ansiColors {
colorMap["C"+strconv.Itoa(i+1)] = fmt.Sprintf("\033[38;5;%sm", ansiColor)
}
}
return colorMap
}
+1 -2
View File
@@ -3,7 +3,7 @@ module stormfetch
go 1.22 go 1.22
require ( require (
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
github.com/jackmordaunt/ghw v1.0.4 github.com/jackmordaunt/ghw v1.0.4
github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/go-ps v1.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
@@ -14,7 +14,6 @@ require (
github.com/jackmordaunt/pcidb v1.0.1 // indirect github.com/jackmordaunt/pcidb v1.0.1 // indirect
github.com/jackmordaunt/wmi v1.2.4 // indirect github.com/jackmordaunt/wmi v1.2.4 // indirect
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.1 // indirect
golang.org/x/sys v0.3.0 // indirect golang.org/x/sys v0.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
) )
+2 -5
View File
@@ -1,5 +1,5 @@
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/jackmordaunt/ghw v1.0.4 h1:as+COFuPuXaNQC3WqzoHS/E2JYWZU7gN8ompNTUxNxs= github.com/jackmordaunt/ghw v1.0.4 h1:as+COFuPuXaNQC3WqzoHS/E2JYWZU7gN8ompNTUxNxs=
@@ -15,10 +15,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+75
View File
@@ -0,0 +1,75 @@
package main
import (
"fmt"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/jackmordaunt/ghw"
"os"
"os/exec"
"slices"
"strings"
)
func GetCPUModel() string {
cpu, err := ghw.CPU()
if err != nil {
return ""
}
if len(cpu.Processors) == 0 {
return ""
}
return cpu.Processors[0].Model
}
func GetCPUThreads() int {
cpu, err := ghw.CPU()
if err != nil {
return 0
}
return int(cpu.TotalThreads)
}
func GetGPUModels() (ret []string) {
cmd := exec.Command("sh", "-c", "lspci -v -m | grep 'VGA' -A6 | grep '^Device:'")
bytes, err := cmd.Output()
if err != nil {
return nil
}
for i, gpu := range strings.Split(string(bytes), "\n") {
if slices.Contains(config.HiddenGPUS, i+1) {
continue
}
if gpu == "" {
continue
}
gpu = strings.TrimPrefix(strings.TrimSpace(gpu), "Device:\t")
ret = append(ret, gpu)
}
return ret
}
func GetMotherboardModel() string {
bytes, err := os.ReadFile("/sys/devices/virtual/dmi/id/board_name")
if err != nil {
return ""
}
return strings.TrimSpace(string(bytes))
}
func GetMonitorResolution() []string {
var monitors []string
if GetDisplayProtocol() != "" {
err := glfw.Init()
if err != nil {
panic(err)
}
for _, monitor := range glfw.GetMonitors() {
mode := monitor.GetVideoMode()
monitors = append(monitors, fmt.Sprintf("%dx%d %dHz", mode.Width, mode.Height, mode.RefreshRate))
}
defer glfw.Terminate()
}
return monitors
}
+48 -92
View File
@@ -3,7 +3,6 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"gopkg.in/yaml.v3"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@@ -12,6 +11,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v3"
) )
var systemConfigDir = "/etc/" var systemConfigDir = "/etc/"
@@ -22,30 +23,28 @@ var fetchScriptPath = ""
var TimeTaken = false var TimeTaken = false
var config = StormfetchConfig{ var config = StormfetchConfig{
Ascii: "auto", Ascii: "auto",
FetchScript: "auto", FetchScript: "auto",
AnsiiColors: make([]int, 0), ShowFSType: false,
ForceConfigAnsii: false, HiddenPartitions: make([]string, 0),
DependencyWarning: true, HiddenGPUS: make([]int, 0),
ShowFSType: false,
HiddenGPUS: make([]int, 0),
} }
type StormfetchConfig struct { type StormfetchConfig struct {
Ascii string `yaml:"distro_ascii"` Ascii string `yaml:"distro_ascii"`
DistroName string `yaml:"distro_name"` DistroName string `yaml:"distro_name"`
FetchScript string `yaml:"fetch_script"` FetchScript string `yaml:"fetch_script"`
AnsiiColors []int `yaml:"ansii_colors"` AnsiiColors []int `yaml:"ansii_colors"`
ForceConfigAnsii bool `yaml:"force_config_ansii"` ForceConfigAnsii bool `yaml:"force_config_ansii"`
DependencyWarning bool `yaml:"dependency_warning"` ShowFSType bool `yaml:"show_fs_type"`
ShowFSType bool `yaml:"show_fs_type"` HiddenPartitions []string `yaml:"hidden_partitions"`
HiddenGPUS []int `yaml:"hidden_gpus"` HiddenFilesystems []string `yaml:"hidden_filesystems"`
HiddenGPUS []int `yaml:"hidden_gpus"`
} }
func main() { func main() {
readConfig() readConfig()
readFlags() readFlags()
checkDependencies()
runStormfetch() runStormfetch()
} }
@@ -95,26 +94,6 @@ func readFlags() {
flag.Parse() flag.Parse()
} }
func checkDependencies() {
// Show Dependency warning if enabled
if config.DependencyWarning {
var dependencies []string
var missing []string
for _, depend := range dependencies {
if _, err := os.Stat(path.Join("/usr/bin/", depend)); err != nil {
missing = append(missing, depend)
}
}
if len(missing) != 0 {
fmt.Println("[WARNING] Stormfetch functionality may be limited due to the following dependencies not being installed:")
for _, depend := range missing {
fmt.Println(depend)
}
fmt.Println("You can disable this warning through your stormfetch config")
}
}
}
func SetupFetchEnv(showTimeTaken bool) []string { func SetupFetchEnv(showTimeTaken bool) []string {
var env = make(map[string]string) var env = make(map[string]string)
setVariable := func(key string, setter func() string) { setVariable := func(key string, setter func() string) {
@@ -122,18 +101,20 @@ func SetupFetchEnv(showTimeTaken bool) []string {
env[key] = setter() env[key] = setter()
end := time.Now().UnixMilli() end := time.Now().UnixMilli()
if showTimeTaken { if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", key, end-start)) fmt.Printf("Setting '%s' took %d milliseconds\n", key, end-start)
} }
} }
setVariable("DISTRO_LONG_NAME", func() string { return getDistroInfo().LongName }) setVariable("PACKAGES", func() string { return GetInstalledPackages() })
setVariable("DISTRO_SHORT_NAME", func() string { return getDistroInfo().ShortName }) setVariable("DISTRO_LONG_NAME", func() string { return GetDistroInfo().LongName })
setVariable("CPU_MODEL", func() string { return getCPUName() }) setVariable("DISTRO_SHORT_NAME", func() string { return GetDistroInfo().ShortName })
setVariable("CPU_THREADS", func() string { return strconv.Itoa(getCPUThreads()) }) setVariable("CPU_MODEL", func() string { return GetCPUModel() })
setVariable("MOTHERBOARD", func() string { return GetMotherboardModel() })
setVariable("CPU_THREADS", func() string { return strconv.Itoa(GetCPUThreads()) })
start := time.Now().UnixMilli() start := time.Now().UnixMilli()
memory := GetMemoryInfo() memory := GetMemoryInfo()
end := time.Now().UnixMilli() end := time.Now().UnixMilli()
if showTimeTaken { if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MEM_*", end-start)) fmt.Printf("Setting '%s' took %d milliseconds\n", "MEM_*", end-start)
} }
if memory != nil { if memory != nil {
env["MEM_TOTAL"] = strconv.Itoa(memory.MemTotal) env["MEM_TOTAL"] = strconv.Itoa(memory.MemTotal)
@@ -141,10 +122,10 @@ func SetupFetchEnv(showTimeTaken bool) []string {
env["MEM_FREE"] = strconv.Itoa(memory.MemAvailable) env["MEM_FREE"] = strconv.Itoa(memory.MemAvailable)
} }
start = time.Now().UnixMilli() start = time.Now().UnixMilli()
partitions := getMountedPartitions() partitions := GetMountedPartitions(config.HiddenPartitions, config.HiddenFilesystems)
end = time.Now().UnixMilli() end = time.Now().UnixMilli()
if showTimeTaken { if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "PARTITION_*", end-start)) fmt.Printf("Setting '%s' took %d milliseconds\n", "PARTITION_*", end-start)
} }
if len(partitions) != 0 { if len(partitions) != 0 {
env["MOUNTED_PARTITIONS"] = strconv.Itoa(len(partitions)) env["MOUNTED_PARTITIONS"] = strconv.Itoa(len(partitions))
@@ -154,8 +135,8 @@ func SetupFetchEnv(showTimeTaken bool) []string {
if part.Label != "" { if part.Label != "" {
env["PARTITION"+strconv.Itoa(i+1)+"_LABEL"] = part.Label env["PARTITION"+strconv.Itoa(i+1)+"_LABEL"] = part.Label
} }
if part.Type != "" && config.ShowFSType { if part.FileystemType != "" && config.ShowFSType {
env["PARTITION"+strconv.Itoa(i+1)+"_TYPE"] = part.Type env["PARTITION"+strconv.Itoa(i+1)+"_TYPE"] = part.FileystemType
} }
env["PARTITION"+strconv.Itoa(i+1)+"_TOTAL_SIZE"] = FormatBytes(part.TotalSize) env["PARTITION"+strconv.Itoa(i+1)+"_TOTAL_SIZE"] = FormatBytes(part.TotalSize)
env["PARTITION"+strconv.Itoa(i+1)+"_USED_SIZE"] = FormatBytes(part.UsedSize) env["PARTITION"+strconv.Itoa(i+1)+"_USED_SIZE"] = FormatBytes(part.UsedSize)
@@ -167,11 +148,12 @@ func SetupFetchEnv(showTimeTaken bool) []string {
setVariable("DISPLAY_PROTOCOL", func() string { return GetDisplayProtocol() }) setVariable("DISPLAY_PROTOCOL", func() string { return GetDisplayProtocol() })
setVariable("LIBC", func() string { return GetLibc() }) setVariable("LIBC", func() string { return GetLibc() })
setVariable("INIT_SYSTEM", func() string { return GetInitSystem() }) setVariable("INIT_SYSTEM", func() string { return GetInitSystem() })
setVariable("LOCAL_IPV4", func() string { return GetLocalIP() })
start = time.Now().UnixMilli() start = time.Now().UnixMilli()
monitors := getMonitorResolution() monitors := GetMonitorResolution()
end = time.Now().UnixMilli() end = time.Now().UnixMilli()
if showTimeTaken { if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MONITOR_*", end-start)) fmt.Printf("Setting '%s' took %d milliseconds\n", "MONITOR_*", end-start)
} }
if len(monitors) != 0 { if len(monitors) != 0 {
env["CONNECTED_MONITORS"] = strconv.Itoa(len(monitors)) env["CONNECTED_MONITORS"] = strconv.Itoa(len(monitors))
@@ -180,10 +162,10 @@ func SetupFetchEnv(showTimeTaken bool) []string {
} }
} }
start = time.Now().UnixMilli() start = time.Now().UnixMilli()
gpus := getGPUNames() gpus := GetGPUModels()
end = time.Now().UnixMilli() end = time.Now().UnixMilli()
if showTimeTaken { if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "GPU_*", end-start)) fmt.Printf("Setting '%s' took %d milliseconds\n", "GPU_*", end-start)
} }
if len(gpus) != 0 { if len(gpus) != 0 {
env["CONNECTED_GPUS"] = strconv.Itoa(len(gpus)) env["CONNECTED_GPUS"] = strconv.Itoa(len(gpus))
@@ -205,49 +187,23 @@ func SetupFetchEnv(showTimeTaken bool) []string {
} }
func runStormfetch() { func runStormfetch() {
// Fetch ascii art and apply colors // Fetch ascii art
colorMap := make(map[string]string) asciiArt := GetDistroAsciiArt()
colorMap["C0"] = "\033[0m"
setColorMap := func() { // Setup color map
for i := 0; i < 6; i++ { colorMap := setupColorMap(asciiArt)
if i > len(config.AnsiiColors)-1 { if len(colorMap) > 0 {
colorMap["C"+strconv.Itoa(i+1)] = "\033[0m" asciiArt = os.Expand(asciiArt, func(s string) string {
continue
}
colorMap["C"+strconv.Itoa(i+1)] = fmt.Sprintf("\033[1m\033[38;5;%dm", config.AnsiiColors[i])
}
}
setColorMap()
ascii := getDistroAsciiArt()
if strings.HasPrefix(ascii, "#/") {
firstLine := strings.Split(ascii, "\n")[0]
if !config.ForceConfigAnsii {
ansiiColors := strings.Split(strings.TrimPrefix(firstLine, "#/"), ";")
for i, color := range ansiiColors {
atoi, err := strconv.Atoi(color)
if err != nil {
log.Fatal(err)
}
if i < len(config.AnsiiColors) {
config.AnsiiColors[i] = atoi
} else {
config.AnsiiColors = append(config.AnsiiColors, atoi)
}
}
setColorMap()
}
ascii = os.Expand(ascii, func(s string) string {
return colorMap[s]
})
ascii = strings.TrimPrefix(ascii, firstLine+"\n")
} else {
ascii = os.Expand(ascii, func(s string) string {
return colorMap[s] return colorMap[s]
}) })
asciiArt = strings.SplitN(asciiArt, "\n", 2)[1]
} }
asciiSplit := strings.Split(ascii, "\n")
asciiNoColor := StripAnsii(ascii) asciiSplit := strings.Split(asciiArt, "\n")
//Execute fetch script asciiNoColor := StripAnsii(asciiArt)
// Execute fetch script
cmd := exec.Command("/bin/bash", fetchScriptPath) cmd := exec.Command("/bin/bash", fetchScriptPath)
cmd.Dir = path.Dir(fetchScriptPath) cmd.Dir = path.Dir(fetchScriptPath)
cmd.Env = os.Environ() cmd.Env = os.Environ()
@@ -258,7 +214,7 @@ func runStormfetch() {
} }
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("Error: Could not run fetch script: %s", err)
} }
// Print Distro Information // Print Distro Information
maxWidth := 0 maxWidth := 0
+57
View File
@@ -0,0 +1,57 @@
package main
import (
"bufio"
"os"
"strconv"
"strings"
)
type Memory struct {
MemTotal int
MemFree int
MemAvailable int
}
func GetMemoryInfo() *Memory {
toInt := func(raw string) int {
if raw == "" {
return 0
}
res, err := strconv.Atoi(raw)
if err != nil {
panic(err)
}
return res
}
parseLine := func(raw string) (key string, value int) {
text := strings.ReplaceAll(raw[:len(raw)-2], " ", "")
keyValue := strings.Split(text, ":")
return keyValue[0], toInt(keyValue[1])
}
if _, err := os.Stat("/proc/meminfo"); err != nil {
return nil
}
file, err := os.Open("/proc/meminfo")
if err != nil {
panic(err)
}
defer file.Close()
bufio.NewScanner(file)
scanner := bufio.NewScanner(file)
res := Memory{}
for scanner.Scan() {
key, value := parseLine(scanner.Text())
switch key {
case "MemTotal":
res.MemTotal = value / 1024
case "MemFree":
res.MemFree = value / 1024
case "MemAvailable":
res.MemAvailable = value / 1024
}
}
return &res
}
+15
View File
@@ -0,0 +1,15 @@
package main
import "net"
func GetLocalIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return ""
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
+119
View File
@@ -0,0 +1,119 @@
package main
import (
"os"
"path/filepath"
"slices"
"strings"
"syscall"
)
type partition struct {
Device string
MountPoint string
Label string
FileystemType string
TotalSize uint64
UsedSize uint64
FreeSize uint64
}
func GetMountedPartitions(hiddenPartitions, hiddenFilesystems []string) []partition {
// Get all filesystem and partition labels
fslabels, err := os.ReadDir("/dev/disk/by-label")
if err != nil && !os.IsNotExist(err) {
return nil
}
partlabels, err := os.ReadDir("/dev/disk/by-partlabel")
if err != nil && !os.IsNotExist(err) {
return nil
}
labels := make(map[string]string)
for _, entry := range partlabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-partlabel/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
for _, entry := range fslabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-label/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
// Get all mounted partitions
file, err := os.ReadFile("/proc/mounts")
if err != nil {
return nil
}
var partitions []partition
for _, entry := range strings.Split(string(file), "\n") {
fields := strings.Fields(entry)
if entry == "" {
continue
}
// Skip virtual partitions not under /dev
if !strings.HasPrefix(fields[0], "/dev") {
continue
}
// Skip partition if explicitly hidden
if slices.Contains(hiddenPartitions, fields[0]) {
continue
}
// Skip filesystem if explicitely hidden
if slices.Contains(hiddenFilesystems, fields[2]) {
continue
}
p := partition{
fields[0],
fields[1],
"",
fields[2],
0,
0,
0,
}
// Skip already added partitions
skip := false
for _, part := range partitions {
if part.Device == p.Device {
skip = true
}
}
if skip {
continue
}
// Set partition label if available
if value, ok := labels[p.Device]; ok {
p.Label = value
}
// Get partition total, used and free space
buf := new(syscall.Statfs_t)
err = syscall.Statfs(p.MountPoint, buf)
if err != nil {
continue
}
totalBlocks := buf.Blocks
freeBlocks := buf.Bfree
usedBlocks := totalBlocks - freeBlocks
blockSize := uint64(buf.Bsize)
p.TotalSize = totalBlocks * blockSize
p.FreeSize = freeBlocks * blockSize
p.UsedSize = usedBlocks * blockSize
partitions = append(partitions, p)
}
return partitions
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"fmt"
"os/exec"
"strings"
)
type PackageManager struct {
Name string
ExecutableName string
PackageListCommand string
}
var PackageManagers = []PackageManager{
{Name: "dpkg", ExecutableName: "dpkg", PackageListCommand: "dpkg-query -f '${Package}\\n' -W"},
{Name: "pacman", ExecutableName: "pacman", PackageListCommand: "pacman -Q"},
{Name: "rpm", ExecutableName: "rpm", PackageListCommand: "rpm -qa"},
{Name: "xbps", ExecutableName: "xbps-query", PackageListCommand: "xbps-query -l"},
{Name: "bpm", ExecutableName: "bpm", PackageListCommand: "bpm list -n"},
{Name: "portage", ExecutableName: "emerge", PackageListCommand: "find /var/db/pkg/*/ -mindepth 1 -maxdepth 1"},
{Name: "flatpak", ExecutableName: "flatpak", PackageListCommand: "flatpak list"},
{Name: "snap", ExecutableName: "snap", PackageListCommand: "snap list | tail +2"},
}
func (pm *PackageManager) CountPackages() int {
// Return 0 if package manager is not found
if _, err := exec.LookPath(pm.ExecutableName); err != nil {
return 0
}
output, err := exec.Command("/bin/sh", "-c", pm.PackageListCommand).Output()
if err != nil {
return 0
}
return strings.Count(string(output), "\n")
}
func GetInstalledPackages() (ret string) {
for _, pm := range PackageManagers {
count := pm.CountPackages()
if count > 0 {
if ret == "" {
ret += fmt.Sprintf("%d (%s)", count, pm.Name)
} else {
ret += fmt.Sprintf(" %d (%s)", count, pm.Name)
}
}
}
return ret
}
+165
View File
@@ -0,0 +1,165 @@
package main
import (
"os"
"os/exec"
"path"
"strings"
"github.com/mitchellh/go-ps"
)
type DistroInfo struct {
ID string
LongName string
ShortName string
}
func GetDistroInfo() DistroInfo {
info := DistroInfo{
ID: "unknown",
LongName: "Unknown",
ShortName: "Unknown",
}
if strings.TrimSpace(config.DistroName) != "" {
info.LongName = strings.TrimSpace(config.DistroName)
info.ShortName = strings.TrimSpace(config.DistroName)
}
// Detect release file location
var releaseFile string
if _, err := os.Stat("/bedrock/etc/os-release"); os.Getenv("BEDROCK_RESTRICT") == "" && err == nil {
// Using Bedrock Linux
releaseFile = "/bedrock/etc/os-release"
} else if _, err := os.Stat("/etc/os-release"); err == nil {
// Using a regular linux distribution
releaseFile = "/etc/os-release"
} else {
return info
}
releaseMap, err := ReadKeyValueFile(releaseFile)
if err != nil {
return info
}
if id, ok := releaseMap["ID"]; ok {
info.ID = id
}
if longName, ok := releaseMap["PRETTY_NAME"]; ok && info.LongName == "Unknown" {
info.LongName = longName
}
if shortName, ok := releaseMap["NAME"]; ok && info.ShortName == "Unknown" {
info.ShortName = shortName
}
return info
}
func GetDistroAsciiArt() string {
defaultAscii :=
` .--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/'\
\___)=(___/ `
var id string
if config.Ascii == "auto" {
id = GetDistroInfo().ID
} else {
id = config.Ascii
}
userConfDir, err := os.UserConfigDir()
if err != nil {
if _, err := os.Stat(path.Join(systemConfigDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(systemConfigDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else {
return defaultAscii
}
}
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else if _, err := os.Stat(path.Join(systemConfigDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(systemConfigDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return strings.TrimRight(string(bytes), "\n\t ")
} else {
return defaultAscii
}
}
func GetInitSystem() string {
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
process, err := ps.FindProcess(1)
if err != nil {
return ""
}
// Special cases
// OpenRC check
if _, err := os.Stat("/usr/sbin/openrc"); err == nil {
return "OpenRC " + runCommand("openrc --version | awk '{print $3}'")
}
// Default PID 1 process name checking
switch process.Executable() {
case "systemd":
return "Systemd " + runCommand("systemctl --version | head -n1 | awk '{print $2}'")
case "runit":
return "Runit"
case "dinit":
return "Dinit " + runCommand("dinit --version | head -n1 | awk '{print substr($3, 1, length($3)-1)}'")
case "enit":
return "Enit " + runCommand("enit --version | awk '{print $3}'")
default:
return process.Executable()
}
}
func GetLibc() string {
checkLibcOutput, err := exec.Command("ldd", "/usr/bin/ls").Output()
if err != nil {
return "Unknown"
}
if strings.Contains(string(checkLibcOutput), "ld-musl") {
// Using Musl Libc
output, _ := exec.Command("ldd").CombinedOutput()
return "Musl " + strings.TrimPrefix(strings.Split(strings.TrimSpace(string(output)), "\n")[1], "Version ")
} else {
// Using Glibc
cmd := exec.Command("ldd", "--version")
output, err := cmd.Output()
if err != nil {
return "Glibc"
}
outputSplit := strings.Split(strings.Split(strings.TrimSpace(string(output)), "\n")[0], " ")
ver := outputSplit[len(outputSplit)-1]
return "Glibc " + ver
}
}
+127
View File
@@ -0,0 +1,127 @@
package main
import (
"github.com/mitchellh/go-ps"
"log"
"os"
"os/exec"
"path/filepath"
"slices"
"strconv"
"strings"
)
func GetShell() string {
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
file, err := os.ReadFile("/etc/passwd")
if err != nil {
return ""
}
str := string(file)
shell := ""
for _, line := range strings.Split(str, "\n") {
if strings.TrimSpace(line) == "" {
continue
}
userInfo := strings.Split(line, ":")
if userInfo[2] == strconv.Itoa(os.Getuid()) {
shell = userInfo[6]
}
}
shellName := filepath.Base(shell)
switch shellName {
case "dash":
return "Dash"
case "bash":
return "Bash " + runCommand("echo $BASH_VERSION")
case "zsh":
return "Zsh " + runCommand("$SHELL --version | awk '{print $2}'")
case "fish":
return "Fish " + runCommand("$SHELL --version | awk '{print $3}'")
case "nu":
return "Nushell " + runCommand("$SHELL --version")
default:
return "Unknown"
}
}
func GetDEWM() string {
processes, err := ps.Processes()
if err != nil {
log.Fatalf("Error: could not get processes: %s", err)
}
var executables []string
for _, process := range processes {
executables = append(executables, process.Executable())
}
processExists := func(process string) bool {
return slices.Contains(executables, process)
}
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
if processExists("plasmashell") {
return "KDE Plasma " + runCommand("plasmashell --version | awk '{print $2}'")
} else if processExists("gnome-session") {
return "Gnome " + runCommand("gnome-shell --version | awk '{print $3}'")
} else if processExists("xfce4-session") {
return "XFCE " + runCommand("xfce4-session --version | head -n1 | awk '{print $2}'")
} else if processExists("cinnamon") {
return "Cinnamon " + runCommand("cinnamon --version | awk '{print $3}'")
} else if processExists("mate-panel") {
return "MATE " + runCommand("mate-about --version | awk '{print $4}'")
} else if processExists("lxsession") {
return "LXDE"
} else if processExists("i3") || processExists("i3-with-shmlog") {
return "i3 " + runCommand("i3 --version | awk '{print $3}'")
} else if processExists("sway") {
if runCommand("sway --version | awk '{print $1}'") == "swayfx" {
return "SwayFX " + runCommand("sway --version | awk '{print $3}'")
} else {
return "Sway " + runCommand("sway --version | awk '{print $3}'")
}
} else if processExists("bspwm") {
return "Bspwm " + runCommand("bspwm -v")
} else if processExists("Hyprland") {
return "Hyprland " + runCommand("hyprctl version | sed -n 3p | awk '{print $2}' | tr -d 'v,'")
} else if processExists("icewm-session") {
return "IceWM " + runCommand("icewm --version | awk '{print $2}'")
}
return ""
}
func GetDisplayProtocol() string {
protocol := os.Getenv("XDG_SESSION_TYPE")
if protocol == "x11" {
return "X11"
} else if protocol == "wayland" {
return "Wayland"
}
return ""
}
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"fmt"
"math"
"os"
"regexp"
"strings"
)
func FormatBytes(bytes uint64) string {
var suffixes [6]string
suffixes[0] = "B"
suffixes[1] = "KiB"
suffixes[2] = "MiB"
suffixes[3] = "GiB"
suffixes[4] = "TiB"
suffixes[5] = "PiB"
bf := float64(bytes)
for _, unit := range suffixes {
if math.Abs(bf) < 1024.0 {
return fmt.Sprintf("%3.1f %s", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
func StripAnsii(str string) string {
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
var re = regexp.MustCompile(ansi)
return re.ReplaceAllString(str, "")
}
func ReadKeyValueFile(filepath string) (map[string]string, error) {
ret := make(map[string]string)
if _, err := os.Stat(filepath); err != nil {
return nil, err
}
bytes, err := os.ReadFile(filepath)
if err != nil {
return nil, err
}
str := string(bytes)
lines := strings.Split(str, "\n")
for _, line := range lines {
if len(strings.Split(line, "=")) >= 2 {
key := strings.SplitN(line, "=", 2)[0]
value := strings.SplitN(line, "=", 2)[1]
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
value = value[1 : len(value)-1]
}
ret[key] = value
}
}
return ret, nil
}
-490
View File
@@ -1,490 +0,0 @@
package main
import (
"bufio"
"fmt"
"github.com/BurntSushi/xgb"
"github.com/BurntSushi/xgb/xinerama"
"github.com/jackmordaunt/ghw"
"github.com/mitchellh/go-ps"
"github.com/moby/sys/mountinfo"
"log"
"math"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"syscall"
)
type DistroInfo struct {
ID string
LongName string
ShortName string
}
func getDistroInfo() DistroInfo {
info := DistroInfo{
ID: "unknown",
LongName: "Unknown",
ShortName: "Unknown",
}
if strings.TrimSpace(config.DistroName) != "" {
info.LongName = strings.TrimSpace(config.DistroName)
info.ShortName = strings.TrimSpace(config.DistroName)
}
var releaseMap = make(map[string]string)
if _, err := os.Stat("/etc/os-release"); err == nil {
releaseMap, err = ReadKeyValueFile("/etc/os-release")
if err != nil {
return info
}
}
if id, ok := releaseMap["ID"]; ok {
info.ID = id
}
if longName, ok := releaseMap["PRETTY_NAME"]; ok && info.LongName == "Unknown" {
info.LongName = longName
}
if shortName, ok := releaseMap["NAME"]; ok && info.ShortName == "Unknown" {
info.ShortName = shortName
}
return info
}
func getDistroAsciiArt() string {
defaultAscii :=
` .--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/'\
\___)=(___/ `
var id string
if config.Ascii == "auto" {
id = getDistroInfo().ID
} else {
id = config.Ascii
}
userConfDir, err := os.UserConfigDir()
if err != nil {
if _, err := os.Stat(path.Join("/etc/stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join("/etc/stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else {
return defaultAscii
}
}
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else if _, err := os.Stat(path.Join("/etc/stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join("/etc/stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return strings.TrimRight(string(bytes), "\n\t ")
} else {
return defaultAscii
}
}
func getCPUName() string {
cpu, err := ghw.CPU()
if err != nil {
return ""
}
if len(cpu.Processors) == 0 {
return ""
}
return cpu.Processors[0].Model
}
func getCPUThreads() int {
cpu, err := ghw.CPU()
if err != nil {
return 0
}
return int(cpu.TotalThreads)
}
func getGPUNames() []string {
var ret []string
cmd := exec.Command("/bin/bash", "-c", "lspci -v -m | grep 'VGA' -A6 | grep '^Device:' | sed 's/^Device://' | awk '{$1=$1};1'")
bytes, err := cmd.Output()
if err != nil {
return nil
}
for _, name := range strings.Split(string(bytes), "\n") {
name = strings.TrimSpace(name)
if name == "" {
continue
}
ret = append(ret, name)
}
return ret
}
type Memory struct {
MemTotal int
MemFree int
MemAvailable int
}
func GetMemoryInfo() *Memory {
toInt := func(raw string) int {
if raw == "" {
return 0
}
res, err := strconv.Atoi(raw)
if err != nil {
panic(err)
}
return res
}
parseLine := func(raw string) (key string, value int) {
text := strings.ReplaceAll(raw[:len(raw)-2], " ", "")
keyValue := strings.Split(text, ":")
return keyValue[0], toInt(keyValue[1])
}
if _, err := os.Stat("/proc/meminfo"); err != nil {
return nil
}
file, err := os.Open("/proc/meminfo")
if err != nil {
panic(err)
}
defer file.Close()
bufio.NewScanner(file)
scanner := bufio.NewScanner(file)
res := Memory{}
for scanner.Scan() {
key, value := parseLine(scanner.Text())
switch key {
case "MemTotal":
res.MemTotal = value / 1024
case "MemFree":
res.MemFree = value / 1024
case "MemAvailable":
res.MemAvailable = value / 1024
}
}
return &res
}
func GetShell() string {
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
file, err := os.ReadFile("/etc/passwd")
if err != nil {
return ""
}
str := string(file)
shell := ""
for _, line := range strings.Split(str, "\n") {
if strings.TrimSpace(line) == "" {
continue
}
userInfo := strings.Split(line, ":")
if userInfo[2] == strconv.Itoa(os.Getuid()) {
shell = userInfo[6]
}
}
shellName := filepath.Base(shell)
switch shellName {
case "dash":
return "Dash"
case "bash":
return "Bash " + runCommand("echo $BASH_VERSION")
case "zsh":
return "Zsh " + runCommand("$SHELL --version | awk '{print $2}'")
case "fish":
return "Fish " + runCommand("$SHELL --version | awk '{print $3}'")
case "nu":
return "Nushell " + runCommand("$SHELL --version")
default:
return "Unknown"
}
}
func GetDEWM() string {
processes, err := ps.Processes()
if err != nil {
log.Fatal(err)
}
var executables []string
for _, process := range processes {
executables = append(executables, process.Executable())
}
processExists := func(process string) bool {
return slices.Contains(executables, process)
}
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
if processExists("plasmashell") {
return "KDE Plasma " + runCommand("plasmashell --version | awk '{print $2}'")
} else if processExists("gnome-session") {
return "Gnome " + runCommand("gnome-shell --version | awk '{print $3}'")
} else if processExists("xfce4-session") {
return "XFCE " + runCommand("xfce4-session --version | grep xfce4-session | awk '{print $2}'")
} else if processExists("cinnamon") {
return "Cinnamon " + runCommand("cinnamon --version | awk '{print $3}'")
} else if processExists("mate-panel") {
return "MATE " + runCommand("mate-about --version | awk '{print $4}'")
} else if processExists("lxsession") {
return "LXDE"
} else if processExists("sway") {
return "Sway " + runCommand("sway --version | awk '{print $3}'")
} else if processExists("bspwm") {
return "Bspwm " + runCommand("bspwm -v")
} else if processExists("icewm-session") {
return "IceWM " + runCommand("icewm --version | awk '{print $2}'")
}
return ""
}
func GetDisplayProtocol() string {
protocol := os.Getenv("XDG_SESSION_TYPE")
if protocol == "x11" {
return "X11"
} else if protocol == "wayland" {
return "Wayland"
}
return ""
}
func getMonitorResolution() []string {
var monitors []string
if GetDisplayProtocol() == "X11" {
conn, err := xgb.NewConn()
if err != nil {
return nil
}
err = xinerama.Init(conn)
if err != nil {
return nil
}
reply, _ := xinerama.QueryScreens(conn).Reply()
conn.Close()
for _, screen := range reply.ScreenInfo {
monitors = append(monitors, strconv.Itoa(int(screen.Width))+"x"+strconv.Itoa(int(screen.Height)))
}
}
return monitors
}
type partition struct {
Device string
MountPoint string
Label string
Type string
TotalSize uint64
UsedSize uint64
FreeSize uint64
}
func getMountedPartitions() []partition {
mounts, err := mountinfo.GetMounts(func(info *mountinfo.Info) (skip, stop bool) {
return !strings.HasPrefix(info.Source, "/dev/"), false
})
fslabels, err := os.ReadDir("/dev/disk/by-label")
if err != nil && !os.IsNotExist(err) {
return nil
}
partlabels, err := os.ReadDir("/dev/disk/by-partlabel")
if err != nil && !os.IsNotExist(err) {
return nil
}
labels := make(map[string]string)
for _, entry := range partlabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-partlabel/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
for _, entry := range fslabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-label/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
var partitions []partition
for _, entry := range mounts {
p := partition{
entry.Source,
entry.Mountpoint,
"",
entry.FSType,
0,
0,
0,
}
skip := false
for _, part := range partitions {
if part.Device == p.Device {
skip = true
}
}
if skip {
continue
}
if value, ok := labels[entry.Source]; ok {
p.Label = value
}
buf := new(syscall.Statfs_t)
err = syscall.Statfs(p.MountPoint, buf)
if err != nil {
continue
}
totalBlocks := buf.Blocks
freeBlocks := buf.Bfree
usedBlocks := totalBlocks - freeBlocks
blockSize := uint64(buf.Bsize)
p.TotalSize = totalBlocks * blockSize
p.FreeSize = freeBlocks * blockSize
p.UsedSize = usedBlocks * blockSize
partitions = append(partitions, p)
}
return partitions
}
func GetInitSystem() string {
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
link, err := os.Readlink("/sbin/init")
if err != nil {
return "Unknown"
}
if path.Base(link) == "systemd" {
return "Systemd " + runCommand("systemctl --version | head -1 | awk '{print $2}'")
} else if path.Base(link) == "openrc-init" {
return "OpenRC " + runCommand("openrc --version | awk '{print $3}'")
} else if path.Base(link) == "runit-init" {
return "Runit"
} else {
return "Unknown"
}
}
func GetLibc() string {
cmd := exec.Command("/bin/bash", "-c", "find /usr/lib64/ -maxdepth 1 -name 'ld-*' | grep musl")
if err := cmd.Run(); err != nil {
cmd = exec.Command("/bin/bash", "-c", "ldd --version | head -1 | cut -d' ' -f4")
bytes, err := cmd.Output()
if err != nil {
return "Glibc"
}
return "Glibc " + strings.TrimSpace(string(bytes))
}
cmd = exec.Command("/bin/bash", "-c", "ldd 2>&1 | grep 'Version' | cut -d' ' -f2")
bytes, err := cmd.Output()
if err != nil {
return "Musl"
}
return "Musl " + strings.TrimSpace(string(bytes))
}
func FormatBytes(bytes uint64) string {
var suffixes [6]string
suffixes[0] = "B"
suffixes[1] = "KiB"
suffixes[2] = "MiB"
suffixes[3] = "GiB"
suffixes[4] = "TiB"
suffixes[5] = "PiB"
bf := float64(bytes)
for _, unit := range suffixes {
if math.Abs(bf) < 1024.0 {
return fmt.Sprintf("%3.1f %s", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
func StripAnsii(str string) string {
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
var re = regexp.MustCompile(ansi)
return re.ReplaceAllString(str, "")
}
func ReadKeyValueFile(filepath string) (map[string]string, error) {
ret := make(map[string]string)
if _, err := os.Stat(filepath); err != nil {
return nil, err
}
bytes, err := os.ReadFile(filepath)
if err != nil {
return nil, err
}
str := string(bytes)
lines := strings.Split(str, "\n")
for _, line := range lines {
if len(strings.Split(line, "=")) >= 2 {
key := strings.SplitN(line, "=", 2)[0]
value := strings.SplitN(line, "=", 2)[1]
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
value = value[1 : len(value)-1]
}
ret[key] = value
}
}
return ret, nil
}