Files
FocusFix/README.md
T

18 KiB
Raw Permalink Blame History

Game Foreground Fix

Windows-Hilfsprogramm, das ein Spielfenster automatisch in den Vordergrund bringt. Gedacht für Spiele wie Crimson Desert, die nach dem Start im Hintergrund landen oder von Steam, Taskleiste, Splashscreens oder Overlay-Fenstern verdeckt werden. Funktioniert mit Steam (automatische Prozesserkennung) und Sunshine/Moonlight (manueller Prozessname).


Inhaltsverzeichnis


Problembeschreibung

Beim Start eines Spiels über Steam oder Sunshine/Moonlight öffnet sich das Spielfenster im Hintergrund — die Steam-Oberfläche, die Windows-Taskleiste oder andere Fenster verdecken es. Der Spieler muss manuell per Alt+Tab zum Spiel wechseln.

Ursache: Windows vergibt den Vordergrundfokus an den Prozess, der ihn aktuell besitzt. Das Spielfenster erhält keinen automatischen Fokus, weil es von einem anderen Prozess (Steam-Launcher, Sunshine) erzeugt wurde.

Zusätzlich erzeugen manche Spiele beim Start mehrere Fenster — Splashscreens, Launcher oder titellose Overlay-Fenster — die den Fokus stören können.

Lösungsansatz

Ein Python-Script überwacht den Spielstart, wartet auf das Hauptfenster des Spiels und erzwingt den Vordergrundfokus über Windows-API-Aufrufe. Ein VBScript-Wrapper sorgt bei Steam dafür, dass weder ein Konsolenfenster noch andere störende Fenster erscheinen. Das Tool ist nicht auf ein bestimmtes Spiel festgelegt, sondern auf typische Startprobleme mit ähnlichem Fensterverhalten ausgelegt.

Architektur

Steam Launch Options / Sunshine Befehlsvorbereitung
        │
        ▼
   ┌──────────┐     extrahiert Prozessname      ┌────────────────────┐
   │ launch.vbs├──── aus %command% ────────────►│ pythonw.exe        │
   │ (nur Steam)│   + startet unsichtbar         │ foreground_fix.py  │
   │           │                                 │ --process GameName │
   │           │     startet normal              │                    │
   │           ├────────────────────────────────►│ (wartet auf Spiel) │
   └──────────┘   %command% = Spiel.exe          └────────┬───────────┘
                                                           │
                                                  Fenster gefunden
                                                           │
                                                  ┌────────▼───────────┐
                                                  │ 1. Fenster erkennen│
                                                  │ 2. Größe abwarten  │
                                                  │ 3. Spiel fokussieren│
                                                  │ 4. Wechsel beobachten│
                                                  │ 5. Script beendet  │
                                                  └────────────────────┘

Zwei Dateien, klare Aufgabentrennung:

Datei Sprache Aufgabe
launch.vbs VBScript Startrampe (nur Steam): extrahiert Prozessname aus %command%, startet das Python-Script unsichtbar und leitet den Spielstart weiter
foreground_fix.py Python 3 Kernlogik: Prozess-Monitoring, Fenstererkennung, Fensterwechsel-Erkennung, Fokus-Erzwingung, Logging

Voraussetzungen

Komponente Version Hinweis
Windows 10 / 11 Benötigt Win32 API (user32.dll)
Python 3.10+ py Launcher muss verfügbar sein
pip-Pakete psutil, pywin32 Werden im .venv installiert
Steam Beliebig Startoptionen müssen konfigurierbar sein

Installation

cd C:\Projekte\Steam_backgroud_fix

# Virtual Environment erstellen
py -m venv .venv

# Aktivieren
.venv\Scripts\activate

# Abhängigkeiten installieren
pip install psutil pywin32

Dateistruktur nach Installation

