fix: 修复启动隐藏与深色主题界面问题

修复托盘退出卡死、启动隐藏闪窗和隐藏实例再次启动无法唤醒的问题。

统一深色主题资源与控件模板,补齐卡片、内置图标、右键菜单和弹窗背景样式。

验证:dotnet build PersonalToolbox.sln;dotnet run --project tests\PersonalToolbox.Tests\PersonalToolbox.Tests.csproj。
This commit is contained in:
2026-05-27 17:43:22 +08:00
parent bbc183cef6
commit 3909764972
14 changed files with 720 additions and 152 deletions

View File

@@ -1,6 +1,10 @@
using System.Configuration;
using System.Data;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Windows;
using System.Threading;
namespace PersonalToolbox;
@@ -9,5 +13,136 @@ namespace PersonalToolbox;
/// </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)
{
}
}
}
}