Files
FocusFix/README.md
T

406 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](#problembeschreibung)
- [Lösungsansatz](#lösungsansatz)
- [Architektur](#architektur)
- [Voraussetzungen](#voraussetzungen)
- [Installation](#installation)
- [Konfiguration](#konfiguration)
- [Steam](#steam)
- [Sunshine / Moonlight](#sunshine--moonlight)
- [Ablauf zur Laufzeit](#ablauf-zur-laufzeit)
- [Dateireferenz](#dateireferenz)
- [launch.vbs](#launchvbs)
- [foreground_fix.py](#foreground_fixpy)
- [Konfigurationsparameter](#konfigurationsparameter)
- [Windows API — Technischer Hintergrund](#windows-api--technischer-hintergrund)
- [Das SetForegroundWindow-Problem](#das-setforegroundwindow-problem)
- [Workaround-Strategie](#workaround-strategie)
- [Splashscreen-Erkennung](#splashscreen-erkennung)
- [Manuelles Testen](#manuelles-testen)
- [Debugging](#debugging)
- [Bekannte Einschränkungen](#bekannte-einschränkungen)
---
## 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
```bash
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
```vb
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:
```python
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)
```bash
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
```bash
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:
```powershell
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