using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; using KronaStaffTerminal.Services; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. namespace KronaStaffTerminal; /// /// Provides application-specific behavior to supplement the default Application class. /// public partial class App : Application { private const string InstanceMutexName = @"Global\KronaStaffTerminal.Singleton"; private const string ActivationEventName = @"Global\KronaStaffTerminal.Activate"; private TrayService? _trayService; private Microsoft.UI.Dispatching.DispatcherQueueTimer? _autostartTimer; private Mutex? _instanceMutex; private EventWaitHandle? _activationEvent; private CancellationTokenSource? _activationCancellation; private Task? _activationTask; /// /// The main application window. Use App.Window from any class that needs /// the window reference (for dialogs, pickers, interop, etc.). /// public static Window Window { get; private set; } = null!; /// /// The UI thread dispatcher. Use App.DispatcherQueue to marshal calls /// to the UI thread. Fully qualified to avoid CS0104 ambiguity with /// . /// public static Microsoft.UI.Dispatching.DispatcherQueue DispatcherQueue { get; private set; } = null!; /// /// The native window handle (HWND). Use for file pickers, /// DataTransferManager, and any WinRT interop that requires /// InitializeWithWindow. /// public static nint WindowHandle => WinRT.Interop.WindowNative.GetWindowHandle(Window); /// /// Initializes the singleton application object. /// public App() { InitializeComponent(); UnhandledException += (_, eventArgs) => CrashLogService.Write(eventArgs.Exception); AppDomain.CurrentDomain.UnhandledException += (_, eventArgs) => { if (eventArgs.ExceptionObject is Exception exception) CrashLogService.Write(exception); }; TaskScheduler.UnobservedTaskException += (_, eventArgs) => { CrashLogService.Write(eventArgs.Exception.Flatten()); eventArgs.SetObserved(); }; } /// /// Invoked when the application is launched. /// /// Details about the launch request and process. protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) { _instanceMutex = new Mutex(true, InstanceMutexName, out var isFirstInstance); if (!isFirstInstance) { try { EventWaitHandle.OpenExisting(ActivationEventName).Set(); } catch (WaitHandleCannotBeOpenedException) { // The first instance is still starting; exiting avoids competing for the scanner. } Environment.Exit(0); return; } Window = new MainWindow(); DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); _trayService = new TrayService(ShowWindow, RequestExit, ShowWindow, RequestSync); _activationEvent = new EventWaitHandle(false, EventResetMode.AutoReset, ActivationEventName); _activationCancellation = new CancellationTokenSource(); _activationTask = Task.Run(() => ListenForActivation(_activationCancellation.Token)); Window.Activate(); if (Environment.GetCommandLineArgs().Any(argument => argument.Equals("--autostart", StringComparison.OrdinalIgnoreCase))) { _autostartTimer = DispatcherQueue.CreateTimer(); _autostartTimer.Interval = TimeSpan.FromSeconds(1); _autostartTimer.IsRepeating = false; _autostartTimer.Tick += (_, _) => { if (Window is MainWindow mainWindow) { mainWindow.HideToTray(); } _autostartTimer = null; }; _autostartTimer.Start(); } } private void ListenForActivation(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { if (_activationEvent?.WaitOne(TimeSpan.FromSeconds(1)) == true) { if (cancellationToken.IsCancellationRequested) return; DispatcherQueue.TryEnqueue(ShowWindow); } } } public void NotifyMinimizedToTray() => _trayService?.ShowRunningBalloon(); public void UpdateTray(TrayState state, int pendingCount, string? statusText = null) => _trayService?.UpdateState(state, pendingCount, statusText); private static void ShowWindow() { if (Window is MainWindow mainWindow) { mainWindow.ShowWindow(); } } private static void RequestSync() { if (Window is MainWindow mainWindow && mainWindow.Content is FrameworkElement fe && fe.FindName("RootFrame") is Microsoft.UI.Xaml.Controls.Frame frame && frame.Content is MainPage page) { _ = page.SyncPendingAttendanceAsync(); } } private static void RequestExit() { if (Window is MainWindow mainWindow) { mainWindow.ShowWindow(); _ = mainWindow.RequestExitAsync(); } } public void CompleteExit() { _activationCancellation?.Cancel(); _activationEvent?.Set(); var activationStopped = true; try { activationStopped = _activationTask?.Wait(TimeSpan.FromSeconds(2)) != false; } catch (AggregateException exception) { CrashLogService.Write(exception.Flatten()); } if (activationStopped) { _activationTask = null; _activationCancellation?.Dispose(); _activationCancellation = null; _activationEvent?.Dispose(); _activationEvent = null; } else { CrashLogService.Write(new TimeoutException("Слушатель активации не завершился; его ресурсы оставлены до выхода процесса.")); } _instanceMutex?.ReleaseMutex(); _instanceMutex?.Dispose(); _instanceMutex = null; _trayService?.Dispose(); _trayService = null; if (Window is MainWindow mainWindow) { mainWindow.ExitApplication(); } } }