C:\Projekte\Steam_backgroud_fix\
├── .venv\                  # Python Virtual Environment
├── foreground_fix.py       # Hauptscript (Kernlogik)
├── foreground_fix.log      # Logdatei (wird automatisch erstellt)
├── launch.vbs              # VBScript-Wrapper für Steam
├── CLAUDE.md               # Kontext für Claude Code
└── README.md               # Diese Dokumentation

Konfiguration

Steam

  1. Steam öffnen → Bibliothek
  2. Rechtsklick auf das Spiel → Eigenschaften
  3. Unter Allgemein → Startoptionen eintragen:
wscript "C:\Projekte\Steam_backgroud_fix\launch.vbs" %command%

launch.vbs extrahiert den Prozessnamen automatisch aus %command% — keine manuelle Anpassung pro Spiel nötig. Die gleiche Startoption kann für beliebige Steam-Spiele mit vergleichbarem Fokus-Problem verwendet werden.

Erklärung:

  • wscript — Windows Script Host, führt VBScript ohne Konsolenfenster aus
  • "...\launch.vbs" — Pfad zum VBScript-Wrapper
  • %command% — Steam-Platzhalter, wird zur Laufzeit durch den vollständigen Pfad zur Spiel-Executable ersetzt (z.B. "C:\SteamLibrary\...\Game.exe")

Sunshine / Moonlight

In Sunshine als Befehlsvorbereitung (Command Preparation) pro Spiel eintragen:

"C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe" "C:\Projekte\Steam_backgroud_fix\foreground_fix.py" --process "Prozessname"

Den Prozessnamen im Task-Manager ablesen (ohne .exe). Beispiele:

Spiel Prozessname
Crimson Desert CrimsonDesert
Wuthering Waves Client-Win64-Shipping

Wichtig: Der vollständige Pfad zu pythonw.exe im .venv ist nötig, damit die Abhängigkeiten (psutil, pywin32) gefunden werden. py oder python nutzt die globale Python-Installation ohne diese Pakete.

Ablauf zur Laufzeit

Zeitachse
─────────────────────────────────────────────────────────────────────
t=0s    Steam/Sunshine startet das Script mit --process "GameName"
t=0.1s  foreground_fix.py beginnt zu pollen (alle 0.5s)
        → Sucht nach Prozess "GameName"

t=~3s   Spielprozess gestartet, ggf. Splashscreen erscheint
        → Script findet den Prozess (PID)
        → Sucht nach Fenster mit Breite >= 800px
        → Splashscreen ist kleiner → wird ignoriert

t=~10s  Hauptfenster des Spiels erscheint (z.B. 2560×1440)
        → Script findet das Fenster (bevorzugt Fenster mit Titel)
        → Wartet 3 Sekunden (Initialisierung abwarten)
        → Wartet bis Fenstergröße stabil ist

t=~13s  Phase 3: Fokus-Erzwingung
        → Spielfenster wird in den Vordergrund gebracht
        → Bei Misserfolg: bis zu 15 Wiederholungen (je 2s Pause)
        → Beobachtet ob sich das Fenster ändert (Splash → Hauptfenster)
        → Ignoriert titellose Overlay-Fenster

t=~15s  Erfolg → foreground_fix.py beendet sich
        → Logdatei enthält den vollständigen Ablauf

Dateireferenz

launch.vbs

Set WshShell = CreateObject("WScript.Shell")

' Prozessname aus dem ersten Argument (Steam %command%) extrahieren
Dim exePath, fso, exeName
Set fso = CreateObject("Scripting.FileSystemObject")
exePath = WScript.Arguments(0)
exeName = fso.GetBaseName(exePath)

' Foreground-Fix unsichtbar starten mit extrahiertem Prozessnamen
WshShell.Run """...\pythonw.exe"" ""...\foreground_fix.py"" --process """ & exeName & """", 0, False

' Spiel starten: Steam-Argumente zusammensetzen
Dim gameCmd, i
gameCmd = ""
For i = 0 To WScript.Arguments.Count - 1
    gameCmd = gameCmd & """" & WScript.Arguments(i) & """ "
