- 定位改为 水平(左/中/右) × 垂直(上/中/下) + 边缘留白,取代四角枚举 - 完成类通知在目标窗口已聚焦时用更短停留(FocusedDurationSeconds) - 托盘左键单击直接打开设置,右键菜单仅保留退出 - 修复 host 拉起用 UseShellExecute=true,脱离钩子 stdout 管道,避免 Claude 卡在 running stop hook
b89223fa54
·
2026-06-22 16:40:27 +08:00
8 Commits
Claude Code Notify — 功能与时序说明
为 Claude Code 提供 原生 Windows Toast 通知 的插件。当 Claude 完成任务或需要你输入时弹出系统通知,点击通知即可跳回原终端/编辑器窗口(并能切回正确的 Windows Terminal 标签页)。核心是一个用 Rust + Win32 API 编写的单一可执行程序 ToastWindow.exe,通过 Claude Code 的 hook 机制驱动。
一、实现的功能
| 功能 | 说明 |
|---|---|
| 原生 Toast 通知 | 任务完成 / 需要输入时弹出系统集成风格的通知 |
| 一键返回 | 左键点击通知 → 激活当初发起请求的窗口 |
| 标签页感知 | 在 Windows Terminal 中能切回提问时所在的标签页 |
| 自动取图标 | 沿进程树向上查找调用方 App(VSCode / Cursor / JetBrains / Windows Terminal …),显示其图标 |
| 会话隔离 | 每个 session_id 独立保存状态于 %TEMP%\claude-notify-{session_id}.txt,多开互不干扰 |
| 通知堆叠 | 多条通知纵向堆叠、平滑滑动、悬停暂停自动关闭 |
| 非侵入显示 | `WS_EX_NOACTIVATE |
exe 的 5 种运行模式
| 模式 | 触发 Hook | 作用 |
|---|---|---|
--save |
UserPromptSubmit |
保存当前窗口句柄、WT 标签 RuntimeId、调用方 exe 路径、prompt |
--notify |
Stop |
弹"任务完成"通知(橙色边框) |
--input |
Notification / PreToolUse |
弹"需要输入"通知(黄色边框),按类型区分标题 |
--cleanup |
SessionEnd |
删除该 session 的状态文件 |
--notify-show |
(由 --notify/--input 内部分离启动) |
实际加载状态、绘制并显示 Toast 窗口 |
二、整体生命周期时序
sequenceDiagram
autonumber
actor User as 用户
participant CC as Claude Code
participant Save as ToastWindow --save
participant State as 状态文件<br/>%TEMP%\claude-notify-{id}.txt
participant Notify as ToastWindow --notify/--input
participant Toast as ToastWindow --notify-show
participant Win as 目标窗口
User->>CC: 发送消息 (UserPromptSubmit)
activate CC
CC->>Save: 调用 exe,stdin 传 session_id + prompt
Note over Save: 启动瞬间立即<br/>GetForegroundWindow() 抓住窗口
Save->>Save: 检测窗口类,若是 WT 则取标签 RuntimeId
Save->>Save: 沿进程树查找调用方 exe(取图标用)
Save->>State: 写入 HWND / RuntimeId / iconPath / prompt
deactivate CC
Note over CC: Claude 处理中…
alt 任务完成 (Stop)
CC->>Notify: ToastWindow --notify
else 需要输入 (Notification / PreToolUse)
CC->>Notify: ToastWindow --input(含 title/message)
end
Notify->>Toast: 分离式 spawn --notify-show
Note over Notify: 立即返回,不阻塞 hook
Toast->>State: 读取状态
Toast->>Toast: 取图标 / 载字体 / 播提示音
Toast-->>User: 显示 Toast(堆叠 / 淡入)
User->>Toast: 左键点击
Toast->>Win: 激活窗口(夺前台 + 切回标签页)
Win-->>User: 回到原窗口
User->>CC: 结束会话 (SessionEnd)
CC->>State: ToastWindow --cleanup 删除状态文件
三、关键时序点(为什么这样设计)
flowchart TD
A[main 启动] -->|第一行代码| B[GetForegroundWindow<br/>立即抓前台窗口]
B --> C[CoInitializeEx 初始化 COM]
C --> D[解析参数 / 初始化日志]
D --> E{模式分发}
E -->|--save| F[校验 HWND 有效性<br/>无效则 fallback]
E -->|--notify/--input| G[只解析 session,<br/>分离 spawn 后立即退出]
E -->|--notify-show| H[加载状态→绘制 Toast<br/>阻塞直到关闭]
style B fill:#ffe9c7,stroke:#e08a00
style G fill:#d7f0ff,stroke:#0078d6
要点:
- 最早抓窗口:
main()第一行就GetForegroundWindow(),避免后续 COM/参数初始化期间前台窗口变化导致句柄失真。 - hook 不阻塞:
--notify/--input只做极少工作,随即spawn_detached出--notify-show子进程后立即返回,保证 Claude Code 的 hook 超时(5–10s)内完成。 - 真正显示在子进程:
--notify-show才阻塞绘制 Toast,与 hook 主进程解耦,通知存活不依赖 hook。 - 过滤无意义通知:
auth_success/elicitation_complete/elicitation_response等类型直接跳过,不弹 toast。
四、点击通知的窗口激活时序
sequenceDiagram
autonumber
actor User as 用户
participant Toast as Toast 窗口
participant API as Win32 API
participant WT as Windows Terminal
participant Win as 目标窗口
User->>Toast: 左键点击
Toast->>API: AllowSetForegroundWindow(ASFW_ANY)
Toast->>API: 模拟 ALT 键 + AttachThreadInput
Toast->>API: SetWindowPos + BringWindowToTop<br/>SwitchToThisWindow + SetForegroundWindow
API->>Win: 窗口拉回前台
opt 目标是 Windows Terminal
Toast->>WT: UIAutomation 枚举标签项
Toast->>WT: 匹配保存的 RuntimeId
Toast->>WT: SelectionItemPattern::Select() 切回标签
end
Win-->>User: 回到提问时的窗口与标签页
五、Hook 注册一览(hooks/hooks.json)
graph LR
UPS[UserPromptSubmit] --> S["--save"]
NOT[Notification] --> I["--input"]
PRE["PreToolUse<br/>AskUserQuestion|ExitPlanMode"] --> I
STOP[Stop] --> N["--notify"]
SE[SessionEnd] --> C["--cleanup"]