Remove single entry and mode launch on ctrl press.

This commit is contained in:
Burak Kaan Köse
2026-02-27 11:00:25 +01:00
parent e1ce85698c
commit 5c510fd7b0
5 changed files with 159 additions and 52 deletions
@@ -7,25 +7,32 @@ internal static class AppModeActivationResolver
{ {
public static WinoApplicationMode Resolve(string? launchArguments, string? tileId, string? appId, WinoApplicationMode defaultMode = WinoApplicationMode.Mail) public static WinoApplicationMode Resolve(string? launchArguments, string? tileId, string? appId, WinoApplicationMode defaultMode = WinoApplicationMode.Mail)
{ {
if (TryResolveFromText(launchArguments, out var mode)) if (TryResolveFromText(launchArguments, defaultMode, out var mode))
return mode; return mode;
if (TryResolveFromText(tileId, out mode)) if (TryResolveFromText(tileId, defaultMode, out mode))
return mode; return mode;
if (TryResolveFromText(appId, out mode)) if (TryResolveFromText(appId, defaultMode, out mode))
return mode; return mode;
return defaultMode; return defaultMode;
} }
private static bool TryResolveFromText(string? value, out WinoApplicationMode mode) private static bool TryResolveFromText(string? value, WinoApplicationMode defaultMode, out WinoApplicationMode mode)
{ {
mode = WinoApplicationMode.Mail; mode = defaultMode;
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
return false; return false;
if (Contains(value, "--mode=toggle-default") ||
Contains(value, "mode=toggle-default"))
{
mode = GetOpposite(defaultMode);
return true;
}
if (Contains(value, "wino-calendar") || if (Contains(value, "wino-calendar") ||
Contains(value, "--mode=calendar") || Contains(value, "--mode=calendar") ||
Contains(value, "mode=calendar") || Contains(value, "mode=calendar") ||
@@ -54,4 +61,9 @@ internal static class AppModeActivationResolver
private static bool EqualsToken(string source, string token) private static bool EqualsToken(string source, string token)
=> string.Equals(source.Trim(), token, StringComparison.OrdinalIgnoreCase); => string.Equals(source.Trim(), token, StringComparison.OrdinalIgnoreCase);
private static WinoApplicationMode GetOpposite(WinoApplicationMode defaultMode)
=> defaultMode == WinoApplicationMode.Mail
? WinoApplicationMode.Calendar
: WinoApplicationMode.Mail;
} }
+25 -3
View File
@@ -38,6 +38,7 @@ public partial class App : WinoApplication,
IRecipient<NewCalendarSynchronizationRequested> IRecipient<NewCalendarSynchronizationRequested>
{ {
private const int InboxSyncsPerFullSync = 20; private const int InboxSyncsPerFullSync = 20;
private const string ToggleDefaultModeLaunchArgument = "--mode=toggle-default";
private ISynchronizationManager? _synchronizationManager; private ISynchronizationManager? _synchronizationManager;
private IPreferencesService? _preferencesService; private IPreferencesService? _preferencesService;
private IAccountService? _accountService; private IAccountService? _accountService;
@@ -136,7 +137,7 @@ public partial class App : WinoApplication,
{ {
base.OnLaunched(args); base.OnLaunched(args);
// Always register notification callbacks for both app entries (Mail and Calendar). // Always register notification callbacks.
TryRegisterAppNotifications(); TryRegisterAppNotifications();
// Initialize required services regardless of launch activation type. // Initialize required services regardless of launch activation type.
@@ -429,7 +430,14 @@ public partial class App : WinoApplication,
if (activationArgs.Kind == ExtendedActivationKind.Launch && if (activationArgs.Kind == ExtendedActivationKind.Launch &&
activationArgs.Data is ILaunchActivatedEventArgs launchArgs) activationArgs.Data is ILaunchActivatedEventArgs launchArgs)
{ {
shellWindow.HandleAppActivation(launchArgs.Arguments, launchArgs.TileId, Environment.CommandLine); var launchArguments = launchArgs.Arguments;
if (Program.TryConsumeCurrentProcessAlternateModeOverride())
{
launchArguments = AppendLaunchArgument(launchArguments, ToggleDefaultModeLaunchArgument);
}
shellWindow.HandleAppActivation(launchArguments, launchArgs.TileId, Environment.CommandLine);
return; return;
} }
@@ -662,7 +670,14 @@ public partial class App : WinoApplication,
if (args.Kind == ExtendedActivationKind.Launch && if (args.Kind == ExtendedActivationKind.Launch &&
args.Data is ILaunchActivatedEventArgs launchArgs) args.Data is ILaunchActivatedEventArgs launchArgs)
{ {
shellWindow.HandleAppActivation(launchArgs.Arguments, launchArgs.TileId); var launchArguments = launchArgs.Arguments;
if (Program.TryConsumeRedirectedAlternateModeOverride())
{
launchArguments = AppendLaunchArgument(launchArguments, ToggleDefaultModeLaunchArgument);
}
shellWindow.HandleAppActivation(launchArguments, launchArgs.TileId);
} }
else if (TryResolveActivationMode(args, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var redirectedMode)) else if (TryResolveActivationMode(args, _preferencesService?.DefaultApplicationMode ?? WinoApplicationMode.Mail, out var redirectedMode))
{ {
@@ -680,6 +695,13 @@ public partial class App : WinoApplication,
private static string GetModeLaunchArgument(WinoApplicationMode mode) private static string GetModeLaunchArgument(WinoApplicationMode mode)
=> mode == WinoApplicationMode.Calendar ? "--mode=calendar" : "--mode=mail"; => mode == WinoApplicationMode.Calendar ? "--mode=calendar" : "--mode=mail";
private static string AppendLaunchArgument(string? launchArguments, string launchArgument)
{
return string.IsNullOrWhiteSpace(launchArguments)
? launchArgument
: $"{launchArguments} {launchArgument}";
}
private static bool TryResolveActivationMode(AppActivationArguments activationArgs, WinoApplicationMode defaultMode, out WinoApplicationMode mode) private static bool TryResolveActivationMode(AppActivationArguments activationArgs, WinoApplicationMode defaultMode, out WinoApplicationMode mode)
{ {
mode = defaultMode; mode = defaultMode;
+23 -40
View File
@@ -90,6 +90,19 @@
</uap:Protocol> </uap:Protocol>
</uap:Extension> </uap:Extension>
<!-- Protocol activation: webcal -->
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="webcal">
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="webcals">
<uap:DisplayName>Calendar Protocol (Secure)</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
<!-- File Assosication: EML --> <!-- File Assosication: EML -->
<uap:Extension Category="windows.fileTypeAssociation"> <uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="eml"> <uap:FileTypeAssociation Name="eml">
@@ -99,48 +112,18 @@
</uap:SupportedFileTypes> </uap:SupportedFileTypes>
</uap:FileTypeAssociation> </uap:FileTypeAssociation>
</uap:Extension> </uap:Extension>
<!-- File Association: ICS -->
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="ics">
<uap:Logo>Assets\AppEntries\CalendarAssets\Square44x44Logo.png</uap:Logo>
<uap:SupportedFileTypes>
<uap:FileType>.ics</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions> </Extensions>
</Application> </Application>
<Application Id="CalendarApp"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$"
uap10:Parameters="--mode=calendar">
<uap:VisualElements
DisplayName="Wino Calendar"
Description="Wino.Mail.WinUI.Calendar"
BackgroundColor="transparent"
Square150x150Logo="Assets\AppEntries\CalendarAssets\Square150x150Logo.png"
Square44x44Logo="Assets\AppEntries\CalendarAssets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\AppEntries\CalendarAssets\Wide310x150Logo.png" Square71x71Logo="Assets\AppEntries\CalendarAssets\SmallTile.png" Square310x310Logo="Assets\AppEntries\CalendarAssets\LargeTile.png"/>
<uap:SplashScreen Image="Assets\AppEntries\CalendarAssets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<!-- Protocol activation: webcal -->
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="webcal">
<uap:DisplayName>Calendar Protocol</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="webcals">
<uap:DisplayName>Calendar Protocol (Secure)</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
<!-- File Association: ICS -->
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="ics">
<uap:Logo>Assets\AppEntries\CalendarAssets\Square44x44Logo.png</uap:Logo>
<uap:SupportedFileTypes>
<uap:FileType>.ics</uap:FileType>
</uap:SupportedFileTypes>
</uap:FileTypeAssociation>
</uap:Extension>
</Extensions>
</Application>
</Applications> </Applications>
<Capabilities> <Capabilities>
+92 -1
View File
@@ -11,6 +11,13 @@ namespace Wino.Mail.WinUI;
public class Program public class Program
{ {
private const string SingleInstanceKey = "WinoMailSingleInstance";
private const string ForceAlternateModeSignalEventName = "Local\\WinoMailForceAlternateMode";
private const int VkControl = 0x11;
private static bool _forceAlternateModeOnLaunch;
private static EventWaitHandle? _forceAlternateModeSignalHandle;
[STAThread] [STAThread]
static int Main(string[] args) static int Main(string[] args)
{ {
@@ -35,15 +42,24 @@ public class Program
{ {
bool isRedirect = false; bool isRedirect = false;
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs(); AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance keyInstance = AppInstance.FindOrRegisterForKey("WinoMailSingleInstance"); _forceAlternateModeOnLaunch = args.Kind == ExtendedActivationKind.Launch && IsCtrlKeyDown();
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(SingleInstanceKey);
if (keyInstance.IsCurrent) if (keyInstance.IsCurrent)
{ {
EnsureAlternateModeOverrideSignalHandle();
keyInstance.Activated += OnActivated; keyInstance.Activated += OnActivated;
} }
else else
{ {
isRedirect = true; isRedirect = true;
if (_forceAlternateModeOnLaunch)
{
SignalForceAlternateMode();
}
RedirectActivationTo(args, keyInstance); RedirectActivationTo(args, keyInstance);
} }
@@ -66,8 +82,83 @@ public class Program
[DllImport("user32.dll")] [DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd); static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
private static IntPtr redirectEventHandle = IntPtr.Zero; private static IntPtr redirectEventHandle = IntPtr.Zero;
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.
}
}
// Do the redirection on another thread, and use a non-blocking // Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete. // wait method to wait for the redirection to complete.
public static void RedirectActivationTo(AppActivationArguments args, public static void RedirectActivationTo(AppActivationArguments args,
@@ -21,7 +21,6 @@ namespace Wino.Mail.WinUI.Services;
public class NotificationBuilder : INotificationBuilder public class NotificationBuilder : INotificationBuilder
{ {
private const string MailApplicationId = "App"; private const string MailApplicationId = "App";
private const string CalendarApplicationId = "CalendarApp";
private readonly IAccountService _accountService; private readonly IAccountService _accountService;
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
@@ -326,8 +325,8 @@ public class NotificationBuilder : INotificationBuilder
private static string GetAppUserModelId(ToastTargetApp targetApp) private static string GetAppUserModelId(ToastTargetApp targetApp)
{ {
var appId = targetApp == ToastTargetApp.Mail ? MailApplicationId : CalendarApplicationId; _ = targetApp;
return $"{Package.Current.Id.FamilyName}!{appId}"; return $"{Package.Current.Id.FamilyName}!{MailApplicationId}";
} }
private enum ToastTargetApp private enum ToastTargetApp