Python speichert (aktualisiert) Bild aller 5 Sekunden, Windows unterbindet dies nach ein paar Minuten

_anonymous0815_

Lt. Commander
Registriert
Aug. 2020
Beiträge
1.307
Hallo liebes Forum,

seit ein paar Wochen lerne ich Python und habe mich für ein erstes, für mich reales Projekt ins Auge gefasst, was relativ trivial ist.
Ich habe einen Laptop mit Windows 11 und vermisse eine Akkuanzeige in Prozent im Benachrichtungsfeld, erst der Hover des Akkusymbols offenbart den Wert, ich möchte diesen direkt als Icon ausgeben.

Das sieht dann so aus:
1666023965966.png


Ich poste nachfolgend mal die knapp 60 Zeilen Code:
Python:
from psutil import sensors_battery
from time import sleep
from PIL import Image, ImageDraw, ImageFont
from sys import exit
from infi.systray import SysTrayIcon
from os import getcwd
import pkg_resources

basedir=str(getcwd())+'\\icon.ico'
batterie_prozent=str(sensors_battery().percent)
fontname = "arial.ttf"
iconsize = 256
colorText='white'

class tray_icon_controller:

    def __init__(self, basedir, batterie_prozent):
        self.menu_options = None
        self.systray = SysTrayIcon(basedir, "Batterieladung in Prozent {}%".format(batterie_prozent), self.menu_options, on_quit=self.close())
        self.systray.start()

    def update(self, basedir, batterie_prozent):
        self.systray.update(basedir, "Batterieladung in Prozent {}%".format(batterie_prozent))

    def close(systray):
        SysTrayIcon.shutdown 

def color(batterie_prozent):
    if int(batterie_prozent) >= 80:
        return 'lightgreen'
    elif int(batterie_prozent) >= 30 and int(batterie_prozent) < 80:
        return 'yellow'
    elif int(batterie_prozent) >= 10 and int(batterie_prozent) < 30:
        return 'orange'
    elif int(batterie_prozent) < 10:
        return 'red'
    else:
        return 'white'

def image(fontname, fontsize, text, colorText, iconsize, basedir ):
    if int(batterie_prozent) == 100:
        fontsize=155
    font = ImageFont.truetype(fontname, fontsize)
    img = Image.new('RGBA', (iconsize, iconsize), color=(255,255,255,0))
    d = ImageDraw.Draw(img)
    d.text((0,0), text, fill=colorText, font=font)
    img.save(basedir, sizes=[(iconsize,iconsize)])
    
icon=tray_icon_controller(basedir, batterie_prozent)

while True:
    batterie_prozent=str(sensors_battery().percent)
    fontsize = 230
    text = batterie_prozent
    image(fontnageme, fontsize, text, color(batterie_prozent), iconsize, basedir)
    icon.update(basedir, batterie_prozent)
    if icon.close:
        exit()
    sleep(5)

Das Problem auf das ich stoße, ist, dass nach ein paar Minuten eine Exception beim Debuggen geworfen wird, dass der Zugriff auf das icon.ico verweigert wird. Ich vermute, dass das passiert, weil zu häufig (aller 5 Sekunden) eine Speicher-Operation ausgeführt wird und suche nach einem Weg, dies gerade zu rücken.
Mir geht es hier explizit nicht um eine Bewertung des Codes, sicher lässt sich dieser besser strukturieren, aber das ist gerade für mich nebensächlich.
Ich bin für jeden Lösungsvorschlag dankbar. :)
Ergänzung ()

Update: Ich glaube der Fehler liegt woanders, ich habe den Code vor kurzem umstrukturiert und es scheint wohl eher damit ein Problem zu geben, denn beim aktuellen Debugging kommt keine Exception, einer Aktualisierung des Icons findet aber nicht statt.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: kuddlmuddl
madmax2010 schrieb:
dann nenn doch mal die fehlermeldung..
Vorführeffekt, ich setze Sleep auf 0.01 und selbst dann wirft er nicht den Fehler.
Aber ich habe ein weiteres Problem ausfindig gemacht.
Python:
if icon.close:
        exit()
hält die Schleife auf, aber mein Wissen reicht irgendwie nicht aus, um das Programm mit "Quit"
1666025297053.png

zu beenden, ich will ja nicht nur das Icon tray beenden, sondern das ganze Programm
Ergänzung ()

Code:
Ausnahme: PermissionError
[Errno 13] Permission denied: 'C:\\Users\\...\\Documents\\Python\\icon.ico'
  File "C:\Users\...\Documents\Python\batterie.py", line 50, in image
    img.save(basedir, sizes=[(iconsize,iconsize)])
  File "C:\Users\...\Documents\Python\batterie.py", line 59, in <module>
    image(fontname, fontsize, text, color(batterie_prozent), iconsize, basedir)
