mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
fix(shortcut-guide): prevent orphaned processes during shutdown (#50091)
## Summary of the Pull Request Prevents Shortcut Guide processes from surviving Runner shutdown and blocking PowerToys upgrades. - Shuts down WinUI through its dispatcher instead of forcing CLR termination from a worker thread. - Opens and retains the Runner process handle before WinUI initialization so early Runner exits cannot be missed. - Gives the native module deterministic ownership of the Shortcut Guide process handle, with graceful shutdown and a bounded forced-termination fallback. - Keeps telemetry subprocess handles separate from the persistent UI process. - Adds `PowerToys.ShortcutGuide.exe` to the installer termination fallback so affected existing installations can recover during upgrade. ## PR Checklist - [x] **Communication:** Discussed and requested by a core contributor after investigating the release regression. - [x] **Tests:** Existing tests pass; process lifecycle and installer file replacement were also validated. ## Detailed Description of the Pull Request / Additional comments The Shortcut Guide lifecycle introduced by #48683 could call `Environment.Exit` from a Runner-watcher worker thread while WinUI was still tearing down. The native module also overwrote its persistent child-process handle when launching telemetry and did not close completed handles. Repeated Runner lifetimes could therefore leave `PowerToys.ShortcutGuide.exe` processes retaining shared WinUI files. The installer did not recover from that state: Restart Manager is disabled, the bundle and WiX close-application steps target only `PowerToys.exe`, and `TerminateProcessesCA` did not include `PowerToys.ShortcutGuide.exe`. Locked files could consequently remain at the previous version while installation continued, producing a mixed payload. `src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs` now synchronously captures the Runner process handle and publishes its exit through a wait handle. `src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs` registers that wait with the UI dispatcher, centralizes idempotent shutdown, and disposes activation listeners and hooks deterministically. `src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp` now uses RAII for the tracked UI process, avoids replacing it with telemetry handles, signals the existing native exit event, waits for graceful shutdown, and terminates only as a bounded fallback. The event is projected to managed code through `src/common/interop/Constants.idl`. `installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp` now includes `PowerToys.ShortcutGuide.exe` in the MSI process-termination fallback, allowing upgrades from already-affected builds. ## Validation Steps Performed - Built `PowerToys.Interop.vcxproj`, `ShortcutGuideModuleInterface.vcxproj`, `ShortcutGuide.Ui.csproj`, and `PowerToysSetupCustomActionsVNext.vcxproj` for x64 Release. - Built and ran `ShortcutGuide.UnitTests`: 48/48 passed. - Repeated Runner/Shortcut Guide startup and parent-exit teardown 10 times; every child exited with code 0. - Verified the race where the Runner exits before Shortcut Guide initializes. - Verified `PowerToys.ShortcutGuide.exe`, `PowerToys.Interop.dll`, and `Microsoft.UI.Xaml.dll` were immediately replaceable after shutdown. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f6c4822-53e0-42ae-a51a-a302a7c6d3ab
This commit is contained in:
@@ -1579,7 +1579,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
|||||||
}
|
}
|
||||||
processes.resize(bytes / sizeof(processes[0]));
|
processes.resize(bytes / sizeof(processes[0]));
|
||||||
|
|
||||||
std::array<std::wstring_view, 45> processesToTerminate = {
|
std::array<std::wstring_view, 46> processesToTerminate = {
|
||||||
L"PowerToys.PowerLauncher.exe",
|
L"PowerToys.PowerLauncher.exe",
|
||||||
L"PowerToys.Settings.exe",
|
L"PowerToys.Settings.exe",
|
||||||
L"PowerToys.AdvancedPaste.exe",
|
L"PowerToys.AdvancedPaste.exe",
|
||||||
@@ -1623,6 +1623,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
|||||||
L"PowerToys.WorkspacesWindowArranger.exe",
|
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||||
L"Microsoft.CmdPal.UI.exe",
|
L"Microsoft.CmdPal.UI.exe",
|
||||||
L"Microsoft.CmdPal.Ext.PowerToys.exe",
|
L"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||||
|
L"PowerToys.ShortcutGuide.exe",
|
||||||
L"PowerToys.ZoomIt.exe",
|
L"PowerToys.ZoomIt.exe",
|
||||||
L"PowerToys.exe",
|
L"PowerToys.exe",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -163,6 +163,12 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
{
|
{
|
||||||
return CommonSharedConstants::POWERACCENT_EXIT_EVENT;
|
return CommonSharedConstants::POWERACCENT_EXIT_EVENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hstring Constants::ShortcutGuideExitEvent()
|
||||||
|
{
|
||||||
|
return CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT;
|
||||||
|
}
|
||||||
|
|
||||||
hstring Constants::ShortcutGuideTriggerEvent()
|
hstring Constants::ShortcutGuideTriggerEvent()
|
||||||
{
|
{
|
||||||
return CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT;
|
return CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT;
|
||||||
@@ -317,4 +323,3 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
return CommonSharedConstants::KEYBOARD_MANAGER_ENGINE_INSTANCE_MUTEX;
|
return CommonSharedConstants::KEYBOARD_MANAGER_ENGINE_INSTANCE_MUTEX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
static hstring ShowPeekEvent();
|
static hstring ShowPeekEvent();
|
||||||
static hstring TerminatePeekEvent();
|
static hstring TerminatePeekEvent();
|
||||||
static hstring PowerAccentExitEvent();
|
static hstring PowerAccentExitEvent();
|
||||||
|
static hstring ShortcutGuideExitEvent();
|
||||||
static hstring ShortcutGuideTriggerEvent();
|
static hstring ShortcutGuideTriggerEvent();
|
||||||
static hstring ShortcutGuideWinKeyHoldEvent();
|
static hstring ShortcutGuideWinKeyHoldEvent();
|
||||||
static hstring RegistryPreviewTriggerEvent();
|
static hstring RegistryPreviewTriggerEvent();
|
||||||
@@ -91,4 +92,3 @@ namespace winrt::PowerToys::Interop::factory_implementation
|
|||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace PowerToys
|
|||||||
static String ShowPeekEvent();
|
static String ShowPeekEvent();
|
||||||
static String TerminatePeekEvent();
|
static String TerminatePeekEvent();
|
||||||
static String PowerAccentExitEvent();
|
static String PowerAccentExitEvent();
|
||||||
|
static String ShortcutGuideExitEvent();
|
||||||
static String ShortcutGuideTriggerEvent();
|
static String ShortcutGuideTriggerEvent();
|
||||||
static String ShortcutGuideWinKeyHoldEvent();
|
static String ShortcutGuideWinKeyHoldEvent();
|
||||||
static String RegistryPreviewTriggerEvent();
|
static String RegistryPreviewTriggerEvent();
|
||||||
@@ -82,4 +83,3 @@ namespace PowerToys
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,10 +20,14 @@ namespace ShortcutGuide
|
|||||||
{
|
{
|
||||||
public sealed class Program
|
public sealed class Program
|
||||||
{
|
{
|
||||||
|
private static readonly ManualResetEvent _runnerExitEvent = new(false);
|
||||||
|
|
||||||
public static Thread CopyAndIndexGenerationThread { get; private set; } = null!;
|
public static Thread CopyAndIndexGenerationThread { get; private set; } = null!;
|
||||||
|
|
||||||
public static nint ForegroundWindowHandle { get; set; } = nint.Zero;
|
public static nint ForegroundWindowHandle { get; set; } = nint.Zero;
|
||||||
|
|
||||||
|
internal static WaitHandle RunnerExitEvent => _runnerExitEvent;
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
@@ -38,21 +43,17 @@ namespace ShortcutGuide
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Length >= 1 && int.TryParse(args[0], out int runnerPID))
|
|
||||||
{
|
|
||||||
RunnerHelper.WaitForPowerToysRunner(runnerPID, () =>
|
|
||||||
{
|
|
||||||
Logger.LogInfo($"PowerToys runner process (PID={runnerPID}) exited. Exiting ShortcutGuide.");
|
|
||||||
Environment.Exit(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShortcutGuideEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShortcutGuideEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||||
{
|
{
|
||||||
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.Length >= 1 && int.TryParse(args[0], out int runnerPID))
|
||||||
|
{
|
||||||
|
MonitorPowerToysRunner(runnerPID);
|
||||||
|
}
|
||||||
|
|
||||||
Directory.CreateDirectory(ManifestInterpreter.PathOfManifestFiles);
|
Directory.CreateDirectory(ManifestInterpreter.PathOfManifestFiles);
|
||||||
|
|
||||||
// Copy every shipped manifest from the install directory to the per-user manifest folder.
|
// Copy every shipped manifest from the install directory to the per-user manifest folder.
|
||||||
@@ -136,9 +137,48 @@ namespace ShortcutGuide
|
|||||||
{
|
{
|
||||||
Logger.LogWarning("Another instance of ShortcutGuide is running. Exiting ShortcutGuide");
|
Logger.LogWarning("Another instance of ShortcutGuide is running. Exiting ShortcutGuide");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The WinRT/WinUI dispatcher thread doesn't terminate cleanly; force exit.
|
private static void MonitorPowerToysRunner(int runnerPID)
|
||||||
Environment.Exit(0);
|
{
|
||||||
|
Process runnerProcess;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runnerProcess = Process.GetProcessById(runnerPID);
|
||||||
|
|
||||||
|
// Force the process handle to open synchronously so a Runner exit
|
||||||
|
// during WinUI initialization cannot be missed or confused with PID reuse.
|
||||||
|
_ = runnerProcess.Handle;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is ArgumentException or InvalidOperationException or Win32Exception)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"PowerToys runner process (PID={runnerPID}) is no longer available. Exiting ShortcutGuide.");
|
||||||
|
_runnerExitEvent.Set();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var runnerWatcher = new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
runnerProcess.WaitForExit();
|
||||||
|
Logger.LogInfo($"PowerToys runner process (PID={runnerPID}) exited. Exiting ShortcutGuide.");
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is InvalidOperationException or Win32Exception)
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Failed while waiting for PowerToys runner process (PID={runnerPID}): {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
runnerProcess.Dispose();
|
||||||
|
_runnerExitEvent.Set();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
IsBackground = true,
|
||||||
|
Name = "ShortcutGuide-RunnerWatcher",
|
||||||
|
};
|
||||||
|
runnerWatcher.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SendSettingsTelemetry()
|
private static void SendSettingsTelemetry()
|
||||||
|
|||||||
@@ -38,16 +38,21 @@ namespace ShortcutGuide
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static OverlayWindow OverlayWindow { get; private set; } = null!;
|
internal static OverlayWindow OverlayWindow { get; private set; } = null!;
|
||||||
|
|
||||||
private HotkeySettingsControlHook _winKeyUpKeyboardHook = null!;
|
private HotkeySettingsControlHook? _winKeyUpKeyboardHook;
|
||||||
|
|
||||||
internal static string CurrentAppName { get; set; } = string.Empty;
|
internal static string CurrentAppName { get; set; } = string.Empty;
|
||||||
|
|
||||||
private readonly SemaphoreSlim _activationGate = new(1, 1);
|
private readonly SemaphoreSlim _activationGate = new(1, 1);
|
||||||
|
private readonly ManualResetEvent _listenerShutdownEvent = new(false);
|
||||||
private EventWaitHandle? _regularHotkeyEvent;
|
private EventWaitHandle? _regularHotkeyEvent;
|
||||||
private EventWaitHandle? _winKeyHoldEvent;
|
private EventWaitHandle? _winKeyHoldEvent;
|
||||||
|
private EventWaitHandle? _exitEvent;
|
||||||
|
private RegisteredWaitHandle? _runnerExitRegistration;
|
||||||
private Thread? _listenForActivationEventsThread;
|
private Thread? _listenForActivationEventsThread;
|
||||||
private int _activeSource = (int)ShortcutGuideActivationSource.None;
|
private int _activeSource = (int)ShortcutGuideActivationSource.None;
|
||||||
private int _activeSurface = (int)ShortcutGuideOverlaySurface.Hidden;
|
private int _activeSurface = (int)ShortcutGuideOverlaySurface.Hidden;
|
||||||
|
private int _disposed;
|
||||||
|
private int _shutdownStarted;
|
||||||
|
|
||||||
private static readonly UIntPtr _ignoreKeyEventFlag = 0x5557;
|
private static readonly UIntPtr _ignoreKeyEventFlag = 0x5557;
|
||||||
|
|
||||||
@@ -68,6 +73,20 @@ namespace ShortcutGuide
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||||
|
_runnerExitRegistration = ThreadPool.RegisterWaitForSingleObject(
|
||||||
|
Program.RunnerExitEvent,
|
||||||
|
(_, _) =>
|
||||||
|
{
|
||||||
|
if (!dispatcher.TryEnqueue(Shutdown))
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Failed to enqueue Shortcut Guide shutdown after the PowerToys runner exited.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
Timeout.Infinite,
|
||||||
|
true);
|
||||||
|
|
||||||
this.LoadData();
|
this.LoadData();
|
||||||
OverlayWindow = new OverlayWindow();
|
OverlayWindow = new OverlayWindow();
|
||||||
OverlayWindow.ClosingStarted += (_, _) => ResetActivationState();
|
OverlayWindow.ClosingStarted += (_, _) => ResetActivationState();
|
||||||
@@ -79,16 +98,12 @@ namespace ShortcutGuide
|
|||||||
OverlayWindow.SessionDurationMs,
|
OverlayWindow.SessionDurationMs,
|
||||||
OverlayWindow.CloseType));
|
OverlayWindow.CloseType));
|
||||||
|
|
||||||
// WinUI3's dispatcher loop does not terminate when the last
|
Shutdown();
|
||||||
// window closes; without Exit() the SG.exe process stays
|
|
||||||
// alive, holds the AppInstance single-instance lock, and
|
|
||||||
// blocks the next launch (the well-known "every other
|
|
||||||
// long-press works" bug).
|
|
||||||
Current.Exit();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_regularHotkeyEvent = TryOpenActivationEvent(Constants.ShortcutGuideTriggerEvent());
|
_regularHotkeyEvent = TryOpenActivationEvent(Constants.ShortcutGuideTriggerEvent());
|
||||||
_winKeyHoldEvent = TryOpenActivationEvent(Constants.ShortcutGuideWinKeyHoldEvent());
|
_winKeyHoldEvent = TryOpenActivationEvent(Constants.ShortcutGuideWinKeyHoldEvent());
|
||||||
|
_exitEvent = TryOpenActivationEvent(Constants.ShortcutGuideExitEvent());
|
||||||
|
|
||||||
_listenForActivationEventsThread = new Thread(ListenForActivationEvents)
|
_listenForActivationEventsThread = new Thread(ListenForActivationEvents)
|
||||||
{
|
{
|
||||||
@@ -133,7 +148,8 @@ namespace ShortcutGuide
|
|||||||
// Any failure in launch is fatal for this short-lived overlay; log and exit
|
// Any failure in launch is fatal for this short-lived overlay; log and exit
|
||||||
// cleanly rather than letting WinUI surface a generic crash dialog.
|
// cleanly rather than letting WinUI surface a generic crash dialog.
|
||||||
Logger.LogError("Failed to launch Shortcut Guide.", ex);
|
Logger.LogError("Failed to launch Shortcut Guide.", ex);
|
||||||
Environment.Exit(1);
|
Environment.ExitCode = 1;
|
||||||
|
Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,31 +211,44 @@ namespace ShortcutGuide
|
|||||||
activationEvents.Add((_winKeyHoldEvent, ShortcutGuideActivationSource.WindowsKeyHold));
|
activationEvents.Add((_winKeyHoldEvent, ShortcutGuideActivationSource.WindowsKeyHold));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activationEvents.Count == 0)
|
List<WaitHandle> handles = activationEvents.ConvertAll(item => item.Handle);
|
||||||
|
int exitEventIndex = -1;
|
||||||
|
if (_exitEvent != null)
|
||||||
{
|
{
|
||||||
Logger.LogError("Failed to open any Shortcut Guide activation trigger events.");
|
exitEventIndex = handles.Count;
|
||||||
|
handles.Add(_exitEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handles.Count == 0)
|
||||||
|
{
|
||||||
|
Logger.LogError("Failed to open any Shortcut Guide events.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WaitHandle[] handles = activationEvents.ConvertAll(item => item.Handle).ToArray();
|
int listenerShutdownEventIndex = handles.Count;
|
||||||
try
|
handles.Add(_listenerShutdownEvent);
|
||||||
{
|
WaitHandle[] waitHandles = handles.ToArray();
|
||||||
Logger.LogInfo("Shortcut Guide activation-event listener started.");
|
Logger.LogInfo("Shortcut Guide activation-event listener started.");
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int eventIndex = WaitHandle.WaitAny(handles);
|
int eventIndex = WaitHandle.WaitAny(waitHandles);
|
||||||
|
if (eventIndex == listenerShutdownEventIndex)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventIndex == exitEventIndex)
|
||||||
|
{
|
||||||
|
Logger.LogInfo("Shortcut Guide exit event signaled.");
|
||||||
|
OverlayWindow.DispatcherQueue.TryEnqueue(Shutdown);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var activationSource = activationEvents[eventIndex].Source;
|
var activationSource = activationEvents[eventIndex].Source;
|
||||||
Logger.LogInfo($"Shortcut Guide trigger event signaled by {activationSource}.");
|
Logger.LogInfo($"Shortcut Guide trigger event signaled by {activationSource}.");
|
||||||
OverlayWindow.DispatcherQueue.TryEnqueue(() => _ = HandleActivationAsync(activationSource));
|
OverlayWindow.DispatcherQueue.TryEnqueue(() => _ = HandleActivationAsync(activationSource));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (ThreadInterruptedException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EventWaitHandle? TryOpenActivationEvent(string eventName)
|
private static EventWaitHandle? TryOpenActivationEvent(string eventName)
|
||||||
{
|
{
|
||||||
@@ -431,32 +460,53 @@ namespace ShortcutGuide
|
|||||||
e.SetObserved();
|
e.SetObserved();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
private void Shutdown()
|
||||||
{
|
{
|
||||||
_regularHotkeyEvent?.Dispose();
|
if (Interlocked.Exchange(ref _shutdownStarted, 1) != 0)
|
||||||
_winKeyHoldEvent?.Dispose();
|
|
||||||
|
|
||||||
if (_listenForActivationEventsThread == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
Current.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _disposed, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_winKeyUpKeyboardHook?.Dispose();
|
||||||
|
_runnerExitRegistration?.Unregister(null);
|
||||||
|
|
||||||
|
this.UnhandledException -= App_UnhandledException;
|
||||||
|
AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
|
||||||
|
TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException;
|
||||||
|
|
||||||
|
_listenerShutdownEvent.Set();
|
||||||
|
if (_listenForActivationEventsThread != null)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_listenForActivationEventsThread.Join(TimeSpan.FromMilliseconds(250)))
|
if (!_listenForActivationEventsThread.Join(TimeSpan.FromSeconds(1)))
|
||||||
{
|
{
|
||||||
_listenForActivationEventsThread.Interrupt();
|
Logger.LogWarning("Shortcut Guide activation-event listener did not stop within the timeout.");
|
||||||
_listenForActivationEventsThread.Join(TimeSpan.FromMilliseconds(250));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ThreadInterruptedException)
|
catch (ThreadStateException ex)
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (ThreadStateException)
|
|
||||||
{
|
{
|
||||||
|
Logger.LogWarning($"Failed to join Shortcut Guide activation-event listener: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenForActivationEventsThread = null;
|
_listenForActivationEventsThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_regularHotkeyEvent?.Dispose();
|
||||||
|
_winKeyHoldEvent?.Dispose();
|
||||||
|
_exitEvent?.Dispose();
|
||||||
|
_listenerShutdownEvent.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,10 +111,7 @@ public:
|
|||||||
if (_enabled)
|
if (_enabled)
|
||||||
{
|
{
|
||||||
_enabled = false;
|
_enabled = false;
|
||||||
if (IsProcessActive())
|
StopProcess();
|
||||||
{
|
|
||||||
TerminateProcess(m_hProcess, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -190,7 +187,7 @@ private:
|
|||||||
//contains the non localized key of the powertoy
|
//contains the non localized key of the powertoy
|
||||||
std::wstring app_key;
|
std::wstring app_key;
|
||||||
bool _enabled = false;
|
bool _enabled = false;
|
||||||
HANDLE m_hProcess = nullptr;
|
winrt::handle m_process;
|
||||||
|
|
||||||
// Hotkey to invoke the module
|
// Hotkey to invoke the module
|
||||||
HotkeyEx m_hotkey;
|
HotkeyEx m_hotkey;
|
||||||
@@ -226,6 +223,14 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool StartProcess(std::wstring args = L"")
|
bool StartProcess(std::wstring args = L"")
|
||||||
|
{
|
||||||
|
const bool trackProcess = args.empty();
|
||||||
|
if (trackProcess && IsProcessActive())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackProcess)
|
||||||
{
|
{
|
||||||
if (exitEvent)
|
if (exitEvent)
|
||||||
{
|
{
|
||||||
@@ -236,10 +241,12 @@ private:
|
|||||||
{
|
{
|
||||||
ResetEvent(triggerEvent);
|
ResetEvent(triggerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (winKeyHoldEvent)
|
if (winKeyHoldEvent)
|
||||||
{
|
{
|
||||||
ResetEvent(winKeyHoldEvent);
|
ResetEvent(winKeyHoldEvent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||||
std::wstring executable_args = L"";
|
std::wstring executable_args = L"";
|
||||||
@@ -267,25 +274,80 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::trace(L"Started SG process with pid={}", GetProcessId(sei.hProcess));
|
winrt::handle launchedProcess{ sei.hProcess };
|
||||||
m_hProcess = sei.hProcess;
|
Logger::trace(L"Started SG process with pid={}", GetProcessId(launchedProcess.get()));
|
||||||
|
if (trackProcess)
|
||||||
|
{
|
||||||
|
m_process = std::move(launchedProcess);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsProcessActive()
|
bool IsProcessActive()
|
||||||
{
|
{
|
||||||
if (!m_hProcess)
|
if (!m_process)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto result = WaitForSingleObject(m_hProcess, 0);
|
|
||||||
|
auto result = WaitForSingleObject(m_process.get(), 0);
|
||||||
if (result == WAIT_FAILED)
|
if (result == WAIT_FAILED)
|
||||||
{
|
{
|
||||||
Logger::error("Failed to wait for SG process.");
|
Logger::error("Failed to wait for SG process.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result == WAIT_OBJECT_0)
|
||||||
|
{
|
||||||
|
m_process = {};
|
||||||
|
}
|
||||||
|
|
||||||
return result == WAIT_TIMEOUT;
|
return result == WAIT_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StopProcess()
|
||||||
|
{
|
||||||
|
if (exitEvent)
|
||||||
|
{
|
||||||
|
if (!SetEvent(exitEvent))
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to signal {}. {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_process)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsProcessActive())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr DWORD gracefulShutdownTimeoutMs = 2000;
|
||||||
|
constexpr DWORD forcedShutdownTimeoutMs = 5000;
|
||||||
|
auto waitResult = WaitForSingleObject(m_process.get(), gracefulShutdownTimeoutMs);
|
||||||
|
if (waitResult == WAIT_TIMEOUT)
|
||||||
|
{
|
||||||
|
Logger::warn("Shortcut Guide did not exit gracefully; terminating it.");
|
||||||
|
if (!TerminateProcess(m_process.get(), 0))
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to terminate Shortcut Guide. {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
else if (WaitForSingleObject(m_process.get(), forcedShutdownTimeoutMs) != WAIT_OBJECT_0)
|
||||||
|
{
|
||||||
|
Logger::error("Shortcut Guide did not terminate within the timeout.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (waitResult == WAIT_FAILED)
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to wait for Shortcut Guide shutdown. {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_process = {};
|
||||||
|
}
|
||||||
|
|
||||||
void InitSettings()
|
void InitSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -409,10 +471,7 @@ private:
|
|||||||
|
|
||||||
void WindowsKeyPressBehavior()
|
void WindowsKeyPressBehavior()
|
||||||
{
|
{
|
||||||
if (IsProcessActive())
|
StopProcess();
|
||||||
{
|
|
||||||
TerminateProcess(m_hProcess, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user