Next
If gameCmd <> "" Then
    WshShell.Run gameCmd, 1, False
End If

WshShell.Run Parameter:

Parameter Wert Bedeutung
Command "pythonw.exe" "foreground_fix.py" --process "GameName" Auszuführender Befehl
WindowStyle 0 Fenster komplett versteckt
WaitOnReturn False Nicht auf Beendigung warten (asynchron)

Warum pythonw.exe statt python.exe? python.exe ist eine Konsolenanwendung und erzeugt automatisch ein CMD-Fenster. pythonw.exe ist die "windowless"-Variante — identischer Python-Interpreter, aber ohne Konsole.

Warum VBScript statt Batch? Eine .bat-Datei wird von cmd.exe ausgeführt, das immer ein Konsolenfenster öffnet — selbst wenn es sich sofort wieder schließt. VBScript über wscript.exe erzeugt zu keinem Zeitpunkt ein sichtbares Fenster.

foreground_fix.py

Funktionsübersicht

Funktion Beschreibung
find_process_pids(name) Sucht laufende Prozesse nach Name (Teilstring, case-insensitiv), gibt PIDs zurück
find_game_window(pids) Findet das Hauptfenster der gegebenen PIDs — bevorzugt Fenster mit Titel, dann nach Größe
wait_for_stable_size(hwnd) Wartet bis die Fenstergröße sich stabilisiert hat (kein Resize mehr)
is_foreground(hwnd) Prüft ob ein Fenster aktuell im Vordergrund ist
bring_to_front(hwnd) Erzwingt den Vordergrundfokus für ein Fenster (Alt-Key + AttachThreadInput)
main() Orchestriert den 3-Phasen-Ablauf

find_game_window(pids) — Fensterauswahl

Nutzt win32gui.EnumWindows() um alle Top-Level-Fenster zu durchlaufen. Für jedes Fenster:

  1. Prüft Sichtbarkeit (IsWindowVisible)
  2. Ermittelt die zugehörige PID (GetWindowThreadProcessId)
  3. Misst die Fenstergröße (GetWindowRect)
  4. Filtert nach MIN_WINDOW_WIDTH (Standard: 800px)

Bei mehreren Treffern wird das Fenster mit Titel bevorzugt, dann nach Fläche sortiert. Dadurch werden sowohl Splashscreens (zu klein) als auch titellose Overlay-Fenster (kein Titel) vom Hauptfenster unterschieden.

bring_to_front(hwnd) — Fokus-Erzwingung

Erzwingt den Vordergrundfokus trotz Windows-Einschränkungen:

try:
    # Schritt 1: Alt-Taste simulieren (hebt SetForegroundWindow-Sperre auf)
    keybd_event(VK_MENU, 0, 0, 0)

    # Schritt 2: Thread-Input verbinden
    AttachThreadInput(target_thread, foreground_thread, True)

    # Schritt 3: Fenster anzeigen und fokussieren
    ShowWindow(hwnd, SW_SHOW)
    BringWindowToTop(hwnd)
    SetForegroundWindow(hwnd)
    SetFocus(hwnd)
    SetActiveWindow(hwnd)

    # Schritt 4: Thread-Input trennen
    AttachThreadInput(target_thread, foreground_thread, False)
except Exception:
    ...
finally:
    # Alt-Taste wird IMMER losgelassen, auch bei Fehlern
    keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0)

Die Alt-Taste wird in einem finally-Block freigegeben, damit sie bei Fehlern (z.B. ungültiges Handle, fehlende Rechte) nicht systemweit gedrückt bleibt.

