#!/usr/bin/env python3 import json import os import re import sys from enum import Enum from glob import glob from os import getenv, getuid from subprocess import CalledProcessError, DEVNULL, PIPE, Popen, \ TimeoutExpired, check_output # ----- Distributions fingerprints ---- # class Distributions(Enum): ARCH_LINUX = 'Arch.*' BUNSENLABS = 'BunsenLabs' CRUNCHBANG = 'CrunchBang' DEBIAN = '(Rasp|De)bian' FEDORA = 'Fedora' GENTOO = 'Gentoo' KALI_LINUX = 'Kali' MANJARO_LINUX = 'ManjaroLinux' LINUX = 'Linux' LINUX_MINT = 'LinuxMint' OPENSUSE = 'openSUSE' RED_HAT = 'Red Hat' UBUNTU = 'Ubuntu' WINDOWS = 'Windows' # ------------ Dictionaries ----------- # colorDict = { Distributions.ARCH_LINUX: ['\x1b[0;34m', '\x1b[1;34m'], Distributions.BUNSENLABS: ['\x1b[1;37m', '\x1b[1;33m', '\x1b[0;33m'], Distributions.CRUNCHBANG: ['\x1b[1;37m', '\x1b[1;37m'], Distributions.DEBIAN: ['\x1b[0;31m', '\x1b[1;31m'], Distributions.FEDORA: ['\x1b[1;37m', '\x1b[0;34m'], Distributions.GENTOO: ['\x1b[1;37m', '\x1b[1;35m'], Distributions.KALI_LINUX: ['\x1b[1;37m', '\x1b[1;34m'], Distributions.MANJARO_LINUX: ['\x1b[0;32m', '\x1b[1;32m'], Distributions.LINUX: ['\x1b[1;33m', '\x1b[1;37m'], Distributions.LINUX_MINT: ['\x1b[1;37m', '\x1b[1;32m'], Distributions.OPENSUSE: ['\x1b[1;37m', '\x1b[1;32m'], Distributions.RED_HAT: ['\x1b[1;37m', '\x1b[1;31m', '\x1b[0;31m'], Distributions.UBUNTU: ['\x1b[0;31m', '\x1b[1;31m', '\x1b[0;33m'], Distributions.WINDOWS: ['\x1b[1;31m', '\x1b[1;34m', '\x1b[1;32m', '\x1b[0;33m'], 'sensors': ['\x1b[0;32m', '\x1b[0;33m', '\x1b[0;31m'], 'clear': '\x1b[0m' } deDict = { 'cinnamon': 'Cinnamon', 'gnome-session': 'GNOME', 'gnome-shell': 'GNOME', 'mate-session': 'MATE', 'ksmserver': 'KDE', 'xfce4-session': 'Xfce', 'fur-box-session': 'Fur Box', 'lxsession': 'LXDE', 'lxqt-session': 'LXQt' } wmDict = { 'awesome': 'Awesome', 'beryl': 'Beryl', 'blackbox': 'Blackbox', 'bspwm': 'bspwm', 'cinnamon': 'Cinnamon', 'compiz': 'Compiz', 'dwm': 'DWM', 'enlightenment': 'Enlightenment', 'herbstluftwm': 'herbstluftwm', 'fluxbox': 'Fluxbox', 'fvwm': 'FVWM', 'i3': 'i3', 'icewm': 'IceWM', 'kwin_x11': 'KWin', 'metacity': 'Metacity', 'musca': 'Musca', 'openbox': 'Openbox', 'pekwm': 'PekWM', 'qtile': 'QTile', 'ratpoison': 'ratpoison', 'scrotwm': 'ScrotWM', 'stumpwm': 'StumpWM', 'subtle': 'Subtle', 'monsterwm': 'MonsterWM', 'wingo': 'Wingo', 'wmaker': 'Window Maker', 'wmfs': 'Wmfs', 'wmii': 'wmii', 'xfwm4': 'Xfwm', 'xmonad': 'xmonad' } logosDict = { Distributions.ARCH_LINUX: """ {c[1]} + {r[0]} {c[1]} # {r[1]} {c[1]} ### {r[2]} {c[1]} ##### {r[3]} {c[1]} ###### {r[4]} {c[1]} ; #####; {r[5]} {c[1]} +##.##### {r[6]} {c[1]} +########## {r[7]} {c[1]} ######{c[0]}#####{c[1]}##; {r[8]} {c[1]} ###{c[0]}############{c[1]}+ {r[9]} {c[1]} #{c[0]}###### #######{c[1]} {r[10]} {c[1]} {c[0]}.######; ;###;`\".{c[1]} {r[11]} {c[1]} {c[0]}.#######; ;#####.{c[1]} {r[12]} {c[1]} {c[0]}#########. .########`{c[1]} {r[13]} {c[1]} {c[0]}######' '######{c[1]} {r[14]} {c[1]} {c[0]};#### ####;{c[1]} {r[15]} {c[1]} {c[0]}##' '##{c[1]} {r[16]} {c[1]} {c[0]}#' `#{c[1]} {r[17]}\n""", Distributions.BUNSENLABS: """ {c[0]} .{c[1]}..{c[0]}+hhy+-` \0 {c[0]} `+hd{c[1]}-{c[0]}+dddd{c[2]}hyso{c[0]}+//: \0 {c[0]} `+dddd{c[1]}:-{c[0]}sdh/. {r[0]} {c[0]} -hdddddh{c[1]}-.{c[2]}/:{c[0]} {r[1]} {c[0]} /ddddddddd{c[1]}:```{c[0]} {r[2]} {c[0]} :ddddddddddd/ {r[3]} {c[0]} `hdddddddddddd+ {r[4]} {c[0]} /dddddddddddddd: {r[5]} {c[0]} odddds..sddddddh {r[6]} {c[0]} oddd/ /dddddd: {r[7]} {c[0]} +dd+ +ddddd+ {r[8]} {c[0]} .dd` `ddddd+ {r[9]} {c[0]} oh ydddd: {r[10]} {c[0]} `o sdddh` {r[11]} {c[0]} yddd: {r[12]} {c[0]} `dddo {r[13]} {c[0]} :s :dds {r[14]} {c[0]} yd/ yd+ {r[15]} {c[0]} `sddy :h- {r[16]} {c[0]} `sddys` :` {r[17]} {c[0]} -hdy+`y+yo./+/:- \0 {c[0]} ... .o++oso+/ \n""", Distributions.CRUNCHBANG: """ {c[0]} ___ ___ _ {r[0]} {c[0]} / / / / | | {r[1]} {c[0]} / / / / | | {r[2]} {c[0]} / / / / | | {r[3]} {c[0]} _______/ /______/ /______ | | {r[4]} {c[0]} /______ _______ _______/ | | {r[5]} {c[0]} / / / / | | {r[6]} {c[0]} / / / / | | {r[7]} {c[0]} / / / / | | {r[8]} {c[0]} ______/ /______/ /______ | | {r[9]} {c[0]} /_____ _______ _______/ | | {r[10]} {c[0]} / / / / | | {r[11]} {c[0]} / / / / |_| {r[12]} {c[0]} / / / / _ {r[13]} {c[0]} / / / / | | {r[14]} {c[0]} /__/ /__/ |_| {r[15]} {c[0]} {r[16]} {c[0]} {r[17]}\n""", Distributions.DEBIAN: """ {c[1]} {r[0]} {c[1]} _sudZUZ#Z#XZo=_ {r[1]} {c[1]} _jmZZ2!!~---~!!X##wx {r[2]} {c[1]} .<wdP~~ -!YZL, {r[3]} {c[1]} .mX2' _xaaa__ XZ[. {r[4]} {c[1]} oZ[ _jdXY!~?S#wa ]Xb; {r[5]} {c[1]} _#e' .]X2( ~Xw| )XXc {r[6]} {c[1]} .2Z` ]X[. xY| ]oZ( {r[7]} {c[1]} .2#; )3k; _s!~ jXf` {r[8]} {c[1]} {c[0]}1Z>{c[1]} {c[0]}-]Xb/{c[1]} {c[0]}~{c[1]} {c[0]}__#2({c[1]} {r[9]} {c[1]} {c[0]}-Zo;{c[1]} {c[0]}+!4ZwerfgnZZXY'{c[1]} {r[10]} {c[1]} {c[0]}*#[,{c[1]} {c[0]}~-?!!!!!!-~{c[1]} {r[11]} {c[1]} {c[0]}XUb;.{c[1]} {r[12]} {c[1]} {c[0]})YXL,,{c[1]} {r[13]} {c[1]} {c[0]}+3#bc,{c[1]} {r[14]} {c[1]} {c[0]}-)SSL,,{c[1]} {r[15]} {c[1]} {c[0]}~~~~~{c[1]} {r[16]} {c[1]} {r[17]}\n""", Distributions.FEDORA: """ {c[1]} :/------------:// {r[0]} {c[1]} :------------------:// {r[1]} {c[1]} :-----------{c[0]}/shhdhyo/{c[1]}-:// {r[2]} {c[1]} /-----------{c[0]}omMMMNNNMMMd/{c[1]}-:/ {r[3]} {c[1]} :-----------{c[0]}sMMMdo:/{c[1]} -:/ {r[4]} {c[1]} :-----------{c[0]}:MMMd{c[1]}------- --:/ {r[5]} {c[1]} /-----------{c[0]}:MMMy{c[1]}------- ---/ {r[6]} {c[1]} :------ --{c[0]}/+MMMh/{c[1]}-- ---: {r[7]} {c[1]} :--- {c[0]}oNMMMMMMMMMNho{c[1]} -----: {r[8]} {c[1]} :-- {c[0]}+shhhMMMmhhy++{c[1]} ------: {r[9]} {c[1]} :- -----{c[0]}:MMMy{c[1]}--------------/ {r[10]} {c[1]} :- ------{c[0]}/MMMy{c[1]}-------------: {r[11]} {c[1]} :- ----{c[0]}/hMMM+{c[1]}------------: {r[12]} {c[1]} :--{c[0]}:dMMNdhhdNMMNo{c[1]}-----------: {r[13]} {c[1]} :---{c[0]}:sdNMMMMNds:{c[1]}----------: {r[14]} {c[1]} :------{c[0]}:://:{c[1]}-----------:// {r[15]} {c[1]} :--------------------:// {r[16]} {c[1]} {r[17]}\n""", Distributions.GENTOO: """ {c[1]} -/oyddmdhs+:. {r[0]} {c[1]} -o{c[0]}dNMMMMMMMMNNmhy+{c[1]}-` {r[1]} {c[1]} -y{c[0]}NMMMMMMMMMMMNNNmmdhy{c[1]}+- {r[2]} {c[1]} `o{c[0]}mMMMMMMMMMMMMNmdmmmmddhhy{c[1]}/` {r[3]} {c[1]} om{c[0]}MMMMMMMMMMMN{c[1]}hhyyyo{c[0]}hmdddhhhd{c[1]}o` {r[4]} {c[1]} .y{c[0]}dMMMMMMMMMMd{c[1]}hs++so/s{c[0]}mdddhhhhdm{c[1]}+` {r[5]} {c[1]} oy{c[0]}hdmNMMMMMMMN{c[1]}dyooy{c[0]}dmddddhhhhyhN{c[1]}d. {r[6]} {c[1]} :o{c[0]}yhhdNNMMMMMMMNNNmmdddhhhhhyym{c[1]}Mh {r[7]} {c[1]} .:{c[0]}+sydNMMMMMNNNmmmdddhhhhhhmM{c[1]}my {r[8]} {c[1]} /m{c[0]}MMMMMMNNNmmmdddhhhhhmMNh{c[1]}s: {r[9]} {c[1]} `o{c[0]}NMMMMMMMNNNmmmddddhhdmMNhs{c[1]}+` {r[10]} {c[1]} `s{c[0]}NMMMMMMMMNNNmmmdddddmNMmhs{c[1]}/. {r[11]} {c[1]} /N{c[0]}MMMMMMMMNNNNmmmdddmNMNdso{c[1]}:` {r[12]} {c[1]} +M{c[0]}MMMMMMNNNNNmmmmdmNMNdso{c[1]}/- {r[13]} {c[1]} yM{c[0]}MNNNNNNNmmmmmNNMmhs+/{c[1]}-` {r[14]} {c[1]} /h{c[0]}MMNNNNNNNNMNdhs++/{c[1]}-` {r[15]} {c[1]} `/{c[0]}ohdmmddhys+++/:{c[1]}.` {r[16]} {c[1]} `-//////:--. {r[17]}\n""", Distributions.KALI_LINUX: """ {c[0]} ,..... \0 {c[0]} ----` `..,;:ccc,. \0 {c[0]} ......''';lxO. {r[0]} {c[0]} .....''''..........,:ld; {r[1]} {c[0]} .';;;:::;,,.x, {r[2]} {c[0]} ..'''. 0Xxoc:,. ... {r[3]} {c[0]} .... ,ONkc;,;cokOdc',. {r[4]} {c[0]} . OMo ':{c[1]}d{c[0]}o. {r[5]} {c[0]} dMc :OO; {r[6]} {c[0]} 0M. .:o. {r[7]} {c[0]} ;Wd {r[8]} {c[0]} ;XO, {r[9]} {c[0]} ,d0Odlc;,.. {r[10]} {c[0]} ..',;:cdOOd::,. {r[11]} {c[0]} .:d;.':;. {r[12]} {c[0]} 'd, .' {r[13]} {c[0]} ;l .. {r[14]} {c[0]} .o {r[15]} {c[0]} c {r[16]} {c[0]} .' {r[17]} {c[0]} . \n""", Distributions.MANJARO_LINUX: """ {c[0]} $$$$$$$$$$$$$$$$ $$$$$$$ {r[0]} {c[0]} M77777777777777M M77777M {r[1]} {c[0]} M77777777777777M M77777M {r[2]} {c[0]} M77777MMMMMMMMMM M77777M {r[3]} {c[0]} M77777M M77777M {r[4]} {c[0]} M77777M $$$$$$$ M77777M {r[5]} {c[0]} MMMMMMM M77777M M77777M {r[6]} {c[0]} M77777M M77777M {r[7]} {c[0]} $$$$$$$ M77777M M77777M {r[8]} {c[0]} M77777M M77777M M77777M {r[9]} {c[0]} M77777M M77777M M77777M {r[10]} {c[0]} M77777M M77777M M77777M {r[11]} {c[0]} M77777M M77777M M77777M {r[12]} {c[0]} M77777M M77777M M77777M {r[13]} {c[0]} M77777M M77777M M77777M {r[14]} {c[0]} M77777M M77777M M77777M {r[15]} {c[0]} M77777M M77777M M77777M {r[16]} {c[0]} MMMMMMM MMMMMMM MMMMMMM {r[17]}\n""", Distributions.LINUX: """ {c[1]} {r[0]} {c[1]} a8888b. {r[1]} {c[1]} d888888b. {r[2]} {c[1]} 8P"YP"Y88 {r[3]} {c[1]} 8|o||o|88 {r[4]} {c[1]} 8{c[0]}\\vvvv/{c[1]}88 {r[5]} {c[1]} 8{c[0]} \\vv/ {c[1]}Y8. {r[6]} {c[1]} d/ {c[0]}`'{c[1]} \8b. {r[7]} {c[1]} .dP . Y8b. {r[8]} {c[1]} d8:' " `::88b. {r[9]} {c[1]} d8" `Y88b {r[10]} {c[1]} :8P ' :888 {r[11]} {c[1]} 8a. : _a88P {r[12]} {c[1]} {c[0]}._/"{c[1]}Yaa_ : .{c[0]}| {c[1]}88P{c[0]}|{c[1]} {r[13]} {c[1]} {c[0]}\++++{c[1]}YP" `{c[0]}| {c[1]}8P{c[0]}++\.{c[1]} {r[14]} {c[1]} {c[0]}/+++++\.{c[1]}_____.d{c[0]}|+++++/{c[1]} {r[15]} {c[1]} {c[0]}\++++++){c[1]}888888P{c[0]}\+++/{c[1]} {r[16]} {c[1]} {r[17]}\n""", Distributions.LINUX_MINT: """ {c[1]} {r[0]} {c[1]} MMMMMMMMMMMMMMMMMMMMMMMMMmds+. {r[1]} {c[1]} MMm----::-://////////////oymNMd+` {r[2]} {c[1]} MMd {c[0]}/++{c[1]} -sNMd: {r[3]} {c[1]} MMNso/` {c[0]}dMM{c[1]} {c[0]}`.::-. .-::.`{c[1]} .hMN: {r[4]} {c[1]} ddddMMh {c[0]}dMM{c[1]} {c[0]}:hNMNMNhNMNMNh:`{c[1]} NMm {r[5]} {c[1]} NMm {c[0]}dMM{c[1]} {c[0]}.NMN/-+MMM+-/NMN`{c[1]} dMM {r[6]} {c[1]} NMm {c[0]}dMM{c[1]} {c[0]}-MMm{c[1]} {c[0]}`MMM{c[1]} {c[0]}dMM.{c[1]} dMM {r[7]} {c[1]} NMm {c[0]}dMM{c[1]} {c[0]}-MMm{c[1]} {c[0]}`MMM{c[1]} {c[0]}dMM.{c[1]} dMM {r[8]} {c[1]} NMm {c[0]}dMM{c[1]} {c[0]}.mmd{c[1]} {c[0]}`mmm{c[1]} {c[0]}yMM.{c[1]} dMM {r[9]} {c[1]} NMm {c[0]}dMM`{c[1]} {c[0]}..`{c[1]} {c[0]}`...{c[1]} {c[0]}ydm.{c[1]} dMM {r[10]} {c[1]} hMM- {c[0]}+MMd/-------...-:sdds{c[1]} MMM {r[11]} {c[1]} -NMm- {c[0]}:hNMNNNmdddddddddy/`{c[1]} dMM {r[12]} {c[1]} -dMNs-``{c[0]}-::::-------.``{c[1]} dMM {r[13]} {c[1]} `/dMNmy+/:-------------:/yMMM {r[14]} {c[1]} ./ydNMMMMMMMMMMMMMMMMMMMMM {r[15]} {c[1]} {r[16]} {c[1]} {r[17]}\n""", Distributions.OPENSUSE: """ {c[0]} .;ldkO0000Okdl;. {r[0]} {c[0]} .;d00xl:^''''''^:ok00d;. {r[1]} {c[0]} .d00l' 'o00d. {r[2]} {c[0]} .d0Kd' {c[1]}Okxol:;,.{c[0]} :O0d. {r[3]} {c[0]} .OK{c[1]}KKK0kOKKKKKKKKKKOxo:,{c[0]} lKO. {r[4]} {c[0]} ,0K{c[1]}KKKKKKKKKKKKKKK0P^{c[0]},,,{c[1]}^dx:{c[0]} ;00, {r[5]} {c[0]} .OK{c[1]}KKKKKKKKKKKKKKKk'{c[0]}.oOPPb.{c[1]}'0k.{c[0]} cKO. {r[6]} {c[0]} :KK{c[1]}KKKKKKKKKKKKKKK: {c[0]}kKx..dd{c[1]} lKd{c[0]} 'OK: {r[7]} {c[0]} dKK{c[1]}KKKKKKKKKOx0KKKd {c[0]}^0KKKO'{c[1]} kKKc{c[0]} dKd {r[8]} {c[0]} dKK{c[1]}KKKKKKKKKK;.;oOKx,..{c[0]}^{c[1]}..;kKKK0.{c[0]} dKd {r[9]} {c[0]} :KK{c[1]}KKKKKKKKKK0o;...^cdxxOK0O/^^'{c[0]} .0K: {r[10]} {c[0]} kKK{c[1]}KKKKKKKKKKKKK0x;,,......,;od{c[0]} lKk {r[11]} {c[0]} '0K{c[1]}KKKKKKKKKKKKKKKKKKKK00KKOo^{c[0]} c00' {r[12]} {c[0]} 'kK{c[1]}KKOxddxkOO00000Okxoc;''{c[0]} .dKk' {r[13]} {c[0]} l0Ko. .c00l' {r[14]} {c[0]} 'l0Kk:. .;xK0l' {r[15]} {c[0]} 'lkK0xl:;,,,,;:ldO0kl' {r[16]} {c[0]} '^:ldxkkkkxdl:^' {r[17]}\n""", Distributions.RED_HAT: """ {c[1]} {r[0]} {c[1]} {c[2]}\`.-..........\`{c[1]} {r[1]} {c[1]} {c[2]}\`////////::.\`-/.{c[1]} {r[2]} {c[1]} {c[2]}-: ....-////////.{c[1]} {r[3]} {c[1]} {c[2]}//:-::///////////\`{c[1]} {r[4]} {c[1]} {c[2]}\`--::: \`-://////////////:{c[1]} {r[5]} {c[1]} {c[2]}//////- \`\`.-://///////{c[1]} .\` {r[6]} {c[1]} {c[2]}\`://////:-.\` :///////::///:\`{c[1]} {r[7]} {c[1]} {c[2]}.-/////////:---/////////////:{c[1]} {r[8]} {c[1]} {c[2]}.-://////////////////////.{c[1]} {r[9]} {c[1]} {c[0]}yMN+\`.-${c[2]}::///////////////-\`{c[1]} {r[10]} {c[1]} {c[0]}.-\`:NMMNMs\` \`..-------..\`{c[1]} {r[11]} {c[1]} {c[0]}MN+/mMMMMMhoooyysshsss{c[1]} {r[12]} {c[1]} {c[0]}MMM MMMMMMMMMMMMMMyyddMMM+{c[1]} {r[13]} {c[1]} {c[0]}MMMM MMMMMMMMMMMMMNdyNMMh\` hyhMMM{c[1]} {r[14]} {c[1]} {c[0]}MMMMMMMMMMMMMMMMyoNNNMMM+. MMMMMMMM{c[1]} {r[15]} {c[1]} {c[0]}MMNMMMNNMMMMMNM+ mhsMNyyyyMNMMMMsMM{c[1]} {r[16]} {c[1]} {r[17]}\n""", Distributions.UBUNTU: """ {c[1]} {c[0]}.oyhhs:{c[1]} {r[0]} {c[1]} ..--.., {c[0]}shhhhhh-{c[1]} {r[1]} {c[1]} -+++++++++`:{c[0]}yyhhyo`{c[1]} {r[2]} {c[1]} {c[2]}.--{c[1]} -++++++++/-.-{c[0]}::-`{c[1]} {r[3]} {c[1]} {c[2]}.::::-{c[1]} :-----:/+++/++/. {r[4]} {c[1]} {c[2]}-:::::-.{c[1]} .:++++++: {r[5]} {c[1]} ,,, {c[2]}.:::::-`{c[1]} .++++++- {r[6]} {c[1]} ./+++/-{c[2]}`-::-{c[1]} ./////: {r[7]} {c[1]} +++++++ {c[2]}.::-{c[1]} {r[8]} {c[1]} ./+++/-`{c[2]}-::-{c[1]} {c[0]}:yyyyyo{c[1]} {r[9]} {c[1]} ``` `{c[2]}-::::-`{c[1]} {c[0]}:yhhhhh:{c[1]} {r[10]} {c[1]} {c[2]}-:::::-.{c[1]} {c[0]}`-ohhhhhh+{c[1]} {r[11]} {c[1]} {c[2]}.::::-`{c[1]} {c[0]}-o+///+oyhhyyyhy:{c[1]} {r[12]} {c[1]} {c[2]}`.--{c[1]} {c[0]}/yhhhhhhhy+{c[2]},....{c[1]} {r[13]} {c[1]} {c[0]}/hhhhhhhhh{c[2]}-.-:::;{c[1]} {r[14]} {c[1]} {c[0]}`.:://::- {c[2]}-:::::;{c[1]} {r[15]} {c[1]} {c[2]}`.-:-'{c[1]} {r[16]} {c[1]} {r[17]}\n""", Distributions.WINDOWS: """ {c[1]} {r[0]} {c[1]} {c[0]},.=:^!^!t3Z3z.,{c[1]} {r[1]} {c[1]} {c[0]}:tt:::tt333EE3{c[1]} {r[2]} {c[1]} {c[0]}Et:::ztt33EEE{c[1]} {c[2]}@Ee.,{c[1]} {c[2]}..,{c[1]} {r[3]} {c[1]} {c[0]};tt:::tt333EE7{c[1]} {c[2]};EEEEEEttttt33#{c[1]} {r[4]} {c[1]} {c[0]}:Et:::zt333EEQ.{c[1]} {c[2]}SEEEEEttttt33QL{c[1]} {r[5]} {c[1]} {c[0]}it::::tt333EEF{c[1]} {c[2]}@EEEEEEttttt33F{c[1]} {r[6]} {c[1]} {c[0]};3=*^```'*4EEV{c[1]} {c[2]}:EEEEEEttttt33@.{c[1]} {r[7]} {c[1]} ,.=::::it=.,{c[0]} `{c[1]} {c[2]}@EEEEEEtttz33QF{c[1]} {r[8]} {c[1]} ;::::::::zt33){c[1]} {c[2]}'4EEEtttji3P*{c[1]} {r[9]} {c[1]} :t::::::::tt33.{c[3]}:Z3z..{c[2]} `` {c[3]},..g.{c[1]} {r[10]} {c[1]} i::::::::zt33F{c[1]} {c[3]}AEEEtttt::::ztF{c[1]} {r[11]} {c[1]} ;:::::::::t33V{c[1]} {c[3]};EEEttttt::::t3{c[1]} {r[12]} {c[1]} E::::::::zt33L{c[1]} {c[3]}@EEEtttt::::z3F{c[1]} {r[13]} {c[1]} {{3=*^```'*4E3){c[1]} {c[3]};EEEtttt:::::tZ`{c[1]} {r[14]} {c[1]} `{c[1]} {c[3]}:EEEEtttt::::z7{c[1]} {r[15]} {c[1]} {c[3]}'VEzjt:;;z>*`{c[1]} {r[16]} {c[1]} {r[17]}\n""" } # ----------- Configuration ----------- # class Configuration(object): def __init__(self): """ Represents the default configuration which will be used by Archey. Values present in the dictionary below are needed. New optional values may be added with `_updateRecursive()` method. """ self.config = { 'colors_palette': { 'use_unicode': False }, 'default_strings': { 'no_address': 'No Address', 'not_detected': 'Not detected', 'virtual_environment': 'Virtual Environment', 'bare_metal_environment': 'Bare-metal Environment' }, 'ip_settings': { 'lan_ip_max_count': 2, 'wan_ip_v6_support': True }, 'temperature': { 'char_before_unit': ' ', 'use_fahrenheit': False }, 'timeout': { 'ipv4_detection': 1, 'ipv6_detection': 1 } } # Let's "save" `STDERR` file descriptor for `suppress_warnings` option self._stderr = sys.stderr # Now, let's load each optional configuration file in a "regular" order self.loadConfiguration('/etc/archey4/') self.loadConfiguration(os.path.expanduser('~') + '/.config/archey4/') self.loadConfiguration(os.path.dirname(os.path.realpath(__file__))) def get(self, key, default=None): """ A binding method to imitate the `dict.get()` behavior. """ return self.config.get(key, default) def loadConfiguration(self, path): # If a previous configuration file has denied overriding... if not self.config.get('allow_overriding', True): # ... don't load this one. return if not path.endswith('/'): path += '/' path += 'config.json' try: with open(path) as file: self._updateRecursive(self.config, json.load(file)) # If the user does not want any warning to appear : 2> /dev/null if self.config.get('suppress_warnings', False): # One more if statement to avoid multiple `open` calls. if sys.stderr == self._stderr: sys.stderr = open(os.devnull, 'w') else: # One more if statement to avoid useless assignments and... # ... for closing previously opened new file descriptor. if sys.stderr != self._stderr: sys.stderr.close() sys.stderr = self._stderr except FileNotFoundError: pass # For backward compatibility with Python versions prior to 3.5.0 # we use `ValueError` instead of `json.JSONDecodeError`. except ValueError as e: print('Warning: {0} ({1})'.format(e, path), file=sys.stderr) def _updateRecursive(self, oldDict, newDict): """ A method for recursively merging dictionaries as... ... `dict.update()` is not able to do this. Original snippet taken from here : https://gist.github.com/angstwad/bf22d1822c38a92ec0a9 """ for key, value in newDict.items(): if key in oldDict and isinstance(oldDict[key], dict) \ and isinstance(value, dict): self._updateRecursive(oldDict[key], value) else: oldDict[key] = value # ---------- Global variables --------- # # We create a global instance of our `Configuration` Class config = Configuration() # We'll list the running processes only one time try: PROCESSES = check_output( ['ps', '-u' + str(getuid()) if getuid() != 0 else '-ax', '-o', 'comm', '--no-headers'], universal_newlines=True ).splitlines() except FileNotFoundError: print('Please, install first `procps` on your distribution.', file=sys.stderr) exit() # ----------- Output handler ---------- # class Output(object): def __init__(self): try: lsbOutput = check_output( ['lsb_release', '-i', '-s'], universal_newlines=True ).rstrip() except FileNotFoundError: print('Please, install first `lsb-release` on your distribution.', file=sys.stderr) exit() if re.search( 'Microsoft', check_output(['uname', '-r'], universal_newlines=True)): self.distribution = Distributions.WINDOWS else: for distribution in Distributions: if re.fullmatch(distribution.value, lsbOutput): self.distribution = distribution break else: self.distribution = Distributions.LINUX # Each class output will be added in the list below afterwards self.results = [] def append(self, key, value): self.results.append( '{0}{1}:{2} {3}'.format( colorDict[self.distribution][1], key, colorDict['clear'], value ) ) def output(self): # Let's center the entries according to the logo (handles odd numbers) self.results[0:0] = [''] * ((18 - len(self.results)) // 2) self.results.extend([''] * (18 - len(self.results))) try: print( logosDict[self.distribution].format( c=colorDict[self.distribution], r=self.results ) + colorDict['clear'] ) except UnicodeError: print( 'Your locale or TTY seems not supporting UTF8 encoding.\n' 'Please disable Unicode within your configuration file.', file=sys.stderr ) # -------------- Entries -------------- # class User: def __init__(self): self.value = getenv( 'USER', config.get('default_strings')['not_detected'] ) class Hostname: def __init__(self): self.value = check_output( ['uname', '-n'], universal_newlines=True ).rstrip() class Model: def __init__(self): try: with open('/sys/devices/virtual/dmi/id/product_name') as file: model = file.read().rstrip() except FileNotFoundError: # The file above does not exist, is this device a Raspberry Pi ? # Let's retrieve the Hardware and Revision IDs with `/proc/cpuinfo` with open('/proc/cpuinfo') as file: output = file.read() hardware = re.search('(?<=Hardware\t: ).*', output) revision = re.search('(?<=Revision\t: ).*', output) # If the output contains 'Hardware' and 'Revision'... if hardware and revision: # ... let's set a pretty info string with these data model = 'Raspberry Pi {0} (Rev. {1})'.format( hardware.group(0), revision.group(0) ) else: # A tricky way to retrieve some details about hypervisor... # ... within virtual contexts. # `archey` needs to be run as root although. try: virtWhat = ', '.join( check_output( ['virt-what'], stderr=DEVNULL, universal_newlines=True ).splitlines() ) if virtWhat: try: # Sometimes we may gather info added by... # ... hosting service provider this way model = check_output( ['dmidecode', '-s', 'system-product-name'], stderr=DEVNULL, universal_newlines=True ).rstrip() except (FileNotFoundError, CalledProcessError): model = config.get( 'default_strings' )['virtual_environment'] model += ' ({0})'.format(virtWhat) else: model = config.get( 'default_strings' )['bare_metal_environment'] except (FileNotFoundError, CalledProcessError): model = config.get('default_strings')['not_detected'] self.value = model class Distro: def __init__(self): self.value = '{0} {1}'.format( check_output( ['lsb_release', '-d', '-s'], universal_newlines=True ).rstrip(), check_output( ['uname', '-m'], universal_newlines=True ).rstrip() ) class Kernel: def __init__(self): self.value = check_output( ['uname', '-r'], universal_newlines=True ).rstrip() class Uptime: def __init__(self): with open('/proc/uptime') as file: fuptime = int(file.read().split('.')[0]) day, fuptime = divmod(fuptime, 86400) hour, fuptime = divmod(fuptime, 3600) minute = fuptime // 60 uptime = '' uptime += ((str(day) + ' day' + ('s' if day > 1 else '') + ('' if minute == 0 and hour == 0 else (' and ' if (minute != 0 and hour == 0) or (minute == 0 and hour != 0) else ', '))) if day >= 1 else '') uptime += ((str(hour) + ' hour' + ('s' if hour > 1 else '') + (' and ' if minute != 0 else '')) if hour >= 1 else '') uptime += ((str(minute) + ' minute' + ('s' if minute > 1 else '')) if minute >= 1 else ('< 1 minute' if day == 0 and hour == 0 else '')) self.value = uptime class WindowManager: def __init__(self): try: wm = re.search( '(?<=Name: ).*', check_output( ['wmctrl', '-m'], stderr=DEVNULL, universal_newlines=True ) ).group(0) except (FileNotFoundError, CalledProcessError): for key in wmDict.keys(): if key in PROCESSES: wm = wmDict[key] break else: wm = config.get('default_strings')['not_detected'] self.value = wm class DesktopEnvironment: def __init__(self): for key in deDict.keys(): if key in PROCESSES: de = deDict[key] break else: # Let's rely on an environment var if the loop above didn't `break` de = getenv( 'XDG_CURRENT_DESKTOP', config.get('default_strings')['not_detected'] ) self.value = de class Shell: def __init__(self): self.value = getenv( 'SHELL', config.get('default_strings')['not_detected'] ) class Terminal: def __init__(self): terminal = getenv( 'TERM', config.get('default_strings')['not_detected'] ) # On systems with non-Unicode locales, we imitate '\u2588' character # ... with '#' to display the terminal colors palette. # This is the default option for backward compatibility. colors = ' '.join([ '\x1b[0;3{0}m{1}\x1b[1;3{0}m{1}{2}'.format( i, '\u2588' if config.get('colors_palette')['use_unicode'] else '#', colorDict['clear'] ) for i in range(7, 0, -1) ]) self.value = '{0} {1}'.format(terminal, colors) class Temperature: def __init__(self): temps = [] try: # Let's try to retrieve a value from the Broadcom chip on Raspberry temp = float( re.search( '\d+\.\d+', check_output( ['/opt/vc/bin/vcgencmd', 'measure_temp'], stderr=DEVNULL, universal_newlines=True ) ).group(0) ) temps.append( self._convertToFahrenheit(temp) if config.get('temperature')['use_fahrenheit'] else temp ) except (FileNotFoundError, CalledProcessError): pass # Now we just check for values within files present in the path below for thermalFile in glob('/sys/class/thermal/thermal_zone*/temp'): with open(thermalFile) as file: temp = float(file.read().strip()) / 1000 if temp != 0.0: temps.append( self._convertToFahrenheit(temp) if config.get('temperature')['use_fahrenheit'] else temp ) if temps: self.value = '{0}{2}{3} (Max. {1}{2}{3})'.format( str(round(sum(temps) / len(temps), 1)), str(round(max(temps), 1)), config.get('temperature')['char_before_unit'], 'F' if config.get('temperature')['use_fahrenheit'] else 'C' ) else: self.value = config.get('default_strings')['not_detected'] """ Simple Celsius to Fahrenheit conversion method """ def _convertToFahrenheit(self, temp): return temp * (9 / 5) + 32 class Packages: def __init__(self): for packagesTool in [['dnf', 'list', 'installed'], ['dpkg', '--get-selections'], ['emerge', '-ep', 'world'], ['pacman', '-Q'], ['rpm', '-qa'], ['yum', 'list', 'installed'], ['zypper', 'search', '-i']]: try: results = check_output( packagesTool, stderr=DEVNULL, env={'LANG': 'C'}, universal_newlines=True ) packages = results.count('\n') if 'dnf' in packagesTool: # Deduct extra heading line packages -= 1 elif 'dpkg' in packagesTool: # Packages removed but not purged packages -= results.count('deinstall') elif 'emerge' in packagesTool: # Deduct extra heading lines packages -= 5 elif 'yum' in packagesTool: # Deduct extra heading lines packages -= 2 elif 'zypper' in packagesTool: # Deduct extra heading lines packages -= 5 break except (FileNotFoundError, CalledProcessError): pass else: packages = config.get('default_strings')['not_detected'] self.value = packages class CPU: def __init__(self): model_name_regex = re.compile( '^model name\s*:\s*(.*)$', flags=re.IGNORECASE | re.MULTILINE ) with open('/proc/cpuinfo') as file: cpuinfo = re.search(model_name_regex, file.read()) # This test case has been built for some ARM architectures (see #29). # Sometimes, `model name` info is not present within `/proc/cpuinfo`. # We use the output of `lscpu` program (util-linux-ng) to retrieve it. if not cpuinfo: cpuinfo = re.search( model_name_regex, check_output( ['lscpu'], env={'LANG': 'C'}, universal_newlines=True ) ) # Sometimes CPU model name contains extra ugly white-spaces. self.value = re.sub('\s+', ' ', cpuinfo.group(1)) class GPU: def __init__(self): """ Some explanations are needed here : * We call `lspci` program to retrieve hardware devices * We keep only the entries with "3D", "VGA" or "Display" * We sort them in the same order as above (for relevancy) """ try: lspci_output = sorted([ (i.split(': ')[0].split(' ')[1], i.split(': ')[1]) for i in check_output( ['lspci'], universal_newlines=True ).splitlines() if '3D' in i or 'VGA' in i or 'Display' in i ], key=lambda x: len(x[1]) ) if lspci_output: gpuinfo = lspci_output[0][1] # If the line got too long, let's truncate it and add some dots if len(gpuinfo) > 48: # This call truncates `gpuinfo` with words preservation gpuinfo = re.search( '.{1,45}(?:\W|$)', gpuinfo ).group(0).strip() + '...' else: gpuinfo = config.get('default_strings')['not_detected'] except (FileNotFoundError, CalledProcessError): gpuinfo = config.get('default_strings')['not_detected'] self.value = gpuinfo class RAM: def __init__(self): try: ram = ''.join( filter( re.compile('Mem').search, check_output( ['free', '-m'], env={'LANG': 'C'}, universal_newlines=True ).splitlines() ) ).split() used = float(ram[2]) total = float(ram[1]) except (IndexError, FileNotFoundError): # An in-digest one-liner to retrieve memory info into a dictionary with open('/proc/meminfo') as file: ram = { i.split(':')[0]: float(i.split(':')[1].strip(' kB')) / 1024 for i in filter(None, file.read().splitlines()) } total = ram['MemTotal'] # Here, let's imitate the `free` command behavior # (https://gitlab.com/procps-ng/procps/blob/master/proc/sysinfo.c#L787) used = total - (ram['MemFree'] + ram['Cached'] + ram['Buffers']) if used < 0: used += ram['Cached'] + ram['Buffers'] self.value = '{0}{1} MB{2} / {3} MB'.format( colorDict['sensors'][int(((used / total) * 100) // 33.34)], int(used), colorDict['clear'], int(total) ) class Disk: def __init__(self): total = re.sub( ',', '.', check_output([ 'df', '-Tlh', '-B', 'GB', '--total', '-t', 'ext4', '-t', 'ext3', '-t', 'ext2', '-t', 'reiserfs', '-t', 'jfs', '-t', 'zfs', '-t', 'ntfs', '-t', 'fat32', '-t', 'btrfs', '-t', 'fuseblk', '-t', 'xfs', '-t', 'simfs', '-t', 'tmpfs' ], universal_newlines=True ).splitlines()[-1] ).split() self.value = '{0}{1}{2} / {3}'.format( colorDict['sensors'][int(float(total[5][:-1]) // 33.34)], re.sub('GB', ' GB', total[3]), colorDict['clear'], re.sub('GB', ' GB', total[2]) ) class LAN_IP: def __init__(self): try: addresses = check_output( ['hostname', '-I'], stderr=DEVNULL, universal_newlines=True ).split() except (CalledProcessError, FileNotFoundError): # Slow manual workaround for old `inetutils` versions, with `ip` addresses = check_output( ['cut', '-d', ' ', '-f', '4'], universal_newlines=True, stdin=Popen(['cut', '-d', '/', '-f', '1'], stdout=PIPE, stdin=Popen(['tr', '-s', ' '], stdout=PIPE, stdin=Popen(['grep', '-E', 'scope (global|site)' ], stdout=PIPE, stdin=Popen(['ip', '-o', 'addr', 'show', 'up'], stdout=PIPE ).stdout ).stdout ).stdout ).stdout ).splitlines() # Use list slice to save only `lan_ip_max_count` from `addresses`. # If set to `False`, don't modify the list. # This option is still optional. self.value = ', '.join( addresses[:( config.get('ip_settings')['lan_ip_max_count'] if config.get('ip_settings')['lan_ip_max_count'] is not False else len(addresses) )] ) or config.get('default_strings')['no_address'] class WAN_IP: def __init__(self): # IPv6 address retrieval (unless the user doesn't want it). if config.get('ip_settings')['wan_ip_v6_support']: try: ipv6_value = check_output([ 'dig', '+short', '-6', 'aaaa', 'myip.opendns.com', '@resolver1.ipv6-sandbox.opendns.com' ], timeout=config.get('timeout')['ipv6_detection'], stderr=DEVNULL, universal_newlines=True ).rstrip() except (FileNotFoundError, TimeoutExpired, CalledProcessError): try: ipv6_value = check_output([ 'wget', '-qO-', 'https://v6.ident.me/' ], timeout=config.get('timeout')['ipv6_detection'], universal_newlines=True ) except (CalledProcessError, TimeoutExpired): # It looks like this user doesn't have any IPv6 address... # ... or is not connected to Internet. ipv6_value = None except FileNotFoundError: ipv6_value = None print('Warning: `wget` has not been found on your system.', file=sys.stderr) else: ipv6_value = None # IPv4 addresses retrieval (anyway). try: ipv4_value = check_output([ 'dig', '+short', 'myip.opendns.com', '@resolver1.opendns.com' ], timeout=config.get('timeout')['ipv4_detection'], stderr=DEVNULL, universal_newlines=True ).rstrip() except (FileNotFoundError, TimeoutExpired, CalledProcessError): try: ipv4_value = check_output([ 'wget', '-qO-', 'https://v4.ident.me/' ], timeout=config.get('timeout')['ipv4_detection'], universal_newlines=True ) except (CalledProcessError, TimeoutExpired): # This user looks not connected to Internet... ipv4_value = None except FileNotFoundError: ipv4_value = None # If statement so as to not print the same message twice. if not config.get('ip_settings')['wan_ip_v6_support']: print('Warning: `wget` has not been found on your system.', file=sys.stderr) self.value = ', '.join( filter(None, (ipv4_value, ipv6_value)) ) or config.get('default_strings')['no_address'] # ----------- Classes Index ----------- # class Classes(Enum): User = User Hostname = Hostname Model = Model Distro = Distro Kernel = Kernel Uptime = Uptime WindowManager = WindowManager DesktopEnvironment = DesktopEnvironment Shell = Shell Terminal = Terminal Packages = Packages Temperature = Temperature CPU = CPU GPU = GPU RAM = RAM Disk = Disk LAN_IP = LAN_IP WAN_IP = WAN_IP # ---------------- Main --------------- # def main(): output = Output() for key in Classes: if config.get('entries', {}).get(key.name, True): output.append(key.name, key.value().value) output.output() if __name__ == '__main__': main()