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

  Actionschnitzel   Lesezeit: 14 Minuten  🗪 11 Kommentare Auf Mastodon ansehen

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

python-tk: ein neofetch-gui selbst schreiben teil 14

Nach so viel harter Denkarbeit haben wir es uns verdient, auch mal etwas entspannter zu arbeiten. Linux-Desktops und eigentlich IT allgemein folgen immer einer Logik, einem Schema. Heute soll es um das Auslesen des Icon-Themes gehen. Den Grundstein dafür haben wir schon gelegt, indem wir Funktionen geschrieben haben, um das Desktop-Theme auszulesen. Da das alles, wie schon erwähnt, einem Schema folgt, müssen wir heute nur Copy & Paste betreiben und ein paar Begriffe/Variablen ändern.

KDE

Aus unserer Funktion vom letzten Mal (get_kde_theme_new()) wird def get_kde_icons().

Die Parameter '--group', 'KDE', '--key', 'LookAndFeelPackage werden zu '--group', 'Icons', '--key', 'Theme'.

# 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
        

Der Rest

Wir schnappen uns die bereits bestehende Funktion def get_desktop_theme() und kopieren sie. Auch hier benennen wir einiges um und die Funktion wird zu get_desktop_icons. Für alle auf Gnome-basierenden Desktops ändern wir den Key zu icon-theme.

In XFCE lesen wir die Icons so aus:

if get_desktop_environment() == "XFCE":
        result = subprocess.run(['xfconf-query', '-c', 'xsettings', '-p'"/Net/IconThemeName","-s",],capture_output=True,text=True, check=True
        )


Im Falle von KDE:

    if get_desktop_environment() == "KDE":
        return get_kde_icons()



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

Um das Ausgelesene auch anzuzeigen benötigen wir ein neues Label:

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)

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

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

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

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

Zum Schluss noch ein kleiner Tipp. Logik ist in Linux allgegenwärtig. Gerade wenn man System-Tools baut, kann allein dieses Wissen Gold wert sein. 

Wer sich genauer mit den Mechanismen eines Desktops auseinandersetzen will, dem empfehle ich den dconf-editor zu installieren. Hier können alle Stellschrauben logisch aufgeschlüsselt eingesehen aber auch geändert werden. Das Tool ist aber auch mit Vorsicht zu genießen.

Der ganze Code auf Github.

Link zu den vorangegangenen Teilen

Tags

GUI, Neofetch, Python, Tkinter

MichaelM
Geschrieben von MichaelM am 16. November 2024 um 14:23

Ich habe eine Korrektur für get_kde_icons(): Wenn man direkt den Pfad ~/.config/kdeglobals' nimmt, wird nichts zurückgeliefert. Es funktioniert nur, wenn man den Pfad wie in get_kde_theme() ermittelt und dann an den Subprozess übergibt:

Pfad zu ~/.config/kdeglobals auflösen

kdeglobals_path = os.path.expanduser('~/.config/kdeglobals')

MichaelM
Geschrieben von MichaelM am 16. November 2024 um 14:25

Sorry, habe das mit Markdown nicht beachtet: # Pfad zu ~/.config/kdeglobals auflösen kdeglobals_path = os.path.expanduser('~/.config/kdeglobals')

Actionschnitzel
Geschrieben von Actionschnitzel am 17. November 2024 um 18:53

Setze mich die Tage dran. Ich teste das ja auf einem Kubuntu und hatte das Problem nicht. Es ist aber auch wieder ein gutes Beispiel dafür, an was man alles denken und was man beachten muss.

Frank B
Geschrieben von Frank B am 20. November 2024 um 16:57

Kreadconfig6 klappt bei mir auch, wenn man den --file Parameter ganz weglässt. Dann wird laut help der Standard genommen: --file Use instead of global config weiter habe ich den code durch pylint laufen lassen und die meisten Warnungen beseitigt, sowie Tuxedo als distro aufgenommen u.a. Ich kann meine Version hochladen (wohin?) falls gewünscht.

Actionschnitzel
Geschrieben von Actionschnitzel am 20. November 2024 um 19:45

Du kannst das Repo klonen, die Modifikationen in einen neuen Ordner ("Teil-14-Frank-B") packen und einen Merge-Request anleiern. Ich gucke mir das gerne an.

Frank B
Geschrieben von Frank B am 22. November 2024 um 15:05

Wenn das so einfach wäre, ich scheitere an github.
clone, branch erstellt, die beiden Dateien lokal commited, ein merge sagt aktuell.
account auf github angelegt, git push versucht, authentifizierung fehlgeschlagen. und nun?

David
Geschrieben von David am 24. November 2024 um 01:35

In der Funktion get_distro_logo() könnte man pattern matching machen: https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching

Actionschnitzel
Geschrieben von Actionschnitzel am 24. November 2024 um 02:02

Ja, das ist eine gute Idee. Kommt auf die Liste!

Frank B
Geschrieben von Frank B am 24. November 2024 um 11:18

Oder wie Claudia schon zu Teil 3 oder 4 schrieb,

Setzt das korrekte Logo für die Distro

def get_distro_logo(distro_name): """ Distro Logo wird automatisch ausgewählt wenn der Name in images 'passt'

distro_logo = tk.PhotoImage(file=&quot;images/test.png&quot;)
arch_logo = tk.PhotoImage(file=&quot;images/arch_logo_350.png&quot;)
debian_logo = tk.PhotoImage(file=&quot;images/debian_logo_350.png&quot;)
mint_logo = tk.PhotoImage(file=&quot;images/mint_logo_350.png&quot;)
suse_logo = tk.PhotoImage(file=&quot;images/osuse_logo_350.png&quot;)
ubuntu_logo = tk.PhotoImage(file=&quot;images/ubuntu_logo_350.png&quot;)
fedora_logo = tk.PhotoImage(file=&quot;images/fedora_logo_350.png&quot;)
tuxedo_logo = tk.PhotoImage(file=&quot;images/tuxedo_logo_350.png&quot;)

&quot;&quot;&quot;
image_path = f&quot;images/{distro_name}_logo_350.png&quot;
if not os.path.isfile(image_path):
    image_path=&quot;images/test.png&quot;
return image_path

Und am Ende (nach label erzeugen) das image setzen.

Frank B
Geschrieben von Frank B am 24. November 2024 um 17:04

Korrektur für die verhunzten Quotes und deshalb ohne die Kommentarzeilen:

def get_distro_logo(distro_name): image_path = f"images/{distro_name}_logo_350.png" if not os.path.isfile(image_path): image_path=f"mages/test.png" return image_path

Micha
Geschrieben von Micha am 3. Dezember 2024 um 21:11

Vielen Dank für die tolle Arbeit. Mir gefällt die Artikel-Serie sehr.

Vor kurzen habe ich diesen Artikel gesehen: s3n🧩tube: Systeminfos auf einen Blick | Nachfolger für neofetch https://s3nnet.de/s3ntube-systeminfos-auf-einen-blick-or-nachfolger-fuer-neofetch/

Ist der bekannt? Da lässt sich doch bestimmt das ein oder andere übernehmen.