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);
_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();
private static void ShowWindow()
{
if (Window is MainWindow mainWindow)
{
mainWindow.ShowWindow();
}
}
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();
}
}
}