#! /usr/bin/python3 import os import subprocess import sys import tkinter as tk from tkinter import messagebox import time # imporrt the standard to read and write config files import configparser #list of required modules modules=list() try: import vlc except: modules.append("python-vlc") try: import PIL except: modules.append('pillow') try: import requests except: modules.append('requests') master=None #when setup can't find the conf file, inited is set to True inited = False #URL for the Intro-Screens, use as default, if conf is inited introURL="https://wolke.netzbegruenung.de/s/2TPGWN5FtWYy2d8/download" #set "dirty" flag to False #any change should set it to True, so we can ask on quit to save data isDirty=False # try: # import crontab # except: # modules.append("python-crontab") def createAutostart(): ''' create the auto start file gruene_signale.desktop in $HOME/.config/autostart/ also create intermediate paths if the do not exist when the file already exists, nothing is done ''' autostartfile = os.path.expanduser("~/.config/autostart/gruene_signale.desktop") whereami = os.path.dirname(os.path.realpath(__file__)) contents = """[Desktop Entry] Type=Application Name=Green Signals Autostart Comment=Starten der Slideshow von Gruene Signale NoDisplay=false Exec=sh -c 'cd %(path)s && %(path)s/venv/bin/python3 gruene_signale.py' """ if os.path.isfile(autostartfile) == False: if os.path.exists(os.path.dirname(autostartfile)) == False: os.makedirs(os.path.dirname(autostartfile)) file=open(autostartfile,"w") file.write(contents%{'path':whereami}) file.close() def removeAutostart(): ''' check if the auto start file exists, and if it does, remove it ''' autostartfile = os.path.expanduser("~/.config/autostart/gruene_signale.desktop") if os.path.isfile(autostartfile) == True: os.remove(autostartfile) def checkAutostartfile(): ''' check if the auto start file gruene_signale.desktop already exists returns True, if it does, False if not ''' autostartfile = os.path.expanduser("~/.config/autostart/gruene_signale.desktop") return int(os.path.isfile(autostartfile)) def installModules(): global modules failed=list() for module in modules: try: #subprocess.check_call(["sleep", "5"]) subprocess.check_call([sys.executable, "-m", "pip", "install", module]) except: print("install module %s failed" % module) failed.append(module) modules=failed class Popup(tk.Toplevel): """modal window requires a master""" def __init__(self, master, **kwargs): geo = '%(w)dx%(h)d+%(x)d+%(y)d' % {'x':master.winfo_x()+4,'y':master.winfo_y()+4,'w':master.winfo_width()-8,'h':master.winfo_height()-8} print(geo) tk.Toplevel.__init__(self, master, **kwargs) self.overrideredirect(True) self.geometry(geo) # set the position and size of the popup self.configure(bg="DarkSeaGreen1") lbl = tk.Label(self, text="Module werden installiert.",bg="DarkSeaGreen1") lbl.place(relx=.5, rely=.5, anchor='c') # The following commands keep the popup on top. # Remove these if you want a program with 2 responding windows. # These commands must be at the end of __init__ self.transient(master) # set to be on top of the main window self.grab_set() # hijack all commands from the master (clicks on the main window are ignored) def open_popup(): if master != None: print("OPEN POPUP") master.popup = Popup(master) master.update() def close_popup(): if master != None: master.popup.destroy() master.update() def installModulesGUI(i=0,event=None): global modules open_popup() installModules() close_popup() if len(modules)==0: master.installButton.configure(bg="pale green") master.installButton.configure(state='disabled') master.installButton.configure(text='bereits installiert.') def validateTimeFields(input,newchar,action,name): if action == "focusout": if ":" in input: hour=int(input.split(":")[0]) minute=int(input.split(":")[1]) if hour < 0 or hour > 23 or minute < 0 or minute > 59: messagebox.showerror("Eingabefehler", "Bitte geben sie eine gueltige Uhrzeit ein oder die Anzahl der Stunden mit vorangestelltem -") master.nametowidget(name).focus() return False else: print(name) if ".start" in name: energyStart.set("%(h)d:%(m)02d" % {'h':hour, 'm':minute}) elif ".stop" in name: energyStop.set("%(h)d:%(m)02d" % {'h':hour, 'm':minute}) return True if newchar == "-" and len(input) < 2: setDirty() return True elif len(input)>0 and input[0] == "-" and newchar in "0123456789": setDirty() return True elif newchar not in '01234567890:.': return False if action != "forced": setDirty() return True; def setDirty(action=None, ign=None): #this is called for buttons etc. global isDirty #print("setDirty() called", sys._getframe().f_back.f_code.co_name, "###", action, "###", ign) isDirty = True return True def setEntryDirty(action=None, ign=None): #this gets called global isDirty #print("setDirty() called", sys._getframe().f_back.f_code.co_name, "###", action, "###", ign) if action != None and int(action) >= 0: isDirty = True return True def readConfig(configFile=None): global bild_dauer, DEBUG_PREVIEW, localPath, remoteURL, isDirty, inited global energyStart, energyStop, energyMode if configFile == None: configFile = os.path.dirname(os.path.realpath(__file__)) + os.sep + "gruene_signale.conf" if os.path.exists(configFile) == False: inited = True config = configparser.ConfigParser() config.read(configFile) #read bild - dauer try: bild_dauer.set(int(config.get('bilder', 'dauer'))) except: bild_dauer.set(10) isDirty = True #read debug - preview try: val = config.get('debug', 'preview') except: val = "0" if val == "1": DEBUG_PREVIEW.set(1) #read pfad - lokal try: val = os.path.realpath(config.get('pfad', 'lokal')) except: val = os.path.dirname(os.path.realpath(__file__)) + os.sep + "slideshow" localPath.set(val) #try to read the remote URL, if not given, work offline try: val = config.get('pfad', 'remote') remoteURL.set(val) except: val = None remoteURL.set("") #read energ - mode try: energyMode.set(int(config.get('energy','mode'))) except: energyMode.set(0) #read energy - start try: val = config.get('energy', 'start') except: val = "" energyStart.set(val) #read energy - stop try: val = config.get('energy', 'stop') except: val = "" energyStop.set(val) def writeConfig(configFile=None): if configFile == None: configFile = os.path.dirname(os.path.realpath(__file__)) + os.sep + "gruene_signale.conf" config = configparser.ConfigParser() config.read(configFile) required_sections = ['bilder','filme','pfad','debug','energy'] for sect in required_sections: if sect not in config.sections(): config.add_section( sect ) config.set('bilder','dauer',"%d" % bild_dauer.get()) config.set('debug', 'preview',"%d" % DEBUG_PREVIEW.get()) config.set('pfad', 'lokal',localPath.get().strip()) config.set('pfad', 'remote',remoteURL.get().strip()) config.set('energy', 'mode', "%d" % energyMode.get()) config.set('energy', 'start', energyStart.get()) config.set('energy', 'stop', energyStop.get()) with open(configFile, 'w') as file: config.write(file) def save(): global isDirty,inited if isDirty == False: return; writeConfig() if checkAutostartfile() == 1 and doAutostart.get() == 0: removeAutostart() if checkAutostartfile() == 0 and doAutostart.get() == 1: createAutostart() isDirty=False inited=False def quit(): if isDirty == True: result = messagebox.askyesno("ungesicherte Daten","Sollen die Einstellungen vor dem Beenden gespeichert werden?") if result: save() master.destroy() exit(0) def buildGUI_1(dx, dy): #add button or notification if Python modules (see above) needs to be installed row=tk.Frame(master,bd=1,relief=tk.SUNKEN) lab=tk.Label(row,text="Python Module",width=30,anchor='w') if len(modules) == 0: obj=tk.Button(row,text="bereits installiert",bg="pale green",state='disabled') else: obj=tk.Button(row,text="Installieren",command=installModulesGUI,fg="red") master.installButton = obj row.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) def buildGUI_2(dx, dy): #add autostart checkbox doAutostart.set(checkAutostartfile()) row=tk.Frame(master,bd=1,relief=tk.SUNKEN) lab=tk.Label(row,text="Gruene Signale automatisch starten",width=30,anchor='w') obj=tk.Checkbutton(row,text="aktiv",variable=doAutostart,command=setDirty) row.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) def buildGUI_3(dx, dy, anyEntryCallback): #add entry field for local path row=tk.Frame(master,bd=1,relief=tk.SUNKEN) lab=tk.Label(row,text="lokaler Pfad",width=30,anchor='w') obj=tk.Entry(row,textvariable=localPath,validate="key",validatecommand=(anyEntryCallback, '%d', '%P')) row.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) def buildGUI_4(dx, dy, anyEntryCallback): #add entry field for remoteURL row=tk.Frame(master,bd=1,relief=tk.SUNKEN) lab=tk.Label(row,text="Remote-URL (wenn leer: Offline-Modus)",width=30,anchor='w') if inited == True: remoteURL.set(introURL) obj=tk.Entry(row,textvariable=remoteURL,validate="key",validatecommand=(anyEntryCallback, '%d','%P')) row.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) def buildGUI_5(dx, dy): #add scale widget for duration row=tk.Frame(master,bd=1,relief=tk.SUNKEN) lab=tk.Label(row,text="Anzeigedauer der Bilder (Sekunden)",width=30,anchor='w') obj=tk.Scale(row,from_=5,to=120,variable=bild_dauer,orient=tk.HORIZONTAL,command=setDirty) row.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) def buildGUI_6(dx, dy, timeEntryCallback): #MARK: add controls for energy savings row1=tk.Frame(master,bd=1,relief=tk.SUNKEN) row1.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) row2=tk.Frame(row1,bd=1,relief=tk.FLAT) row2.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) row3=tk.Frame(row1,bd=1,relief=tk.FLAT) row3.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) row4=tk.Frame(row1,bd=1,relief=tk.FLAT) row4.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab=tk.Label(row2,text="Energiesparoptionen",width=30,anchor='w') obj0=tk.Radiobutton(row2,text="ohne",variable=energyMode,command=setDirty,value=0) obj1=tk.Radiobutton(row2,text="RasPi ausschalten",variable=energyMode,command=setDirty,value=1) obj2=tk.Radiobutton(row2,text="Monitor deaktivieren",variable=energyMode,command=setDirty,value=2) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj2.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) obj1.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) obj0.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) lab=tk.Label(row3,text="",width=30,anchor='w') lab.pack(side=tk.LEFT,pady=dy,padx=dx) lab=tk.Label(row3,text="um",width=3,anchor='w') lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj1=tk.Entry(row3,name="start",textvariable=energyStart,validate="all",validatecommand=(timeEntryCallback, '%P','%S', "%V", "%W")) obj1.pack(side=tk.LEFT,fill=tk.X,padx=dx) lab=tk.Label(row3,text="bis",width=3,anchor='w') lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj2=tk.Entry(row3,name="stop",textvariable=energyStop,validate="all",validatecommand=(timeEntryCallback, '%P', '%S', "%V", "%W")) obj2.pack(side=tk.LEFT,fill=tk.X,padx=dx) def buildGUI_7(dx, dy): #add checkbox for Debug Preview Mode row=tk.Frame(master,bd=1,relief=tk.SUNKEN) lab=tk.Label(row,text="Debug Preview Modus",width=30,anchor='w') obj=tk.Checkbutton(row,text="aktiv",variable=DEBUG_PREVIEW,command=setDirty) row.pack(side=tk.TOP,padx=dx,pady=dy,expand=tk.YES,fill=tk.X) lab.pack(side=tk.LEFT,pady=dy,padx=dx) obj.pack(side=tk.RIGHT,expand=tk.YES,fill=tk.X,padx=dx) def buildGUI_8(dx, dy): #add buttons row=tk.Frame(master) saveButton=tk.Button(row,text="Speichern",command=save,pady=4,padx=dx) saveButton.pack(side=tk.LEFT,pady=dy,padx=dx) quitButton=tk.Button(row,text="Beenden",command=quit,pady=4,padx=dx) quitButton.pack(side=tk.RIGHT,pady=dy,padx=dx) row.pack(side=tk.BOTTOM) def buildGUI(dx, dy, timeEntryCallback, anyEntryCallback): buildGUI_1(dx, dy) buildGUI_2(dx, dy) buildGUI_3(dx, dy, anyEntryCallback) buildGUI_4(dx, dy, anyEntryCallback) buildGUI_5(dx, dy) buildGUI_6(dx, dy, timeEntryCallback) buildGUI_7(dx, dy) buildGUI_8(dx, dy) def createVenv(): if sys.prefix == sys.base_prefix: print( "Not running in a virtual environment" ) if not os.path.exists( "./venv/bin/python3" ): splash = tk.Tk() splash.geometry("440x120+660+400") # splash.title("Einstellungen: Gruene Signale") splash.configure(bg="DarkSeaGreen1") splash.overrideredirect(True) label = tk.Label(splash, text="Bitte ein wenig Geduld", bg="DarkSeaGreen1", font=("System", 24)) label.pack(padx=4, pady=4, ipady=4) label2 = tk.Label(splash, text="Es wird eine virtuelle Umgebung angelegt.", bg="DarkSeaGreen1", font=("System", 14)) label2.pack(padx=4, pady=0, ipady=4) splash.update() print( "Create a new virtual environment" ) subprocess.check_call([sys.executable, "-m", "venv", "./venv"] ) splash.destroy() splash.update() print( "Restart in virtual environment" ) p=subprocess.Popen(["./venv/bin/python3", "setup.py"], start_new_session=True ) #p.wait() exit(0) # print( "running in a virtual environment" ) return def main(): global master #, dx, dy createVenv() #init variables used in config file and some other places global doAutostart, bild_dauer, DEBUG_PREVIEW, localPath, remoteURL, energyMode, energyStart, energyStop #the main window try: master=tk.Tk() except: if len(sys.argv)>1: # started from command line with arguments if "modinst" in sys.argv: installModules() print("Die Module wurden erfolgreich installiert.") if "autostart" in sys.argv: createAutostart() print("Das Autostart-Objekt wurde erstellt.") if "noautostart" in sys.argv: removeAutostart() print("Das Autostart-Objekt wurde entfernt.") else: print("Es ist ein Tcl-Fehler aufgetreten. Das Skript muss im Desktop-Modus gestartet werden.") print("""Folgende Kommandos sind erlaubt: modinst: wenn notwendig werden fehlende Python Module installiert autostart: Das Autostart-Objekt wird geschrieben noautostart: Das Autostart-Objekt wird entfernt Die weiteren Enstellungen koennen in der Datei gruene-signal.conf angepasst werden.""") exit(0) doAutostart=tk.IntVar() bild_dauer=tk.IntVar() DEBUG_PREVIEW=tk.IntVar() localPath=tk.StringVar() remoteURL=tk.StringVar() energyMode=tk.IntVar() energyStart=tk.StringVar() energyStop=tk.StringVar() readConfig() master.minsize(600,300) master.geometry("800x450+560+300") master.title("Einstellungen: Gruene Signale") master.option_add('*Dialog.msg.font', 'System 10') #register callbacks for entry fields timeEntryCallback = master.register(validateTimeFields) anyEntryCallback = master.register(setEntryDirty) #fixed value for pady and padx used by pack() function dy=2 dx=4 buildGUI(dx, dy, timeEntryCallback, anyEntryCallback) master.mainloop() if __name__ == "__main__": main()