Claude Code 任务栏通知系统

功能效果

Claude Code 会话完成(Stop 事件)时,自动触发以下通知:

  1. 任务栏黄色进度条 — 对应 VSCode 的任务栏图标变为黄色(TBPF_PAUSED 状态),提示本次对话已结束
  2. 窗口标题标记 — 若同时打开了多个 VSCode 窗口,该窗口的 Win32 标题临时变为 ★ Claude: <项目目录名>,鼠标悬浮任务栏图标组时可通过缩略图标题识别是哪个窗口
  3. Toast 弹窗 — 右下角系统通知弹窗,内容为 Claude [<项目目录名>]
  4. 自动清除 — 用户点击(聚焦)对应 VSCode 窗口后,黄色进度条在 500 ms 内消失,窗口标题由 VSCode 自动恢复

涉及文件

文件位置职责
claude-notify.ps1%USERPROFILE%\.claude\主通知脚本:设黄色进度条、改窗口标题、发 Toast、启动 reset 进程
claude-notify-reset.ps1%USERPROFILE%\.claude\重置脚本:后台轮询前台窗口,用户聚焦 VSCode 后清除黄色
settings.json%USERPROFILE%\.claude\Claude Code 全局配置,通过 hooks.Stop 触发主通知脚本
hook_test.log%USERPROFILE%\.claude\运行日志,记录每次通知和清除的时间戳、窗口句柄、进程 PID

触发机制

Claude Code 会话结束
        │
        ▼ hooks.Stop(settings.json)
claude-notify.ps1(同步执行,<1 秒)
        │
        ├─ 遍历父进程链,找到有窗口句柄的 Code.exe(VSCode 主进程)
        │   └─ 若未找到,按窗口标题匹配当前目录的 Code.exe
        │
        ├─ ITaskbarList3::SetProgressState(hwnd, TBPF_PAUSED=8)
        │   └─ 任务栏图标变为黄色
        │
        ├─ SetWindowText(hwnd, "★ Claude: <dir>")
        │   └─ 任务栏缩略图标题变化,多窗口时可识别
        │
        ├─ WinRT Toast 弹窗
        │
        └─ Start-Process → claude-notify-reset.ps1(后台异步,最长 10 分钟)
claude-notify-reset.ps1(后台持续轮询)
        │
        ├─ EnumWindows 枚举 TargetPid 的所有顶层窗口
        │
        ├─ WMI 递归枚举 TargetPid 的全部子/孙 Code.exe 进程 PID
        │   └─ VSCode = 1 个主进程 + 十余个渲染器子进程
        │
        └─ 每 500 ms 检查前台窗口
            ├─ GetForegroundWindow() → GetWindowThreadProcessId() → fgPid
            ├─ GetAncestor(fg, GA_ROOT) → rootHwnd
            │
            └─ 命中条件:rootHwnd == HwndValue  OR  fgPid ∈ relatedPids
                └─ 对所有枚举到的窗口调用 SetProgressState(h, TBPF_NOPROGRESS=0)

依赖的 Windows API

claude-notify.ps1

API用途
ITaskbarList3::SetProgressState设置任务栏进度条状态(黄色 = TBPF_PAUSED=8,清除 = 0)
ITaskbarList3::SetProgressValue配合 SetProgressState 固定进度为 100%
user32!SetWindowText (Unicode)临时修改 Win32 窗口标题,用于多窗口识别
user32!GetWindowThreadProcessId从窗口句柄取进程 PID
WinRT ToastNotificationManager发送系统 Toast 弹窗

claude-notify-reset.ps1

API / 机制用途
user32!GetForegroundWindow获取当前前台窗口句柄
user32!GetAncestor(hWnd, GA_ROOT=2)沿父窗口链找根窗口,用于匹配主窗口
user32!GetWindowThreadProcessId前台窗口 → 进程 PID
user32!EnumWindows枚举指定进程的所有顶层窗口
ITaskbarList3::SetProgressState清除任务栏进度条
WMI Win32_Process递归枚举 VSCode 子进程树(渲染器进程)

VSCode 多进程架构说明

VSCode 基于 Electron,运行时由多个 Code.exe 构成进程树:

Code.exe(浏览器主进程,PID=XXXX,有主窗口句柄)
├── Code.exe(GPU 进程)
├── Code.exe(渲染器进程,拥有编辑器/面板窗口)
│   └── Code.exe(Extension Host 渲染器)
├── Code.exe(渲染器进程,拥有终端面板窗口)
└── Code.exe(工具进程)

当用户点击 VSCode 编辑器区域时,聚焦的是渲染器子进程所有的窗口,而不是主进程窗口。
因此 reset 脚本必须通过 WMI 递归收集整棵子进程树的 PID,才能正确检测到"用户回到 VSCode"这一事件。


settings.json 关键配置

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell -NoProfile -ExecutionPolicy Bypass -STA -File \"%USERPROFILE%\\.claude\\claude-notify.ps1\""
          }
        ]
      }
    ]
  },
  "permissions": {
    "allow": ["Bash(*)", "PowerShell(*)"]
  }
}
  • hooks.Stop:Claude Code 内置钩子,每次会话结束时同步执行指定命令
  • -STA 参数:PowerShell 单线程模式,COM 对象(ITaskbarList3、WinRT Toast)需要此模式
  • PowerShell(*):允许 Claude 在调试时免提示执行 PowerShell 工具调用

已知限制

  • reset 脚本在启动时一次性枚举进程树,之后不刷新。若 VSCode 在通知期间新建渲染器进程,该进程 PID 不在集合中,点击其窗口不会触发清除(概率较低)
  • 窗口标题由 VSCode 在获得焦点后自动恢复,极少数情况下(VSCode 长时间无内部事件)可能滞留
  • 同一台机器运行多个不同目录的 Claude Code 会话时,各自独立触发通知,互不干扰

更多推荐