5ce2c8a982
为 Claude Code 提供原生 Windows toast 通知:点击跳回原窗口、切回 Windows Terminal 标签、跨虚拟桌面、调用方图标、非阻塞投递;NativeAOT 单文件分发。
2.9 KiB
2.9 KiB
原生互操作与 AOT
所有 Win32 / COM 互操作都为 NativeAOT 准备:用 LibraryImport(源生成 P/Invoke)和 [GeneratedComInterface](源生成 COM),不用 System.Drawing、经典 [ComImport]、反射式封送(它们在 AOT 下不可用)。
| 文件 | 职责 | 关键点 |
|---|---|---|
Win32.cs |
基础 P/Invoke | GetForegroundWindow、GetClassName、工具窗口样式等 |
WindowActivator.cs |
抢前台激活 | ALT 模拟 + AttachThreadInput + 多 API 组合,绕过防焦点抢占 |
WinTerminalTabs.cs |
WT 切标签 | 源生成 COM 调 UIAutomation,按 RuntimeId 定位标签 |
VirtualDesktopPinner.cs |
跨虚拟桌面 | 未公开 COM IVirtualDesktopPinnedApps.PinView |
ProcessTree.cs |
进程树上溯 | Toolhelp 快照,跳过 shell/运行时找调用方 App |
AppIcon.cs |
取图标 | ExtractIconEx + GDI 读 BGRA 像素 → Avalonia 位图 |
Sound.cs |
提示音 | winmm PlaySound 从内存异步播放 |
窗口激活(WindowActivator)
Windows 限制后台进程抢焦点。组合技:还原最小化 → 模拟一次 ALT 抬起 → AttachThreadInput 把当前线程与前台/目标线程输入队列挂接 → AllowSetForegroundWindow + SetWindowPos/BringWindowToTop/SwitchToThisWindow/SetForegroundWindow 多管齐下 → 解除挂接。
Windows Terminal 切标签(WinTerminalTabs)
save时:检测前台窗口类是否CASCADIA_HOSTING_WINDOW_CLASS;是则用 UIAutomation 找当前选中的TabItem,取其 RuntimeId(一串 int,SAFEARRAY)存入状态。- 点击时:激活 WT 窗口后,枚举标签找到 RuntimeId 匹配的,调
SelectionItemPattern.Select()。 - AOT 要点:接口用
[GeneratedComInterface];未用到的 vtable 槽用占位方法按 SDK 头文件顺序补齐(顺序 / GUID 均取自UIAutomationClient.h);IApplicationView以裸IntPtr传递。
跨虚拟桌面(VirtualDesktopPinner)
- 经
CoCreateInstance(CLSCTX_LOCAL_SERVER)拿 ImmersiveShell →IApplicationViewCollection.GetViewForHwnd→IVirtualDesktopPinnedApps.PinView。 - GUID 取自 Win11 24H2;整段 try/catch,失败自动退回"仅当前桌面"。
- 窗口刚打开时 view 可能尚未就绪,短间隔重试直到成功。
取图标(AppIcon)
ExtractIconEx 拿 HICON → GetIconInfo 取彩色位图 → GetDIBits 以 32bpp 自上而下读出 BGRA → 构造 Avalonia.Media.Imaging.Bitmap。老图标无 alpha(全 0)时补成不透明,避免整块透明。取不到则回退默认 Claude 图标。
AOT
csproj开IsAotCompatible;PublishAot条件块 +TrimmerRootAssembly(Ursa / Semi 整体保留)+CoreUtils.*.Static静态链接 Skia / HarfBuzz / ANGLE。- 原生链接需要 MSVC 工具链(从 "Developer Command Prompt for VS" 跑,或用配好 vcvars 的脚本)。
- 源生成 COM / UIAutomation 与静态渲染需在真机运行验证。