跳过正文
  1. 文章/

谢谢你,ChatGPT

作者
yellow13441
HLSay

今天遇到了一个很奇怪、也很烦人的 Windows 桌面问题。

屏幕上突然出现了一个浅黄色背景、只有 “Close” 字样的小方框。

它看起来像一个普通的提示框,但行为却一点也不普通:

  • 长按它,无法拖动;
  • 右键点击,没有任何反应;
  • Win + D 回到桌面,它依然悬浮在最前面;
  • 打开任何窗口,它仍然压在所有窗口之上;
  • Win + Tab 新建一个虚拟桌面,它竟然还会出现在新的桌面里;
  • 我完全不知道它到底是哪一个软件留下来的。

屏幕上异常悬浮的浅黄色 “Close” 提示框
异常悬浮在桌面最前方的浅黄色 “Close” 提示框。


我是怎么描述问题的
#

我给出的描述大致是:

电脑屏幕上出现了一个黄色背景的 “Close”。长按无法拖动,也无法右键点击。我想知道这是哪个软件留下的。

我使用 Win + D 回到桌面,它依然在最前面;无论打开任何窗口,它都保持置顶。

更奇怪的是,我用 Win + Tab 新建虚拟桌面之后,它依然出现在新的桌面上。

现在回头看,我觉得这次问题能够很快被定位,一个很重要的原因就是:我没有只说“电脑上有个奇怪的框”,而是把它的行为特征一起描述出来了。

ChatGPT 后来也特别肯定了这一点。

Win + D 无法让它消失、普通窗口盖不住它、切换虚拟桌面后仍然存在——这些信息实际上已经排除了很多普通窗口的可能性,并把范围缩小到了 Topmost 悬浮层、Overlay、Tooltip 或特殊工具窗口

这也是我这次提问做得比较对的地方之一:尽量描述“现象”,而不是急着替现象下结论。


ChatGPT 给出的判断
#

ChatGPT 根据这些现象,判断它更像是某个程序创建出来的:

始终置顶的悬浮窗口、Overlay,或者工具提示窗口。

它首先推荐我使用微软 Sysinternals 的 Process Explorer,通过 “Find Window’s Process” 准星直接拖到这个 Close 上,从而定位创建窗口的进程。

不过我并没有下载 Process Explorer。

ChatGPT 同时还给了我另一个方案:

只使用 Windows 自带的 PowerShell,通过鼠标所在位置获取窗口句柄,再根据窗口句柄查询所属进程。

这个方案正合我意。


完整 PowerShell 脚本
#

下面就是我实际执行的完整脚本。

运行之后会等待 3 秒。在这 3 秒内,只需要把鼠标移动到那个异常的 Close 提示框上,不需要点击。

展开查看完整 PowerShell 脚本
Add-Type @"
using System;
using System.Text;
using System.Runtime.InteropServices;

public class WinDetect {
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT {
        public int X;
        public int Y;
    }

    [DllImport("user32.dll")]
    public static extern bool GetCursorPos(out POINT lpPoint);

    [DllImport("user32.dll")]
    public static extern IntPtr WindowFromPoint(POINT Point);

    [DllImport("user32.dll")]
    public static extern IntPtr GetAncestor(IntPtr hwnd, uint gaFlags);

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

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

    [DllImport("user32.dll", CharSet=CharSet.Unicode)]
    public static extern int GetClassName(
        IntPtr hWnd,
        StringBuilder lpClassName,
        int nMaxCount
    );
}
"@

Write-Host "3秒内把鼠标移动到黄色 Close 上,不要点击..."
Start-Sleep -Seconds 3

$pt = [WinDetect+POINT]::new()
[void][WinDetect]::GetCursorPos([ref]$pt)

$hwnd = [WinDetect]::WindowFromPoint($pt)

# 获取最外层窗口
$root = [WinDetect]::GetAncestor($hwnd, 2)
if ($root -ne [IntPtr]::Zero) {
    $hwnd = $root
}

$procId = [uint32]0
[void][WinDetect]::GetWindowThreadProcessId($hwnd, [ref]$procId)

$title = New-Object System.Text.StringBuilder 512
$class = New-Object System.Text.StringBuilder 512

[void][WinDetect]::GetWindowText($hwnd, $title, 512)
[void][WinDetect]::GetClassName($hwnd, $class, 512)

$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$procId"

Write-Host ""
Write-Host "HWND       :" $hwnd
Write-Host "窗口标题   :" $title.ToString()
Write-Host "窗口类名   :" $class.ToString()
Write-Host "PID        :" $procId
Write-Host "进程名     :" $proc.Name
Write-Host "程序路径   :" $proc.ExecutablePath
Write-Host "命令行     :" $proc.CommandLine
Write-Host "父进程 PID :" $proc.ParentProcessId

真凶出现了:Telegram
#

执行脚本之后,最关键的输出是:

窗口标题   :
窗口类名   : MicrosoftWindowsTooltip
进程名     : Telegram.exe
程序路径   : C:\Users\<用户名>\AppData\Roaming\Telegram Desktop\Telegram.exe
命令行     : "C:\Users\<用户名>\AppData\Roaming\Telegram Desktop\Telegram.exe" -noupdate

这里已经几乎可以直接确定:

这个烦人的 “Close” 是 Telegram Desktop 创建出来的。

为了进一步验证,我直接退出了 Telegram。

结果——

那个悬浮在屏幕最前面的 “Close” 立刻消失。

至此,问题彻底确认。


原来它不是普通窗口,而是 Tooltip
#

更有意思的是这一行:

窗口类名 : MicrosoftWindowsTooltip

也就是说,这个东西本质上并不是 Telegram 的一个正常窗口,而是一个 Windows Tooltip(工具提示)

正常情况下,它可能只是 Telegram 某个按钮在鼠标悬停时短暂显示的 “Close”。

但不知道因为什么原因——可能是界面刷新、窗口状态切换、休眠唤醒,或者单纯就是 Telegram Desktop 的 UI Bug——这个 Tooltip 没有被正常销毁,于是留在了桌面上。

这也解释了它之前那些诡异的行为:

  • 不能拖动,因为 Tooltip 本来就不是普通窗口;
  • 不能正常右键操作,因为它不是为交互设计的;
  • 一直保持置顶,因为 Tooltip 往往使用顶层窗口;
  • Win + D 对它没有正常效果;
  • 切换虚拟桌面后,它仍然可能继续存在;
  • Telegram 退出后,所属进程结束,Windows 自动销毁对应窗口,于是它立即消失。

解决方案,简要版
#

如果以后再遇到类似的“幽灵窗口”,可以按照下面的方法排查:

  1. 先观察异常窗口的行为。
    比如它是否始终置顶、是否能拖动、Win + D 是否有效、切换虚拟桌面后是否仍然存在。

  2. 把鼠标移动到异常窗口上。

  3. 运行上面的 PowerShell 脚本。
    它会通过 WindowFromPoint 获取鼠标所在位置的窗口,再通过 GetWindowThreadProcessId 找到创建这个窗口的进程。

  4. 重点查看:

    • 窗口类名
    • 进程名
    • 程序路径
    • 命令行
    • 父进程 PID
  5. 退出对应软件进行验证。
    如果异常窗口随着软件退出立即消失,那么基本就可以确认来源。

这套方法最大的好处是:

不需要靠猜,也不需要一个一个关闭程序。

Windows 自己其实知道屏幕上的窗口属于谁,我们只是通过 Win32 API 把这个信息查了出来。


这次我也想夸夸自己
#

既然文章标题叫《谢谢你,ChatGPT》,当然要夸 ChatGPT。

但 ChatGPT 在最后也指出,这次排查能够这么顺利,我自己的提问方式其实也发挥了很大作用。

我觉得下面几点确实值得保留:

第一,我描述了可验证的现象。

我没有只说“桌面上出现了一个奇怪的 Close”,而是补充说明:

Win + D 无法隐藏、任何窗口都盖不住、虚拟桌面之间也存在。

这些细节对判断窗口类型非常重要。

第二,我愿意执行一个可验证的诊断步骤。

拿到 PowerShell 脚本之后,我没有停留在猜测阶段,而是直接运行它,让系统告诉我这个窗口到底属于哪个 PID 和哪个 EXE。

第三,我把完整结果反馈了回来。

我没有只回复一句“好像是 Telegram”,而是把窗口类名、PID、进程名、程序路径、命令行和父进程 PID 都贴了出来。

这样 ChatGPT 才能进一步发现:

MicrosoftWindowsTooltip

这个非常关键的线索。

第四,我又做了一次因果验证。

查到 Telegram.exe 之后,我直接退出 Telegram。

Close 当场消失。

这一步很重要,因为它把“相关”变成了“基本可以确认的因果关系”。

从这个角度看,这其实是一套非常标准的故障排查过程:

观察现象 → 提出假设 → 获取系统信息 → 锁定对象 → 实验验证。


谢谢你,ChatGPT
#

这件事本身不算什么大问题。

它不会导致电脑无法启动,也没有造成数据丢失。

但这种小问题最容易让人难受:

屏幕上就那么挂着一个东西,你看得见它,却不知道它是什么;你没法拖走它,也不知道是谁创建了它。

过去遇到这种情况,我可能会选择:

关几个软件试试,不行就重启电脑。

重启当然大概率也能解决。

但那样我永远不会知道:

它究竟是谁留下来的?

这一次,ChatGPT 没有让我重启电脑,也没有让我盲目卸载软件,而是给了我一小段 PowerShell,让我直接问 Windows:

“鼠标现在指着的这个窗口,到底属于哪个进程?”

几秒钟后,Windows 给出了答案:

Telegram.exe

问题解决了,而且我还顺便学到了 WindowFromPoint、窗口句柄、PID,以及 MicrosoftWindowsTooltip 这些原本完全不会去了解的东西。

所以,这篇文章就叫:

谢谢你,ChatGPT
#