mirror of
https://github.com/EnumeratedDev/stormfetch.git
synced 2026-09-16 07:56:12 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ba86fdf1b
|
||
|
|
26af051000
|
||
|
|
b77dd0f621
|
||
|
|
47fb9b0e64
|
||
|
|
c712f1528a
|
||
|
|
33b346785b
|
||
|
|
f04a4b000a
|
||
|
|
3fdafd5c0c
|
||
|
|
da463b35e3
|
||
|
|
a919ffb71b
|
||
|
|
8147d302c8
|
||
|
|
8dfe7b3a0e
|
||
|
|
8cafe93b33
|
||
|
|
adcf06c149
|
||
|
|
5ef39f89cc
|
||
|
|
738d2c2086
|
||
|
|
637bd2c3e3 | ||
|
|
08035010c9
|
||
|
|
983b347d88
|
||
|
|
db96cd7e5e
|
||
|
|
96a2dee0e1
|
||
|
|
85d96fceec
|
||
|
|
d83440491b
|
||
|
|
16b27c4002
|
||
|
|
701585bd5e
|
||
|
|
b60c9df495
|
||
|
|
428ee7ceee
|
||
|
|
fbc9977bf4
|
||
|
|
2f71da0673
|
||
|
|
d4e324d79f
|
||
|
|
7f7c7d646d
|
||
|
|
cecce857e1
|
||
|
|
fabc1adbf7
|
||
|
|
3b7cb5e4e2
|
||
|
|
1b0ad32b6b
|
||
|
|
b52145614f
|
||
|
|
705a40047b
|
||
|
|
341138116b
|
||
|
|
051301aacd
|
||
|
|
3131d94429
|
||
|
|
c7bfaf7a31
|
@@ -0,0 +1,61 @@
|
|||||||
|
name: CI/CD pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
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 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
@@ -0,0 +1,10 @@
|
|||||||
|
# Ignore IDE directories
|
||||||
|
/.idea
|
||||||
|
|
||||||
|
# Ignore build directories
|
||||||
|
/build
|
||||||
|
/deb/temp
|
||||||
|
/deb/out
|
||||||
|
|
||||||
|
# Ignore act files
|
||||||
|
event.json
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||

|

