mirror of
https://github.com/Andre-Bzk/FocusFix.git
synced 2026-11-15 09:33:14 +01:00
Feature: Wrapper, Logging und Doku überarbeitet
- Debug-Logging optional gemacht, den Legacy-Wrapper portabler aufgebaut (relative Pfade, Debug-Weitergabe, Python-Fallback) und die README für Endanwender und Entwickler klarer neu strukturiert.
This commit is contained in:
+35
-21
@@ -26,11 +26,9 @@ import win32process
|
||||
from pathlib import Path
|
||||
|
||||
_LOG_PATH = Path(__file__).resolve().parent / "focusfix.log"
|
||||
logging.basicConfig(
|
||||
filename=str(_LOG_PATH),
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s %(levelname)s %(message)s",
|
||||
)
|
||||
_LOGGER = logging.getLogger("focusfix")
|
||||
_LOGGER.addHandler(logging.NullHandler())
|
||||
_LOGGER.propagate = False
|
||||
|
||||
# Minimale Fensterbreite in Pixeln, um den Splashscreen auszuschließen.
|
||||
MIN_WINDOW_WIDTH = 800
|
||||
@@ -46,8 +44,21 @@ def derive_process_name(command: list[str]) -> str:
|
||||
return Path(command[0]).stem
|
||||
|
||||
|
||||
def configure_logging(enabled: bool, log_path: Path = _LOG_PATH) -> None:
|
||||
_LOGGER.handlers.clear()
|
||||
if not enabled:
|
||||
_LOGGER.addHandler(logging.NullHandler())
|
||||
_LOGGER.setLevel(logging.CRITICAL + 1)
|
||||
return
|
||||
|
||||
file_handler = logging.FileHandler(log_path, mode="w", encoding="utf-8")
|
||||
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
||||
_LOGGER.addHandler(file_handler)
|
||||
_LOGGER.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def launch_game(command: list[str]) -> subprocess.Popen:
|
||||
logging.info("Launching game command: %r", command)
|
||||
_LOGGER.info("Launching game command: %r", command)
|
||||
return subprocess.Popen(command)
|
||||
|
||||
|
||||
@@ -82,7 +93,7 @@ def find_game_window(pids: set[int]) -> int | None:
|
||||
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)
|
||||
_LOGGER.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
|
||||
|
||||
@@ -115,7 +126,7 @@ def wait_for_stable_size(hwnd: int, polls: int = 3, interval: float = 0.5,
|
||||
stable_count = 0
|
||||
last_size = size
|
||||
time.sleep(interval)
|
||||
logging.debug("Stable size for hwnd=%s: %dx%d (after %d stable polls)",
|
||||
_LOGGER.debug("Stable size for hwnd=%s: %dx%d (after %d stable polls)",
|
||||
hwnd, last_size[0], last_size[1], stable_count)
|
||||
return last_size
|
||||
|
||||
@@ -154,11 +165,11 @@ def bring_to_front(hwnd: int) -> bool:
|
||||
ctypes.windll.user32.AttachThreadInput(tgt_tid, fg_tid, False)
|
||||
|
||||
result = is_foreground(hwnd)
|
||||
logging.debug("bring_to_front result: %s (foreground hwnd=%s, target hwnd=%s)",
|
||||
result, win32gui.GetForegroundWindow(), hwnd)
|
||||
_LOGGER.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)
|
||||
_LOGGER.exception("bring_to_front failed: %s", e)
|
||||
return False
|
||||
finally:
|
||||
win32api.keybd_event(win32con.VK_MENU, 0, win32con.KEYEVENTF_KEYUP, 0)
|
||||
@@ -167,12 +178,15 @@ def bring_to_front(hwnd: int) -> bool:
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-d", "--debug", "-debug", action="store_true",
|
||||
help="Debug-Logging nach focusfix.log aktivieren")
|
||||
parser.add_argument("--timeout", type=int, default=120)
|
||||
parser.add_argument("--process", type=str,
|
||||
help="Prozessname des Spiels (ohne .exe)")
|
||||
parser.add_argument("command", nargs="*",
|
||||
help="Steam-Spielbefehl, z.B. %%command%%")
|
||||
args, extra_command_args = parser.parse_known_args()
|
||||
configure_logging(args.debug)
|
||||
|
||||
process_name = args.process
|
||||
command = args.command + extra_command_args
|
||||
@@ -187,13 +201,13 @@ def main() -> None:
|
||||
try:
|
||||
launch_game(command)
|
||||
except OSError as exc:
|
||||
logging.exception("Failed to launch game command: %s", exc)
|
||||
_LOGGER.exception("Failed to launch game command: %s", exc)
|
||||
sys.exit(1)
|
||||
elif not process_name:
|
||||
parser.error("Either --process or a game command is required.")
|
||||
|
||||
deadline = time.time() + args.timeout
|
||||
logging.info("Monitoring process name: %s", process_name)
|
||||
_LOGGER.info("Monitoring process name: %s", process_name)
|
||||
|
||||
# --- Phase 1: Warten auf den Prozess ---
|
||||
pids: set[int] = set()
|
||||
@@ -211,13 +225,13 @@ def main() -> None:
|
||||
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))
|
||||
_LOGGER.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")
|
||||
_LOGGER.error("Timeout waiting for game window")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Phase 3: Spielfenster fokussieren und auf Fensterwechsel reagieren ---
|
||||
@@ -227,7 +241,7 @@ def main() -> None:
|
||||
for attempt in range(FOCUS_RETRIES):
|
||||
pids = find_process_pids(process_name)
|
||||
if not pids:
|
||||
logging.warning("Game process gone, stopping")
|
||||
_LOGGER.warning("Game process gone, stopping")
|
||||
break
|
||||
|
||||
current_hwnd = find_game_window(pids)
|
||||
@@ -237,10 +251,10 @@ def main() -> None:
|
||||
old_title = win32gui.GetWindowText(hwnd) if hwnd else ""
|
||||
# Nicht von einem Fenster mit Titel auf ein titelloses wechseln
|
||||
if not new_title and old_title:
|
||||
logging.debug("Ignoring titleless window hwnd=%s (keeping %s title=%r)",
|
||||
_LOGGER.debug("Ignoring titleless window hwnd=%s (keeping %s title=%r)",
|
||||
current_hwnd, hwnd, old_title)
|
||||
else:
|
||||
logging.info("Window changed: old=%s new=%s title=%r",
|
||||
_LOGGER.info("Window changed: old=%s new=%s title=%r",
|
||||
hwnd, current_hwnd, new_title)
|
||||
hwnd = current_hwnd
|
||||
focused_hwnd = None # neues Fenster muss neu fokussiert werden
|
||||
@@ -258,15 +272,15 @@ def main() -> None:
|
||||
# Warten bis Fenstergröße stabil ist (Resize abgeschlossen)
|
||||
wait_for_stable_size(hwnd)
|
||||
|
||||
logging.info("Focus attempt %d/%d for hwnd=%s title=%r",
|
||||
_LOGGER.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)
|
||||
_LOGGER.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)
|
||||
_LOGGER.info("Done. Final focused hwnd=%s", focused_hwnd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user