using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Threading; using AvaloniaWebView; using Hua.Todo.Avalonia.Models; using Hua.Todo.Avalonia.Services; using Hua.Todo.Avalonia.Services.Platforms; using Hua.Todo.Avalonia.ViewModels; using Hua.Todo.Avalonia.Views; using System; using System.ComponentModel; using System.IO; using System.Text.Json; using System.Threading.Tasks; namespace Hua.Todo.Avalonia; /// /// Avalonia 应用入口。 /// 负责启动嵌入式 WebServer、创建主窗口并初始化 WebView 能力。 /// public partial class App : global::Avalonia.Application { private AppSettings? _appSettings; private IEmbeddedWebServerService? _webServer; private IHotKeySettingsService? _hotKeySettingsService; private IGlobalHotKeyService? _hotKeyService; private HotKeyConfig? _hotKeyConfig; private WindowsKeyboardHandler? _keyboardHandler; private MainWindow? _mainWindow; private bool _isExitRequested; private bool _isHotkeyRegistered; /// /// 初始化应用资源并加载配置。 /// public override void Initialize() { AvaloniaXamlLoader.Load(this); LoadSettings(); } /// /// 注册应用级服务。 /// public override void RegisterServices() { base.RegisterServices(); AvaloniaWebViewBuilder.Initialize(default); } private void LoadSettings() { try { var settingsPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json"); if (File.Exists(settingsPath)) { var json = File.ReadAllText(settingsPath); _appSettings = JsonSerializer.Deserialize(json); } } catch (Exception ex) { Console.WriteLine($"[App] Failed to load settings: {ex.Message}"); } _appSettings ??= new AppSettings(); EnsureDefaultConnectionString(_appSettings); } private static void EnsureDefaultConnectionString(AppSettings appSettings) { if (string.IsNullOrWhiteSpace(appSettings.WebServer.ConnectionString)) { var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var dbDir = Path.Combine(localAppData, "Hua.Todo"); if (!Directory.Exists(dbDir)) { Directory.CreateDirectory(dbDir); } var dbPath = Path.Combine(dbDir, "Hua.Todo.db"); appSettings.WebServer.ConnectionString = $"Data Source={dbPath};Cache=Shared"; } } /// /// Avalonia 框架初始化完成回调。 /// 启动嵌入式 WebServer(后台启动并捕获异常,避免阻塞/崩溃),并创建主窗口。 /// public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { _webServer = EmbeddedWebServerServiceFactory.Create(_appSettings!); _ = Task.Run(async () => { try { await _webServer.StartAsync(); } catch (Exception ex) { Console.WriteLine($"[App] Web server start failed: {ex}"); } }); _hotKeySettingsService = new HotKeySettingsService(_appSettings!); _hotKeyConfig = _hotKeySettingsService.GetConfig(); _hotKeyService = GlobalHotKeyServiceFactory.Create(); if (OperatingSystem.IsWindows()) { _keyboardHandler = new WindowsKeyboardHandler(); _keyboardHandler.EscKeyPressed += (_, _) => { Dispatcher.UIThread.Post(() => { if (_mainWindow is { IsVisible: true }) { _mainWindow.WindowState = WindowState.Minimized; } }); }; } // 只在支持的平台上初始化系统托盘 if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) { var trayViewModel = new AppTrayViewModel( AppMetadata.GetTrayTooltipText(), ShowMainWindow, () => ExitApplication(desktop)); InitializeTrayIcon(trayViewModel); } _mainWindow = new MainWindow(_appSettings!, _webServer) { Width = 450, Height = 640, Title = AppMetadata.GetWindowTitle(), }; _mainWindow.Opened += (_, _) => { RegisterHotkey(); _keyboardHandler?.Start(); }; _mainWindow.Closing += (_, e) => { if (_isExitRequested) { return; } e.Cancel = true; _mainWindow.Hide(); }; desktop.MainWindow = _mainWindow; desktop.Exit += async (s, e) => { _keyboardHandler?.Dispose(); _keyboardHandler = null; if (_isHotkeyRegistered) { _hotKeyService?.UnregisterHotKey(); _isHotkeyRegistered = false; } if (_hotKeyService is IDisposable disposableHotKeyService) { disposableHotKeyService.Dispose(); } if (_webServer != null) { await _webServer.StopAsync(); } }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView) { _webServer = EmbeddedWebServerServiceFactory.Create(_appSettings!); singleView.MainView = new MainView(_appSettings!, _webServer); } base.OnFrameworkInitializationCompleted(); } private void ExitApplication(IClassicDesktopStyleApplicationLifetime desktop) { _isExitRequested = true; desktop.Shutdown(); } private void ShowMainWindow() { if (_mainWindow == null) return; if (!_mainWindow.IsVisible) { _mainWindow.Show(); } if (_mainWindow.WindowState == WindowState.Minimized) { _mainWindow.WindowState = WindowState.Normal; } _mainWindow.Activate(); } private void RegisterHotkey() { if (_hotKeyService == null || _hotKeySettingsService == null) return; var config = _hotKeyConfig ?? _hotKeySettingsService.GetConfig(); _hotKeyConfig = config; if (_hotKeyService.IsSupported && config.IsEnabled) { _hotKeyService.RegisterHotKey(config.Modifiers, config.Key, () => { Dispatcher.UIThread.Post(ShowMainWindow); }); _isHotkeyRegistered = true; } config.PropertyChanged -= OnHotKeyConfigPropertyChanged; config.PropertyChanged += OnHotKeyConfigPropertyChanged; } private void OnHotKeyConfigPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (_hotKeyService == null || _hotKeySettingsService == null || _hotKeyConfig == null) return; if (e.PropertyName != nameof(HotKeyConfig.Modifiers) && e.PropertyName != nameof(HotKeyConfig.Key) && e.PropertyName != nameof(HotKeyConfig.IsEnabled)) { return; } _hotKeySettingsService.SaveConfig(_hotKeyConfig); if (_hotKeyConfig.IsEnabled && _hotKeyService.IsSupported) { _hotKeyService.UpdateHotKey(_hotKeyConfig.Modifiers, _hotKeyConfig.Key); _isHotkeyRegistered = true; } else { _hotKeyService.UnregisterHotKey(); _isHotkeyRegistered = false; } } private void InitializeTrayIcon(AppTrayViewModel trayViewModel) { try { var trayIcons = new TrayIcons(); var iconUri = new Uri("avares://Hua.Todo.Avalonia/Assets/avalonia-logo.ico"); var icon = new WindowIcon(AssetLoader.Open(iconUri)); var trayIcon = new TrayIcon { Icon = icon, ToolTipText = trayViewModel.TrayTooltipText, Command = trayViewModel.ShowMainWindowCommand }; var menu = new NativeMenu(); menu.Items.Add(new NativeMenuItem { Header = "显示主窗口", Command = trayViewModel.ShowMainWindowCommand }); menu.Items.Add(new NativeMenuItemSeparator()); menu.Items.Add(new NativeMenuItem { Header = "退出", Command = trayViewModel.ExitApplicationCommand }); trayIcon.Menu = menu; trayIcons.Add(trayIcon); TrayIcon.SetIcons(this, trayIcons); } catch (Exception ex) { Console.WriteLine($"[App] Tray icon initialization failed: {ex}"); } } }