dot/scripts/vfetch
2023-07-11 15:28:11 +01:00

288 lines
7.7 KiB
Python
Executable file

#!/usr/bin/env python3
from xdg.BaseDirectory import xdg_config_home
from enum import Enum
import subprocess
import platform
import distro
import json
import re
import os
import wmctrl
colors = [
# Regular colors.
"\u001b[30m",
"\u001b[31m",
"\u001b[32m",
"\u001b[33m",
"\u001b[34m",
"\u001b[35m",
"\u001b[36m",
"\u001b[37m",
# Bright colors.
"\u001b[30;1m",
"\u001b[31;1m",
"\u001b[32;1m",
"\u001b[33;1m",
"\u001b[34;1m",
"\u001b[35;1m",
"\u001b[36;1m",
"\u001b[37;1m",
# Reset.
"\u001b[0m"
]
decorations = [
"\u001b[1m", # Bold.
"\u001b[4m", # Underline.
"\u001b[7m" # Reversed.
]
# Creates a copy of the specified string with color and decorations added.
def colored(string, colorIndex, decorationIndices=[]):
newString = colors[colorIndex]
for decorationIndex in decorationIndices:
newString += decorations[decorationIndex]
newString += string + colors[len(colors)-1]
return newString
# Enum for the different data types.
class Type(str, Enum):
os = 'os'
kernel = 'kernel'
wm = 'wm'
packages = 'packages'
uptime = 'uptime'
# Enum for the different align modes.
class AlignMode(str, Enum):
spaces = 'spaces'
center = 'center'
# Loads the settings from the configuration file.
# First checks for a configuration file in ~/.config/vfetch/vfetch.conf,
# else it defaults to the configuration file in the same folder as the script.
def loadSettings():
try:
file = open(xdg_config_home + '/vfetch/vfetch.conf', 'r')
except FileNotFoundError:
file = open(os.path.dirname(os.path.realpath(__file__)) + '/vfetch.conf', 'r')
content = file.read()
settings = json.loads(content)
file.close()
return settings
# Prints string without ending with a new line.
def printn(string):
print(string, end="")
# Prints string at a specified position.
def printAt(string, *position):
if len(position) == 1:
x = position[0][0]
y = position[0][1]
else:
x = position[0]
y = position[1]
printn("\x1b7\x1b[%d;%df%s\x1b8" % (y+1, x+1, string))
# Prints the data lines.
def printLines(lines, colorIndex, offsetX, offsetY, alignMode, alignSpace):
longestName = 0
dataPosition = 0
if alignMode is AlignMode.spaces:
for line in lines:
position = len(line[0]) + alignSpace
if position > dataPosition:
dataPosition = position
else:
# Finds the length of the longest name.
longestName = len(max(lines, key = lambda data: len(data[0]))[0])
y = 0
x = offsetX
# Prints the lines and formats them accordingly.
for line in lines:
if alignMode is AlignMode.spaces:
printAt(line[1], x + dataPosition, y+offsetY)
elif alignMode is AlignMode.center:
line[0] = ' ' * (longestName - len(line[0])) + line[0]
printAt(colored(line[0], colorIndex, [0]), x, y+offsetY)
if alignMode is AlignMode.center:
printAt(' ~ ' + line[1], x+len(line[0]), y+offsetY)
y += 1
# Sets the cursor position.
def setCursorPosition(*position, newLine=False):
if len(position) == 1:
x = position[0][0]
y = position[0][1]
else:
x = position[0]
y = position[1]
string = '\033[%d;%dH' % (y, x)
if newLine:
print(string)
else:
printn(string)
# Runs the specified terminal command.
def termRun(command, arguments):
output = subprocess.run([command, arguments], text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return output.stdout
# Prints ascii image.
def printAscii(position, asciiImage):
setCursorPosition(position)
lines = asciiImage.split('\n')
for line in lines:
print(line)
# Gets the operating system.
def getOS(architecture=False, removeLinux=False):
os = distro.linux_distribution()[0]
if removeLinux:
os = re.sub('linux', '', os, flags=re.IGNORECASE)
os = os.rstrip()
if architecture:
os += ' ' + platform.machine()
return os
# Gets the kernel.
def getKernel(fullName=True):
kernel = platform.release()
if not fullName:
kernel = kernel.split('-')[0]
return kernel
# Gets the window manager.
def getWM():
try:
return wmctrl.os.environ.get('DESKTOP_SESSION')
except:
pass
try:
return wmctrl.os.environ.get('XDG_SESSION_DESKTOP')
except:
return None
# Gets the number of packages.
def getPackages(displayPackageManager=False):
try:
packages = termRun('pacman', '-Qq')
string = str(len(packages.split('\n')))
if displayPackageManager:
string += ' (pacman)'
return string
except:
return None
# Gets the machine uptime.
def getUptime():
with open('/proc/uptime', 'r') as f:
uptime_seconds = float(f.readline().split()[0])
hours = uptime_seconds / 3600
minutes = (hours - int(hours)) * 60
hours = int(hours)
minutes = int(minutes)
string = ''
if hours != 0:
string += str(hours) + 'h '
if minutes != 0 or hours == 0:
string += str(minutes) + 'm'
return string
# Gets the data for the specified data type.
def getData(type, settings):
data = {
Type.os: getOS(settings['displayArchitecture'], settings['removeLinux']),
Type.kernel: getKernel(settings['kernelFullName']),
Type.wm: getWM(),
Type.packages: getPackages(settings['displayPackageManager']),
Type.uptime: getUptime()
}.get(type, None)
if data is None:
return None
name = {
Type.os: [ 'OS', '' ],
Type.kernel: [ 'Kernel', '' ],
Type.wm: [ 'WM', '' ],
Type.packages: [ 'Packages', '' ],
Type.uptime: [ 'Uptime', '' ]
}.get(type, None)[int(settings['iconMode'])]
if settings['lowercase']:
name = name.lower()
data = data.lower()
return [name, data]
# Gets the size of the specified ascii image.
def asciiSize(asciiImage):
x = 0
split = asciiImage.split('\n')
for line in split:
if len(line) > x:
x = len(line)
return [x, len(split)]
# Trims the specified ascii image of empty lines and trailing whitespaces.
def trimAscii(asciiImage):
lines = asciiImage.split('\n')
string = ''
for line in lines:
trimmedString = line.rstrip()
if len(trimmedString) != 0:
string += trimmedString + '\n'
string = string[:-1] # Removes last newline.
return string
# Loads the ascii image at the specified path.
def loadAsciiImage(path):
file = open(path, 'r')
asciiImage = trimAscii(file.read())
file.close()
return asciiImage
settings = loadSettings()
displayAscii = settings['displayAscii']
offset = settings['offset']
# Loads the data lines. If the data is invalid (None) it does not get added.
lines = []
for dataType in settings['data']:
data = getData(dataType, settings)
if data is not None:
lines.append(data)
# Loads the ascii image if the option is set for it.
if displayAscii:
asciiImage = loadAsciiImage(settings['asciiImage'])
size = asciiSize(asciiImage)
offset[0] += size[0]
finalPosition = [0, size[1]]
else:
finalPosition = [0, len(lines)+offset[1]]
# Makes the prompt after the script finishes have a blank line before it.
finalPosition[1] += 1
os.system('clear')
if displayAscii:
printAscii([0,0], asciiImage)
alignMode = AlignMode(settings['alignMode'])
printLines(lines, settings['colorIndex'], offset[0], offset[1], alignMode, settings['alignSpace'])
# Sets the final cursor position for the prompt to end up at.
setCursorPosition(finalPosition, newLine=True)