修复托盘退出卡死、启动隐藏闪窗和隐藏实例再次启动无法唤醒的问题。 统一深色主题资源与控件模板,补齐卡片、内置图标、右键菜单和弹窗背景样式。 验证:dotnet build PersonalToolbox.sln;dotnet run --project tests\PersonalToolbox.Tests\PersonalToolbox.Tests.csproj。
149 lines
4.2 KiB
C#
149 lines
4.2 KiB
C#
using System.Configuration;
|
|
using System.Data;
|
|
using System.IO;
|
|
using System.IO.Pipes;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Threading;
|
|
|
|
namespace PersonalToolbox;
|
|
|
|
/// <summary>
|
|
/// Interaction logic for App.xaml
|
|
/// </summary>
|
|
public partial class App : System.Windows.Application
|
|
{
|
|
private const string ActivatePipeName = "PersonalToolbox.Activate";
|
|
|
|
private FileStream? _instanceLockFile;
|
|
private CancellationTokenSource? _activationPipeCancellation;
|
|
private MainWindow? _mainWindow;
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
{
|
|
base.OnStartup(e);
|
|
|
|
ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
|
|
|
if (!TryAcquireInstanceLock())
|
|
{
|
|
SignalExistingInstance();
|
|
Environment.Exit(0);
|
|
return;
|
|
}
|
|
|
|
var mainWindow = new MainWindow();
|
|
_mainWindow = mainWindow;
|
|
MainWindow = mainWindow;
|
|
StartActivationPipe();
|
|
|
|
await mainWindow.InitializeShellAsync();
|
|
if (!mainWindow.StartHiddenToTray)
|
|
{
|
|
mainWindow.ShowMainWindow();
|
|
}
|
|
else
|
|
{
|
|
mainWindow.RefreshTrayMenu();
|
|
}
|
|
|
|
await mainWindow.RunStartupTasksAsync();
|
|
}
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
{
|
|
_activationPipeCancellation?.Cancel();
|
|
_activationPipeCancellation?.Dispose();
|
|
_instanceLockFile?.Dispose();
|
|
base.OnExit(e);
|
|
}
|
|
|
|
private bool TryAcquireInstanceLock()
|
|
{
|
|
try
|
|
{
|
|
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
|
var configDirectory = Path.Combine(appData, "PersonalToolbox");
|
|
Directory.CreateDirectory(configDirectory);
|
|
var lockPath = Path.Combine(configDirectory, "PersonalToolbox.lock");
|
|
|
|
_instanceLockFile = new FileStream(lockPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
|
_instanceLockFile.SetLength(0);
|
|
var processId = Encoding.UTF8.GetBytes(Environment.ProcessId.ToString());
|
|
_instanceLockFile.Write(processId, 0, processId.Length);
|
|
_instanceLockFile.Flush();
|
|
return true;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
return false;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void SignalExistingInstance()
|
|
{
|
|
for (var attempt = 0; attempt < 3; attempt++)
|
|
{
|
|
try
|
|
{
|
|
using var pipe = new NamedPipeClientStream(".", ActivatePipeName, PipeDirection.Out);
|
|
pipe.Connect(800);
|
|
using var writer = new StreamWriter(pipe, Encoding.UTF8)
|
|
{
|
|
AutoFlush = true
|
|
};
|
|
writer.WriteLine("show");
|
|
return;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
}
|
|
|
|
Thread.Sleep(150);
|
|
}
|
|
}
|
|
|
|
private void StartActivationPipe()
|
|
{
|
|
_activationPipeCancellation = new CancellationTokenSource();
|
|
_ = Task.Run(() => ListenForActivationAsync(_activationPipeCancellation.Token));
|
|
}
|
|
|
|
private async Task ListenForActivationAsync(CancellationToken cancellationToken)
|
|
{
|
|
while (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await using var pipe = new NamedPipeServerStream(
|
|
ActivatePipeName,
|
|
PipeDirection.In,
|
|
1,
|
|
PipeTransmissionMode.Byte,
|
|
PipeOptions.Asynchronous);
|
|
|
|
await pipe.WaitForConnectionAsync(cancellationToken);
|
|
using var reader = new StreamReader(pipe, Encoding.UTF8);
|
|
_ = await reader.ReadLineAsync(cancellationToken);
|
|
|
|
await Dispatcher.BeginInvoke(() => _mainWindow?.ShowMainWindow());
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|