claude-hook-notify 1.4.1 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/activate.js
CHANGED
|
@@ -26,6 +26,7 @@ const WIN_TERMINAL_NAMES = new Set([
|
|
|
26
26
|
"windowsterminal",
|
|
27
27
|
"code",
|
|
28
28
|
"cursor",
|
|
29
|
+
"claude",
|
|
29
30
|
"cmd",
|
|
30
31
|
"powershell",
|
|
31
32
|
"pwsh",
|
|
@@ -137,6 +138,9 @@ function detectWindows() {
|
|
|
137
138
|
timeout: 8000,
|
|
138
139
|
}).trim();
|
|
139
140
|
|
|
141
|
+
// 遍历整棵进程树,取最高层级(最靠近根)的终端匹配
|
|
142
|
+
// 避免误取 powershell/cmd 等 shell 进程 —— 它们在 Windows Terminal 下不拥有窗口
|
|
143
|
+
let bestMatch = null;
|
|
140
144
|
for (const line of output.split("\n")) {
|
|
141
145
|
const parts = line.trim().split("|");
|
|
142
146
|
if (parts.length < 3) continue;
|
|
@@ -144,9 +148,10 @@ function detectWindows() {
|
|
|
144
148
|
const name = parts[0].replace(/\.exe$/i, "").toLowerCase();
|
|
145
149
|
const pid = parseInt(parts[1], 10);
|
|
146
150
|
if (WIN_TERMINAL_NAMES.has(name)) {
|
|
147
|
-
|
|
151
|
+
bestMatch = { name: parts[0].replace(/\.exe$/i, ""), pid, processName: parts[0] };
|
|
148
152
|
}
|
|
149
153
|
}
|
|
154
|
+
return bestMatch;
|
|
150
155
|
} catch {
|
|
151
156
|
// PowerShell 调用失败
|
|
152
157
|
}
|
package/src/notify.js
CHANGED
|
@@ -254,7 +254,8 @@ async function sendNotification(options = {}) {
|
|
|
254
254
|
// Windows: notify-helper.exe 通知 + 点击激活终端(无控制台窗口)
|
|
255
255
|
method = "notify-helper";
|
|
256
256
|
command = path.join(__dirname, "..", "vendor", "notify-helper", "ClaudeCodeNotify.exe");
|
|
257
|
-
|
|
257
|
+
// 注意:此列表需与 activate.js 的 WIN_TERMINAL_NAMES 保持同步
|
|
258
|
+
const targetNames = ["WindowsTerminal", "Code", "cursor", "claude", "cmd", "pwsh", "powershell", "mintty"];
|
|
258
259
|
const pid = (terminalInfo && terminalInfo.pid) ? String(terminalInfo.pid) : "0";
|
|
259
260
|
args = [title, message, targetNames.join(","), pid];
|
|
260
261
|
}
|
|
Binary file
|
|
@@ -27,6 +27,24 @@ class NotifyHelper
|
|
|
27
27
|
[DllImport("user32.dll")]
|
|
28
28
|
static extern bool IsWindowVisible(IntPtr hWnd);
|
|
29
29
|
|
|
30
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
31
|
+
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
|
32
|
+
|
|
33
|
+
[DllImport("user32.dll")]
|
|
34
|
+
static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
|
35
|
+
|
|
36
|
+
[DllImport("user32.dll")]
|
|
37
|
+
static extern IntPtr GetForegroundWindow();
|
|
38
|
+
|
|
39
|
+
[DllImport("kernel32.dll")]
|
|
40
|
+
static extern uint GetCurrentThreadId();
|
|
41
|
+
|
|
42
|
+
// 已知 GUI 终端的窗口类名(用于 PID 查找失败时的回退)
|
|
43
|
+
static string[] KnownWindowClasses = new string[] {
|
|
44
|
+
"CASCADIA_HOSTING_WINDOW_CLASS", // Windows Terminal
|
|
45
|
+
"mintty", // Git Bash / mintty
|
|
46
|
+
};
|
|
47
|
+
|
|
30
48
|
// Claude sparkle 图标 PNG(内嵌,无需外部文件)
|
|
31
49
|
static string IconBase64 = "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";
|
|
32
50
|
|
|
@@ -46,15 +64,17 @@ class NotifyHelper
|
|
|
46
64
|
{
|
|
47
65
|
try
|
|
48
66
|
{
|
|
49
|
-
var proc = Process.GetProcessById(targetPid)
|
|
50
|
-
if (proc.MainWindowHandle != IntPtr.Zero)
|
|
67
|
+
using (var proc = Process.GetProcessById(targetPid))
|
|
51
68
|
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
if (proc.MainWindowHandle != IntPtr.Zero)
|
|
70
|
+
{
|
|
71
|
+
targetHandle = proc.MainWindowHandle;
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
{
|
|
75
|
+
// MainWindowHandle 可能为零(如 Windows Terminal),用 EnumWindows 查找
|
|
76
|
+
targetHandle = FindWindowByPid(targetPid);
|
|
77
|
+
}
|
|
58
78
|
}
|
|
59
79
|
}
|
|
60
80
|
catch { /* 进程可能已退出,回退到名称查找 */ }
|
|
@@ -68,20 +88,48 @@ class NotifyHelper
|
|
|
68
88
|
try
|
|
69
89
|
{
|
|
70
90
|
var procs = Process.GetProcessesByName(name.Trim());
|
|
71
|
-
|
|
91
|
+
try
|
|
72
92
|
{
|
|
73
|
-
|
|
93
|
+
foreach (var p in procs)
|
|
74
94
|
{
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
if (p.MainWindowHandle != IntPtr.Zero)
|
|
96
|
+
{
|
|
97
|
+
targetHandle = p.MainWindowHandle;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
// MainWindowHandle 为零时尝试 EnumWindows 查找
|
|
101
|
+
IntPtr hwnd = FindWindowByPid(p.Id);
|
|
102
|
+
if (hwnd != IntPtr.Zero)
|
|
103
|
+
{
|
|
104
|
+
targetHandle = hwnd;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
77
107
|
}
|
|
78
108
|
}
|
|
109
|
+
finally
|
|
110
|
+
{
|
|
111
|
+
foreach (var p in procs) p.Dispose();
|
|
112
|
+
}
|
|
79
113
|
if (targetHandle != IntPtr.Zero) break;
|
|
80
114
|
}
|
|
81
115
|
catch { }
|
|
82
116
|
}
|
|
83
117
|
}
|
|
84
118
|
|
|
119
|
+
// 策略 3:通过已知窗口类名查找(兜底方案)
|
|
120
|
+
if (targetHandle == IntPtr.Zero)
|
|
121
|
+
{
|
|
122
|
+
foreach (var cls in KnownWindowClasses)
|
|
123
|
+
{
|
|
124
|
+
IntPtr hwnd = FindWindow(cls, null);
|
|
125
|
+
if (hwnd != IntPtr.Zero)
|
|
126
|
+
{
|
|
127
|
+
targetHandle = hwnd;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
85
133
|
notify = new NotifyIcon();
|
|
86
134
|
notify.Icon = LoadEmbeddedIcon();
|
|
87
135
|
notify.Visible = true;
|
|
@@ -142,8 +190,22 @@ class NotifyHelper
|
|
|
142
190
|
{
|
|
143
191
|
if (targetHandle != IntPtr.Zero)
|
|
144
192
|
{
|
|
145
|
-
ShowWindow(targetHandle, 9);
|
|
146
|
-
SetForegroundWindow
|
|
193
|
+
ShowWindow(targetHandle, 9); // SW_RESTORE
|
|
194
|
+
// 使用 AttachThreadInput 确保 SetForegroundWindow 成功
|
|
195
|
+
// (Windows 限制后台进程直接抢占前台焦点)
|
|
196
|
+
uint unusedPid;
|
|
197
|
+
uint targetThread = GetWindowThreadProcessId(targetHandle, out unusedPid);
|
|
198
|
+
uint curThread = GetCurrentThreadId();
|
|
199
|
+
if (curThread != targetThread)
|
|
200
|
+
{
|
|
201
|
+
AttachThreadInput(curThread, targetThread, true);
|
|
202
|
+
SetForegroundWindow(targetHandle);
|
|
203
|
+
AttachThreadInput(curThread, targetThread, false);
|
|
204
|
+
}
|
|
205
|
+
else
|
|
206
|
+
{
|
|
207
|
+
SetForegroundWindow(targetHandle);
|
|
208
|
+
}
|
|
147
209
|
}
|
|
148
210
|
Cleanup();
|
|
149
211
|
}
|