Fixes for PowerToys Awake (#12215)

* Change how background threads operate
This should reduce CPU usage.

* Well there is just no need for the console here

* Need to keep the full name sanitized

* Update the changes to console handling

* Updating how we handle constants

* Fix process termination logic

* Making some tweaks to the termination logic

* Update how the process is tracked

* Updating how application closing is done for Awake.

* Update with explicit types

* Fix high CPU usage for timed keep awake.

* Logging typo fix.

* Update some of the timer logic.

* Fix variable naming for consistency

* Cleanup the C++ interface

* Cleanup the code a bit
This change introduces support for:

- Proper event handling across the two apps (Awake and runner).
- Removal of unnecessary functions.

* Cleaning up the code even further

* Remove unnecessary functions
This commit is contained in:
Den Delimarsky
2021-07-13 10:39:46 -07:00
committed by GitHub
parent 5027a55e7f
commit 579881002e
8 changed files with 216 additions and 139 deletions

View File

@@ -186,5 +186,9 @@ public
static String ^ ShowColorPickerSharedEvent() { static String ^ ShowColorPickerSharedEvent() {
return gcnew String(CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT); return gcnew String(CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT);
} }
static String ^ AwakeExitEvent() {
return gcnew String(CommonSharedConstants::AWAKE_EXIT_EVENT);
}
}; };
} }

View File

@@ -28,6 +28,9 @@ namespace CommonSharedConstants
const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14"; 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. // Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100; const DWORD VK_DISABLED = 0x100;
} }

View File

@@ -21,6 +21,18 @@ namespace Awake.Core
ES_SYSTEM_REQUIRED = 0x00000001, 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);
/// <summary> /// <summary>
/// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts
/// of the codebase. /// of the codebase.
@@ -37,6 +49,10 @@ namespace Awake.Core
private static CancellationToken _threadToken; private static CancellationToken _threadToken;
private static Task? _runnerThread; 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)] [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
@@ -63,10 +79,16 @@ namespace Awake.Core
static APIHelper() static APIHelper()
{ {
_timedLoopTimer = new System.Timers.Timer();
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
} }
public static void SetConsoleControlHandler(ConsoleEventHandler handler, bool addHandler)
{
SetConsoleCtrlHandler(handler, addHandler);
}
public static void AllocateConsole() public static void AllocateConsole()
{ {
_log.Debug("Bootstrapping the console allocation routine."); _log.Debug("Bootstrapping the console allocation routine.");
@@ -139,7 +161,7 @@ namespace Awake.Core
} }
catch (OperationCanceledException) 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) 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(); _tokenSource = new CancellationTokenSource();
@@ -223,14 +245,25 @@ namespace Awake.Core
if (success) if (success)
{ {
_log.Info($"Initiated temporary keep awake in background thread: {GetCurrentThreadId()}. Screen on: {keepDisplayOn}"); _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) _tokenSource.Cancel();
{
_threadToken.ThrowIfCancellationRequested(); _timedLoopTimer.Stop();
} };
}
_timedLoopTimer.Disposed += (s, e) =>
{
_log.Info("Old timer disposed.");
};
_timedLoopTimer.Start();
WaitHandle.WaitAny(new[] { _threadToken.WaitHandle });
_timedLoopTimer.Stop();
_timedLoopTimer.Dispose();
return success; return success;
} }

View File

@@ -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;
}
}

View File

