2025-11-14 12:51:19 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2026-04-05 02:19:11 +02:00
|
|
|
using Microsoft.Toolkit.Uwp.Notifications;
|
2025-11-14 12:51:19 +01:00
|
|
|
using Microsoft.UI.Dispatching;
|
|
|
|
|
using Microsoft.UI.Xaml;
|
2026-04-05 02:19:11 +02:00
|
|
|
using Microsoft.Windows.AppNotifications;
|
2025-11-14 12:51:19 +01:00
|
|
|
using Microsoft.Windows.AppLifecycle;
|
2026-04-05 02:19:11 +02:00
|
|
|
using Wino.Core.Domain;
|
|
|
|
|
using Wino.Core.Domain.Enums;
|
2025-11-14 12:51:19 +01:00
|
|
|
|
|
|
|
|
namespace Wino.Mail.WinUI;
|
|
|
|
|
|
|
|
|
|
public class Program
|
|
|
|
|
{
|
2026-02-27 11:00:25 +01:00
|
|
|
private const string SingleInstanceKey = "WinoMailSingleInstance";
|
|
|
|
|
private const string ForceAlternateModeSignalEventName = "Local\\WinoMailForceAlternateMode";
|
|
|
|
|
private const int VkControl = 0x11;
|
|
|
|
|
|
|
|
|
|
private static bool _forceAlternateModeOnLaunch;
|
|
|
|
|
private static EventWaitHandle? _forceAlternateModeSignalHandle;
|
|
|
|
|
|
2025-11-14 12:51:19 +01:00
|
|
|
[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();
|
2026-02-27 11:00:25 +01:00
|
|
|
_forceAlternateModeOnLaunch = args.Kind == ExtendedActivationKind.Launch && IsCtrlKeyDown();
|
|
|
|
|
|
|
|
|
|
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(SingleInstanceKey);
|
2025-11-14 12:51:19 +01:00
|
|
|
|
|
|
|
|
if (keyInstance.IsCurrent)
|
|
|
|
|
{
|
2026-02-27 11:00:25 +01:00
|
|
|
EnsureAlternateModeOverrideSignalHandle();
|
2025-11-14 12:51:19 +01:00
|
|
|
keyInstance.Activated += OnActivated;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
isRedirect = true;
|
2026-02-27 11:00:25 +01:00
|
|
|
|
|
|
|
|
if (_forceAlternateModeOnLaunch)
|
|
|
|
|
{
|
|
|
|
|
SignalForceAlternateMode();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 12:51:19 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-02-27 11:00:25 +01:00
|
|
|
[DllImport("user32.dll")]
|
|
|
|
|
private static extern short GetAsyncKeyState(int vKey);
|
|
|
|
|
|
2025-11-14 12:51:19 +01:00
|
|
|
private static IntPtr redirectEventHandle = IntPtr.Zero;
|
|
|
|
|
|
2026-02-27 11:00:25 +01:00
|
|
|
public static bool TryConsumeCurrentProcessAlternateModeOverride()
|
|
|
|
|
{
|
|
|
|
|
if (!_forceAlternateModeOnLaunch)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
_forceAlternateModeOnLaunch = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool TryConsumeRedirectedAlternateModeOverride()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (_forceAlternateModeSignalHandle != null)
|
|
|
|
|
{
|
|
|
|
|
return _forceAlternateModeSignalHandle.WaitOne(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EventWaitHandle.TryOpenExisting(ForceAlternateModeSignalEventName, out EventWaitHandle? signal))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
using (signal)
|
|
|
|
|
{
|
|
|
|
|
return signal.WaitOne(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool IsCtrlKeyDown() => (GetAsyncKeyState(VkControl) & 0x8000) != 0;
|
|
|
|
|
|
|
|
|
|
private static void EnsureAlternateModeOverrideSignalHandle()
|
|
|
|
|
{
|
|
|
|
|
if (_forceAlternateModeSignalHandle != null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_forceAlternateModeSignalHandle = new EventWaitHandle(false, EventResetMode.AutoReset, ForceAlternateModeSignalEventName);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
_forceAlternateModeSignalHandle = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SignalForceAlternateMode()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (EventWaitHandle.TryOpenExisting(ForceAlternateModeSignalEventName, out EventWaitHandle? signal))
|
|
|
|
|
{
|
|
|
|
|
using (signal)
|
|
|
|
|
{
|
|
|
|
|
signal.Set();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using EventWaitHandle fallbackSignal = new(false, EventResetMode.AutoReset, ForceAlternateModeSignalEventName);
|
|
|
|
|
fallbackSignal.Set();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Ignore signaling failures and continue with normal activation redirection.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 12:51:19 +01:00
|
|
|
// 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);
|
|
|
|
|
|
2026-04-05 02:19:11 +02:00
|
|
|
if (ShouldBringWindowToForegroundAfterRedirection(args))
|
|
|
|
|
{
|
|
|
|
|
Process process = Process.GetProcessById((int)keyInstance.ProcessId);
|
|
|
|
|
SetForegroundWindow(process.MainWindowHandle);
|
|
|
|
|
}
|
2025-11-14 12:51:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-05 02:19:11 +02:00
|
|
|
|
|
|
|
|
private static bool ShouldBringWindowToForegroundAfterRedirection(AppActivationArguments args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Kind != ExtendedActivationKind.AppNotification ||
|
|
|
|
|
args.Data is not AppNotificationActivatedEventArgs toastArgs)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var toastArguments = ToastArguments.Parse(toastArgs.Argument);
|
|
|
|
|
|
|
|
|
|
if (toastArguments.TryGetValue(Constants.ToastStoreUpdateActionKey, out string storeUpdateAction) &&
|
|
|
|
|
storeUpdateAction == Constants.ToastStoreUpdateActionInstall)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (toastArguments.TryGetValue(Constants.ToastCalendarActionKey, out string calendarAction))
|
|
|
|
|
{
|
|
|
|
|
return calendarAction == Constants.ToastCalendarNavigateAction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (toastArguments.TryGetValue(Constants.ToastActionKey, out MailOperation mailAction))
|
|
|
|
|
{
|
|
|
|
|
return mailAction == MailOperation.Navigate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-11-14 12:51:19 +01:00
|
|
|
}
|