Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
b6dba0061d | |||
d41c00f972 | |||
d51cfdf363 | |||
0f46e313e6 | |||
a76295c455 | |||
47fd83f8cd | |||
ca17578cac | |||
7d121c330d | |||
6185af37d5 | |||
cd0faaefe5 | |||
4b9bc59810 | |||
0ed976a06e | |||
ce9813a2af | |||
0d3d3fafb2 | |||
a99defd401 | |||
![]() |
f34537eb5a | ||
120a1cf8e4 | |||
76974ee789 | |||
20cf7fe869 | |||
c5e02c07b5 | |||
e8a95e5313 |
12
Makefile
12
Makefile
@ -1,3 +1,5 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
ifeq ($(PREFIX),)
|
||||
PREFIX := /usr/local
|
||||
endif
|
||||
@ -13,7 +15,7 @@ endif
|
||||
|
||||
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/
|
||||
mkdir -p $(DESTDIR)$(BINDIR)
|
||||
@ -21,14 +23,6 @@ install: build/stormfetch config/
|
||||
cp build/stormfetch $(DESTDIR)$(BINDIR)/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
|
||||
build/stormfetch
|
||||
|
||||
|
@ -2,9 +2,6 @@
|
||||
# Stormfetch
|
||||
## A simple linux fetch program written in go and bash
|
||||
|
||||
### Developers:
|
||||
- [CapCreeperGR ](https://gitlab.com/CapCreeperGR)
|
||||
|
||||
### 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 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.
|
||||
|
@ -2,6 +2,8 @@ distro_ascii: auto
|
||||
fetch_script: auto
|
||||
ansii_colors: []
|
||||
force_config_ansii: false
|
||||
dependency_warning: true
|
||||
show_fs_type: true
|
||||
hidden_partitions: []
|
||||
# Hiding squashfs prevents snaps from showing up
|
||||
hidden_filesystems: ["squashfs"]
|
||||
hidden_gpus: []
|
||||
|
@ -1,20 +1,18 @@
|
||||
source fetch_script_functions.sh
|
||||
|
||||
echo -e "${C3}Distribution: ${C4}${DISTRO_LONG_NAME} ($(uname -m))"
|
||||
echo -e "${C3}Hostname: ${C4}$(cat /etc/hostname)"
|
||||
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}Init: ${C4}${INIT_SYSTEM}"
|
||||
echo -e "${C3}Libc: ${C4}${LIBC}"
|
||||
if [ ! -z "$CPU_MODEL" ]; then echo -e "${C3}CPU: ${C4}${CPU_MODEL} (${CPU_THREADS} threads)"; fi
|
||||
for i in $(seq ${CONNECTED_GPUS}); do
|
||||
[ -n "$MOTHERBOARD" ] && echo -e "${C3}Motherboard: ${C4}${MOTHERBOARD}"
|
||||
[ -n "$CPU_MODEL" ] && echo -e "${C3}CPU: ${C4}${CPU_MODEL} (${CPU_THREADS} threads)"
|
||||
for i in $(seq "${CONNECTED_GPUS}"); do
|
||||
gpu="GPU$i"
|
||||
echo -e "${C3}GPU: ${C4}${!gpu}"
|
||||
done
|
||||
if [ ! -z "$MEM_TOTAL" ] && [ ! -z "$MEM_USED" ]; then echo -e "${C3}Memory: ${C4}${MEM_USED} MiB / ${MEM_TOTAL} MiB"; fi
|
||||
for i in $(seq ${MOUNTED_PARTITIONS}); do
|
||||
device="PARTITION${i}_DEVICE"
|
||||
[ -n "$MEM_TOTAL" ] && [ -n "$MEM_USED" ] && echo -e "${C3}Memory: ${C4}${MEM_USED} MiB / ${MEM_TOTAL} MiB"
|
||||
for i in $(seq "${MOUNTED_PARTITIONS}"); do
|
||||
mountpoint="PARTITION${i}_MOUNTPOINT"
|
||||
label="PARTITION${i}_LABEL"
|
||||
type="PARTITION${i}_TYPE"
|
||||
@ -34,11 +32,15 @@ for i in $(seq ${MOUNTED_PARTITIONS}); do
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ ! -z "$DISPLAY_PROTOCOL" ]; then
|
||||
[ -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}"
|
||||
for i in $(seq ${CONNECTED_MONITORS}); do
|
||||
for i in $(seq "${CONNECTED_MONITORS}"); do
|
||||
monitor="MONITOR$i"
|
||||
echo -e "${C3}Screen $i: ${C4}${!monitor}"
|
||||
done
|
||||
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
|
@ -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
|
||||
}
|
@ -3,7 +3,7 @@ module stormfetch
|
||||
go 1.22
|
||||
|
||||
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/mitchellh/go-ps v1.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -14,7 +14,6 @@ require (
|
||||
github.com/jackmordaunt/pcidb v1.0.1 // indirect
|
||||
github.com/jackmordaunt/wmi v1.2.4 // 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
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
@ -1,5 +1,5 @@
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
|
||||
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 h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
||||
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/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
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/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/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
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=
|
75
src/hardware.go
Normal file
75
src/hardware.go
Normal 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
|
||||
}
|
@ -26,8 +26,8 @@ var config = StormfetchConfig{
|
||||
FetchScript: "auto",
|
||||
AnsiiColors: make([]int, 0),
|
||||
ForceConfigAnsii: false,
|
||||
DependencyWarning: true,
|
||||
ShowFSType: false,
|
||||
HiddenPartitions: make([]string, 0),
|
||||
HiddenGPUS: make([]int, 0),
|
||||
}
|
||||
|
||||
@ -37,15 +37,15 @@ type StormfetchConfig struct {
|
||||
FetchScript string `yaml:"fetch_script"`
|
||||
AnsiiColors []int `yaml:"ansii_colors"`
|
||||
ForceConfigAnsii bool `yaml:"force_config_ansii"`
|
||||
DependencyWarning bool `yaml:"dependency_warning"`
|
||||
ShowFSType bool `yaml:"show_fs_type"`
|
||||
HiddenPartitions []string `yaml:"hidden_partitions"`
|
||||
HiddenFilesystems []string `yaml:"hidden_filesystems"`
|
||||
HiddenGPUS []int `yaml:"hidden_gpus"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
readConfig()
|
||||
readFlags()
|
||||
checkDependencies()
|
||||
runStormfetch()
|
||||
}
|
||||
|
||||
@ -95,26 +95,6 @@ func readFlags() {
|
||||
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 {
|
||||
var env = make(map[string]string)
|
||||
setVariable := func(key string, setter func() string) {
|
||||
@ -125,10 +105,12 @@ func SetupFetchEnv(showTimeTaken bool) []string {
|
||||
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", key, end-start))
|
||||
}
|
||||
}
|
||||
setVariable("DISTRO_LONG_NAME", func() string { return getDistroInfo().LongName })
|
||||
setVariable("DISTRO_SHORT_NAME", func() string { return getDistroInfo().ShortName })
|
||||
setVariable("CPU_MODEL", func() string { return getCPUName() })
|
||||
setVariable("CPU_THREADS", func() string { return strconv.Itoa(getCPUThreads()) })
|
||||
setVariable("PACKAGES", func() string { return GetInstalledPackages() })
|
||||
setVariable("DISTRO_LONG_NAME", func() string { return GetDistroInfo().LongName })
|
||||
setVariable("DISTRO_SHORT_NAME", func() string { return GetDistroInfo().ShortName })
|
||||
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()
|
||||
memory := GetMemoryInfo()
|
||||
end := time.Now().UnixMilli()
|
||||
@ -141,7 +123,7 @@ func SetupFetchEnv(showTimeTaken bool) []string {
|
||||
env["MEM_FREE"] = strconv.Itoa(memory.MemAvailable)
|
||||
}
|
||||
start = time.Now().UnixMilli()
|
||||
partitions := getMountedPartitions()
|
||||
partitions := GetMountedPartitions(config.HiddenPartitions, config.HiddenFilesystems)
|
||||
end = time.Now().UnixMilli()
|
||||
if showTimeTaken {
|
||||
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "PARTITION_*", end-start))
|
||||
@ -154,8 +136,8 @@ func SetupFetchEnv(showTimeTaken bool) []string {
|
||||
if part.Label != "" {
|
||||
env["PARTITION"+strconv.Itoa(i+1)+"_LABEL"] = part.Label
|
||||
}
|
||||
if part.Type != "" && config.ShowFSType {
|
||||
env["PARTITION"+strconv.Itoa(i+1)+"_TYPE"] = part.Type
|
||||
if part.FileystemType != "" && config.ShowFSType {
|
||||
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)+"_USED_SIZE"] = FormatBytes(part.UsedSize)
|
||||
@ -167,8 +149,9 @@ func SetupFetchEnv(showTimeTaken bool) []string {
|
||||
setVariable("DISPLAY_PROTOCOL", func() string { return GetDisplayProtocol() })
|
||||
setVariable("LIBC", func() string { return GetLibc() })
|
||||
setVariable("INIT_SYSTEM", func() string { return GetInitSystem() })
|
||||
setVariable("LOCAL_IPV4", func() string { return GetLocalIP() })
|
||||
start = time.Now().UnixMilli()
|
||||
monitors := getMonitorResolution()
|
||||
monitors := GetMonitorResolution()
|
||||
end = time.Now().UnixMilli()
|
||||
if showTimeTaken {
|
||||
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MONITOR_*", end-start))
|
||||
@ -180,7 +163,7 @@ func SetupFetchEnv(showTimeTaken bool) []string {
|
||||
}
|
||||
}
|
||||
start = time.Now().UnixMilli()
|
||||
gpus := getGPUNames()
|
||||
gpus := GetGPUModels()
|
||||
end = time.Now().UnixMilli()
|
||||
if showTimeTaken {
|
||||
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "GPU_*", end-start))
|
||||
@ -218,7 +201,7 @@ func runStormfetch() {
|
||||
}
|
||||
}
|
||||
setColorMap()
|
||||
ascii := getDistroAsciiArt()
|
||||
ascii := GetDistroAsciiArt()
|
||||
if strings.HasPrefix(ascii, "#/") {
|
||||
firstLine := strings.Split(ascii, "\n")[0]
|
||||
if !config.ForceConfigAnsii {
|
||||
@ -258,7 +241,7 @@ func runStormfetch() {
|
||||
}
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf("Error: Could not run fetch script: %s", err)
|
||||
}
|
||||
// Print Distro Information
|
||||
maxWidth := 0
|
57
src/memory.go
Normal file
57
src/memory.go
Normal 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
src/network.go
Normal file
15
src/network.go
Normal 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
src/partitions.go
Normal file
119
src/partitions.go
Normal 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
src/pms.go
Normal file
53
src/pms.go
Normal 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
|
||||
}
|
153
src/system.go
Normal file
153
src/system.go
Normal file
@ -0,0 +1,153 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/go-ps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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(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
src/user.go
Normal file
127
src/user.go
Normal 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
src/utils.go
Normal file
58
src/utils.go
Normal 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
utils.go
490
utils.go
@ -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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user