mirror of
https://github.com/Andre-Bzk/FocusFix.git
synced 2026-11-15 09:33:14 +01:00
refactor: Steam-Wrapper-Logik nach focusfix.py verlagert , launch.vbs jetzt obsolet/optional
- Projektname auf FocusFix geändert
This commit is contained in:
@@ -4,12 +4,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
Windows utility that forces a game window into the foreground after launch. Works with **Steam** (automatic process name extraction from `%command%`) and **Sunshine/Moonlight** (manual `--process` argument).
|
FocusFix is a Windows utility that forces a game window into the foreground after launch. Works with **Steam** (directly via `%command%`) and **Sunshine/Moonlight** (manual `--process` argument).
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- **`launch.vbs`** — VBScript entry point called from Steam launch options. Extracts the game executable name from `%command%` via `FileSystemObject.GetBaseName()`, passes it as `--process` to `foreground_fix.py`, then launches the actual game. No console window is created.
|
- **`focusfix.py`** — Python script that supports two modes: `--process Name` for monitoring an already-known process, or direct Steam wrapper mode where `%command%` is passed as positional arguments. In wrapper mode it derives the process name from the target executable, launches the game itself, then polls for the game process, waits for its main window (filtered by size to skip splash screens, preferring windows with titles over titleless overlays), waits for the window size to stabilize, then brings the game window to foreground using Win32 API workarounds (`AttachThreadInput` + `Alt` key simulation to bypass `SetForegroundWindow` restrictions). Monitors for window changes (splash → main window) and re-focuses automatically. Retries up to 15 times. Runtime logging goes to `focusfix.log`.
|
||||||
- **`foreground_fix.py`** — Python script that polls for the game process, waits for its main window (filtered by size to skip splash screens, preferring windows with titles over titleless overlays), waits for the window size to stabilize, then brings the game window to foreground using Win32 API workarounds (`AttachThreadInput` + `Alt` key simulation to bypass `SetForegroundWindow` restrictions). Monitors for window changes (splash → main window) and re-focuses automatically. Retries up to 15 times. Runtime logging goes to `foreground_fix.log`.
|
- **`launch.vbs`** — Legacy Steam wrapper. No longer required for normal usage, but still present in the repository.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@@ -22,29 +22,30 @@ pip install psutil pywin32
|
|||||||
## Steam Launch Options
|
## Steam Launch Options
|
||||||
|
|
||||||
```
|
```
|
||||||
wscript "C:\Projekte\Steam_backgroud_fix\launch.vbs" %command%
|
"C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe" "C:\Projekte\Steam_backgroud_fix\focusfix.py" %command%
|
||||||
```
|
```
|
||||||
|
|
||||||
Same launch option works for all Steam games — process name is extracted automatically.
|
Same launch option works for all Steam games with similar foreground/focus issues — the process name is extracted automatically.
|
||||||
|
|
||||||
## Sunshine / Moonlight
|
## Sunshine / Moonlight
|
||||||
|
|
||||||
Use as command preparation in Sunshine (full venv path required):
|
Use as command preparation in Sunshine (full venv path required):
|
||||||
|
|
||||||
```
|
```
|
||||||
"C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe" "C:\Projekte\Steam_backgroud_fix\foreground_fix.py" --process "ProcessName"
|
"C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe" "C:\Projekte\Steam_backgroud_fix\focusfix.py" --process "ProcessName"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running Manually (for testing)
|
## Running Manually (for testing)
|
||||||
|
|
||||||
```
|
```
|
||||||
.venv\Scripts\python foreground_fix.py --process CrimsonDesert
|
.venv\Scripts\python focusfix.py --process CrimsonDesert
|
||||||
.venv\Scripts\python foreground_fix.py --process CrimsonDesert --timeout 180
|
.venv\Scripts\python focusfix.py --process CrimsonDesert --timeout 180
|
||||||
|
.venv\Scripts\python focusfix.py "C:\Games\CrimsonDesert.exe"
|
||||||
```
|
```
|
||||||
|
|
||||||
`--process` is required (no default). Runtime logging goes to `foreground_fix.log`. When started with `pythonw.exe`, no console window is shown; manual runs with `python.exe` can still print argument or interpreter errors to the console.
|
Use either `--process` or a game command. Runtime logging goes to `focusfix.log`. When started with `pythonw.exe`, no console window is shown; manual runs with `python.exe` can still print argument or interpreter errors to the console.
|
||||||
|
|
||||||
## Key Constants in foreground_fix.py
|
## Key Constants in focusfix.py
|
||||||
|
|
||||||
- `MIN_WINDOW_WIDTH = 800` — distinguishes game window from splash screen
|
- `MIN_WINDOW_WIDTH = 800` — distinguishes game window from splash screen
|
||||||
- `FOCUS_RETRIES = 15` / `FOCUS_RETRY_DELAY = 2.0` — retry parameters for foreground focus
|
- `FOCUS_RETRIES = 15` / `FOCUS_RETRY_DELAY = 2.0` — retry parameters for foreground focus
|
||||||
@@ -54,5 +55,5 @@ Use as command preparation in Sunshine (full venv path required):
|
|||||||
- `SetForegroundWindow` is restricted by Windows unless the calling process owns the foreground. Workaround: simulate an `Alt` keypress + `AttachThreadInput` to the foreground thread.
|
- `SetForegroundWindow` is restricted by Windows unless the calling process owns the foreground. Workaround: simulate an `Alt` keypress + `AttachThreadInput` to the foreground thread.
|
||||||
- Alt key release is in a `finally` block to prevent it from staying pressed on errors.
|
- Alt key release is in a `finally` block to prevent it from staying pressed on errors.
|
||||||
- `pythonw.exe` (not `python.exe`) is required for invisible background execution.
|
- `pythonw.exe` (not `python.exe`) is required for invisible background execution.
|
||||||
- VBScript `WshShell.Run` with window style `0` ensures no console window appears.
|
- `launch.vbs` is legacy-only; normal Steam usage now calls `focusfix.py` directly.
|
||||||
- Window selection prefers titled windows over titleless ones (prevents overlay issues like in Wuthering Waves).
|
- Window selection prefers titled windows over titleless ones (prevents overlay issues like in Wuthering Waves).
|
||||||
|
|||||||
@@ -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)
|
- [Sunshine / Moonlight](#sunshine--moonlight)
|
||||||
- [Ablauf zur Laufzeit](#ablauf-zur-laufzeit)
|
- [Ablauf zur Laufzeit](#ablauf-zur-laufzeit)
|
||||||
- [Dateireferenz](#dateireferenz)
|
- [Dateireferenz](#dateireferenz)
|
||||||
|
- [focusfix.py](#focusfixpy)
|
||||||
- [launch.vbs](#launchvbs)
|
- [launch.vbs](#launchvbs)
|
||||||
- [foreground_fix.py](#foreground_fixpy)
|
|
||||||
- [Konfigurationsparameter](#konfigurationsparameter)
|
- [Konfigurationsparameter](#konfigurationsparameter)
|
||||||
- [Windows API — Technischer Hintergrund](#windows-api--technischer-hintergrund)
|
- [Windows API — Technischer Hintergrund](#windows-api--technischer-hintergrund)
|
||||||
- [Das SetForegroundWindow-Problem](#das-setforegroundwindow-problem)
|
- [Das SetForegroundWindow-Problem](#das-setforegroundwindow-problem)
|
||||||
@@ -31,15 +31,18 @@ Windows-Hilfsprogramm, das ein Spielfenster automatisch in den Vordergrund bring
|
|||||||
|
|
||||||
## Problembeschreibung
|
## 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.
|
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.
|
||||||
|
|
||||||
Zusätzlich erzeugen manche Spiele beim Start mehrere Fenster — Splashscreens, Launcher oder titellose Overlay-Fenster — die den Fokus stören können.
|
|
||||||
|
|
||||||
## Lösungsansatz
|
## 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
|
## Architektur
|
||||||
|
|
||||||
@@ -47,31 +50,36 @@ Ein Python-Script überwacht den Spielstart, wartet auf das Hauptfenster des Spi
|
|||||||
Steam Launch Options / Sunshine Befehlsvorbereitung
|
Steam Launch Options / Sunshine Befehlsvorbereitung
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
┌──────────┐ extrahiert Prozessname ┌────────────────────┐
|
┌────────────────────┐
|
||||||
│ launch.vbs├──── aus %command% ────────────►│ pythonw.exe │
|
│ pythonw.exe │
|
||||||
│ (nur Steam)│ + startet unsichtbar │ foreground_fix.py │
|
│ focusfix.py │
|
||||||
│ │ │ --process GameName │
|
│ │
|
||||||
│ │ startet normal │ │
|
│ Steam: %command% │
|
||||||
│ ├────────────────────────────────►│ (wartet auf Spiel) │
|
│ Sunshine: │
|
||||||
└──────────┘ %command% = Spiel.exe └────────┬───────────┘
|
│ --process Name │
|
||||||
│
|
└────────┬───────────┘
|
||||||
Fenster gefunden
|
│
|
||||||
│
|
Steam-Modus:
|
||||||
┌────────▼───────────┐
|
startet das Spiel selbst
|
||||||
│ 1. Fenster erkennen│
|
│
|
||||||
│ 2. Größe abwarten │
|
Prozess/Fenster gefunden
|
||||||
│ 3. Spiel fokussieren│
|
│
|
||||||
│ 4. Wechsel beobachten│
|
┌────────▼───────────┐
|
||||||
│ 5. Script beendet │
|
│ 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 |
|
| Datei | Sprache | Aufgabe |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `launch.vbs` | VBScript | Startrampe (nur Steam): extrahiert Prozessname aus `%command%`, startet das Python-Script unsichtbar und leitet den Spielstart weiter |
|
| `focusfix.py` | Python 3 | Steam-Wrapper-Modus, Direktmodus per `--process`, Prozess-Monitoring, Fenstererkennung, Fensterwechsel-Erkennung, Fokus-Erzwingung, Logging |
|
||||||
| `foreground_fix.py` | Python 3 | Kernlogik: 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
|
## Voraussetzungen
|
||||||
|
|
||||||
@@ -85,7 +93,7 @@ Steam Launch Options / Sunshine Befehlsvorbereitung
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd C:\Projekte\Steam_backgroud_fix
|
cd C:\Path\To\FocusFix
|
||||||
|
|
||||||
# Virtual Environment erstellen
|
# Virtual Environment erstellen
|
||||||
py -m venv .venv
|
py -m venv .venv
|
||||||
@@ -100,11 +108,11 @@ pip install psutil pywin32
|
|||||||
### Dateistruktur nach Installation
|
### Dateistruktur nach Installation
|
||||||
|
|
||||||
```
|
```
|
||||||
C:\Projekte\Steam_backgroud_fix\
|
C:\Path\To\FocusFix\
|
||||||
├── .venv\ # Python Virtual Environment
|
├── .venv\ # Python Virtual Environment
|
||||||
├── foreground_fix.py # Hauptscript (Kernlogik)
|
├── focusfix.py # Hauptscript
|
||||||
├── foreground_fix.log # Logdatei (wird automatisch erstellt)
|
├── focusfix.log # Logdatei (wird automatisch erstellt)
|
||||||
├── launch.vbs # VBScript-Wrapper für Steam
|
├── launch.vbs # Legacy-Wrapper, optional
|
||||||
├── CLAUDE.md # Kontext für Claude Code
|
├── CLAUDE.md # Kontext für Claude Code
|
||||||
└── README.md # Diese Dokumentation
|
└── README.md # Diese Dokumentation
|
||||||
```
|
```
|
||||||
@@ -115,167 +123,129 @@ C:\Projekte\Steam_backgroud_fix\
|
|||||||
|
|
||||||
1. Steam öffnen → **Bibliothek**
|
1. Steam öffnen → **Bibliothek**
|
||||||
2. Rechtsklick auf das Spiel → **Eigenschaften**
|
2. Rechtsklick auf das Spiel → **Eigenschaften**
|
||||||
3. Unter **Allgemein → Startoptionen** eintragen:
|
3. Unter **Allgemein → Startoptionen** eine der folgenden Varianten eintragen:
|
||||||
|
|
||||||
```
|
#### Variante A — Empfohlen: direkt mit `focusfix.py`
|
||||||
wscript "C:\Projekte\Steam_backgroud_fix\launch.vbs" %command%
|
|
||||||
|
```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:**
|
**Erklärung:**
|
||||||
- `wscript` — Windows Script Host, führt VBScript ohne Konsolenfenster aus
|
- `pythonw.exe` — startet Python ohne Konsolenfenster
|
||||||
- `"...\launch.vbs"` — Pfad zum VBScript-Wrapper
|
- `"...\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"`)
|
- `%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
|
### Sunshine / Moonlight
|
||||||
|
|
||||||
In Sunshine als **Befehlsvorbereitung** (Command Preparation) pro Spiel eintragen:
|
In Sunshine als **Befehlsvorbereitung** (Command Preparation) pro Spiel eintragen:
|
||||||
|
|
||||||
```
|
```text
|
||||||
"C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe" "C:\Projekte\Steam_backgroud_fix\foreground_fix.py" --process "Prozessname"
|
"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 |
|
| Spiel | Prozessname |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Crimson Desert | `CrimsonDesert` |
|
| Crimson Desert | `CrimsonDesert` |
|
||||||
| Wuthering Waves | `Client-Win64-Shipping` |
|
| 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
|
## Ablauf zur Laufzeit
|
||||||
|
|
||||||
```
|
```text
|
||||||
Zeitachse
|
Zeitachse
|
||||||
─────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────
|
||||||
t=0s Steam/Sunshine startet das Script mit --process "GameName"
|
t=0s Steam startet das Script mit %command%
|
||||||
t=0.1s foreground_fix.py beginnt zu pollen (alle 0.5s)
|
oder Sunshine mit --process "GameName"
|
||||||
→ Sucht nach Prozess "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
|
t=~3s Spielprozess gestartet, ggf. Splashscreen erscheint
|
||||||
→ Script findet den Prozess (PID)
|
→ Script findet den Prozess (PID)
|
||||||
→ Sucht nach Fenster mit Breite >= 800px
|
→ 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)
|
t=~10s Hauptfenster des Spiels erscheint
|
||||||
→ Script findet das Fenster (bevorzugt Fenster mit Titel)
|
→ Script findet das Fenster
|
||||||
→ Wartet 3 Sekunden (Initialisierung abwarten)
|
→ Bevorzugt Fenster mit Titel
|
||||||
|
→ Wartet 3 Sekunden
|
||||||
→ Wartet bis Fenstergröße stabil ist
|
→ Wartet bis Fenstergröße stabil ist
|
||||||
|
|
||||||
t=~13s Phase 3: Fokus-Erzwingung
|
t=~13s Phase 3: Fokus-Erzwingung
|
||||||
→ Spielfenster wird in den Vordergrund gebracht
|
→ Spielfenster wird in den Vordergrund gebracht
|
||||||
→ Bei Misserfolg: bis zu 15 Wiederholungen (je 2s Pause)
|
→ Bei Misserfolg: bis zu 15 Wiederholungen mit je 2s Pause
|
||||||
→ Beobachtet ob sich das Fenster ändert (Splash → Hauptfenster)
|
→ Beobachtet Fensterwechsel (z.B. Splash → Hauptfenster)
|
||||||
→ Ignoriert titellose Overlay-Fenster
|
→ 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
|
→ Logdatei enthält den vollständigen Ablauf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dateireferenz
|
## Dateireferenz
|
||||||
|
|
||||||
### launch.vbs
|
### focusfix.py
|
||||||
|
|
||||||
```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
|
#### Funktionsübersicht
|
||||||
|
|
||||||
| Funktion | Beschreibung |
|
| 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_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 |
|
| `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 (kein Resize mehr) |
|
| `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 |
|
| `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) |
|
| `bring_to_front(hwnd)` | Erzwingt den Vordergrundfokus für ein Fenster |
|
||||||
| `main()` | Orchestriert den 3-Phasen-Ablauf |
|
| `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`)
|
### launch.vbs
|
||||||
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.
|
`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.
|
||||||
|
|
||||||
#### `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
|
## Konfigurationsparameter
|
||||||
|
|
||||||
@@ -283,10 +253,13 @@ Die Alt-Taste wird in einem `finally`-Block freigegeben, damit sie bei Fehlern (
|
|||||||
|
|
||||||
| Argument | Pflicht | Standard | Beschreibung |
|
| 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 |
|
| `--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 |
|
| 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_RETRIES` | `15` | Maximale Anzahl der Fokus-Versuche |
|
||||||
| `FOCUS_RETRY_DELAY` | `2.0` | Sekunden zwischen Fokus-Versuchen |
|
| `FOCUS_RETRY_DELAY` | `2.0` | Sekunden zwischen Fokus-Versuchen |
|
||||||
|
|
||||||
### Hardcodierte Pfade in `launch.vbs`
|
### Relevante Pfade
|
||||||
|
|
||||||
| Pfad | Zweck |
|
| Pfad | Zweck |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe` | Python-Interpreter (windowless) |
|
| `C:\Path\To\FocusFix\.venv\Scripts\pythonw.exe` | Beispielpfad zum Python-Interpreter ohne Konsolenfenster, für Steam empfohlen |
|
||||||
| `C:\Projekte\Steam_backgroud_fix\foreground_fix.py` | Hauptscript |
|
| `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
|
## Windows API — Technischer Hintergrund
|
||||||
|
|
||||||
### Das SetForegroundWindow-Problem
|
### 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**
|
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.
|
||||||
- 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
|
### Workaround-Strategie
|
||||||
|
|
||||||
@@ -323,83 +290,77 @@ Das Script kombiniert drei Techniken:
|
|||||||
|
|
||||||
**1. Alt-Key-Simulation (`keybd_event`)**
|
**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`)**
|
**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**
|
**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
|
### Splashscreen-Erkennung
|
||||||
|
|
||||||
Viele Spiele zeigen beim Start einen Splashscreen, bevor das Hauptfenster erscheint. Beide gehören zum selben Prozess.
|
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.
|
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.
|
||||||
|
|
||||||
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
|
## Manuelles Testen
|
||||||
|
|
||||||
### Script alleine testen (Spiel muss laufen)
|
### Direktmodus testen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd C:\Projekte\Steam_backgroud_fix
|
cd C:\Path\To\FocusFix
|
||||||
.venv\Scripts\python foreground_fix.py --process "CrimsonDesert" --timeout 5
|
.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.
|
### Steam-Wrapper-Modus testen
|
||||||
|
|
||||||
### VBScript alleine testen
|
|
||||||
|
|
||||||
```bash
|
```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
|
### Gesamtablauf testen
|
||||||
|
|
||||||
1. Steam-Startoptionen wie oben konfigurieren
|
1. Steam-Startoptionen wie oben konfigurieren.
|
||||||
2. Spiel über Steam starten
|
2. Spiel über Steam starten.
|
||||||
3. Erwartung: Spielfenster erscheint nach dem Ladescreen im Vordergrund, keine Taskleiste sichtbar, keine CMD- oder Script-Fenster
|
3. Erwartung: Spielfenster erscheint nach dem Ladescreen im Vordergrund, ohne zusätzliches Konsolenfenster.
|
||||||
4. `foreground_fix.log` prüfen für den detaillierten Ablauf
|
4. `focusfix.log` prüfen.
|
||||||
|
|
||||||
## Debugging
|
## 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
|
- Fensterwechsel und ignorierte titellose Fenster
|
||||||
- Fokus-Versuche mit Ergebnis (Erfolg/Misserfolg)
|
- Fokus-Versuche mit Ergebnis
|
||||||
- Fehler mit vollständigem Traceback
|
- Fehler mit Traceback
|
||||||
|
|
||||||
### Häufige Probleme
|
### Häufige Probleme
|
||||||
|
|
||||||
| Symptom | Ursache | Lösung |
|
| Symptom | Ursache | Lösung |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Spiel startet nicht | Steam-Startoptionen fehlerhaft | Prüfen ob `%command%` am Ende steht |
|
| Spiel startet nicht | Steam-Startoptionen fehlerhaft | Prüfen ob `%command%` am Ende steht und `pythonw.exe`/`focusfix.py` korrekt referenziert werden |
|
||||||
| `ModuleNotFoundError: psutil` | Globales Python statt venv | Vollständigen Pfad zu `.venv\Scripts\pythonw.exe` verwenden |
|
| `ModuleNotFoundError: psutil` | Falsche Python-Installation | Vollständigen Pfad zu `.venv\Scripts\pythonw.exe` verwenden |
|
||||||
| Script findet Prozess nicht | Prozessname stimmt nicht | `--process` anpassen, Namen im Task-Manager prüfen |
|
| 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 |
|
| 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 |
|
| 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 |
|
| CMD-Fenster sichtbar | `python.exe` statt `pythonw.exe` | Steam-/Sunshine-Konfiguration prüfen |
|
||||||
| Verdunkeltes/blockiertes Spiel | Titelloses Overlay-Fenster | Log prüfen — sollte durch Fenster-mit-Titel-Bevorzugung gelöst sein |
|
| Verdunkeltes/blockiertes Spiel | Titelloses Overlay-Fenster | Log prüfen; sollte meist durch Fenster-mit-Titel-Bevorzugung abgefangen werden |
|
||||||
|
|
||||||
### Fenster-Inspektion per PowerShell
|
### Fenster-Inspektion per PowerShell
|
||||||
|
|
||||||
Um alle Fenster eines laufenden Spielprozesses zu inspizieren:
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
Get-Process -Name GameName | Select-Object Id, Name, MainWindowTitle, MainWindowHandle
|
Get-Process -Name GameName | Select-Object Id, Name, MainWindowTitle, MainWindowHandle
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bekannte Einschränkungen
|
## Bekannte Einschränkungen
|
||||||
|
|
||||||
- **Kein Admin erforderlich**, aber manche Spiele starten mit erhöhten Rechten — in dem Fall muss das Script ebenfalls als Admin laufen
|
- **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 (kein Borderless Windowed) können sich anders verhalten, da sie die komplette Bildschirmausgabe übernehmen
|
- **Exklusiver Vollbildmodus:** Spiele im exklusiven Vollbild können sich anders verhalten.
|
||||||
- **Anti-Cheat:** Einige Anti-Cheat-Systeme könnten die `keybd_event`-Simulation oder `AttachThreadInput`-Aufrufe als verdächtig einstufen — bisher keine Probleme bekannt
|
- **Anti-Cheat:** Manche Anti-Cheat-Systeme könnten Eingabesimulation oder Fokus-Workarounds blockieren.
|
||||||
- **Mehrere Monitore:** `find_game_window` wählt das Fenster unabhängig davon, auf welchem Monitor es liegt
|
- **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
|
- **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.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
foreground_fix.py
|
focusfix.py
|
||||||
Wartet auf den Start von CrimsonDesert.exe und bringt das Spielfenster
|
Bringt ein gestartetes Spiel automatisch in den Vordergrund.
|
||||||
automatisch in den Vordergrund.
|
|
||||||
|
|
||||||
Verwendung:
|
Verwendung:
|
||||||
pythonw foreground_fix.py [--timeout 120] [--process CrimsonDesert]
|
pythonw focusfix.py [--timeout 120] --process GameName
|
||||||
|
pythonw focusfix.py [--timeout 120] "C:\\Path\\To\\Game.exe" [args...]
|
||||||
|
|
||||||
Abhängigkeiten (im .venv):
|
Abhängigkeiten (im .venv):
|
||||||
pip install psutil pywin32
|
pip install psutil pywin32
|
||||||
@@ -13,6 +13,7 @@ Abhängigkeiten (im .venv):
|
|||||||
import argparse
|
import argparse
|
||||||
import ctypes
|
import ctypes
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ import win32process
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_LOG_PATH = Path(__file__).resolve().parent / "foreground_fix.log"
|
_LOG_PATH = Path(__file__).resolve().parent / "focusfix.log"
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename=str(_LOG_PATH),
|
filename=str(_LOG_PATH),
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
@@ -39,6 +40,17 @@ FOCUS_RETRIES = 15
|
|||||||
FOCUS_RETRY_DELAY = 2.0
|
FOCUS_RETRY_DELAY = 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def derive_process_name(command: list[str]) -> str:
|
||||||
|
if not command:
|
||||||
|
raise ValueError("No game command provided")
|
||||||
|
return Path(command[0]).stem
|
||||||
|
|
||||||
|
|
||||||
|
def launch_game(command: list[str]) -> subprocess.Popen:
|
||||||
|
logging.info("Launching game command: %r", command)
|
||||||
|
return subprocess.Popen(command)
|
||||||
|
|
||||||
|
|
||||||
def find_process_pids(name: str) -> set[int]:
|
def find_process_pids(name: str) -> set[int]:
|
||||||
pids = set()
|
pids = set()
|
||||||
for proc in psutil.process_iter(["pid", "name"]):
|
for proc in psutil.process_iter(["pid", "name"]):
|
||||||
@@ -156,12 +168,32 @@ def bring_to_front(hwnd: int) -> bool:
|
|||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--timeout", type=int, default=120)
|
parser.add_argument("--timeout", type=int, default=120)
|
||||||
parser.add_argument("--process", type=str, required=True,
|
parser.add_argument("--process", type=str,
|
||||||
help="Prozessname des Spiels (ohne .exe)")
|
help="Prozessname des Spiels (ohne .exe)")
|
||||||
args = parser.parse_args()
|
parser.add_argument("command", nargs="*",
|
||||||
|
help="Steam-Spielbefehl, z.B. %%command%%")
|
||||||
|
args, extra_command_args = parser.parse_known_args()
|
||||||
|
|
||||||
|
process_name = args.process
|
||||||
|
command = args.command + extra_command_args
|
||||||
|
|
||||||
|
if command:
|
||||||
|
if process_name:
|
||||||
|
parser.error("Use either --process or a game command, not both.")
|
||||||
|
try:
|
||||||
|
process_name = derive_process_name(command)
|
||||||
|
except ValueError as exc:
|
||||||
|
parser.error(str(exc))
|
||||||
|
try:
|
||||||
|
launch_game(command)
|
||||||
|
except OSError as exc:
|
||||||
|
logging.exception("Failed to launch game command: %s", exc)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not process_name:
|
||||||
|
parser.error("Either --process or a game command is required.")
|
||||||
|
|
||||||
deadline = time.time() + args.timeout
|
deadline = time.time() + args.timeout
|
||||||
process_name = args.process
|
logging.info("Monitoring process name: %s", process_name)
|
||||||
|
|
||||||
# --- Phase 1: Warten auf den Prozess ---
|
# --- Phase 1: Warten auf den Prozess ---
|
||||||
pids: set[int] = set()
|
pids: set[int] = set()
|
||||||
+2
-2
@@ -6,8 +6,8 @@ Set fso = CreateObject("Scripting.FileSystemObject")
|
|||||||
exePath = WScript.Arguments(0)
|
exePath = WScript.Arguments(0)
|
||||||
exeName = fso.GetBaseName(exePath)
|
exeName = fso.GetBaseName(exePath)
|
||||||
|
|
||||||
' Foreground-Fix Script unsichtbar starten (pythonw = kein Konsolenfenster)
|
' FocusFix Script unsichtbar starten (pythonw = kein Konsolenfenster)
|
||||||
WshShell.Run """C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe"" ""C:\Projekte\Steam_backgroud_fix\foreground_fix.py"" --process """ & exeName & """", 0, False
|
WshShell.Run """C:\Projekte\Steam_backgroud_fix\.venv\Scripts\pythonw.exe"" ""C:\Projekte\Steam_backgroud_fix\focusfix.py"" --process """ & exeName & """", 0, False
|
||||||
|
|
||||||
' Spiel starten: alle Argumente (= Steam %command%) zusammensetzen und ausführen
|
' Spiel starten: alle Argumente (= Steam %command%) zusammensetzen und ausführen
|
||||||
Dim gameCmd, i
|
Dim gameCmd, i
|
||||||
|
|||||||
Reference in New Issue
Block a user