Files
notify/Notify/Interop/Sound.cs
T
chuan f390bcd133 feat: 提示音、调用方图标与消息清洗(P2)
- 提示音:winmm 从内存异步播放打包 wav,可在设置开关,带防连环音
- 调用方图标:进程树上溯识别编辑器/终端,ExtractIconEx + GDI 转 Avalonia 位图显示
- 进程树跳过 shell/运行时,取不到图标回退默认 Claude 图标
- 消息清洗:折叠换行/多余空白为单行,避免撑乱 toast 布局
- 全程 LibraryImport/源生成,AOT 友好
2026-06-22 16:00:59 +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);
}
}