krona-staff/apps/KronaStaffTerminal/App.xaml.cs
2026-09-24 18:09:42 +07:00

192 lines
6.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
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;
/// <summary>
/// The main application window. Use <c>App.Window</c> from any class that needs
/// the window reference (for dialogs, pickers, interop, etc.).
/// </summary>
public static Window Window { get; private set; } = null!;
/// <summary>
/// The UI thread dispatcher. Use <c>App.DispatcherQueue</c> to marshal calls
/// to the UI thread. Fully qualified to avoid CS0104 ambiguity with
/// <see cref="Windows.System.DispatcherQueue"/>.
/// </summary>
public static Microsoft.UI.Dispatching.DispatcherQueue DispatcherQueue { get; private set; } = null!;
/// <summary>
/// The native window handle (HWND). Use for file pickers,
/// <c>DataTransferManager</c>, and any WinRT interop that requires
/// <c>InitializeWithWindow</c>.
/// </summary>
public static nint WindowHandle =>
WinRT.Interop.WindowNative.GetWindowHandle(Window);
/// <summary>
/// Initializes the singleton application object.
/// </summary>
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();
};
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
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 ShowNotificationBalloon(string title, string message, uint infoFlags = 1) =>
_trayService?.ShowNotificationBalloon(title, message, infoFlags);
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();
}
}
}