LOL切回桌面问题,采用监控抓出元凶方式

LOL 更新后,偶尔在进入游戏时会自动跳回桌面。搜索后发现有人提到可以用监控软件查看是哪个应用进程抢占了窗口焦点。受此启发,我编写了一个 PowerShell 脚本,通过实时监控前台窗口切换来揪出元凶。

使用方法

1. 下载与准备

WindowMonitor.ps1启动监视器.bat 两个文件放在同一目录下(例如桌面或任意文件夹)。

2. 启动监视器

方式一(推荐): 双击 启动监视器.bat 文件,脚本会自动启动并打开 PowerShell 窗口。

方式二: 右键 WindowMonitor.ps1 → 选择「使用 PowerShell 运行」。

3. 观察与记录

启动后,控制台窗口会实时显示每次窗口焦点切换的信息,格式如下:

复制代码
[21:30:15.123]  FOCUS >> [LOL客户端]  (LeagueClientUx / PID:12345)
[21:30:18.456]  FOCUS >> [Google Chrome]  (chrome / PID:67890)
[21:30:18.789]  FOCUS >> BACK TO DESKTOP (explorer.exe)
  • 正常游戏时,焦点应保持在游戏窗口。
  • 如果突然跳回桌面,控制台会以红色 显示 BACK TO DESKTOP 记录,同时日志中会记录跳转前最后一个获得焦点的进程,帮助定位元凶。

4. 查看日志

脚本会在同目录下自动生成日志文件,命名格式为 WindowMonitor_Log_20260613_213000.txt。日志记录了每次焦点切换的详细时间、窗口标题、进程名和 PID,方便事后分析。

5. 停止监视

在 PowerShell 窗口中按 Ctrl + C 即可停止监视,控制台会显示本次共记录了多少次焦点切换事件。

脚本内容

WindowMonitor.ps1 文件内容如下

ps1 复制代码
<#
  窗口活动监视器 v2.0 (便携版)
  功能:实时监控前台窗口切换,记录每次焦点变化的时间、窗口标题和进程
  用法:双击 启动监视器.bat 或右键此文件用 PowerShell 运行
  日志:同目录下自动生成日志文件
#>

Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;

public class WinAPI {
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("user32.dll")]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("user32.dll")]
    public static extern bool IsWindowVisible(IntPtr hWnd);
}
"@

$PollIntervalMs = 200
$LogDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$LogFile = Join-Path $LogDir "WindowMonitor_Log_$ts.txt"
$ShowDesktopAsFocus = $true

function Get-ForegroundInfo {
    $hwnd = [WinAPI]::GetForegroundWindow()
    if ($hwnd -eq [IntPtr]::Zero) { return $null }

    $len = [WinAPI]::GetWindowTextLength($hwnd)
    $sb  = New-Object System.Text.StringBuilder ($len + 1)
    [WinAPI]::GetWindowText($hwnd, $sb, $sb.Capacity) | Out-Null
    $title = $sb.ToString()

    $pidOut = [uint32]0
    [WinAPI]::GetWindowThreadProcessId($hwnd, [ref]$pidOut) | Out-Null

    $procName = ""
    try {
        $proc = Get-Process -Id $pidOut -ErrorAction SilentlyContinue
        if ($proc) { $procName = $proc.ProcessName }
    } catch {}

    return @{
        Hwnd     = $hwnd
        Title    = $title
        ProcName = $procName
        Pid      = $pidOut
    }
}

$Host.UI.RawUI.WindowTitle = "Window Monitor v2.0"

Write-Host ""
Write-Host "  ================================================" -ForegroundColor Cyan
Write-Host "       Window Monitor v2.0 (Portable)" -ForegroundColor Cyan
Write-Host "  ================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "  Log: $LogFile" -ForegroundColor Yellow
Write-Host "  Poll: ${PollIntervalMs}ms | Ctrl+C to stop" -ForegroundColor Yellow
Write-Host ""
Write-Host "  ------------------------------------------------" -ForegroundColor DarkGray
Write-Host ""

$header = "Window Monitor v2.0 Log`r`nStart: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`nPoll: ${PollIntervalMs}ms`r`n================================`r`n"
$header | Out-File -FilePath $LogFile -Encoding UTF8

$lastHwnd = [IntPtr]::Zero
$eventCount = 0