@@ -6,8 +6,10 @@ using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using NLog;
#pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
@@ -16,32 +18,43 @@ namespace Awake.Core
{ {
internal static class TrayHelper 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() static TrayHelper()
{ {
_log = LogManager.GetCurrentClassLogger();
TrayIcon = new NotifyIcon(); TrayIcon = new NotifyIcon();
ModuleSettings = new SettingsUtils(); ModuleSettings = new SettingsUtils();
} }
public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null) public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null)
{ {
System.Threading.Tasks.Task.Factory.StartNew( Task.Factory.StartNew(
(tray) => (tray) =>
{ {
((NotifyIcon?)tray).Text = text; ((NotifyIcon?)tray).Text = text;
((NotifyIcon?)tray).Icon = icon; ((NotifyIcon?)tray).Icon = icon;
((NotifyIcon?)tray).ContextMenuStrip = contextMenu; ((NotifyIcon?)tray).ContextMenuStrip = contextMenu;
((NotifyIcon?)tray).Visible = true; ((NotifyIcon?)tray).Visible = true;
Application.Run(); _log.Info("Setting up the tray.");
}, TrayIcon); 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) internal static void SetTray(string text, AwakeSettings settings)
@@ -50,10 +63,10 @@ namespace Awake.Core
text, text,
settings.Properties.KeepDisplayOn, settings.Properties.KeepDisplayOn,
settings.Properties.Mode, settings.Properties.Mode,
PassiveKeepAwakeCallback(text), PassiveKeepAwakeCallback(InternalConstants.AppName),
IndefiniteKeepAwakeCallback(text), IndefiniteKeepAwakeCallback(InternalConstants.AppName),
TimedKeepAwakeCallback(text), TimedKeepAwakeCallback(InternalConstants.AppName),
KeepDisplayOnCallback(text), KeepDisplayOnCallback(InternalConstants.AppName),
ExitCallback()); ExitCallback());
} }
@@ -61,7 +74,7 @@ namespace Awake.Core
{ {
return () => 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<uint, uint> timedKeepAwakeCallback, Action keepDisplayOnCallback, Action exitCallback) public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Action passiveKeepAwakeCallback, Action indefiniteKeepAwakeCallback, Action<uint, uint> timedKeepAwakeCallback, Action keepDisplayOnCallback, Action exitCallback)
{ {
var contextMenuStrip = new ContextMenuStrip(); ContextMenuStrip? contextMenuStrip = new ContextMenuStrip();
// Main toolstrip. // Main toolstrip.
var operationContextMenu = new ToolStripMenuItem ToolStripMenuItem? operationContextMenu = new ToolStripMenuItem
{ {
Text = "Mode", Text = "Mode",
}; };
// No keep-awake menu item. // No keep-awake menu item.
var passiveMenuItem = new ToolStripMenuItem ToolStripMenuItem? passiveMenuItem = new ToolStripMenuItem
{ {
Text = "Off (Passive)", Text = "Off (Passive)",
}; };
if (mode == AwakeMode.PASSIVE) passiveMenuItem.Checked = mode == AwakeMode.PASSIVE;
{
passiveMenuItem.Checked = true;
}
else
{
passiveMenuItem.Checked = false;
}
passiveMenuItem.Click += (e, s) => passiveMenuItem.Click += (e, s) =>
{ {
@@ -183,19 +189,12 @@ namespace Awake.Core
}; };
// Indefinite keep-awake menu item. // Indefinite keep-awake menu item.
var indefiniteMenuItem = new ToolStripMenuItem ToolStripMenuItem? indefiniteMenuItem = new ToolStripMenuItem
{ {
Text = "Keep awake indefinitely", Text = "Keep awake indefinitely",
}; };
if (mode == AwakeMode.INDEFINITE) indefiniteMenuItem.Checked = mode == AwakeMode.INDEFINITE;
{
indefiniteMenuItem.Checked = true;
}
else
{
indefiniteMenuItem.Checked = false;
}
indefiniteMenuItem.Click += (e, s) => indefiniteMenuItem.Click += (e, s) =>
{ {
@@ -203,18 +202,12 @@ namespace Awake.Core
indefiniteKeepAwakeCallback(); indefiniteKeepAwakeCallback();
}; };
var displayOnMenuItem = new ToolStripMenuItem ToolStripMenuItem? displayOnMenuItem = new ToolStripMenuItem
{ {
Text = "Keep screen on", Text = "Keep screen on",
}; };
if (keepDisplayOn)
{ displayOnMenuItem.Checked = keepDisplayOn;
displayOnMenuItem.Checked = true;
}
else
{
displayOnMenuItem.Checked = false;
}
displayOnMenuItem.Click += (e, s) => displayOnMenuItem.Click += (e, s) =>
{ {
@@ -223,43 +216,40 @@ namespace Awake.Core
}; };
// Timed keep-awake menu item // Timed keep-awake menu item
var timedMenuItem = new ToolStripMenuItem ToolStripMenuItem? timedMenuItem = new ToolStripMenuItem
{ {
Text = "Keep awake temporarily", 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", Text = "30 minutes",
}; };
halfHourMenuItem.Click += (e, s) => halfHourMenuItem.Click += (e, s) =>
{ {
// User is setting the keep-awake to 30 minutes. // User is setting the keep-awake to 30 minutes.
timedKeepAwakeCallback(0, 30); timedKeepAwakeCallback(0, 30);
}; };
var oneHourMenuItem = new ToolStripMenuItem ToolStripMenuItem? oneHourMenuItem = new ToolStripMenuItem
{ {
Text = "1 hour", Text = "1 hour",
}; };
oneHourMenuItem.Click += (e, s) => oneHourMenuItem.Click += (e, s) =>
{ {
// User is setting the keep-awake to 1 hour. // User is setting the keep-awake to 1 hour.
timedKeepAwakeCallback(1, 0); timedKeepAwakeCallback(1, 0);
}; };
var twoHoursMenuItem = new ToolStripMenuItem ToolStripMenuItem? twoHoursMenuItem = new ToolStripMenuItem
{ {
Text = "2 hours", Text = "2 hours",
}; };
twoHoursMenuItem.Click += (e, s) => twoHoursMenuItem.Click += (e, s) =>
{ {
// User is setting the keep-awake to 2 hours. // User is setting the keep-awake to 2 hours.
@@ -267,10 +257,11 @@ namespace Awake.Core
}; };
// Exit menu item. // Exit menu item.
var exitContextMenu = new ToolStripMenuItem ToolStripMenuItem? exitContextMenu = new ToolStripMenuItem
{ {
Text = "Exit", Text = "Exit",
}; };
exitContextMenu.Click += (e, s) => exitContextMenu.Click += (e, s) =>
{ {
// User is setting the keep-awake to 2 hours. // User is setting the keep-awake to 2 hours.

View File

@@ -15,6 +15,7 @@ using System.Reflection;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using Awake.Core; using Awake.Core;
using interop;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using NLog; using NLog;
@@ -27,8 +28,6 @@ namespace Awake
internal class Program internal class Program
{ {
private static Mutex? _mutex = null; private static Mutex? _mutex = null;
private const string AppName = "Awake";
private const string FullAppName = "PowerToys " + AppName;
private static FileSystemWatcher? _watcher = null; private static FileSystemWatcher? _watcher = null;
private static SettingsUtils? _settingsUtils = null; private static SettingsUtils? _settingsUtils = null;
@@ -36,17 +35,25 @@ namespace Awake
private static Logger? _log; 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) private static int Main(string[] args)
{ {
bool instantiated; // Log initialization needs to always happen before we test whether
LockMutex = new Mutex(true, AppName, out instantiated); // only one instance of Awake is running.
_log = LogManager.GetCurrentClassLogger();
LockMutex = new Mutex(true, InternalConstants.AppName, out bool instantiated);
if (!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(); _settingsUtils = new SettingsUtils();
_log.Info("Launching PowerToys Awake..."); _log.Info("Launching PowerToys Awake...");
@@ -56,7 +63,7 @@ namespace Awake
_log.Info("Parsing parameters..."); _log.Info("Parsing parameters...");
var configOption = new Option<bool>( Option<bool>? configOption = new Option<bool>(
aliases: new[] { "--use-pt-config", "-c" }, aliases: new[] { "--use-pt-config", "-c" },
getDefaultValue: () => false, getDefaultValue: () => false,
description: "Specifies whether PowerToys Awake will be using the PowerToys configuration file for managing the state.") 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; configOption.Required = false;
var displayOption = new Option<bool>( Option<bool>? displayOption = new Option<bool>(
aliases: new[] { "--display-on", "-d" }, aliases: new[] { "--display-on", "-d" },
getDefaultValue: () => true, getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.") description: "Determines whether the display should be kept awake.")
@@ -82,7 +89,7 @@ namespace Awake
displayOption.Required = false; displayOption.Required = false;
var timeOption = new Option<uint>( Option<uint>? timeOption = new Option<uint>(
aliases: new[] { "--time-limit", "-t" }, aliases: new[] { "--time-limit", "-t" },
getDefaultValue: () => 0, getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.") description: "Determines the interval, in seconds, during which the computer is kept awake.")
@@ -95,7 +102,7 @@ namespace Awake
timeOption.Required = false; timeOption.Required = false;
var pidOption = new Option<int>( Option<int>? pidOption = new Option<int>(
aliases: new[] { "--pid", "-p" }, aliases: new[] { "--pid", "-p" },
getDefaultValue: () => 0, getDefaultValue: () => 0,
description: "Bind the execution of PowerToys Awake to another process.") description: "Bind the execution of PowerToys Awake to another process.")
@@ -108,7 +115,7 @@ namespace Awake
pidOption.Required = false; pidOption.Required = false;
var rootCommand = new RootCommand RootCommand? rootCommand = new RootCommand
{ {
configOption, configOption,
displayOption, displayOption,
@@ -116,7 +123,7 @@ namespace Awake
pidOption, pidOption,
}; };
rootCommand.Description = AppName; rootCommand.Description = InternalConstants.AppName;
rootCommand.Handler = CommandHandler.Create<bool, bool, uint, int>(HandleCommandLineArguments); rootCommand.Handler = CommandHandler.Create<bool, bool, uint, int>(HandleCommandLineArguments);
@@ -125,14 +132,38 @@ namespace Awake
return rootCommand.InvokeAsync(args).Result; 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); _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) private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid)
{ {
_handler += new ConsoleEventHandler(ExitHandler);
APIHelper.SetConsoleControlHandler(_handler, true);
if (pid == 0) if (pid == 0)
{ {
_log.Info("No PID specified. Allocating console..."); _log.Info("No PID specified. Allocating console...");
@@ -150,11 +181,18 @@ namespace Awake
// and instead watch for changes in the file. // and instead watch for changes in the file.
try try
{ {
#pragma warning disable CS8604 // Possible null reference argument. new Thread(() =>
TrayHelper.InitializeTray(FullAppName, new Icon(Application.GetResourceStream(new Uri("/Images/Awake.ico", UriKind.Relative)).Stream)); {
#pragma warning restore CS8604 // Possible null reference argument. 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}"); _log.Info($"Reading configuration file: {settingsPath}");
_watcher = new FileSystemWatcher _watcher = new FileSystemWatcher
@@ -165,22 +203,22 @@ namespace Awake
Filter = Path.GetFileName(settingsPath), Filter = Path.GetFileName(settingsPath),
}; };
var changedObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? changedObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Changed += h, h => _watcher.Changed += h,
h => _watcher.Changed -= h); h => _watcher.Changed -= h);
var createdObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? createdObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
cre => _watcher.Created += cre, cre => _watcher.Created += cre,
cre => _watcher.Created -= cre); cre => _watcher.Created -= cre);
var mergedObservable = Observable.Merge(changedObservable, createdObservable); IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? mergedObservable = Observable.Merge(changedObservable, createdObservable);
mergedObservable.Throttle(TimeSpan.FromMilliseconds(25)) mergedObservable.Throttle(TimeSpan.FromMilliseconds(25))
.SubscribeOn(TaskPoolScheduler.Default) .SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs) .Select(e => e.EventArgs)
.Subscribe(HandleAwakeConfigChange); .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 // Initially the file might not be updated, so we need to start processing
// settings right away. // settings right away.
@@ -188,14 +226,14 @@ namespace Awake
} }
catch (Exception ex) 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.Info(errorString);
_log.Debug(errorString); _log.Debug(errorString);
} }
} }
else else
{ {
var mode = timeLimit <= 0 ? AwakeMode.INDEFINITE : AwakeMode.TIMED; AwakeMode mode = timeLimit <= 0 ? AwakeMode.INDEFINITE : AwakeMode.TIMED;
if (mode == AwakeMode.INDEFINITE) if (mode == AwakeMode.INDEFINITE)
{ {
@@ -207,22 +245,19 @@ namespace Awake
} }
} }
var exitSignal = new ManualResetEvent(false);
if (pid != 0) if (pid != 0)
{ {
RunnerHelper.WaitForPowerToysRunner(pid, () => RunnerHelper.WaitForPowerToysRunner(pid, () =>
{ {
exitSignal.Set(); Exit("Terminating from PowerToys binding hook.", 0, true);
Environment.Exit(0);
}); });
} }
exitSignal.WaitOne(); _exitSignal.WaitOne();
} }
private static void SetupIndefiniteKeepAwake(bool displayOn) private static void SetupIndefiniteKeepAwake(bool displayOn)
{ {
// Indefinite keep awake.
APIHelper.SetIndefiniteKeepAwake(LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn); APIHelper.SetIndefiniteKeepAwake(LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
} }
@@ -237,7 +272,7 @@ namespace Awake
{ {
try try
{ {
AwakeSettings settings = _settingsUtils.GetSettings<AwakeSettings>(AppName); AwakeSettings settings = _settingsUtils.GetSettings<AwakeSettings>(InternalConstants.AppName);
if (settings != null) if (settings != null)
{ {
@@ -251,14 +286,12 @@ namespace Awake
case AwakeMode.INDEFINITE: case AwakeMode.INDEFINITE:
{ {
// Indefinite keep awake.
SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn); SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn);
break; break;
} }
case AwakeMode.TIMED: case AwakeMode.TIMED:
{ {
// Timed keep-awake.
uint computedTime = (settings.Properties.Hours * 60 * 60) + (settings.Properties.Minutes * 60); uint computedTime = (settings.Properties.Hours * 60 * 60) + (settings.Properties.Minutes * 60);
SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn); SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
@@ -267,25 +300,25 @@ namespace Awake
default: default:
{ {
var errorMessage = "Unknown mode of operation. Check config file."; string? errorMessage = "Unknown mode of operation. Check config file.";
_log.Info(errorMessage); _log.Info(errorMessage);
_log.Debug(errorMessage); _log.Debug(errorMessage);
break; break;
} }
} }
TrayHelper.SetTray(FullAppName, settings); TrayHelper.SetTray(InternalConstants.FullAppName, settings);
} }
else else
{ {
var errorMessage = "Settings are null."; string? errorMessage = "Settings are null.";
_log.Info(errorMessage); _log.Info(errorMessage);
_log.Debug(errorMessage); _log.Debug(errorMessage);
} }
} }
catch (Exception ex) 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.Info(errorMessage);
_log.Debug(errorMessage); _log.Debug(errorMessage);
} }
@@ -307,7 +340,7 @@ namespace Awake
private static void LogUnexpectedOrCancelledKeepAwakeThreadCompletion() 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.Info(errorMessage);
_log.Debug(errorMessage); _log.Debug(errorMessage);
} }

