38 Commits
Author SHA1 Message Date
EnumDev d3837db2df Add oxwm detection 2026-05-25 10:44:09 +03:00
EnumDev 1d42bf9126 Hide snap mounts 2026-04-23 13:47:17 +03:00
EnumDev 690fefbffb Install build-essential in publish workflow job 2026-04-18 19:12:11 +03:00
EnumDev 0d4df6b222 Update CI/CD workflow 2026-04-18 18:59:31 +03:00
EnumDev 25a27e3c16 v8.0 2026-04-18 18:49:33 +03:00
EnumDevandGitHub 5d7e72fa2e Merge pull request #2 from EnumeratedDev/debian
Merge debian and CI/CD changes
2026-04-18 15:29:25 +00:00
EnumDev 5ba86fdf1b Add sudo to CI/CD workflow 2026-04-18 18:15:22 +03:00
EnumDev 26af051000 Fix typo in CI/CD workflow 2026-04-18 18:11:28 +03:00
EnumDev b77dd0f621 Update actions/checkout to v6 2026-04-18 18:08:38 +03:00
EnumDev 47fb9b0e64 Add changelog 2026-04-18 18:06:28 +03:00
EnumDev c712f1528a Add CI/CD pipeline 2026-04-18 18:00:50 +03:00
EnumDev 33b346785b Update debian package source 2026-04-18 17:36:09 +03:00
EnumDev f04a4b000a Add debian package source 2026-04-17 17:47:33 +03:00
EnumDev 3fdafd5c0c Add .gitignore 2026-04-17 16:12:17 +03:00
EnumDev da463b35e3 Separate monitor detection code into different program 2026-04-17 13:14:04 +03:00
EnumDev a919ffb71b Fix multiple spaces when version number is empty 2026-02-16 15:18:12 +02:00
EnumDev 8147d302c8 Fix bash version checking 2026-02-16 14:15:59 +02:00
EnumDev 8dfe7b3a0e Return unknown GPU if device info is nil 2026-02-16 14:12:19 +02:00
EnumDev 8cafe93b33 Update README.md 2026-02-15 20:12:48 +02:00
EnumDev adcf06c149 Reduce gif quality for faster playback 2026-02-15 19:55:49 +02:00
EnumDev 5ef39f89cc Update preview gif 2026-02-15 19:52:35 +02:00
EnumDev 738d2c2086 Disale local IP module if no ip is available 2026-02-15 19:42:18 +02:00
EnumDevandGitHub 637bd2c3e3 Merge pull request #1 from EnumeratedDev/experimental
Merge experimental changes
2026-02-15 17:39:04 +00:00
EnumDev 08035010c9 Add used and total GPU vram variables 2025-12-29 19:30:28 +02:00
EnumDev 983b347d88 Add post installation steps to README.md 2025-12-24 17:59:07 +02:00
EnumDev db96cd7e5e Detect GPU VRAM 2025-12-24 17:52:00 +02:00
EnumDev 96a2dee0e1 Update go modules 2025-12-24 17:40:56 +02:00
EnumDev 85d96fceec Improve GPU name fetching 2025-12-24 17:28:42 +02:00
EnumDev d83440491b Merge branch 'master' into experimental 2025-12-23 16:33:58 +02:00
EnumDev 16b27c4002 Update README.md 2025-12-23 16:32:50 +02:00
EnumDev 701585bd5e Remove duplicate whitespaces in motherboard names 2025-12-23 11:42:15 +02:00
EnumDev b60c9df495 Fallback to product name if subsystem can't be detected 2025-12-23 11:27:12 +02:00
EnumDev 428ee7ceee Improve 'cpus' module 2025-12-23 11:21:40 +02:00
EnumDev fbc9977bf4 Improve 'gpus' module 2025-12-23 11:09:07 +02:00
EnumDev 2f71da0673 Rework package counting 2025-12-22 21:18:06 +02:00
EnumDev d4e324d79f Do not attempt to show memory if not found 2025-12-22 20:02:02 +02:00
EnumDev 7f7c7d646d Do not attempt to show init system if not found 2025-12-22 20:01:14 +02:00
EnumDev cecce857e1 Fetch module information outside os.Expand() function 2025-12-22 17:38:35 +02:00
31 changed files with 1033 additions and 378 deletions
+63
View File
@@ -0,0 +1,63 @@
name: CI/CD pipeline
on:
push:
branches:
- "**"
tags:
- "*"
pull_request:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Fetch repository
uses: actions/checkout@v6
- name: Upgrade packages
run: sudo apt update -y && sudo apt upgrade -y
- name: Download and install dependencies
run: sudo apt install -y make golang libgl-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libxxf86vm-dev
- name: Build using make
run: make PREFIX=/usr SYSCONFDIR=/etc
- name: Install to root using make
run: sudo make PREFIX=/usr SYSCONFDIR=/etc install
- name: Run stormfetch
run: stormfetch
publish:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && needs.test.result == 'success'
steps:
- name: Fetch repository
uses: actions/checkout@v6
- name: Check debian changelog version
run: test $(grep '^stormfetch' debian/changelog | head -n1 | grep -Po '(?<=\().*(?=\))' | cut -d '-' -f1) == ${{ github.ref_name }}
- name: Upgrade packages
run: sudo apt update -y && sudo apt upgrade -y
- name: Download and install dependencies
run: sudo apt install -y build-essential make debhelper golang libgl-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libxxf86vm-dev
- name: Build deb package
run: dpkg-buildpackage -b
- name: Read changelog
id: read_changelog
run: |
r=$(cat CHANGELOG.md) # Read CHANGELOG.md
r="${r//'%'/'%25'}" # Multiline escape sequences for %
r="${r//$'\n'/'%0A'}" # Multiline escape sequences for '\n'
r="${r//$'\r'/'%0D'}" # Multiline escape sequences for '\r'
echo "RELEASE_BODY=$r" >> $GITHUB_OUTPUT # Set environment variable
- name: Upload deb packages release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
release_name: v${{ github.ref_name }}
file: ../*.deb
tag: ${{ github.ref }}
overwrite: true
file_glob: true
draft: true
body: |
${{ steps.read_changelog.outputs.RELEASE_BODY }}
+10
View File
@@ -0,0 +1,10 @@
# Ignore IDE directories
/.idea
# Ignore build directories
/build
/deb/temp
/deb/out
# Ignore act files
event.json
+9
View File
@@ -0,0 +1,9 @@
# Stormfetch v8.0 Changelog
### Changes:
- [Add CI/CD pipeline](https://github.com/EnumeratedDev/stormfetch/commit/c712f1528af45fcd7859b28b084c3dce64d6e07c)
- [Add debian package source](https://github.com/EnumeratedDev/stormfetch/commit/f04a4b000a419181dd5eb940440676260beaae5f)
- [Separate monitor detection code into different program](https://github.com/EnumeratedDev/stormfetch/commit/da463b35e36eb4413d6ffc258f12398015fedb2b)
### Fixes
- [Return unknown GPU if device info is nil](https://github.com/EnumeratedDev/stormfetch/commit/8dfe7b3a0e0c1f95d34020b1f3fda5142f113671)
- [Fix bash version checking](https://github.com/EnumeratedDev/stormfetch/commit/8147d302c8a8d170c6942378b4b74eac77d755ac)
- [Fix multiple spaces when version number is empty](https://github.com/EnumeratedDev/stormfetch/commit/a919ffb71b78d706062030203029ef470b190e9c)
+27 -6
View File
@@ -1,24 +1,44 @@
# Installation paths # Installation paths
PREFIX ?= /usr/local PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin BINDIR ?= $(PREFIX)/bin
LIBEXECDIR ?= $(PREFIX)/libexec
SYSCONFDIR := $(PREFIX)/etc SYSCONFDIR := $(PREFIX)/etc
# Compilers and tools # Compilers and tools
GO ?= go GO ?= go
# Compiler flags
GOOS ?= $(shell $(GO) env | grep '^GOOS' | cut -d'=' -f2 | tr -d "'")
GOARCH ?= $(shell $(GO) env | grep '^GOARCH' | cut -d'=' -f2 | tr -d "'")
LDFLAGS ?= -w
# Build-time variables # Build-time variables
VERSION ?= $(shell git describe --tags --dirty) VERSION ?= $(shell git describe --tags --dirty)
build: build: build-stormfetch build-stormfetch-monitor-detection
mkdir -p build
cd src; $(GO) build -ldflags "-w -X 'main.StormfetchVersion=$(VERSION)' -X 'main.SystemConfigDir=$(SYSCONFDIR)'" -o ../build/stormfetch stormfetch
install: build/stormfetch config/ build-stormfetch:
# Create directoriy install -d build
cd src/stormfetch; GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build -ldflags "$(LDFLAGS) -X 'main.StormfetchVersion=$(VERSION)' -X 'main.SystemConfigDir=$(SYSCONFDIR)' -X 'main.LibexecDir=$(LIBEXECDIR)'" -o ../../build/stormfetch
build-stormfetch-monitor-detection:
install -d build
cd src/stormfetch-monitor-detection; GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build -ldflags "$(LDFLAGS)" -o ../../build/stormfetch-monitor-detection
install: install-stormfetch install-stormfetch-monitor-detection install-config
install-stormfetch: build/stormfetch
# Create directory
install -dm755 $(DESTDIR)$(BINDIR) install -dm755 $(DESTDIR)$(BINDIR)
# Install binary # Install binary
install -m755 build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch install -m755 build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch
install-stormfetch-monitor-detection: build/stormfetch-monitor-detection
# Create directory
install -dm755 $(DESTDIR)$(LIBEXECDIR)
# Install binary
install -m755 build/stormfetch-monitor-detection $(DESTDIR)$(LIBEXECDIR)/stormfetch-monitor-detection
install-config: install-config:
# Create directory # Create directory
install -dm755 $(DESTDIR)$(SYSCONFDIR) install -dm755 $(DESTDIR)$(SYSCONFDIR)
@@ -27,9 +47,10 @@ install-config:
uninstall: uninstall:
-rm -f $(DESTDIR)$(BINDIR)/stormfetch -rm -f $(DESTDIR)$(BINDIR)/stormfetch
-rm -f $(DESTDIR)$(LIBEXEDIR)/stormfetch-monitor-detection
-rm -rf $(DESTDIR)$(SYSCONFDIR)/stormfetch -rm -rf $(DESTDIR)$(SYSCONFDIR)/stormfetch
clean: clean:
rm -r build/ rm -r build/
.PHONY: build install install-config uninstall clean .PHONY: build build-stormfetch build-stormfetch-monitor-detection install install-config uninstall clean
+11 -4
View File
@@ -4,14 +4,18 @@
### Project Information ### Project Information
Stormfetch is a program that can read your system's information and display it in the terminal along with the ASCII art of the Linux distribution you are running. Stormfetch is a program that can read your system's information and display it in the terminal along with the ASCII art of the Linux distribution you are running.
Stormfetch is still in beta, so distro compatibility is limited. If you would like to contribute ASCII art or add other compatibility features feel free to create a pull request or notify me through GitLab Issues. At the moment ascii art for different distributions is limited. If you would like to contribute ascii art feel free to make a pull request.
### How it looks ### How it looks
![Stormfetch gif](media/stormfetch.gif) ![Stormfetch gif](media/stormfetch.gif)
### Installation Guide ### Installation Guide
#### Using a package manager #### Using a package manager
- Arch Linux: You may use your favorite AUR manager to install the `stormfetch` package | Distribution | Package name |
|:----------------------:|:------------------------------------------ |
| Arch Linux/Artix Linux | `stormfetch` from the AUR |
| Debian/Ubuntu | latest .deb file from the releases section |
| Tide Linux | `stormfetch` from the main repository |
#### Building from source #### Building from source
- Download `go` from your package manager or from the go website - Download `go` from your package manager or from the go website
- Download `make` from your package manager - Download `make` from your package manager
@@ -19,7 +23,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
+19
View File
@@ -0,0 +1,19 @@
stormfetch (8.0-1) trixie; urgency=medium
* Update package version to 8.0
-- EnumDev <[email protected]> Sat, 18 Apr 2026 14:18:37 +0300
stormfetch (7.4-1) trixie; urgency=medium
* Fix version number
* Update debian codename
* Fix debhelper dependency
-- EnumDev <[email protected]> Sat, 18 Apr 2026 14:18:37 +0300
stormfetch (7.4.3-1) stretch; urgency=medium
* Initial packaging work with dpkg-buildpackage.
-- EnumDev <[email protected]> Fri, 17 Apr 2026 16:54:39 +0300
+1
View File
@@ -0,0 +1 @@
9
+27
View File
@@ -0,0 +1,27 @@
Source: stormfetch
Priority: optional
Maintainer: EnumDev <[email protected]>
Build-Depends:
debhelper (>= 9),
libgl-dev,
libx11-dev,
libxcursor-dev,
libxi-dev,
libxinerama-dev,
libxrandr-dev,
libxxf86vm-dev
Vcs-Git: https://github.com/EnumeratedDev/stormfetch.git
Vcs-Browser: https://github.com/EnumeratedDev/stormfetch
Package: stormfetch
Section: universe/utils
Architecture: any
Depends: bash
Suggests: stormfetch-monitor-detection
Description: A linux fetch program written in go
Package: stormfetch-monitor-detection
Section: universe/utils
Architecture: any
Depends: stormfetch,libgl1,libx11-6,libxcursor1,libxi6,libxinerama1,libxrandr2,libxxf86vm1
Description: Monitor detection utility for stormfetch
Vendored Executable
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/make -f
clean:
-rm -r debian/stormfetch
-rm -r debian/stormfetch-monitor-detection
build:
# Build program
make PREFIX=/usr SYSCONFDIR=/etc
binary:
# Create directories
mkdir -p debian/stormfetch
mkdir -p debian/stormfetch-monitor-detection
# Install program
make DESTDIR=debian/stormfetch PREFIX=/usr SYSCONFDIR=/etc install-stormfetch
make DESTDIR=debian/stormfetch PREFIX=/usr SYSCONFDIR=/etc install-config
# Install monitor detection program
make DESTDIR=debian/stormfetch-monitor-detection PREFIX=/usr SYSCONFDIR=/etc install-stormfetch-monitor-detection
# Finalize packages
dh_gencontrol
dh_builddeb
Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 12 MiB

-99
View File
@@ -1,99 +0,0 @@
package main
import (
"os"
"os/exec"
"slices"
"strings"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/jackmordaunt/ghw"
)
type CPU struct {
Vendor string
Model string
Cores int
Threads int
}
type Monitor struct {
Width int
Height int
RefreshRate int
}
func GetCPUs(hiddenCPUs []int) []CPU {
ret := make([]CPU, 0)
cpus, err := ghw.CPU()
if err != nil {
return ret
}
for i, cpu := range cpus.Processors {
if slices.Contains(hiddenCPUs, i+1) {
continue
}
ret = append(ret, CPU{
Vendor: cpu.Vendor,
Model: cpu.Model,
Cores: int(cpu.NumCores),
Threads: int(cpu.NumThreads),
})
}
return ret
}
func GetGPUModels(hiddenGPUS []int) (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(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 GetMonitors() []Monitor {
var monitors []Monitor
if GetDisplayProtocol() != "" {
err := glfw.Init()
if err != nil {
panic(err)
}
for _, monitor := range glfw.GetMonitors() {
mode := monitor.GetVideoMode()
monitors = append(monitors, Monitor{
Width: mode.Width,
Height: mode.Height,
RefreshRate: mode.RefreshRate,
})
}
defer glfw.Terminate()
}
return monitors
}
-53
View File
@@ -1,53 +0,0 @@
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: "ls /var/lib/bpm/installed/"},
{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
}
+5
View File
@@ -0,0 +1,5 @@
module stormfetch-monitor-detection
go 1.24.0
require github.com/go-gl/glfw/v3.3/glfw v0.0.0-20260406072232-3ac4aa2bb164
+2
View File
@@ -0,0 +1,2 @@
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20260406072232-3ac4aa2bb164 h1:ddtotcEXIT7dFhSeXIWjK8YMFYl5bB2NdYQwgv6Uqjk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20260406072232-3ac4aa2bb164/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"fmt"
"os"
"strconv"
"github.com/go-gl/glfw/v3.3/glfw"
)
func main() {
defer func() {
if r := recover(); r != nil {
os.Exit(1)
}
}()
err := glfw.Init()
if err != nil {
os.Exit(1)
}
for _, monitor := range glfw.GetMonitors() {
mode := monitor.GetVideoMode()
fmt.Print(monitor.GetName() + ",")
fmt.Print(strconv.Itoa(mode.Width) + ",")
fmt.Print(strconv.Itoa(mode.Height) + ",")
fmt.Print(strconv.Itoa(mode.RefreshRate))
fmt.Println()
}
defer glfw.Terminate()
}
+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{
+4 -5
View File
@@ -1,19 +1,18 @@
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/jackmordaunt/ghw v1.0.5
github.com/jackmordaunt/ghw v1.0.4
github.com/mitchellh/go-ps v1.0.0 github.com/mitchellh/go-ps v1.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
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
) )
+7 -9
View File
@@ -1,9 +1,7 @@
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/jackmordaunt/ghw v1.0.5 h1:3rTXwu0D9RkungV7/WlhC8HVuDlPaq0aKY8u0I51jLY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/jackmordaunt/ghw v1.0.5/go.mod h1:VpFlLXnJErgoRttR3WOxun4v5EE8/xfB4cK26G5q2U0=
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/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=
@@ -15,9 +13,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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=
+251
View File
@@ -0,0 +1,251 @@
package main
import (
"fmt"
"os"
"os/exec"
"path"
"slices"
"strconv"
"strings"
"github.com/jackmordaunt/ghw"
)
type CPU struct {
Model string
Cores 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 {
Name string
Width int
Height int
RefreshRate int
}
func GetCPUs(hiddenCPUs []int) []CPU {
ret := make([]CPU, 0)
cpus, err := ghw.CPU()
if err != nil {
return ret
}
for i, cpu := range cpus.Processors {
if slices.Contains(hiddenCPUs, i+1) {
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{
Model: model,
Cores: int(cpu.NumCores),
Threads: int(cpu.NumThreads),
})
}
return ret
}
func GetGPUModels(hiddenGPUs []int) []GPU {
ret := make([]GPU, 0)
// Set stderr to nil to avoid warnings
stderr := os.Stderr
os.Stderr = nil
gpus, err := ghw.GPU()
if err != nil {
return ret
}
// Restore stderr
os.Stderr = stderr
for i, gpu := range gpus.GraphicsCards {
if slices.Contains(hiddenGPUs, i+1) {
continue
}
// Check if device info is set
if gpu.DeviceInfo == nil {
ret = append(ret, GPU{
PCIAddress: gpu.Address,
Vendor: "Unknown",
Name: "GPU",
Product: "Unknown",
Subsystem: "Unknown",
Driver: "Unknown",
VramTotal: "Unknown",
VramUsed: "Unknown",
})
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
}
// 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
}
func GetMotherboardModel() string {
bytes, err := os.ReadFile("/sys/devices/virtual/dmi/id/board_name")
if err != nil {
return ""
}
// Remove duplicate whitespaces
ret := strings.Join(strings.Fields(string(bytes)), " ")
return ret
}
func GetMonitors() []Monitor {
var monitors []Monitor
if GetDisplayProtocol() != "" {
// Find stormfetch-monitor-detection program
execPath, _ := os.Executable()
possiblePaths := []string{
path.Join(path.Dir(execPath), "stormfetch-monitor-detection"),
path.Join(LibexecDir, "stormfetch-monitor-detection"),
}
monitorDetectionPath := ""
for _, path := range possiblePaths {
if _, err := os.Stat(path); err == nil {
monitorDetectionPath = path
break
}
}
if monitorDetectionPath == "" {
return nil
}
// Get monitor detection program output
output, err := exec.Command(monitorDetectionPath).Output()
if err != nil {
return nil
}
// Parse output
for line := range strings.SplitSeq(strings.TrimSpace(string(output)), "\n") {
lineSplit := strings.Split(line, ",")
if len(lineSplit) != 4 {
continue
}
name := lineSplit[0]
width, err := strconv.Atoi(lineSplit[1])
height, err := strconv.Atoi(lineSplit[2])
refreshRate, err := strconv.Atoi(lineSplit[3])
if err != nil {
continue
}
monitors = append(monitors, Monitor{
Name: name,
Width: width,
Height: height,
RefreshRate: refreshRate,
})
}
}
return monitors
}
+1
View File
@@ -12,6 +12,7 @@ import (
// Build-time variables // Build-time variables
var StormfetchVersion = "dev" var StormfetchVersion = "dev"
var SystemConfigDir = "/etc/" var SystemConfigDir = "/etc/"
var LibexecDir = "/libexec/"
// Flag variables // Flag variables
var ShowVersion = false var ShowVersion = false
+50 -18
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,17 @@ 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 empty string if local IP is unavailable
if localIP == "Unknown" {
return ""
}
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 ""
} }
@@ -342,13 +375,14 @@ func initializeModuleMap() {
RegisterModule(localIpModule) RegisterModule(localIpModule)
// DEWM module // DEWM module
dewmModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "de_wm", Format: "%3${DEWM_TYPE}: %4${DEWM_NAME} ${DEWM_VERSION} ($DISPLAY_PROTOCOL)"}, Execute: func(sm StormfetchModule) string { dewmModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "de_wm", Format: "%3${DEWM_TYPE}: %4${DEWM_NAME} ($DISPLAY_PROTOCOL)"}, Execute: func(sm StormfetchModule) string {
// Return empty string if currently in TTY // Return empty string if currently in TTY
if os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "tty" { if os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "tty" {
return "" return ""
} }
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" {
@@ -361,10 +395,8 @@ func initializeModuleMap() {
return dewm.Name return dewm.Name
case "DEWM_TYPE": case "DEWM_TYPE":
return dewm.Type return dewm.Type
case "DEWM_VERSION":
return dewm.Version
case "DISPLAY_PROTOCOL": case "DISPLAY_PROTOCOL":
return GetDisplayProtocol() return displayProtocol
default: default:
return "" return ""
} }
@@ -373,15 +405,15 @@ func initializeModuleMap() {
RegisterModule(dewmModule) RegisterModule(dewmModule)
// Monitors module // Monitors module
monitorsModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "monitors", Format: "%3Monitor: %4${MONITOR_WIDTH}x${MONITOR_HEIGHT} ${MONITOR_REFRESH_RATE}Hz"}, Execute: func(sm StormfetchModule) string { monitorsModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "monitors", Format: "%3Monitor ${MONITOR_NAME}: %4${MONITOR_WIDTH}x${MONITOR_HEIGHT} ${MONITOR_REFRESH_RATE}Hz"}, Execute: func(sm StormfetchModule) string {
builder := strings.Builder{} builder := strings.Builder{}
monitors := GetMonitors() monitors := GetMonitors()
for i, monitor := range monitors { for _, monitor := range monitors {
expanded := os.Expand(sm.Format, func(s string) string { expanded := os.Expand(sm.Format, func(s string) string {
switch s { switch s {
case "MONITOR_NUM": case "MONITOR_NAME":
return strconv.Itoa(i + 1) return monitor.Name
case "MONITOR_WIDTH": case "MONITOR_WIDTH":
return strconv.Itoa(monitor.Width) return strconv.Itoa(monitor.Width)
case "MONITOR_HEIGHT": case "MONITOR_HEIGHT":
@@ -62,6 +62,11 @@ func GetMountedPartitions(hiddenPartitions, hiddenFilesystems []string) []partit
continue continue
} }
// Skip snap mountpoints
if strings.HasPrefix(fields[1], "/snap/") {
continue
}
// Skip partition if explicitly hidden // Skip partition if explicitly hidden
if slices.Contains(hiddenPartitions, fields[0]) { if slices.Contains(hiddenPartitions, fields[0]) {
continue continue
+177
View File
@@ -0,0 +1,177 @@
package main
import (
"fmt"
"os"
"os/exec"
"path"
"strconv"
"strings"
)
type PackageManager struct {
Name string
ExecutableName string
GetPackages func(...any) int
FunctionInput []any
}
var PackageManagers = []PackageManager{
{Name: "dpkg", ExecutableName: "dpkg", GetPackages: pmFileLines, FunctionInput: []any{"/var/lib/dpkg/status", "Status: install ok installed"}},
{Name: "pacman", ExecutableName: "pacman", GetPackages: pmDirectoryElements, FunctionInput: []any{"/var/lib/pacman/local/", true}},
{Name: "rpm", ExecutableName: "rpm", GetPackages: pmShellCommandLines, FunctionInput: []any{"rpm -qa"}},
{Name: "xbps", ExecutableName: "xbps-query", GetPackages: pmFileLines, FunctionInput: []any{"/var/db/xbps/pkgdb-0.38.plist", "<string>installed</string>"}},
{Name: "bpm", ExecutableName: "bpm", GetPackages: pmDirectoryElements, FunctionInput: []any{"/var/lib/bpm/installed/"}},
{Name: "portage", ExecutableName: "emerge", GetPackages: pmPortage},
{Name: "flatpak", ExecutableName: "flatpak", GetPackages: pmFlatpak},
{Name: "snap", ExecutableName: "snap", GetPackages: pmSnap},
}
func (pm *PackageManager) CountPackages() int {
// Return 0 if package manager is not found
if _, err := exec.LookPath(pm.ExecutableName); err != nil {
return 0
}
return pm.GetPackages(pm.FunctionInput...)
}
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
}
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
}
+27 -8
View File
@@ -138,24 +138,43 @@ 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 {
return "OpenRC " + runCommand("openrc --version | awk '{print $3}'", "/bin/sh") openrcVersion := runCommand("openrc --version | awk '{print $3}'", "/bin/sh")
if openrcVersion != "" {
return "OpenRC " + openrcVersion
} else {
return "OpenRC"
}
} }
// Default PID 1 process name checking // Default PID 1 process name checking
switch process.Executable() { initName := process.Executable()
initVersion := ""
switch initName {
case "systemd": case "systemd":
return "Systemd " + runCommand("systemctl --version | head -n1 | awk '{print $2}'", "/bin/sh") initName = "Systemd"
initVersion = runCommand("systemctl --version | head -n1 | awk '{print $2}'", "/bin/sh")
case "runit": case "runit":
return "Runit" initName = "Runit"
case "dinit": case "dinit":
return "Dinit " + runCommand("dinit --version | head -n1 | awk '{print substr($3, 1, length($3)-1)}'", "/bin/sh") initName = "Dinit"
initVersion = runCommand("dinit --version | head -n1 | awk '{print substr($3, 1, length($3)-1)}'", "/bin/sh")
case "enit": case "enit":
return "Enit " + runCommand("enit --version | awk '{print $3}'", "/bin/sh") initName = "Enit"
default: initVersion = runCommand("enit --version | awk '{print $3}'", "/bin/sh")
return process.Executable() }
if initVersion != "" {
return initName + " " + initVersion
} else {
return initName
} }
} }
+235
View File
@@ -0,0 +1,235 @@
package main
import (
"log"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/mitchellh/go-ps"
)
type DEWM struct {
Name string
Type string
}
func GetShell() string {
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 := "Unknown"
shellVersion := ""
switch filepath.Base(shell) {
case "bash":
shellName = "Bash"
shellVersion = runCommand("$SHELL --version | head -n1 | awk '{print $4}'", "/bin/sh")
case "zsh":
shellName = "Zsh"
shellVersion = runCommand("$SHELL --version | awk '{print $2}'", "/bin/sh")
case "fish":
shellName = "Fish"
shellVersion = runCommand("$SHELL --version | awk '{print $3}'", "/bin/sh")
case "nu":
shellName = "Nushell"
shellVersion = runCommand("$SHELL --version", "/bin/sh")
default:
return "Unknown"
}
if shellVersion != "" {
return shellName + " " + shellVersion
} else {
return shellName
}
}
func GetDEWM() DEWM {
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)
}
if processExists("plasmashell") {
dewm := DEWM{
Name: "KDE Plasma",
Type: "DE",
}
if version := runCommand("plasmashell --version | awk '{print $2}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("gnome-session") {
dewm := DEWM{
Name: "Gnome",
Type: "DE",
}
if version := runCommand("gnome-shell --version | awk '{print $3}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("xfce4-session") {
dewm := DEWM{
Name: "XFCE",
Type: "DE",
}
if version := runCommand("xfce4-session --version | head -n1 | awk '{print $2}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("cinnamon") {
dewm := DEWM{
Name: "Cinnamon",
Type: "DE",
}
if version := runCommand("cinnamon --version | awk '{print $3}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("mate-panel") {
dewm := DEWM{
Name: "MATE",
Type: "DE",
}
if version := runCommand("mate-about --version | awk '{print $4}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("lxsession") {
dewm := DEWM{
Name: "LXDE",
Type: "DE",
}
return dewm
} else if processExists("lxqt-session") {
dewm := DEWM{
Name: "LXQt",
Type: "DE",
}
if version := runCommand("lxqt-session --version | head -n1 | awk '{print $2}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("i3") || processExists("i3-with-shmlog") {
dewm := DEWM{
Name: "i3",
Type: "WM",
}
if version := runCommand("i3 --version | awk '{print $3}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("sway") {
dewm := DEWM{
Name: "Sway",
Type: "WM",
}
if runCommand("sway --version | awk '{print $1}'", "/bin/sh") == "swayfx" {
dewm.Name = "SwayFX"
} else {
dewm.Name = "Sway"
}
if version := runCommand("sway --version | awk '{print $3}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("bspwm") {
dewm := DEWM{
Name: "Bspwm",
Type: "WM",
}
if version := runCommand("bspwm -v", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("Hyprland") {
dewm := DEWM{
Name: "Hyprland",
Type: "WM",
}
if version := runCommand("hyprctl version | sed -n 3p | awk '{print $2}' | tr -d 'v,'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("icewm-session") {
dewm := DEWM{
Name: "IceWM",
Type: "WM",
}
if version := runCommand("icewm --version | awk '{print $2}'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
} else if processExists("oxwm") {
dewm := DEWM{
Name: "OXWM",
Type: "WM",
}
if version := runCommand("oxwm --version 2>&1 | tr -d 'v'", "/bin/sh"); version != "" {
dewm.Name += " " + version
}
return dewm
}
return DEWM{
Name: "Unknown",
Type: "Unknown",
}
}
func GetDisplayProtocol() string {
protocol := os.Getenv("XDG_SESSION_TYPE")
if protocol == "x11" {
return "X11"
} else if protocol == "wayland" {
return "Wayland"
}
return ""
}
+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()
-172
View File
@@ -1,172 +0,0 @@
package main
import (
"log"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/mitchellh/go-ps"
)
type DEWM struct {
Name string
Type string
Version string
}
func GetShell() string {
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", "/bin/sh")
case "zsh":
return "Zsh " + runCommand("$SHELL --version | awk '{print $2}'", "/bin/sh")
case "fish":
return "Fish " + runCommand("$SHELL --version | awk '{print $3}'", "/bin/sh")
case "nu":
return "Nushell " + runCommand("$SHELL --version", "/bin/sh")
default:
return "Unknown"
}
}
func GetDEWM() DEWM {
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)
}
if processExists("plasmashell") {
dewm := DEWM{
Name: "KDE Plasma",
Type: "DE",
Version: runCommand("plasmashell --version | awk '{print $2}'", "/bin/sh"),
}
return dewm
} else if processExists("gnome-session") {
dewm := DEWM{
Name: "Gnome",
Type: "DE",
Version: runCommand("gnome-shell --version | awk '{print $3}'", "/bin/sh"),
}
return dewm
} else if processExists("xfce4-session") {
dewm := DEWM{
Name: "XFCE",
Type: "DE",
Version: runCommand("xfce4-session --version | head -n1 | awk '{print $2}'", "/bin/sh"),
}
return dewm
} else if processExists("cinnamon") {
dewm := DEWM{
Name: "Cinnamon",
Type: "DE",
Version: runCommand("cinnamon --version | awk '{print $3}'", "/bin/sh"),
}
return dewm
} else if processExists("mate-panel") {
dewm := DEWM{
Name: "MATE",
Type: "DE",
Version: runCommand("mate-about --version | awk '{print $4}'", "/bin/sh"),
}
return dewm
} else if processExists("lxsession") {
dewm := DEWM{
Name: "LXDE",
Type: "DE",
Version: "",
}
return dewm
} else if processExists("lxqt-session") {
dewm := DEWM{
Name: "LXQt",
Type: "DE",
Version: runCommand("lxqt-session --version | head -n1 | awk '{print $2}'", "/bin/sh"),
}
return dewm
} else if processExists("i3") || processExists("i3-with-shmlog") {
dewm := DEWM{
Name: "i3",
Type: "WM",
Version: runCommand("i3 --version | awk '{print $3}'", "/bin/sh"),
}
return dewm
} else if processExists("sway") {
dewm := DEWM{
Name: "Sway",
Type: "WM",
Version: runCommand("sway --version | awk '{print $3}'", "/bin/sh"),
}
if runCommand("sway --version | awk '{print $1}'", "/bin/sh") == "swayfx" {
dewm.Name = "SwayFX"
} else {
dewm.Name = "Sway"
}
return dewm
} else if processExists("bspwm") {
dewm := DEWM{
Name: "Bspwm",
Type: "WM",
Version: runCommand("bspwm -v", "/bin/sh"),
}
return dewm
} else if processExists("Hyprland") {
dewm := DEWM{
Name: "Hyprland",
Type: "WM",
Version: runCommand("hyprctl version | sed -n 3p | awk '{print $2}' | tr -d 'v,'", "/bin/sh"),
}
return dewm
} else if processExists("icewm-session") {
dewm := DEWM{
Name: "IceWM",
Type: "WM",
Version: runCommand("icewm --version | awk '{print $2}'", "/bin/sh"),
}
return dewm
}
return DEWM{
Name: "Unknown",
Type: "Unknown",
Version: "Unknown",
}
}
func GetDisplayProtocol() string {
protocol := os.Getenv("XDG_SESSION_TYPE")
if protocol == "x11" {
return "X11"
} else if protocol == "wayland" {
return "Wayland"
}
return ""
}