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]));
|
||||
|
||||
std::array<std::wstring_view, 45> processesToTerminate = {
|
||||
std::array<std::wstring_view, 46> processesToTerminate = {
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.Settings.exe",
|
||||
L"PowerToys.AdvancedPaste.exe",
|
||||
@@ -1623,6 +1623,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||
L"Microsoft.CmdPal.UI.exe",
|
||||
L"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
L"PowerToys.ShortcutGuide.exe",
|
||||
L"PowerToys.ZoomIt.exe",
|
||||
L"PowerToys.exe",
|
||||
};
|
||||
|
||||
@@ -163,6 +163,12 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::POWERACCENT_EXIT_EVENT;
|
||||
}
|
||||
|
||||
hstring Constants::ShortcutGuideExitEvent()
|
||||
{
|
||||
return CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT;
|
||||
}
|
||||
|
||||
hstring Constants::ShortcutGuideTriggerEvent()
|
||||
{
|
||||
return CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT;
|
||||
@@ -317,4 +323,3 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
return CommonSharedConstants::KEYBOARD_MANAGER_ENGINE_INSTANCE_MUTEX;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring ShowPeekEvent();
|
||||
static hstring TerminatePeekEvent();
|
||||
static hstring PowerAccentExitEvent();
|
||||
static hstring ShortcutGuideExitEvent();
|
||||
static hstring ShortcutGuideTriggerEvent();
|
||||
static hstring ShortcutGuideWinKeyHoldEvent();
|
||||
static hstring RegistryPreviewTriggerEvent();
|
||||
@@ -91,4 +92,3 @@ namespace winrt::PowerToys::Interop::factory_implementation
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace PowerToys
|
||||
static String ShowPeekEvent();
|
||||
static String TerminatePeekEvent();
|
||||
static String PowerAccentExitEvent();
|
||||
static String ShortcutGuideExitEvent();
|
||||
static String ShortcutGuideTriggerEvent();
|
||||
static String ShortcutGuideWinKeyHoldEvent();
|
||||
static String RegistryPreviewTriggerEvent();
|
||||
@@ -82,4 +83,3 @@ namespace PowerToys
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
@@ -19,10 +20,14 @@ namespace ShortcutGuide
|
||||
{
|
||||
public sealed class Program
|
||||
{
|
||||
private static readonly ManualResetEvent _runnerExitEvent = new(false);
|
||||
|
||||
public static Thread CopyAndIndexGenerationThread { get; private set; } = null!;
|
||||
|
||||
public static nint ForegroundWindowHandle { get; set; } = nint.Zero;
|
||||
|
||||
internal static WaitHandle RunnerExitEvent => _runnerExitEvent;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
@@ -38,21 +43,17 @@ namespace ShortcutGuide
|
||||
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)
|
||||
{
|
||||
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length >= 1 && int.TryParse(args[0], out int runnerPID))
|
||||
{
|
||||
MonitorPowerToysRunner(runnerPID);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(ManifestInterpreter.PathOfManifestFiles);
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
// The WinRT/WinUI dispatcher thread doesn't terminate cleanly; force exit.
|
||||
Environment.Exit(0);
|
||||
private static void MonitorPowerToysRunner(int runnerPID)
|
||||
{
|
||||
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()
|
||||
|
||||
@@ -38,16 +38,21 @@ namespace ShortcutGuide
|
||||
/// </summary>
|
||||
internal static OverlayWindow OverlayWindow { get; private set; } = null!;
|
||||
|
||||
private HotkeySettingsControlHook _winKeyUpKeyboardHook = null!;
|
||||
private HotkeySettingsControlHook? _winKeyUpKeyboardHook;
|
||||
|
||||
internal static string CurrentAppName { get; set; } = string.Empty;
|
||||
|
||||
private readonly SemaphoreSlim _activationGate = new(1, 1);
|
||||
private readonly ManualResetEvent _listenerShutdownEvent = new(false);
|
||||
private EventWaitHandle? _regularHotkeyEvent;
|
||||
private EventWaitHandle? _winKeyHoldEvent;
|
||||
private EventWaitHandle? _exitEvent;
|
||||
private RegisteredWaitHandle? _runnerExitRegistration;
|
||||
private Thread? _listenForActivationEventsThread;
|
||||
private int _activeSource = (int)ShortcutGuideActivationSource.None;
|
||||
private int _activeSurface = (int)ShortcutGuideOverlaySurface.Hidden;
|
||||
private int _disposed;
|
||||
private int _shutdownStarted;
|
||||
|
||||
private static readonly UIntPtr _ignoreKeyEventFlag = 0x5557;
|
||||
|
||||
@@ -68,6 +73,20 @@ namespace ShortcutGuide
|
||||
{
|
||||
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();
|
||||
OverlayWindow = new OverlayWindow();
|
||||
OverlayWindow.ClosingStarted += (_, _) => ResetActivationState();
|
||||
@@ -79,16 +98,12 @@ namespace ShortcutGuide
|
||||
OverlayWindow.SessionDurationMs,
|
||||
OverlayWindow.CloseType));
|
||||
|
||||
// WinUI3's dispatcher loop does not terminate when the last
|
||||
// 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();
|
||||
Shutdown();
|
||||
};
|
||||
|
||||
_regularHotkeyEvent = TryOpenActivationEvent(Constants.ShortcutGuideTriggerEvent());
|
||||
_winKeyHoldEvent = TryOpenActivationEvent(Constants.ShortcutGuideWinKeyHoldEvent());
|
||||
_exitEvent = TryOpenActivationEvent(Constants.ShortcutGuideExitEvent());
|
||||
|
||||
_listenForActivationEventsThread = new Thread(ListenForActivationEvents)
|
||||
{
|
||||
@@ -133,7 +148,8 @@ namespace ShortcutGuide
|
||||
// Any failure in launch is fatal for this short-lived overlay; log and exit
|
||||
// cleanly rather than letting WinUI surface a generic crash dialog.
|
||||
Logger.LogError("Failed to launch Shortcut Guide.", ex);
|
||||
Environment.Exit(1);
|
||||
Environment.ExitCode = 1;
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,29 +211,42 @@ namespace ShortcutGuide
|
||||
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;
|
||||
}
|
||||
|
||||
WaitHandle[] handles = activationEvents.ConvertAll(item => item.Handle).ToArray();
|
||||
try
|
||||
int listenerShutdownEventIndex = handles.Count;
|
||||
handles.Add(_listenerShutdownEvent);
|
||||
WaitHandle[] waitHandles = handles.ToArray();
|
||||
Logger.LogInfo("Shortcut Guide activation-event listener started.");
|
||||
while (true)
|
||||
{
|
||||
Logger.LogInfo("Shortcut Guide activation-event listener started.");
|
||||
while (true)
|
||||
int eventIndex = WaitHandle.WaitAny(waitHandles);
|
||||
if (eventIndex == listenerShutdownEventIndex)
|
||||
{
|
||||
int eventIndex = WaitHandle.WaitAny(handles);
|
||||
var activationSource = activationEvents[eventIndex].Source;
|
||||
Logger.LogInfo($"Shortcut Guide trigger event signaled by {activationSource}.");
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(() => _ = HandleActivationAsync(activationSource));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
|
||||
if (eventIndex == exitEventIndex)
|
||||
{
|
||||
Logger.LogInfo("Shortcut Guide exit event signaled.");
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(Shutdown);
|
||||
return;
|
||||
}
|
||||
|
||||
var activationSource = activationEvents[eventIndex].Source;
|
||||
Logger.LogInfo($"Shortcut Guide trigger event signaled by {activationSource}.");
|
||||
OverlayWindow.DispatcherQueue.TryEnqueue(() => _ = HandleActivationAsync(activationSource));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,32 +460,53 @@ namespace ShortcutGuide
|
||||
e.SetObserved();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private void Shutdown()
|
||||
{
|
||||
_regularHotkeyEvent?.Dispose();
|
||||
_winKeyHoldEvent?.Dispose();
|
||||
|
||||
if (_listenForActivationEventsThread == null)
|
||||
if (Interlocked.Exchange(ref _shutdownStarted, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!_listenForActivationEventsThread.Join(TimeSpan.FromMilliseconds(250)))
|
||||
{
|
||||
_listenForActivationEventsThread.Interrupt();
|
||||
_listenForActivationEventsThread.Join(TimeSpan.FromMilliseconds(250));
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
}
|
||||
catch (ThreadStateException)
|
||||
Dispose();
|
||||
Current.Exit();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_listenForActivationEventsThread = null;
|
||||
_winKeyUpKeyboardHook?.Dispose();
|
||||
_runnerExitRegistration?.Unregister(null);
|
||||
|
||||
this.UnhandledException -= App_UnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
|
||||
TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException;
|
||||
|
||||
_listenerShutdownEvent.Set();
|
||||
if (_listenForActivationEventsThread != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_listenForActivationEventsThread.Join(TimeSpan.FromSeconds(1)))
|
||||
{
|
||||
Logger.LogWarning("Shortcut Guide activation-event listener did not stop within the timeout.");
|
||||
}
|
||||
}
|
||||
catch (ThreadStateException ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to join Shortcut Guide activation-event listener: {ex.Message}");
|
||||
}
|
||||
|
||||
_listenForActivationEventsThread = null;
|
||||
}
|
||||
|
||||
_regularHotkeyEvent?.Dispose();
|
||||
_winKeyHoldEvent?.Dispose();
|
||||
_exitEvent?.Dispose();
|
||||
_listenerShutdownEvent.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +111,7 @@ public:
|
||||
if (_enabled)
|
||||
{
|
||||
_enabled = false;
|
||||
if (IsProcessActive())
|
||||
{
|
||||
TerminateProcess(m_hProcess, 0);
|
||||
}
|
||||
StopProcess();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -190,7 +187,7 @@ private:
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
bool _enabled = false;
|
||||
HANDLE m_hProcess = nullptr;
|
||||
winrt::handle m_process;
|
||||
|
||||
// Hotkey to invoke the module
|
||||
HotkeyEx m_hotkey;
|
||||
@@ -227,18 +224,28 @@ private:
|
||||
|
||||
bool StartProcess(std::wstring args = L"")
|
||||
{
|
||||
if (exitEvent)
|
||||
const bool trackProcess = args.empty();
|
||||
if (trackProcess && IsProcessActive())
|
||||
{
|
||||
ResetEvent(exitEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (triggerEvent)
|
||||
if (trackProcess)
|
||||
{
|
||||
ResetEvent(triggerEvent);
|
||||
}
|
||||
if (winKeyHoldEvent)
|
||||
{
|
||||
ResetEvent(winKeyHoldEvent);
|
||||
if (exitEvent)
|
||||
{
|
||||
ResetEvent(exitEvent);
|
||||
}
|
||||
|
||||
if (triggerEvent)
|
||||
{
|
||||
ResetEvent(triggerEvent);
|
||||
}
|
||||
|
||||
if (winKeyHoldEvent)
|
||||
{
|
||||
ResetEvent(winKeyHoldEvent);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
@@ -267,25 +274,80 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger::trace(L"Started SG process with pid={}", GetProcessId(sei.hProcess));
|
||||
m_hProcess = sei.hProcess;
|
||||
winrt::handle launchedProcess{ sei.hProcess };
|
||||
Logger::trace(L"Started SG process with pid={}", GetProcessId(launchedProcess.get()));
|
||||
if (trackProcess)
|
||||
{
|
||||
m_process = std::move(launchedProcess);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsProcessActive()
|
||||
{
|
||||
if (!m_hProcess)
|
||||
if (!m_process)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto result = WaitForSingleObject(m_hProcess, 0);
|
||||
|
||||
auto result = WaitForSingleObject(m_process.get(), 0);
|
||||
if (result == WAIT_FAILED)
|
||||
{
|
||||
Logger::error("Failed to wait for SG process.");
|
||||
}
|
||||
|
||||
if (result == WAIT_OBJECT_0)
|
||||
{
|
||||
m_process = {};
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
try
|
||||
@@ -409,10 +471,7 @@ private:
|
||||
|
||||
void WindowsKeyPressBehavior()
|
||||
{
|
||||
if (IsProcessActive())
|
||||
{
|
||||
TerminateProcess(m_hProcess, 0);
|
||||
}
|
||||
StopProcess();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user