diff --git a/Wino.Mail.WinUI/App.xaml.cs b/Wino.Mail.WinUI/App.xaml.cs index a930ffe0..490d5c70 100644 --- a/Wino.Mail.WinUI/App.xaml.cs +++ b/Wino.Mail.WinUI/App.xaml.cs @@ -351,4 +351,29 @@ public partial class App : WinoApplication, IRecipient + /// Handles activation redirected from another instance (single-instancing). + /// This is called when a second instance tries to launch and redirects to this existing instance. + /// + public void HandleRedirectedActivation(AppActivationArguments args) + { + // Dispatch to UI thread since this is called from Program.OnActivated + MainWindow?.DispatcherQueue.TryEnqueue(() => + { + // Handle different activation kinds + if (args.Kind == ExtendedActivationKind.AppNotification) + { + // Handle toast notification activation + var toastArgs = (AppNotificationActivatedEventArgs)args.Data; + _ = HandleToastActivationAsync(toastArgs); + } + else + { + // For other activation types (Launch, Protocol, etc.), bring window to front + MainWindow?.BringToFront(); + MainWindow?.Activate(); + } + }); + } } diff --git a/Wino.Mail.WinUI/Program.cs b/Wino.Mail.WinUI/Program.cs new file mode 100644 index 00000000..62d87af2 --- /dev/null +++ b/Wino.Mail.WinUI/Program.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.Windows.AppLifecycle; + +namespace Wino.Mail.WinUI; + +public class Program +{ + [STAThread] + static int Main(string[] args) + { + WinRT.ComWrappersSupport.InitializeComWrappers(); + bool isRedirect = DecideRedirection(); + + if (!isRedirect) + { + Application.Start((p) => + { + var context = new DispatcherQueueSynchronizationContext( + DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + _ = new App(); + }); + } + + return 0; + } + + private static bool DecideRedirection() + { + bool isRedirect = false; + AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs(); + ExtendedActivationKind kind = args.Kind; + AppInstance keyInstance = AppInstance.FindOrRegisterForKey("WinoMailSingleInstance"); + + if (keyInstance.IsCurrent) + { + keyInstance.Activated += OnActivated; + } + else + { + isRedirect = true; + RedirectActivationTo(args, keyInstance); + } + + return isRedirect; + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr CreateEvent( + IntPtr lpEventAttributes, bool bManualReset, + bool bInitialState, string lpName); + + [DllImport("kernel32.dll")] + private static extern bool SetEvent(IntPtr hEvent); + + [DllImport("ole32.dll")] + private static extern uint CoWaitForMultipleObjects( + uint dwFlags, uint dwMilliseconds, ulong nHandles, + IntPtr[] pHandles, out uint dwIndex); + + [DllImport("user32.dll")] + static extern bool SetForegroundWindow(IntPtr hWnd); + + private static IntPtr redirectEventHandle = IntPtr.Zero; + + // Do the redirection on another thread, and use a non-blocking + // wait method to wait for the redirection to complete. + public static void RedirectActivationTo(AppActivationArguments args, + AppInstance keyInstance) + { + redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null!); + Task.Run(() => + { + keyInstance.RedirectActivationToAsync(args).AsTask().Wait(); + SetEvent(redirectEventHandle); + }); + + uint CWMO_DEFAULT = 0; + uint INFINITE = 0xFFFFFFFF; + _ = CoWaitForMultipleObjects( + CWMO_DEFAULT, INFINITE, 1, + [redirectEventHandle], out uint handleIndex); + + // Bring the window to the foreground + Process process = Process.GetProcessById((int)keyInstance.ProcessId); + SetForegroundWindow(process.MainWindowHandle); + } + + private static void OnActivated(object? sender, AppActivationArguments args) + { + // When a new instance tries to launch, this event fires in the existing instance. + // We need to notify the App to handle the activation (e.g., bring window to front, handle protocol). + if (Application.Current is App app) + { + app.HandleRedirectedActivation(args); + } + } +} diff --git a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj index d27338d1..05a4576e 100644 --- a/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj +++ b/Wino.Mail.WinUI/Wino.Mail.WinUI.csproj @@ -11,6 +11,7 @@ true true enable + $(DefineConstants);DISABLE_XAML_GENERATED_MAIN