Heute werden wir „fertig“. Wenn überhaupt, steht am Ende dieses Artikels eine Alpha-Version. Dies ist der 15. Teil der Reihe, und wer es bis hierhin geschafft hat, kann stolz auf sich sein, denn leicht war es sicher nicht.
Wer diesen Artikel durchgearbeitet hat, sollte so etwas vor sich haben:
Wie geht es jetzt weiter?
Zunächst einmal werde ich mir die Kommentare unter allen Artikeln zu Gemüte führen. Ihr wart fleißig und habt viele Verbesserungsvorschläge und Anmerkungen eingebracht, die ebenfalls umgesetzt werden sollen.
Pakete zählen
Soweit ich weiß, ist es kein Verbrechen bei sich selbst zu klauen. Im Mai habe ich einen kleinen Artikel darüber geschrieben, wie man mithilfe von Python eine eigene Terminal-Version von Neofetch schreibt.
Wir bedienen uns heute daran, denn in dem dort vorgestellten Code befindet sich etwas, das wir benötigen.
Gleich vorab sei gesagt, dass ich auf Debian programmiere. Wer es bis Teil 15 geschafft hat, dem traue ich zu, ein paar Modifikationen selbst vornehmen zu können. In klarer Sprache: Ich habe keine Ahnung, wie man unter anderen Paket-Managern eine Listung vornimmt, wie diese Leistungen strukturiert sind und wie man sie ausließt. Das ist eure kleine "Knobel-Aufgabe".
import subprocess
# Get installed Debian Packages
deb_count = subprocess.run(["dpkg", "--list"], stdout=subprocess.PIPE, text=True)
deb_counted = len(deb_count.stdout.splitlines()) - 5
print(f"Packages: {deb_counted} (Debian)")
>>> %Run -c $EDITOR_CONTENT
Packages: 2772 (Debian)
Logik:
- Der
Subprocess
liest überdpkg --list
die Pakete aus, das nimmt nicht viel Zeit in Anspruch. Der Output sieht in gekürzter Form so aus:
Gewünscht=Unbekannt/Installieren/R=Entfernen/P=Vollständig Löschen/Halten
| Status=Nicht/Installiert/Config/U=Entpackt/halb konFiguriert/
Halb installiert/Trigger erWartet/Trigger anhängig
|/ Fehler?=(kein)/R=Neuinstallation notwendig (Status, Fehler: GROSS=schlecht)
||/ Name Version >
+++-==============================================-============================>
ii 7zip 23.01+dfsg-11 >
ii aardvark-dns 1.4.0-5 >
ii accountsservice 23.13.9-2ubuntu6 >
ii acl 2.3.2-1build1.1 >
ii adduser 3.137ubuntu1 >
ii adwaita-icon-theme 46.0-1
[...]
- Wir nutzen
len, um
ganz plump, die ausgegebenen Reihen zu zählen. stdout
macht das ganze für uns besser nutzbar- Mit
splitlines()
erhalten wir eine ordentliche Liste mit Zeilen - 5
(minus fünf) rationalisiert die in der oberen Ausgabe zu sehende Header-Zeilen weg, welche keine Information über ein Paket beinhalten.
Einbau ein unser GUI
Im Grunde ändern wir nicht viel. Das ganze landet in einer Funktion, damit wir den Code weiter benutzen können. Statt print
wollen wir, dass die Funktion den ermittelten Wert zurückgibt.
def get_pkg_count():
# Get installed Debian Packages
deb_count = subprocess.run(["dpkg", "--list"], stdout=subprocess.PIPE, text=True)
deb_counted = len(deb_count.stdout.splitlines()) - 5
return deb_counted
Dazu noch das obligatorische Label:
# Label mit Text Pakages:
pkgs_label = tk.Label(stat_frame,text=f"Packages: {get_pkg_count()} (Debian)",background="#FFFFFF",font=("Sans",14))
pkgs_label.pack(anchor=tk.NW)
Der Terminal-Emulator
Ich hatte mich eigentlich darauf verlassen, dass jemand, der mehr Hirnschmalz besitzt als ich, schon irgendwo im Netz eine Methode bereitgestellt hat, um das "Terminal" auszulesen. Leider ist das nicht so.
Meine Hoffnung war, dass man über update-alternatives etwas auslesen kann. Nun ist es aber so – zumindest glaube ich –, dass openSUSE update-alternatives
gar nicht nutzt. Wie es bei anderen Betriebssystemen aussieht, kann ich auch nicht genau sagen. Deshalb hier gleich die Vorwarnung, dass der Code nicht absturzsicher ist.
def get_terminal_emu():
result = subprocess.run(
["update-alternatives", "--query", "x-terminal-emulator"],
stdout=subprocess.PIPE,
text=True,
check=True
)
# Den relevanten Teil filtern
for line in result.stdout.splitlines():
if line.startswith("Value:"):
return line.split("bin/")[1].strip()
Logik
- Wir holen uns über den Standard-Output den Wert von Value.
- Dazu lesen wir jede Zeile mit einem For-Loop aus.
- Und wenn eine Zeile Value enthält, soll diese Zeile angezeigt werden.
- Über
split
schneiden wir den Pfad ab, sodass wir nur noch den Dateinamen haben.
Das ist keine saubere Methode, denn rein theoretisch könnte in der Ausgabe noch ein "Value" vorhanden sein. Sollte es bei euch nicht funktionieren, lasst die Ausgabe des Terminals vorerst weg oder versucht euch an eine eigene Lösung.
Der ganze Code
## Teil 14 ###
import tkinter as tk
from PIL import Image, ImageTk
import os
import socket
import distro
import platform
import psutil
import datetime
import subprocess
from os import popen
# Ließt den Desktop aus
def get_desktop_environment():
xdg_current_desktop = os.environ.get("XDG_CURRENT_DESKTOP","").lower()
if xdg_current_desktop == "x-cinnamon" or xdg_current_desktop == "cinnamon":
return "CINNAMON"
elif xdg_current_desktop == "unity":
return "UNITY"
elif xdg_current_desktop == "ubuntu:gnome":
return "GNOME"
elif "gnome" in xdg_current_desktop:
return "GNOME"
elif "plasma" == xdg_current_desktop or "kde" == xdg_current_desktop:
return "KDE"
elif "xfce" == xdg_current_desktop:
return "XFCE"
elif os.environ.get("DESKTOP_SESSION", "").lower() == "lxde-pi-wayfire":
return "PI-WAYFIRE"
elif "mate" == xdg_current_desktop:
return "MATE"
else:
return "Unknown"
# Ließt das KDE Theme aus
def get_kde_theme_new():
result = subprocess.run(['plasmashell', '--version'],capture_output=True,text=True, check=True
)
plasma_version = result.stdout.strip()
if "plasmashell 5." in plasma_version:
result = subprocess.run(['kreadconfig5', '--file', '~/.config/kdeglobals', '--group', 'KDE', '--key', 'LookAndFeelPackage'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output = result.stdout.strip()
return output
if "plasmashell 6." in plasma_version:
result = subprocess.run(['kreadconfig6', '--file', '~/.config/kdeglobals', '--group', 'KDE', '--key', 'LookAndFeelPackage'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output = result.stdout.strip()
return output
# Ließt die KDE-Icons aus
def get_kde_icons():
result = subprocess.run(['plasmashell', '--version'],capture_output=True,text=True, check=True
)
plasma_version = result.stdout.strip()
if "plasmashell 5." in plasma_version:
result = subprocess.run(['kreadconfig5', '--file', '~/.config/kdeglobals', '--group', 'Icons', '--key', 'Theme'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output = result.stdout.strip()
return output
if "plasmashell 6." in plasma_version:
result = subprocess.run(['kreadconfig6', '--file', '~/.config/kdeglobals', '--group', 'Icons', '--key', 'Theme'],stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output = result.stdout.strip()
return output
#print(get_kde_icons())
# Ließt das DE-Theme aus
def get_desktop_theme():
if get_desktop_environment() == "GNOME":
result = subprocess.run(['gsettings', 'get', 'org.gnome.desktop.interface', 'gtk-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "CINNAMON":
result = subprocess.run(['gsettings', 'get', 'org.cinnamon.desktop.interface', 'gtk-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "MATE":
result = subprocess.run(['gsettings', 'get', 'org.mate.interface', 'gtk-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "XFCE":
result = subprocess.run(['xfconf-query', '-c', 'xsettings', '-p','/Net/ThemeName'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "PI-WAYFIRE":
result = subprocess.run(['gsettings', 'get', 'org.gnome.desktop.interface', 'gtk-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "KDE":
return get_kde_theme_new()
def get_desktop_icons():
if get_desktop_environment() == "GNOME":
result = subprocess.run(['gsettings', 'get', 'org.gnome.desktop.interface', 'icon-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "CINNAMON":
result = subprocess.run(['gsettings', 'get', 'org.cinnamon.desktop.interface', 'icon-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "MATE":
result = subprocess.run(['gsettings', 'get', 'org.mate.interface', 'icon-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "XFCE":
result = subprocess.run(['xfconf-query', '-c', 'xsettings', '-p'"/Net/IconThemeName","-s",],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "PI-WAYFIRE":
result = subprocess.run(['gsettings', 'get', 'org.gnome.desktop.interface', 'icon-theme'],capture_output=True,text=True, check=True
)
return result.stdout.strip().strip("'")
if get_desktop_environment() == "KDE":
return get_kde_icons()
# Ließt den Window-Manager aus
def get_window_manager_name():
try:
result = subprocess.run(
["wmctrl", "-m"], capture_output=True, text=True, check=True
)
output_lines = result.stdout.strip().split("\n")
for line in output_lines:
if line.startswith("Name: "):
window_manager_name = line.split("Name: ")[1]
if window_manager_name == "GNOME Shell":
return "Mutter"
return window_manager_name
except subprocess.CalledProcessError as e:
print(f"Error running wmctrl: {e}")
# Macht die RAM-Größe lesbar
def get_size(bytes, suffix="B"):
"""
Scale bytes to its proper format
e.g:
1253656 => '1.20MB'
1253656678 => '1.17GB'
"""
factor = 1024
for unit in ["", "K", "M", "G", "T", "P"]:
if bytes < factor:
return f"{bytes:.2f}{unit}{suffix}"
bytes /= factor
# Findet die Auflösung heraus
def get_screen_size():
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
return f"{screen_width}x{screen_height}"
def get_sys_uptime():
# System-Startzeit ermitteln
boot_time_timestamp = psutil.boot_time()
boot_time = datetime.datetime.fromtimestamp(boot_time_timestamp)
# Aktuelle Zeit
now = datetime.datetime.now()
# Uptime berechnen
uptime = now - boot_time
# Uptime in Stunden und Minuten umrechnen
uptime_hours, remainder = divmod(uptime.total_seconds(), 3600)
uptime_minutes = remainder // 60
# Weist an das diese Funktion Stunden und Minuten ausgeben soll
return f"{int(uptime_hours)} h , {int(uptime_minutes)} m"
# Setzt das korrekte Logo für die Distro
def get_distro_logo():
if distro_id == "debian":
distro_icon.configure(image=debian_logo)
elif distro_id == "arch":
distro_icon.configure(image=arch_logo)
elif distro_id == "mint":
distro_icon.configure(image=mint_logo)
elif distro_id == "ubuntu":
distro_icon.configure(image=ubuntu_logo)
elif distro_id == "opensuse":
distro_icon.configure(image=osuse_logo)
elif distro_id == "fedora":
distro_icon.configure(image=fedora_logo)
else:
distro_icon.configure(image=distro_logo)
def get_pkg_count():
# Get installed Debian Packages
deb_count = subprocess.run(["dpkg", "--list"], stdout=subprocess.PIPE, text=True)
deb_counted = len(deb_count.stdout.splitlines()) - 5
return deb_counted
def get_terminal_emu():
result = subprocess.run(
["update-alternatives", "--query", "x-terminal-emulator"],
stdout=subprocess.PIPE,
text=True,
check=True
)
# Den relevanten Teil filtern
for line in result.stdout.splitlines():
if line.startswith("Value:"):
return line.split("bin/")[1].strip()
# Vars für die Labels
# Ließt den User aus
user = os.environ["USER"]
# Ließt den Host aus
hostname = socket.gethostname()
# Ließt den Pretty Name aus
os_release_pretty = distro.name(pretty=True)
# Ließt den Kernel aus
kernel_release = platform.release()
# Basis um den RAM auszulesen
svmem = psutil.virtual_memory()
# Basis um CPU-Werte auszulesen
cpu_freq = psutil.cpu_freq()
# Ließt Anzahl der CPU-Kerne aus
cpu_core_count = psutil.cpu_count(logical=False)
# Gibt die aktuelle Shell aus
active_shell = os.environ["SHELL"]
# Gibt die Distro-ID aus
distro_id = distro.id()
# Erstelle das Hauptfenster
root = tk.Tk()
root.title("Neofetch-Tk")
root.geometry("800x500")
root["background"]="#FFFFFF" # Weiß
# Distro Logos
distro_logo = tk.PhotoImage(file="images/test.png")
arch_logo = tk.PhotoImage(file="images/arch_logo_350.png")
debian_logo = tk.PhotoImage(file="images/debian_logo_350.png")
mint_logo = tk.PhotoImage(file="images/mint_logo_350.png")
suse_logo = tk.PhotoImage(file="images/osuse_logo_350.png")
ubuntu_logo = tk.PhotoImage(file="images/ubuntu_logo_350.png")
fedora_logo = tk.PhotoImage(file="images/fedora_logo_350.png")
# Einen Frame Zeichen
logo_frame = tk.Frame(root,background="#FFFFFF")
logo_frame.pack(fill="both",expand=False,side='left',padx=10,pady=10)
# Distro-Logo-Label
distro_icon = tk.Label(logo_frame,text="DISTRO LOGO",image=distro_logo,background="#FFFFFF")
distro_icon.pack(anchor=tk.NW)
# Einen Frame Zeichen
stat_frame = tk.Frame(root,background="#FFFFFF")
stat_frame.pack(fill="both",expand=True,side='left',padx=10,pady=10)
# Label mit Text USER@HOST
user_host_label = tk.Label(stat_frame,text=f"{user}@{hostname}",background="#FFFFFF",font=("Sans",14))
user_host_label.pack(anchor=tk.NW)
# Label mit Text OS:
os_label = tk.Label(stat_frame,text=f"OS: {os_release_pretty}",background="#FFFFFF",font=("Sans",14))
os_label.pack(anchor=tk.NW)
# Label mit Text Host:
host_label = tk.Label(stat_frame,text=f"Host: {hostname}",background="#FFFFFF",font=("Sans",14))
host_label.pack(anchor=tk.NW)
# Label mit Text Kernel:
kernel_label = tk.Label(stat_frame,text=f"Kernel: {kernel_release}",background="#FFFFFF",font=("Sans",14))
kernel_label.pack(anchor=tk.NW)
# Label mit Text Uptime:
uptime_label = tk.Label(stat_frame,text=f"Uptime: {get_sys_uptime()}",background="#FFFFFF",font=("Sans",14))
uptime_label.pack(anchor=tk.NW)
# Label mit Text Pakages:
pkgs_label = tk.Label(stat_frame,text=f"Packages: {get_pkg_count()} (Debian)",background="#FFFFFF",font=("Sans",14))
pkgs_label.pack(anchor=tk.NW)
# Label mit Text Shell:
shell_label = tk.Label(stat_frame,text=f"Shell: {active_shell}",background="#FFFFFF",font=("Sans",14))
shell_label.pack(anchor=tk.NW)
# Label mit Text Resolution:
res_label = tk.Label(stat_frame,text=f"Resolution: {get_screen_size()}",background="#FFFFFF",font=("Sans",14))
res_label.pack(anchor=tk.NW)
# Label mit Text DE:
de_label = tk.Label(stat_frame,text=f"DE: {get_desktop_environment()}",background="#FFFFFF",font=("Sans",14))
de_label.pack(anchor=tk.NW)
# Label mit Text Window-Manager
wm_label = tk.Label(stat_frame,text=f"WM: {get_window_manager_name()}",background="#FFFFFF",font=("Sans",14))
wm_label.pack(anchor=tk.NW)
# Label mit Text Theme
wm_theme_label = tk.Label(stat_frame,text=f"Theme: {get_desktop_theme()}",background="#FFFFFF",font=("Sans",14))
wm_theme_label.pack(anchor=tk.NW)
# Label mit Text Icons
icon_theme_label = tk.Label(stat_frame,text=f"Icons: {get_desktop_icons()}",background="#FFFFFF",font=("Sans",14))
icon_theme_label.pack(anchor=tk.NW)
# Label mit Text Terminal
terminal_label = tk.Label(stat_frame,text=f"Terminal: {get_terminal_emu()}",background="#FFFFFF",font=("Sans",14))
terminal_label.pack(anchor=tk.NW)
# Label mit Text CPU:
cpu_label = tk.Label(stat_frame,text=f"CPU: ({cpu_core_count}) @ {cpu_freq.max:.2f} Mhz",background="#FFFFFF",font=("Sans",14))
cpu_label.pack(anchor=tk.NW)
# Label mit Text Memory:
mem_label = tk.Label(stat_frame,text=f"Memory: {(get_size(svmem.used))}/{get_size(svmem.total)}",background="#FFFFFF",font=("Sans",14))
mem_label.pack(anchor=tk.NW)
# Führt get_distro_logo aus
get_distro_logo()
# Starte die Hauptschleife
root.mainloop()