Da hätten wir den ursprünglichen Fehler, der anscheinend gar nicht das Problem war.
 
na, vielleicht schon.
Keine ahnung, wie windows user und permission handling im Detail macht, aber schau mal welche user welche Berechtigungen habe und als welcher user dein Code laeuft.
Es kann auch sein, dass die Datei kurz gesperrt wird und das Programm sich sozusagen "Selbst überholt"
 
_anonymous0815_ schrieb:
Python:
def close(systray):
    SysTrayIcon.shutdown
das muss doch ganz sicher SysTrayIcon.shutdown() heissen, also mit klammern. und anstatt das bild auf der platte zu speichern, würde ich es im speicher lassen. dafür bietet sich BytesIO an. dann gibt es auch keine zugriffsprobleme, falls das ein problem sein sollte.
 
  • Gefällt mir
Reaktionen: madmax2010
0x8100 schrieb:
das muss doch ganz sicher SysTrayIcon.shutdown() heissen
Und genau da hört mein geringes Wissen schon auf:
1666025847534.png
1666025892308.png

Ergänzung ()

Ich wollte unbedingt mal etwas mit Klassen versuchen, vielleicht sollte ich das erst mal verwerfen.
Ergänzung ()

https://stackoverflow.com/a/62528693
Habe mich nach der Lösung gehalten, er schreibts ohne ()
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Fortatus
Probier stattdessen mal
Python:
self.systray.shutdown()

(Vorausgesetzt shutdown() ist dir richtige Funktion)
 
0x8100 schrieb:
probier mal
Python:
def close(systray):
self.systray.shutdown()
Von systray.shutdown bin ich weg, da dabei immer eine Exception geworfen wurde. Ich würde die dann noch nachreichen, wenn ich wieder am Rechner sitze.
Ergänzung ()

1666032352525.png

1666032391223.png

Ergänzung ()

das
Python:
das # type: ignore
in #28 stammt noch von
Python:
def close():
    SysTrayIcon.shutdown
Weil dann diese Meldung aufkam:
1666032834525.png
 
Zuletzt bearbeitet:
ehrlich gesagt versteh ich gerade nicht, wie die ihr on_quit haben wollen, daher habe ich selbst was gebaut :) beim beenden des systrays wird der message_loop_thread von infi.systray beendet, das habe ich als trigger genommen, um auch das hauptprogramm zu beenden.
Python:
import time
import os
from psutil import sensors_battery
from PIL import Image, ImageDraw, ImageFont
from infi.systray import SysTrayIcon


class MySysTrayIcon(SysTrayIcon):

    def is_running(self):
        return self._message_loop_thread.is_alive()


class TrayIcon(object):

    def __init__(self):

        self.icon_path = os.path.join(os.path.dirname(__file__), "icon.ico")
        self.battery_percent = self.get_battery_percent()
        self.create_icon()
        self.menu_options = None

        self.systray = MySysTrayIcon(
            self.icon_path,
            self.battery_percent,
            self.menu_options,
        )

        self.update()
        self.systray.start()

    @staticmethod
    def get_battery_percent():
        return sensors_battery().percent

    def get_color_by_battery_percentage(self):
        if self.battery_percent >= 80:
            return 'lightgreen'
        elif 30 <= self.battery_percent < 80:
            return 'yellow'
        elif 10 <= self.battery_percent < 30:
            return 'orange'
        elif self.battery_percent < 10:
            return 'red'
        else:
            return 'white'

    def create_icon(self):

        icon_size = 256
        fontname = "arial.ttf"
        color = self.get_color_by_battery_percentage()
        text = str(self.battery_percent)
        fontsize = 155 if self.battery_percent == 100 else 230

        font = ImageFont.truetype(fontname, fontsize)
        img = Image.new('RGBA', (icon_size, icon_size), color=(255, 255, 255, 0))
        d = ImageDraw.Draw(img)
        d.text((0, 0), text, fill=color, font=font)
        img.save(self.icon_path, sizes=[(icon_size, icon_size)])

    def update(self):
        self.battery_percent = self.get_battery_percent()
        self.create_icon()
        self.systray.update(self.icon_path, str(self.battery_percent))


if __name__ == '__main__':
    tray = TrayIcon()

    while tray.systray.is_running():
        tray.update()
        time.sleep(5)

das mit bild im ram geht nicht, da infi.systray schlussendlich irgendeine windows-funktion zum laden des bildes aufruft, die dann einen pfad haben will.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Bonanca und _anonymous0815_
Zurück
Oben