View File

@@ -15,6 +15,7 @@
#include <common/utils/winapi_error.h> #include <common/utils/winapi_error.h>
#include <filesystem> #include <filesystem>
#include <set>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 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; return TRUE;
} }
// The PowerToy name that will be shown in the settings.
const static wchar_t* MODULE_NAME = L"Awake"; 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."; 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 class Awake : public PowertoyModuleIface
{ {
std::wstring app_name; std::wstring app_name;
// Contains the non localized key of the powertoy
std::wstring app_key; std::wstring app_key;
private: private:
// The PowerToy state.
bool m_enabled = false; bool m_enabled = false;
HANDLE m_hProcess;
HANDLE send_telemetry_event; HANDLE send_telemetry_event;
// Handle to event used to invoke PowerToys Awake
HANDLE m_hInvokeEvent; HANDLE m_hInvokeEvent;
PROCESS_INFORMATION p_info;
bool is_process_running() bool is_process_running()
{ {
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; return WaitForSingleObject(p_info.hProcess, 0) == WAIT_TIMEOUT;
} }
void launch_process() void launch_process()
@@ -69,26 +59,22 @@ private:
unsigned long powertoys_pid = GetCurrentProcessId(); unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"--use-pt-config --pid " + std::to_wstring(powertoys_pid); 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); Logger::trace(L"PowerToys Awake launching with parameters: " + executable_args);
SHELLEXECUTEINFOW sei{ sizeof(sei) }; STARTUPINFO info = { sizeof(info) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"modules\\Awake\\PowerToys.Awake.exe"; if (!CreateProcess(application_path.c_str(), full_command_path.data(), NULL, NULL, true, NULL, NULL, NULL, &info, &p_info))
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (!ShellExecuteExW(&sei))
{ {
DWORD error = GetLastError(); 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); message += std::to_wstring(error);
Logger::error(message); Logger::error(message);
} }
m_hProcess = sei.hProcess;
} }
public: public:
// Constructor
Awake() Awake()
{ {
app_name = GET_RESOURCE_STRING(IDS_AWAKE_NAME); app_name = GET_RESOURCE_STRING(IDS_AWAKE_NAME);
@@ -99,37 +85,31 @@ public:
Logger::info("Launcher object is constructing"); Logger::info("Launcher object is constructing");
}; };
// Destroy the powertoy and free memory
virtual void destroy() override virtual void destroy() override
{ {
delete this; delete this;
} }
// Return the display name of the powertoy, this will be cached by the runner
virtual const wchar_t* get_name() override virtual const wchar_t* get_name() override
{ {
return MODULE_NAME; return MODULE_NAME;
} }
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{ {
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase); HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name()); PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(MODULE_DESC); settings.set_description(MODULE_DESC);
return settings.serialize_to_buffer(buffer, buffer_size); 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 virtual const wchar_t* get_key() override
{ {
return app_key.c_str(); 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 virtual void set_config(const wchar_t* config) override
{ {
try try
@@ -139,7 +119,7 @@ public:
PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
// If you don't need to do any custom processing of the settings, proceed // 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(); values.save_to_settings_file();
} }
catch (std::exception&) catch (std::exception&)
@@ -160,15 +140,36 @@ public:
{ {
if (m_enabled) if (m_enabled)
{ {
Logger::trace(L"Disabling Awake...");
ResetEvent(send_telemetry_event); ResetEvent(send_telemetry_event);
ResetEvent(m_hInvokeEvent); 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; m_enabled = false;
} }
// Returns if the powertoys is enabled
virtual bool is_enabled() override virtual bool is_enabled() override
{ {
return m_enabled; return m_enabled;

View File

@@ -58,7 +58,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
OnPropertyChanged(nameof(IsEnabled)); OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(IsTimeConfigurationEnabled)); OnPropertyChanged(nameof(IsTimeConfigurationEnabled));
var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString()); SendConfigMSG(outgoing.ToString());
NotifyPropertyChanged(); NotifyPropertyChanged();
} }
@@ -143,7 +143,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
SndAwakeSettings outsettings = new SndAwakeSettings(Settings); SndAwakeSettings outsettings = new SndAwakeSettings(Settings);
SndModuleSettings<SndAwakeSettings> ipcMessage = new SndModuleSettings<SndAwakeSettings>(outsettings); SndModuleSettings<SndAwakeSettings> ipcMessage = new SndModuleSettings<SndAwakeSettings>(outsettings);
var targetMessage = ipcMessage.ToJsonString(); string targetMessage = ipcMessage.ToJsonString();
SendConfigMSG(targetMessage); SendConfigMSG(targetMessage);
} }
} }