Zuletzt ging es um das Desktop-Environment. Für diesen und den nächsten Teil war es essenziell, das "DE" auszulesen, denn nur so haben wir eine Grundlage, um Informationen zu Thema und Icons zu erlangen.
Die Logik dahinter
Wir haben bereits gelegentlich mit if/else-Statements
gearbeitet, und auch in diesem Teil wird das der Fall sein. Wir wissen also, welcher Desktop aktuell auf dem System läuft. Das ist gut, denn nun können wir die Möglichkeiten eingrenzen.
Das ist unser Code vom letzten Mal. Bei mir gibt er den Wert GNOME aus.
import os
def get_desktop_environment():
xdg_current_desktop = os.environ.get("XDG_CURRENT_DESKTOP")
if xdg_current_desktop == "x-cinnamon":
return "CINNAMON"
elif xdg_current_desktop == "ubuntu:GNOME":
return "GNOME"
else:
return xdg_current_desktop
print(get_desktop_environment())
>>> %Run -c $EDITOR_CONTENT
GNOME
Diese Funktion oder vielmehr die Ausgabe können wir nicht nur nutzen, um sie in einem Label anzeigen zu lassen. Wir können wie auch weiter verarbeiten.
import os
def get_desktop_environment():
xdg_current_desktop = os.environ.get("XDG_CURRENT_DESKTOP")
if xdg_current_desktop == "x-cinnamon":
return "CINNAMON"
elif xdg_current_desktop == "ubuntu:GNOME":
return "GNOME"
else:
return xdg_current_desktop
print(get_desktop_environment())
if get_desktop_environment() == "GNOME":
print("True")
>>> %Run -c $EDITOR_CONTENT
GNOME
True
Logik
- Wenn der Desktop dem hart-kodierten String entspricht, soll "Wahr" ausgegeben werden.
Ein bisschen professioneller wäre es den Boolean, also den True- oder False-Wert auszulesen.
import os
def get_desktop_environment():
xdg_current_desktop = os.environ.get("XDG_CURRENT_DESKTOP")
if xdg_current_desktop == "x-cinnamon":
return "CINNAMON"
elif xdg_current_desktop == "ubuntu:GNOME":
return "GNOME"
else:
return xdg_current_desktop
def is_gnome():
return get_desktop_environment() == "CINNAMON"
print(is_gnome())
>>> %Run -c $EDITOR_CONTENT
False
Wo liegen die Informationen über das aktuelle Theme?
Der Code zuvor war noch der einfache Teil. Wie bereits im letzten Abschnitt erwähnt, hat jede Desktop-Oberfläche ihre eigene Art, Daten zu verwalten. Was es uns etwas einfacher macht, ist, dass einige der beliebtesten Desktops Forks bzw. Abkömmlinge von GNOME sind. Somit können wir bereits ein gewisses Schema ableiten.
Für GNOME eignet sich gsettings
gut, um das Theme auszulesen. Darüber habe ich schon vor einiger Zeit einen Artikel geschrieben, den ihr euch für ein besseres Verständnis durchlesen solltet.
Wie holt sich Neofetch das Theme?
Erste Anhaltspunkte, wie Neofetch das Theme erkennt, finden wir in Zeile 3216. Auf den ersten Blick bringt uns der Code nicht viel, aber bei genauerem Hinsehen wird es erhellend. Was wir hier haben, sind Variablen, die nach Tools benannt sind, die zur Konfiguration des Desktops verwendet werden. Diese Variablen halten den auszulesenden Wert als String – in diesem Fall das Theme.
Wenn wir uns die letzte Zeile ansehen, finden wir get_style
. Dies verweist auf eine weitere Funktion und sollte genauer untersucht werden.
get_theme() {
name="gtk-theme-name"
gsettings="gtk-theme"
gconf="gtk_theme"
xfconf="/Net/ThemeName"
kde="Name"
get_style
}
Und Jackpot! Da ich nur ins Blaue geraten und nach dem Begriff "Theme" gesucht habe, ist mir entgangen, dass get_style
direkt darüber liegt. Ab Zeile 3124 finden wir alle Kommandos, die Neofetch nutzt, um das Desktop-Theme auszulesen. Ihr könnt gerne noch etwas in den verlinkten Zeilen stöbern, aber ich liste uns schon mal unsere Optionen auf.
Cinnamon
gsettings get org.cinnamon.desktop.interface "$gsettings"
Gnome | Unity | Budgie
gsettings get org.gnome.desktop.interface "$gsettings"
Mate
gsettings get org.mate.interface "$gsettings"
XFCE
xfconf-query -c xsettings -p "$xfconf"
Test
Wir nutzen hier wieder subprocess.run
welches wir schon kennengelernt haben. Zu beachten ist, dass dieser Code nicht fehlerfrei bzw. crash-sicher ist. Das rührt daher, dass versucht wird ein GTK-Theme auszulesen und ja nun mal nicht jeder Gnome nutzt. Weiter oben habe ich schon eine Liste aufgeführt, wie man die Themes verschiedener Desktops ausliest. Versucht doch, als kleine Übung den Code so zu modifizieren, dass er für eueren Desktop passt.
import subprocess
def get_desktop_theme_test():
result = subprocess.run(
['gsettings', 'get', 'org.gnome.desktop.interface', 'gtk-theme'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
return result.stdout.strip().strip("'")
#get_desktop_theme_test()
print(f"{get_desktop_theme_test()}")
Warnung: Heavy Testing
Code zu schreiben und theoretisch wissen, wie es geht, ist eine Sache. Ob dann wirklich alles so läuft, ist wieder etwas ganz anderes. Einen Schwung SSDs mit verschiedenen Betriebssystemen zu haben ist übrigens Gold wert, wenn man übergreifend arbeitet.
Beim Testen ist mir aufgefallen, das XDG_CURRENT_DESKTOP
auf Raspberry Pi OS None
ausgibt. Dadurch kann somit auch nicht das Theme ausgelesen werden. Wir müssen mit dem Wert aus DESKTOP_SESSION
gehen.
Hier eine verbesserte und er weitete Version von get_desktop_enviornment()
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"
Logik
- Da dieser Suchlauf auf Groß- und Kleinschreibung achtet, nutze ich, damit
.lower()
die Ausgabe komplett kleingeschrieben ist. - Sollte nichts zutreffen, wird zusätzlich noch DESKTOP_SESSION abgefragt
Anmerkung: Es geht natürlich eleganter, aber um zu verstehen, wie die Abfrage funktioniert, ist das die übersichtlichste Formulierung. Um die Ausgabe und bzw. Groß- und Kleinschreibung kümmern wir uns noch.
Theme Funktion
Sortiert nach Desktops, werden die Daten ausgelesen und über return
zurückgegeben. Zu beachten ist, dass in diesem Beispiel KDE None
zurückgibt.
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 "None"
Logik
- Wenn der Desktop dem hart-kodierten String (z.B. CINNAMON) entspricht, wird die eingereihte Aktion ausgeführt.
.strip(" ' ")
entfernt die Anführungszeichen, die andernfalls mit ausgegeben würden.
Spezial-Fall KDE
Die Desktop-Umgebung hat mir viel Kopfzerbrechen bereitet. Als Anhaltpunkt habe ich zwar den Code von Neofetch, reibungslos geht aber anders.
Wir haben zwar in Zeile 3137 den Verweis auf die ~/.config/kdeglobals
, bei meinem Test auf Kubuntu wurde dieses File aber erst angelegt, als ich das Theme zum ersten Mal geändert habe. Darauf muss man auch erstmal kommen.
Leider habe ich auch nach langem Suchen keine so richtig gute und erprobte Methode gefunden, um das Theme auszulesen.
Selbst-Testen ist also das Gebot der Stunde. Bei geöffnetem ~/.config/kdeglobals
habe ich das Theme geändert, daraus hat sich ergeben, dass sich der Parameter LookAndFeelPackage
verlässlich auslesen lässt und das globale Theme wieder gibt.
Wir gehen also mit diesem Parameter und müssen diesen in einer eigenen Funktion auslesen, damit es auch übersichtlich bleibt.
def get_kde_theme():
file_path = os.path.expanduser("~/.config/kdeglobals")
with open(file_path, 'r') as file:
for line in file:
if line.startswith("LookAndFeelPackage="):
look_and_feel = line.strip().split("=")[-1]
if look_and_feel.startswith("org.kde."):
look_and_feel = look_and_feel.replace("org.kde.", "")
return look_and_feel
file_path = os.path.expanduser("~/.config/kdeglobals")
definiert den Pfad zur Datei.expanduser
tauscht ~ gehen den tatsächlichen Pfad aus (~
kann manchmal zu Verhakungen / Fehlern führen)with open(file_path, 'r') as file:
gibt die Anweisung, dass nun das File in der Variable geöffnet werden soll.'r'
legt fest, dass nur gelesen werden darf. Um damit weiter zu arbeiten wir mitas file
alles in eine Variable verpackt.for line in file:
DieserFor-Loop
durchsucht jede Zeile der Datei.line
kann aber auch i oder x genannt werden, das ist nicht festgelegt.if line.startswith("LookAndFeelPackage="):
Sollte eine Zeile den quotierten Begriff enthalten, wird die gesamte Zeile als Variablelook_and_feel
gleichgesetzt, mitstrip
auseinander gezogen und mit nur das einbezogen, was nach "=" steht. Mit [-1] wird sichergestellt das kein Absatz mitgenommen wird
Ergebnisse
Wie man gut sehen kann, wird das Theme korrekt ausgelesen. Lasst euch nicht von den falschen Distro-Logos oder Namen stören, das gehört alles noch zum Plan.
Kubuntu
MX Linux
Raspberry Pi OS
Der ganze Code
## Teil 12 ###
import tkinter as tk
from PIL import Image, ImageTk
import os
import socket
import distro
import platform
import psutil
import datetime
import subprocess
# 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():
file_path = os.path.expanduser("~/.config/kdeglobals")
with open(file_path, 'r') as file:
for line in file:
if line.startswith("LookAndFeelPackage="):
look_and_feel = line.strip().split("=")[-1]
if look_and_feel.startswith("org.kde."):
look_and_feel = look_and_feel.replace("org.kde.", "")
return look_and_feel
# 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()
# 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)
# 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 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 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()
Das ist jetzt alles richtig neu und interessant. Vielen Dank! Zwei Dinge wollte ich noch fragen, die hier vielleicht in den Rahmen passen könnten:
GitHub ist schon fast ein Fass ohne Boden. Ich habe hier ein Buch von Öggler und Kofler, das mir zur Seite steht.
Was CustomTkinter angeht:
Ja, das sieht super aus. Damit habe ich natürlich auch schon herumgespielt. Für Tkinter-Anfänger würde ich allerdings empfehlen, zuerst Tkinter und ttk zu lernen (als Grundlage). CT (CustomTkinter) baut zwar auf der Syntax von Tkinter auf, bringt aber viele Änderungen mit, die erst erlernt werden müssen.
Mein letzter Stand ist, dass man CT nur über PIP installieren kann. Auf Distributionen mit Debian-Basis kann man externe Pakete ja nur noch in einer virtuellen Python-Umgebung installieren. Ich versuche hier erstmal nur auf Pakete aus dem Repo zurückzugreifen.
Zwei Hinweise als KDE-User:
Und vielen Dank für diese Serie, das ist echt cool.
Na, das ist doch mal 'ne Ansage. Ich teste das und dann kommt's als Update in den nächsten Teil. Da merkt man aber auch, dass ich Gnome-Nutzer bin und von KDE keinen Plan hab.
Macht ja nichts, dafür gibt es ja auch Leser. Ich hätte dafür mit Gnome das gleiche Problem :D Und ich habe das auch erst rausgefunden, weil ich eine bessere Lösung für mich gesucht habe.