Files
FocusFix/README.md
T
Andre 9747022ad6 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.
2026-04-18 17:33:58 +02:00

434 lines
18 KiB
Markdown

# FocusFix - Game Foreground Fix for Windows
Windows-Hilfsprogramm, das ein Spielfenster automatisch in den Vordergrund bringt. Gedacht für Spiele (z.b. Crimson Desert), 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
- [Schnellstart](#schnellstart)
- [Für Nutzer](#fur-nutzer)
- [Für Entwickler](#fur-entwickler)
- [Problembeschreibung](#problembeschreibung)
- [Lösungsansatz](#lösungsansatz)
- [Architektur](#architektur)
- [Voraussetzungen](#voraussetzungen)
- [Direkte Verwendung](#direkte-verwendung)
- [Aus Quellcode ausführen](#aus-quellcode-ausfuhren)
- [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)
---
## Schnellstart
### Für Nutzer
Wenn du FocusFix direkt verwenden willst, ist eine fertige `FocusFix.exe` der empfohlene Weg. Du brauchst dann weder ein Virtual Environment noch ein manuelles `pip install`.
Steam-Beispiel:
```text
"C:\Path\To\FocusFix\FocusFix.exe" %command%
```
Mit Debug-Logging:
```text
"C:\Path\To\FocusFix\FocusFix.exe" -debug %command%
```
### Für Entwickler
Wenn du FocusFix aus dem Repository starten oder weiterentwickeln willst:
```bash
cd C:\Path\To\FocusFix
py -m pip install -r requirements.txt
```
Danach kannst du `focusfix.py` direkt mit deinem Python-Interpreter oder über `pythonw.exe` verwenden.
Steam-Beispiel für die Quellcode-Nutzung mit lokalem `.venv`:
```text
"C:\Projekte\FocusFix\.venv\Scripts\pythonw.exe" "C:\Projekte\FocusFix\focusfix.py" %command%
```
Ohne lokales `.venv` und mit globalem Python im `PATH`:
```text
"pythonw.exe" "C:\Projekte\FocusFix\focusfix.py" %command%
```
## 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) |
| Steam | Beliebig | Startoptionen müssen konfigurierbar sein |
| Python | 3.10+ | Nur für Quellcode-Nutzung erforderlich |
| pip-Pakete | `psutil`, `pywin32` | Nur für Quellcode-Nutzung erforderlich |
## Direkte Verwendung
Für normale Nutzer ist eine fertige `.exe` der einfachste Weg.
## Aus Quellcode ausführen
```bash
cd C:\Path\To\FocusFix
py -m pip install -r requirements.txt
```
Optional kannst du dafür auch ein Virtual Environment verwenden. Für Entwickler ist das sinnvoll, für Endanwender aber nicht der Standardweg.
### Dateistruktur im Repository
```
C:\Path\To\FocusFix\
├── focusfix.py # Hauptscript
├── focusfix.log # Logdatei (nur bei --debug / -debug)
├── launch.vbs # Legacy-Wrapper, optional
├── requirements.txt # Python-Abhängigkeiten für Quellcode-Nutzung
├── 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 für Nutzer: `FocusFix.exe`
```text
"C:\Path\To\FocusFix\FocusFix.exe" %command%
```
Mit Debug-Logging:
```text
"C:\Path\To\FocusFix\FocusFix.exe" -debug %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 für Endanwender gedacht.
Die Pfade im Beispiel sind Platzhalter und müssen durch deinen tatsächlichen Installationspfad ersetzt werden.
#### Variante B — Quellcode direkt mit `focusfix.py`
```text
"C:\Path\To\Python\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 C — 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%
Beispiel: "C:\Projekte\FocusFix\launch.vbs" %command%
```
Mit Debug-Logging:
```text
wscript "C:\Path\To\FocusFix\launch.vbs" -debug %command%
wscript "C:\Projekte\FocusFix\launch.vbs" -debug %command%
```
`launch.vbs` extrahiert den Prozessnamen aus `%command%`, startet `focusfix.py` unsichtbar mit `--process` und startet danach das eigentliche Spiel. `-d`, `-debug` und `--debug` werden an `focusfix.py` weitergereicht und nicht an das Spiel übergeben. Diese Variante funktioniert weiterhin, ist aber nur noch als Legacy-/Fallback-Weg gedacht, weil `focusfix.py` den Wrapper-Job inzwischen selbst übernehmen kann.
Der Wrapper erwartet `focusfix.py` relativ zu seinem eigenen Speicherort. Für Python nutzt er zuerst `.venv\Scripts\pythonw.exe` im selben Ordner und fällt, falls nicht vorhanden, auf `pythonw.exe` aus der globalen Installation beziehungsweise dem `PATH` zurück.
**Erklärung:**
- `wscript` — startet VBScript ohne sichtbares Konsolenfenster
- `"...\launch.vbs"` — Legacy-Wrapper, der `focusfix.py` indirekt startet
- `-debug` — optional, aktiviert `focusfix.log` für `focusfix.py`
- `%command%` — Steam-Platzhalter mit dem ursprünglichen Spielstartbefehl
### Sunshine / Moonlight
Für Nutzer mit `.exe`:
```text
"C:\Path\To\FocusFix\FocusFix.exe" --process "Prozessname"
```
Mit Debug-Logging:
```text
"C:\Path\To\FocusFix\FocusFix.exe" --debug --process "Prozessname"
```
Für Quellcode-Nutzung in Sunshine als **Befehlsvorbereitung** (Command Preparation) pro Spiel:
```text
"C:\Path\To\Python\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:** Wenn du den Quellcode-Weg nutzt, muss der verwendete Python-Interpreter Zugriff auf die installierten Abhängigkeiten aus `requirements.txt` haben. `pythonw.exe` ist für unsichtbare Hintergrundausführung sinnvoller als `python.exe`.
## 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 Nutzung für Endanwender erfolgt über `FocusFix.exe`. Die Datei kann als Fallback im Repository bleiben, wird für den normalen Ablauf aber nicht mehr benötigt. Der Wrapper löst `focusfix.py` relativ zu seinem eigenen Ordner auf, verwendet bevorzugt `.venv\Scripts\pythonw.exe` daneben und fällt sonst auf `pythonw.exe` aus der globalen Python-Installation beziehungsweise dem `PATH` zurück. `-d`/`-debug`/`--debug` kann er an `focusfix.py` weitergeben.
## 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\FocusFix.exe` | Empfohlener Pfad für Endanwender |
| `C:\Path\To\Python\pythonw.exe` | Beispielpfad zum Python-Interpreter ohne Konsolenfenster, für Quellcode-Nutzung |
| `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
python focusfix.py --process "CrimsonDesert" --timeout 5
```
### Steam-Wrapper-Modus testen
```bash
python focusfix.py "C:\Path\To\Game\TestGame.exe"
```
Standardmäßig erzeugt FocusFix keine Logdatei. Mit `--debug` oder `-debug` landen die Laufzeit-Logs in `focusfix.log`; bei jedem neuen Scriptstart wird die Datei dabei überschrieben. 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. Falls mit `--debug` gestartet wurde, `focusfix.log` prüfen.
## Debugging
Für die Fehlersuche FocusFix mit `--debug` oder `-debug` starten. Dann werden alle Informationen in `focusfix.log` protokolliert; ein vorhandenes Logfile aus einem früheren Lauf wird dabei ersetzt:
- 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 `.exe` oder `pythonw.exe`/`focusfix.py` korrekt referenziert werden |
| `ModuleNotFoundError: psutil` | Falsche Python-Installation | `py -m pip install -r requirements.txt` ausführen oder den richtigen Python-Interpreter 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 oder `.exe` verwenden |
| 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.