Files
FocusFix/README.md
T
2026-04-18 16:10:59 +02:00

367 lines
16 KiB
Markdown

# FocusFix - Game Foreground Fix for Windows
Windows-Hilfsprogramm, das ein Spielfenster automatisch in den Vordergrund bringt. Gedacht für Spiele, die nach dem Start im Hintergrund landen oder von Steam, Taskleiste, Splashscreens oder Overlay-Fenstern verdeckt werden. Funktioniert mit **Steam** (direkt über `%command%`) 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)
- [focusfix.py](#focusfixpy)
- [launch.vbs](#launchvbs)
- [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, und der Spieler muss manuell per Alt+Tab wechseln.
Ursache: Windows vergibt den Vordergrundfokus nur eingeschränkt an Prozesse, die das System als berechtigt ansieht. Zusätzlich erzeugen manche Spiele beim Start mehrere Fenster wie Splashscreens, Launcher oder titellose Overlay-Fenster, die den Fokus stören können.
## Lösungsansatz
`focusfix.py` kann in zwei Modi arbeiten:
1. **Steam-Wrapper-Modus:** Das Script erhält `%command%`, leitet daraus den Prozessnamen ab, startet das Spiel selbst und überwacht anschließend den Fensterfokus.
2. **Direktmodus:** Das Script wird mit `--process Name` gestartet und überwacht einen bekannten Spielprozess.
Das Tool ist nicht auf ein bestimmtes Spiel festgelegt, sondern für viele ähnliche Fokus-Probleme mit vergleichbarem Fensterverhalten gedacht.
## Architektur
```
Steam Launch Options / Sunshine Befehlsvorbereitung
│
▼
┌────────────────────┐
│ pythonw.exe │
│ focusfix.py │
│ │
│ Steam: %command% │
│ Sunshine: │
│ --process Name │
└────────┬───────────┘
│
Steam-Modus:
startet das Spiel selbst
│
Prozess/Fenster gefunden
│
┌────────▼───────────┐
│ 1. Prozess finden │
│ 2. Fenster erkennen│
│ 3. Größe abwarten │
│ 4. Spiel fokussieren│
│ 5. Wechsel beobachten│
│ 6. Script beendet │
└────────────────────┘
```
**Zwei Dateien**, davon eine nur noch optional:
| Datei | Sprache | Aufgabe |
|---|---|---|
| `focusfix.py` | Python 3 | Steam-Wrapper-Modus, Direktmodus per `--process`, Prozess-Monitoring, Fenstererkennung, Fensterwechsel-Erkennung, Fokus-Erzwingung, Logging |
| `launch.vbs` | VBScript | Legacy-Wrapper für ältere Steam-Konfigurationen; für den aktuellen Ablauf nicht mehr erforderlich |
## 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:\Path\To\FocusFix
# Virtual Environment erstellen
py -m venv .venv
# Aktivieren
.venv\Scripts\activate
# Abhängigkeiten installieren
pip install psutil pywin32
```
### Dateistruktur nach Installation
```
C:\Path\To\FocusFix\
├── .venv\ # Python Virtual Environment
├── focusfix.py # Hauptscript
├── focusfix.log # Logdatei (wird automatisch erstellt)
├── launch.vbs # Legacy-Wrapper, optional
├── 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** eine der folgenden Varianten eintragen:
#### Variante A — Empfohlen: direkt mit `focusfix.py`
```text
"C:\Path\To\FocusFix\.venv\Scripts\pythonw.exe" "C:\Path\To\FocusFix\focusfix.py" %command%
```
`focusfix.py` extrahiert den Prozessnamen automatisch aus `%command%`, startet das eigentliche Spiel und überwacht danach den Fensterfokus. Die gleiche Startoption kann für beliebige Steam-Spiele mit vergleichbarem Fokus-Problem verwendet werden.
Die Pfade im Beispiel sind Platzhalter und müssen durch deinen tatsächlichen Installationspfad ersetzt werden.
**Erklärung:**
- `pythonw.exe` — startet Python ohne Konsolenfenster
- `"...\focusfix.py"` — Python-Script im Steam-Wrapper-Modus
- `%command%` — Steam-Platzhalter, wird zur Laufzeit durch den vollständigen Pfad zur Spiel-Executable ersetzt, z.B. `"C:\SteamLibrary\...\Game.exe"`
#### Variante B — Legacy: über `launch.vbs`
Wenn du den alten VBScript-Wrapper weiterhin verwenden willst, kannst du in Steam Folgendes eintragen:
```text
wscript "C:\Path\To\FocusFix\launch.vbs" %command%
```
`launch.vbs` extrahiert den Prozessnamen aus `%command%`, startet `focusfix.py` unsichtbar mit `--process` und startet danach das eigentliche Spiel. Diese Variante funktioniert weiterhin, ist aber nur noch als Legacy-/Fallback-Weg gedacht, weil `focusfix.py` den Wrapper-Job inzwischen selbst übernehmen kann.
Die Pfade im Beispiel sind Platzhalter und müssen durch deinen tatsächlichen Installationspfad ersetzt werden.
**Erklärung:**
- `wscript` — startet VBScript ohne sichtbares Konsolenfenster
- `"...\launch.vbs"` — Legacy-Wrapper, der `focusfix.py` indirekt startet
- `%command%` — Steam-Platzhalter mit dem ursprünglichen Spielstartbefehl
#### Variante C — Release-Exe
Wenn du eine gebaute Release-Datei verwenden willst, kannst du statt Python direkt die `.exe` in Steam eintragen. In diesem Repository liegt aktuell eine Build-Datei unter `dist\nuinstall\FocusFix.exe`:
```text
"C:\Path\To\FocusFix\dist\nuinstall\FocusFix.exe" %command%
```
Die `.exe` übernimmt denselben Wrapper-Modus wie `focusfix.py`: Sie erhält `%command%`, startet das Spiel und überwacht danach den Fokus. Diese Variante ist praktisch, wenn du keine sichtbare Python-Installation oder kein venv in den Steam-Startoptionen referenzieren willst.
**Wichtig:** Der Dateiname der Release-Exe hängt vom Build ab. Wenn du später eine neu benannte Exe wie `FocusFix.exe` baust, muss genau dieser Pfad in Steam eingetragen werden.
Auch hier sind die Pfade im Beispiel Platzhalter und müssen durch deinen tatsächlichen Installationspfad ersetzt werden.
### Sunshine / Moonlight
In Sunshine als **Befehlsvorbereitung** (Command Preparation) pro Spiel eintragen:
```text
"C:\Path\To\FocusFix\.venv\Scripts\pythonw.exe" "C:\Path\To\FocusFix\focusfix.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 sonst möglicherweise eine andere Python-Installation.
## Ablauf zur Laufzeit
```text
Zeitachse
─────────────────────────────────────────────────────────────────────
t=0s Steam startet das Script mit %command%
oder Sunshine mit --process "GameName"
t=0.1s focusfix.py beginnt zu arbeiten
→ Steam-Modus: startet zuerst das Spiel und leitet den Prozessnamen ab
→ Danach: sucht nach Prozess "GameName"
t=~3s Spielprozess gestartet, ggf. Splashscreen erscheint
→ Script findet den Prozess (PID)
→ Sucht nach Fenster mit Breite >= 800px
→ Kleine Splashscreens werden ignoriert
t=~10s Hauptfenster des Spiels erscheint
→ Script findet das Fenster
→ Bevorzugt Fenster mit Titel
→ Wartet 3 Sekunden
→ Wartet bis Fenstergröße stabil ist
t=~13s Phase 3: Fokus-Erzwingung
→ Spielfenster wird in den Vordergrund gebracht
→ Bei Misserfolg: bis zu 15 Wiederholungen mit je 2s Pause
→ Beobachtet Fensterwechsel (z.B. Splash → Hauptfenster)
→ Ignoriert problematische titellose Overlay-Fenster
t=~15s Erfolg → focusfix.py beendet sich
→ Logdatei enthält den vollständigen Ablauf
```
## Dateireferenz
### focusfix.py
#### Funktionsübersicht
| Funktion | Beschreibung |
|---|---|
| `derive_process_name(command)` | Leitet den Prozessnamen aus dem ersten Element eines Spielbefehls ab |
| `launch_game(command)` | Startet das eigentliche Spiel im Steam-Wrapper-Modus |
| `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 und dann nach Größe |
| `wait_for_stable_size(hwnd)` | Wartet bis die Fenstergröße sich stabilisiert hat |
| `is_foreground(hwnd)` | Prüft ob ein Fenster aktuell im Vordergrund ist |
| `bring_to_front(hwnd)` | Erzwingt den Vordergrundfokus für ein Fenster |
| `main()` | Orchestriert Startmodus, Prozesssuche, Fenstersuche und Fokus-Erzwingung |
#### `main()` — Ablauf
1. **Startphase:** Entweder wird der Prozessname direkt über `--process` übergeben oder im Steam-Modus aus dem Spielbefehl abgeleitet.
2. **Steam-Wrapper-Modus:** Falls ein Spielbefehl übergeben wurde, startet das Script das Spiel selbst.
3. **Phase 1 — Prozess-Suche:** Pollt alle 0,5 Sekunden nach dem Prozessnamen. Bricht nach `--timeout` Sekunden ab.
4. **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.
5. **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 und fokussiert das neue Fenster erneut.
### launch.vbs
`launch.vbs` ist nur noch ein Legacy-Wrapper. Die aktuelle empfohlene Steam-Konfiguration nutzt `focusfix.py` direkt. Die Datei kann als Fallback im Repository bleiben, wird für den normalen Ablauf aber nicht mehr benötigt.
## Konfigurationsparameter
### Kommandozeilenargumente
| Argument | Pflicht | Standard | Beschreibung |
|---|---|---|---|
| `--process` | Nein | — | Teilstring des Prozessnamens (case-insensitiv, ohne `.exe`) |
| `--timeout` | Nein | `120` | Maximale Wartezeit in Sekunden für Prozess- und Fensterfund |
| `command` | Nein | — | Steam-Spielbefehl, z.B. `%command%`; startet das Spiel und leitet den Prozessnamen automatisch ab |
Es muss entweder `--process` oder ein Spielbefehl übergeben werden.
### Konstanten in `focusfix.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 |
### Relevante Pfade
| Pfad | Zweck |
|---|---|
| `C:\Path\To\FocusFix\.venv\Scripts\pythonw.exe` | Beispielpfad zum Python-Interpreter ohne Konsolenfenster, für Steam empfohlen |
| `C:\Path\To\FocusFix\focusfix.py` | Beispielpfad zum Hauptscript |
Bei einer Änderung des Installationsverzeichnisses müssen diese Pfade in den Steam-Startoptionen oder in Sunshine 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 eingeschränkt. Andernfalls blinkt das Fenster lediglich in der Taskleiste.
Da `focusfix.py` als eigenständiger Prozess läuft und nicht Teil der normalen Spiel-UI ist, erfüllt es diese Bedingungen oft nicht direkt.
### 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 eher wie einen Prozess mit Benutzerinteraktion.
**2. Thread-Input-Verbindung (`AttachThreadInput`)**
Verbindet die Eingabewarteschlangen zweier Threads. Dadurch kann das Ziel-Fenster zuverlässiger in den Vordergrund gebracht werden.
**3. Mehrfache Fokus-Befehle**
`ShowWindow`, `BringWindowToTop`, `SetForegroundWindow`, `SetFocus` und `SetActiveWindow` adressieren jeweils einen leicht anderen Aspekt des Fenster-Fokus unter Windows.
### Splashscreen-Erkennung
Viele Spiele zeigen beim Start einen Splashscreen, bevor das Hauptfenster erscheint. Beide gehören zum selben Prozess.
Das wichtigste Unterscheidungsmerkmal ist hier die Fenstergröße. Kleine Startfenster werden über `MIN_WINDOW_WIDTH = 800` aussortiert. Zusätzlich werden Fenster mit Titel bevorzugt, um titellose Overlay-Fenster nicht fälschlich als Hauptfenster zu wählen.
## Manuelles Testen
### Direktmodus testen
```bash
cd C:\Path\To\FocusFix
.venv\Scripts\python focusfix.py --process "CrimsonDesert" --timeout 5
```
### Steam-Wrapper-Modus testen
```bash
.venv\Scripts\python focusfix.py "C:\Path\To\Game\TestGame.exe"
```
Die Laufzeit-Logs landen in `focusfix.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.
### Gesamtablauf testen
1. Steam-Startoptionen wie oben konfigurieren.
2. Spiel über Steam starten.
3. Erwartung: Spielfenster erscheint nach dem Ladescreen im Vordergrund, ohne zusätzliches Konsolenfenster.
4. `focusfix.log` prüfen.
## Debugging
Alle Informationen werden in `focusfix.log` protokolliert:
- Gefundene Kandidaten-Fenster mit Handle, Titel, Größe und PID
- Fensterwechsel und ignorierte titellose Fenster
- Fokus-Versuche mit Ergebnis
- Fehler mit Traceback
### Häufige Probleme
| Symptom | Ursache | Lösung |
|---|---|---|
| Spiel startet nicht | Steam-Startoptionen fehlerhaft | Prüfen ob `%command%` am Ende steht und `pythonw.exe`/`focusfix.py` korrekt referenziert werden |
| `ModuleNotFoundError: psutil` | Falsche Python-Installation | Vollständigen Pfad zu `.venv\Scripts\pythonw.exe` verwenden |
| Script findet Prozess nicht | Prozessname stimmt nicht oder Launcher-Kette ist ungewöhnlich | Mit `--process` testen oder Log 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` | Steam-/Sunshine-Konfiguration prüfen |
| Verdunkeltes/blockiertes Spiel | Titelloses Overlay-Fenster | Log prüfen; sollte meist durch Fenster-mit-Titel-Bevorzugung abgefangen werden |
### Fenster-Inspektion per PowerShell
```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. Dann muss das Script ebenfalls als Admin laufen.
- **Exklusiver Vollbildmodus:** Spiele im exklusiven Vollbild können sich anders verhalten.
- **Anti-Cheat:** Manche Anti-Cheat-Systeme könnten Eingabesimulation oder Fokus-Workarounds blockieren.
- **Mehrere Monitore:** `find_game_window` wählt das Fenster unabhängig vom Monitor.
- **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.