main() — Dreistufiger Ablauf

  1. Phase 1 — Prozess-Suche: Pollt alle 0,5 Sekunden nach dem Prozessnamen. Bricht nach --timeout Sekunden ab.

  2. Phase 2 — Fenster-Suche: Pollt alle 0,5 Sekunden nach einem sichtbaren Fenster mit ausreichender Größe. Nach Fund: 3 Sekunden Wartezeit für die vollständige Initialisierung.

  3. Phase 3 — Fokus mit Fensterwechsel-Erkennung: Bis zu 15 Versuche den Spielfokus zu erzwingen, mit je 2 Sekunden Pause. Beobachtet dabei, ob sich das Fenster ändert (z.B. Splash → Hauptfenster) und fokussiert das neue Fenster. Titellose Overlay-Fenster werden ignoriert, wenn bereits ein Fenster mit Titel fokussiert wurde.

Konfigurationsparameter

Kommandozeilenargumente

Argument Pflicht Standard Beschreibung
--process Ja — Teilstring des Prozessnamens (case-insensitiv, ohne .exe)
--timeout Nein 120 Maximale Wartezeit in Sekunden für Prozess- und Fensterfund

Konstanten in foreground_fix.py

Konstante Wert Beschreibung
MIN_WINDOW_WIDTH 800 Mindestbreite in Pixeln, um Splashscreens auszufiltern
FOCUS_RETRIES 15 Maximale Anzahl der Fokus-Versuche
FOCUS_RETRY_DELAY 2.0 Sekunden zwischen Fokus-Versuchen

Hardcodierte Pfade in launch.vbs

Pfad Zweck
C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe Python-Interpreter (windowless)
C:\Projekte\Steam_backgroud_fix\foreground_fix.py Hauptscript

Bei einer Änderung des Installationsverzeichnisses müssen diese Pfade in launch.vbs angepasst werden.

Windows API — Technischer Hintergrund

Das SetForegroundWindow-Problem

Seit Windows 2000 verhindert das Betriebssystem, dass beliebige Prozesse den Vordergrundfokus "stehlen" können. SetForegroundWindow(hwnd) funktioniert nur, wenn:

  • Der aufrufende Prozess der Vordergrundprozess ist, oder
  • Der aufrufende Prozess von dem Vordergrundprozess gestartet wurde, oder
  • Der letzte Eingabe-Event vom aufrufenden Prozess stammt

Andernfalls blinkt das Fenster lediglich in der Taskleiste.

Da foreground_fix.py als eigenständiger Prozess läuft (nicht vom Spiel gestartet), erfüllt es keine dieser Bedingungen.

Workaround-Strategie

Das Script kombiniert drei Techniken:

1. Alt-Key-Simulation (keybd_event)

Simuliert einen Tastendruck der Alt-Taste. Windows behandelt den aufrufenden Prozess danach so, als hätte der Benutzer eine Eingabe getätigt — dies hebt die SetForegroundWindow-Sperre auf.

2. Thread-Input-Verbindung (AttachThreadInput)

Verbindet die Eingabewarteschlangen zweier Threads. Danach teilen sich beide Threads den gleichen Eingabefokus-Zustand. Der Ziel-Thread (Spiel) kann nun den Fokus erhalten, als wäre er Teil des Vordergrund-Threads.

3. Mehrfache Fokus-Befehle

ShowWindow, BringWindowToTop, SetForegroundWindow, SetFocus und SetActiveWindow adressieren jeweils einen leicht anderen Aspekt des Fenster-Fokus unter Windows. Die Kombination stellt sicher, dass das Fenster sowohl visuell im Vordergrund ist als auch Tastatur- und Mauseingaben empfängt.

Splashscreen-Erkennung

Viele Spiele zeigen beim Start einen Splashscreen, bevor das Hauptfenster erscheint. Beide gehören zum selben Prozess.

Unterscheidungsmerkmal: Fenstergröße. Der Splashscreen ist typischerweise 300–600px breit. Das Spielfenster ist mindestens so groß wie die Bildschirmauflösung (z.B. 2560×1440). Der Schwellwert MIN_WINDOW_WIDTH = 800 trennt beide zuverlässig.

