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