4 months ago

Python-Tk: Ein Neofetch-GUI selbst schreiben Teil 8



Der 8. Teil einer Einführung in die Python3-Bibliothek Tkinter und der lange Weg zum perfekten GUI.

Grüße,

ich würde sagen, legen sofort los und beginnen mit einer Bestandsaufnahme.

Die Tabelle zeigt, dass wir 8 von 16 Funktionen schon implementiert haben, wobei es 3 gibt, die noch Nacharbeitung benötigen.

Zu erledigen sind noch Distro Logo, Uptime, Packages, DE, WM, WM-Theme, Icons und Terminal.

Distro Logo Nur Platzhalter
USER & HOST Check Sollte keine Probleme machen
OS Check Sollte keine Probleme machen
Host Check Muss angepasst werden
Kernel Check Sollte keine Probleme machen
Uptime
Packages
Shell Check Muss angepasst werden
Resolution Check Sollte keine Probleme machen
DE
WM
WM-Theme
Icons
Terminal
CPU Check Sollte keine Probleme machen
Memory Check Sollte keine Probleme machen

Rückmeldungen

Mir ist natürlich von Anfang an bewusst gewesen, dass dieses Tutorial nicht alle Linux-Nutzer abholen kann. Das ist dem Grund geschuldet, dass ich hier teils Debian-Befehlen arbeite. Es besteht ja die Möglichkeit eine virtuelle Python Umgebung zu schaffen, die ohne die Bibliotheken einer bestimmten Distro funktioniert. Natürlich will ich niemanden ausschließen.

Die Debian-Basis ist nun mal am weitesten verbreitet und kein Python-Einsteiger-Tutorial der Welt fängt an mit der Erklärung, wie man den Python-Paket-Manager nutzt. Ich werde mir aber in der Sommerpause Gedanken machen, wie/wann ich diesen am besten einführe. 

Uptime

Wir wollen also herausfinden, wie lange unser PC schon an ist. Da hier wieder Mathematik und tiefergehendes Verständnis nötig ist, legen wir ein Test-File an.

Wir importieren:

import psutil import datetime

Mehr zu datetime

Nur machen wir den Zeitpunkt, an dem der PC gestartet ist. Von psutil nutzen wir die Methode boot_time.

import psutil import datetime unix_timestamp = psutil.boot_time() print(unix_timestamp)

Das Ergebnis sieht bei mir so aus:

1720781358.0

psutil.boot_time() gibt die Zeit als Unix-Zeitstempel zurück, zu der das System gestartet wurde. Um diese Zahl besser verständlich auszudrücken, nutzen wir datetime.

boot_time = datetime.datetime.fromtimestamp(unix_timestamp)

Dieses Modul ist etwas verwirrend. Man nutzt die Methode frometimestamp aus Methode datetime des Moduls datetime und geb unseren unix_timestamp hinein.

Das Resultat 

import psutil import datetime unix_timestamp = psutil.boot_time() boot_time = datetime.datetime.fromtimestamp(unix_timestamp) print(boot_time) >>> 2024-07-12 12:49:18

Jetzt wissen wir genau, wann ich meinen PC angeschaltet habe. Nun müssen wir noch herausfinden welches Datum um wie viel Uhr es gerade in diesem Augenblick sind.

now = datetime.datetime.now()

Jetzt kommt ganz simple Subtraktion zum Einsatz

uptime = now - boot_time

[Jetzt] - (MINUS) [Wann wurde der PC angeschaltet] 

Der ganze Test-Code

import psutil import datetime unix_timestamp = psutil.boot_time() boot_time = datetime.datetime.fromtimestamp(unix_timestamp) now = datetime.datetime.now() uptime = now - boot_time print(f"Startzeit: {boot_time}") print(f"Jetzt: {now}") print(f"Uptime: {uptime}") >>> Startzeit: 2024-07-12 12:49:18 Jetzt: 2024-07-12 17:49:43.825505 Uptime: 5:00:25.825505

In Main einbinden

Wie auch im Test-Skript importieren wir datetime

import datetime

