refactor: Steam-Wrapper-Logik nach focusfix.py verlagert , launch.vbs jetzt obsolet/optional

- Projektname auf FocusFix geändert
This commit is contained in:
Andre
2026-04-18 16:10:59 +02:00
parent 654a094cb5
commit a00bd1ed30
4 changed files with 208 additions and 214 deletions
+154 -193
View File
@@ -1,6 +1,6 @@
# Game Foreground Fix
# FocusFix - Game Foreground Fix for Windows
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).
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).
---
@@ -16,8 +16,8 @@ Windows-Hilfsprogramm, das ein Spielfenster automatisch in den Vordergrund bring
- [Sunshine / Moonlight](#sunshine--moonlight)
- [Ablauf zur Laufzeit](#ablauf-zur-laufzeit)
- [Dateireferenz](#dateireferenz)
- [focusfix.py](#focusfixpy)
- [launch.vbs](#launchvbs)
- [foreground_fix.py](#foreground_fixpy)
- [Konfigurationsparameter](#konfigurationsparameter)
- [Windows API — Technischer Hintergrund](#windows-api--technischer-hintergrund)
- [Das SetForegroundWindow-Problem](#das-setforegroundwindow-problem)
@@ -31,15 +31,18 @@ Windows-Hilfsprogramm, das ein Spielfenster automatisch in den Vordergrund bring
## 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.
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 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.
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
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.
`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
@@ -47,31 +50,36 @@ Ein Python-Script überwacht den Spielstart, wartet auf das Hauptfenster des Spi
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 │
└────────────────────┘
┌────────────────────┐
│ 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**, klare Aufgabentrennung:
**Zwei Dateien**, davon eine nur noch optional:
| 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 |
| `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
@@ -85,7 +93,7 @@ Steam Launch Options / Sunshine Befehlsvorbereitung
## Installation
```bash
cd C:\Projekte\Steam_backgroud_fix
cd C:\Path\To\FocusFix
# Virtual Environment erstellen
py -m venv .venv
@@ -100,11 +108,11 @@ pip install psutil pywin32
### Dateistruktur nach Installation
```
C:\Projekte\Steam_backgroud_fix\
C:\Path\To\FocusFix\
├── .venv\ # Python Virtual Environment
├── foreground_fix.py # Hauptscript (Kernlogik)
├── foreground_fix.log # Logdatei (wird automatisch erstellt)
├── launch.vbs # VBScript-Wrapper für Steam
├── 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
```
@@ -115,167 +123,129 @@ C:\Projekte\Steam_backgroud_fix\
1. Steam öffnen → **Bibliothek**
2. Rechtsklick auf das Spiel → **Eigenschaften**
3. Unter **Allgemein → Startoptionen** eintragen:
3. Unter **Allgemein → Startoptionen** eine der folgenden Varianten eintragen:
```
wscript "C:\Projekte\Steam_backgroud_fix\launch.vbs" %command%
#### Variante A — Empfohlen: direkt mit `focusfix.py`
```text
"C:\Path\To\FocusFix\.venv\Scripts\pythonw.exe" "C:\Path\To\FocusFix\focusfix.py" %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.
`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:**
- `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"`)
- `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:
```
"C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe" "C:\Projekte\Steam_backgroud_fix\foreground_fix.py" --process "Prozessname"
```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:
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.
**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/Sunshine startet das Script mit --process "GameName"
t=0.1s foreground_fix.py beginnt zu pollen (alle 0.5s)
→ Sucht nach Prozess "GameName"
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
→ Splashscreen ist kleiner → wird ignoriert
→ Kleine Splashscreens werden ignoriert
t=~10s Hauptfenster des Spiels erscheint (z.B. 2560×1440)
→ Script findet das Fenster (bevorzugt Fenster mit Titel)
→ Wartet 3 Sekunden (Initialisierung abwarten)
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 (je 2s Pause)
→ Beobachtet ob sich das Fenster ändert (Splash → Hauptfenster)
→ Ignoriert titellose Overlay-Fenster
→ Bei Misserfolg: bis zu 15 Wiederholungen mit je 2s Pause
→ Beobachtet Fensterwechsel (z.B. Splash → Hauptfenster)
→ Ignoriert problematische titellose Overlay-Fenster
t=~15s Erfolg → foreground_fix.py beendet sich
t=~15s Erfolg → focusfix.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
### 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, dann nach Größe |
| `wait_for_stable_size(hwnd)` | Wartet bis die Fenstergröße sich stabilisiert hat (kein Resize mehr) |
| `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 (Alt-Key + AttachThreadInput) |
| `main()` | Orchestriert den 3-Phasen-Ablauf |
| `bring_to_front(hwnd)` | Erzwingt den Vordergrundfokus für ein Fenster |
| `main()` | Orchestriert Startmodus, Prozesssuche, Fenstersuche und Fokus-Erzwingung |
#### `find_game_window(pids)` — Fensterauswahl
#### `main()` — Ablauf
Nutzt `win32gui.EnumWindows()` um alle Top-Level-Fenster zu durchlaufen. Für jedes Fenster:
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.
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)
### launch.vbs
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.
`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
@@ -283,10 +253,13 @@ Die Alt-Taste wird in einem `finally`-Block freigegeben, damit sie bei Fehlern (
| Argument | Pflicht | Standard | Beschreibung |
|---|---|---|---|
| `--process` | Ja | — | Teilstring des Prozessnamens (case-insensitiv, ohne `.exe`) |
| `--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 |
### Konstanten in `foreground_fix.py`
Es muss entweder `--process` oder ein Spielbefehl übergeben werden.
### Konstanten in `focusfix.py`
| Konstante | Wert | Beschreibung |
|---|---|---|
@@ -294,28 +267,22 @@ Die Alt-Taste wird in einem `finally`-Block freigegeben, damit sie bei Fehlern (
| `FOCUS_RETRIES` | `15` | Maximale Anzahl der Fokus-Versuche |
| `FOCUS_RETRY_DELAY` | `2.0` | Sekunden zwischen Fokus-Versuchen |
### Hardcodierte Pfade in `launch.vbs`
### Relevante Pfade
| Pfad | Zweck |
|---|---|
| `C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe` | Python-Interpreter (windowless) |
| `C:\Projekte\Steam_backgroud_fix\foreground_fix.py` | Hauptscript |
| `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 `launch.vbs` angepasst werden.
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, wenn:
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.
- 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.
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
@@ -323,83 +290,77 @@ 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.
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. 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.
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. Die Kombination stellt sicher, dass das Fenster sowohl visuell im Vordergrund ist als auch Tastatur- und Mauseingaben empfängt.
`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.
**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.
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
### Script alleine testen (Spiel muss laufen)
### Direktmodus testen
```bash
cd C:\Projekte\Steam_backgroud_fix
.venv\Scripts\python foreground_fix.py --process "CrimsonDesert" --timeout 5
cd C:\Path\To\FocusFix
.venv\Scripts\python focusfix.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
### Steam-Wrapper-Modus testen
```bash
wscript "C:\Projekte\Steam_backgroud_fix\launch.vbs" "C:\dummy\TestGame.exe"
.venv\Scripts\python focusfix.py "C:\Path\To\Game\TestGame.exe"
```
Sollte das Python-Script unsichtbar starten mit `--process "TestGame"`. Prüfen ob `pythonw.exe` im Task-Manager erscheint.
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, keine Taskleiste sichtbar, keine CMD- oder Script-Fenster
4. `foreground_fix.log` prüfen für den detaillierten Ablauf
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 `foreground_fix.log` protokolliert:
Alle Informationen werden in `focusfix.log` protokolliert:
- Gefundene Kandidaten-Fenster (Handle, Titel, Größe, PID)
- Gefundene Kandidaten-Fenster mit Handle, Titel, Größe und PID
- Fensterwechsel und ignorierte titellose Fenster
- Fokus-Versuche mit Ergebnis (Erfolg/Misserfolg)
- Fehler mit vollständigem Traceback
- 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 |
| `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 |
| 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` | 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 |
| 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
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
- **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.