Selaa lähdekoodia

feat: 程序单实例限制 - 防止双开,重复启动时激活已有窗口

DESKTOP-74CLTRG\Leol 1 kuukausi sitten
vanhempi
commit
910f068a8f
1 muutettua tiedostoa jossa 150 lisäystä ja 0 poistoa
  1. 150 0
      src/YZWater.Avalonia/Services/SingleInstanceHelper.cs

+ 150 - 0
src/YZWater.Avalonia/Services/SingleInstanceHelper.cs

@@ -0,0 +1,150 @@
+using System;
+using System.IO;
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using Serilog;
+
+namespace YZWater.Avalonia;
+
+/// <summary>
+/// 单实例管理:确保程序只运行一个实例,新启动时激活已有窗口
+/// </summary>
+internal static class SingleInstanceHelper
+{
+    private const string MutexName = "Global\\YZWater3_SingleInstance";
+    private const string PipeName = "YZWater3_SingleInstance_Pipe";
+
+    private static Mutex? _mutex;
+
+    /// <summary>
+    /// 尝试获取单实例权限
+    /// </summary>
+    /// <returns>true 表示这是第一个实例;false 表示已有实例运行</returns>
+    public static bool TryAcquire()
+    {
+        _mutex = new Mutex(true, MutexName, out bool createdNew);
+        return createdNew;
+    }
+
+    /// <summary>
+    /// 通知已有实例激活窗口
+    /// </summary>
+    public static void ActivateExistingInstance()
+    {
+        try
+        {
+            using var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out);
+            client.Connect(1000); // 1秒超时
+            using var writer = new StreamWriter(client);
+            writer.WriteLine("ACTIVATE");
+            writer.Flush();
+        }
+        catch (Exception ex)
+        {
+            Log.Warning(ex, "通知已有实例失败");
+        }
+    }
+
+    /// <summary>
+    /// 启动监听,等待新实例的激活请求
+    /// </summary>
+    public static void StartListening()
+    {
+        Task.Run(() =>
+        {
+            while (true)
+            {
+                try
+                {
+                    using var server = new NamedPipeServerStream(
+                        PipeName, PipeDirection.In, 1,
+                        PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
+
+                    // 等待连接(阻塞)
+                    var ar = server.BeginWaitForConnection(null, null);
+                    server.EndWaitForConnection(ar);
+
+                    if (!server.IsConnected) continue;
+
+                    // 读取命令
+                    using var reader = new StreamReader(server);
+                    var command = reader.ReadLine();
+
+                    if (command == "ACTIVATE")
+                    {
+                        // 在 UI 线程激活主窗口
+                        Dispatcher.UIThread.Post(() =>
+                        {
+                            ActivateMainWindow();
+                        });
+                    }
+
+                    // 断开后继续等待下一次
+                    server.Disconnect();
+                }
+                catch (Exception ex)
+                {
+                    Log.Warning(ex, "单实例监听异常");
+                    Thread.Sleep(500); // 避免异常风暴
+                }
+            }
+        });
+    }
+
+    /// <summary>
+    /// 激活主窗口(带到前台并聚焦)
+    /// </summary>
+    private static void ActivateMainWindow()
+    {
+        try
+        {
+            if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                var mainWindow = desktop.MainWindow;
+                if (mainWindow == null) return;
+
+                // 如果窗口最小化,恢复正常状态
+                if (mainWindow.WindowState == WindowState.Minimized)
+                {
+                    mainWindow.WindowState = WindowState.Normal;
+                }
+
+                // 激活窗口(带到前台)
+                mainWindow.Activate();
+                mainWindow.Focus();
+
+                // 尝试置顶再取消(某些系统需要这个技巧才能真正到前台)
+                var oldTopmost = mainWindow.Topmost;
+                mainWindow.Topmost = true;
+                Task.Delay(100).ContinueWith(_ =>
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        mainWindow.Topmost = oldTopmost;
+                    });
+                });
+
+                Log.Information("已激活主窗口");
+            }
+        }
+        catch (Exception ex)
+        {
+            Log.Warning(ex, "激活主窗口失败");
+        }
+    }
+
+    /// <summary>
+    /// 释放单实例资源
+    /// </summary>
+    public static void Release()
+    {
+        _mutex?.ReleaseMutex();
+        _mutex?.Dispose();
+        _mutex = null;
+    }
+}