Compare commits

..

30 Commits
5.0 ... master

Author SHA1 Message Date
b6dba0061d Remove developers section from README.md 2025-03-21 17:12:44 +02:00
d41c00f972 Remove dependency warnings 2025-03-21 16:11:49 +02:00
d51cfdf363 Improved GetGPUModels function 2025-03-21 16:03:15 +02:00
0f46e313e6 Remove compress goal from Makefile 2025-03-21 14:57:11 +02:00
a76295c455 Moved go files to src/ folder 2025-03-21 14:56:58 +02:00
47fd83f8cd Improve GetMountedPartitions function and added 'hidden_partitions' and 'hidden_filesystems' config options 2025-03-21 14:11:33 +02:00
ca17578cac Improve GetLibc function 2025-03-21 12:31:29 +02:00
7d121c330d Move package count detection to go code 2025-03-21 09:57:36 +02:00
6185af37d5 Add i3-with-shmlog detection 2025-03-20 13:27:46 +02:00
cd0faaefe5 Add Enit init system detection 2025-03-10 22:43:06 +02:00
4b9bc59810 Change init system checking code 2025-03-10 22:42:18 +02:00
0ed976a06e Fixed bug where the getDistroAsciiArt function would ignore the systemConfigDir variable 2025-02-06 21:18:55 +02:00
ce9813a2af Made fatal errors clearer and fixed bug in fetch script 2025-02-06 21:18:55 +02:00
0d3d3fafb2 Added motherboard information 2025-02-06 21:18:55 +02:00
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
120a1cf8e4 Fix for Xfce 2024-11-04 13:37:57 +02:00
76974ee789 Added i3, swayfx and hyprland WM detection 2024-10-23 17:14:23 +03:00
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
c5e02c07b5 Added wayland monitor resolution support 2024-08-23 15:13:09 +03:00
e8a95e5313 Added Local IPv4 address support 2024-08-23 14:32:26 +03:00
0fd198d83e Added init system support 2024-08-22 21:54:14 +03:00
673f231c42 Added nushell support 2024-08-22 21:25:46 +03:00
CapCreeperGR
e311649a48 Added PopOS ascii 2024-07-18 15:35:14 +03:00
CapCreeperGR
712fd78e21 Updated tide ascii 2024-07-18 14:12:45 +03:00
CapCreeperGR
7d83b0996d Improved GPU and added --time-taken flag 2024-07-17 20:11:30 +03:00
CapCreeperGR
f3a016b674 Added Libc information 2024-07-17 13:18:31 +03:00
CapCreeperGR
0e2631d5b0 Added Artix ascii 2024-07-16 20:55:06 +03:00
153758b68e make should now be able to locate the go executable more easily 2024-07-01 13:16:37 +03:00
ed45b739a5 Updated BPM package count fetching 2024-07-01 12:44:22 +03:00
20 changed files with 826 additions and 605 deletions

View File

@ -1,3 +1,5 @@
SHELL := /bin/bash
ifeq ($(PREFIX),)
PREFIX := /usr/local
endif
@ -8,12 +10,12 @@ ifeq ($(SYSCONFDIR),)
SYSCONFDIR := $(PREFIX)/etc
endif
ifeq ($(GO),)
GO := /usr/bin/go
GO := $(shell type -a -P go | head -n 1)
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,18 +23,10 @@ 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
clean:
rm -r build/
.PHONY: build
.PHONY: build

View File

@ -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.

20
config/ascii/artix Normal file
View File

@ -0,0 +1,20 @@
#/45;27;45;7
${C1} 'o'
'ooo'
'ooxoo'
'ooxxxoo'
'oookkxxoo'
'oiioxkkxxoo'
':;:iiiioxxxoo'
`'.;::ioxxoo'
'-. `':;jiooo'
'oooio-.. `'i:io'
'ooooxxxxoio:,. `'-;'
'ooooxxxxxkkxoooIi:-. `'
'ooooxxxxxkkkkxoiiiiiji'
'ooooxxxxxkxxoiiii:'` .i'
'ooooxxxxxoi:::'` .;ioxo'
'ooooxooi::'` .:iiixkxxo'
'ooooi:'` `'';ioxxo'
'i:'` '':io'
'` `'

21
config/ascii/pop Normal file
View File

@ -0,0 +1,21 @@
#/33;7;11;15
${C1} /////////////
/////////////////////
///////${C2}*767${C1}////////////////
//////${C2}7676767676*${C1}//////////////
/////${C2}76767${C1}//${C2}7676767${C1}//////////////
/////${C2}767676${C1}///${C2}*76767${C1}///////////////
///////${C2}767676${C1}///${C2}76767${C1}.///${C2}7676*${C1}///////
/////////${C2}767676${C1}//${C2}76767${C1}///${C2}767676${C1}////////
//////////${C2}76767676767${C1}////${C2}76767${C1}/////////
///////////${C2}76767676${C1}//////${C2}7676${C1}//////////
////////////,${C2}7676${C1},///////${C2}767${C1}///////////
/////////////*${C2}7676${C1}///////${C2}76${C1}////////////
///////////////${C2}7676${C1}////////////////////
///////////////${C2}7676${C1}///${C2}767${C1}////////////
//////////////////////${C2}'${C1}////////////
//////${C2}.7676767676767676767,${C1}//////
/////${C2}767676767676767676767${C1}/////
///////////////////////////
/////////////////////
/////////////

