5ce2c8a982
为 Claude Code 提供原生 Windows toast 通知:点击跳回原窗口、切回 Windows Terminal 标签、跨虚拟桌面、调用方图标、非阻塞投递;NativeAOT 单文件分发。
122 lines
3.7 KiB
C#
122 lines
3.7 KiB
C#
using System;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
using Avalonia.Markup.Xaml;
|
|
using Avalonia.Threading;
|
|
using Notify.Ipc;
|
|
using Notify.Models;
|
|
using Notify.Services;
|
|
using Notify.ViewModels;
|
|
using Notify.Views;
|
|
|
|
namespace Notify;
|
|
|
|
public partial class App : Application
|
|
{
|
|
private SettingsWindow? _settingsWindow;
|
|
private SpoolWatcher? _spoolWatcher;
|
|
|
|
public static new App Current => (App)Application.Current!;
|
|
|
|
public SettingsService Settings { get; } = new();
|
|
|
|
public ToastManager Toasts { get; private set; } = null!;
|
|
|
|
public override void Initialize() => AvaloniaXamlLoader.Load(this);
|
|
|
|
public override void OnFrameworkInitializationCompleted()
|
|
{
|
|
Settings.Load();
|
|
Toasts = new ToastManager(Settings);
|
|
|
|
// 监视 spool 目录,把 CLI 投递的请求转成 toast
|
|
_spoolWatcher = new SpoolWatcher(OnNotify);
|
|
_spoolWatcher.Start();
|
|
|
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
{
|
|
// 无主窗口的常驻进程:仅托盘存在,靠托盘菜单或外部请求驱动
|
|
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
|
|
|
// --demo:启动即弹一条 toast 并打开设置,便于无托盘交互地验证
|
|
if (desktop.Args is { Length: > 0 } args && System.Array.IndexOf(args, "--demo") >= 0)
|
|
{
|
|
Dispatcher.UIThread.Post(RunDemo);
|
|
}
|
|
}
|
|
|
|
base.OnFrameworkInitializationCompleted();
|
|
}
|
|
|
|
// 监视线程收到请求,切回 UI 线程弹出 toast
|
|
private void OnNotify(NotifyMessage message)
|
|
{
|
|
if (Settings.Current.PlaySound)
|
|
{
|
|
Notify.Interop.Sound.Play();
|
|
}
|
|
|
|
// 目标窗口已是前台(你正盯着看):完成类通知弹一下即可,用更短的停留
|
|
int? durationOverride = null;
|
|
if (!message.InputMode && message.TargetHwnd != 0 &&
|
|
Notify.Interop.Win32.GetForegroundWindow().ToInt64() == message.TargetHwnd)
|
|
{
|
|
durationOverride = Settings.Current.FocusedDurationSeconds;
|
|
}
|
|
|
|
Dispatcher.UIThread.Post(() => Toasts.Show(new ToastRequest
|
|
{
|
|
Title = message.Title,
|
|
Message = message.Message,
|
|
InputMode = message.InputMode,
|
|
Sticky = message.Sticky,
|
|
TargetHwnd = message.TargetHwnd,
|
|
WtRuntimeId = message.WtRuntimeId,
|
|
IconPath = message.IconPath,
|
|
DurationSecondsOverride = durationOverride,
|
|
}));
|
|
}
|
|
|
|
private void RunDemo()
|
|
{
|
|
// 普通:会自动消失
|
|
Toasts.Show(new ToastRequest { Title = "Claude Code", Message = "任务已完成 — 4 秒后自动消失" });
|
|
// 常驻:InputMode 且 Sticky,不点不消失
|
|
Toasts.Show(new ToastRequest
|
|
{
|
|
Title = "需要你的输入",
|
|
Message = "权限请求 — 常驻,点击 / ✕ 才关闭",
|
|
InputMode = true,
|
|
Sticky = true,
|
|
});
|
|
}
|
|
|
|
// 左键单击托盘图标直接打开设置
|
|
private void OnTrayClicked(object? sender, EventArgs e) => OpenSettings();
|
|
|
|
private void OpenSettings()
|
|
{
|
|
if (_settingsWindow is { } w)
|
|
{
|
|
w.Activate();
|
|
return;
|
|
}
|
|
|
|
_settingsWindow = new SettingsWindow
|
|
{
|
|
DataContext = new SettingsViewModel(Settings, Toasts),
|
|
};
|
|
_settingsWindow.Closed += (_, _) => _settingsWindow = null;
|
|
_settingsWindow.Show();
|
|
}
|
|
|
|
private void OnExitClick(object? sender, EventArgs e)
|
|
{
|
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
{
|
|
desktop.Shutdown();
|
|
}
|
|
}
|
|
}
|