Alles, was wir bis Teil 4 gemacht haben, war eigentlich nur ein Bild zu zeichnen. Das ist nicht nur eine Metapher, sondern durchaus korrekt dargestellt. Wir haben Code geschrieben, der dem Fenster-Manager sagt: Ich brauche ein Fenster mit zwei farbigen Rahmen und einem Bild darin. Wirklich etwas falsch machen kann man hier nicht, außer fehlerhaften Code einzugeben. Der Compiler würde uns dann schon sagen, was nicht läuft. Wir können das bisher Erarbeitete also als Kunst verbuchen, und Kunst ist nun mal Ansichtssache.
Nun sind wir fürs Erste fertig mit diesem Teil und wollen dazu übergehen, Informationen auszulesen und sie unserem GUI anzeigen zu lassen. Ab jetzt gibt es unzählige Möglichkeiten und Varianten, dies zu tun. Wir kommen in einen Bereich, der viel Lektüre beansprucht und bei dem es um Lesbarkeit, Simplizismus, Effektivität und auch Sicherheit geht.
Es wird unvermeidbar sein, auf Bugs zu stoßen oder Probleme mit Distro-übergreifenden Funktionen zu bekommen. Sprich: Wir werden die einfachsten Lösungen nutzen, wir werden Fehler machen und versuchen, diese zu beseitigen, aber wir müssen uns auch damit auseinandersetzen, dass das, was wir zusammen gebaut haben, vielleicht gar nicht auf den Systemen anderer Leute funktioniert.
Unser Workflow:
DENKEN
> Code schreiben
> Testen
> DENKEN
> Fehler beheben
> Testen
Der User
Es gibt dutzende Wege, den aktuellen Nutzer auszulesen. Wir gehen den Weg über das os
-Modul. Hiermit kann man so einiges nicht nur auslesen, sondern auch manipulieren. Im Laufe der Serie werden wir `os
` noch besser beleuchten; die schiere Menge an Möglichkeiten kann aber schnell überfordern. Wie immer gilt: Eines nach dem anderen.
Das Modul ist standardmäßig in Python implementiert, und ihr müsst nichts nachinstallieren.
os.getlogin()
Return the name of the user logged in on the controlling terminal of the process. For most purposes, it is more useful to use getpass.getuser() since the latter checks the environment variables LOGNAME or USERNAME to find out who the user is, and falls back to pwd.getpwuid(os.getuid())[0] to get the login name of the current real user id.
Test-Code
import os
user = os.getlogin()
print(user)
Der Output sollte Euer Nutzer-Name sein.
Einbauen
Zu unserer Importliste fügen wir import os
. Nun legen wir die Variable user = os.getlogin()
an. Als Nächstes müssen wir den Text des Labels manipulieren. Jetzt kommt der f-String
ins Spiel.
Vorher:
user_host_label = tk.Label(stat_frame,text="USER@HOST")
user_host_label.pack(anchor=tk.NW)
Nachher:
user_host_label = tk.Label(stat_frame,text=f"{user}@HOST")
user_host_label.pack(anchor=tk.NW)
Logik
- Vor die Quotes(
" "
) wird ein "f
" gesetzt, um den String zu manipulieren - Es wird
USER
ersetzt durchuser
in{ }
(geschweifte Klammern) - die Variable
user
am Anfang des Codes wird gespeichert und innerhalb des Strings ausgegeben
Der Host
Diesen können wir mit dem socket
-Modul auslesen, welches ebenfalls in Python enthalten sein sollte. Es bietet Zugriff auf die Netzwerk-Schnittstellen des Betriebssystems und ermöglicht es, Netzwerkkommunikation mittels Sockets durchzuführen. Ein Socket ist ein Endpunkt einer bidirektionalen Kommunikationsverbindung zwischen zwei Programmen, die über ein Netzwerk kommunizieren.
Wir benötigen erstmal nur eine Funktion dieses Moduls
socket.gethostname()
Einbauen
Wir auch zuvor binden wir socket
in den Code ein mit import socket
, Dann legen wir die nötige Variable an.
hostname = socket.gethostname()
Ich hoffe, ihr hab aufgepasst und wisst, was nun folgt. Die Erklärung solltet ihr Euch selbst erschließen können.
user_host_label = tk.Label(stat_frame,text=f"{user}@{hostname}")
user_host_label.pack(anchor=tk.NW)
Der Code bis hier
## Teil 5 ###
import tkinter as tk
from PIL import Image, ImageTk
import os
import socket
user = os.getlogin()
hostname = socket.gethostname()
# Erstelle das Hauptfenster
root = tk.Tk()
root.title("Neofetch-Tk")
root.geometry("800x500")
distro_logo = tk.PhotoImage(file="images/test.png")
# Einen Frame Zeichen
logo_frame = tk.Frame(root,background="yellow")
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="yellow")
distro_icon.pack(anchor=tk.NW)
# Einen Frame Zeichen
stat_frame = tk.Frame(root,background="cyan")
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}")
user_host_label.pack(anchor=tk.NW)
# Starte die Hauptschleife
root.mainloop()
Ist Euch eigentlich etwas aufgefallen? Nein? Schade. In Teil 4 war 2x user_host_label
vorhanden. Python liest das File von oben nach unten durch und es ist dem Interpreter egal, ob die vorherige Variable genauso hieß. Bei Labels ist das weiter nicht schlimm. Wenn ihr aber eine Variable mit einem wichtigen, wer verseht z.B. zahl_eins = 1
und danach aus Versehen zahl_eins = 2
als setzt, ist kann das zum Problem werden. Also achtet auf die Benennung.
OS Label
Wir korrigieren nun diesen Fehler und benennen die Variable um, das sollte dann so aussehen:
# Label mit Text USER@HOST
user_host_label = tk.Label(stat_frame,text=f"{user}@{hostname}")
user_host_label.pack(anchor=tk.NW)
# Label mit Text OS:
os_label = tk.Label(stat_frame,text=f"OS: ")
os_label.pack(anchor=tk.NW)
Wie kommen wir jetzt an unseren OS-NAMEN?
Wir werfen zunächst einen Blick in /bin/neofetch
. Ob Ihr das nun mit cat
macht, oder Eurem Liebling-Editor ist vollkommen egal. Um die betreffende Stelle zu finden, müssten wir zunächst an hunderten auskommentierte Zeilen vorbei.
Nicht ganz ausführlich, aber ausreichten erklärt holt sich neofetch
die Information aus /etc/os-release
und/oder /etc/lsb-release
. Das ist noch wichtig zu wissen für später.
Wie wir das nachbilden können, habe ich schon in dem Artikel zur Terminal-Version veranschaulichen:
# Get Name of Distro
os_release = subprocess.run(["grep", "-E", "^(PRETTY_NAME)=", "/etc/os-release"], stdout=subprocess.PIPE, text=True)
nice_name = os_release.stdout.strip().split('=')[1].strip('"')
print(f"OS: {nice_name}")
Das ist aber viel zu kompliziert und dieses Konzept der Daten-Erfassung werden wir uns zu einem späteren Zeitpunkt anschauen.
Es gibt ein Python-Modul, das es relativ einfach macht, den os-release
auszulesen.
distro
Sollte auf Eurem Debian/Ubuntu-Derivat das Paket nicht installiert sein:
sudo apt install python3-distro
Wir wollen also den Pretty-Name
und den bekommen wir so:
import distro
pretty_name = distro.name(pretty=True)
print("Pretty Name:", pretty_name)
>>> Pretty Name: Ubuntu 24.04 LTS (In meinem Fall)
Logik
- distro ist das Modul
- name die Methode
- mit
distro.name
greifen wir darauf zu. distro.name(pretty=True)
Ohne pretty=True
würde das so aussehen:
import distro
pretty_name = distro.name()
print("Pretty Name:", pretty_name)
>>> Pretty Name: Ubuntu
Ganz ausführlich wäre:
import distro
print(f"Pretty Name: {distro.name(pretty=True)}")
print(f"Name: {distro.name()}")
print(f"Version: {distro.version()}")
print(f"ID: {distro.id()}")
print(f"Alles: {distro.info()}")
Das Modul in unseren Code einbauen
Ihr bekommt jetzt eine kleine Denkaufgabe:
distro importieren
- die Variable
os_release_pretty
setzen os_release_pretty
in den Text vonos_label
einbauen
Der Ganze Code
## Teil 5 ###
import tkinter as tk
from PIL import Image, ImageTk
import os
import socket
import distro
user = os.getlogin()
hostname = socket.gethostname()
os_release_pretty = distro.name(pretty=True)
# Erstelle das Hauptfenster
root = tk.Tk()
root.title("Neofetch-Tk")
root.geometry("800x500")
distro_logo = tk.PhotoImage(file="images/test.png")
# Einen Frame Zeichen
logo_frame = tk.Frame(root,background="yellow")
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="yellow")
distro_icon.pack(anchor=tk.NW)
# Einen Frame Zeichen
stat_frame = tk.Frame(root,background="cyan")
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}")
user_host_label.pack(anchor=tk.NW)
# Label mit Text OS:
os_label = tk.Label(stat_frame,text=f"OS: {os_release_pretty}")
os_label.pack(anchor=tk.NW)
# Starte die Hauptschleife
root.mainloop()
Zum Abschluss
Ich habe schon ein paar Mal darauf hingewiesen, dass wir ab jetzt in Bug-Territorium kommen. Vorweg nehme ich schon einmal, dass es sein kann, dass z.B. auf MX-Linux in der OS-Zeile "GNU/Linux Debian 12" stehen könnte. Das ist aber nicht unser Fehler, sondern der Fehler der Distro-Entwickler. Es gibt die eine oder andere Distribution, die in os-release
nicht ausreichend deklariert, wie denn nun deren Derivat heißt. Letztendlich helfen Schuldzuweisungen auch nicht weiter. Wir bekommen das aber hin.
Um so etwas zu umgehen, müssten wir noch zusätzliche Informationen einbinden. Ich gehe auch nur kurz darauf ein. Sagen wir, die Distro gibt den Wert Debian aus, wir aber auf MX unterwegs sind, können wir es etwas zurechtbasteln.
Beispiel: Wenn die Distro Debian ergibt und die Datei /bin/mx-tools
existiert, soll os_release_pretty
den Wert MX-Linux ausgeben. Das ist nicht unbedingt akkurat, denn wir wissen noch immer nicht, welche Versionsnummer und welchen Codenamen wir nutzen. Lass das erstmal sacken, mit diesen Mechaniken werden wir uns bald auseinandersetzen müssen.
Hausaufgabe
Sollte der OS-Output
auf Eurem PC nicht Eurer Distro entsprechen, dann postet es doch mal unter dem Artikel. So können wir uns einen Überblick darüber machen, für welche Distros wir einen Workaround basteln müssen. Vielleicht findet sich ja in lsb_release
etwas Brauchbares.
cat /etc/os-release
cat /etc/lsb-release
To-Do (aus Rückmeldungen)
Distro-Sonder-Fälle
- Siduction:
/etc/siduction-version
Get User Error
import os
user = os.environ["USER"]
# ODER:
import pwd
import os
user = pwd.getpwuid(os.getuid()).pw_name
Bis nächste Woche
Danke, ich freue mich auf die Fortsetzungen. Ich verwende die Distribution Siduction www.siduction.org.
lsb_release -a
Distributor ID: Debian Description: Debian GNU/Linux trixie/sid Release: n/a Codename: trixie
ls /etc/*version
-rw-r--r-- 1 root root 11 2023-06-11 12:00 /etc/debian_version -r--r--r-- 1 root root 49 2023-09-09 21:08 /etc/siduction-version
cat /etc/*version
trixie/sid siduction 2023.1.1 giants - xfce - (202309091902)
Super, danke! Ich habe auch nochmal den Neofetch-Code nachgelesen. Für Siduction gibt es einen speziellen Fall, der dazu führt, dass
/etc/siduction-version
ausgelesen wird.Erst mal sorry für die tausend Zeilenumbrüche, aber die Syntax hier in dieser Feedback-Box ist mir nicht klar und es gibt keine Voransicht...
'user = os.getlogin()'
Bringt unter Ubuntu 22.04: OSError: [Errno 6] No such device or address
Nach kurzer Recherche zeigt sich, dass Python über die glibc eine /proc/self/loginuid ausliest, die wohl nicht überall gesetzt ist (?).
Auf diesem Rechner funktioniert es, entweder die Umgebungsvariable "LOGNAME" auszulesen:
os.environ["LOGNAME"]
Oder noch das Password-Modul zu laden und die passwd-Info auszulesen (das scheint mir unter Linux die sicherste Variante):
import pwd
pwd.getpwuid(os.geteuid())[0])
Genau so etwas habe ich erwartet; ich habe ja schon oft erwähnt, dass es mit zunehmender Komplexität zu Fehlern kommen kann. Kommt auf die Liste. Entweder gehen wir mit:
import os user = os.environ["USER"]
ODER:
import pwd import os user = pwd.getpwuid(os.getuid()).pw_name
... Wie gesagt: Duzenden Möglichkeiten :-D
Erstmal Danke für diese tolle Tutorial.
Hallo, habe jetzt mehrmals "/bin/neofetch" gesehen. Ich habe diese auf meinem PC (Ubuntu) gesucht und nicht gefunden. Könnte es auch irgendwo in einem anderen Verzeichnis stecken?
Die Datei findest unter /usr/bin/neofetch.