Files
chuan 5ce2c8a982 feat: Claude Code 原生 Windows 通知(C# / .NET 10 + Avalonia 12)
为 Claude Code 提供原生 Windows toast 通知:点击跳回原窗口、切回 Windows
Terminal 标签、跨虚拟桌面、调用方图标、非阻塞投递;NativeAOT 单文件分发。
2026-06-22 18:05:15 +08:00

68 lines
1.7 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia.Platform;
namespace Notify.Interop;
/// <summary>
/// 播放打包的提示音 wav
///
/// 用 winmm 的 PlaySound 从内存异步播放;为配合 SND_ASYNC,wav 拷到不会被 GC
/// 移动的非托管内存里常驻
/// </summary>
internal static partial class Sound
{
private const uint SND_ASYNC = 0x0001;
private const uint SND_NODEFAULT = 0x0002;
private const uint SND_MEMORY = 0x0004;
private static IntPtr _wavPtr;
private static DateTime _lastPlay = DateTime.MinValue;
[LibraryImport("winmm.dll", EntryPoint = "PlaySoundW")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool PlaySound(IntPtr pszSound, IntPtr hmod, uint fdwSound);
public static void Play()
{
// 防连环音:300ms 内只响一次
var now = DateTime.UtcNow;
if ((now - _lastPlay).TotalMilliseconds < 300)
{
return;
}
_lastPlay = now;
try
{
EnsureLoaded();
if (_wavPtr != IntPtr.Zero)
{
PlaySound(_wavPtr, IntPtr.Zero, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
}
}
catch
{
// 播放失败无所谓
}
}
private static void EnsureLoaded()
{
if (_wavPtr != IntPtr.Zero)
{
return;
}
using var s = AssetLoader.Open(new Uri("avares://notify/Assets/notification.wav"));
using var ms = new MemoryStream();
s.CopyTo(ms);
var bytes = ms.ToArray();
_wavPtr = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, _wavPtr, bytes.Length);
}
}