Zusätzlich erzeugen manche Spiele (z.B. Wuthering Waves) titellose Overlay-Fenster in voller Bildschirmgröße. Diese werden durch die Bevorzugung von Fenstern mit Titel in der Sortierung und durch die Fensterwechsel-Logik ignoriert.

Manuelles Testen

Script alleine testen (Spiel muss laufen)

cd C:\Projekte\Steam_backgroud_fix
.venv\Scripts\python foreground_fix.py --process "CrimsonDesert" --timeout 5

Die Laufzeit-Logs landen in foreground_fix.log. Bei Ausführung über pythonw.exe gibt es keine sichtbare Ausgabe; bei manuellen Starts mit python.exe können Argument- oder Interpreter-Fehler weiterhin in der Konsole erscheinen.

VBScript alleine testen

wscript "C:\Projekte\Steam_backgroud_fix\launch.vbs" "C:\dummy\TestGame.exe"

Sollte das Python-Script unsichtbar starten mit --process "TestGame". Prüfen ob pythonw.exe im Task-Manager erscheint.

Gesamtablauf testen

  1. Steam-Startoptionen wie oben konfigurieren
  2. Spiel über Steam starten
  3. Erwartung: Spielfenster erscheint nach dem Ladescreen im Vordergrund, keine Taskleiste sichtbar, keine CMD- oder Script-Fenster
  4. foreground_fix.log prüfen für den detaillierten Ablauf

Debugging

Alle Informationen werden in foreground_fix.log protokolliert:

  • Gefundene Kandidaten-Fenster (Handle, Titel, Größe, PID)
  • Fensterwechsel und ignorierte titellose Fenster
  • Fokus-Versuche mit Ergebnis (Erfolg/Misserfolg)
  • Fehler mit vollständigem Traceback

Häufige Probleme

Symptom Ursache Lösung
Spiel startet nicht Steam-Startoptionen fehlerhaft Prüfen ob %command% am Ende steht
ModuleNotFoundError: psutil Globales Python statt venv Vollständigen Pfad zu .venv\Scripts\pythonw.exe verwenden
Script findet Prozess nicht Prozessname stimmt nicht --process anpassen, Namen im Task-Manager prüfen
Fenster wird nicht fokussiert Windows-Restriktion zu streng Retry-Anzahl/Delay in Konstanten erhöhen
Alt-Taste bleibt hängen Spiel mit Admin-Rechten, Script ohne Script ebenfalls als Admin starten
CMD-Fenster sichtbar python.exe statt pythonw.exe Pfad in launch.vbs bzw. Sunshine-Konfiguration prüfen
Verdunkeltes/blockiertes Spiel Titelloses Overlay-Fenster Log prüfen — sollte durch Fenster-mit-Titel-Bevorzugung gelöst sein

Fenster-Inspektion per PowerShell

Um alle Fenster eines laufenden Spielprozesses zu inspizieren:

Get-Process -Name GameName | Select-Object Id, Name, MainWindowTitle, MainWindowHandle

Bekannte Einschränkungen

  • Kein Admin erforderlich, aber manche Spiele starten mit erhöhten Rechten — in dem Fall muss das Script ebenfalls als Admin laufen
  • Exklusiver Vollbildmodus: Spiele im exklusiven Vollbild (kein Borderless Windowed) können sich anders verhalten, da sie die komplette Bildschirmausgabe übernehmen
  • Anti-Cheat: Einige Anti-Cheat-Systeme könnten die keybd_event-Simulation oder AttachThreadInput-Aufrufe als verdächtig einstufen — bisher keine Probleme bekannt
  • Mehrere Monitore: find_game_window wählt das Fenster unabhängig davon, auf welchem Monitor es liegt
  • Nicht universell für jedes Spiel: Das Tool ist generisch für viele ähnliche Fälle, aber nicht garantiert für jeden Launcher, jedes Anti-Cheat-System oder jedes ungewöhnliche Fenster-/Startverhalten