View File

@ -1,18 +1,15 @@
#/6;10;5;12
${C1} @@@@@@@@@
@@@~~~~~~~~~@@
@@~~~ ~~@
@~~ @
@~ @@@ @
@@~ @ @@
@@~~ @
@@@~~ ~@@ @@@
~~~ ~~@@@ @@@@~~~
~~~@@@@~~~~
@@@@ @@@@@@@ ~~~~ @@@@
~~~~@@@@ @@~~~~~~~@@ @@@@
~~~~@@@@~~ ~~@@@@~~~~
@@@ ~~~~ @@@@@ ~~~~ @@@
~~~@@@@ @@@@~~~~~@@@@ @@@@~~~
~~~~@@@~~~~ ~~~~@@@~~~
~~~ ~~~
#/27;39;5;12
${C1}@@@@ ${C1}@@@${C2}*
${C1}@@${C2}~~~~${C1}@@ ${C1}@@${C2}~~~
${C1}@@${C2}~~ ${C2}~~${C1}@ ${C1}@@${C2}~~
${C1}@@${C2}~~ ${C2}~${C1}@ ${C1}@@@${C2}~~
${C2}*${C1}@@${C2}~~ ${C1}@@@@ ${C2}~${C1}@@@@${C2}~~~ ${C1}@@@${C2}*
${C2}~~ ${C1}@@${C2}~~~~${C1}@@ ${C2}~~~~ ${C1}@@${C2}~~~
${C1}@@${C2}~~ ${C2}~~${C1}@ ${C1}@@${C2}~~
${C1}@@${C2}~~ ${C2}~${C1}@ ${C1}@@@${C2}~~
${C2}*${C1}@@${C2}~~ ${C1}@@@@ ${C2}~${C1}@@@@${C2}~~~ ${C1}@@@${C2}*
${C2}~~ ${C1}@@${C2}~~~~${C1}@@ ${C2}~~~~ ${C1}@@${C2}~~~
${C1}@@${C2}~~ ${C2}~~${C1}@ ${C1}@@${C2}~~
${C1}@@${C2}~~ ${C2}~${C1}@ ${C1}@@@${C2}~~
${C2}*${C1}@@${C2}~~ ${C2}~${C1}@@@@${C2}~~~
${C2}~~ ${C2}~~~~

View File

@ -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: []

View File

@ -1,42 +1,46 @@
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}"
if [ ! -z "$CPU_MODEL" ]; then echo -e "${C3}CPU: ${C4}${CPU_MODEL} (${CPU_THREADS} threads)"; fi
for i in $(seq ${CONNECTED_GPUS}); do
echo -e "${C3}Init: ${C4}${INIT_SYSTEM}"
echo -e "${C3}Libc: ${C4}${LIBC}"
[ -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"
mountpoint="PARTITION${i}_MOUNTPOINT"
label="PARTITION${i}_LABEL"
type="PARTITION${i}_TYPE"
total="PARTITION${i}_TOTAL_SIZE"
used="PARTITION${i}_USED_SIZE"
if [ -z "${!type}" ]; then
if [ -z "${!label}" ]; then
echo -e "${C3}Partition ${!mountpoint}: ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label}: ${C4}${!used}/${!total}"
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
mountpoint="PARTITION${i}_MOUNTPOINT"
label="PARTITION${i}_LABEL"
type="PARTITION${i}_TYPE"
total="PARTITION${i}_TOTAL_SIZE"
used="PARTITION${i}_USED_SIZE"
if [ -z "${!type}" ]; then
if [ -z "${!label}" ]; then
echo -e "${C3}Partition ${!mountpoint}: ${C4}${!used}/${!total}"
else
if [ -z "${!label}" ]; then
echo -e "${C3}Partition ${!mountpoint} (${!type}): ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label} (${!type}): ${C4}${!used}/${!total}"
fi
echo -e "${C3}Partition ${!label}: ${C4}${!used}/${!total}"
fi
done
if [ ! -z "$DISPLAY_PROTOCOL" ]; then
else
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}"
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

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 -n) (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
}

View File

@ -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
)

View File

@ -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
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
}

View File

