1 month ago

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



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

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

Mehr zu boolean

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.

Mehr zu strip

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 mit as file alles in eine Variable verpackt.
  •    for line in file: Dieser For-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 Variable look_and_feel gleichgesetzt, mit strip auseinander gezogen und mit nur das einbezogen, was nach "=" steht. Mit [-1] wird sichergestellt das kein Absatz mitgenommen wird

Mehr zu open

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()

Der ganze Code auf Github.

Link zu den vorangegangenen Teilen


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