192 lines
6.9 KiB
C#
192 lines
6.9 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|