@ -11,6 +11,7 @@ import (
"regexp"
"strconv"
"strings"
"time"
)
var systemConfigDir = "/etc/"
@ -18,31 +19,33 @@ var systemConfigDir = "/etc/"
var configPath = ""
var fetchScriptPath = ""
var TimeTaken = false
var config = StormfetchConfig{
Ascii: "auto",
FetchScript: "auto",
AnsiiColors: make([]int, 0),
ForceConfigAnsii: false,
DependencyWarning: true,
ShowFSType: false,
HiddenGPUS: make([]int, 0),
Ascii: "auto",
FetchScript: "auto",
AnsiiColors: make([]int, 0),
ForceConfigAnsii: false,
ShowFSType: false,
HiddenPartitions: make([]string, 0),
HiddenGPUS: make([]int, 0),
}
type StormfetchConfig struct {
Ascii string `yaml:"distro_ascii"`
DistroName string `yaml:"distro_name"`
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"`
HiddenGPUS []int `yaml:"hidden_gpus"`
Ascii string `yaml:"distro_ascii"`
DistroName string `yaml:"distro_name"`
FetchScript string `yaml:"fetch_script"`
AnsiiColors []int `yaml:"ansii_colors"`
ForceConfigAnsii bool `yaml:"force_config_ansii"`
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()
}
@ -86,44 +89,45 @@ func readConfig() {
}
func readFlags() {
flag.StringVar(&config.Ascii, "ascii", config.Ascii, "help message for flagname")
flag.StringVar(&config.DistroName, "distro-name", config.DistroName, "help message for flagname")
flag.StringVar(&config.Ascii, "ascii", config.Ascii, "Set distro ascii")
flag.StringVar(&config.DistroName, "distro-name", config.DistroName, "Set distro name")
flag.BoolVar(&TimeTaken, "time-taken", false, "Show time taken for fetched information")
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) {
start := time.Now().UnixMilli()
env[key] = setter()
end := time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", key, end-start))
}
}
}
func SetupFetchEnv() []string {
var env = make(map[string]string)
env["DISTRO_LONG_NAME"] = getDistroInfo().LongName
env["DISTRO_SHORT_NAME"] = getDistroInfo().ShortName
env["CPU_MODEL"] = getCPUName()
env["CPU_THREADS"] = 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()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MEM_*", end-start))
}
if memory != nil {
env["MEM_TOTAL"] = strconv.Itoa(memory.MemTotal)
env["MEM_USED"] = strconv.Itoa(memory.MemTotal - memory.MemAvailable)
env["MEM_FREE"] = strconv.Itoa(memory.MemAvailable)
}
partitions := getMountedPartitions()
start = time.Now().UnixMilli()
partitions := GetMountedPartitions(config.HiddenPartitions, config.HiddenFilesystems)
end = time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "PARTITION_*", end-start))
}
if len(partitions) != 0 {
env["MOUNTED_PARTITIONS"] = strconv.Itoa(len(partitions))
for i, part := range partitions {
@ -132,25 +136,38 @@ func SetupFetchEnv() []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)
env["PARTITION"+strconv.Itoa(i+1)+"_FREE_SIZE"] = FormatBytes(part.FreeSize)
}
}
env["DE_WM"] = GetDEWM()
env["USER_SHELL"] = GetShell()
env["DISPLAY_PROTOCOL"] = GetDisplayProtocol()
monitors := getMonitorResolution()
setVariable("DE_WM", func() string { return GetDEWM() })
setVariable("USER_SHELL", func() string { return GetShell() })
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()
end = time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MONITOR_*", end-start))
}
if len(monitors) != 0 {
env["CONNECTED_MONITORS"] = strconv.Itoa(len(monitors))
for i, monitor := range monitors {
env["MONITOR"+strconv.Itoa(i+1)] = monitor
}
}
gpus := getGPUNames()
start = time.Now().UnixMilli()
gpus := GetGPUModels()
end = time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "GPU_*", end-start))
}
if len(gpus) != 0 {
env["CONNECTED_GPUS"] = strconv.Itoa(len(gpus))
for i, gpu := range gpus {
@ -184,7 +201,7 @@ func runStormfetch() {
}
}
setColorMap()
ascii := getDistroAsciiArt()
ascii := GetDistroAsciiArt()
if strings.HasPrefix(ascii, "#/") {
firstLine := strings.Split(ascii, "\n")[0]
if !config.ForceConfigAnsii {
@ -217,14 +234,14 @@ func runStormfetch() {
cmd := exec.Command("/bin/bash", fetchScriptPath)
cmd.Dir = path.Dir(fetchScriptPath)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, SetupFetchEnv()...)
cmd.Env = append(cmd.Env, SetupFetchEnv(TimeTaken)...)
cmd.Env = append(cmd.Env, "C0=\033[0m")
for key, value := range colorMap {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
}
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
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
src/network.go Normal file
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
src/partitions.go Normal file
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
src/pms.go Normal file
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
}

153
src/system.go Normal file
View 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
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
src/utils.go Normal file
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
}

444
utils.go
View File

@ -1,444 +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
null, _ := os.Open(os.DevNull)
serr := os.Stderr
os.Stderr = null
gpu, err := ghw.GPU()
defer null.Close()
os.Stderr = serr
if err != nil {
return nil
}
for i, graphics := range gpu.GraphicsCards {
if slices.Contains(config.HiddenGPUS, i+1) {
continue
}
if graphics.DeviceInfo != nil {
ret = append(ret, graphics.DeviceInfo.Product.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}'")
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 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
}