97 Commits
Author SHA1 Message Date
EnumDev fabc1adbf7 Take default color length into account when calculating module text
length
2025-12-22 14:28:51 +02:00
EnumDev 3b7cb5e4e2 Fix wrong module name in default config 2025-12-22 14:14:03 +02:00
EnumDev 1b0ad32b6b Trim last newline character from ascii art 2025-12-22 13:53:14 +02:00
EnumDev b52145614f Insert default color at the start of each module's text 2025-12-22 13:40:17 +02:00
EnumDev 705a40047b Remove 'distro name' flag and config option 2025-12-22 13:12:28 +02:00
EnumDev 341138116b Add default color map 2025-12-22 12:42:36 +02:00
EnumDev 051301aacd Rewrite 'GetDistroAsciiArt' function 2025-12-22 12:26:42 +02:00
EnumDev 3131d94429 Add 'config' flag 2025-12-22 12:07:03 +02:00
EnumDev c7bfaf7a31 Add 'custom' module 2025-12-22 11:54:30 +02:00
EnumDev 9ce8e5ef0b Replace old color code formatting with new one in cachyos ascii art 2025-12-21 22:25:58 +02:00
EnumDev 92ea038c77 Fix color codes not carrying over from last line of ascii art 2025-12-21 22:19:55 +02:00
EnumDev 9680c024b9 Rename config.yaml to config.yml 2025-12-21 18:40:48 +02:00
EnumDev 13cd6f1d45 Improve partitions module 2025-12-21 17:27:39 +02:00
EnumDev e400e338a0 Rename and improve cpus module 2025-12-21 17:16:05 +02:00
EnumDev d1b095ba73 Improve motherboard module 2025-12-21 16:36:24 +02:00
EnumDev 663e69638b Improve DE/WM module 2025-12-21 16:35:55 +02:00
EnumDev 4e296b5104 Update Makefile and add version flag 2025-12-21 16:34:57 +02:00
EnumDev eef69eed9a Remove dependency on bash, switch to new module system and simplify code 2025-12-21 15:42:57 +02:00
EnumDev 489e6e2143 Get BPM package count using directory contents 2025-10-25 15:28:21 +03:00
EnumDev ba43a98926 Add LXQt detection 2025-10-12 14:32:21 +03:00
EnumDev 1439a22378 Simplify color map system 2025-08-23 17:43:51 +03:00
EnumDev 74eb84e706 Simplify print statements 2025-08-23 16:39:21 +03:00
EnumDev 5ab8a6ee68 Add Bedrock Linux support 2025-08-23 16:32:14 +03:00
EnumDev 1ee2691b44 Update LICENSE 2025-04-01 17:44:16 +03:00
EnumDev b6dba0061d Remove developers section from README.md 2025-03-21 17:12:44 +02:00
EnumDev d41c00f972 Remove dependency warnings 2025-03-21 16:11:49 +02:00
EnumDev d51cfdf363 Improved GetGPUModels function 2025-03-21 16:03:15 +02:00
EnumDev 0f46e313e6 Remove compress goal from Makefile 2025-03-21 14:57:11 +02:00
EnumDev a76295c455 Moved go files to src/ folder 2025-03-21 14:56:58 +02:00
EnumDev 47fd83f8cd Improve GetMountedPartitions function and added 'hidden_partitions' and 'hidden_filesystems' config options 2025-03-21 14:11:33 +02:00
EnumDev ca17578cac Improve GetLibc function 2025-03-21 12:31:29 +02:00
EnumDev 7d121c330d Move package count detection to go code 2025-03-21 09:57:36 +02:00
EnumDev 6185af37d5 Add i3-with-shmlog detection 2025-03-20 13:27:46 +02:00
EnumDev cd0faaefe5 Add Enit init system detection 2025-03-10 22:43:06 +02:00
EnumDev 4b9bc59810 Change init system checking code 2025-03-10 22:42:18 +02:00
EnumDev 0ed976a06e Fixed bug where the getDistroAsciiArt function would ignore the systemConfigDir variable 2025-02-06 21:18:55 +02:00
EnumDev ce9813a2af Made fatal errors clearer and fixed bug in fetch script 2025-02-06 21:18:55 +02:00
EnumDev 0d3d3fafb2 Added motherboard information 2025-02-06 21:18:55 +02:00
EnumDev a99defd401 Makefile will now always use bash 2025-02-06 21:18:55 +02:00
EnumDev f34537eb5a Updated README.md 2025-01-24 18:10:17 +00:00
EnumDev 120a1cf8e4 Fix for Xfce 2024-11-04 13:37:57 +02:00
EnumDev 76974ee789 Added i3, swayfx and hyprland WM detection 2024-10-23 17:14:23 +03:00
EnumDev 20cf7fe869 Fixed error when attempting to use stormfetch without being connected to the internet and simplified fetch script 2024-09-06 19:07:45 +03:00
EnumDev c5e02c07b5 Added wayland monitor resolution support 2024-08-23 15:13:09 +03:00
EnumDev e8a95e5313 Added Local IPv4 address support 2024-08-23 14:32:26 +03:00
EnumDev 0fd198d83e Added init system support 2024-08-22 21:54:14 +03:00
EnumDev 673f231c42 Added nushell support 2024-08-22 21:25:46 +03:00
CapCreeperGR e311649a48 Added PopOS ascii 2024-07-18 15:35:14 +03:00
CapCreeperGR 712fd78e21 Updated tide ascii 2024-07-18 14:12:45 +03:00
CapCreeperGR 7d83b0996d Improved GPU and added --time-taken flag 2024-07-17 20:11:30 +03:00
CapCreeperGR f3a016b674 Added Libc information 2024-07-17 13:18:31 +03:00
CapCreeperGR 0e2631d5b0 Added Artix ascii 2024-07-16 20:55:06 +03:00
EnumDev 153758b68e make should now be able to locate the go executable more easily 2024-07-01 13:16:37 +03:00
EnumDev ed45b739a5 Updated BPM package count fetching 2024-07-01 12:44:22 +03:00
EnumDev 8c380dbec4 Stormfetch can now read config files in the directory specified with the Makefile SYSCONFDIR variable 2024-06-20 21:23:50 +03:00
EnumDev 7c493dc9f9 Added filesystem label detection, btrfs subvolumes should now be grouped under one partition and the filesystem type will now be shown if enabled in config.yaml 2024-06-19 18:20:16 +03:00
EnumDev b8d7017299 Fixed bug where xbps packages would be counted on arch systems and not void systems 2024-06-18 07:49:19 +03:00
EnumDev cd08003752 Fixed emerge package count and added VoidLinux/xbps support 2024-06-17 19:41:20 +03:00
EnumDev c4c71fc49a Added hidden_gpus option in the config 2024-06-09 14:45:17 +03:00
EnumDev efdbe94193 Stormfetch will now list all GPUs if multiple of them are connected 2024-06-09 14:39:24 +03:00
EnumDev 7b8c017ed0 Added a --distro-name flag and distro_name config option 2024-06-09 11:11:18 +03:00
EnumDev 66f3891b40 Slightly changed ubuntu ascii to fix a minor spacing bug 2024-06-09 10:52:01 +03:00
EnumDev efae7c4b93 Split main.go code into more functions,improved extra newline removal and added --ascii flag 2024-06-09 10:49:11 +03:00
EnumDev 24da6ce083 Removed extra new lines at the end of the output 2024-06-09 10:30:55 +03:00
EnumDev b90b869643 Stormfetch will no longer crash if there is an error while reading partition size 2024-06-09 10:20:28 +03:00
EnumDev 720eb845ca Improved partition detection 2024-06-09 10:12:36 +03:00
EnumDev 81c4006e48 Removed .gitignore from repository due to being unnecessary 2024-06-09 09:45:28 +03:00
EnumDev 58b3e82346 Added Linux Mint ascii 2024-06-08 20:47:33 +03:00
EnumDev ba67c074db Fixed bug where system info would be cut off if ascii was short and added cachyos ascii 2024-06-08 20:33:33 +03:00
EnumDev c8b3f9f4a4 Text color should now reset at the end of the stormfetch output 2024-06-05 16:43:27 +03:00
EnumDev c05f34eb7d Fixed minor bug and updated fetch_script.sh
Fixed bug which would cause partitions not to show up if /dev/disk/by-partlabel/ did not exist
Updated fetch_script.sh to use labels if available
2024-05-16 22:24:16 +03:00
EnumDev 39b901e6fd Added PARTITION<n>_LABEL bash variable 2024-05-16 22:05:03 +03:00
EnumDev 2af3ab1156 Close xdg connection after querying 2024-05-16 17:01:11 +03:00
EnumDev aba10e3e9e Updated README.md 2024-05-16 16:58:48 +03:00
EnumDev 0c376702c7 Added partition size support
Added MOUNTED_PARTITIONS bash variable
Added PARTITION<n>_DEVICE bash variable
Added PARTITION<n>_MOUNTPOINT bash variable
Added PARTITION<n>_TOTAL_SIZE bash variable
Added PARTITION<n>_USED_SIZE bash variable
Added PARTITION<n>_FREE_SIZE bash variable
2024-05-16 16:57:12 +03:00
EnumDev a5ffb4b32e Tidied up code
The fetch script environment is now mostly set up in each own function
2024-05-16 14:44:38 +03:00
EnumDev 4de0d34e28 Improved GPU and Screen resolution fetching
Replaced calls to bash for getting monitor resolutions
getGPUName() now returns the first GPU with available device information
2024-05-16 08:41:21 +03:00
EnumDev 7ed54ec1a5 DISPLAY_PROTOCOL Variable and separate monitor resolutions (On X11)
Added a DISPLAY_PROTOCOL variable
Resolution information has been converted to a Golang function and now lists different monitors separately
2024-05-15 16:37:09 +03:00
EnumDev 2984a7da5f Fixed chroot bugs and reduced executable size
Stormfetch will now only list CPU and memory information if available
Added 'ldflag "-w"' to go build which reduces file size
Added 'run' goal to Makefile
2024-05-15 13:25:11 +03:00
EnumDev 4a2833afff Shell information has been converted to a Golang function 2024-05-15 12:02:43 +03:00
EnumDev c13c730e4c Removed lshw as a dependency
Renamed Utils.go to utils.go
2024-05-15 10:07:20 +03:00
EnumDev 4c72295972 DE/WM information has been converted to a Golang function 2024-05-15 09:15:15 +03:00
EnumDev a29a8f1dd9 CPU, GPU and memory information have been converted to Golang functions 2024-05-14 10:03:18 +03:00
EnumDev 1fc9a9ea79 Added Bspwm support 2024-05-12 16:58:32 +03:00
EnumDev 01b23277fd Added gif to README.md
Changed README.md formatting
2024-05-12 11:24:58 +03:00
EnumDev 75deb165ad Updated configuration to support color 2024-05-12 10:14:00 +03:00
EnumDev 56511c90db Merge remote-tracking branch 'origin/master' 2024-05-11 21:18:10 +03:00
EnumDev 42a60296a9 Improved color support 2024-05-11 21:17:39 +03:00
EnumDev af4c534685 Merge branch 'master' into 'master'
Add AUR link and tidy up README grammar

