24 Commits
Author SHA1 Message Date
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
37 changed files with 1636 additions and 1115 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 CapCreeperGR Copyright (c) 2024 EnumDev
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+23 -28
View File
@@ -1,40 +1,35 @@
SHELL := /bin/bash # Installation paths
PREFIX ?= /usr/local
ifeq ($(PREFIX),) BINDIR ?= $(PREFIX)/bin
PREFIX := /usr/local
endif
ifeq ($(BINDIR),)
BINDIR := $(PREFIX)/bin
endif
ifeq ($(SYSCONFDIR),)
SYSCONFDIR := $(PREFIX)/etc SYSCONFDIR := $(PREFIX)/etc
endif
ifeq ($(GO),) # Compilers and tools
GO := $(shell type -a -P go | head -n 1) GO ?= go
endif
# Build-time variables
VERSION ?= $(shell git describe --tags --dirty)
build: build:
mkdir -p build mkdir -p build
$(GO) build -ldflags "-w -X 'main.systemConfigDir=$(SYSCONFDIR)'" -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/ install: build/stormfetch config/
mkdir -p $(DESTDIR)$(BINDIR) # Create directoriy
mkdir -p $(DESTDIR)$(SYSCONFDIR)/stormfetch/ install -dm755 $(DESTDIR)$(BINDIR)
cp build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch # Install binary
cp -r config/. $(DESTDIR)$(SYSCONFDIR)/stormfetch/ install -m755 build/stormfetch $(DESTDIR)$(BINDIR)/stormfetch
compress: build/stormfetch config/ install-config:
mkdir -p stormfetch/$(BINDIR) # Create directory
mkdir -p stormfetch/$(SYSCONFDIR)/stormfetch/ install -dm755 $(DESTDIR)$(SYSCONFDIR)
cp build/stormfetch stormfetch/$(BINDIR)/stormfetch # Install files
cp -r config/. stormfetch/$(SYSCONFDIR)/stormfetch/ cp -r config $(DESTDIR)$(SYSCONFDIR)/stormfetch/
tar --owner=root --group=root -czf stormfetch.tar.gz stormfetch
rm -r stormfetch
run: build/stormfetch uninstall:
build/stormfetch -rm -f $(DESTDIR)$(BINDIR)/stormfetch
-rm -rf $(DESTDIR)$(SYSCONFDIR)/stormfetch
clean: clean:
rm -r build/ rm -r build/
.PHONY: build .PHONY: build install install-config uninstall clean
+1 -4
View File
@@ -1,9 +1,6 @@
# Stormfetch # Stormfetch
## A simple linux fetch program written in go and bash ## A simple linux fetch program written in go
### Developers:
- [EnumDev](https://gitlab.com/EnumDev)
### 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.
+4 -4
View File
@@ -1,5 +1,5 @@
#/4;27;4;11 #/4;27;4;11
${C1} -` %1 -`
.o+` .o+`
`ooo/ `ooo/
`+oooo: `+oooo:
@@ -8,9 +8,9 @@ ${C1} -`
`/:-:++oooo+: `/:-:++oooo+:
`/++++/+++++++: `/++++/+++++++:
`/++++++++++++++: `/++++++++++++++:
`/+++o${C2}oooooooo${C1}oooo/` `/+++o%2oooooooo%1oooo/`
${C2} ${C1}./${C2}ooosssso++osssssso${C1}+` %2 %1./%2ooosssso++osssssso%1+`
${C2} .oossssso-````/ossssss+` %2 .oossssso-````/ossssss+`
-osssssso. :ssssssso. -osssssso. :ssssssso.
:osssssss/ osssso+++. :osssssss/ osssso+++.
/ossssssss/ +ssssooo/- /ossssssss/ +ssssooo/-
+1 -1
View File
@@ -1,5 +1,5 @@
#/45;27;45;7 #/45;27;45;7
${C1} 'o' %1 'o'
'ooo' 'ooo'
'ooxoo' 'ooxoo'
'ooxxxoo' 'ooxxxoo'
+12
View File
@@ -0,0 +1,12 @@
#/7;8;8;7
%1 \\\\\\\\\\\\
\\\ \\\
\\\ \\\
\\\ \\\\\\\\\\\\\\\\\
\\\ \\\
\\\ \\\
\\\ %2______ \\\
\\\ ///
\\\ ///
\\\ ///
\\\////////////////
+21 -21
View File
@@ -1,23 +1,23 @@
#/43;45;45;15 #/43;45;45;15
${C3}.${C1}-------------------------: ${C3}.%1-------------------------:
.${C2}+=${C1}========================. .%2+=%1========================.
:${C2}++${C1}===${C2}++===${C1}===============- :${C2}++${C1}- :%2++%1===%2++===%1===============- :%2++%1-
:${C2}*++${C1}====${C2}+++++==${C1}===========- .==: :%2*++%1====%2+++++==%1===========- .==:
-${C2}*+++${C1}=====${C2}+***++=${C1}=========: -%2*+++%1=====%2+***++=%1=========:
=${C2}*++++=${C1}=======------------: =%2*++++=%1=======------------:
=${C2}*+++++=${C1}====- ${C3}...${C1} =%2*+++++=%1====- ${C3}...%1
.${C2}+*+++++${C1}=-===: .${C2}=+++=${C1}: .%2+*+++++%1=-===: .%2=+++=%1:
:${C2}++++${C1}=====-==: -***${C2}**${C1}+ :%2++++%1=====-==: -***%2**%1+
:${C2}++=${C1}=======-=. .=+**+${C3}.${C1} :%2++=%1=======-=. .=+**+${C3}.%1
.${C2}+${C1}==========-. ${C3}.${C1} .%2+%1==========-. ${C3}.%1
:${C2}+++++++${C1}====- ${C3}.${C1}--==-${C3}.${C1} :%2+++++++%1====- ${C3}.%1--==-${C3}.%1
:${C2}++${C1}==========. ${C3}:${C2}+++++++${C1}${C3}: :%2++%1==========. ${C3}:%2+++++++%1${C3}:
${C1}.-===========. =*****+*+ %1.-===========. =*****+*+
${C1}.-===========: .+*****+: %1.-===========: .+*****+:
${C1}-=======${C2}++++${C1}:::::::::::::::::::::::::-: ${C3}.${C1}---: %1-=======%2++++%1:::::::::::::::::::::::::-: ${C3}.%1---:
:======${C2}++++${C1}====${C2}+++******************=. :======%2++++%1====%2+++******************=.
${C1}:=====${C2}+++${C1}==========${C2}++++++++++++++*- %1:=====%2+++%1==========%2++++++++++++++*-
${C1}.====${C2}++${C1}==============${C2}++++++++++*- %1.====%2++%1==============%2++++++++++*-
${C1}.===${C2}+${C1}==================${C2}+++++++: %1.===%2+%1==================%2+++++++:
${C1}.-=======================${C2}+++: %1.-=======================%2+++:
${C3}.......................... ${C3}..........................
+7 -7
View File
@@ -1,16 +1,16 @@
#/1;7;9;15 #/1;7;9;15
${C2} _,met&&&&&gg. %2 _,met&&&&&gg.
,g&&&&&&&&&&&&&&&P. ,g&&&&&&&&&&&&&&&P.
,g&&P" """Y&&.". ,g&&P" """Y&&.".
,&&P' `&&&. ,&&P' `&&&.
',&&P ,ggs. `&&b: ',&&P ,ggs. `&&b:
`d&&' ,&P"' ${C1}.${C2} &&& `d&&' ,&P"' %1.%2 &&&
&&P d&' ${C1},${C2} &&P &&P d&' %1,%2 &&P
&&: &&. ${C1}-${C2} ,d&&' &&: &&. %1-%2 ,d&&'
&&; Y&b._ _,d&P' &&; Y&b._ _,d&P'
Y&&. ${C1}`.${C2}`"Y&&&&P"' Y&&. %1`.%2`"Y&&&&P"'
${C2} `&&b ${C1}"-.__ %2 `&&b %1"-.__
${C2} `Y&& %2 `Y&&
`Y&&. `Y&&.
`&&b. `&&b.
`Y&&b. `Y&&b.
+13 -13
View File
@@ -1,20 +1,20 @@
#/12;7;4;15 #/12;7;4;15
${C1} .',;::::;,'. %1 .',;::::;,'.
.';:cccccccccccc:;,. .';:cccccccccccc:;,.
.;cccccccccccccccccccccc;. .;cccccccccccccccccccccc;.
.:cccccccccccccccccccccccccc:. .:cccccccccccccccccccccccccc:.
.;ccccccccccccc;${C2}.:dddl:.${C1};ccccccc;. .;ccccccccccccc;%2.:dddl:.%1;ccccccc;.
.:ccccccccccccc;${C2}OWMKOOXMWd${C1};ccccccc:. .:ccccccccccccc;%2OWMKOOXMWd%1;ccccccc:.
.:ccccccccccccc;${C2}KMMc${C1};cc;${C2}xMMc${C1};ccccccc:. .:ccccccccccccc;%2KMMc%1;cc;%2xMMc%1;ccccccc:.
,cccccccccccccc;${C2}MMM.${C1};cc;${C2};WW:${C1};cccccccc, ,cccccccccccccc;%2MMM.%1;cc;%2;WW:%1;cccccccc,
:cccccccccccccc;${C2}MMM.${C1};cccccccccccccccc: :cccccccccccccc;%2MMM.%1;cccccccccccccccc:
:ccccccc;${C2}oxOOOo${C1};${C2}MMM0OOk.${C1};cccccccccccc: :ccccccc;%2oxOOOo%1;%2MMM0OOk.%1;cccccccccccc:
cccccc;${C2}0MMKxdd:${C1};${C2}MMMkddc.${C1};cccccccccccc; cccccc;%20MMKxdd:%1;%2MMMkddc.%1;cccccccccccc;
ccccc;${C2}XM0'${C1};cccc;${C2}MMM.${C1};cccccccccccccccc' ccccc;%2XM0'%1;cccc;%2MMM.%1;cccccccccccccccc'
ccccc;${C2}MMo${C1};ccccc;${C2}MMW.${C1};ccccccccccccccc; ccccc;%2MMo%1;ccccc;%2MMW.%1;ccccccccccccccc;
ccccc;${C2}0MNc.${C1}ccc${C2}.xMMd${C1};ccccccccccccccc; ccccc;%20MNc.%1ccc%2.xMMd%1;ccccccccccccccc;
cccccc;${C2}dNMWXXXWM0:${C1};cccccccccccccc:, cccccc;%2dNMWXXXWM0:%1;cccccccccccccc:,
cccccccc;${C2}.:odl:.${C1};cccccccccccccc:,. cccccccc;%2.:odl:.%1;cccccccccccccc:,.
:cccccccccccccccccccccccccccc:'. :cccccccccccccccccccccccccccc:'.
.:cccccccccccccccccccccc:;,.. .:cccccccccccccccccccccc:;,..
'::cccccccccccccc::;,. '::cccccccccccccc::;,.
+17 -17
View File
@@ -1,19 +1,19 @@
#/5;7;13;69 #/5;7;13;69
${C1} -/oyddmdhs+:. %1 -/oyddmdhs+:.
-o${C2}dNMMMMMMMMNNmhy+${C1}-` -o%2dNMMMMMMMMNNmhy+%1-`
-y${C2}NMMMMMMMMMMMNNNmmdhy${C1}+- -y%2NMMMMMMMMMMMNNNmmdhy%1+-
`o${C2}mMMMMMMMMMMMMNmdmmmmddhhy${C1}/` `o%2mMMMMMMMMMMMMNmdmmmmddhhy%1/`
om${C2}MMMMMMMMMMMN${C1}hhyyyo${C2}hmdddhhhd${C1}o` om%2MMMMMMMMMMMN%1hhyyyo%2hmdddhhhd%1o`
.y${C2}dMMMMMMMMMMd${C1}hs++so/s${C2}mdddhhhhdm${C1}+` .y%2dMMMMMMMMMMd%1hs++so/s%2mdddhhhhdm%1+`
oy${C2}hdmNMMMMMMMN${C1}dyooy${C2}dmddddhhhhyhN${C1}d. oy%2hdmNMMMMMMMN%1dyooy%2dmddddhhhhyhN%1d.
:o${C2}yhhdNNMMMMMMMNNNmmdddhhhhhyym${C1}Mh :o%2yhhdNNMMMMMMMNNNmmdddhhhhhyym%1Mh
.:${C2}+sydNMMMMMNNNmmmdddhhhhhhmM${C1}my .:%2+sydNMMMMMNNNmmmdddhhhhhhmM%1my
/m${C2}MMMMMMNNNmmmdddhhhhhmMNh${C1}s: /m%2MMMMMMNNNmmmdddhhhhhmMNh%1s:
`o${C2}NMMMMMMMNNNmmmddddhhdmMNhs${C1}+` `o%2NMMMMMMMNNNmmmddddhhdmMNhs%1+`
`s${C2}NMMMMMMMMNNNmmmdddddmNMmhs${C1}/. `s%2NMMMMMMMMNNNmmmdddddmNMmhs%1/.
/N${C2}MMMMMMMMNNNNmmmdddmNMNdso${C1}:` /N%2MMMMMMMMNNNNmmmdddmNMNdso%1:`
+M${C2}MMMMMMNNNNNmmmmdmNMNdso${C1}/- +M%2MMMMMMNNNNNmmmmdmNMNdso%1/-
yM${C2}MNNNNNNNmmmmmNNMmhs+/${C1}-` yM%2MNNNNNNNmmmmmNNMmhs+/%1-`
/h${C2}MMNNNNNNNNMNdhs++/${C1}-` /h%2MMNNNNNNNNMNdhs++/%1-`
`/${C2}ohdmmddhys+++/:${C1}.` `/%2ohdmmddhys+++/:%1.`
`-//////:--. `-//////:--.
+19 -19
View File
@@ -1,20 +1,20 @@
#/34;15;46;252 #/34;15;46;252
${C2} ...-:::::-... %2 ...-:::::-...
${C2} .-MMMMMMMMMMMMMMM-. %2 .-MMMMMMMMMMMMMMM-.
.-MMMM${C1}`..-:::::::-..`${C2}MMMM-. .-MMMM%1`..-:::::::-..`%2MMMM-.
.:MMMM${C1}.:MMMMMMMMMMMMMMM:.${C2}MMMM:. .:MMMM%1.:MMMMMMMMMMMMMMM:.%2MMMM:.
-MMM${C1}-M---MMMMMMMMMMMMMMMMMMM.${C2}MMM- -MMM%1-M---MMMMMMMMMMMMMMMMMMM.%2MMM-
`:MMM${C1}:MM` :MMMM:....::-...-MMMM:${C2}MMM:` `:MMM%1:MM` :MMMM:....::-...-MMMM:%2MMM:`
:MMM${C1}:MMM` :MM:` `` `` `:MMM:${C2}MMM: :MMM%1:MMM` :MM:` `` `` `:MMM:%2MMM:
.MMM${C1}.MMMM` :MM. -MM. .MM- `MMMM.${C2}MMM. .MMM%1.MMMM` :MM. -MM. .MM- `MMMM.%2MMM.
:MMM${C1}:MMMM` :MM. -MM- .MM: `MMMM-${C2}MMM: :MMM%1:MMMM` :MM. -MM- .MM: `MMMM-%2MMM:
:MMM${C1}:MMMM` :MM. -MM- .MM: `MMMM:${C2}MMM: :MMM%1:MMMM` :MM. -MM- .MM: `MMMM:%2MMM:
:MMM${C1}:MMMM` :MM. -MM- .MM: `MMMM-${C2}MMM: :MMM%1:MMMM` :MM. -MM- .MM: `MMMM-%2MMM:
.MMM${C1}.MMMM` :MM:--:MM:--:MM: `MMMM.${C2}MMM. .MMM%1.MMMM` :MM:--:MM:--:MM: `MMMM.%2MMM.
:MMM${C1}:MMM- `-MMMMMMMMMMMM-` -MMM-${C2}MMM: :MMM%1:MMM- `-MMMMMMMMMMMM-` -MMM-%2MMM:
:MMM${C1}:MMM:` `:MMM:${C2}MMM: :MMM%1:MMM:` `:MMM:%2MMM:
.MMM${C1}.MMMM:--------------:MMMM.${C2}MMM. .MMM%1.MMMM:--------------:MMMM.%2MMM.
'-MMMM${C1}.-MMMMMMMMMMMMMMM-.${C2}MMMM-' '-MMMM%1.-MMMMMMMMMMMMMMM-.%2MMMM-'
'.-MMMM${C1}``--:::::--``${C2}MMMM-.' '.-MMMM%1``--:::::--``%2MMMM-.'
${C2} '-MMMMMMMMMMMMM-' %2 '-MMMMMMMMMMMMM-'
${C2} ``-:::::-`` %2 ``-:::::-``
+12 -12
View File
@@ -1,18 +1,18 @@
#/2;7;10;15 #/2;7;10;15
${C2} .;ldkO0000Okdl;. %2 .;ldkO0000Okdl;.
.;d00xl:^''''''^:ok00d;. .;d00xl:^''''''^:ok00d;.
.d00l' 'o00d. .d00l' 'o00d.
.d0Kd'${C1} Okxol:;,. ${C2}:O0d. .d0Kd'%1 Okxol:;,. %2:O0d.
.OK${C1}KKK0kOKKKKKKKKKKOxo:, ${C2}lKO. .OK%1KKK0kOKKKKKKKKKKOxo:, %2lKO.
,0K${C1}KKKKKKKKKKKKKKK0P^${C2},,,${C1}^dx:${C2} ;00, ,0K%1KKKKKKKKKKKKKKK0P^%2,,,%1^dx:%2 ;00,
.OK${C1}KKKKKKKKKKKKKKKk'${C2}.oOPPb.${C1}'0k.${C2} cKO. .OK%1KKKKKKKKKKKKKKKk'%2.oOPPb.%1'0k.%2 cKO.
:KK${C1}KKKKKKKKKKKKKKK: ${C2}kKx..dd ${C1}lKd${C2} 'OK: :KK%1KKKKKKKKKKKKKKK: %2kKx..dd %1lKd%2 'OK:
dKK${C1}KKKKKKKKKOx0KKKd ${C2}^0KKKO' ${C1}kKKc${C2} dKd dKK%1KKKKKKKKKOx0KKKd %2^0KKKO' %1kKKc%2 dKd
dKK${C1}KKKKKKKKKK;.;oOKx,..${C2}^${C1}..;kKKK0.${C2} dKd dKK%1KKKKKKKKKK;.;oOKx,..%2^%1..;kKKK0.%2 dKd
:KK${C1}KKKKKKKKKK0o;...^cdxxOK0O/^^' ${C2}.0K: :KK%1KKKKKKKKKK0o;...^cdxxOK0O/^^' %2.0K:
kKK${C1}KKKKKKKKKKKKK0x;,,......,;od ${C2}lKk kKK%1KKKKKKKKKKKKK0x;,,......,;od %2lKk
'0K${C1}KKKKKKKKKKKKKKKKKKKK00KKOo^ ${C2}c00' '0K%1KKKKKKKKKKKKKKKKKKKK00KKOo^ %2c00'
'kK${C1}KKOxddxkOO00000Okxoc;'' ${C2}.dKk' 'kK%1KKOxddxkOO00000Okxoc;'' %2.dKk'
l0Ko. .c00l' l0Ko. .c00l'
'l0Kk:. .;xK0l' 'l0Kk:. .;xK0l'
'lkK0xl:;,,,,;:ldO0kl' 'lkK0xl:;,,,,;:ldO0kl'
+1 -1
View File
@@ -1,5 +1,5 @@
#/2;7;10;15 #/2;7;10;15
${C1} ...... %1 ......
.,cdxxxoc,. .:kKMMMNWMMMNk:. .,cdxxxoc,. .:kKMMMNWMMMNk:.
cKMMN0OOOKWMMXo. ; ;0MWk:. .:OMMk. cKMMN0OOOKWMMXo. ; ;0MWk:. .:OMMk.
;WMK;. .lKMMNM, :NMK, .OMW; ;WMK;. .lKMMNM, :NMK, .OMW;
+16 -16
View File
@@ -1,21 +1,21 @@
#/33;7;11;15 #/33;7;11;15
${C1} ///////////// %1 /////////////
///////////////////// /////////////////////
///////${C2}*767${C1}//////////////// ///////%2*767%1////////////////
//////${C2}7676767676*${C1}////////////// //////%27676767676*%1//////////////
/////${C2}76767${C1}//${C2}7676767${C1}////////////// /////%276767%1//%27676767%1//////////////
/////${C2}767676${C1}///${C2}*76767${C1}/////////////// /////%2767676%1///%2*76767%1///////////////
///////${C2}767676${C1}///${C2}76767${C1}.///${C2}7676*${C1}/////// ///////%2767676%1///%276767%1.///%27676*%1///////
/////////${C2}767676${C1}//${C2}76767${C1}///${C2}767676${C1}//////// /////////%2767676%1//%276767%1///%2767676%1////////
//////////${C2}76767676767${C1}////${C2}76767${C1}///////// //////////%276767676767%1////%276767%1/////////
///////////${C2}76767676${C1}//////${C2}7676${C1}////////// ///////////%276767676%1//////%27676%1//////////
////////////,${C2}7676${C1},///////${C2}767${C1}/////////// ////////////,%27676%1,///////%2767%1///////////
/////////////*${C2}7676${C1}///////${C2}76${C1}//////////// /////////////*%27676%1///////%276%1////////////
///////////////${C2}7676${C1}//////////////////// ///////////////%27676%1////////////////////
///////////////${C2}7676${C1}///${C2}767${C1}//////////// ///////////////%27676%1///%2767%1////////////
//////////////////////${C2}'${C1}//////////// //////////////////////%2'%1////////////
//////${C2}.7676767676767676767,${C1}////// //////%2.7676767676767676767,%1//////
/////${C2}767676767676767676767${C1}///// /////%2767676767676767676767%1/////
/////////////////////////// ///////////////////////////
///////////////////// /////////////////////
///////////// /////////////
+14 -14
View File
@@ -1,15 +1,15 @@
#/27;39;5;12 #/27;39;5;12
${C1}@@@@ ${C1}@@@${C2}* %1@@@@ %1@@@%2*
${C1}@@${C2}~~~~${C1}@@ ${C1}@@${C2}~~~ %1@@%2~~~~%1@@ %1@@%2~~~
${C1}@@${C2}~~ ${C2}~~${C1}@ ${C1}@@${C2}~~ %1@@%2~~ %2~~%1@ %1@@%2~~
${C1}@@${C2}~~ ${C2}~${C1}@ ${C1}@@@${C2}~~ %1@@%2~~ %2~%1@ %1@@@%2~~
${C2}*${C1}@@${C2}~~ ${C1}@@@@ ${C2}~${C1}@@@@${C2}~~~ ${C1}@@@${C2}* %2*%1@@%2~~ %1@@@@ %2~%1@@@@%2~~~ %1@@@%2*
${C2}~~ ${C1}@@${C2}~~~~${C1}@@ ${C2}~~~~ ${C1}@@${C2}~~~ %2~~ %1@@%2~~~~%1@@ %2~~~~ %1@@%2~~~
${C1}@@${C2}~~ ${C2}~~${C1}@ ${C1}@@${C2}~~ %1@@%2~~ %2~~%1@ %1@@%2~~
${C1}@@${C2}~~ ${C2}~${C1}@ ${C1}@@@${C2}~~ %1@@%2~~ %2~%1@ %1@@@%2~~
${C2}*${C1}@@${C2}~~ ${C1}@@@@ ${C2}~${C1}@@@@${C2}~~~ ${C1}@@@${C2}* %2*%1@@%2~~ %1@@@@ %2~%1@@@@%2~~~ %1@@@%2*
${C2}~~ ${C1}@@${C2}~~~~${C1}@@ ${C2}~~~~ ${C1}@@${C2}~~~ %2~~ %1@@%2~~~~%1@@ %2~~~~ %1@@%2~~~
${C1}@@${C2}~~ ${C2}~~${C1}@ ${C1}@@${C2}~~ %1@@%2~~ %2~~%1@ %1@@%2~~
${C1}@@${C2}~~ ${C2}~${C1}@ ${C1}@@@${C2}~~ %1@@%2~~ %2~%1@ %1@@@%2~~
${C2}*${C1}@@${C2}~~ ${C2}~${C1}@@@@${C2}~~~ %2*%1@@%2~~ %2~%1@@@@%2~~~
${C2}~~ ${C2}~~~~ %2~~ %2~~~~
+16 -16
View File
@@ -1,21 +1,21 @@
#/1;7;3;15 #/1;7;3;15
${C1} .-/+oossssoo+\-. %1 .-/+oossssoo+\-.
':+ssssssssssssssssss+:' ':+ssssssssssssssssss+:'
-+ssssssssssssssssssyyssss+- -+ssssssssssssssssssyyssss+-
.ossssssssssssssssss${C2}dMMMNy${C1}sssso. .ossssssssssssssssss%2dMMMNy%1sssso.
/sssssssssss${C2}hdmmNNmmyNMMMMh${C1}ssssss\ /sssssssssss%2hdmmNNmmyNMMMMh%1ssssss\
+sssssssss${C2}hm${C1}yd${C2}MMMMMMMNddddy${C1}ssssssss+ +sssssssss%2hm%1yd%2MMMMMMMNddddy%1ssssssss+
/ssssssss${C2}hNMMM${C1}yh${C2}hyyyyhmNMMMNh${C1}ssssssss\ /ssssssss%2hNMMM%1yh%2hyyyyhmNMMMNh%1ssssssss\
.ssssssss${C2}dMMMNh${C1}ssssssssss${C2}hNMMMd${C1}ssssssss. .ssssssss%2dMMMNh%1ssssssssss%2hNMMMd%1ssssssss.
+ssss${C2}hhhyNMMNy${C1}ssssssssssss${C2}yNMMMy${C1}sssssss+ +ssss%2hhhyNMMNy%1ssssssssssss%2yNMMMy%1sssssss+
oss${C2}yNMMMNyMMh${C1}ssssssssssssss${C2}hmmmh${C1}ssssssso oss%2yNMMMNyMMh%1ssssssssssssss%2hmmmh%1ssssssso
oss${C2}yNMMMNyMMh${C1}sssssssssssssshmmmh${C1}ssssssso oss%2yNMMMNyMMh%1sssssssssssssshmmmh%1ssssssso
+ssss${C2}hhhyNMMNy${C1}ssssssssssss${C2}yNMMMy${C1}sssssss+ +ssss%2hhhyNMMNy%1ssssssssssss%2yNMMMy%1sssssss+
.ssssssss${C2}dMMMNh${C1}ssssssssss${C2}hNMMMd${C1}ssssssss. .ssssssss%2dMMMNh%1ssssssssss%2hNMMMd%1ssssssss.
\ssssssss${C2}hNMMM${C1}yh${C2}hyyyyhdNMMMNh${C1}ssssssss/ \ssssssss%2hNMMM%1yh%2hyyyyhdNMMMNh%1ssssssss/
+sssssssss${C2}dm${C1}yd${C2}MMMMMMMMddddy${C1}ssssssss+ +sssssssss%2dm%1yd%2MMMMMMMMddddy%1ssssssss+
\sssssssssss${C2}hdmNNNNmyNMMMMh${C1}ssssss/ \sssssssssss%2hdmNNNNmyNMMMMh%1ssssss/
.ossssssssssssssssss${C2}dMMMNy${C1}sssso. .ossssssssssssssssss%2dMMMNy%1sssso.
-+sssssssssssssssss${C2}yyy${C1}ssss+- -+sssssssssssssssss%2yyy%1ssss+-
':+ssssssssssssssssss+:' ':+ssssssssssssssssss+:'
.-\+oossssoo+/-. .-\+oossssoo+/-.
+18 -18
View File
@@ -1,19 +1,19 @@
#/2;36;72;15 #/2;36;72;15
${C2}:::::----::::: %2:::::----:::::
${C2}::----------------::= %2::----------------::=
${C2}:-------------------:: %2:-------------------::
${C1} : ${C2}:::. ..:-------: %1 : %2:::. ..:-------:
${C1} =*=. ${C2}.:-----: %1 =*=. %2.:-----:
${C1} =****- ${C2}:-----: %1 =****- %2:-----:
${C1}=*****- ${C2}:::::: :-----: %1=*****- %2:::::: :-----:
${C1}=****+ ${C2}:--------: :----: %1=****+ %2:--------: :----:
${C1}+****- ${C2}.----------. :----: %1+****- %2.----------. :----:
${C1}+****- ${C2}.----------. :----- %1+****- %2.----------. :-----
${C1}=****+ ${C2}:--------: :----: %1=****+ %2:--------: :----:
${C1}=*****- ${C2}:::::: :-----: %1=*****- %2:::::: :-----:
${C1} =*****- ${C2}:----: %1 =*****- %2:----:
${C1} =*****+: ${C2}:-: %1 =*****+: %2:-:
${C1} =*******=:: ::=-. ${C2}. %1 =*******=:: ::=-. %2.
${C1} ==*******************- %1 ==*******************-
${C1} ===****************=- %1 ===****************=-
${C1} ===+=****++=== %1 ===+=****++===
-7
View File
@@ -1,7 +0,0 @@
distro_ascii: auto
fetch_script: auto
ansii_colors: []
force_config_ansii: false
dependency_warning: true
show_fs_type: true
hidden_gpus: []
+16
View File
@@ -0,0 +1,16 @@
distro_ascii: auto
modules:
- name: distribution
- name: hostname
- name: kernel
- name: packages
- name: shell
- name: init
- name: motherboard
- name: cpus
- name: gpus
- name: memory
- name: partitions
- name: local_ip
- name: de_wm
- name: monitors
-48
View File
@@ -1,48 +0,0 @@
source fetch_script_functions.sh
echo -e "${C3}Distribution: ${C4}${DISTRO_LONG_NAME} ($(uname -m))"
echo -e "${C3}Hostname: ${C4}$(cat /etc/hostname)"
echo -e "${C3}Kernel: ${C4}$(uname -s) $(uname -r)"
echo -e "${C3}Packages: ${C4}$(get_packages)"
echo -e "${C3}Shell: ${C4}${USER_SHELL}"
echo -e "${C3}Init: ${C4}${INIT_SYSTEM}"
echo -e "${C3}Libc: ${C4}${LIBC}"
[ -n "$MOTHERBOARD" ] && echo -e "${C3}Motherboard: ${C4}${MOTHERBOARD}"
[ -n "$CPU_MODEL" ] && echo -e "${C3}CPU: ${C4}${CPU_MODEL} (${CPU_THREADS} threads)"
for i in $(seq "${CONNECTED_GPUS}"); do
gpu="GPU$i"
echo -e "${C3}GPU: ${C4}${!gpu}"
done
[ -n "$MEM_TOTAL" ] && [ -n "$MEM_USED" ] && echo -e "${C3}Memory: ${C4}${MEM_USED} MiB / ${MEM_TOTAL} MiB"
for i in $(seq "${MOUNTED_PARTITIONS}"); do
mountpoint="PARTITION${i}_MOUNTPOINT"
label="PARTITION${i}_LABEL"
type="PARTITION${i}_TYPE"
total="PARTITION${i}_TOTAL_SIZE"
used="PARTITION${i}_USED_SIZE"
if [ -z "${!type}" ]; then
if [ -z "${!label}" ]; then
echo -e "${C3}Partition ${!mountpoint}: ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label}: ${C4}${!used}/${!total}"
fi
else
if [ -z "${!label}" ]; then
echo -e "${C3}Partition ${!mountpoint} (${!type}): ${C4}${!used}/${!total}"
else
echo -e "${C3}Partition ${!label} (${!type}): ${C4}${!used}/${!total}"
fi
fi
done
[ -n "$LOCAL_IPV4" ] && echo -e "${C3}Local IPv4 Address: ${C4}${LOCAL_IPV4}"
if [ -n "$DISPLAY_PROTOCOL" ]; then
echo -e "${C3}Display Protocol: ${C4}${DISPLAY_PROTOCOL}"
for i in $(seq "${CONNECTED_MONITORS}"); do
monitor="MONITOR$i"
echo -e "${C3}Screen $i: ${C4}${!monitor}"
done
fi
[ -n "$DE_WM" ] && echo -e "${C3}DE/WM: ${C4}${DE_WM}"
# Exiting with error code 0 in case the condition above returns 1
exit 0
-40
View File
@@ -1,40 +0,0 @@
command_exists() {
if [ -z "$1" ]; then
return 1
fi
if command -v "$1" &> /dev/null; then
return 0
else
return 1
fi
}
get_packages() {
ARRAY=()
if command_exists dpkg; then
ARRAY+=("$(dpkg-query -f '.\n' -W | wc -l) (dpkg)")
fi
if command_exists pacman; then
ARRAY+=("$(pacman -Q | wc -l) (pacman)")
fi
if command_exists rpm; then
ARRAY+=("$(rpm -qa | wc -l) (rpm)")
fi
if command_exists xbps-query; then
ARRAY+=("$(xbps-query -l | wc -l) (xbps)")
fi
if command_exists bpm; then
ARRAY+=("$(bpm list -c) (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
}
-306
View File
@@ -1,306 +0,0 @@
package main
import (
"flag"
"fmt"
"gopkg.in/yaml.v3"
"log"
"os"
"os/exec"
"path"
"regexp"
"strconv"
"strings"
"time"
)
var systemConfigDir = "/etc/"
var configPath = ""
var fetchScriptPath = ""
var TimeTaken = false
var config = StormfetchConfig{
Ascii: "auto",
FetchScript: "auto",
AnsiiColors: make([]int, 0),
ForceConfigAnsii: false,
DependencyWarning: true,
ShowFSType: false,
HiddenGPUS: make([]int, 0),
}
type StormfetchConfig struct {
Ascii string `yaml:"distro_ascii"`
DistroName string `yaml:"distro_name"`
FetchScript string `yaml:"fetch_script"`
AnsiiColors []int `yaml:"ansii_colors"`
ForceConfigAnsii bool `yaml:"force_config_ansii"`
DependencyWarning bool `yaml:"dependency_warning"`
ShowFSType bool `yaml:"show_fs_type"`
HiddenGPUS []int `yaml:"hidden_gpus"`
}
func main() {
readConfig()
readFlags()
checkDependencies()
runStormfetch()
}
func readConfig() {
// Get home directory
userConfigDir, _ := os.UserConfigDir()
// Find valid config directory
if _, err := os.Stat(path.Join(userConfigDir, "stormfetch/config.yaml")); err == nil {
configPath = path.Join(userConfigDir, "stormfetch/config.yaml")
} else if _, err := os.Stat(path.Join(systemConfigDir, "stormfetch/config.yaml")); err == nil {
configPath = path.Join(systemConfigDir, "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(userConfigDir, "stormfetch/fetch_script.sh")); err == nil {
fetchScriptPath = path.Join(userConfigDir, "stormfetch/fetch_script.sh")
} else if _, err := os.Stat(path.Join(systemConfigDir, "stormfetch/fetch_script.sh")); err == nil {
fetchScriptPath = path.Join(systemConfigDir, "stormfetch/fetch_script.sh")
} else {
log.Fatalf("Fetch script file not found: %s", err.Error())
}
}
func readFlags() {
flag.StringVar(&config.Ascii, "ascii", config.Ascii, "Set distro ascii")
flag.StringVar(&config.DistroName, "distro-name", config.DistroName, "Set distro name")
flag.BoolVar(&TimeTaken, "time-taken", false, "Show time taken for fetched information")
flag.Parse()
}
func checkDependencies() {
// Show Dependency warning if enabled
if config.DependencyWarning {
var dependencies []string
var missing []string
for _, depend := range dependencies {
if _, err := os.Stat(path.Join("/usr/bin/", depend)); err != nil {
missing = append(missing, depend)
}
}
if len(missing) != 0 {
fmt.Println("[WARNING] Stormfetch functionality may be limited due to the following dependencies not being installed:")
for _, depend := range missing {
fmt.Println(depend)
}
fmt.Println("You can disable this warning through your stormfetch config")
}
}
}
func SetupFetchEnv(showTimeTaken bool) []string {
var env = make(map[string]string)
setVariable := func(key string, setter func() string) {
start := time.Now().UnixMilli()
env[key] = setter()
end := time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", key, end-start))
}
}
setVariable("DISTRO_LONG_NAME", func() string { return getDistroInfo().LongName })
setVariable("DISTRO_SHORT_NAME", func() string { return getDistroInfo().ShortName })
setVariable("CPU_MODEL", func() string { return getCPUName() })
setVariable("MOTHERBOARD", func() string { return getMotherboardModel() })
setVariable("CPU_THREADS", func() string { return strconv.Itoa(getCPUThreads()) })
start := time.Now().UnixMilli()
memory := GetMemoryInfo()
end := time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MEM_*", end-start))
}
if memory != nil {
env["MEM_TOTAL"] = strconv.Itoa(memory.MemTotal)
env["MEM_USED"] = strconv.Itoa(memory.MemTotal - memory.MemAvailable)
env["MEM_FREE"] = strconv.Itoa(memory.MemAvailable)
}
start = time.Now().UnixMilli()
partitions := getMountedPartitions()
end = time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "PARTITION_*", end-start))
}
if len(partitions) != 0 {
env["MOUNTED_PARTITIONS"] = strconv.Itoa(len(partitions))
for i, part := range partitions {
env["PARTITION"+strconv.Itoa(i+1)+"_DEVICE"] = part.Device
env["PARTITION"+strconv.Itoa(i+1)+"_MOUNTPOINT"] = part.MountPoint
if part.Label != "" {
env["PARTITION"+strconv.Itoa(i+1)+"_LABEL"] = part.Label
}
if part.Type != "" && config.ShowFSType {
env["PARTITION"+strconv.Itoa(i+1)+"_TYPE"] = part.Type
}
env["PARTITION"+strconv.Itoa(i+1)+"_TOTAL_SIZE"] = FormatBytes(part.TotalSize)
env["PARTITION"+strconv.Itoa(i+1)+"_USED_SIZE"] = FormatBytes(part.UsedSize)
env["PARTITION"+strconv.Itoa(i+1)+"_FREE_SIZE"] = FormatBytes(part.FreeSize)
}
}
setVariable("DE_WM", func() string { return GetDEWM() })
setVariable("USER_SHELL", func() string { return GetShell() })
setVariable("DISPLAY_PROTOCOL", func() string { return GetDisplayProtocol() })
setVariable("LIBC", func() string { return GetLibc() })
setVariable("INIT_SYSTEM", func() string { return GetInitSystem() })
setVariable("LOCAL_IPV4", func() string { return GetLocalIP() })
start = time.Now().UnixMilli()
monitors := getMonitorResolution()
end = time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "MONITOR_*", end-start))
}
if len(monitors) != 0 {
env["CONNECTED_MONITORS"] = strconv.Itoa(len(monitors))
for i, monitor := range monitors {
env["MONITOR"+strconv.Itoa(i+1)] = monitor
}
}
start = time.Now().UnixMilli()
gpus := getGPUNames()
end = time.Now().UnixMilli()
if showTimeTaken {
fmt.Println(fmt.Sprintf("Setting '%s' took %d milliseconds", "GPU_*", end-start))
}
if len(gpus) != 0 {
env["CONNECTED_GPUS"] = strconv.Itoa(len(gpus))
for i, gpu := range gpus {
if gpu == "" {
continue
}
env["GPU"+strconv.Itoa(i+1)] = gpu
}
}
var ret = make([]string, len(env))
i := 0
for key, value := range env {
ret[i] = fmt.Sprintf("%s=%s", key, value)
i++
}
return ret
}
func runStormfetch() {
// Fetch ascii art and apply colors
colorMap := make(map[string]string)
colorMap["C0"] = "\033[0m"
setColorMap := func() {
for i := 0; i < 6; i++ {
if i > len(config.AnsiiColors)-1 {
colorMap["C"+strconv.Itoa(i+1)] = "\033[0m"
continue
}
colorMap["C"+strconv.Itoa(i+1)] = fmt.Sprintf("\033[1m\033[38;5;%dm", config.AnsiiColors[i])
}
}
setColorMap()
ascii := getDistroAsciiArt()
if strings.HasPrefix(ascii, "#/") {
firstLine := strings.Split(ascii, "\n")[0]
if !config.ForceConfigAnsii {
ansiiColors := strings.Split(strings.TrimPrefix(firstLine, "#/"), ";")
for i, color := range ansiiColors {
atoi, err := strconv.Atoi(color)
if err != nil {
log.Fatal(err)
}
if i < len(config.AnsiiColors) {
config.AnsiiColors[i] = atoi
} else {
config.AnsiiColors = append(config.AnsiiColors, atoi)
}
}
setColorMap()
}
ascii = os.Expand(ascii, func(s string) string {
return colorMap[s]
})
ascii = strings.TrimPrefix(ascii, firstLine+"\n")
} else {
ascii = os.Expand(ascii, func(s string) string {
return colorMap[s]
})
}
asciiSplit := strings.Split(ascii, "\n")
asciiNoColor := StripAnsii(ascii)
//Execute fetch script
cmd := exec.Command("/bin/bash", fetchScriptPath)
cmd.Dir = path.Dir(fetchScriptPath)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, SetupFetchEnv(TimeTaken)...)
cmd.Env = append(cmd.Env, "C0=\033[0m")
for key, value := range colorMap {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
}
out, err := cmd.Output()
if err != nil {
log.Fatalf("Error: Could not run fetch script: %s", err)
}
// Print Distro Information
maxWidth := 0
for _, line := range strings.Split(asciiNoColor, "\n") {
if len(line) > maxWidth {
maxWidth = len(line)
}
}
final := ""
y := len(asciiSplit)
if len(asciiSplit) < len(strings.Split(string(out), "\n")) {
y = len(strings.Split(string(out), "\n"))
}
for lineIndex := 0; lineIndex < y; lineIndex++ {
line := ""
for i := 0; i < maxWidth+5; i++ {
line = line + " "
}
lastAsciiColor := ""
if lineIndex < len(asciiSplit) {
line = asciiSplit[lineIndex]
lineVisibleLength := len(strings.Split(asciiNoColor, "\n")[lineIndex])
if lineIndex != 0 {
r := regexp.MustCompile("\033[38;5;[0-9]+m")
matches := r.FindAllString(asciiSplit[lineIndex-1], -1)
if len(matches) != 0 {
lastAsciiColor = r.FindAllString(asciiSplit[lineIndex-1], -1)[len(matches)-1]
}
}
for i := lineVisibleLength; i < maxWidth+5; i++ {
line = line + " "
}
asciiSplit[lineIndex] = lastAsciiColor + line
}
str := string(out)
if lineIndex < len(strings.Split(str, "\n")) {
line = line + colorMap["C0"] + strings.Split(str, "\n")[lineIndex]
}
final += lastAsciiColor + line + "\n"
}
final = strings.TrimRight(final, "\n\t ")
fmt.Println(final + "\033[0m")
}
+24
View File
@@ -0,0 +1,24 @@
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
}
+46
View File
@@ -0,0 +1,46 @@
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 -3
View File
@@ -3,19 +3,17 @@ module stormfetch
go 1.22 go 1.22
require ( require (
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
github.com/jackmordaunt/ghw v1.0.4 github.com/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-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // 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
github.com/moby/sys/mountinfo v0.7.1 // indirect
golang.org/x/sys v0.3.0 // indirect golang.org/x/sys v0.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
) )
-5
View File
@@ -1,5 +1,3 @@
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@@ -17,10 +15,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/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=
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
+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
}
+141
View File
@@ -0,0 +1,141 @@
package main
import (
"flag"
"fmt"
"strconv"
"strings"
"time"
)
// Build-time variables
var StormfetchVersion = "dev"
var SystemConfigDir = "/etc/"
// Flag variables
var ShowVersion = false
var ShowModuleTimeTaken = false
var configPath = ""
func main() {
readConfig()
parseFlags()
initializeModuleMap()
run()
}
func parseFlags() {
flag.BoolVar(&ShowVersion, "version", false, "Show Stormfetch version")
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(&config.DistroName, "distro-name", config.DistroName, "Set distro name")
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
// 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()
// 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) == 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 lien
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
}
+402
View File
@@ -0,0 +1,402 @@
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)
}
+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
}
+203
View File
@@ -0,0 +1,203 @@
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",
}
if strings.TrimSpace(config.DistroName) != "" {
info.LongName = strings.TrimSpace(config.DistroName)
info.ShortName = strings.TrimSpace(config.DistroName)
}
// 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
}
releaseMap, err := ReadKeyValueFile(releaseFile)
if err != nil {
return info
}
if id, ok := releaseMap["ID"]; ok {
info.ID = id
}
if longName, ok := releaseMap["PRETTY_NAME"]; ok && info.LongName == "Unknown" {
info.LongName = longName
}
if shortName, ok := releaseMap["NAME"]; ok && info.ShortName == "Unknown" {
info.ShortName = shortName
}
return info
}
func GetDistroAsciiArt() string {
defaultAscii :=
` .--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/'\
\___)=(___/ `
var id string
if config.Ascii == "auto" {
id = GetDistroInfo().ID
} else {
id = config.Ascii
}
userConfDir, err := os.UserConfigDir()
if err != nil {
if _, err := os.Stat(path.Join(SystemConfigDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(SystemConfigDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else {
return defaultAscii
}
}
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else if _, err := os.Stat(path.Join(SystemConfigDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(SystemConfigDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return strings.TrimRight(string(bytes), "\n\t ")
} else {
return defaultAscii
}
}
func 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 {
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)
if err != nil {
return ""
}
// Special cases
// OpenRC check
if _, err := os.Stat("/usr/sbin/openrc"); err == nil {
return "OpenRC " + runCommand("openrc --version | awk '{print $3}'")
}
// Default PID 1 process name checking
switch process.Executable() {
case "systemd":
return "Systemd " + runCommand("systemctl --version | head -n1 | awk '{print $2}'")
case "runit":
return "Runit"
case "dinit":
return "Dinit " + runCommand("dinit --version | head -n1 | awk '{print substr($3, 1, length($3)-1)}'")
case "enit":
return "Enit " + runCommand("enit --version | awk '{print $3}'")
default:
return process.Executable()
}
}
func GetLibc() string {
checkLibcOutput, err := exec.Command("ldd", "/usr/bin/ls").Output()
if err != nil {
return "Unknown"
}
if strings.Contains(string(checkLibcOutput), "ld-musl") {
// Using Musl Libc
output, _ := exec.Command("ldd").CombinedOutput()
return "Musl " + strings.TrimPrefix(strings.Split(strings.TrimSpace(string(output)), "\n")[1], "Version ")
} else {
// Using Glibc
cmd := exec.Command("ldd", "--version")
output, err := cmd.Output()
if err != nil {
return "Glibc"
}
outputSplit := strings.Split(strings.Split(strings.TrimSpace(string(output)), "\n")[0], " ")
ver := outputSplit[len(outputSplit)-1]
return "Glibc " + ver
}
}
+201
View File
@@ -0,0 +1,201 @@
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 ""
}
+58
View File
@@ -0,0 +1,58 @@
package main
import (
"fmt"
"math"
"os"
"regexp"
"strings"
)
func FormatBytes(bytes uint64) string {
var suffixes [6]string
suffixes[0] = "B"
suffixes[1] = "KiB"
suffixes[2] = "MiB"
suffixes[3] = "GiB"
suffixes[4] = "TiB"
suffixes[5] = "PiB"
bf := float64(bytes)
for _, unit := range suffixes {
if math.Abs(bf) < 1024.0 {
return fmt.Sprintf("%3.1f %s", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
func StripAnsii(str string) string {
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
var re = regexp.MustCompile(ansi)
return re.ReplaceAllString(str, "")
}
func ReadKeyValueFile(filepath string) (map[string]string, error) {
ret := make(map[string]string)
if _, err := os.Stat(filepath); err != nil {
return nil, err
}
bytes, err := os.ReadFile(filepath)
if err != nil {
return nil, err
}
str := string(bytes)
lines := strings.Split(str, "\n")
for _, line := range lines {
if len(strings.Split(line, "=")) >= 2 {
key := strings.SplitN(line, "=", 2)[0]
value := strings.SplitN(line, "=", 2)[1]
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
value = value[1 : len(value)-1]
}
ret[key] = value
}
}
return ret, nil
}
-514
View File
@@ -1,514 +0,0 @@
package main
import (
"bufio"
"fmt"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/jackmordaunt/ghw"
"github.com/mitchellh/go-ps"
"github.com/moby/sys/mountinfo"
"log"
"math"
"net"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"syscall"
)
type DistroInfo struct {
ID string
LongName string
ShortName string
}
func getDistroInfo() DistroInfo {
info := DistroInfo{
ID: "unknown",
LongName: "Unknown",
ShortName: "Unknown",
}
if strings.TrimSpace(config.DistroName) != "" {
info.LongName = strings.TrimSpace(config.DistroName)
info.ShortName = strings.TrimSpace(config.DistroName)
}
var releaseMap = make(map[string]string)
if _, err := os.Stat("/etc/os-release"); err == nil {
releaseMap, err = ReadKeyValueFile("/etc/os-release")
if err != nil {
return info
}
}
if id, ok := releaseMap["ID"]; ok {
info.ID = id
}
if longName, ok := releaseMap["PRETTY_NAME"]; ok && info.LongName == "Unknown" {
info.LongName = longName
}
if shortName, ok := releaseMap["NAME"]; ok && info.ShortName == "Unknown" {
info.ShortName = shortName
}
return info
}
func getDistroAsciiArt() string {
defaultAscii :=
` .--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/'\
\___)=(___/ `
var id string
if config.Ascii == "auto" {
id = getDistroInfo().ID
} else {
id = config.Ascii
}
userConfDir, err := os.UserConfigDir()
if err != nil {
if _, err := os.Stat(path.Join(systemConfigDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(systemConfigDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else {
return defaultAscii
}
}
if _, err := os.Stat(path.Join(userConfDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(userConfDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return string(bytes)
} else if _, err := os.Stat(path.Join(systemConfigDir, "stormfetch/ascii/", id)); err == nil {
bytes, err := os.ReadFile(path.Join(systemConfigDir, "stormfetch/ascii/", id))
if err != nil {
return defaultAscii
}
return strings.TrimRight(string(bytes), "\n\t ")
} else {
return defaultAscii
}
}
func getMotherboardModel() string {
bytes, err := os.ReadFile("/sys/devices/virtual/dmi/id/board_name")
if err != nil {
return ""
}
return strings.TrimSpace(string(bytes))
}
func getCPUName() string {
cpu, err := ghw.CPU()
if err != nil {
return ""
}
if len(cpu.Processors) == 0 {
return ""
}
return cpu.Processors[0].Model
}
func getCPUThreads() int {
cpu, err := ghw.CPU()
if err != nil {
return 0
}
return int(cpu.TotalThreads)
}
func getGPUNames() []string {
var ret []string
cmd := exec.Command("/bin/bash", "-c", "lspci -v -m | grep 'VGA' -A6 | grep '^Device:' | sed 's/^Device://' | awk '{$1=$1};1'")
bytes, err := cmd.Output()
if err != nil {
return nil
}
for _, name := range strings.Split(string(bytes), "\n") {
name = strings.TrimSpace(name)
if name == "" {
continue
}
ret = append(ret, name)
}
return ret
}
type Memory struct {
MemTotal int
MemFree int
MemAvailable int
}
func GetMemoryInfo() *Memory {
toInt := func(raw string) int {
if raw == "" {
return 0
}
res, err := strconv.Atoi(raw)
if err != nil {
panic(err)
}
return res
}
parseLine := func(raw string) (key string, value int) {
text := strings.ReplaceAll(raw[:len(raw)-2], " ", "")
keyValue := strings.Split(text, ":")
return keyValue[0], toInt(keyValue[1])
}
if _, err := os.Stat("/proc/meminfo"); err != nil {
return nil
}
file, err := os.Open("/proc/meminfo")
if err != nil {
panic(err)
}
defer file.Close()
bufio.NewScanner(file)
scanner := bufio.NewScanner(file)
res := Memory{}
for scanner.Scan() {
key, value := parseLine(scanner.Text())
switch key {
case "MemTotal":
res.MemTotal = value / 1024
case "MemFree":
res.MemFree = value / 1024
case "MemAvailable":
res.MemAvailable = value / 1024
}
}
return &res
}
func GetShell() string {
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
file, err := os.ReadFile("/etc/passwd")
if err != nil {
return ""
}
str := string(file)
shell := ""
for _, line := range strings.Split(str, "\n") {
if strings.TrimSpace(line) == "" {
continue
}
userInfo := strings.Split(line, ":")
if userInfo[2] == strconv.Itoa(os.Getuid()) {
shell = userInfo[6]
}
}
shellName := filepath.Base(shell)
switch shellName {
case "dash":
return "Dash"
case "bash":
return "Bash " + runCommand("echo $BASH_VERSION")
case "zsh":
return "Zsh " + runCommand("$SHELL --version | awk '{print $2}'")
case "fish":
return "Fish " + runCommand("$SHELL --version | awk '{print $3}'")
case "nu":
return "Nushell " + runCommand("$SHELL --version")
default:
return "Unknown"
}
}
func GetDEWM() string {
processes, err := ps.Processes()
if err != nil {
log.Fatalf("Error: could not get processes: %s", err)
}
var executables []string
for _, process := range processes {
executables = append(executables, process.Executable())
}
processExists := func(process string) bool {
return slices.Contains(executables, process)
}
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
if processExists("plasmashell") {
return "KDE Plasma " + runCommand("plasmashell --version | awk '{print $2}'")
} else if processExists("gnome-session") {
return "Gnome " + runCommand("gnome-shell --version | awk '{print $3}'")
} else if processExists("xfce4-session") {
return "XFCE " + runCommand("xfce4-session --version | head -n1 | awk '{print $2}'")
} else if processExists("cinnamon") {
return "Cinnamon " + runCommand("cinnamon --version | awk '{print $3}'")
} else if processExists("mate-panel") {
return "MATE " + runCommand("mate-about --version | awk '{print $4}'")
} else if processExists("lxsession") {
return "LXDE"
} else if processExists("i3") {
return "i3 " + runCommand("i3 --version | awk '{print $3}'")
} else if processExists("sway") {
if runCommand("sway --version | awk '{print $1}'") == "swayfx" {
return "SwayFX " + runCommand("sway --version | awk '{print $3}'")
} else {
return "Sway " + runCommand("sway --version | awk '{print $3}'")
}
} else if processExists("bspwm") {
return "Bspwm " + runCommand("bspwm -v")
} else if processExists("Hyprland") {
return "Hyprland " + runCommand("hyprctl version | sed -n 3p | awk '{print $2}' | tr -d 'v,'")
} else if processExists("icewm-session") {
return "IceWM " + runCommand("icewm --version | awk '{print $2}'")
}
return ""
}
func GetDisplayProtocol() string {
protocol := os.Getenv("XDG_SESSION_TYPE")
if protocol == "x11" {
return "X11"
} else if protocol == "wayland" {
return "Wayland"
}
return ""
}
func getMonitorResolution() []string {
var monitors []string
if GetDisplayProtocol() != "" {
err := glfw.Init()
if err != nil {
panic(err)
}
for _, monitor := range glfw.GetMonitors() {
mode := monitor.GetVideoMode()
monitors = append(monitors, fmt.Sprintf("%dx%d %dHz", mode.Width, mode.Height, mode.RefreshRate))
}
defer glfw.Terminate()
}
return monitors
}
type partition struct {
Device string
MountPoint string
Label string
Type string
TotalSize uint64
UsedSize uint64
FreeSize uint64
}
func getMountedPartitions() []partition {
mounts, err := mountinfo.GetMounts(func(info *mountinfo.Info) (skip, stop bool) {
return !strings.HasPrefix(info.Source, "/dev/"), false
})
fslabels, err := os.ReadDir("/dev/disk/by-label")
if err != nil && !os.IsNotExist(err) {
return nil
}
partlabels, err := os.ReadDir("/dev/disk/by-partlabel")
if err != nil && !os.IsNotExist(err) {
return nil
}
labels := make(map[string]string)
for _, entry := range partlabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-partlabel/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
for _, entry := range fslabels {
link, err := filepath.EvalSymlinks(filepath.Join("/dev/disk/by-label/", entry.Name()))
if err != nil {
continue
}
labels[link] = entry.Name()
}
var partitions []partition
for _, entry := range mounts {
p := partition{
entry.Source,
entry.Mountpoint,
"",
entry.FSType,
0,
0,
0,
}
skip := false
for _, part := range partitions {
if part.Device == p.Device {
skip = true
}
}
if skip {
continue
}
if value, ok := labels[entry.Source]; ok {
p.Label = value
}
buf := new(syscall.Statfs_t)
err = syscall.Statfs(p.MountPoint, buf)
if err != nil {
continue
}
totalBlocks := buf.Blocks
freeBlocks := buf.Bfree
usedBlocks := totalBlocks - freeBlocks
blockSize := uint64(buf.Bsize)
p.TotalSize = totalBlocks * blockSize
p.FreeSize = freeBlocks * blockSize
p.UsedSize = usedBlocks * blockSize
partitions = append(partitions, p)
}
return partitions
}
func GetInitSystem() string {
runCommand := func(command string) string {
cmd := exec.Command("/bin/bash", "-c", command)
workdir, err := os.Getwd()
if err != nil {
return ""
}
cmd.Dir = workdir
cmd.Env = os.Environ()
out, err := cmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}
link, err := os.Readlink("/sbin/init")
if err != nil {
return "Unknown"
}
if path.Base(link) == "systemd" {
return "Systemd " + runCommand("systemctl --version | head -1 | awk '{print $2}'")
} else if path.Base(link) == "openrc-init" {
return "OpenRC " + runCommand("openrc --version | awk '{print $3}'")
} else if path.Base(link) == "runit-init" {
return "Runit"
} else {
return "Unknown"
}
}
func GetLibc() string {
cmd := exec.Command("/bin/bash", "-c", "find /usr/lib64/ -maxdepth 1 -name 'ld-*' | grep musl")
if err := cmd.Run(); err != nil {
cmd = exec.Command("/bin/bash", "-c", "ldd --version | head -1 | cut -d' ' -f4")
bytes, err := cmd.Output()
if err != nil {
return "Glibc"
}
return "Glibc " + strings.TrimSpace(string(bytes))
}
cmd = exec.Command("/bin/bash", "-c", "ldd 2>&1 | grep 'Version' | cut -d' ' -f2")
bytes, err := cmd.Output()
if err != nil {
return "Musl"
}
return "Musl " + strings.TrimSpace(string(bytes))
}
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()
}
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
}