15 Commits
10 changed files with 368 additions and 54 deletions
+5 -2
View File
@@ -19,7 +19,10 @@ Stormfetch is still in beta, so distro compatibility is limited. If you would li
``` ```
make SYSCONFDIR=/etc make SYSCONFDIR=/etc
``` ```
- Run the following command to install stormfetch into your system. You may also append a DESTDIR variable at the end of this line if you wish to install in a different location - Run the following command to install stormfetch into your system. You may also append a DESTDIR variable at the end of this line if you wish to install in a different root directory
``` ```
make install PREFIX=/usr SYSCONFDIR=/etc make install
make install-config SYSCONFDIR=/etc
``` ```
### Post installation
- (Optional) Download `curl` from your package manager to fetch the amdgpu.ids database for AMD GPUs. It may be safely uninstalled after running stormfetch once with curl installed
+1
View File
@@ -1,4 +1,5 @@
distro_ascii: auto distro_ascii: auto
disable_amdgpu_ids_warning: false
modules: modules:
- name: distribution - name: distribution
- name: hostname - name: hostname
+5 -4
View File
@@ -9,10 +9,11 @@ import (
) )
type StormfetchConfig struct { type StormfetchConfig struct {
Ascii string `yaml:"distro_ascii"` Ascii string `yaml:"distro_ascii"`
Modules []stormfetchModuleConfig `yaml:"modules"` DisableAmdgpuIdsWarning bool `yaml:"disable_amdgpu_ids_warning"`
AnsiiColors []int `yaml:"ansii_colors"` Modules []stormfetchModuleConfig `yaml:"modules"`
ForceConfigAnsii bool `yaml:"force_config_ansii"` AnsiiColors []int `yaml:"ansii_colors"`
ForceConfigAnsii bool `yaml:"force_config_ansii"`
} }
var config = StormfetchConfig{ var config = StormfetchConfig{
+5 -5
View File
@@ -1,19 +1,19 @@
module stormfetch module stormfetch
go 1.22 go 1.24.0
require ( require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728
github.com/jackmordaunt/ghw v1.0.4 github.com/jackmordaunt/ghw v1.0.5
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
) )
require ( require (
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
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
golang.org/x/sys v0.3.0 // indirect golang.org/x/sys v0.39.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
) )
+9
View File
@@ -1,9 +1,15 @@
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 h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
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/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/jackmordaunt/ghw v1.0.4 h1:as+COFuPuXaNQC3WqzoHS/E2JYWZU7gN8ompNTUxNxs= github.com/jackmordaunt/ghw v1.0.4 h1:as+COFuPuXaNQC3WqzoHS/E2JYWZU7gN8ompNTUxNxs=
github.com/jackmordaunt/ghw v1.0.4/go.mod h1:4dReYvJ36CoAzIxlEx8du25Qi/YqKYvEGE9QJoRXiK8= github.com/jackmordaunt/ghw v1.0.4/go.mod h1:4dReYvJ36CoAzIxlEx8du25Qi/YqKYvEGE9QJoRXiK8=
github.com/jackmordaunt/ghw v1.0.5 h1:3rTXwu0D9RkungV7/WlhC8HVuDlPaq0aKY8u0I51jLY=
github.com/jackmordaunt/ghw v1.0.5/go.mod h1:VpFlLXnJErgoRttR3WOxun4v5EE8/xfB4cK26G5q2U0=
github.com/jackmordaunt/pcidb v1.0.1 h1:uLLZa6kD5P39r2cwMyJJkxmuHfH9Wq19gYQEbYcB0Z4= github.com/jackmordaunt/pcidb v1.0.1 h1:uLLZa6kD5P39r2cwMyJJkxmuHfH9Wq19gYQEbYcB0Z4=
github.com/jackmordaunt/pcidb v1.0.1/go.mod h1:OMmhrZOZVu2hYXhBDZXddypxwKR/dp4DbIgzCkQDxdQ= github.com/jackmordaunt/pcidb v1.0.1/go.mod h1:OMmhrZOZVu2hYXhBDZXddypxwKR/dp4DbIgzCkQDxdQ=
github.com/jackmordaunt/wmi v1.2.4 h1:/XyuMiKby0qXNQp1j0uU0JqTWLM0QGOVok1Hf5Yagtg= github.com/jackmordaunt/wmi v1.2.4 h1:/XyuMiKby0qXNQp1j0uU0JqTWLM0QGOVok1Hf5Yagtg=
@@ -16,8 +22,11 @@ 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=
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.1.0/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=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+116 -15
View File
@@ -1,9 +1,10 @@
package main package main
import ( import (
"fmt"
"os" "os"
"os/exec"
"slices" "slices"
"strconv"
"strings" "strings"
"github.com/go-gl/glfw/v3.3/glfw" "github.com/go-gl/glfw/v3.3/glfw"
@@ -11,12 +12,22 @@ import (
) )
type CPU struct { type CPU struct {
Vendor string
Model string Model string
Cores int Cores int
Threads int Threads int
} }
type GPU struct {
PCIAddress string
Vendor string
Name string
Product string
Subsystem string
Driver string
VramTotal string
VramUsed string
}
type Monitor struct { type Monitor struct {
Width int Width int
Height int Height int
@@ -36,9 +47,23 @@ func GetCPUs(hiddenCPUs []int) []CPU {
continue continue
} }
// Remove unnecessary information from the CPU model
model := cpu.Model
stringsToRemove := []string{
" CPU", " FPU", " APU", " Processor",
" Dual-Core", " Quad-Core", " Six-Core", " Eight-Core", " Ten-Core",
" 2-Core", " 4-Core", " 6-Core", " 8-Core", " 10-Core", " 12-Core", " 14-Core", " 16-Core",
}
for _, str := range stringsToRemove {
model = strings.ReplaceAll(model, str, "")
}
model = strings.Split(model, "w/ Radeon ")[0]
model = strings.Split(model, "with Radeon ")[0]
model = strings.Split(model, "@")[0]
model = strings.TrimSpace(model)
ret = append(ret, CPU{ ret = append(ret, CPU{
Vendor: cpu.Vendor, Model: model,
Model: cpu.Model,
Cores: int(cpu.NumCores), Cores: int(cpu.NumCores),
Threads: int(cpu.NumThreads), Threads: int(cpu.NumThreads),
}) })
@@ -47,22 +72,94 @@ func GetCPUs(hiddenCPUs []int) []CPU {
return ret return ret
} }
func GetGPUModels(hiddenGPUS []int) (ret []string) { func GetGPUModels(hiddenGPUs []int) []GPU {
cmd := exec.Command("sh", "-c", "lspci -v -m | grep 'VGA' -A6 | grep '^Device:'") ret := make([]GPU, 0)
bytes, err := cmd.Output()
// Set stderr to nil to avoid warnings
stderr := os.Stderr
os.Stderr = nil
gpus, err := ghw.GPU()
if err != nil { if err != nil {
return nil return ret
} }
for i, gpu := range strings.Split(string(bytes), "\n") { // Restore stderr
if slices.Contains(hiddenGPUS, i+1) { os.Stderr = stderr
for i, gpu := range gpus.GraphicsCards {
if slices.Contains(hiddenGPUs, i+1) {
continue continue
} }
if gpu == "" {
continue // Set alternative names for vendors
var vendor string
switch gpu.DeviceInfo.Vendor.ID {
case "1002":
vendor = "AMD"
case "10de":
vendor = "Nvidia"
case "8086":
vendor = "Intel"
default:
vendor = gpu.DeviceInfo.Vendor.Name
} }
gpu = strings.TrimPrefix(strings.TrimSpace(gpu), "Device:\t")
ret = append(ret, gpu) // Set GPU name
name := ""
// Use GPU name from amdgpu.ids database
if vendor == "AMD" {
fetchedName, err := fetchAmdGpuName(gpu.DeviceInfo.Product.ID, gpu.DeviceInfo.Revision)
if err == nil && !config.DisableAmdgpuIdsWarning {
name = fetchedName
} else {
fmt.Println("Warning: could not fetch GPU name from amdgpu.ids database! Error: " + err.Error())
fmt.Println(" You can disable this warning in the configuration file")
}
}
if name == "" {
if gpu.DeviceInfo.Subsystem.Name == "" || gpu.DeviceInfo.Subsystem.Name == "unknown" {
// Set GPU name to product name
name = gpu.DeviceInfo.Product.Name
} else {
// Set GPU name to subsystem name
name = gpu.DeviceInfo.Subsystem.Name
}
// Use GPU name in brackets
leftBracket := strings.IndexByte(name, '[')
rightBracket := strings.IndexByte(name, ']')
if leftBracket != -1 && rightBracket != -1 {
name = name[leftBracket+1 : rightBracket]
}
}
// Get VRAM
vramTotal := "Unknown"
bytes, err := os.ReadFile("/sys/class/drm/card" + strconv.Itoa(gpu.Index) + "/device/mem_info_vram_total")
if err == nil {
vramUint, _ := strconv.ParseUint(strings.TrimSpace(string(bytes)), 10, 64)
vramTotal = FormatBytes(vramUint)
}
vramUsed := "Unknown"
bytes, err = os.ReadFile("/sys/class/drm/card" + strconv.Itoa(gpu.Index) + "/device/mem_info_vram_used")
if err == nil {
vramUint, _ := strconv.ParseUint(strings.TrimSpace(string(bytes)), 10, 64)
vramUsed = FormatBytes(vramUint)
}
ret = append(ret, GPU{
PCIAddress: gpu.Address,
Vendor: vendor,
Name: name,
Product: gpu.DeviceInfo.Product.Name,
Subsystem: gpu.DeviceInfo.Subsystem.Name,
Driver: gpu.DeviceInfo.Driver,
VramTotal: vramTotal,
VramUsed: vramUsed,
})
} }
return ret return ret
@@ -73,7 +170,11 @@ func GetMotherboardModel() string {
if err != nil { if err != nil {
return "" return ""
} }
return strings.TrimSpace(string(bytes))
// Remove duplicate whitespaces
ret := strings.Join(strings.Fields(string(bytes)), " ")
return ret
} }
func GetMonitors() []Monitor { func GetMonitors() []Monitor {
+40 -11
View File
@@ -100,10 +100,12 @@ func initializeModuleMap() {
// Packages module // Packages module
packagesModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "packages", Format: "%3Packages: %4$PACKAGES"}, Execute: func(sm StormfetchModule) string { packagesModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "packages", Format: "%3Packages: %4$PACKAGES"}, Execute: func(sm StormfetchModule) string {
packages := GetInstalledPackages()
return os.Expand(sm.Format, func(s string) string { return os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "PACKAGES": case "PACKAGES":
return GetInstalledPackages() return packages
default: default:
return "" return ""
} }
@@ -113,10 +115,12 @@ func initializeModuleMap() {
// Shell module // Shell module
shellModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "shell", Format: "%3Shell: %4$SHELL"}, Execute: func(sm StormfetchModule) string { shellModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "shell", Format: "%3Shell: %4$SHELL"}, Execute: func(sm StormfetchModule) string {
shell := GetShell()
return os.Expand(sm.Format, func(s string) string { return os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "SHELL": case "SHELL":
return GetShell() return shell
default: default:
return "" return ""
} }
@@ -126,10 +130,16 @@ func initializeModuleMap() {
// Init system module // Init system module
initSystemModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "init_system", Format: "%3Init: %4$INIT"}, Execute: func(sm StormfetchModule) string { initSystemModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "init_system", Format: "%3Init: %4$INIT"}, Execute: func(sm StormfetchModule) string {
initSystem := GetInitSystem()
if initSystem == "" {
return ""
}
return os.Expand(sm.Format, func(s string) string { return os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "INIT": case "INIT":
return GetInitSystem() return initSystem
default: default:
return "" return ""
} }
@@ -139,10 +149,12 @@ func initializeModuleMap() {
// Libc module // Libc module
libcModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "libc", Format: "%3Libc: %4$LIBC"}, Execute: func(sm StormfetchModule) string { libcModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "libc", Format: "%3Libc: %4$LIBC"}, Execute: func(sm StormfetchModule) string {
libc := GetLibc()
return os.Expand(sm.Format, func(s string) string { return os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "LIBC": case "LIBC":
return GetLibc() return libc
default: default:
return "" return ""
} }
@@ -188,8 +200,6 @@ func initializeModuleMap() {
switch s { switch s {
case "CPU_NUM": case "CPU_NUM":
return strconv.Itoa(i + 1) return strconv.Itoa(i + 1)
case "CPU_VENDOR":
return cpu.Vendor
case "CPU_MODEL": case "CPU_MODEL":
return cpu.Model return cpu.Model
case "CPU_CORES": case "CPU_CORES":
@@ -209,7 +219,7 @@ func initializeModuleMap() {
RegisterModule(cpusModule) RegisterModule(cpusModule)
// GPUs module // GPUs module
gpusModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "gpus", Format: "%3GPU: %4$GPU_MODEL"}, Execute: func(sm StormfetchModule) string { gpusModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "gpus", Format: "%3GPU: %4$GPU_VENDOR $GPU_NAME"}, Execute: func(sm StormfetchModule) string {
hiddenGPUsInterface, _ := sm.GetData("hidden_gpus", make([]any, 0)) hiddenGPUsInterface, _ := sm.GetData("hidden_gpus", make([]any, 0))
// Convert interface slices to string slices // Convert interface slices to string slices
@@ -226,8 +236,20 @@ func initializeModuleMap() {
switch s { switch s {
case "GPU_NUM": case "GPU_NUM":
return strconv.Itoa(i + 1) return strconv.Itoa(i + 1)
case "GPU_MODEL": case "GPU_VENDOR":
return gpu return gpu.Vendor
case "GPU_NAME":
return gpu.Name
case "GPU_PRODUCT":
return gpu.Product
case "GPU_SUBSYSTEM":
return gpu.Subsystem
case "GPU_DRIVER":
return gpu.Driver
case "GPU_VRAM_TOTAL":
return gpu.VramTotal
case "GPU_VRAM_USED":
return gpu.VramUsed
default: default:
return "" return ""
} }
@@ -244,6 +266,10 @@ func initializeModuleMap() {
memoryModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "memory", Format: "%3Memory: %4$MEM_USED MiB / $MEM_TOTAL MiB"}, Execute: func(sm StormfetchModule) string { memoryModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "memory", Format: "%3Memory: %4$MEM_USED MiB / $MEM_TOTAL MiB"}, Execute: func(sm StormfetchModule) string {
memoryInfo := GetMemoryInfo() memoryInfo := GetMemoryInfo()
if memoryInfo == nil {
return ""
}
return os.Expand(sm.Format, func(s string) string { return os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "MEM_TOTAL": case "MEM_TOTAL":
@@ -330,10 +356,12 @@ func initializeModuleMap() {
// Local IP module // Local IP module
localIpModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "local_ip", Format: "%3Local IP: %4$LOCAL_IP"}, Execute: func(sm StormfetchModule) string { localIpModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "local_ip", Format: "%3Local IP: %4$LOCAL_IP"}, Execute: func(sm StormfetchModule) string {
localIP := GetLocalIP()
return os.Expand(sm.Format, func(s string) string { return os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "LOCAL_IP": case "LOCAL_IP":
return GetLocalIP() return localIP
default: default:
return "" return ""
} }
@@ -349,6 +377,7 @@ func initializeModuleMap() {
} }
dewm := GetDEWM() dewm := GetDEWM()
displayProtocol := GetDisplayProtocol()
// Return empty string if can't detect DE/WM // Return empty string if can't detect DE/WM
if dewm.Name == "Unknown" { if dewm.Name == "Unknown" {
@@ -364,7 +393,7 @@ func initializeModuleMap() {
case "DEWM_VERSION": case "DEWM_VERSION":
return dewm.Version return dewm.Version
case "DISPLAY_PROTOCOL": case "DISPLAY_PROTOCOL":
return GetDisplayProtocol() return displayProtocol
default: default:
return "" return ""
} }
+141 -17
View File
@@ -2,25 +2,29 @@ package main
import ( import (
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"path"
"strconv"
"strings" "strings"
) )
type PackageManager struct { type PackageManager struct {
Name string Name string
ExecutableName string ExecutableName string
PackageListCommand string GetPackages func(...any) int
FunctionInput []any
} }
var PackageManagers = []PackageManager{ var PackageManagers = []PackageManager{
{Name: "dpkg", ExecutableName: "dpkg", PackageListCommand: "dpkg-query -f '${Package}\\n' -W"}, {Name: "dpkg", ExecutableName: "dpkg", GetPackages: pmFileLines, FunctionInput: []any{"/var/lib/dpkg/status", "Status: install ok installed"}},
{Name: "pacman", ExecutableName: "pacman", PackageListCommand: "pacman -Q"}, {Name: "pacman", ExecutableName: "pacman", GetPackages: pmDirectoryElements, FunctionInput: []any{"/var/lib/pacman/local/", true}},
{Name: "rpm", ExecutableName: "rpm", PackageListCommand: "rpm -qa"}, {Name: "rpm", ExecutableName: "rpm", GetPackages: pmShellCommandLines, FunctionInput: []any{"rpm -qa"}},
{Name: "xbps", ExecutableName: "xbps-query", PackageListCommand: "xbps-query -l"}, {Name: "xbps", ExecutableName: "xbps-query", GetPackages: pmFileLines, FunctionInput: []any{"/var/db/xbps/pkgdb-0.38.plist", "<string>installed</string>"}},
{Name: "bpm", ExecutableName: "bpm", PackageListCommand: "ls /var/lib/bpm/installed/"}, {Name: "bpm", ExecutableName: "bpm", GetPackages: pmDirectoryElements, FunctionInput: []any{"/var/lib/bpm/installed/"}},
{Name: "portage", ExecutableName: "emerge", PackageListCommand: "find /var/db/pkg/*/ -mindepth 1 -maxdepth 1"}, {Name: "portage", ExecutableName: "emerge", GetPackages: pmPortage},
{Name: "flatpak", ExecutableName: "flatpak", PackageListCommand: "flatpak list"}, {Name: "flatpak", ExecutableName: "flatpak", GetPackages: pmFlatpak},
{Name: "snap", ExecutableName: "snap", PackageListCommand: "snap list | tail +2"}, {Name: "snap", ExecutableName: "snap", GetPackages: pmSnap},
} }
func (pm *PackageManager) CountPackages() int { func (pm *PackageManager) CountPackages() int {
@@ -29,12 +33,7 @@ func (pm *PackageManager) CountPackages() int {
return 0 return 0
} }
output, err := exec.Command("/bin/sh", "-c", pm.PackageListCommand).Output() return pm.GetPackages(pm.FunctionInput...)
if err != nil {
return 0
}
return strings.Count(string(output), "\n")
} }
func GetInstalledPackages() (ret string) { func GetInstalledPackages() (ret string) {
@@ -51,3 +50,128 @@ func GetInstalledPackages() (ret string) {
return ret return ret
} }
func pmDirectoryElements(args ...any) int {
directory := args[0].(string)
dirsOnly := false
if len(args) >= 2 {
dirsOnly = args[1].(bool)
}
total := 0
dirEntries, _ := os.ReadDir(directory)
for _, entry := range dirEntries {
if !entry.IsDir() && dirsOnly {
continue
}
total++
}
return total
}
func pmFileLines(args ...any) int {
filepath := args[0].(string)
mustContain := ""
if len(args) >= 2 {
mustContain = args[1].(string)
}
total := 0
content, err := os.ReadFile(filepath)
if err == nil {
for _, line := range strings.Split(strings.TrimSpace(string(content)), "\n") {
if mustContain != "" && !strings.Contains(line, mustContain) {
continue
}
total++
}
}
return total
}
func pmShellCommandLines(args ...any) int {
command := args[0].(string)
output := runCommand(command, "/bin/sh")
return len(strings.Split(output, "\n"))
}
func pmShellCommandOutput(args ...any) int {
command := args[0].(string)
output := runCommand(command, "/bin/sh")
packageCount, _ := strconv.Atoi(output)
return packageCount
}
func pmPortage(args ...any) int {
portageDir := "/var/db/pkg"
total := 0
dirEntries, _ := os.ReadDir(portageDir)
for _, repo := range dirEntries {
if !repo.IsDir() {
continue
}
packages, _ := os.ReadDir(path.Join(portageDir, repo.Name()))
total += len(packages)
}
return total
}
func pmFlatpak(args ...any) int {
arch := GetArch()
flatpakDir := "/var/lib/flatpak"
total := 0
// Count applications
apps, err := os.ReadDir(path.Join(flatpakDir, "app"))
if err == nil {
for _, app := range apps {
if strings.HasSuffix(app.Name(), ".Locale") || strings.HasSuffix(app.Name(), ".Debug") {
continue
}
dirEntries, _ := os.ReadDir(path.Join(flatpakDir, "app", app.Name(), arch))
total += len(dirEntries)
}
}
// Count runtimes
runtimes, err := os.ReadDir(path.Join(flatpakDir, "runtime"))
if err == nil {
for _, runtime := range runtimes {
if strings.HasSuffix(runtime.Name(), ".Locale") || strings.HasSuffix(runtime.Name(), ".Debug") {
continue
}
dirEntries, _ := os.ReadDir(path.Join(flatpakDir, "runtime", runtime.Name(), arch))
total += len(dirEntries)
}
}
return total
}
func pmSnap(args ...any) int {
total := pmDirectoryElements("/snap", true)
if total > 0 {
return total - 1
}
total = pmDirectoryElements("/var/lib/snapd/snap", true)
if total > 0 {
return total - 1
}
return 0
}
+5
View File
@@ -138,6 +138,11 @@ func GetInitSystem() string {
return "" return ""
} }
// Return if init system can't be found
if process == nil {
return ""
}
// Special cases // Special cases
// OpenRC check // OpenRC check
if _, err := os.Stat("/usr/sbin/openrc"); err == nil { if _, err := os.Stat("/usr/sbin/openrc"); err == nil {
+41
View File
@@ -5,6 +5,7 @@ import (
"math" "math"
"os" "os"
"os/exec" "os/exec"
"path"
"regexp" "regexp"
"strings" "strings"
) )
@@ -58,6 +59,46 @@ func ReadKeyValueFile(filepath string) (map[string]string, error) {
return ret, nil return ret, nil
} }
func fetchAmdGpuName(productId, revision string) (string, error) {
productId = strings.ToUpper(productId)
revision = strings.ToUpper(revision[2:])
// Get cache directory
cachedir, err := os.UserCacheDir()
if err != nil {
return "", err
}
// Ensure amdgpu.ids file exists and download it if it doesn't
if _, err := os.Stat(path.Join(cachedir, "amdgpu.ids")); err != nil {
cmd := exec.Command("curl", "-o", path.Join(cachedir, "amdgpu.ids"), "https://gitlab.freedesktop.org/mesa/libdrm/-/raw/main/data/amdgpu.ids")
err = cmd.Run()
if err != nil {
return "", fmt.Errorf("Could not fetch amdgpu.ids using curl")
}
}
// Read amdgpu.ids file
amdgpuIds, err := os.ReadFile(path.Join(cachedir, "amdgpu.ids"))
if err != nil {
return "", err
}
// Parse read data and find GPU
for _, line := range strings.Split(string(amdgpuIds), "\n") {
if len(line) < 2 || line[0] == '#' {
continue
}
fields := strings.Split(line, ",\t")
if fields[0] == productId && fields[1] == revision {
return strings.TrimPrefix(fields[2], "AMD "), nil
}
}
return "", nil
}
func runCommand(command string, shell string) string { func runCommand(command string, shell string) string {
cmd := exec.Command(shell, "-c", command) cmd := exec.Command(shell, "-c", command)
workdir, err := os.Getwd() workdir, err := os.Getwd()