Da wir ja irgendwann Live-Daten auslesen wollen, habe ich die UPTIME in eine Funktion gepackt und ein paar Änderungen vorgenommen, um die Lesbarkeit zu verbessern.

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 return f"{int(uptime_hours)} h , {int(uptime_minutes)} m"

Es wird mal wieder etwas komplexer. Der Wert aus uptime wird hier in Stunden und Minuten umgewandelt. Was übrigbleibt, wird durch 60 geteilt. Wird die Funktion abgerufen, soll "X h, X m" ausgegeben werden.

uptime_hours, remainder = divmod(uptime.total_seconds(), 3600) uptime_minutes = remainder // 60 return f"{int(uptime_hours)} h , {int(uptime_minutes)} m"

Logik

  • Eine Stunde hat 3600 Sekunden.
  • divmod Teil den Wert aus uptime._total_seconds.
  • Was bleibt sind Stunde und ein Rest
  • Der Rest wird durch 60 geteilt
  • Ergibt der Rest eine oder mehrere ganze Minuten, landet in uptime_minutes
  • über return findet die Ausgabe statt
  • Da wir ganze Zahlen wollen, aber eine Kommazahl bekommen(float), nutzen wir int um die Zahlen zu konvertieren
  • Das geht aber nur, weil wir durch divmod ganze Zahlen z. B. 10.0 zurückbekommen

Unter dem kernel_label machen wir etwas Platz frei und schreiben wie gewohnt ein Label, dessen Text den Inhalt der Funktion wiedergibt.

uptime_label = tk.Label(stat_frame,text=f"Uptime: {get_sys_uptime()}") uptime_label.pack(anchor=tk.NW)

Der ganze Code in Main

## Teil 8 ### import tkinter as tk from PIL import Image, ImageTk import os import socket import distro import platform import psutil import datetime 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 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 return f"{int(uptime_hours)} h , {int(uptime_minutes)} m" user = os.environ["USER"] hostname = socket.gethostname() os_release_pretty = distro.name(pretty=True) kernel_release = platform.release() svmem = psutil.virtual_memory() cpu_freq = psutil.cpu_freq() cpu_core_count = psutil.cpu_count(logical=False) active_shell = os.environ["SHELL"] # 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) host_label = tk.Label(stat_frame,text=f"Host: {hostname}") host_label.pack(anchor=tk.NW) kernel_label = tk.Label(stat_frame,text=f"Kernel: {kernel_release}") kernel_label.pack(anchor=tk.NW) uptime_label = tk.Label(stat_frame,text=f"Uptime: {get_sys_uptime()}") uptime_label.pack(anchor=tk.NW) shell_label = tk.Label(stat_frame,text=f"Shell: {active_shell}") shell_label.pack(anchor=tk.NW) res_label = tk.Label(stat_frame,text=f"Resolution: {get_screen_size()}") res_label.pack(anchor=tk.NW) cpu_label = tk.Label(stat_frame,text=f"CPU: ({cpu_core_count}) @ {cpu_freq.max:.2f} Mhz") cpu_label.pack(anchor=tk.NW) mem_label = tk.Label(stat_frame,text=f"Memory: {(get_size(svmem.used))}/{get_size(svmem.total)}") mem_label.pack(anchor=tk.NW) # Starte die Hauptschleife root.mainloop()

Bye, Bye Platzhalter-Logo

Auch hier gehen wir es langsam an. Wir möchten ja das korrekte Logo unterer Distribution angezeigt bekommen. Wie stellen wir das jetzt an? Um die Basis herauszufinden, können wir wieder ein Modul nutzen.

sudo apt install python3-distro

In manchen Fällen ist distro aber auch schon vorinstalliert. Nutzen können wir es wie folgt.

import distro print(f"id: {distro.id()}") print(f"name: {distro.name()}") print(f"version: {distro.version()}") print(f"codename: {distro.codename()}") >>> id: ubuntu name: Ubuntu version: 24.04 codename: noble

