# -*- coding: utf-8 -*- """Nur ein Fenster zulassen und das offene nach vorn holen. Zwei Fenster lesen beim Start denselben Zähler und schlagen dieselbe Rechnungsnummer vor - die Nummer wird dann doppelt vergeben. """ import os import sys import config def einzelinstanz_pruefen(): """Sperrt das Programm gegen ein zweites Fenster. Beide Fenster lesen beim Start denselben Zaehler aus der config und schlagen darum dieselbe Rechnungsnummer vor -> die Nummer wird doppelt vergeben. Darum: nur eine Instanz je Windows-Sitzung. Rueckgabe: Handle (offen halten, solange das Programm laeuft) oder False, wenn schon eine Instanz laeuft. """ if not sys.platform.startswith("win"): return None import ctypes from ctypes import wintypes ERROR_ALREADY_EXISTS = 183 k32 = ctypes.WinDLL("kernel32", use_last_error=True) k32.CreateMutexW.argtypes = [wintypes.LPVOID, wintypes.BOOL, wintypes.LPCWSTR] k32.CreateMutexW.restype = wintypes.HANDLE handle = k32.CreateMutexW(None, False, "RechnungstoolCampinghofBartl") if ctypes.get_last_error() == ERROR_ALREADY_EXISTS: return False return handle # Merkzettel mit der PID der laufenden Instanz - liegt neben der config.json. PID_DATEI = os.path.join(config.BASE_DIR, "rechnungstool.pid") def eigene_pid_merken(): """Schreibt die eigene PID, damit eine zweite Instanz das Fenster findet.""" try: with open(PID_DATEI, "w", encoding="ascii") as f: f.write(str(os.getpid())) except OSError: pass def laufende_pid_lesen(): """PID der laufenden Instanz aus dem Merkzettel; None wenn unlesbar.""" try: with open(PID_DATEI, "r", encoding="ascii") as f: return int(f.read().strip()) except (OSError, ValueError): return None def fenster_nach_vorn(pid): """Holt das Fenster des Prozesses nach vorn. True bei Erfolg. Gesucht wird ueber die PID, nicht ueber den Fenstertitel - der Titel haengt am Firmennamen aus der config und aendert sich mit ihm. """ if not pid or not sys.platform.startswith("win"): return False import ctypes from ctypes import wintypes SW_RESTORE = 9 u32 = ctypes.WinDLL("user32", use_last_error=True) u32.IsWindowVisible.argtypes = [wintypes.HWND] u32.IsIconic.argtypes = [wintypes.HWND] u32.ShowWindow.argtypes = [wintypes.HWND, ctypes.c_int] u32.SetForegroundWindow.argtypes = [wintypes.HWND] u32.GetWindowTextLengthW.argtypes = [wintypes.HWND] u32.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)] treffer = [] def besuch(hwnd, _lparam): fremd = wintypes.DWORD() u32.GetWindowThreadProcessId(hwnd, ctypes.byref(fremd)) # nur das echte Hauptfenster: sichtbar und mit Titel if fremd.value == pid and u32.IsWindowVisible(hwnd) and u32.GetWindowTextLengthW(hwnd): treffer.append(hwnd) return False return True CB = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM) u32.EnumWindows(CB(besuch), 0) if not treffer: return False hwnd = treffer[0] if u32.IsIconic(hwnd): u32.ShowWindow(hwnd, SW_RESTORE) u32.SetForegroundWindow(hwnd) return True