Files
notify/Notify/App.axaml.cs
T
chuan b89223fa54 feat: 定位模型重做、聚焦短显、托盘交互与 host spawn 修复
- 定位改为 水平(左/中/右) × 垂直(上/中/下) + 边缘留白,取代四角枚举
- 完成类通知在目标窗口已聚焦时用更短停留(FocusedDurationSeconds)
- 托盘左键单击直接打开设置,右键菜单仅保留退出
- 修复 host 拉起用 UseShellExecute=true,脱离钩子 stdout 管道,避免 Claude 卡在 running stop hook
2026-06-22 16:40:27 +08:00

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 目录,把瘦客户端投递的请求转成 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();
}
}
}