138 lines
5.2 KiB
C#
138 lines
5.2 KiB
C#
using System.IO.Pipes;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Threading;
|
|
|
|
namespace SHD.Windows;
|
|
|
|
public partial class App : Application
|
|
{
|
|
private const string MutexName = @"Local\SHD.Desktop.Windows";
|
|
private const string PipeName = "SHD.Desktop.Windows.Auth";
|
|
private static readonly UTF8Encoding Utf8WithoutBom = new(encoderShouldEmitUTF8Identifier: false);
|
|
private Mutex? _instanceMutex;
|
|
private bool _ownsMutex;
|
|
private MainWindow? _mainWindow;
|
|
private readonly CancellationTokenSource _pipeCancellation = new();
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
{
|
|
base.OnStartup(e);
|
|
|
|
_instanceMutex = new Mutex(initiallyOwned: true, MutexName, out _ownsMutex);
|
|
if (!_ownsMutex)
|
|
{
|
|
var command = e.Args.FirstOrDefault(IsDesktopAuthCallback) ?? "activate";
|
|
await ForwardToPrimaryInstanceAsync(command);
|
|
Shutdown();
|
|
return;
|
|
}
|
|
|
|
_mainWindow = new MainWindow();
|
|
MainWindow = _mainWindow;
|
|
_mainWindow.Show();
|
|
_ = ListenForSecondInstanceAsync(_pipeCancellation.Token);
|
|
|
|
var callback = e.Args.FirstOrDefault(IsDesktopAuthCallback);
|
|
if (callback is not null)
|
|
{
|
|
await _mainWindow.Dispatcher.InvokeAsync(() => _mainWindow.HandleAuthCallback(callback), DispatcherPriority.Loaded);
|
|
}
|
|
}
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
{
|
|
_pipeCancellation.Cancel();
|
|
if (_ownsMutex)
|
|
{
|
|
try { _instanceMutex?.ReleaseMutex(); }
|
|
catch (ApplicationException) { }
|
|
}
|
|
_instanceMutex?.Dispose();
|
|
_pipeCancellation.Dispose();
|
|
base.OnExit(e);
|
|
}
|
|
|
|
private async Task ListenForSecondInstanceAsync(CancellationToken cancellationToken)
|
|
{
|
|
while (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await using var server = new NamedPipeServerStream(
|
|
PipeName,
|
|
PipeDirection.In,
|
|
1,
|
|
PipeTransmissionMode.Byte,
|
|
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
|
|
await server.WaitForConnectionAsync(cancellationToken);
|
|
using var reader = new StreamReader(server, Utf8WithoutBom, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true);
|
|
var message = await reader.ReadLineAsync(cancellationToken);
|
|
if (string.IsNullOrWhiteSpace(message)) continue;
|
|
|
|
await Dispatcher.InvokeAsync(() =>
|
|
{
|
|
var windows = Application.Current.Windows.OfType<MainWindow>().ToArray();
|
|
if (IsDesktopAuthCallback(message))
|
|
{
|
|
var authWindow = windows.FirstOrDefault(window => window.CanHandleAuthCallback(message));
|
|
if (authWindow is not null)
|
|
{
|
|
if (authWindow.WindowState == WindowState.Minimized) authWindow.WindowState = WindowState.Normal;
|
|
authWindow.Activate();
|
|
authWindow.HandleAuthCallback(message);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var fallbackWindow = windows.FirstOrDefault() ?? _mainWindow;
|
|
if (fallbackWindow is null) return;
|
|
if (fallbackWindow.WindowState == WindowState.Minimized) fallbackWindow.WindowState = WindowState.Normal;
|
|
fallbackWindow.Activate();
|
|
if (IsDesktopAuthCallback(message)) fallbackWindow.HandleAuthCallback(message);
|
|
});
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
if (!cancellationToken.IsCancellationRequested) await Task.Delay(150, cancellationToken);
|
|
}
|
|
catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static async Task ForwardToPrimaryInstanceAsync(string command)
|
|
{
|
|
for (var attempt = 0; attempt < 12; attempt++)
|
|
{
|
|
try
|
|
{
|
|
await using var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out, PipeOptions.Asynchronous);
|
|
await client.ConnectAsync(500);
|
|
await using var writer = new StreamWriter(client, Utf8WithoutBom, bufferSize: 1024, leaveOpen: true);
|
|
await writer.WriteLineAsync(command);
|
|
await writer.FlushAsync();
|
|
return;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
await Task.Delay(150);
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
await Task.Delay(150);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsDesktopAuthCallback(string value) =>
|
|
Uri.TryCreate(value, UriKind.Absolute, out var uri) &&
|
|
uri.Scheme.Equals("shd-auth", StringComparison.OrdinalIgnoreCase);
|
|
}
|