See merge request CapCreeperGR/stormfetch!1
2024-05-11 17:31:13 +00:00
let 25a596cd5e tidy up the grammar a bit 2024-05-11 20:05:04 +03:00
let 4d414daccc add AUR to README 2024-05-11 20:03:08 +03:00
EnumDev 664b639fb9 Added color support 2024-05-11 19:16:53 +03:00
EnumDev 2b33b64602 Added missing dependency warning 2024-05-08 14:26:01 +03:00
EnumDev 200e300be0 Improved DE/WM Detection 2024-05-08 14:12:38 +03:00
EnumDev 139e64d682 Added GO variable to Makefile 2024-05-08 13:00:29 +03:00
EnumDev 0608d1b84b Added a 'compress' goal to the Makefile to quickly create a tar.gz archive 2024-05-08 11:52:59 +03:00
EnumDev 325d6bff13 Updated fetch script
Added new ascii art for more linux distros
2024-05-08 11:21:13 +03:00
40 changed files with 1783 additions and 398 deletions
-2
View File
@@ -1,2 +0,0 @@
.idea
build
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 CapCreeperGR
Copyright (c) 2024 EnumDev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+26 -13
View File
@@ -1,22 +1,35 @@
ifeq ($(PREFIX),)
PREFIX := /usr/local
endif
ifeq ($(BINDIR),)
BINDIR := $(PREFIX)/bin
endif
ifeq ($(SYSCONFDIR),)
# Installation paths
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
SYSCONFDIR := $(PREFIX)/etc
endif
# Compilers and tools
GO ?= go
# Build-time variables
VERSION ?= $(shell git describe --tags --dirty)
build:
mkdir -p build
go build -o build/stormfetch stormfetch
cd src; $(GO) build -ldflags "-w -X 'main.StormfetchVersion=$(VERSION)' -X 'main.SystemConfigDir=$(SYSCONFDIR)'" -o ../build/stormfetch stormfetch
install: build/stormfetch config/
mkdir -p $(DESTDIR)$(BINDIR)
mkdir -p $(DESTDIR)$(SYSCONFDIR)/stormfetch/
cp build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch
cp -r config/. $(DESTDIR)$(SYSCONFDIR)/stormfetch/
# Create directoriy
install -dm755 $(DESTDIR)$(BINDIR)
# Install binary
install -m755 build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch
install-config:
# Create directory
install -dm755 $(DESTDIR)$(SYSCONFDIR)
# Install files
cp -r config $(DESTDIR)$(SYSCONFDIR)/stormfetch/
uninstall:
-rm -f $(DESTDIR)$(BINDIR)/stormfetch
-rm -rf $(DESTDIR)$(SYSCONFDIR)/stormfetch
clean:
rm -r build/
.PHONY: build install install-config uninstall clean
+10 -8
View File
@@ -1,21 +1,23 @@
# Stormfetch
## A simple linux fetch program written in go and bash
### Developers:
- [CapCreeperGR ](https://gitlab.com/CapCreeperGR)
## A simple linux fetch program written in go
### Project Information
Stormfetch is a program that can read your system's information and display it in the terminal along with ascii art of the linux distribution you are running.
Stormfetch is still in beta and 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
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.
### How it looks
![Stormfetch gif](media/stormfetch.gif)
### Installation Guide
#### Using a package manager
- Arch Linux: You may use your favorite AUR manager to install the `stormfetch` package
#### Building from source
- Download `go` from your package manager or from the go website
- Download `make` from your package manager
- (Optional) Download `lshw` from your package manager to display GPU information
- Run the following command to compile the project
```
make
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
```
+5 -4
View File
@@ -1,4 +1,5 @@
-`
#/4;27;4;11
%1 -`
.o+`
`ooo/
`+oooo:
@@ -7,9 +8,9 @@
`/:-:++oooo+:
`/++++/+++++++:
`/++++++++++++++:
`/+++ooooooooooooo/`
./ooosssso++osssssso+`
.oossssso-````/ossssss+`
`/+++o%2oooooooo%1oooo/`
%2 %1./%2ooosssso++osssssso%1+`
%2 .oossssso-````/ossssss+`
-osssssso. :ssssssso.
:osssssss/ osssso+++.
/ossssssss/ +ssssooo/-
+20
View File
@@ -0,0 +1,20 @@
#/45;27;45;7
%1 'o'
'ooo'
'ooxoo'
'ooxxxoo'
'oookkxxoo'
'oiioxkkxxoo'
':;:iiiioxxxoo'
`'.;::ioxxoo'
'-. `':;jiooo'
'oooio-.. `'i:io'
'ooooxxxxoio:,. `'-;'
'ooooxxxxxkkxoooIi:-. `'
'ooooxxxxxkkkkxoiiiiiji'
'ooooxxxxxkxxoiiii:'` .i'
'ooooxxxxxoi:::'` .;ioxo'
'ooooxooi::'` .:iiixkxxo'
'ooooi:'` `'';ioxxo'
'i:'` '':io'
'` `'
+12
View File
@@ -0,0 +1,12 @@
#/7;8;8;7
%1 \\\\\\\\\\\\
\\\ \\\
\\\ \\\
\\\ \\\\\\\\\\\\\\\\\
\\\ \\\
\\\ \\\
\\\ %2______ \\\
\\\ ///
\\\ ///
\\\ ///
\\\////////////////
+23
View File
@@ -0,0 +1,23 @@
#/43;45;45;15
%3.%1-------------------------:
.%2+=%1========================.
:%2++%1===%2++===%1===============- :%2++%1-
:%2*++%1====%2+++++==%1===========- .==:
-%2*+++%1=====%2+***++=%1=========:
=%2*++++=%1=======------------:
=%2*+++++=%1====- %3...%1
.%2+*+++++%1=-===: .%2=+++=%1:
:%2++++%1=====-==: -***%2**%1+
:%2++=%1=======-=. .=+**+%3.%1
.%2+%1==========-. %3.%1
:%2+++++++%1====- %3.%1--==-%3.%1
:%2++%1==========. %3:%2+++++++%1%3:
%1.-===========. =*****+*+
%1.-===========: .+*****+:
%1-=======%2++++%1:::::::::::::::::::::::::-: %3.%1---:
:======%2++++%1====%2+++******************=.
%1:=====%2+++%1==========%2++++++++++++++*-
%1.====%2++%1==============%2++++++++++*-
%1.===%2+%1==================%2+++++++:
%1.-=======================%2+++:
%3..........................
+17 -16
View File
@@ -1,16 +1,17 @@
_,met$$$$$gg.
,g$$$$$$$$$$$$$$$P.
,g$$P" """Y$$.".
,$$P' `$$$.
',$$P ,ggs. `$$b:
`d$$' ,$P"' . $$$
$$P d$' , $$P
$$: $$. - ,d$$'
$$; Y$b._ _,d$P'
Y$$. `.`"Y$$$$P"'
`$$b "-.__
`Y$$
`Y$$.
`$$b.
`Y$$b.
`"Y$b._
#/1;7;9;15
%2 _,met&&&&&gg.
,g&&&&&&&&&&&&&&&P.
,g&&P" """Y&&.".
,&&P' `&&&.
',&&P ,ggs. `&&b:
`d&&' ,&P"' %1.%2 &&&
&&P d&' %1,%2 &&P
&&: &&. %1-%2 ,d&&'
&&; Y&b._ _,d&P'
Y&&. %1`.%2`"Y&&&&P"'
%2 `&&b %1"-.__
%2 `Y&&
`Y&&.
`&&b.
`Y&&b.
`"Y&b._
+14 -13
View File
@@ -1,19 +1,20 @@
.',;::::;,'.
#/12;7;4;15
%1 .',;::::;,'.
.';:cccccccccccc:;,.
.;cccccccccccccccccccccc;.
.:cccccccccccccccccccccccccc:.
.;ccccccccccccc;.:dddl:.;ccccccc;.
.:ccccccccccccc;OWMKOOXMWd;ccccccc:.
.:ccccccccccccc;KMMc;cc;xMMc;ccccccc:.
,cccccccccccccc;MMM.;cc;;WW:;cccccccc,
:cccccccccccccc;MMM.;cccccccccccccccc:
:ccccccc;oxOOOo;MMM0OOk.;cccccccccccc:
cccccc;0MMKxdd:;MMMkddc.;cccccccccccc;
ccccc;XM0';cccc;MMM.;cccccccccccccccc'
ccccc;MMo;ccccc;MMW.;ccccccccccccccc;
ccccc;0MNc.ccc.xMMd;ccccccccccccccc;
cccccc;dNMWXXXWM0:;cccccccccccccc:,
cccccccc;.:odl:.;cccccccccccccc:,.
.;ccccccccccccc;%2.:dddl:.%1;ccccccc;.
.:ccccccccccccc;%2OWMKOOXMWd%1;ccccccc:.
.:ccccccccccccc;%2KMMc%1;cc;%2xMMc%1;ccccccc:.
,cccccccccccccc;%2MMM.%1;cc;%2;WW:%1;cccccccc,
:cccccccccccccc;%2MMM.%1;cccccccccccccccc:
:ccccccc;%2oxOOOo%1;%2MMM0OOk.%1;cccccccccccc:
cccccc;%20MMKxdd:%1;%2MMMkddc.%1;cccccccccccc;
ccccc;%2XM0'%1;cccc;%2MMM.%1;cccccccccccccccc'
ccccc;%2MMo%1;ccccc;%2MMW.%1;ccccccccccccccc;
ccccc;%20MNc.%1ccc%2.xMMd%1;ccccccccccccccc;
cccccc;%2dNMWXXXWM0:%1;cccccccccccccc:,
cccccccc;%2.:odl:.%1;cccccccccccccc:,.
:cccccccccccccccccccccccccccc:'.
.:cccccccccccccccccccccc:;,..
'::cccccccccccccc::;,.
+18 -17
View File
@@ -1,18 +1,19 @@
-/oyddmdhs+:.
-odNMMMMMMMMNNmhy+-`
-yNMMMMMMMMMMMNNNmmdhy+-
`omMMMMMMMMMMMMNmdmmmmddhhy/`
omMMMMMMMMMMMNhhyyyohmdddhhhdo`
.ydMMMMMMMMMMdhs++so/smdddhhhhdm+`
oyhdmNMMMMMMMNdyooydmddddhhhhyhNd.
:oyhhdNNMMMMMMMNNNmmdddhhhhhyymMh
.:+sydNMMMMMNNNmmmdddhhhhhhmMmy
/mMMMMMMNNNmmmdddhhhhhmMNhs:
`oNMMMMMMMNNNmmmddddhhdmMNhs+`
`sNMMMMMMMMNNNmmmdddddmNMmhs/.
/NMMMMMMMMNNNNmmmdddmNMNdso:`
+MMMMMMMNNNNNmmmmdmNMNdso/-
yMMNNNNNNNmmmmmNNMmhs+/-`
/hMMNNNNNNNNMNdhs++/-`
`/ohdmmddhys+++/:.`
#/5;7;13;69
%1 -/oyddmdhs+:.
-o%2dNMMMMMMMMNNmhy+%1-`
-y%2NMMMMMMMMMMMNNNmmdhy%1+-
`o%2mMMMMMMMMMMMMNmdmmmmddhhy%1/`
om%2MMMMMMMMMMMN%1hhyyyo%2hmdddhhhd%1o`
.y%2dMMMMMMMMMMd%1hs++so/s%2mdddhhhhdm%1+`
oy%2hdmNMMMMMMMN%1dyooy%2dmddddhhhhyhN%1d.
:o%2yhhdNNMMMMMMMNNNmmdddhhhhhyym%1Mh
.:%2+sydNMMMMMNNNmmmdddhhhhhhmM%1my
/m%2MMMMMMNNNmmmdddhhhhhmMNh%1s:
`o%2NMMMMMMMNNNmmmddddhhdmMNhs%1+`
`s%2NMMMMMMMMNNNmmmdddddmNMmhs%1/.
/N%2MMMMMMMMNNNNmmmdddmNMNdso%1:`
+M%2MMMMMMNNNNNmmmmdmNMNdso%1/-
yM%2MNNNNNNNmmmmmNNMmhs+/%1-`
/h%2MMNNNNNNNNMNdhs++/%1-`
`/%2ohdmmddhys+++/:%1.`
`-//////:--.
+20
View File
@@ -0,0 +1,20 @@
#/34;15;46;252
%2 ...-:::::-...
%2 .-MMMMMMMMMMMMMMM-.
.-MMMM%1`..-:::::::-..`%2MMMM-.
.:MMMM%1.:MMMMMMMMMMMMMMM:.%2MMMM:.
-MMM%1-M---MMMMMMMMMMMMMMMMMMM.%2MMM-
`:MMM%1:MM` :MMMM:....::-...-MMMM:%2MMM:`
:MMM%1:MMM` :MM:` `` `` `:MMM:%2MMM:
.MMM%1.MMMM` :MM. -MM. .MM- `MMMM.%2MMM.
:MMM%1:MMMM` :MM. -MM- .MM: `MMMM-%2MMM:
:MMM%1:MMMM` :MM. -MM- .MM: `MMMM:%2MMM:
:MMM%1:MMMM` :MM. -MM- .MM: `MMMM-%2MMM:
.MMM%1.MMMM` :MM:--:MM:--:MM: `MMMM.%2MMM.
:MMM%1:MMM- `-MMMMMMMMMMMM-` -MMM-%2MMM:
:MMM%1:MMM:` `:MMM:%2MMM:
.MMM%1.MMMM:--------------:MMMM.%2MMM.
'-MMMM%1.-MMMMMMMMMMMMMMM-.%2MMMM-'
'.-MMMM%1``--:::::--``%2MMMM-.'
%2 '-MMMMMMMMMMMMM-'
%2 ``-:::::-``
+19
View File
@@ -0,0 +1,19 @@
#/2;7;10;15
%2 .;ldkO0000Okdl;.
.;d00xl:^''''''^:ok00d;.
.d00l' 'o00d.
.d0Kd'%1 Okxol:;,. %2:O0d.
.OK%1KKK0kOKKKKKKKKKKOxo:, %2lKO.
,0K%1KKKKKKKKKKKKKKK0P^%2,,,%1^dx:%2 ;00,
.OK%1KKKKKKKKKKKKKKKk'%2.oOPPb.%1'0k.%2 cKO.
:KK%1KKKKKKKKKKKKKKK: %2kKx..dd %1lKd%2 'OK:
dKK%1KKKKKKKKKOx0KKKd %2^0KKKO' %1kKKc%2 dKd
dKK%1KKKKKKKKKK;.;oOKx,..%2^%1..;kKKK0.%2 dKd
:KK%1KKKKKKKKKK0o;...^cdxxOK0O/^^' %2.0K:
kKK%1KKKKKKKKKKKKK0x;,,......,;od %2lKk
'0K%1KKKKKKKKKKKKKKKKKKKK00KKOo^ %2c00'
'kK%1KKOxddxkOO00000Okxoc;'' %2.dKk'
l0Ko. .c00l'
'l0Kk:. .;xK0l'
'lkK0xl:;,,,,;:ldO0kl'
'^:ldxkkkkxdl:^'
+14
View File
@@ -0,0 +1,14 @@
#/2;7;10;15
%1 ......
.,cdxxxoc,. .:kKMMMNWMMMNk:.
cKMMN0OOOKWMMXo. ; ;0MWk:. .:OMMk.
;WMK;. .lKMMNM, :NMK, .OMW;
cMW; 'WMMMN ,XMK, oMM'
.MMc ..;l. xMN: KM0
'MM. 'NMO oMM
.MM, .kMMl xMN
KM0 .kMM0. .dl:,.. .WMd
.XM0. ,OMMK, OMMMK. .XMK
oWMO:. .;xNMMk, NNNMKl. .xWMx
:ONMMNXMMMKx; . ,xNMWKkxllox0NMWk,
..... .:dOOXXKOxl,
+21
View File
@@ -0,0 +1,21 @@
#/33;7;11;15
%1 /////////////
/////////////////////
///////%2*767%1////////////////
//////%27676767676*%1//////////////
/////%276767%1//%27676767%1//////////////
/////%2767676%1///%2*76767%1///////////////
///////%2767676%1///%276767%1.///%27676*%1///////
/////////%2767676%1//%276767%1///%2767676%1////////
//////////%276767676767%1////%276767%1/////////
///////////%276767676%1//////%27676%1//////////
////////////,%27676%1,///////%2767%1///////////
/////////////*%27676%1///////%276%1////////////
///////////////%27676%1////////////////////
///////////////%27676%1///%2767%1////////////
//////////////////////%2'%1////////////
//////%2.7676767676767676767,%1//////
/////%2767676767676767676767%1/////
///////////////////////////
/////////////////////
/////////////
+15
View File
@@ -0,0 +1,15 @@
#/27;39;5;12
%1@@@@ %1@@@%2*
%1@@%2~~~~%1@@ %1@@%2~~~
%1@@%2~~ %2~~%1@ %1@@%2~~
%1@@%2~~ %2~%1@ %1@@@%2~~
%2*%1@@%2~~ %1@@@@ %2~%1@@@@%2~~~ %1@@@%2*
%2~~ %1@@%2~~~~%1@@ %2~~~~ %1@@%2~~~
%1@@%2~~ %2~~%1@ %1@@%2~~
%1@@%2~~ %2~%1@ %1@@@%2~~
%2*%1@@%2~~ %1@@@@ %2~%1@@@@%2~~~ %1@@@%2*
%2~~ %1@@%2~~~~%1@@ %2~~~~ %1@@%2~~~
%1@@%2~~ %2~~%1@ %1@@%2~~
%1@@%2~~ %2~%1@ %1@@@%2~~
%2*%1@@%2~~ %2~%1@@@@%2~~~
%2~~ %2~~~~
+21
View File
@@ -0,0 +1,21 @@
#/1;7;3;15
%1 .-/+oossssoo+\-.
':+ssssssssssssssssss+:'
-+ssssssssssssssssssyyssss+-
.ossssssssssssssssss%2dMMMNy%1sssso.
/sssssssssss%2hdmmNNmmyNMMMMh%1ssssss\
+sssssssss%2hm%1yd%2MMMMMMMNddddy%1ssssssss+
/ssssssss%2hNMMM%1yh%2hyyyyhmNMMMNh%1ssssssss\
.ssssssss%2dMMMNh%1ssssssssss%2hNMMMd%1ssssssss.
+ssss%2hhhyNMMNy%1ssssssssssss%2yNMMMy%1sssssss+
oss%2yNMMMNyMMh%1ssssssssssssss%2hmmmh%1ssssssso
oss%2yNMMMNyMMh%1sssssssssssssshmmmh%1ssssssso
+ssss%2hhhyNMMNy%1ssssssssssss%2yNMMMy%1sssssss+
.ssssssss%2dMMMNh%1ssssssssss%2hNMMMd%1ssssssss.
\ssssssss%2hNMMM%1yh%2hyyyyhdNMMMNh%1ssssssss/
+sssssssss%2dm%1yd%2MMMMMMMMddddy%1ssssssss+
\sssssssssss%2hdmNNNNmyNMMMMh%1ssssss/
.ossssssssssssssssss%2dMMMNy%1sssso.
-+sssssssssssssssss%2yyy%1ssss+-
':+ssssssssssssssssss+:'
.-\+oossssoo+/-.
+19
View File
@@ -0,0 +1,19 @@
#/2;36;72;15
%2:::::----:::::
%2::----------------::=
%2:-------------------::
%1 : %2:::. ..:-------:
%1 =*=. %2.:-----:
%1 =****- %2:-----:
%1=*****- %2:::::: :-----:
%1=****+ %2:--------: :----:
%1+****- %2.----------. :----:
%1+****- %2.----------. :-----
%1=****+ %2:--------: :----:
%1=*****- %2:::::: :-----:
%1 =*****- %2:----:
%1 =*****+: %2:-:
%1 =*******=:: ::=-. %2.
%1 ==*******************-
%1 ===****************=-
%1 ===+=****++===
-2
View File
@@ -1,2 +0,0 @@
distro_ascii: auto
fetch_script: auto
+16
View File
@@ -0,0 +1,16 @@
distro_ascii: auto
modules:
- name: distribution
- name: hostname
- name: kernel
- name: packages
- name: shell
- name: init_system
- name: motherboard
- name: cpus
- name: gpus
- name: memory
- name: partitions
- name: local_ip
- name: de_wm
- name: monitors
-17
View File
@@ -1,17 +0,0 @@
source fetch_script_functions.sh
echo "Distribution: ${DISTRO_LONG_NAME} ($(uname -m))"
echo "Hostname: $(cat /etc/hostname)"
echo "Kernel: $(uname -s) $(uname -r)"
echo "Packages: $(get_packages)"
echo "Shell: $(get_shell)"
echo "CPU: $(get_cpu_name) ($(nproc) threads)"
if command_exists lshw; then
echo "GPU: $(lshw -class display 2> /dev/null | grep 'product' | cut -d":" -f2 | xargs)"
fi
echo "Memory: $(get_used_mem) MiB / $(get_total_mem) MiB"
if xhost >& /dev/null ; then
echo "DE/WM: $(get_de_wm)"
echo "Screen Resolution: $(get_screen_resolution)"
fi
-92
View File
@@ -1,92 +0,0 @@
command_exists() {
if [ -z "$1" ]; then
return 1
fi
if command -v "$1" &> /dev/null; then
return 0
else
return 1
fi
}
get_shell() {
case ${SHELL##*/} in
"")
echo "Unknown"
;;
sh|ash|dash|es)
echo "${SHELL##*/} $(${SHELL##*/} --version)"
;;
bash)
echo "${SHELL##*/} $(${SHELL##*/} -c "echo "'$BASH_VERSION')"
;;
*)
SHELL_NAME=${SHELL##*/}
SHELL_VERSION="$($SHELL --version)"
SHELL_VERSION=$(SHELL_VERSION//,//)
SHELL_VERSION=$(SHELL_VERSION// //)
SHELL_VERSION=$(SHELL_VERSION//VERSION//)
SHELL_VERSION=$(SHELL_VERSION//"$SHELL_NAME"//)
echo "$SHELL_NAME $SHELL_VERSION"
unset SHELL_NAME
unset SHELL_VERSION
;;
esac
}
get_cpu_name() {
grep -m1 "model name" /proc/cpuinfo | cut -d: -f2 | xargs
}
get_total_mem() {
free --mebi -t | grep 'Total' | tr -s ' ' | cut -d" " -f2
}
get_free_mem() {
free --mebi -t | grep 'Total' | tr -s ' ' | cut -d" " -f4
}
get_used_mem() {
free --mebi -t | grep 'Total' | tr -s ' ' | cut -d" " -f3
}
get_de_wm() {
if [[ $DESKTOP_SESSION == "plasma" ]]; then
echo "KDE Plasma $(plasmashell --version | sed 's/plasmashell //g')"
else
echo "$DESKTOP_SESSION"
fi
}
get_screen_resolution() {
if xhost >& /dev/null ;then
xdpyinfo | grep dimensions | tr -s ' ' | cut -d " " -f3
fi
}
get_packages() {
ARRAY=()
if command_exists dpkg; then
ARRAY+=("$(dpkg-query -f '.\n' -W | wc -l) (dpkg)")
fi
if command_exists pacman; then
ARRAY+=("$(pacman -Q | wc -l) (pacman)")
fi
if command_exists rpm; then
ARRAY+=("$(rpm -qa | wc -l) (rpm)")
fi
if command_exists bpm; then
ARRAY+=("$(bpm list -n) (bpm)")
fi
if command_exists emerge; then
ARRAY+=("$(ls -l /var/db/pkg/ | wc -l) (emerge)")
fi
if command_exists flatpak; then
ARRAY+=("$(flatpak list | wc -l) (flatpak)")
fi
if command_exists snap; then
ARRAY+=("$(snap list | wc -l) (snap)")
fi
echo "${ARRAY[@]}"
unset ARRAY
}
-5
View File
@@ -1,5 +0,0 @@
module stormfetch
go 1.22
require gopkg.in/yaml.v3 v3.0.1 // indirect
-3
View File
@@ -1,3 +0,0 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-203
View File
@@ -1,203 +0,0 @@
package main
import (
"fmt"
yaml "gopkg.in/yaml.v3"
"log"
"os"
"os/exec"
"path"
"strings"
)
var configPath = ""
var fetchScriptPath = ""
var config = StormfetchConfig{}
type StormfetchConfig struct {
Ascii string `yaml:"distro_ascii"`
FetchScript string `yaml:"fetch_script"`
}
type DistroInfo struct {
ID string
LongName string
ShortName string
}
func main() {
readConfig()
}
func readConfig() {
// Get home directory
configDir, _ := os.UserConfigDir()
// Find valid config directory
if _, err := os.Stat(path.Join(configDir, "stormfetch/config.yaml")); err == nil {
configPath = path.Join(configDir, "stormfetch/config.yaml")
} else if _, err := os.Stat("/etc/stormfetch/config.yaml"); err == nil {
configPath = "/etc/stormfetch/config.yaml"
} 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)
}
if config.FetchScript == "" {
log.Fatalf("Fetch script path is empty")
} else if config.FetchScript != "auto" {
stat, err := os.Stat(config.FetchScript)
if err != nil {
log.Fatalf("Fetch script file not found: %s", err.Error())
} else if stat.IsDir() {
log.Fatalf("Fetch script path points to a directory")
}
}
if _, err := os.Stat(path.Join(configDir, "stormfetch/fetch_script.sh")); err == nil {
fetchScriptPath = path.Join(configDir, "stormfetch/fetch_script.sh")
} else if _, err := os.Stat("/etc/stormfetch/fetch_script.sh"); err == nil {
fetchScriptPath = "/etc/stormfetch/fetch_script.sh"
} else {
log.Fatalf("Fetch script file not found: %s", err.Error())
}
//Execute fetch script
cmd := exec.Command("/bin/bash", fetchScriptPath)
cmd.Dir = path.Dir(fetchScriptPath)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "DISTRO_LONG_NAME="+getDistroInfo().LongName)
cmd.Env = append(cmd.Env, "DISTRO_SHORT_NAME="+getDistroInfo().ShortName)
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
// Print Distro Information
ascii := getDistroAscii()
maxWidth := 0
for _, line := range strings.Split(ascii, "\n") {
if len(line) > maxWidth {
maxWidth = len(line)
}
}
for lineIndex, line := range strings.Split(ascii, "\n") {
for i := len(line); i < maxWidth+5; i++ {
line = line + " "
}
if lineIndex < len(strings.Split(string(out), "\n")) {
line = line + strings.Split(string(out), "\n")[lineIndex]
}
fmt.Println(line)
}
}
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 getDistroInfo() DistroInfo {
distroID := ""
var releaseMap = make(map[string]string)
if _, err := os.Stat("/etc/os-release"); err == nil {
releaseMap, err = readKeyValueFile("/etc/os-release")
if err != nil {
return DistroInfo{
ID: "unknown",
LongName: "Unknown",
ShortName: "Unknown",
}
}
if value, ok := releaseMap["ID"]; ok {
distroID = value
}
}
switch distroID {
default:
if id, ok := releaseMap["ID"]; ok {
if longName, ok := releaseMap["PRETTY_NAME"]; ok {
if shortName, ok := releaseMap["NAME"]; ok {
return DistroInfo{
ID: id,
LongName: longName,
ShortName: shortName,
}
}
}
}
return DistroInfo{
ID: "unknown",
LongName: "Unknown",
ShortName: "Unknown",
}
}
}
func getDistroAscii() string {
defaultAscii :=
` .--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/'\
\___)=(___/ `
var id string
if config.Ascii == "auto" {
id = getDistroInfo().ID
} else {
id = config.Ascii
}
userConfDir, err := os.UserConfigDir()
if err != nil {
if _, err := os.Stat(path.Join("/etc/stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join("/etc/stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else {
return defaultAscii
}
}
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else if _, err := os.Stat(path.Join("/etc/stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join("/etc/stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else {
return defaultAscii
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

+28
View File
@@ -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
}
+47
View File
@@ -0,0 +1,47 @@
package main
import (
"log"
"os"
"path"
"gopkg.in/yaml.v3"
)
type StormfetchConfig struct {
Ascii string `yaml:"distro_ascii"`
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)
}
}
+19
View File
@@ -0,0 +1,19 @@
module stormfetch
go 1.22
require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
github.com/jackmordaunt/ghw v1.0.4
github.com/mitchellh/go-ps v1.0.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/jackmordaunt/pcidb v1.0.1 // indirect
github.com/jackmordaunt/wmi v1.2.4 // indirect
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/sys v0.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
+25
View File
@@ -0,0 +1,25 @@
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/jackmordaunt/ghw v1.0.4 h1:as+COFuPuXaNQC3WqzoHS/E2JYWZU7gN8ompNTUxNxs=
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/go.mod h1:OMmhrZOZVu2hYXhBDZXddypxwKR/dp4DbIgzCkQDxdQ=
github.com/jackmordaunt/wmi v1.2.4 h1:/XyuMiKby0qXNQp1j0uU0JqTWLM0QGOVok1Hf5Yagtg=
github.com/jackmordaunt/wmi v1.2.4/go.mod h1:K/+FH4467Vk2fJXhYQTZzhctkCMMR7PwHqKbnizFySo=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+99
View File
@@ -0,0 +1,99 @@
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
}
+159
View File
@@ -0,0 +1,159 @@
package main
import (
"flag"
"fmt"
"strconv"
"strings"
"time"
"unicode"
)
// Build-time variables
var StormfetchVersion = "dev"
var SystemConfigDir = "/etc/"
// Flag variables
var ShowVersion = false
var ConfigPath = ""
var Ascii = ""
var ShowModuleTimeTaken = false
func main() {
parseFlags()
readConfig()
initializeModuleMap()
run()
}
func parseFlags() {
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.StringVar(&Ascii, "ascii", "", "Set distro ascii")
flag.Parse()
}
func run() {
if ShowVersion {
fmt.Printf("Stormfetch version %s\n", StormfetchVersion)
return
}
// Fetch ascii art and remove header
asciiArt := GetDistroAsciiArt()
asciiArtHeader := ""
if strings.HasPrefix(asciiArt, "#/") {
asciiArtHeader = strings.SplitN(asciiArt, "\n", 2)[0]
asciiArt = strings.SplitN(asciiArt, "\n", 2)[1]
}
asciiArtNoColor := asciiArt
// Rewrite last color code to the start of the next line
lastColor := "0"
for i := 1; i < len(asciiArt); i++ {
rune := rune(asciiArt[i])
if unicode.IsDigit(rune) && asciiArt[i-1] == '%' {
lastColor = string(rune)
}
if rune == '\n' {
asciiArt = asciiArt[:i+1] + "%" + lastColor + asciiArt[i+1:]
}
}
// Setup color map and replace colors in ascii art
colorMap := setupColorMap(asciiArtHeader)
for key, value := range colorMap {
asciiArt = strings.ReplaceAll(asciiArt, "%"+strconv.Itoa(key), value)
asciiArtNoColor = strings.ReplaceAll(asciiArtNoColor, "%"+strconv.Itoa(key), "")
}
// Execute modules in order
modulesText := make([]string, 0)
for _, moduleConfig := range config.Modules {
module, ok := Modules[moduleConfig.Name]
if !ok {
continue
}
// Set module config options
if moduleConfig.Format != "" {
module.Format = moduleConfig.Format
}
if moduleConfig.Data != nil {
module.Data = moduleConfig.Data
}
// Execute module
start := time.Now().UnixMilli()
text := module.Execute(module)
end := time.Now().UnixMilli()
// Insert default color at the start of the module's text
text = colorMap[0] + text
// Show time taken
if ShowModuleTimeTaken {
fmt.Printf("Module '%s' took %d milliseconds\n", module.Name, end-start)
}
// Replace colors in returned string
textNoColor := text
for key, value := range colorMap {
text = strings.ReplaceAll(text, "%"+strconv.Itoa(key), value)
textNoColor = strings.ReplaceAll(textNoColor, "%"+strconv.Itoa(key), value)
}
// Continue if text length is 0
if len(textNoColor)-len(colorMap[0]) == 0 {
continue
}
// Add text to slice
for _, line := range strings.Split(strings.TrimSpace(text), "\n") {
modulesText = append(modulesText, line)
}
}
// Get longest line in ascii art
maxWidth := 0
for _, line := range strings.Split(asciiArtNoColor, "\n") {
if len(line) > maxWidth {
maxWidth = len(line)
}
}
// Split ascii art into each line
asciiArtSplit := strings.Split(asciiArt, "\n")
asciiArtNoColorSplit := strings.Split(asciiArtNoColor, "\n")
// Get amount of lines to print
lineCount := max(len(asciiArtSplit), len(modulesText))
// Combine ascii art and module text
final := strings.Builder{}
for i := range lineCount {
// Write ascii art
currentLineLength := 0
if i < len(asciiArtSplit) {
final.WriteString(asciiArtSplit[i])
currentLineLength += len(asciiArtNoColorSplit[i])
}
// Write blank space between ascii art and module text
for i := currentLineLength; i < maxWidth+3; i++ {
final.WriteString(" ")
}
// Write module text
if i < len(modulesText) {
final.WriteString(modulesText[i])
}
final.WriteString("\n")
}
fmt.Println(strings.TrimRight(final.String(), "\n") + "\033[0m")
}
+57
View File
@@ -0,0 +1,57 @@
package main
import (
"bufio"
"os"
"strconv"
"strings"
)
type Memory struct {
MemTotal int
MemFree int
MemAvailable int
}
func GetMemoryInfo() *Memory {
toInt := func(raw string) int {
if raw == "" {
return 0
}
res, err := strconv.Atoi(raw)
if err != nil {
panic(err)
}
return res
}
parseLine := func(raw string) (key string, value int) {
text := strings.ReplaceAll(raw[:len(raw)-2], " ", "")
keyValue := strings.Split(text, ":")
return keyValue[0], toInt(keyValue[1])
}
if _, err := os.Stat("/proc/meminfo"); err != nil {
return nil
}
file, err := os.Open("/proc/meminfo")
if err != nil {
panic(err)
}
defer file.Close()
bufio.NewScanner(file)
scanner := bufio.NewScanner(file)
res := Memory{}
for scanner.Scan() {
key, value := parseLine(scanner.Text())
switch key {
case "MemTotal":
res.MemTotal = value / 1024
case "MemFree":
res.MemFree = value / 1024
case "MemAvailable":
res.MemAvailable = value / 1024
}
}
return &res
}
+435
View File
@@ -0,0 +1,435 @@
package main
import (
"os"
"reflect"
"strconv"
"strings"
)
// Used to declare modules in config files
type stormfetchModuleConfig struct {
Name string `yaml:"name"`
Format string `yaml:"format"`
Data map[string]any `yaml:"data"`
}
type StormfetchModule struct {
Execute func(StormfetchModule) string
stormfetchModuleConfig
}
var Modules map[string]StormfetchModule = make(map[string]StormfetchModule)
func (sm StormfetchModule) GetData(key string, defaultValue any) (any, bool) {
if sm.Data == nil {
return defaultValue, false
}
data, ok := sm.Data[key]
if !ok {
return defaultValue, false
}
if reflect.ValueOf(data).Kind() != reflect.ValueOf(defaultValue).Kind() {
return defaultValue, false
}
return data, true
}
func RegisterModule(module StormfetchModule) bool {
if _, ok := Modules[module.Name]; ok {
return false
}
Modules[module.Name] = module
return true
}
func initializeModuleMap() {
// Distribution Module
distributionModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "distribution", Format: "%3Distribution: %4$DISTRO_SHORT ($ARCH)"}, Execute: func(sm StormfetchModule) string {
distroInfo := GetDistroInfo()
return os.Expand(sm.Format, func(s string) string {
switch s {
case "DISTRO_ID":
return distroInfo.ID
case "DISTRO_SHORT":
return distroInfo.ShortName
case "DISTRO_LONG":
return distroInfo.LongName
case "ARCH":
return GetArch()
default:
return ""
}
})
}}
RegisterModule(distributionModule)
// Hostname module
hostnameModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "hostname", Format: "%3Hostname: %4$HOSTNAME"}, Execute: func(sm StormfetchModule) string {
hostname, _ := os.Hostname()
return os.Expand(sm.Format, func(s string) string {
switch s {
case "HOSTNAME":
return hostname
default:
return ""
}
})
}}
RegisterModule(hostnameModule)
// Kernel module
kernelModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "kernel", Format: "%3Kernel: %4$KERNEL_NAME $KERNEL_RELEASE"}, Execute: func(sm StormfetchModule) string {
kernelName, kernelRelease := GetKernel()
return os.Expand(sm.Format, func(s string) string {
switch s {
case "KERNEL_NAME":
return kernelName
case "KERNEL_RELEASE":
return kernelRelease
default:
return ""
}
})
}}
RegisterModule(kernelModule)
// Packages module
packagesModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "packages", Format: "%3Packages: %4$PACKAGES"}, Execute: func(sm StormfetchModule) string {
return os.Expand(sm.Format, func(s string) string {
switch s {
case "PACKAGES":
return GetInstalledPackages()
default:
return ""
}
})
}}
RegisterModule(packagesModule)
// Shell module
shellModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "shell", Format: "%3Shell: %4$SHELL"}, Execute: func(sm StormfetchModule) string {
return os.Expand(sm.Format, func(s string) string {
switch s {
case "SHELL":
return GetShell()
default:
return ""
}
})
}}
RegisterModule(shellModule)
// Init system module
initSystemModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "init_system", Format: "%3Init: %4$INIT"}, Execute: func(sm StormfetchModule) string {
return os.Expand(sm.Format, func(s string) string {
switch s {
case "INIT":
return GetInitSystem()
default:
return ""
}
})
}}
RegisterModule(initSystemModule)
// Libc module
libcModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "libc", Format: "%3Libc: %4$LIBC"}, Execute: func(sm StormfetchModule) string {
return os.Expand(sm.Format, func(s string) string {
switch s {
case "LIBC":
return GetLibc()
default:
return ""
}
})
}}
RegisterModule(libcModule)
// Motherboard module
MotherboardModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "motherboard", Format: "%3Motherboard: %4$MOTHERBOARD"}, Execute: func(sm StormfetchModule) string {
motherboard := GetMotherboardModel()
// Return empty string if can't detect motherboard model
if motherboard == "" {
return ""
}
return os.Expand(sm.Format, func(s string) string {
switch s {
case "MOTHERBOARD":
return motherboard
default:
return ""
}
})
}}
RegisterModule(MotherboardModule)
// Motherboard module
cpusModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "cpus", Format: "%3CPU: %4$CPU_MODEL ($CPU_THREADS threads)"}, Execute: func(sm StormfetchModule) string {
hiddenCPUsInterface, _ := sm.GetData("hidden_cpus", make([]any, 0))
// Convert interface slices to string slices
hiddenCPUs := make([]int, 0)
for _, value := range hiddenCPUsInterface.([]any) {
hiddenCPUs = append(hiddenCPUs, value.(int))
}
builder := strings.Builder{}
cpus := GetCPUs(hiddenCPUs)
for i, cpu := range cpus {
expanded := os.Expand(sm.Format, func(s string) string {
switch s {
case "CPU_NUM":
return strconv.Itoa(i + 1)
case "CPU_VENDOR":
return cpu.Vendor
case "CPU_MODEL":
return cpu.Model
case "CPU_CORES":
return strconv.Itoa(cpu.Cores)
case "CPU_THREADS":
return strconv.Itoa(cpu.Threads)
default:
return ""
}
})
builder.WriteString(expanded + "\n")
}
return builder.String()
}}
RegisterModule(cpusModule)
// GPUs module
gpusModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "gpus", Format: "%3GPU: %4$GPU_MODEL"}, Execute: func(sm StormfetchModule) string {
hiddenGPUsInterface, _ := sm.GetData("hidden_gpus", make([]any, 0))
// Convert interface slices to string slices
hiddenGPUs := make([]int, 0)
for _, value := range hiddenGPUsInterface.([]any) {
hiddenGPUs = append(hiddenGPUs, value.(int))
}
builder := strings.Builder{}
gpus := GetGPUModels(hiddenGPUs)
for i, gpu := range gpus {
expanded := os.Expand(sm.Format, func(s string) string {
switch s {
case "GPU_NUM":
return strconv.Itoa(i + 1)
case "GPU_MODEL":
return gpu
default:
return ""
}
})
builder.WriteString(expanded + "\n")
}
return builder.String()
}}
RegisterModule(gpusModule)
// Memory module
memoryModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "memory", Format: "%3Memory: %4$MEM_USED MiB / $MEM_TOTAL MiB"}, Execute: func(sm StormfetchModule) string {
memoryInfo := GetMemoryInfo()
return os.Expand(sm.Format, func(s string) string {
switch s {
case "MEM_TOTAL":
return strconv.Itoa(memoryInfo.MemTotal)
case "MEM_AVAILABLE":
return strconv.Itoa(memoryInfo.MemAvailable)
case "MEM_FREE":
return strconv.Itoa(memoryInfo.MemFree)
case "MEM_USED":
return strconv.Itoa(memoryInfo.MemTotal - memoryInfo.MemAvailable)
default:
return ""
}
})
}}
RegisterModule(memoryModule)
// Partitions module
partitionsModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "partitions", Format: "%3Partition ${PART_AUTONAME} (${PART_FS}): %4${PART_USED} / ${PART_TOTAL}"}, Execute: func(sm StormfetchModule) string {
hiddenPartitionsInterface, _ := sm.GetData("hidden_partitions", make([]any, 0))
hiddenFilesystemsInterface, _ := sm.GetData("hidden_filesystems", make([]any, 0))
alternativeNamesInterface, _ := sm.GetData("alternative_names", make(map[string]any, 0))
// Convert interface slices to string slices
hiddenPartitions := make([]string, 0)
for _, value := range hiddenPartitionsInterface.([]any) {
hiddenPartitions = append(hiddenPartitions, value.(string))
}
hiddenFilesystems := make([]string, 0)
for _, value := range hiddenFilesystemsInterface.([]any) {
hiddenFilesystems = append(hiddenFilesystems, value.(string))
}
// Convert interface map to map[string]string
alternativeNames := make(map[string]string)
for key, value := range alternativeNamesInterface.(map[string]any) {
alternativeNames[key] = value.(string)
}
builder := strings.Builder{}
partitions := GetMountedPartitions(hiddenPartitions, hiddenFilesystems)
for i, partition := range partitions {
partitionAutoname := ""
if altName, ok := alternativeNames[partition.Device]; ok {
partitionAutoname = altName
} else if partition.Label != "" {
partitionAutoname = partition.Label
} else {
partitionAutoname = partition.MountPoint
}
expanded := os.Expand(sm.Format, func(s string) string {
switch s {
case "PART_NUM":
return strconv.Itoa(i + 1)
case "PART_FS":
return partition.FileystemType
case "PART_DEVICE":
return partition.Device
case "PART_AUTONAME":
return partitionAutoname
case "PART_LABEL":
return partition.Label
case "PART_MOUNTPOINT":
return partition.MountPoint
case "PART_FREE":
return FormatBytes(partition.FreeSize)
case "PART_USED":
return FormatBytes(partition.UsedSize)
case "PART_TOTAL":
return FormatBytes(partition.TotalSize)
default:
return ""
}
})
builder.WriteString(expanded + "\n")
}
return builder.String()
}}
RegisterModule(partitionsModule)
// Local IP module
localIpModule := StormfetchModule{stormfetchModuleConfig: stormfetchModuleConfig{Name: "local_ip", Format: "%3Local IP: %4$LOCAL_IP"}, Execute: func(sm StormfetchModule) string {
return os.Expand(sm.Format, func(s string) string {
switch s {
case "LOCAL_IP":
return GetLocalIP()
default:
return ""
}
})
}}
RegisterModule(localIpModule)
// 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 {
// Return empty string if currently in TTY
if os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "tty" {
return ""
}
dewm := GetDEWM()
// Return empty string if can't detect DE/WM
if dewm.Name == "Unknown" {
return ""
}
return os.Expand(sm.Format, func(s string) string {
switch s {
case "DEWM_NAME":
return dewm.Name
case "DEWM_TYPE":
return dewm.Type
case "DEWM_VERSION":
return dewm.Version
case "DISPLAY_PROTOCOL":
return GetDisplayProtocol()
default:
return ""
}
})
}}
RegisterModule(dewmModule)
// 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 {
builder := strings.Builder{}
monitors := GetMonitors()
for i, monitor := range monitors {
expanded := os.Expand(sm.Format, func(s string) string {
switch s {
case "MONITOR_NUM":
return strconv.Itoa(i + 1)
case "MONITOR_WIDTH":
return strconv.Itoa(monitor.Width)
case "MONITOR_HEIGHT":
return strconv.Itoa(monitor.Height)
case "MONITOR_REFRESH_RATE":
return strconv.Itoa(monitor.RefreshRate)
default:
return ""
}
})
builder.WriteString(expanded + "\n")
}
return builder.String()
}}
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)
}
+15
View File
@@ -0,0 +1,15 @@
package main
import "net"
func GetLocalIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return ""
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
+124
View File
@@ -0,0 +1,124 @@
package main
import (
"os"
"path/filepath"
"slices"
"strings"
"syscall"
)
type partition struct {
Device string
MountPoint string
Label string
FileystemType string
TotalSize uint64
UsedSize uint64
FreeSize uint64
}
func GetMountedPartitions(hiddenPartitions, hiddenFilesystems []string) []partition {
// Get all filesystem and partition labels
fslabels, err := os.ReadDir("/dev/disk/by-label")
if err != nil && !os.IsNotExist(err) {
return nil
}
partlabels, err := os.ReadDir("/dev/disk/by-partlabel")
if err != nil && !os.IsNotExist(err) {
return nil
}
labels := make(map[string]string)
for _, entry := range partlabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-partlabel/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
for _, entry := range fslabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-label/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
// Get all mounted partitions
file, err := os.ReadFile("/proc/mounts")
if err != nil {
return nil
}
var partitions []partition
for _, entry := range strings.Split(string(file), "\n") {
fields := strings.Fields(entry)
if entry == "" {
continue
}
// Skip virtual partitions not under /dev
if !strings.HasPrefix(fields[0], "/dev") {
continue
}
// Skip partition if explicitly hidden
if slices.Contains(hiddenPartitions, fields[0]) {
continue
}
// Skip filesystem if explicitely hidden
if slices.Contains(hiddenFilesystems, fields[2]) {
continue
}
device, err := filepath.EvalSymlinks(fields[0])
if err != nil {
device = fields[0]
}
p := partition{
Device: device,
MountPoint: fields[1],
Label: "",
FileystemType: fields[2],
TotalSize: 0,
UsedSize: 0,
FreeSize: 0,
}
// Skip already added partitions
skip := false
for _, part := range partitions {
if part.Device == p.Device {
skip = true
}
}
if skip {
continue
}
// Set partition label if available
if value, ok := labels[p.Device]; ok {
p.Label = strings.ReplaceAll(value, "\\x20", " ")
}
// Get partition total, used and free space
buf := new(syscall.Statfs_t)
err = syscall.Statfs(p.MountPoint, buf)
if err != nil {
continue
}
totalBlocks := buf.Blocks
freeBlocks := buf.Bfree
usedBlocks := totalBlocks - freeBlocks
blockSize := uint64(buf.Bsize)
p.TotalSize = totalBlocks * blockSize
p.FreeSize = freeBlocks * blockSize
p.UsedSize = usedBlocks * blockSize
partitions = append(partitions, p)
}
return partitions
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"fmt"
"os/exec"
"strings"
)
type PackageManager struct {
Name string
ExecutableName string
PackageListCommand string
}
var PackageManagers = []PackageManager{
{Name: "dpkg", ExecutableName: "dpkg", PackageListCommand: "dpkg-query -f '${Package}\\n' -W"},
{Name: "pacman", ExecutableName: "pacman", PackageListCommand: "pacman -Q"},
{Name: "rpm", ExecutableName: "rpm", PackageListCommand: "rpm -qa"},
{Name: "xbps", ExecutableName: "xbps-query", PackageListCommand: "xbps-query -l"},
{Name: "bpm", ExecutableName: "bpm", PackageListCommand: "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
}
+183
View File
@@ -0,0 +1,183 @@
package main
import (
"os"
"os/exec"
"path"
"strings"
"syscall"
"github.com/mitchellh/go-ps"
)
type DistroInfo struct {
ID string
LongName string
ShortName string
}
func GetDistroInfo() DistroInfo {
info := DistroInfo{
ID: "unknown",
LongName: "Unknown",
ShortName: "Unknown",
}
// Detect release file location
var releaseFile string
if _, err := os.Stat("/bedrock/etc/os-release"); os.Getenv("BEDROCK_RESTRICT") == "" && err == nil {
// Using Bedrock Linux
releaseFile = "/bedrock/etc/os-release"
} else if _, err := os.Stat("/etc/os-release"); err == nil {
// Using a regular linux distribution
releaseFile = "/etc/os-release"
} else {
return info
}
// Read release file
releaseMap, err := ReadKeyValueFile(releaseFile)
if err != nil {
return info
}
// Set struct fields
if id, ok := releaseMap["ID"]; ok {
info.ID = id
}
if longName, ok := releaseMap["PRETTY_NAME"]; ok {
info.LongName = longName
}
if shortName, ok := releaseMap["NAME"]; ok {
info.ShortName = shortName
}
return info
}
func GetDistroAsciiArt() string {
defaultAscii :=
` .--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/'\
\___)=(___/`
// Get ascii name to use
var asciiName string
if Ascii != "" {
asciiName = Ascii
} else if config.Ascii == "auto" {
asciiName = GetDistroInfo().ID
} else {
asciiName = config.Ascii
}
// Check for ascii art in home directory
userConfDir, err := os.UserConfigDir()
if err == nil {
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", asciiName)); err == nil {
if bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", asciiName)); 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
}
func GetArch() string {
uname := syscall.Utsname{}
err := syscall.Uname(&uname)
if err != nil {
return "unknown"
}
var byteString [65]byte
var indexLength int
for ; uname.Machine[indexLength] != 0; indexLength++ {
byteString[indexLength] = uint8(uname.Machine[indexLength])
}
return string(byteString[:indexLength])
}
func GetKernel() (string, string) {
uname := syscall.Utsname{}
err := syscall.Uname(&uname)
if err != nil {
return "unknown", "unknown"
}
var kernelNameByteString [65]byte
var kernelNameLength int
for ; uname.Sysname[kernelNameLength] != 0; kernelNameLength++ {
kernelNameByteString[kernelNameLength] = uint8(uname.Sysname[kernelNameLength])
}
var kernelReleaseByteString [65]byte
var kernelReleaseLength int
for ; uname.Release[kernelReleaseLength] != 0; kernelReleaseLength++ {
kernelReleaseByteString[kernelReleaseLength] = uint8(uname.Release[kernelReleaseLength])
}
return string(kernelNameByteString[:kernelNameLength]), string(kernelReleaseByteString[:kernelReleaseLength])
}
func GetInitSystem() string {
process, err := ps.FindProcess(1)
if err != nil {
return ""
}
// Special cases
// OpenRC check
if _, err := os.Stat("/usr/sbin/openrc"); err == nil {
return "OpenRC " + runCommand("openrc --version | awk '{print $3}'", "/bin/sh")
}
// Default PID 1 process name checking
switch process.Executable() {
case "systemd":
return "Systemd " + runCommand("systemctl --version | head -n1 | awk '{print $2}'", "/bin/sh")
case "runit":
return "Runit"
case "dinit":
return "Dinit " + runCommand("dinit --version | head -n1 | awk '{print substr($3, 1, length($3)-1)}'", "/bin/sh")
case "enit":
return "Enit " + runCommand("enit --version | awk '{print $3}'", "/bin/sh")
default:
return process.Executable()
}
}
func GetLibc() string {
checkLibcOutput, err := exec.Command("ldd", "/usr/bin/ls").Output()
if err != nil {
return "Unknown"
}
if strings.Contains(string(checkLibcOutput), "ld-musl") {
// Using Musl Libc
output, _ := exec.Command("ldd").CombinedOutput()
return "Musl " + strings.TrimPrefix(strings.Split(strings.TrimSpace(string(output)), "\n")[1], "Version ")
} else {
// Using Glibc
cmd := exec.Command("ldd", "--version")
output, err := cmd.Output()
if err != nil {
return "Glibc"
}
outputSplit := strings.Split(strings.Split(strings.TrimSpace(string(output)), "\n")[0], " ")
ver := outputSplit[len(outputSplit)-1]
return "Glibc " + ver
}
}
+172
View File
@@ -0,0 +1,172 @@
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 ""
}
+74
View File
@@ -0,0 +1,74 @@
package main
import (
"fmt"
"math"
"os"
"os/exec"
"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 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))
}