diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp index 09529e9d1c..6dd4dc151b 100644 --- a/src/common/interop/interop.cpp +++ b/src/common/interop/interop.cpp @@ -186,5 +186,9 @@ public static String ^ ShowColorPickerSharedEvent() { return gcnew String(CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT); } + + static String ^ AwakeExitEvent() { + return gcnew String(CommonSharedConstants::AWAKE_EXIT_EVENT); + } }; } diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 176530ce1d..9c882e8675 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -28,6 +28,9 @@ namespace CommonSharedConstants const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14"; + // Path to the event used by Awake + const wchar_t AWAKE_EXIT_EVENT[] = L"Local\\PowerToysAwakeExitEvent-c0d5e305-35fc-4fb5-83ec-f6070cfaf7fe"; + // Max DWORD for key code to disable keys. const DWORD VK_DISABLED = 0x100; } diff --git a/src/modules/awake/Awake/Core/APIHelper.cs b/src/modules/awake/Awake/Core/APIHelper.cs index da128f639b..5e17380751 100644 --- a/src/modules/awake/Awake/Core/APIHelper.cs +++ b/src/modules/awake/Awake/Core/APIHelper.cs @@ -21,6 +21,18 @@ namespace Awake.Core ES_SYSTEM_REQUIRED = 0x00000001, } + // See: https://docs.microsoft.com/windows/console/handlerroutine + public enum ControlType + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6, + } + + public delegate bool ConsoleEventHandler(ControlType ctrlType); + /// /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts /// of the codebase. @@ -37,6 +49,10 @@ namespace Awake.Core private static CancellationToken _threadToken; private static Task? _runnerThread; + private static System.Timers.Timer _timedLoopTimer; + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleCtrlHandler(ConsoleEventHandler handler, bool add); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); @@ -63,10 +79,16 @@ namespace Awake.Core static APIHelper() { + _timedLoopTimer = new System.Timers.Timer(); _log = LogManager.GetCurrentClassLogger(); _tokenSource = new CancellationTokenSource(); } + public static void SetConsoleControlHandler(ConsoleEventHandler handler, bool addHandler) + { + SetConsoleCtrlHandler(handler, addHandler); + } + public static void AllocateConsole() { _log.Debug("Bootstrapping the console allocation routine."); @@ -139,7 +161,7 @@ namespace Awake.Core } catch (OperationCanceledException) { - _log.Info("Confirmed background thread cancellation when setting passive keep awake."); + _log.Info("Confirmed background thread cancellation when disabling explicit keep awake."); } } @@ -156,7 +178,7 @@ namespace Awake.Core } catch (OperationCanceledException) { - _log.Info("Confirmed background thread cancellation when setting indefinite keep awake."); + _log.Info("Confirmed background thread cancellation when setting timed keep awake."); } _tokenSource = new CancellationTokenSource(); @@ -223,14 +245,25 @@ namespace Awake.Core if (success) { _log.Info($"Initiated temporary keep awake in background thread: {GetCurrentThreadId()}. Screen on: {keepDisplayOn}"); - var startTime = DateTime.UtcNow; - while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(Math.Abs(seconds))) + + _timedLoopTimer = new System.Timers.Timer(seconds * 1000); + _timedLoopTimer.Elapsed += (s, e) => { - if (_threadToken.IsCancellationRequested) - { - _threadToken.ThrowIfCancellationRequested(); - } - } + _tokenSource.Cancel(); + + _timedLoopTimer.Stop(); + }; + + _timedLoopTimer.Disposed += (s, e) => + { + _log.Info("Old timer disposed."); + }; + + _timedLoopTimer.Start(); + + WaitHandle.WaitAny(new[] { _threadToken.WaitHandle }); + _timedLoopTimer.Stop(); + _timedLoopTimer.Dispose(); return success; } diff --git a/src/modules/awake/Awake/Core/InternalConstants.cs b/src/modules/awake/Awake/Core/InternalConstants.cs new file mode 100644 index 0000000000..2a74eea9de --- /dev/null +++ b/src/modules/awake/Awake/Core/InternalConstants.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Awake.Core +{ + internal static class InternalConstants + { + internal const string AppName = "Awake"; + internal const string FullAppName = "PowerToys " + AppName; + } +} diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index ec44ae49ea..96ea80bad7 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -6,8 +6,10 @@ using System; using System.Drawing; using System.IO; using System.Text.Json; +using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.PowerToys.Settings.UI.Library; +using NLog; #pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8603 // Possible null reference return. @@ -16,32 +18,43 @@ namespace Awake.Core { internal static class TrayHelper { - private static NotifyIcon? trayIcon; + private static readonly Logger _log; - private static NotifyIcon TrayIcon { get => trayIcon; set => trayIcon = value; } + private static NotifyIcon? _trayIcon; - private static SettingsUtils? moduleSettings; + private static NotifyIcon TrayIcon { get => _trayIcon; set => _trayIcon = value; } - private static SettingsUtils ModuleSettings { get => moduleSettings; set => moduleSettings = value; } + private static SettingsUtils? _moduleSettings; + + private static SettingsUtils ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; } static TrayHelper() { + _log = LogManager.GetCurrentClassLogger(); TrayIcon = new NotifyIcon(); ModuleSettings = new SettingsUtils(); } public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null) { - System.Threading.Tasks.Task.Factory.StartNew( + Task.Factory.StartNew( (tray) => - { - ((NotifyIcon?)tray).Text = text; - ((NotifyIcon?)tray).Icon = icon; - ((NotifyIcon?)tray).ContextMenuStrip = contextMenu; - ((NotifyIcon?)tray).Visible = true; + { + ((NotifyIcon?)tray).Text = text; + ((NotifyIcon?)tray).Icon = icon; + ((NotifyIcon?)tray).ContextMenuStrip = contextMenu; + ((NotifyIcon?)tray).Visible = true; - Application.Run(); - }, TrayIcon); + _log.Info("Setting up the tray."); + Application.Run(); + _log.Info("Tray setup complete."); + }, TrayIcon); + } + + public static void ClearTray() + { + TrayIcon.Icon = null; + TrayIcon.Dispose(); } internal static void SetTray(string text, AwakeSettings settings) @@ -50,10 +63,10 @@ namespace Awake.Core text, settings.Properties.KeepDisplayOn, settings.Properties.Mode, - PassiveKeepAwakeCallback(text), - IndefiniteKeepAwakeCallback(text), - TimedKeepAwakeCallback(text), - KeepDisplayOnCallback(text), + PassiveKeepAwakeCallback(InternalConstants.AppName), + IndefiniteKeepAwakeCallback(InternalConstants.AppName), + TimedKeepAwakeCallback(InternalConstants.AppName), + KeepDisplayOnCallback(InternalConstants.AppName), ExitCallback()); } @@ -61,7 +74,7 @@ namespace Awake.Core { return () => { - Environment.Exit(0); + Environment.Exit(Environment.ExitCode); }; } @@ -153,28 +166,21 @@ namespace Awake.Core public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Action passiveKeepAwakeCallback, Action indefiniteKeepAwakeCallback, Action timedKeepAwakeCallback, Action keepDisplayOnCallback, Action exitCallback) { - var contextMenuStrip = new ContextMenuStrip(); + ContextMenuStrip? contextMenuStrip = new ContextMenuStrip(); // Main toolstrip. - var operationContextMenu = new ToolStripMenuItem + ToolStripMenuItem? operationContextMenu = new ToolStripMenuItem { Text = "Mode", }; // No keep-awake menu item. - var passiveMenuItem = new ToolStripMenuItem + ToolStripMenuItem? passiveMenuItem = new ToolStripMenuItem { Text = "Off (Passive)", }; - if (mode == AwakeMode.PASSIVE) - { - passiveMenuItem.Checked = true; - } - else - { - passiveMenuItem.Checked = false; - } + passiveMenuItem.Checked = mode == AwakeMode.PASSIVE; passiveMenuItem.Click += (e, s) => { @@ -183,19 +189,12 @@ namespace Awake.Core }; // Indefinite keep-awake menu item. - var indefiniteMenuItem = new ToolStripMenuItem + ToolStripMenuItem? indefiniteMenuItem = new ToolStripMenuItem { Text = "Keep awake indefinitely", }; - if (mode == AwakeMode.INDEFINITE) - { - indefiniteMenuItem.Checked = true; - } - else - { - indefiniteMenuItem.Checked = false; - } + indefiniteMenuItem.Checked = mode == AwakeMode.INDEFINITE; indefiniteMenuItem.Click += (e, s) => { @@ -203,18 +202,12 @@ namespace Awake.Core indefiniteKeepAwakeCallback(); }; - var displayOnMenuItem = new ToolStripMenuItem + ToolStripMenuItem? displayOnMenuItem = new ToolStripMenuItem { Text = "Keep screen on", }; - if (keepDisplayOn) - { - displayOnMenuItem.Checked = true; - } - else - { - displayOnMenuItem.Checked = false; - } + + displayOnMenuItem.Checked = keepDisplayOn; displayOnMenuItem.Click += (e, s) => { @@ -223,43 +216,40 @@ namespace Awake.Core }; // Timed keep-awake menu item - var timedMenuItem = new ToolStripMenuItem + ToolStripMenuItem? timedMenuItem = new ToolStripMenuItem { Text = "Keep awake temporarily", }; - if (mode == AwakeMode.TIMED) - { - timedMenuItem.Checked = true; - } - else - { - timedMenuItem.Checked = false; - } - var halfHourMenuItem = new ToolStripMenuItem + timedMenuItem.Checked = mode == AwakeMode.TIMED; + + ToolStripMenuItem? halfHourMenuItem = new ToolStripMenuItem { Text = "30 minutes", }; + halfHourMenuItem.Click += (e, s) => { // User is setting the keep-awake to 30 minutes. timedKeepAwakeCallback(0, 30); }; - var oneHourMenuItem = new ToolStripMenuItem + ToolStripMenuItem? oneHourMenuItem = new ToolStripMenuItem { Text = "1 hour", }; + oneHourMenuItem.Click += (e, s) => { // User is setting the keep-awake to 1 hour. timedKeepAwakeCallback(1, 0); }; - var twoHoursMenuItem = new ToolStripMenuItem + ToolStripMenuItem? twoHoursMenuItem = new ToolStripMenuItem { Text = "2 hours", }; + twoHoursMenuItem.Click += (e, s) => { // User is setting the keep-awake to 2 hours. @@ -267,10 +257,11 @@ namespace Awake.Core }; // Exit menu item. - var exitContextMenu = new ToolStripMenuItem + ToolStripMenuItem? exitContextMenu = new ToolStripMenuItem { Text = "Exit", }; + exitContextMenu.Click += (e, s) => { // User is setting the keep-awake to 2 hours. diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index cab9b78451..d69794b491 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -15,6 +15,7 @@ using System.Reflection; using System.Threading; using System.Windows; using Awake.Core; +using interop; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using NLog; @@ -27,8 +28,6 @@ namespace Awake internal class Program { private static Mutex? _mutex = null; - private const string AppName = "Awake"; - private const string FullAppName = "PowerToys " + AppName; private static FileSystemWatcher? _watcher = null; private static SettingsUtils? _settingsUtils = null; @@ -36,17 +35,25 @@ namespace Awake private static Logger? _log; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + private static ConsoleEventHandler _handler; +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + + private static ManualResetEvent _exitSignal = new ManualResetEvent(false); + private static int Main(string[] args) { - bool instantiated; - LockMutex = new Mutex(true, AppName, out instantiated); + // Log initialization needs to always happen before we test whether + // only one instance of Awake is running. + _log = LogManager.GetCurrentClassLogger(); + + LockMutex = new Mutex(true, InternalConstants.AppName, out bool instantiated); if (!instantiated) { - ForceExit(AppName + " is already running! Exiting the application.", 1); + Exit(InternalConstants.AppName + " is already running! Exiting the application.", 1, true); } - _log = LogManager.GetCurrentClassLogger(); _settingsUtils = new SettingsUtils(); _log.Info("Launching PowerToys Awake..."); @@ -56,7 +63,7 @@ namespace Awake _log.Info("Parsing parameters..."); - var configOption = new Option( + Option? configOption = new Option( aliases: new[] { "--use-pt-config", "-c" }, getDefaultValue: () => false, description: "Specifies whether PowerToys Awake will be using the PowerToys configuration file for managing the state.") @@ -69,7 +76,7 @@ namespace Awake configOption.Required = false; - var displayOption = new Option( + Option? displayOption = new Option( aliases: new[] { "--display-on", "-d" }, getDefaultValue: () => true, description: "Determines whether the display should be kept awake.") @@ -82,7 +89,7 @@ namespace Awake displayOption.Required = false; - var timeOption = new Option( + Option? timeOption = new Option( aliases: new[] { "--time-limit", "-t" }, getDefaultValue: () => 0, description: "Determines the interval, in seconds, during which the computer is kept awake.") @@ -95,7 +102,7 @@ namespace Awake timeOption.Required = false; - var pidOption = new Option( + Option? pidOption = new Option( aliases: new[] { "--pid", "-p" }, getDefaultValue: () => 0, description: "Bind the execution of PowerToys Awake to another process.") @@ -108,7 +115,7 @@ namespace Awake pidOption.Required = false; - var rootCommand = new RootCommand + RootCommand? rootCommand = new RootCommand { configOption, displayOption, @@ -116,7 +123,7 @@ namespace Awake pidOption, }; - rootCommand.Description = AppName; + rootCommand.Description = InternalConstants.AppName; rootCommand.Handler = CommandHandler.Create(HandleCommandLineArguments); @@ -125,14 +132,38 @@ namespace Awake return rootCommand.InvokeAsync(args).Result; } - private static void ForceExit(string message, int exitCode) + private static bool ExitHandler(ControlType ctrlType) + { + _log.Info($"Exited through handler with control type: {ctrlType}"); + + Exit("Exiting from the internal termination handler.", Environment.ExitCode); + + return false; + } + + private static void Exit(string message, int exitCode, bool force = false) { _log.Info(message); - Environment.Exit(exitCode); + + APIHelper.SetNoKeepAwake(); + TrayHelper.ClearTray(); + + // Because we are running a message loop for the tray, we can't just use Environment.Exit, + // but have to make sure that we properly send the termination message. + bool cwResult = System.Diagnostics.Process.GetCurrentProcess().CloseMainWindow(); + _log.Info($"Request to close main window status: {cwResult}"); + + if (force) + { + Environment.Exit(exitCode); + } } private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid) { + _handler += new ConsoleEventHandler(ExitHandler); + APIHelper.SetConsoleControlHandler(_handler, true); + if (pid == 0) { _log.Info("No PID specified. Allocating console..."); @@ -150,11 +181,18 @@ namespace Awake // and instead watch for changes in the file. try { -#pragma warning disable CS8604 // Possible null reference argument. - TrayHelper.InitializeTray(FullAppName, new Icon(Application.GetResourceStream(new Uri("/Images/Awake.ico", UriKind.Relative)).Stream)); -#pragma warning restore CS8604 // Possible null reference argument. + new Thread(() => + { + EventWaitHandle? eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AwakeExitEvent()); + if (eventHandle.WaitOne()) + { + Exit("Received a signal to end the process. Making sure we quit...", 0, true); + } + }).Start(); - var settingsPath = _settingsUtils.GetSettingsFilePath(AppName); + TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon(Application.GetResourceStream(new Uri("/Images/Awake.ico", UriKind.Relative)).Stream)); + + string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName); _log.Info($"Reading configuration file: {settingsPath}"); _watcher = new FileSystemWatcher @@ -165,22 +203,22 @@ namespace Awake Filter = Path.GetFileName(settingsPath), }; - var changedObservable = Observable.FromEventPattern( + IObservable>? changedObservable = Observable.FromEventPattern( h => _watcher.Changed += h, h => _watcher.Changed -= h); - var createdObservable = Observable.FromEventPattern( + IObservable>? createdObservable = Observable.FromEventPattern( cre => _watcher.Created += cre, cre => _watcher.Created -= cre); - var mergedObservable = Observable.Merge(changedObservable, createdObservable); + IObservable>? mergedObservable = Observable.Merge(changedObservable, createdObservable); mergedObservable.Throttle(TimeSpan.FromMilliseconds(25)) .SubscribeOn(TaskPoolScheduler.Default) .Select(e => e.EventArgs) .Subscribe(HandleAwakeConfigChange); - TrayHelper.SetTray(FullAppName, new AwakeSettings()); + TrayHelper.SetTray(InternalConstants.FullAppName, new AwakeSettings()); // Initially the file might not be updated, so we need to start processing // settings right away. @@ -188,14 +226,14 @@ namespace Awake } catch (Exception ex) { - var errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}"; + string? errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}"; _log.Info(errorString); _log.Debug(errorString); } } else { - var mode = timeLimit <= 0 ? AwakeMode.INDEFINITE : AwakeMode.TIMED; + AwakeMode mode = timeLimit <= 0 ? AwakeMode.INDEFINITE : AwakeMode.TIMED; if (mode == AwakeMode.INDEFINITE) { @@ -207,22 +245,19 @@ namespace Awake } } - var exitSignal = new ManualResetEvent(false); if (pid != 0) { RunnerHelper.WaitForPowerToysRunner(pid, () => { - exitSignal.Set(); - Environment.Exit(0); + Exit("Terminating from PowerToys binding hook.", 0, true); }); } - exitSignal.WaitOne(); + _exitSignal.WaitOne(); } private static void SetupIndefiniteKeepAwake(bool displayOn) { - // Indefinite keep awake. APIHelper.SetIndefiniteKeepAwake(LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn); } @@ -237,7 +272,7 @@ namespace Awake { try { - AwakeSettings settings = _settingsUtils.GetSettings(AppName); + AwakeSettings settings = _settingsUtils.GetSettings(InternalConstants.AppName); if (settings != null) { @@ -251,14 +286,12 @@ namespace Awake case AwakeMode.INDEFINITE: { - // Indefinite keep awake. SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn); break; } case AwakeMode.TIMED: { - // Timed keep-awake. uint computedTime = (settings.Properties.Hours * 60 * 60) + (settings.Properties.Minutes * 60); SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn); @@ -267,25 +300,25 @@ namespace Awake default: { - var errorMessage = "Unknown mode of operation. Check config file."; + string? errorMessage = "Unknown mode of operation. Check config file."; _log.Info(errorMessage); _log.Debug(errorMessage); break; } } - TrayHelper.SetTray(FullAppName, settings); + TrayHelper.SetTray(InternalConstants.FullAppName, settings); } else { - var errorMessage = "Settings are null."; + string? errorMessage = "Settings are null."; _log.Info(errorMessage); _log.Debug(errorMessage); } } catch (Exception ex) { - var errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}"; + string? errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}"; _log.Info(errorMessage); _log.Debug(errorMessage); } @@ -307,7 +340,7 @@ namespace Awake private static void LogUnexpectedOrCancelledKeepAwakeThreadCompletion() { - var errorMessage = "The keep-awake thread was terminated early."; + string? errorMessage = "The keep-awake thread was terminated early."; _log.Info(errorMessage); _log.Debug(errorMessage); } diff --git a/src/modules/awake/AwakeModuleInterface/dllmain.cpp b/src/modules/awake/AwakeModuleInterface/dllmain.cpp index cd9e89cad7..4b5859e3b2 100644 --- a/src/modules/awake/AwakeModuleInterface/dllmain.cpp +++ b/src/modules/awake/AwakeModuleInterface/dllmain.cpp @@ -15,6 +15,7 @@ #include #include +#include BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { @@ -33,34 +34,23 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv return TRUE; } -// The PowerToy name that will be shown in the settings. const static wchar_t* MODULE_NAME = L"Awake"; - -// Add a description that will we shown in the module settings page. const static wchar_t* MODULE_DESC = L"A module that keeps your computer awake on-demand."; -// Implement the PowerToy Module Interface and all the required methods. class Awake : public PowertoyModuleIface { std::wstring app_name; - - // Contains the non localized key of the powertoy std::wstring app_key; private: - // The PowerToy state. bool m_enabled = false; - - HANDLE m_hProcess; - HANDLE send_telemetry_event; - - // Handle to event used to invoke PowerToys Awake HANDLE m_hInvokeEvent; + PROCESS_INFORMATION p_info; bool is_process_running() { - return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; + return WaitForSingleObject(p_info.hProcess, 0) == WAIT_TIMEOUT; } void launch_process() @@ -69,26 +59,22 @@ private: unsigned long powertoys_pid = GetCurrentProcessId(); std::wstring executable_args = L"--use-pt-config --pid " + std::to_wstring(powertoys_pid); + std::wstring application_path = L"modules\\Awake\\PowerToys.Awake.exe"; + std::wstring full_command_path = application_path + L" " + executable_args.data(); Logger::trace(L"PowerToys Awake launching with parameters: " + executable_args); - SHELLEXECUTEINFOW sei{ sizeof(sei) }; - sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; - sei.lpFile = L"modules\\Awake\\PowerToys.Awake.exe"; - sei.nShow = SW_SHOWNORMAL; - sei.lpParameters = executable_args.data(); - if (!ShellExecuteExW(&sei)) + STARTUPINFO info = { sizeof(info) }; + + if (!CreateProcess(application_path.c_str(), full_command_path.data(), NULL, NULL, true, NULL, NULL, NULL, &info, &p_info)) { DWORD error = GetLastError(); - std::wstring message = L"PowerToys Awake failed to start with error = "; + std::wstring message = L"PowerToys Awake failed to start with error: "; message += std::to_wstring(error); Logger::error(message); } - - m_hProcess = sei.hProcess; } public: - // Constructor Awake() { app_name = GET_RESOURCE_STRING(IDS_AWAKE_NAME); @@ -99,37 +85,31 @@ public: Logger::info("Launcher object is constructing"); }; - // Destroy the powertoy and free memory virtual void destroy() override { delete this; } - // Return the display name of the powertoy, this will be cached by the runner virtual const wchar_t* get_name() override { return MODULE_NAME; } - // Return JSON with the configuration options. virtual bool get_config(wchar_t* buffer, int* buffer_size) override { HINSTANCE hinstance = reinterpret_cast(&__ImageBase); - // Create a Settings object. PowerToysSettings::Settings settings(hinstance, get_name()); settings.set_description(MODULE_DESC); return settings.serialize_to_buffer(buffer, buffer_size); } - // Return the non localized key of the powertoy, this will be cached by the runner virtual const wchar_t* get_key() override { return app_key.c_str(); } - // Called by the runner to pass the updated settings values as a serialized JSON. virtual void set_config(const wchar_t* config) override { try @@ -139,7 +119,7 @@ public: PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); // If you don't need to do any custom processing of the settings, proceed - // to persists the values calling: + // to persists the values. values.save_to_settings_file(); } catch (std::exception&) @@ -160,15 +140,36 @@ public: { if (m_enabled) { + Logger::trace(L"Disabling Awake..."); ResetEvent(send_telemetry_event); ResetEvent(m_hInvokeEvent); - TerminateProcess(m_hProcess, 1); + + auto exitEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::AWAKE_EXIT_EVENT); + if (!exitEvent) + { + Logger::warn(L"Failed to create exit event for PowerToys Awake. {}", get_last_error_or_default(GetLastError())); + } + else + { + Logger::trace(L"Signaled exit event for PowerToys Awake."); + if (!SetEvent(exitEvent)) + { + Logger::warn(L"Failed to signal exit event for PowerToys Awake. {}", get_last_error_or_default(GetLastError())); + + // For some reason, we couldn't process the signal correctly, so we still + // need to terminate the Awake process. + TerminateProcess(p_info.hProcess, 1); + } + + ResetEvent(exitEvent); + CloseHandle(exitEvent); + CloseHandle(p_info.hProcess); + } } m_enabled = false; } - // Returns if the powertoys is enabled virtual bool is_enabled() override { return m_enabled; diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/AwakeViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/AwakeViewModel.cs index 357631b5ac..e283f1190e 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/AwakeViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/AwakeViewModel.cs @@ -58,7 +58,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels OnPropertyChanged(nameof(IsEnabled)); OnPropertyChanged(nameof(IsTimeConfigurationEnabled)); - var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); + OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); SendConfigMSG(outgoing.ToString()); NotifyPropertyChanged(); } @@ -143,7 +143,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels SndAwakeSettings outsettings = new SndAwakeSettings(Settings); SndModuleSettings ipcMessage = new SndModuleSettings(outsettings); - var targetMessage = ipcMessage.ToJsonString(); + string targetMessage = ipcMessage.ToJsonString(); SendConfigMSG(targetMessage); } }