tk_main_menue
1# Standardbibliothek 2import platform 3import sys 4import tempfile 5import webbrowser 6from datetime import datetime, timedelta 7from pathlib import Path 8 9# Drittanbieter-Bibliotheken 10import tkinter as tk 11from tkinter import messagebox, scrolledtext 12 13import ttkbootstrap as ttk 14from ttkbootstrap.dialogs import Messagebox 15from ttkbootstrap.scrolled import ScrolledText 16 17 18# Eigene Module 19from jamfscripts import * # besser: gezielte Funktionen importieren 20from tk_jamf_login import JamfLogin 21 22 23JAMF_URL = "" 24TOKEN = "" 25#ZUSTIMMUNGSDATEI = os.path.join(os.getcwd(), "zustimmung.json") 26#NUTZUNGSDATEI = os.path.join(os.getcwd(), "nutzung.json") 27config_dir = user_data_dir("Classload", "chribdo") 28os.makedirs(config_dir, exist_ok=True) 29 30ZUSTIMMUNGSDATEI = os.path.join(config_dir, "zustimmung.json") 31NUTZUNGSDATEI = os.path.join(config_dir, "nutzung.json") 32 33def get_resource_path(filename): 34 """ 35 Gibt den Pfad zur Datei zurück – funktioniert mit PyInstaller, py2app und lokal. 36 """ 37 if hasattr(sys, '_MEIPASS'): 38 return os.path.join(sys._MEIPASS, filename) 39 elif hasattr(sys, 'frozen') and 'RESOURCEPATH' in os.environ: 40 return os.path.join(os.environ['RESOURCEPATH'], filename) 41 else: 42 # Lokaler Entwicklungsmodus – Bezug relativ zur Python-Datei 43 base_path = Path(__file__).resolve().parent 44 return str(base_path / filename) 45 46LIZENZ = get_resource_path("LICENSE.txt") 47 48def init_dpi_awareness(): 49 """ 50 Aktiviert DPI-Awareness unter Windows, um eine scharfe und korrekt skalierte 51 Darstellung in tkinter/ttkbootstrap-Fenstern zu gewährleisten. 52 Hat keinen Effekt auf macOS oder Linux. 53 """ 54 if sys.platform.startswith("win"): 55 try: 56 import ctypes 57 # 1 = system DPI aware, 2 = per-monitor DPI aware 58 ctypes.windll.shcore.SetProcessDpiAwareness(1) 59 except Exception: 60 pass 61 62def set_window_icon(widget): 63 """setzt ein icon-Window. Wird aktuell nicht verwendet.""" 64 try: 65 if sys.platform == "darwin": 66 # macOS: kleineres Icon verwenden, sonst kommt "path is bad" 67 icon_relpath = os.path.join("assets", "icon_small.png") 68 else: 69 # Windows/Linux: großes Icon mit Alpha verwenden 70 icon_relpath = os.path.join("assets", "icon.png") 71 72 icon_path = get_resource_path(icon_relpath) 73 print(f"🖼 Verwende Icon: {icon_path}") 74 75 icon_img = tk.PhotoImage(file=icon_path) 76 widget.iconphoto(True, icon_img) 77 widget.icon_img = icon_img # Referenz halten! 78 except Exception as e: 79 print(f"⚠️ Icon konnte nicht gesetzt werden: {e}") 80 81def lade_nutzungsinfo(): 82 """öffnet die Nutzungsdatei, sofern vorhanden.""" 83 if os.path.exists(NUTZUNGSDATEI): 84 with open(NUTZUNGSDATEI, "r") as f: 85 return json.load(f) 86 return {} 87 88def speichere_nutzungsinfo(info): 89 """speichert die Art der Nutzung(privat/gewerblich) in einer.json-Datei""" 90 with open(NUTZUNGSDATEI, "w") as f: 91 json.dump(info, f) 92 93def pruefe_testversion(root, verbleibend): 94 """Prüft ob die gewerbliche Lizenz noch gültig ist. Wenn ja wird nach Bestätigung das Login-Fenster Jamf-Login aufgerufen.""" 95 root.deiconify() 96 #root.geometry("400x300") # Fenstergröße setzen 97 root.minsize(400, 250) 98 if verbleibend.days < 0: 99 def beenden(): 100 root.destroy() 101 sys.exit() 102 103 frame = ttk.Frame(root, padding=30) 104 frame.pack(expand=True) 105 label = ttk.Label(frame, 106 text="❌ Die 7-Tage-Testversion ist abgelaufen.\nBitte kontaktieren Sie den Entwickler für eine Lizenz.", 107 font=("Arial", 12), justify="center") 108 label.pack() 109 root.after(5000, beenden) # Automatisch schließen & Programm beenden 110 else: 111 def weiter(): 112 JamfLogin(root) 113 hinweis.destroy() 114 root.withdraw() 115 116 hinweis = ttk.Frame(root, padding=20) 117 hinweis.place(relx=0.5, rely=0.5, anchor="center") 118 119 label = ttk.Label(hinweis, text=f"✔ Testversion aktiv\nNoch {verbleibend.days + 1} Tage verfügbar", font=("Arial", 11)) 120 label.pack(pady=(0, 10)) 121 122 btn = ttk.Button(hinweis, text="OK", command=weiter) 123 btn.pack() 124 root.update_idletasks() 125 root.minsize(root.winfo_width(), root.winfo_height()) 126 127def pruefe_nutzungsart(root): 128 """ 129 Die Nutzungsart wird überprüft. 130 Sie wird abgespeichert, falls sie noch nicht abgespeichert ist, sonst wird sie gelesen. 131 Für gewerbliche Nutzung wird die verbleibende Nutzungszeit geprüft und ggf. weitergegeben. 132 Bei privater Nutzung wird direkt das Login-Fenster JamfLogin geöffnet. 133 """ 134 info = lade_nutzungsinfo() 135 if "nutzung" not in info: 136 nutzungsart = zeige_nutzungsdialog(root) 137 if not nutzungsart: 138 Messagebox.ok(title="Abbruch", message="Nutzungstyp nicht festgelegt. Programm wird beendet.", alert=True) 139 sys.exit() 140 nutzungsart = nutzungsart.strip().lower() 141 info["nutzung"] = nutzungsart 142 if nutzungsart == "gewerblich": 143 info["startdatum"] = datetime.today().strftime("%Y-%m-%d") 144 speichere_nutzungsinfo(info) 145 elif info["nutzung"] == "privat": 146 JamfLogin(root) 147 elif info["nutzung"] == "gewerblich": 148 startdatum = datetime.strptime(info["startdatum"], "%Y-%m-%d") 149 verbleibend = (startdatum + timedelta(days=7)) - datetime.today() 150 """ 151 if verbleibend.days < 0: 152 Messagebox.ok(title="Testzeitraum abgelaufen", 153 message="Die 7-Tage-Testversion ist abgelaufen. Bitte kontaktieren Sie den Entwickler für eine Lizenz.", 154 alert=True) 155 sys.exit() 156 else: 157 Messagebox.ok(title="Testversion", 158 message=f"Testversion aktiv. Noch {verbleibend.days + 1} Tage verfügbar.", alert=False) 159 """ 160 pruefe_testversion(root, verbleibend) 161 162 163def zeige_lizenz(): 164 """Die Lizenz wird einfach angezeigt. Wird aus dem Menü aufgerufen.""" 165 if not os.path.exists(LIZENZ): 166 Messagebox.ok(title="Lizenz", message="LICENSE.txt nicht gefunden.", alert=False) 167 return 168 lizfenster = tk.Toplevel() 169 #set_window_icon(lizfenster) 170 lizfenster.title("Lizenz") 171 lizfenster.geometry("600x500") 172 textfeld = scrolledtext.ScrolledText(lizfenster, wrap="word") 173 with open(LIZENZ, "r", encoding="utf-8") as f: 174 textfeld.insert("1.0", f.read()) 175 textfeld.config(state="disabled") 176 lizfenster.update_idletasks() 177 lizfenster.minsize(lizfenster.winfo_width(), lizfenster.winfo_height()) 178 textfeld.pack(fill="both", expand=True) 179 180def zeige_nutzungsdialog(root): 181 """Die Art der Nutzung muss ausgewählt und bestätigt werden. Wird nur bei der Erstnutzung aufgerufen.""" 182 auswahlfenster = tk.Toplevel() 183 auswahlfenster.title("Nutzungsart wählen") 184 auswahlfenster.geometry("500x300") 185 auswahlfenster.grab_set() 186 auswahlfenster.resizable(False, True) 187 auswahlfenster.update_idletasks() 188 auswahlfenster.minsize(auswahlfenster.winfo_width(), auswahlfenster.winfo_height()) 189 190 auswahl = tk.StringVar() 191 auswahl.set("privat") 192 193 def bestätigen(): 194 JamfLogin(root) 195 auswahlfenster.destroy() 196 197 label = ttk.Label(auswahlfenster, text="Bitte wählen Sie die Art der Nutzung:") 198 label.pack(pady=10) 199 200 r1 = ttk.Radiobutton(auswahlfenster, text="Privat/Schule (dauerhaft erlaubt)", variable=auswahl, value="privat") 201 r2 = ttk.Radiobutton(auswahlfenster, text="Gewerblich/Testversion (7 Tage)", variable=auswahl, value="gewerblich") 202 r1.pack(anchor="w", padx=30, pady=5) 203 r2.pack(anchor="w", padx=30, pady=5) 204 205 button = ttk.Button(auswahlfenster, text="Bestätigen", command=bestätigen) 206 button.pack(pady=20) 207 208 auswahlfenster.wait_window() 209 return auswahl.get() 210 211def zustimmung_bereits_erfolgt(): 212 """prüft, ob die Zustimmung bereits erfolgt ist.""" 213 if os.path.exists(ZUSTIMMUNGSDATEI): 214 try: 215 print(f"✅ Schreiben/Lesen von: {ZUSTIMMUNGSDATEI}") 216 with open(ZUSTIMMUNGSDATEI, "r") as f: 217 data = json.load(f) 218 return data.get("zugestimmt", False) 219 except Exception: 220 return False 221 return False 222 223def speichere_zustimmung(): 224 """speichert die erfolgte Zustimmung in der Zustimmungsdatei (json)""" 225 print(f"✅ Schreiben/Lesen von: {ZUSTIMMUNGSDATEI}") 226 with open(ZUSTIMMUNGSDATEI, "w") as f: 227 json.dump({"zugestimmt": True}, f) 228 229def show_license_dialog(root): 230 """zeigt den Lizenz-Dialog zu Beginn. Man wird aufgefordert, die Lizenz zu bestötigen.""" 231 try: 232 with open(LIZENZ, "r", encoding="utf-8") as f: 233 license_text = f.read() 234 except FileNotFoundError: 235 Messagebox.show_error("LICENSE.txt nicht gefunden.", "Fehler", parent=root) 236 return False 237 238 if sys.platform == "darwin": 239 dialog = ttk.Toplevel(root) 240 else: 241 dialog = ttk.Toplevel(root, iconphoto=None) 242 #set_window_icon(dialog) 243 dialog.title("Lizenzvereinbarung") 244 dialog.minsize(700, 500) 245 dialog.transient(root) 246 dialog.grab_set() 247 248 # Label oben 249 ttk.Label(dialog, text="Bitte lesen Sie die Lizenzbedingungen:", font=("Helvetica", 12)).pack(pady=10) 250 251 # Textbereich in eigenem Frame 252 text_frame = ttk.Frame(dialog) 253 text_frame.pack(fill="both", expand=True, padx=20, pady=10) 254 255 text_area = ScrolledText(text_frame, autohide=True) 256 text_area.pack(fill="both", expand=True) 257 text_area.text.insert("1.0", license_text) 258 text_area.text.configure(state="disabled") 259 260 # Button-Frame 261 button_frame = ttk.Frame(dialog) 262 button_frame.pack(pady=10) 263 264 result = {"accepted": None} 265 266 def agree(): 267 result["accepted"] = True 268 dialog.destroy() 269 270 def disagree(): 271 result["accepted"] = False 272 dialog.destroy() 273 274 ttk.Button(button_frame, text="Ich stimme zu", command=agree, bootstyle="success").pack(side="left", padx=10) 275 ttk.Button(button_frame, text="Ich lehne ab", command=disagree, bootstyle="danger").pack(side="right", padx=10) 276 277 dialog.wait_window() 278 return result["accepted"] 279 280def show_help(): 281 """zeigt die zu hilfe.html umgewandelte README.md im Browser an.""" 282 help_file = get_resource_path("hilfe.html") 283 if not os.path.exists(help_file): 284 print(f"❌ Hilfe-Datei nicht gefunden: {help_file}") 285 return 286 url = f"file://{help_file}" 287 print(f"🌐 Öffne Hilfe: {url}") 288 webbrowser.open(url) 289 290def show_about(): 291 """zeigt die Aboutbox""" 292 # messagebox.showinfo("Über Classload", "Classload\nVersion 1.0\n(c) 2025") 293 messagebox.showinfo("Über Classload","Classload\nzum Austausch von Daten mit Jamf\nVersion 0.9\n(c)2025 Christiane Borchel") 294 295def main(): 296 """ 297 Bei der ersten Nutzung wird die Lizenz angezeigt, die bestätigt werden muss. 298 Danach muss die Nutzungsart (privat/gewerblich) gewählt werden. 299 Bei allen späteren Nutzungen wird zunächst die Nutzungsart geprüft. 300 Falls sie gewerblich ist, wird zunächst angezeigt, wie viele Tage die Lizenz noch gültig ist. 301 Falls sie nicht mehr gültig ist, endet das Programm. Sonst wird nach Bestätigung das Login-Fenster angezeigt. 302 Bei privater/schulischer Nutzung wird direkt das Login-Fenster JAMF-Login angezeigt. 303 """ 304 init_dpi_awareness() 305 if sys.platform == "darwin": 306 root = ttk.Window(themename="cosmo") 307 else: 308 root = ttk.Window(themename="cosmo", iconphoto=None) 309 root.title("Classload") 310 311 root.lift() 312 root.focus_force() 313 root.attributes("-topmost", True) 314 root.after(100, lambda: root.attributes("-topmost", False)) 315 316 base_path = getattr(sys, '_MEIPASS', os.path.dirname(__file__)) 317 # icon_path = os.path.join(base_path, "assets", "icon.png") 318 #icon_img = tk.PhotoImage(file=icon_path) 319 #icon_relpath = os.path.join("assets", "icon.png") # Pfad zusammensetzen 320 if sys.platform == "darwin": 321 # macOS → kleineres, sicheres Icon verwenden 322 icon_relpath = os.path.join("assets", "icon_small.png") 323 else: 324 # Windows/Linux → großes Icon mit Alpha 325 icon_relpath = os.path.join("assets", "icon.png") 326 327 icon_path = get_resource_path(icon_relpath) # Pfad für PyInstaller/py2app auflösen 328 icon_img = tk.PhotoImage(file=icon_path) 329 print("🔍 Icon-Pfad:", icon_path) 330 print("📦 Existiert:", os.path.exists(icon_path)) 331 #root.iconphoto(True, icon_img) 332 #root.icon_img = icon_img 333 if sys.platform.startswith("win"): 334 try: 335 icon_path = os.path.join(base_path, "assets", "icon.ico") 336 root.iconbitmap(icon_path) 337 except Exception as e: 338 print(f"Icon konnte nicht gesetzt werden: {e}") 339 #set_window_icon(root) 340 341 if not zustimmung_bereits_erfolgt(): 342 if not show_license_dialog(root): 343 return 344 speichere_zustimmung() 345 346 menubar = tk.Menu(root) 347 hilfe_menu = tk.Menu(menubar, tearoff=0) 348 hilfe_menu.add_command(label="Lizenz anzeigen", command=zeige_lizenz) 349 hilfe_menu.add_command(label="Hilfe anzeigen", command=lambda: show_help()) 350 351 if platform.system() == "Darwin": 352 apple_menu = tk.Menu(menubar, name="apple", tearoff=0) 353 apple_menu.add_command(label="Über Classload", command=show_about) 354 menubar.add_cascade(menu=apple_menu) 355 menubar.add_cascade(label="Hilfe", menu=hilfe_menu) 356 root["menu"] = menubar 357 else: 358 menubar.add_command(label="Über Classload", command=show_about) 359 menubar.add_cascade(label="Hilfe", menu=hilfe_menu) 360 root.config(menu=menubar) 361 # menubar.add_cascade(label="Hilfe", menu=hilfe_menu) 362 root.config(menu=menubar) 363 364 root.withdraw() # root bleibt im Hintergrund, aber notwendig für Tkinter 365 366 367 pruefe_nutzungsart(root) 368 #login = JamfLogin(root) 369 root.iconphoto(True, icon_img) 370 #root.icon_img = icon_img 371 root.mainloop() 372 373 374if __name__ == "__main__": 375 try: 376 main() 377 except Exception as e: 378 import traceback 379 log_path = os.path.join(tempfile.gettempdir(), "classload_error.log") 380 #log_path = os.path.join(os.path.expanduser("~"), "Desktop", "classload_error.log") 381 382 383 with open(log_path, "w", encoding="utf-8") as f: 384 f.write(traceback.format_exc()) 385 raise
34def get_resource_path(filename): 35 """ 36 Gibt den Pfad zur Datei zurück – funktioniert mit PyInstaller, py2app und lokal. 37 """ 38 if hasattr(sys, '_MEIPASS'): 39 return os.path.join(sys._MEIPASS, filename) 40 elif hasattr(sys, 'frozen') and 'RESOURCEPATH' in os.environ: 41 return os.path.join(os.environ['RESOURCEPATH'], filename) 42 else: 43 # Lokaler Entwicklungsmodus – Bezug relativ zur Python-Datei 44 base_path = Path(__file__).resolve().parent 45 return str(base_path / filename)
Gibt den Pfad zur Datei zurück – funktioniert mit PyInstaller, py2app und lokal.
49def init_dpi_awareness(): 50 """ 51 Aktiviert DPI-Awareness unter Windows, um eine scharfe und korrekt skalierte 52 Darstellung in tkinter/ttkbootstrap-Fenstern zu gewährleisten. 53 Hat keinen Effekt auf macOS oder Linux. 54 """ 55 if sys.platform.startswith("win"): 56 try: 57 import ctypes 58 # 1 = system DPI aware, 2 = per-monitor DPI aware 59 ctypes.windll.shcore.SetProcessDpiAwareness(1) 60 except Exception: 61 pass
Aktiviert DPI-Awareness unter Windows, um eine scharfe und korrekt skalierte Darstellung in tkinter/ttkbootstrap-Fenstern zu gewährleisten. Hat keinen Effekt auf macOS oder Linux.
63def set_window_icon(widget): 64 """setzt ein icon-Window. Wird aktuell nicht verwendet.""" 65 try: 66 if sys.platform == "darwin": 67 # macOS: kleineres Icon verwenden, sonst kommt "path is bad" 68 icon_relpath = os.path.join("assets", "icon_small.png") 69 else: 70 # Windows/Linux: großes Icon mit Alpha verwenden 71 icon_relpath = os.path.join("assets", "icon.png") 72 73 icon_path = get_resource_path(icon_relpath) 74 print(f"🖼 Verwende Icon: {icon_path}") 75 76 icon_img = tk.PhotoImage(file=icon_path) 77 widget.iconphoto(True, icon_img) 78 widget.icon_img = icon_img # Referenz halten! 79 except Exception as e: 80 print(f"⚠️ Icon konnte nicht gesetzt werden: {e}")
setzt ein icon-Window. Wird aktuell nicht verwendet.
82def lade_nutzungsinfo(): 83 """öffnet die Nutzungsdatei, sofern vorhanden.""" 84 if os.path.exists(NUTZUNGSDATEI): 85 with open(NUTZUNGSDATEI, "r") as f: 86 return json.load(f) 87 return {}
öffnet die Nutzungsdatei, sofern vorhanden.
89def speichere_nutzungsinfo(info): 90 """speichert die Art der Nutzung(privat/gewerblich) in einer.json-Datei""" 91 with open(NUTZUNGSDATEI, "w") as f: 92 json.dump(info, f)
speichert die Art der Nutzung(privat/gewerblich) in einer.json-Datei
94def pruefe_testversion(root, verbleibend): 95 """Prüft ob die gewerbliche Lizenz noch gültig ist. Wenn ja wird nach Bestätigung das Login-Fenster Jamf-Login aufgerufen.""" 96 root.deiconify() 97 #root.geometry("400x300") # Fenstergröße setzen 98 root.minsize(400, 250) 99 if verbleibend.days < 0: 100 def beenden(): 101 root.destroy() 102 sys.exit() 103 104 frame = ttk.Frame(root, padding=30) 105 frame.pack(expand=True) 106 label = ttk.Label(frame, 107 text="❌ Die 7-Tage-Testversion ist abgelaufen.\nBitte kontaktieren Sie den Entwickler für eine Lizenz.", 108 font=("Arial", 12), justify="center") 109 label.pack() 110 root.after(5000, beenden) # Automatisch schließen & Programm beenden 111 else: 112 def weiter(): 113 JamfLogin(root) 114 hinweis.destroy() 115 root.withdraw() 116 117 hinweis = ttk.Frame(root, padding=20) 118 hinweis.place(relx=0.5, rely=0.5, anchor="center") 119 120 label = ttk.Label(hinweis, text=f"✔ Testversion aktiv\nNoch {verbleibend.days + 1} Tage verfügbar", font=("Arial", 11)) 121 label.pack(pady=(0, 10)) 122 123 btn = ttk.Button(hinweis, text="OK", command=weiter) 124 btn.pack() 125 root.update_idletasks() 126 root.minsize(root.winfo_width(), root.winfo_height())
Prüft ob die gewerbliche Lizenz noch gültig ist. Wenn ja wird nach Bestätigung das Login-Fenster Jamf-Login aufgerufen.
128def pruefe_nutzungsart(root): 129 """ 130 Die Nutzungsart wird überprüft. 131 Sie wird abgespeichert, falls sie noch nicht abgespeichert ist, sonst wird sie gelesen. 132 Für gewerbliche Nutzung wird die verbleibende Nutzungszeit geprüft und ggf. weitergegeben. 133 Bei privater Nutzung wird direkt das Login-Fenster JamfLogin geöffnet. 134 """ 135 info = lade_nutzungsinfo() 136 if "nutzung" not in info: 137 nutzungsart = zeige_nutzungsdialog(root) 138 if not nutzungsart: 139 Messagebox.ok(title="Abbruch", message="Nutzungstyp nicht festgelegt. Programm wird beendet.", alert=True) 140 sys.exit() 141 nutzungsart = nutzungsart.strip().lower() 142 info["nutzung"] = nutzungsart 143 if nutzungsart == "gewerblich": 144 info["startdatum"] = datetime.today().strftime("%Y-%m-%d") 145 speichere_nutzungsinfo(info) 146 elif info["nutzung"] == "privat": 147 JamfLogin(root) 148 elif info["nutzung"] == "gewerblich": 149 startdatum = datetime.strptime(info["startdatum"], "%Y-%m-%d") 150 verbleibend = (startdatum + timedelta(days=7)) - datetime.today() 151 """ 152 if verbleibend.days < 0: 153 Messagebox.ok(title="Testzeitraum abgelaufen", 154 message="Die 7-Tage-Testversion ist abgelaufen. Bitte kontaktieren Sie den Entwickler für eine Lizenz.", 155 alert=True) 156 sys.exit() 157 else: 158 Messagebox.ok(title="Testversion", 159 message=f"Testversion aktiv. Noch {verbleibend.days + 1} Tage verfügbar.", alert=False) 160 """ 161 pruefe_testversion(root, verbleibend)
Die Nutzungsart wird überprüft. Sie wird abgespeichert, falls sie noch nicht abgespeichert ist, sonst wird sie gelesen. Für gewerbliche Nutzung wird die verbleibende Nutzungszeit geprüft und ggf. weitergegeben. Bei privater Nutzung wird direkt das Login-Fenster JamfLogin geöffnet.
164def zeige_lizenz(): 165 """Die Lizenz wird einfach angezeigt. Wird aus dem Menü aufgerufen.""" 166 if not os.path.exists(LIZENZ): 167 Messagebox.ok(title="Lizenz", message="LICENSE.txt nicht gefunden.", alert=False) 168 return 169 lizfenster = tk.Toplevel() 170 #set_window_icon(lizfenster) 171 lizfenster.title("Lizenz") 172 lizfenster.geometry("600x500") 173 textfeld = scrolledtext.ScrolledText(lizfenster, wrap="word") 174 with open(LIZENZ, "r", encoding="utf-8") as f: 175 textfeld.insert("1.0", f.read()) 176 textfeld.config(state="disabled") 177 lizfenster.update_idletasks() 178 lizfenster.minsize(lizfenster.winfo_width(), lizfenster.winfo_height()) 179 textfeld.pack(fill="both", expand=True)
Die Lizenz wird einfach angezeigt. Wird aus dem Menü aufgerufen.
181def zeige_nutzungsdialog(root): 182 """Die Art der Nutzung muss ausgewählt und bestätigt werden. Wird nur bei der Erstnutzung aufgerufen.""" 183 auswahlfenster = tk.Toplevel() 184 auswahlfenster.title("Nutzungsart wählen") 185 auswahlfenster.geometry("500x300") 186 auswahlfenster.grab_set() 187 auswahlfenster.resizable(False, True) 188 auswahlfenster.update_idletasks() 189 auswahlfenster.minsize(auswahlfenster.winfo_width(), auswahlfenster.winfo_height()) 190 191 auswahl = tk.StringVar() 192 auswahl.set("privat") 193 194 def bestätigen(): 195 JamfLogin(root) 196 auswahlfenster.destroy() 197 198 label = ttk.Label(auswahlfenster, text="Bitte wählen Sie die Art der Nutzung:") 199 label.pack(pady=10) 200 201 r1 = ttk.Radiobutton(auswahlfenster, text="Privat/Schule (dauerhaft erlaubt)", variable=auswahl, value="privat") 202 r2 = ttk.Radiobutton(auswahlfenster, text="Gewerblich/Testversion (7 Tage)", variable=auswahl, value="gewerblich") 203 r1.pack(anchor="w", padx=30, pady=5) 204 r2.pack(anchor="w", padx=30, pady=5) 205 206 button = ttk.Button(auswahlfenster, text="Bestätigen", command=bestätigen) 207 button.pack(pady=20) 208 209 auswahlfenster.wait_window() 210 return auswahl.get()
Die Art der Nutzung muss ausgewählt und bestätigt werden. Wird nur bei der Erstnutzung aufgerufen.
212def zustimmung_bereits_erfolgt(): 213 """prüft, ob die Zustimmung bereits erfolgt ist.""" 214 if os.path.exists(ZUSTIMMUNGSDATEI): 215 try: 216 print(f"✅ Schreiben/Lesen von: {ZUSTIMMUNGSDATEI}") 217 with open(ZUSTIMMUNGSDATEI, "r") as f: 218 data = json.load(f) 219 return data.get("zugestimmt", False) 220 except Exception: 221 return False 222 return False
prüft, ob die Zustimmung bereits erfolgt ist.
224def speichere_zustimmung(): 225 """speichert die erfolgte Zustimmung in der Zustimmungsdatei (json)""" 226 print(f"✅ Schreiben/Lesen von: {ZUSTIMMUNGSDATEI}") 227 with open(ZUSTIMMUNGSDATEI, "w") as f: 228 json.dump({"zugestimmt": True}, f)
speichert die erfolgte Zustimmung in der Zustimmungsdatei (json)
230def show_license_dialog(root): 231 """zeigt den Lizenz-Dialog zu Beginn. Man wird aufgefordert, die Lizenz zu bestötigen.""" 232 try: 233 with open(LIZENZ, "r", encoding="utf-8") as f: 234 license_text = f.read() 235 except FileNotFoundError: 236 Messagebox.show_error("LICENSE.txt nicht gefunden.", "Fehler", parent=root) 237 return False 238 239 if sys.platform == "darwin": 240 dialog = ttk.Toplevel(root) 241 else: 242 dialog = ttk.Toplevel(root, iconphoto=None) 243 #set_window_icon(dialog) 244 dialog.title("Lizenzvereinbarung") 245 dialog.minsize(700, 500) 246 dialog.transient(root) 247 dialog.grab_set() 248 249 # Label oben 250 ttk.Label(dialog, text="Bitte lesen Sie die Lizenzbedingungen:", font=("Helvetica", 12)).pack(pady=10) 251 252 # Textbereich in eigenem Frame 253 text_frame = ttk.Frame(dialog) 254 text_frame.pack(fill="both", expand=True, padx=20, pady=10) 255 256 text_area = ScrolledText(text_frame, autohide=True) 257 text_area.pack(fill="both", expand=True) 258 text_area.text.insert("1.0", license_text) 259 text_area.text.configure(state="disabled") 260 261 # Button-Frame 262 button_frame = ttk.Frame(dialog) 263 button_frame.pack(pady=10) 264 265 result = {"accepted": None} 266 267 def agree(): 268 result["accepted"] = True 269 dialog.destroy() 270 271 def disagree(): 272 result["accepted"] = False 273 dialog.destroy() 274 275 ttk.Button(button_frame, text="Ich stimme zu", command=agree, bootstyle="success").pack(side="left", padx=10) 276 ttk.Button(button_frame, text="Ich lehne ab", command=disagree, bootstyle="danger").pack(side="right", padx=10) 277 278 dialog.wait_window() 279 return result["accepted"]
zeigt den Lizenz-Dialog zu Beginn. Man wird aufgefordert, die Lizenz zu bestötigen.
281def show_help(): 282 """zeigt die zu hilfe.html umgewandelte README.md im Browser an.""" 283 help_file = get_resource_path("hilfe.html") 284 if not os.path.exists(help_file): 285 print(f"❌ Hilfe-Datei nicht gefunden: {help_file}") 286 return 287 url = f"file://{help_file}" 288 print(f"🌐 Öffne Hilfe: {url}") 289 webbrowser.open(url)
zeigt die zu hilfe.html umgewandelte README.md im Browser an.
291def show_about(): 292 """zeigt die Aboutbox""" 293 # messagebox.showinfo("Über Classload", "Classload\nVersion 1.0\n(c) 2025") 294 messagebox.showinfo("Über Classload","Classload\nzum Austausch von Daten mit Jamf\nVersion 0.9\n(c)2025 Christiane Borchel")
zeigt die Aboutbox
296def main(): 297 """ 298 Bei der ersten Nutzung wird die Lizenz angezeigt, die bestätigt werden muss. 299 Danach muss die Nutzungsart (privat/gewerblich) gewählt werden. 300 Bei allen späteren Nutzungen wird zunächst die Nutzungsart geprüft. 301 Falls sie gewerblich ist, wird zunächst angezeigt, wie viele Tage die Lizenz noch gültig ist. 302 Falls sie nicht mehr gültig ist, endet das Programm. Sonst wird nach Bestätigung das Login-Fenster angezeigt. 303 Bei privater/schulischer Nutzung wird direkt das Login-Fenster JAMF-Login angezeigt. 304 """ 305 init_dpi_awareness() 306 if sys.platform == "darwin": 307 root = ttk.Window(themename="cosmo") 308 else: 309 root = ttk.Window(themename="cosmo", iconphoto=None) 310 root.title("Classload") 311 312 root.lift() 313 root.focus_force() 314 root.attributes("-topmost", True) 315 root.after(100, lambda: root.attributes("-topmost", False)) 316 317 base_path = getattr(sys, '_MEIPASS', os.path.dirname(__file__)) 318 # icon_path = os.path.join(base_path, "assets", "icon.png") 319 #icon_img = tk.PhotoImage(file=icon_path) 320 #icon_relpath = os.path.join("assets", "icon.png") # Pfad zusammensetzen 321 if sys.platform == "darwin": 322 # macOS → kleineres, sicheres Icon verwenden 323 icon_relpath = os.path.join("assets", "icon_small.png") 324 else: 325 # Windows/Linux → großes Icon mit Alpha 326 icon_relpath = os.path.join("assets", "icon.png") 327 328 icon_path = get_resource_path(icon_relpath) # Pfad für PyInstaller/py2app auflösen 329 icon_img = tk.PhotoImage(file=icon_path) 330 print("🔍 Icon-Pfad:", icon_path) 331 print("📦 Existiert:", os.path.exists(icon_path)) 332 #root.iconphoto(True, icon_img) 333 #root.icon_img = icon_img 334 if sys.platform.startswith("win"): 335 try: 336 icon_path = os.path.join(base_path, "assets", "icon.ico") 337 root.iconbitmap(icon_path) 338 except Exception as e: 339 print(f"Icon konnte nicht gesetzt werden: {e}") 340 #set_window_icon(root) 341 342 if not zustimmung_bereits_erfolgt(): 343 if not show_license_dialog(root): 344 return 345 speichere_zustimmung() 346 347 menubar = tk.Menu(root) 348 hilfe_menu = tk.Menu(menubar, tearoff=0) 349 hilfe_menu.add_command(label="Lizenz anzeigen", command=zeige_lizenz) 350 hilfe_menu.add_command(label="Hilfe anzeigen", command=lambda: show_help()) 351 352 if platform.system() == "Darwin": 353 apple_menu = tk.Menu(menubar, name="apple", tearoff=0) 354 apple_menu.add_command(label="Über Classload", command=show_about) 355 menubar.add_cascade(menu=apple_menu) 356 menubar.add_cascade(label="Hilfe", menu=hilfe_menu) 357 root["menu"] = menubar 358 else: 359 menubar.add_command(label="Über Classload", command=show_about) 360 menubar.add_cascade(label="Hilfe", menu=hilfe_menu) 361 root.config(menu=menubar) 362 # menubar.add_cascade(label="Hilfe", menu=hilfe_menu) 363 root.config(menu=menubar) 364 365 root.withdraw() # root bleibt im Hintergrund, aber notwendig für Tkinter 366 367 368 pruefe_nutzungsart(root) 369 #login = JamfLogin(root) 370 root.iconphoto(True, icon_img) 371 #root.icon_img = icon_img 372 root.mainloop()
Bei der ersten Nutzung wird die Lizenz angezeigt, die bestätigt werden muss. Danach muss die Nutzungsart (privat/gewerblich) gewählt werden. Bei allen späteren Nutzungen wird zunächst die Nutzungsart geprüft. Falls sie gewerblich ist, wird zunächst angezeigt, wie viele Tage die Lizenz noch gültig ist. Falls sie nicht mehr gültig ist, endet das Programm. Sonst wird nach Bestätigung das Login-Fenster angezeigt. Bei privater/schulischer Nutzung wird direkt das Login-Fenster JAMF-Login angezeigt.