StartupManager 早就实现了,但之前: 1. 没 UI 入口 —— 用户得去注册表手动改 2. App.OnStartup 里只 EnableCurrentUserStartup,不 Disable —— 用户禁用了下次启动会被强制写回,改不掉 现在: MainWindow.xaml 设置区(URL/Token 下方)加 CheckBox,跨两列: - 标签:"开机自动启动(写入 HKCU\Software\Microsoft\Windows\CurrentVersion\Run,只对当前用户生效)" - Tooltip 说明 - 初始状态从 config.Startup.Enabled 读 MainWindow.xaml.cs StartupCheckBox_Click: - 勾选变化 → 改 _config.Startup.Enabled → 调 EnableCurrentUserStartup 或 DisableCurrentUserStartup → 异步 SaveAsync - 状态条反馈 "已开启/关闭开机启动" - 异常时状态条变 ErrorDot 红色 App.xaml.cs OnStartup 双向同步: - config.Startup.Enabled=true → Enable - config.Startup.Enabled=false → Disable (之前是单向只 Enable,导致用户在 UI 禁用了重启又被写回) config 是 source of truth,UI 改了立刻落盘 + 同步注册表,下次启动再 sync 一次保证一致。
165 lines
5.2 KiB
C#
165 lines
5.2 KiB
C#
using System.Diagnostics;
|
|
using System.Windows;
|
|
using Microsoft.Windows.AppNotifications;
|
|
|
|
// 双开 WPF + WinForms 时,Application 在两个命名空间都有,用别名强制走 WPF 的。
|
|
using Application = System.Windows.Application;
|
|
|
|
namespace WindowsGotify;
|
|
|
|
public partial class App : Application
|
|
{
|
|
private static Mutex? _mutex;
|
|
private Services.ConfigService? _configService;
|
|
private Services.Logger? _logger;
|
|
private Services.StateService? _stateService;
|
|
private Services.GotifyApiService? _apiService;
|
|
private Services.GotifyWebSocketService? _webSocketService;
|
|
private Services.NotificationService? _notificationService;
|
|
private Services.ApplicationCache? _appCache;
|
|
private Tray.TrayIconService? _trayService;
|
|
private MainWindow? _mainWindow;
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
{
|
|
base.OnStartup(e);
|
|
|
|
_mutex = new Mutex(true, "WindowsGotifyAgent-SingleInstance", out bool createdNew);
|
|
if (!createdNew)
|
|
{
|
|
Shutdown();
|
|
return;
|
|
}
|
|
|
|
_logger = new Services.Logger();
|
|
_configService = new Services.ConfigService(_logger);
|
|
var config = await _configService.LoadAsync();
|
|
|
|
_stateService = new Services.StateService(_logger);
|
|
_apiService = new Services.GotifyApiService(config, _logger);
|
|
_notificationService = new Services.NotificationService(config, _logger);
|
|
_appCache = new Services.ApplicationCache(_apiService, _logger);
|
|
|
|
try
|
|
{
|
|
// NotificationInvoked 必须在 Register 之前注册。
|
|
AppNotificationManager.Default.NotificationInvoked += OnNotificationInvoked;
|
|
AppNotificationManager.Default.Register();
|
|
_logger.Info("Windows App SDK notification manager registered.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error("Notification registration failed.", ex);
|
|
}
|
|
|
|
_mainWindow = new MainWindow();
|
|
_mainWindow.Hide();
|
|
|
|
_trayService = new Tray.TrayIconService(
|
|
onShowSettings: () => _mainWindow.ShowFromTray(),
|
|
onTest: async () => await SendTestNotificationAsync(),
|
|
onReconnect: () => _webSocketService?.RequestReconnect(),
|
|
onExit: () => Shutdown()
|
|
);
|
|
|
|
_webSocketService = new Services.GotifyWebSocketService(
|
|
config,
|
|
_logger,
|
|
_stateService,
|
|
_apiService,
|
|
_notificationService,
|
|
status => _trayService.SetStatus(status));
|
|
|
|
_mainWindow.Initialize(
|
|
config,
|
|
_configService,
|
|
_logger,
|
|
_trayService,
|
|
_webSocketService,
|
|
_apiService,
|
|
_appCache);
|
|
|
|
// 开机启动:config 是 source of truth,启动时双向同步 Run 项
|
|
// (如果用户在 UI 里禁用过,启动时也会确保从 Run 项里删掉)
|
|
if (config.Startup.Enabled)
|
|
{
|
|
Utils.StartupManager.EnableCurrentUserStartup(_logger);
|
|
}
|
|
else
|
|
{
|
|
Utils.StartupManager.DisableCurrentUserStartup(_logger);
|
|
}
|
|
|
|
// 后台异步拉一次应用列表,不阻塞启动
|
|
_ = _appCache.RefreshAsync();
|
|
|
|
// 加载主题(必须在 StartAsync 之前,这样主窗第一次 Show 时资源已经在)
|
|
Services.ThemeService.Apply(config.Ui.Theme);
|
|
|
|
await _webSocketService.StartAsync();
|
|
}
|
|
|
|
private async Task SendTestNotificationAsync()
|
|
{
|
|
if (_notificationService is null)
|
|
return;
|
|
|
|
await _notificationService.ShowAsync(
|
|
new Models.GotifyMessage
|
|
{
|
|
Id = 0,
|
|
AppId = 0,
|
|
Message = "Windows Gotify Agent 测试通知。",
|
|
Title = "Gotify 测试",
|
|
Priority = 5,
|
|
Date = DateTimeOffset.UtcNow
|
|
});
|
|
}
|
|
|
|
private void OnNotificationInvoked(
|
|
AppNotificationManager sender,
|
|
AppNotificationActivatedEventArgs args)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
try
|
|
{
|
|
// Windows App SDK 2.4.0:args.Arguments 已经是 IDictionary<string, string>,由 AddArgument(key, value) 注入
|
|
if (args.Arguments.TryGetValue("url", out var url) && !string.IsNullOrWhiteSpace(url))
|
|
{
|
|
Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = url,
|
|
UseShellExecute = true
|
|
});
|
|
}
|
|
else
|
|
{
|
|
_mainWindow?.ShowFromTray();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.Error("Notification invocation failed.", ex);
|
|
}
|
|
});
|
|
}
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
_webSocketService?.Stop();
|
|
_trayService?.Dispose();
|
|
AppNotificationManager.Default.Unregister();
|
|
AppNotificationManager.Default.NotificationInvoked -= OnNotificationInvoked;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.Error("Application cleanup failed.", ex);
|
|
}
|
|
|
|
base.OnExit(e);
|
|
}
|
|
}
|