3 months ago

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



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

Frisch ans Werk. Zuletzt haben wir uns mit dem Styling beschäftigt. Was wir gelernt haben, war aber nur die Spitze des Eisbergs. Fürs Erste soll uns dieser Wissensstand aber reichen.

Heute soll es um das Auslesen des Window-Managers gehen, ich muss aber weit ausholen: Wir benötigen das Tool wmctrl aus unserem Repository.

Das Tool lässt sich über das Terminal mit bash ansteuern. Soweit so gut, wir lernen hier aber Python.

sudo apt install wmctrl -m Show information about the window manager and about the environment.

Beispiel: Gnome

~$ wmctrl -m Name: GNOME Shell Class: N/A PID: N/A Window manager's "showing the desktop" mode: OFF

Hier besagt der Name, dass es sich um GNOME Shell handel. Wir wissen aber natürlich, dass der Window-Manager MUTTER heißt. Darum kümmern wir uns auch noch.

Beispiel: XFCE

~$ wmctrl -m Name: Xfwm4 Class: xfwm4 PID: 2043 Window manager's "showing the desktop" mode: OFF

Dieses kleine Problem bringt uns zu unserer nächsten Übung.

System-Befehle in Python ausführen

Es gibt hier mehrerer Herangehensweisen, wobei die ersten beiden also veraltet gelten. Dennoch sind sie gut um ein Verständnis für die Funktionsweise zu erlangen.

Es handelt sich hier um Übungen, die so nicht im eigentlichen Tool vorkommen werden. Legt euch also jeweils Beispiel-Skripte an, z.B.: os_system_test.py

os.system

https://docs.python.org/3/library/os.html#os.system

import os os.system("x-terminal-emulator")

Wenn alles gut geht, sollte sich der Default-Terminal-Emulator öffnen und wir haben somit das System angesteuert. os.system hat aber leider einen kleinen Nachteil. Es blockiert den Loop und erst, wenn der angesteuerte Prozess beendet ist, geht es weiter. Um das zu veranschaulichen, machen wir Folgendes:

import os os.system("x-terminal-emulator") print("Hallo") os.system("x-terminal-emulator") print("Hallo")

Ihr werdet merken, dass der Print-Befehl erst ausgegeben wird, wenn Ihr das Terminal-Fenster geschlossen habet. Natürlich können wir hiermit viel mehr anstellen, als nur ein Terminal zu öffnen. Im Grunde können wir innerhalb der Quotes genauso wie in der Bash agieren. 

import os os.system("x-terminal-emulator -e pkexec apt update")

Die Flag `-e` weist hier an, dass in der `bash` des Terminal-Emulators etwas ausgeführt werden soll. Euch sollte auch aufgefallen, dass sich das Terminal-Fenster selbstständig geschlossen hat. Für eine GUI-Anwendung ist das relativ unschön, kann aber auch mit etwas Bash-Voodoo umgangen werden. Das lassen wir aber erstmal außen vor.

Oder:

import os os.system("pkexec apt update")

Bei diesem Beispiel wird der Output direkt über die Python-Konsole abgewickelt.

Wer nicht unbedingt sein Repository auffrischen will, kann natürlich auch `ls` statt `apt update` nehmen.

os.popen

https://docs.python.org/3/library/os.html#os.popen

Diese Methode funktioniert ähnlich, ist aber flexibler.

import os os.popen("x-terminal-emulator") print("Hallo") os.popen("x-terminal-emulator") print("Hallo")

Wenn Ihr denn Code startet, solltet alle Aktionen sofort hintereinander ohne Interaktion ausgeführt werden.

import os output = os.popen("ls").read() print(f"Output:{output}")

Logik:

  • Die Variable `output` hält `os.popen("ls").read()`.
  • `.read()` least den Inhalt aus.
  • über `print` lässt sich der Inhalt der Variable auslesen.

subprocess.run

https://docs.python.org/3/library/subprocess.html#subprocess.run

Wesentlich flexibler, auskunftsfreudiger, aber auch komplizierter ist das Modul `subprocess.run`. 

import subprocess result = subprocess.run(["ls"], capture_output=True, text=True) print("Erfolg?:", result.returncode) print("Ausgabe:", result.stdout)

Auf wmctrl bezogen bedeutet das für uns:

import subprocess result = subprocess.run(["wmctrl","-m"], capture_output=True, text=True) print("Erfolg?:", result.returncode) print("Ausgabe:", result.stdout)

Ausgabe:

>>> %Run -c $EDITOR_CONTENT Erfolg?: 0 Ausgabe: Name: GNOME Shell Class: N/A PID: N/A Window manager's "showing the desktop" mode: OFF

Das schöne hierbei ist, dass mit .returncode können wir den Status auslesen. Wir haben hier eine nicht-interaktive Applikation, die im Grunde fehlerfrei laufen sollte. Nehmen wir mal an, wir wollen ein Update durchführen und es existiert keine Internetverbindung. Ist dem so würde der Fehler-Code 1 ausgegeben. Um dem Nutzer rückzumelden, dass etwas schiefgelaufen ist, könnten wir nun einbauen, dass bei der Ausgabe ein Pop-up-Fenster geöffnet werden soll, welches den Nutzer darauf hinweist.

Einbau in unseren Code

import subprocess def get_window_manager_name(): result = subprocess.run(["wmctrl", "-m"], capture_output=True, text=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

Logik:

  • ["wmctrl", "-m"]- Jedes Segment wird in Anführungszeichen gesetzt und durch Kommata getrennt.
  • Mit capture_output=True wird ausgesagt, dass der Output erfasst werden soll.
  • text=True legt fest, dass der Output als String ausgegeben wird.
  • Über .stdout wird sich der Output geholt.
  • strip() entfernt führende und nachfolgende Leerzeichen.
  • Durch split("\n") werden Absätze zu Listen umgewandelt.
  • for line in output_lines: Für jetze ausgelesene Zeile.
  • if line.startswith("Name: ") prüft, ob eine Zeile mit "Name:" beginnt.
  • Mit line.split("Name: ")[1] wird die Zeile nach "Name: " geteilt.
  • Wenn der Output "GNOME Shell" soll MUTTER ausgegeben werden.
  • Ansonsten wird das Ergebnis normal zurückgegeben.

Der ganze Code

## Teil 10 ### 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 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 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 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()

Wie angedroht wird es komplizierter... Bis nächste Woche ;-)

Alles auf Github


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