From b4429c4b6f8bb00df1541b2425ba4e5ecddc519d Mon Sep 17 00:00:00 2001 From: uye <99072975+ABA2396@users.noreply.github.com> Date: Wed, 29 Apr 2026 21:33:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E5=A4=8D=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=97=B6=E9=80=9A=E8=BF=87=E8=B7=A8=E8=BF=9B=E7=A8=8B=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=BF=80=E6=B4=BB=E4=B8=BB=E7=AA=97=E5=8F=A3=E8=80=8C?= =?UTF-8?q?=E9=9D=9E=E5=BC=B9=E7=AA=97=E8=AD=A6=E5=91=8A=20(#16363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 重复启动时通过跨进程事件激活主窗口而非弹窗警告 * perf: GetSingleInstanceKey 改用路径哈希 --- src/MaaWpfGui/Main/Bootstrapper.cs | 111 ++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/src/MaaWpfGui/Main/Bootstrapper.cs b/src/MaaWpfGui/Main/Bootstrapper.cs index c244d11a47..72c472461d 100644 --- a/src/MaaWpfGui/Main/Bootstrapper.cs +++ b/src/MaaWpfGui/Main/Bootstrapper.cs @@ -30,6 +30,7 @@ using System.Windows.Threading; using GlobalHotKey; using MaaWpfGui.Configuration.Factory; using MaaWpfGui.Constants; +using MaaWpfGui.Extensions; using MaaWpfGui.Helper; using MaaWpfGui.Properties; using MaaWpfGui.Services; @@ -62,6 +63,8 @@ public class Bootstrapper : Bootstrapper private static Mutex _mutex; private static bool _hasMutex; + private static EventWaitHandle _instanceActivationEvent; + private static CancellationTokenSource _instanceActivationListenerCancellation; public static readonly string UiLogFile = Path.Combine(PathsHelper.DebugDir, "gui.log"); public static readonly string UiLogBakFile = Path.Combine(PathsHelper.DebugDir, "gui.bak.log"); @@ -540,17 +543,24 @@ public class Bootstrapper : Bootstrapper private static bool HandleMultipleInstances() { - // 设置互斥量的名称 - string mutexName = "MAA_" + PathsHelper.BaseDir.Replace("\\", "_").Replace(":", string.Empty); + string instanceKey = GetSingleInstanceKey(); + string mutexName = "MAA_" + instanceKey; + string activationEventName = "MAA_SHOW_" + instanceKey; _mutex = new Mutex(true, mutexName, out var isOnlyInstance); try { if (isOnlyInstance || _mutex.WaitOne(500)) { + EnsureInstanceActivationEvent(activationEventName); return true; } + if (SignalExistingInstance(activationEventName)) + { + return false; + } + MessageBoxHelper.Show(LocalizationHelper.GetString("MultiInstanceUnderSamePath"), "MAA", MessageBoxButton.OK, MessageBoxImage.Warning); return false; } @@ -558,6 +568,7 @@ public class Bootstrapper : Bootstrapper { // 上一个程序没有正常释放互斥量 // 即使捕获到这个异常,此时也已经获得了锁 + EnsureInstanceActivationEvent(activationEventName); return true; } catch (Exception e) @@ -567,6 +578,94 @@ public class Bootstrapper : Bootstrapper } } + private static string GetSingleInstanceKey() + { + var normalizedBaseDir = Path.GetFullPath(PathsHelper.BaseDir) + .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .ToUpperInvariant(); + return normalizedBaseDir.StableHash(); + } + + private static void EnsureInstanceActivationEvent(string activationEventName) + { + _instanceActivationEvent ??= new EventWaitHandle(false, EventResetMode.AutoReset, activationEventName); + } + + private static bool SignalExistingInstance(string activationEventName) + { + try + { + using var activationEvent = EventWaitHandle.OpenExisting(activationEventName); + activationEvent.Set(); + _logger.Information("Secondary launch detected, activation signal sent to existing instance"); + return true; + } + catch (WaitHandleCannotBeOpenedException) + { + _logger.Warning("Secondary launch detected, but no activation listener was available"); + return false; + } + catch (Exception e) + { + _logger.Warning(e, "Failed to signal the existing instance"); + return false; + } + } + + private static void StartInstanceActivationListener() + { + if (_instanceActivationEvent == null || _instanceActivationListenerCancellation != null) + { + return; + } + + _instanceActivationListenerCancellation = new CancellationTokenSource(); + _ = Task.Run(() => ListenForInstanceActivation(_instanceActivationListenerCancellation.Token)); + } + + private static void ListenForInstanceActivation(CancellationToken cancellationToken) + { + if (_instanceActivationEvent == null) + { + return; + } + + WaitHandle[] waitHandles = [_instanceActivationEvent, cancellationToken.WaitHandle]; + + try + { + while (true) + { + int signaledIndex = WaitHandle.WaitAny(waitHandles); + if (signaledIndex != 0) + { + return; + } + + Application.Current?.Dispatcher.BeginInvoke(new Action(ActivateMainWindow), DispatcherPriority.Normal); + } + } + catch (ObjectDisposedException) + { + // ignored during shutdown + } + catch (Exception e) + { + _logger.Warning(e, "Existing instance activation listener stopped unexpectedly"); + } + } + + private static void ActivateMainWindow() + { + if (Application.Current == null || Application.Current.IsShuttingDown()) + { + return; + } + + Instances.MainWindowManager.Show(); + _logger.Information("Existing instance window activated by a secondary launch"); + } + public static bool IsUserAdministrator() => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); public static bool IsUacEnabled() @@ -639,6 +738,7 @@ public class Bootstrapper : Bootstrapper Instances.WindowManager.ShowWindow(rootViewModel); Instances.InstantiateOnRootViewDisplayed(Container); + StartInstanceActivationListener(); // 如果 IsFirstBootAfterUpdate 从 false 变为 true,说明这次启动只是解压更新包,不用执行后续逻辑 if (!wasFirstBoot && Instances.VersionUpdateDialogViewModel.IsFirstBootAfterUpdate) @@ -735,6 +835,13 @@ public class Bootstrapper : Bootstrapper public static void Release() { + _instanceActivationListenerCancellation?.Cancel(); + _instanceActivationListenerCancellation?.Dispose(); + _instanceActivationListenerCancellation = null; + + _instanceActivationEvent?.Dispose(); + _instanceActivationEvent = null; + ETagCache.Save(); Instances.SettingsViewModel.Sober(); Instances.MaaHotKeyManager.Release();