|
||||||
|
|
||||||
### 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
|
||||||
|
|||||||
+2
-1
@@ -1,11 +1,12 @@
|
|||||||
distro_ascii: auto
|
distro_ascii: auto
|
||||||
|
disable_amdgpu_ids_warning: false
|
||||||
modules:
|
modules:
|
||||||
- name: distribution
|
- name: distribution
|
||||||
- name: hostname
|
- name: hostname
|
||||||
- name: kernel
|
- name: kernel
|
||||||
- name: packages
|
- name: packages
|
||||||
- name: shell
|
- name: shell
|
||||||
- name: init
|
- name: init_system
|
||||||
- name: motherboard
|
- name: motherboard
|
||||||
- name: cpus
|
- name: cpus
|
||||||
- name: gpus
|
- name: gpus
|
||||||
|
|||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
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
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
9
|
||||||
Vendored
+27
@@ -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
|
||||||
+22
@@ -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 |
@@ -1,24 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupColorMap(asciiArtHeader string) map[int]string {
|
|
||||||
colorMap := make(map[int]string)
|
|
||||||
colorMap[0] = "\033[0m"
|
|
||||||
|
|
||||||
// Return if header is empty
|
|
||||||
if asciiArtHeader == "" {
|
|
||||||
return colorMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read colors from ascii art header
|
|
||||||
ansiColors := strings.Split(strings.TrimPrefix(asciiArtHeader, "#/"), ";")
|
|
||||||
for i, ansiColor := range ansiColors {
|
|
||||||
colorMap[i+1] = fmt.Sprintf("\033[38;5;%sm", ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return colorMap
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StormfetchConfig struct {
|
|
||||||
Ascii string `yaml:"distro_ascii"`
|
|
||||||
DistroName string `yaml:"distro_name"`
|
|
||||||
Modules []stormfetchModuleConfig `yaml:"modules"`
|
|
||||||
AnsiiColors []int `yaml:"ansii_colors"`
|
|
||||||
ForceConfigAnsii bool `yaml:"force_config_ansii"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = StormfetchConfig{
|
|
||||||
Ascii: "auto",
|
|
||||||
Modules: make([]stormfetchModuleConfig, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfig() {
|
|
||||||
// Get home directory
|
|
||||||
userConfigDir, _ := os.UserConfigDir()
|
|
||||||
|
|
||||||
// Find valid config directory
|
|
||||||
if _, err := os.Stat(path.Join(userConfigDir, "stormfetch/config.yml")); err == nil {
|
|
||||||
configPath = path.Join(userConfigDir, "stormfetch/config.yml")
|
|
||||||
} else if _, err := os.Stat(path.Join(SystemConfigDir, "stormfetch/config.yml")); err == nil {
|
|
||||||
configPath = path.Join(SystemConfigDir, "stormfetch/config.yml")
|
|
||||||
} else {
|
|
||||||
log.Fatalf("Config file not found: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse config
|
|
||||||
bytes, err := os.ReadFile(configPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = yaml.Unmarshal(bytes, &config)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -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=
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupColorMap(asciiArtHeader string) []string {
|
||||||
|
colorMap := make([]string, 10)
|
||||||
|
|
||||||
|
// Set default color map values
|
||||||
|
for i := range 10 {
|
||||||
|
colorMap[i] = "\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if header is empty
|
||||||
|
if asciiArtHeader == "" {
|
||||||
|
return colorMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read colors from ascii art header
|
||||||
|
ansiColors := strings.Split(strings.TrimPrefix(asciiArtHeader, "#/"), ";")
|
||||||
|
for i := 0; i < 9 && i < len(ansiColors); i++ {
|
||||||
|
colorMap[i+1] = fmt.Sprintf("\033[38;5;%sm", ansiColors[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorMap
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StormfetchConfig struct {
|
||||||
|
Ascii string `yaml:"distro_ascii"`
|
||||||
|
DisableAmdgpuIdsWarning bool `yaml:"disable_amdgpu_ids_warning"`
|
||||||
|
Modules []stormfetchModuleConfig `yaml:"modules"`
|
||||||
|
AnsiiColors []int `yaml:"ansii_colors"`
|
||||||
|
ForceConfigAnsii bool `yaml:"force_config_ansii"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = StormfetchConfig{
|
||||||
|
Ascii: "auto",
|
||||||
|
Modules: make([]stormfetchModuleConfig, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfig() {
|
||||||
|
if ConfigPath == "" {
|
||||||
|
// Get home directory
|
||||||
|
userConfigDir, _ := os.UserConfigDir()
|
||||||
|
|
||||||
|
// Find valid config directory
|
||||||
|
if _, err := os.Stat(path.Join(userConfigDir, "stormfetch/config.yml")); err == nil {
|
||||||
|
ConfigPath = path.Join(userConfigDir, "stormfetch/config.yml")
|
||||||
|
} else if _, err := os.Stat(path.Join(SystemConfigDir, "stormfetch/config.yml")); err == nil {
|
||||||
|
ConfigPath = path.Join(SystemConfigDir, "stormfetch/config.yml")
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Config file not found: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse config
|
||||||
|
bytes, err := os.ReadFile(ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(bytes, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
@@ -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=
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -12,25 +12,26 @@ 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
|
||||||
|
var ConfigPath = ""
|
||||||
|
var Ascii = ""
|
||||||
var ShowModuleTimeTaken = false
|
var ShowModuleTimeTaken = false
|
||||||
|
|
||||||
var configPath = ""
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
readConfig()
|
|
||||||
parseFlags()
|
parseFlags()
|
||||||
|
readConfig()
|
||||||
initializeModuleMap()
|
initializeModuleMap()
|
||||||
run()
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFlags() {
|
func parseFlags() {
|
||||||
flag.BoolVar(&ShowVersion, "version", false, "Show Stormfetch version")
|
flag.BoolVar(&ShowVersion, "version", false, "Show Stormfetch version")
|
||||||
|
flag.StringVar(&ConfigPath, "config", "", "Use the specified config file")
|
||||||
flag.BoolVar(&ShowModuleTimeTaken, "time-taken", false, "Show time taken to execute each module")
|
flag.BoolVar(&ShowModuleTimeTaken, "time-taken", false, "Show time taken to execute each module")
|
||||||
flag.StringVar(&config.Ascii, "ascii", config.Ascii, "Set distro ascii")
|
flag.StringVar(&Ascii, "ascii", "", "Set distro ascii")
|
||||||
flag.StringVar(&config.DistroName, "distro-name", config.DistroName, "Set distro name")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +92,9 @@ func run() {
|
|||||||
text := module.Execute(module)
|
text := module.Execute(module)
|
||||||
end := time.Now().UnixMilli()
|
end := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
// Insert default color at the start of the module's text
|
||||||
|
text = colorMap[0] + text
|
||||||
|
|
||||||
// Show time taken
|
// Show time taken
|
||||||
if ShowModuleTimeTaken {
|
if ShowModuleTimeTaken {
|
||||||
fmt.Printf("Module '%s' took %d milliseconds\n", module.Name, end-start)
|
fmt.Printf("Module '%s' took %d milliseconds\n", module.Name, end-start)
|
||||||
@@ -104,7 +108,7 @@ func run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Continue if text length is 0
|
// Continue if text length is 0
|
||||||
if len(textNoColor) == 0 {
|
if len(textNoColor)-len(colorMap[0]) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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":
|
||||||
@@ -399,4 +431,37 @@ func initializeModuleMap() {
|
|||||||
return builder.String()
|
return builder.String()
|
||||||
}}
|
}}
|
||||||
RegisterModule(monitorsModule)
|
RegisterModule(monitorsModule)
|
||||||
|
|
||||||
|
// Custom module
|
||||||
|
customModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "custom"}, Execute: func(sm StormfetchModule) string {
|
||||||
|
shell, _ := sm.GetData("shell", "/bin/sh")
|
||||||
|
commandList, _ := sm.GetData("commands", make([]any, 0))
|
||||||
|
|
||||||
|
// Exeucte all commands
|
||||||
|
commandOutput := make(map[int]string)
|
||||||
|
for i, value := range commandList.([]any) {
|
||||||
|
command, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
commandOutput[i+1] = runCommand(command, shell.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Expand(sm.Format, func(s string) string {
|
||||||
|
if len(s) <= 4 || !strings.HasPrefix(s, "CMD_") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
commandIndexStr := strings.Split(s, "CMD_")[1]
|
||||||
|
|
||||||
|
commandIndex, err := strconv.Atoi(commandIndexStr)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandOutput[commandIndex]
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
RegisterModule(customModule)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -22,10 +22,6 @@ func GetDistroInfo() DistroInfo {
|
|||||||
LongName: "Unknown",
|
LongName: "Unknown",
|
||||||
ShortName: "Unknown",
|
ShortName: "Unknown",
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(config.DistroName) != "" {
|
|
||||||
info.LongName = strings.TrimSpace(config.DistroName)
|
|
||||||
info.ShortName = strings.TrimSpace(config.DistroName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect release file location
|
// Detect release file location
|
||||||
var releaseFile string
|
var releaseFile string
|
||||||
@@ -39,20 +35,23 @@ func GetDistroInfo() DistroInfo {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read release file
|
||||||
releaseMap, err := ReadKeyValueFile(releaseFile)
|
releaseMap, err := ReadKeyValueFile(releaseFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set struct fields
|
||||||
if id, ok := releaseMap["ID"]; ok {
|
if id, ok := releaseMap["ID"]; ok {
|
||||||
info.ID = id
|
info.ID = id
|
||||||
}
|
}
|
||||||
if longName, ok := releaseMap["PRETTY_NAME"]; ok && info.LongName == "Unknown" {
|
if longName, ok := releaseMap["PRETTY_NAME"]; ok {
|
||||||
info.LongName = longName
|
info.LongName = longName
|
||||||
}
|
}
|
||||||
if shortName, ok := releaseMap["NAME"]; ok && info.ShortName == "Unknown" {
|
if shortName, ok := releaseMap["NAME"]; ok {
|
||||||
info.ShortName = shortName
|
info.ShortName = shortName
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,40 +63,36 @@ func GetDistroAsciiArt() string {
|
|||||||
// \ \
|
// \ \
|
||||||
(| | )
|
(| | )
|
||||||
/'\_ _/'\
|
/'\_ _/'\
|
||||||
\___)=(___/ `
|
\___)=(___/`
|
||||||
var id string
|
|
||||||
if config.Ascii == "auto" {
|
// Get ascii name to use
|
||||||
id = GetDistroInfo().ID
|
var asciiName string
|
||||||
|
if Ascii != "" {
|
||||||
|
asciiName = Ascii
|
||||||
|
} else if config.Ascii == "auto" {
|
||||||
|
asciiName = GetDistroInfo().ID
|
||||||
} else {
|
} else {
|
||||||
id = config.Ascii
|
asciiName = config.Ascii
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for ascii art in home directory
|
||||||
userConfDir, err := os.UserConfigDir()
|
userConfDir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
if _, err := os.Stat(path.Join(SystemConfigDir, "stormfetch/ascii/", id)); err == nil {
|
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", asciiName)); err == nil {
|
||||||
bytes, err := os.ReadFile(path.Join(SystemConfigDir, "stormfetch/ascii/", id))
|
if bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", asciiName)); err == nil {
|
||||||
if err != nil {
|
return strings.TrimRight(string(bytes), "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for ascii art in system config directory
|
||||||
|
if _, err := os.Stat(path.Join(SystemConfigDir, "stormfetch/ascii/", asciiName)); err == nil {
|
||||||
|
if bytes, err := os.ReadFile(path.Join(SystemConfigDir, "stormfetch/ascii/", asciiName)); err == nil {
|
||||||
|
return strings.TrimRight(string(bytes), "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return defaultAscii
|
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 GetArch() string {
|
func GetArch() string {
|
||||||
@@ -138,44 +133,48 @@ func GetKernel() (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetInitSystem() string {
|
func GetInitSystem() string {
|
||||||
runCommand := func(command string) string {
|
|
||||||
cmd := exec.Command("/bin/sh", "-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)
|
process, err := ps.FindProcess(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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}'")
|
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}'")
|
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)}'")
|
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}'")
|
initName = "Enit"
|
||||||
default:
|
initVersion = runCommand("enit --version | awk '{print $3}'", "/bin/sh")
|
||||||
return process.Executable()
|
}
|
||||||
|
|
||||||
|
if initVersion != "" {
|
||||||
|
return initName + " " + initVersion
|
||||||
|
} else {
|
||||||
|
return initName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
cmd := exec.Command(shell, "-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))
|
||||||
|
}
|
||||||
-201
@@ -1,201 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-ps"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DEWM struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Version string
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetShell() string {
|
|
||||||
runCommand := func(command string) string {
|
|
||||||
cmd := exec.Command("/bin/sh", "-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() 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)
|
|
||||||
}
|
|
||||||
runCommand := func(command string) string {
|
|
||||||
cmd := exec.Command("/bin/sh", "-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") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "KDE Plasma",
|
|
||||||
Type: "DE",
|
|
||||||
Version: runCommand("plasmashell --version | awk '{print $2}'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("gnome-session") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "Gnome",
|
|
||||||
Type: "DE",
|
|
||||||
Version: runCommand("gnome-shell --version | awk '{print $3}'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("xfce4-session") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "XFCE",
|
|
||||||
Type: "DE",
|
|
||||||
Version: runCommand("xfce4-session --version | head -n1 | awk '{print $2}'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("cinnamon") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "Cinnamon",
|
|
||||||
Type: "DE",
|
|
||||||
Version: runCommand("cinnamon --version | awk '{print $3}'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("mate-panel") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "MATE",
|
|
||||||
Type: "DE",
|
|
||||||
Version: runCommand("mate-about --version | awk '{print $4}'"),
|
|
||||||
}
|
|
||||||
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}'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("i3") || processExists("i3-with-shmlog") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "i3",
|
|
||||||
Type: "WM",
|
|
||||||
Version: runCommand("i3 --version | awk '{print $3}'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("sway") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "Sway",
|
|
||||||
Type: "WM",
|
|
||||||
Version: runCommand("sway --version | awk '{print $3}'"),
|
|
||||||
}
|
|
||||||
if runCommand("sway --version | awk '{print $1}'") == "swayfx" {
|
|
||||||
dewm.Name = "SwayFX"
|
|
||||||
} else {
|
|
||||||
dewm.Name = "Sway"
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("bspwm") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "Bspwm",
|
|
||||||
Type: "WM",
|
|
||||||
Version: runCommand("bspwm -v"),
|
|
||||||
}
|
|
||||||
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,'"),
|
|
||||||
}
|
|
||||||
return dewm
|
|
||||||
} else if processExists("icewm-session") {
|
|
||||||
dewm := DEWM{
|
|
||||||
Name: "IceWM",
|
|
||||||
Type: "WM",
|
|
||||||
Version: runCommand("icewm --version | awk '{print $2}'"),
|
|
||||||
}
|
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user