Files
PowerToys/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Program.cs

141 lines
5.1 KiB
C#
Raw Normal View History

2025-12-02 22:26:22 -06:00
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
2025-12-08 14:22:16 -06:00
using Microsoft.CommandPalette.UI.Models.Events;
2025-12-02 22:26:22 -06:00
using Microsoft.CommandPalette.UI.Services;
using Microsoft.Extensions.Logging;
2025-12-08 14:22:16 -06:00
using Microsoft.PowerToys.Telemetry;
2025-12-02 22:26:22 -06:00
using Microsoft.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
namespace Microsoft.CommandPalette.UI;
// cribbed heavily from
//
// https://github.com/microsoft/WindowsAppSDK-Samples/tree/main/Samples/AppLifecycle/Instancing/cs2/cs-winui-packaged/CsWinUiDesktopInstancing
internal sealed partial class Program
{
private static DispatcherQueueSynchronizationContext? uiContext;
private static App? app;
private static ILogger logger = new CmdPalLogger();
// LOAD BEARING
//
// Main cannot be async. If it is, then the clipboard won't work, and neither will narrator.
// That means you, the person thinking about making this a MTA thread. Don't
// do it. It won't work. That's not the solution.
[STAThread]
private static int Main(string[] args)
{
if (Helpers.GpoValueChecker.GetConfiguredCmdPalEnabledValue() == Helpers.GpoRuleConfiguredValue.Disabled)
{
// There's a GPO rule configured disabling CmdPal. Exit as soon as possible.
return 0;
}
Log_AppStart(logger, DateTime.UtcNow);
2025-12-08 14:22:16 -06:00
PowerToysTelemetry.Log.WriteEvent(new ProcessStartedEvent());
2025-12-02 22:26:22 -06:00
WinRT.ComWrappersSupport.InitializeComWrappers();
var isRedirect = DecideRedirection();
if (!isRedirect)
{
Microsoft.UI.Xaml.Application.Start((p) =>
{
uiContext = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(uiContext);
app = new App(logger);
});
}
return 0;
}
private static bool DecideRedirection()
{
var isRedirect = false;
var args = AppInstance.GetCurrent().GetActivatedEventArgs();
var keyInstance = AppInstance.FindOrRegisterForKey("randomKey");
if (keyInstance.IsCurrent)
{
2025-12-08 14:22:16 -06:00
PowerToysTelemetry.Log.WriteEvent(new ColdLaunchEvent());
2025-12-02 22:26:22 -06:00
keyInstance.Activated += OnActivated;
}
else
{
isRedirect = true;
2025-12-08 14:22:16 -06:00
PowerToysTelemetry.Log.WriteEvent(new ReactivateInstanceEvent());
2025-12-02 22:26:22 -06:00
RedirectActivationTo(args, keyInstance);
}
return isRedirect;
}
private static void RedirectActivationTo(AppActivationArguments args, AppInstance keyInstance)
{
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
using var redirectSemaphore = new Semaphore(0, 1);
var redirectTimeout = TimeSpan.FromSeconds(32);
_ = Task.Run(() =>
{
using var cts = new CancellationTokenSource(redirectTimeout);
try
{
keyInstance.RedirectActivationToAsync(args)
.AsTask(cts.Token)
.GetAwaiter()
.GetResult();
}
catch (OperationCanceledException)
{
Log_FailedToActivateTimeout(logger, redirectTimeout);
}
catch (Exception ex)
{
Log_FailedToActivate(logger, ex);
}
finally
{
redirectSemaphore.Release();
}
});
_ = PInvoke.CoWaitForMultipleObjects(
(uint)CWMO_FLAGS.CWMO_DEFAULT,
PInvoke.INFINITE,
[new HANDLE(redirectSemaphore.SafeWaitHandle.DangerousGetHandle())],
out _);
}
private static void OnActivated(object? sender, AppActivationArguments args)
{
// If we already have a form, display the message now.
// Otherwise, add it to the collection for displaying later.
if (App.Current?.AppWindow is MainWindow mainWindow)
{
// LOAD BEARING
// This must be synchronous to ensure the method does not return
// before the activation is fully handled and the parameters are processed.
// The sending instance remains blocked until this returns; afterward it may quit,
// causing the activation arguments to be lost.
mainWindow.HandleLaunchNonUI(args);
}
}
[LoggerMessage(Level = LogLevel.Debug, Message = "Starting at {startTime}")]
static partial void Log_AppStart(ILogger logger, DateTime startTime);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to activate existing instance; timed out after {redirectTimeout}.")]
static partial void Log_FailedToActivateTimeout(ILogger logger, TimeSpan redirectTimeout);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to activate existing instance")]
static partial void Log_FailedToActivate(ILogger logger, Exception ex);
}