try {
    while ($true) {
        $info = Get-ForegroundInfo

        if ($null -ne $info -and $info.Hwnd -ne $lastHwnd) {
            $now = Get-Date -Format "HH:mm:ss.fff"
            $t = $info.Title
            $p = $info.ProcName
            $pidVal = $info.Pid

            if ([string]::IsNullOrEmpty($t)) { $t = "(no title)" }
            if ([string]::IsNullOrEmpty($p)) { $p = "unknown" }

            $isDesktop = ($p -eq "explorer" -and ($info.Title -eq "" -or $info.Title -eq "Program Manager"))

            if ($isDesktop -and -not $ShowDesktopAsFocus) {
                Start-Sleep -Milliseconds $PollIntervalMs
                continue
            }

            $eventCount++

            if ($isDesktop) {
                $line = "  [$now]  >> BACK TO DESKTOP (explorer.exe)"
                $color = "Red"
            } else {
                $line = "  [$now]  FOCUS >> [$t]  ($p / PID:$pidVal)"
                $color = "White"
            }

            Write-Host $line -ForegroundColor $color

            $logLine = "[$now]  FOCUS >> Title: $t | Process: $p | PID: $pidVal"
            $logLine | Out-File -FilePath $LogFile -Encoding UTF8 -Append

            $lastHwnd = $info.Hwnd
        }

        Start-Sleep -Milliseconds $PollIntervalMs
    }
}
finally {
    Write-Host ""
    Write-Host "  ------------------------------------------------" -ForegroundColor DarkGray
    Write-Host "  Stopped. $eventCount events recorded." -ForegroundColor Yellow
    Write-Host "  Log: $LogFile" -ForegroundColor Yellow
    Write-Host ""
}

WindowMonitor.ps1 文件v2.1版本内容如下

bash 复制代码
<#
  Window Monitor v2.1 (Portable - Hybrid Edition)
  - 50ms fast polling to catch quick popups
  - Auto snapshot of all visible windows when BACK TO DESKTOP detected
  - Monitor window minimized to avoid stealing game focus
  Usage: double-click the .bat launcher or run this .ps1 in PowerShell
#>

Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;

public class WinAPI2 {
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("user32.dll")]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("user32.dll")]
    public static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [DllImport("user32.dll")]
    public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetConsoleWindow();

    public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT {
        public int Left, Top, Right, Bottom;
    }

    public const int SW_MINIMIZE = 6;
    public const int SW_SHOWMINNOACTIVE = 7;

    public static void MinimizeConsole() {
        IntPtr console = GetConsoleWindow();
        if (console != IntPtr.Zero) {
            ShowWindow(console, SW_MINIMIZE);
        }
    }

    public static List<string> GetVisibleWindowSnapshot() {
        var result = new List<string>();
        EnumWindows(delegate(IntPtr hWnd, IntPtr param) {
            if (IsWindowVisible(hWnd)) {
                int len = GetWindowTextLength(hWnd);
                if (len > 0) {
                    StringBuilder sb = new StringBuilder(len + 1);
                    GetWindowText(hWnd, sb, sb.Capacity);
                    string title = sb.ToString();

                    uint pid = 0;
                    GetWindowThreadProcessId(hWnd, out pid);

                    string procName = "";
                    try {
                        var p = System.Diagnostics.Process.GetProcessById((int)pid);
                        if (p != null) procName = p.ProcessName;
                    } catch {}

                    RECT rect;
                    GetWindowRect(hWnd, out rect);
                    int w = rect.Right - rect.Left;
                    int h = rect.Bottom - rect.Top;

                    if (w > 5 && h > 5 && procName != "") {
                        result.Add(string.Format("{0} | {1} | PID:{2} | {3}x{4}",
                            title, procName, pid, w, h));
                    }
                }
            }
            return true;
        }, IntPtr.Zero);
        return result;
    }
}
"@

$PollIntervalMs = 50
$LogDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$LogFile = Join-Path $LogDir "WindowMonitor_Log_$ts.txt"

$Host.UI.RawUI.WindowTitle = "Window Monitor v2.1"

Write-Host ""
Write-Host "  ================================================" -ForegroundColor Cyan
Write-Host "       Window Monitor v2.1 (Hybrid)" -ForegroundColor Cyan
Write-Host "  ================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "  Log: $LogFile" -ForegroundColor Yellow
Write-Host "  Poll: ${PollIntervalMs}ms + desktop snapshot" -ForegroundColor Yellow
Write-Host "  Ctrl+C to stop" -ForegroundColor Yellow
Write-Host ""
Write-Host "  Monitor will minimize in 3 seconds..." -ForegroundColor DarkGray

Start-Sleep -Seconds 3
[WinAPI2]::MinimizeConsole()

Write-Host ""
Write-Host "  ------------------------------------------------" -ForegroundColor DarkGray
Write-Host ""

$header = "Window Monitor v2.1 Log`r`nStart: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`nPoll: ${PollIntervalMs}ms + snapshot on desktop`r`n================================`r`n"
$header | Out-File -FilePath $LogFile -Encoding UTF8

