Files
FocusFix/foreground_fix.py
T
Andre 8625bb0188 Logging, Fensterwechsel-Erkennung und stabile Fenstergröße
- Datei-Logging nach foreground_fix.log für Debugging hinzugefügt
- Fensterwechsel (Splash -> Hauptfenster) erkennen und automatisch neu fokussieren
- Vor dem Fokussieren warten bis die Fenstergröße stabil ist
- minimize_other_windows entfernt (unnötig)
- Retries erhöht (5->15) und Delay (1s->2s) für mehr Zuverlässigkeit
2026-04-04 11:19:56 +02:00

233 lines
7.3 KiB
Python

"""
foreground_fix.py
Wartet auf den Start von CrimsonDesert.exe und bringt das Spielfenster
automatisch in den Vordergrund.
Verwendung:
pythonw foreground_fix.py [--timeout 120] [--process CrimsonDesert]
Abhängigkeiten (im .venv):
pip install psutil pywin32
"""
import argparse
import ctypes
import logging
import sys
import time
import psutil
import win32api
import win32con
import win32gui
import win32process
from pathlib import Path
_LOG_PATH = Path(__file__).resolve().parent / "foreground_fix.log"
logging.basicConfig(
filename=str(_LOG_PATH),
level=logging.DEBUG,
format="%(asctime)s %(levelname)s %(message)s",
)
# Minimale Fensterbreite in Pixeln, um den Splashscreen auszuschließen.
MIN_WINDOW_WIDTH = 800
# Anzahl der Fokus-Versuche und Pause dazwischen
FOCUS_RETRIES = 15
FOCUS_RETRY_DELAY = 2.0
def find_process_pids(name: str) -> set[int]:
pids = set()
for proc in psutil.process_iter(["pid", "name"]):
try:
if name.lower() in proc.info["name"].lower():
pids.add(proc.info["pid"])
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
return pids
def find_game_window(pids: set[int]) -> int | None:
found = []
def callback(hwnd, _):
if not win32gui.IsWindowVisible(hwnd):
return True
try:
_, pid = win32process.GetWindowThreadProcessId(hwnd)
except Exception:
return True
if pid not in pids:
return True
try:
rect = win32gui.GetWindowRect(hwnd)
except Exception:
return True
width = rect[2] - rect[0]
height = rect[3] - rect[1]
if width >= MIN_WINDOW_WIDTH:
title = win32gui.GetWindowText(hwnd)
logging.debug("Candidate window: hwnd=%s title=%r size=%dx%d pid=%d", hwnd, title, width, height, pid)
found.append((hwnd, title, width, height))
return True
win32gui.EnumWindows(callback, None)
if not found:
return None
found.sort(key=lambda x: x[2] * x[3], reverse=True)
return found[0][0]
def wait_for_stable_size(hwnd: int, polls: int = 3, interval: float = 0.5,
timeout: float = 5.0) -> tuple[int, int]:
"""Wartet bis die Fenstergröße sich stabilisiert hat (kein Resize mehr).
Gibt die finale (width, height) zurück."""
last_size = (0, 0)
stable_count = 0
deadline = time.time() + timeout
while time.time() < deadline:
try:
rect = win32gui.GetWindowRect(hwnd)
size = (rect[2] - rect[0], rect[3] - rect[1])
except Exception:
break
if size == last_size:
stable_count += 1
if stable_count >= polls:
break
else:
stable_count = 0
last_size = size
time.sleep(interval)
logging.debug("Stable size for hwnd=%s: %dx%d (after %d stable polls)",
hwnd, last_size[0], last_size[1], stable_count)
return last_size
def is_foreground(hwnd: int) -> bool:
"""Prüft ob das angegebene Fenster aktuell das Vordergrundfenster ist."""
return win32gui.GetForegroundWindow() == hwnd
def bring_to_front(hwnd: int) -> bool:
"""
Bringt das Fenster in den Vordergrund. Gibt True zurück wenn erfolgreich.
Verwendet Alt-Key + AttachThreadInput als Workaround.
"""
try:
# Alt-Taste simulieren — hebt die SetForegroundWindow-Sperre auf
win32api.keybd_event(win32con.VK_MENU, 0, 0, 0)
time.sleep(0.05)
fg_hwnd = win32gui.GetForegroundWindow()
fg_tid, _ = win32process.GetWindowThreadProcessId(fg_hwnd)
tgt_tid, _ = win32process.GetWindowThreadProcessId(hwnd)
attached = False
if fg_tid != tgt_tid:
ctypes.windll.user32.AttachThreadInput(tgt_tid, fg_tid, True)
attached = True
win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
win32gui.BringWindowToTop(hwnd)
win32gui.SetForegroundWindow(hwnd)
ctypes.windll.user32.SetFocus(hwnd)
ctypes.windll.user32.SetActiveWindow(hwnd)
if attached:
ctypes.windll.user32.AttachThreadInput(tgt_tid, fg_tid, False)
win32api.keybd_event(win32con.VK_MENU, 0, win32con.KEYEVENTF_KEYUP, 0)
result = is_foreground(hwnd)
logging.debug("bring_to_front result: %s (foreground hwnd=%s, target hwnd=%s)",
result, win32gui.GetForegroundWindow(), hwnd)
return result
except Exception as e:
logging.exception("bring_to_front failed: %s", e)
return False
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--timeout", type=int, default=120)
parser.add_argument("--process", type=str, default="CrimsonDesert")
args = parser.parse_args()
deadline = time.time() + args.timeout
process_name = args.process
# --- Phase 1: Warten auf den Prozess ---
pids: set[int] = set()
while time.time() < deadline:
pids = find_process_pids(process_name)
if pids:
break
time.sleep(0.5)
else:
sys.exit(1)
# --- Phase 2: Warten auf das Hauptfenster ---
hwnd: int | None = None
while time.time() < deadline:
pids = find_process_pids(process_name)
hwnd = find_game_window(pids)
if hwnd:
logging.info("Initial window found: hwnd=%s title=%r", hwnd, win32gui.GetWindowText(hwnd))
# Warten bis das Spiel sein Fenster vollständig aufgebaut hat
time.sleep(3)
break
time.sleep(0.5)
else:
logging.error("Timeout waiting for game window")
sys.exit(1)
# --- Phase 3: Spielfenster fokussieren und auf Fensterwechsel reagieren ---
# Beobachtet über die gesamte Retry-Dauer, ob sich das Fenster ändert
# (Splash -> Hauptfenster), und fokussiert jedes neue Fenster erneut.
focused_hwnd: int | None = None
for attempt in range(FOCUS_RETRIES):
pids = find_process_pids(process_name)
if not pids:
logging.warning("Game process gone, stopping")
break
current_hwnd = find_game_window(pids)
if current_hwnd and current_hwnd != hwnd:
logging.info("Window changed: old=%s new=%s title=%r",
hwnd, current_hwnd, win32gui.GetWindowText(current_hwnd))
hwnd = current_hwnd
focused_hwnd = None # neues Fenster muss neu fokussiert werden
time.sleep(1)
if not hwnd:
time.sleep(FOCUS_RETRY_DELAY)
continue
# Bereits erfolgreich fokussiert — weiter beobachten ob Fenster wechselt
if focused_hwnd == hwnd and is_foreground(hwnd):
time.sleep(FOCUS_RETRY_DELAY)
continue
# Warten bis Fenstergröße stabil ist (Resize abgeschlossen)
wait_for_stable_size(hwnd)
logging.info("Focus attempt %d/%d for hwnd=%s title=%r",
attempt + 1, FOCUS_RETRIES, hwnd, win32gui.GetWindowText(hwnd))
success = bring_to_front(hwnd)
if success:
logging.info("Focus succeeded on attempt %d", attempt + 1)
focused_hwnd = hwnd
time.sleep(FOCUS_RETRY_DELAY)
logging.info("Done. Final focused hwnd=%s", focused_hwnd)
if __name__ == "__main__":
main()