Ich habe bei Distrosea so einige Distributionen durchgesehen und leider stand in den release-Files immer nur die Basis. Würden wir den obigen Code so nutzen könnten wir also nur auslesen, dass wir ein Ubuntu nutzen aber nicht ein Kubuntu oder Xubuntu. Linux Mint bildet übrigens eine Ausnahme. Sucht man die ID steht dort nicht ubuntu, sondern linuxmint.

Natürlich ist es schöner gleich das korrekte Logo zu sehen, um zu lernen, wie wir das überhaupt anstellen, gehen wir erstmal nur mit den Logos der Distro-Basis. 

Ach und Siduction habe ich nicht vergessen! Das ist aber ein Sonderfall. Hier müssen wir den Inhalt einer Datei auslesen. Dazu benötigt man with open. Das muss ich auch erst erklären. (nach der Pause). 

Zunächst brauchen wir überhaupt mal Logos. Ich habe uns welche vorbereitet. Ihr könnt sie einfach auf Github herunterladen. 

PNGs auf Github

Die PNGs sollten wie folgt in die Ordnerstruktur eingegliedert werden.

~/neofetch-tk$ tree . ├── images │ ├── arch_logo_350.png │ ├── debian_logo_350.png │ ├── fedora_logo_350.png │ ├── mint_logo_350.png │ ├── osuse_logo_350.png │ ├── test.png │ └── ubuntu_logo_350.png └── main.py

Wir gehen in unser main.py-File und importieren distro und fügen folgenden Code zu den anderen Variablen hinzu.

distro_id = distro.id()

Nach dem Vorbild unseres Platzhalters erstellen wir nun Variablen, die Logos beinhalten.

distro_logo = tk.PhotoImage(file="images/test.png")

Neue 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")

Der Übersichtlichkeit halber bauen wir für die Nutzung eine Funktion, diese soll am Ende des Scripts ausgeführt werden.

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 == "linuxmint": 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) else: distro_icon.configure(image=distro_logo)

[...]

get_distro_logo() # Starte die Hauptschleife root.mainloop()

Logik

  • Wenn(if) distro_id genau "debian" entspricht, wird mit der Methode configure das image durch debian_logo ersetzt
  • Ist dem nicht so, werden die anderen Fälle(elif) durchgegangen, bis etwas Passendes gefunden wird
  • Ist keine Annahme zutreffen (else:) wird unser Testbild gesetzt.

Der ganze Code(Mit aktualisierten Kommentaren)

## Teil 8 ### import tkinter as tk from PIL import Image, ImageTk import os import socket import distro import platform import psutil import datetime import distro # 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 == "linuxmint": 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") # 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="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) # Label mit Text Host: host_label = tk.Label(stat_frame,text=f"Host: {hostname}") host_label.pack(anchor=tk.NW) # Label mit Text Kernel: kernel_label = tk.Label(stat_frame,text=f"Kernel: {kernel_release}") kernel_label.pack(anchor=tk.NW) # Label mit Text Uptime: uptime_label = tk.Label(stat_frame,text=f"Uptime: {get_sys_uptime()}") uptime_label.pack(anchor=tk.NW) # Label mit Text Shell: shell_label = tk.Label(stat_frame,text=f"Shell: {active_shell}") shell_label.pack(anchor=tk.NW) # Label mit Text Resolution: res_label = tk.Label(stat_frame,text=f"Resolution: {get_screen_size()}") res_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") 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)}") mem_label.pack(anchor=tk.NW) # Führt get_distro_logo aus get_distro_logo() # Starte die Hauptschleife root.mainloop()

Das war's für heute. Nächstes Mal machen wir alles "schön".

Bis nächste Woche.


GNU/Linux.ch ist ein Community-Projekt. Bei uns kannst du nicht nur mitlesen, sondern auch selbst aktiv werden. Wir freuen uns, wenn du mit uns über die Artikel in unseren Chat-Gruppen oder im Fediverse diskutierst. Auch du selbst kannst Autor werden. Reiche uns deinen Artikelvorschlag über das Formular auf unserer Webseite ein.
Gesamten Artikel lesen

© Varient 2024. All rights are reserved