Files
PowerToys/src/modules/cmdpal/ext/SamplePagesExtension/Program.cs
copilot-swe-agent[bot] 4c9e007fd9 CmdPal: Add AppLifeMonitor to fix extension hang on OS shutdown
Add AppLifeMonitor class to Microsoft.CommandPalette.Extensions.Toolkit
that creates a hidden STA window on a background thread to handle
WM_QUERYENDSESSION and WM_ENDSESSION. Update extension template and
sample extensions to use it, preventing MOAPPLICATION_HANG reports.

Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/96bd57fd-4d90-4c2e-85e9-3dfd7f712546

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 09:40:01 +00:00

49 lines
2.1 KiB
C#

// 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.
using System;
using System.Threading;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Shmuelie.WinRTServer;
using Shmuelie.WinRTServer.CsWinRT;
namespace SamplePagesExtension;
public class Program
{
[MTAThread]
public static void Main(string[] args)
{
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
{
global::Shmuelie.WinRTServer.ComServer server = new();
ManualResetEvent extensionDisposedEvent = new(false);
// AppLifeMonitor creates a hidden window on a background STA thread to handle
// WM_QUERYENDSESSION and WM_ENDSESSION. Without it, extensions hang on OS
// shutdown because the MTA main thread has no message loop to receive those messages.
using AppLifeMonitor monitor = new(extensionDisposedEvent);
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
SampleExtension extensionInstance = new(extensionDisposedEvent);
server.RegisterClass<SampleExtension, IExtension>(() => extensionInstance);
server.Start();
// This will make the main thread wait until the event is signalled by the extension class.
// Since we have single instance of the extension object, we exit as soon as it is disposed.
extensionDisposedEvent.WaitOne();
server.Stop();
server.UnsafeDispose();
}
else
{
Console.WriteLine("Not being launched as a Extension... exiting.");
}
}
}