$lastHwnd = [IntPtr]::Zero
$eventCount = 0
$myPid = $PID

try {
    while ($true) {
        $hwnd = [WinAPI2]::GetForegroundWindow()

        if ($hwnd -ne [IntPtr]::Zero -and $hwnd -ne $lastHwnd) {
            $now = Get-Date -Format "HH:mm:ss.fff"
            $len = [WinAPI2]::GetWindowTextLength($hwnd)
            $sb  = New-Object System.Text.StringBuilder ($len + 1)
            [WinAPI2]::GetWindowText($hwnd, $sb, $sb.Capacity) | Out-Null
            $rawTitle = $sb.ToString()

            $pidOut = [UInt32]0
            [WinAPI2]::GetWindowThreadProcessId($hwnd, [ref]$pidOut) | Out-Null

            $procName = ""
            try {
                $proc = Get-Process -Id $pidOut -ErrorAction SilentlyContinue
                if ($proc) { $procName = $proc.ProcessName }
            } catch {}

            # Skip our own window to avoid stealing game focus
            if ($pidOut -eq $myPid) {
                $lastHwnd = $hwnd
                Start-Sleep -Milliseconds $PollIntervalMs
                continue
            }

            $title = $rawTitle
            if ([string]::IsNullOrEmpty($title)) { $title = "(no title)" }
            if ([string]::IsNullOrEmpty($procName)) { $procName = "unknown" }

            $isDesktop = ($procName -eq "explorer" -and ($rawTitle -eq "" -or $rawTitle -eq "Program Manager"))

            $eventCount++

            if ($isDesktop) {
                # BACK TO DESKTOP - take snapshot
                $line = "  [$now]  >> BACK TO DESKTOP (explorer.exe)"
                Write-Host $line -ForegroundColor Red
                "[$now]  >> BACK TO DESKTOP" | Out-File -FilePath $LogFile -Encoding UTF8 -Append

                Write-Host "  [$now]  >> Snapshot: all visible windows ---" -ForegroundColor Magenta
                "[$now]  >> SNAPSHOT:" | Out-File -FilePath $LogFile -Encoding UTF8 -Append

                $snap = [WinAPI2]::GetVisibleWindowSnapshot()
                foreach ($entry in $snap) {
                    # Skip explorer desktop and our own process
                    if ($entry -match "Program Manager \| explorer") { continue }
                    Write-Host "    $entry" -ForegroundColor DarkYellow
                    "  $entry" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
                }
                Write-Host "  [$now]  >> End snapshot" -ForegroundColor Magenta
                "---" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
                Write-Host ""

            } else {
                $line = "  [$now]  FOCUS >> [$title]  ($procName / PID:$pidOut)"
                Write-Host $line -ForegroundColor White
                "[$now]  FOCUS >> Title: $title | Process: $procName | PID: $pidOut" | Out-File -FilePath $LogFile -Encoding UTF8 -Append
            }

            $lastHwnd = $hwnd
        }

        Start-Sleep -Milliseconds $PollIntervalMs
    }
}
finally {
    Write-Host ""
    Write-Host "  ------------------------------------------------" -ForegroundColor DarkGray
    Write-Host "  Stopped. $eventCount events recorded." -ForegroundColor Yellow
    Write-Host "  Log: $LogFile" -ForegroundColor Yellow
    Write-Host ""
}

启动监视器.bat内容如下

bash 复制代码
@echo off
chcp 65001 >nul 2>&1
title Window Monitor v2.0
echo.
echo   Starting Window Monitor...
echo.
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0WindowMonitor.ps1"
pause
相关推荐
Aurorar0rua1 小时前
CS50 x 2024 Notes Arrays - 04
c语言·开发语言·学习方法
一起吃元宵2 小时前
百度网盘下载不限速的办法_百度网盘不限速
开发语言·百度网盘·下载不限速·不限速·百度网盘不限速
人道领域2 小时前
【LeetCode刷题日记】47.全排列Ⅱ
java·开发语言·算法·leetcode
ch3nyuyu2 小时前
socket套接字
开发语言·php
源图客3 小时前
境外电商 - 龙虾智能体-综合选品推荐报告
开发语言·javascript·ecmascript
是苏浙3 小时前
Java实现链表1
java·开发语言
Jinkxs3 小时前
Rust 性能优化全流程:从 flamegraph 定位瓶颈到 unsafe 与 SIMD 加速,响应快 2 倍
开发语言·性能优化·rust
尘中远3 小时前
Qt高性能绘图库QIm——实现二维三维科学绘图
开发语言·qt·信息可视化
雨辰AI3 小时前
从零搭建大模型本地运行环境|Python+CUDA 基础配置避坑大全
大数据·开发语言·人工智能·python·ai·ai编程·ai写作