mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 02:06:36 +02:00
Awake vNext - NOBLE_SIX_02162023 (#24183)
* Initial scaffolding for expiration configuration * Simplifying the code and adding support for expiration events * Bit more cleanup * Initial support for expirable keep-awake * Update some of the threading logic * Logging and timing consistency * Initial UI scaffolding * Fix pathing issue for the icon when using config file * Add missing definitions * Update with basic interface * Cleanup redundant calls * Update name per convention * Simplify declaration * Proper binding to secondary Time property * Cleanup the terminology use * Standardize naming conventions. * More Awake cleanup * Ability to update the UI when the tray icon updates * Small tweaks before ViewModel refactor * Refactor the view model logic * Some consistency fixes * Remove the build props change * Add settings scaffolding when a file does not exist * Update expect.txt * Fix typos * Update build in logs * Updating based on discussion in #24183. This specifically addresses the fact that the `ExpirationDateTime` property was incorrectly auto-initialized to `DateTime.MinValue` when it should've been set to `DateTimeOffset.MinValue` to be consistent with the underlying type and assumptions around date/time. --------- Co-authored-by: Clint Rutkas <clint@rutkas.com>
This commit is contained in:
@@ -6,6 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -82,31 +84,54 @@ namespace Awake.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetIndefiniteKeepAwake(Action<bool> callback, Action failureCallback, bool keepDisplayOn = false)
|
||||
private static bool SetAwakeStateBasedOnDisplaySetting(bool keepDisplayOn)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeIndefinitelyKeepAwakeEvent());
|
||||
if (keepDisplayOn)
|
||||
{
|
||||
return SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
else
|
||||
{
|
||||
return SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CancelExistingThread()
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
|
||||
try
|
||||
{
|
||||
_log.Info("Attempting to ensure that the thread is properly cleaned up...");
|
||||
|
||||
if (_runnerThread != null && !_runnerThread.IsCanceled)
|
||||
{
|
||||
_runnerThread.Wait(_threadToken);
|
||||
}
|
||||
|
||||
_log.Info("Thread is clean.");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_log.Info("Confirmed background thread cancellation when setting indefinite keep awake.");
|
||||
_log.Info("Confirmed background thread cancellation when disabling explicit keep awake.");
|
||||
}
|
||||
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_threadToken = _tokenSource.Token;
|
||||
|
||||
_log.Info("Instantiating of new token source and thread token completed.");
|
||||
}
|
||||
|
||||
public static void SetIndefiniteKeepAwake(Action callback, Action failureCallback, bool keepDisplayOn = false)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeIndefinitelyKeepAwakeEvent());
|
||||
|
||||
CancelExistingThread();
|
||||
|
||||
try
|
||||
{
|
||||
_runnerThread = Task.Run(() => RunIndefiniteLoop(keepDisplayOn), _threadToken)
|
||||
.ContinueWith((result) => callback(result.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
|
||||
_runnerThread = Task.Run(() => RunIndefiniteJob(keepDisplayOn), _threadToken)
|
||||
.ContinueWith((result) => callback, TaskContinuationOptions.OnlyOnRanToCompletion)
|
||||
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -117,80 +142,101 @@ namespace Awake.Core
|
||||
|
||||
public static void SetNoKeepAwake()
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeNoKeepAwakeEvent());
|
||||
|
||||
try
|
||||
{
|
||||
if (_runnerThread != null && !_runnerThread.IsCanceled)
|
||||
{
|
||||
_runnerThread.Wait(_threadToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_log.Info("Confirmed background thread cancellation when disabling explicit keep awake.");
|
||||
}
|
||||
CancelExistingThread();
|
||||
}
|
||||
|
||||
public static void SetTimedKeepAwake(uint seconds, Action<bool> callback, Action failureCallback, bool keepDisplayOn = true)
|
||||
public static void SetExpirableKeepAwake(DateTimeOffset expireAt, Action callback, Action failureCallback, bool keepDisplayOn = true)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeTimedKeepAwakeEvent());
|
||||
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeExpirableKeepAwakeEvent());
|
||||
|
||||
_tokenSource.Cancel();
|
||||
CancelExistingThread();
|
||||
|
||||
try
|
||||
if (expireAt > DateTime.Now && expireAt != null)
|
||||
{
|
||||
if (_runnerThread != null && !_runnerThread.IsCanceled)
|
||||
{
|
||||
_runnerThread.Wait(_threadToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_log.Info("Confirmed background thread cancellation when setting timed keep awake.");
|
||||
}
|
||||
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_threadToken = _tokenSource.Token;
|
||||
|
||||
_runnerThread = Task.Run(() => RunTimedLoop(seconds, keepDisplayOn), _threadToken)
|
||||
.ContinueWith((result) => callback(result.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
|
||||
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
|
||||
}
|
||||
|
||||
private static bool RunIndefiniteLoop(bool keepDisplayOn = false)
|
||||
{
|
||||
bool success;
|
||||
if (keepDisplayOn)
|
||||
{
|
||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
||||
_runnerThread = Task.Run(() => RunExpiringJob(expireAt, keepDisplayOn), _threadToken)
|
||||
.ContinueWith((result) => callback, TaskContinuationOptions.OnlyOnRanToCompletion)
|
||||
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
||||
// The target date is not in the future.
|
||||
_log.Error("The specified target date and time is not in the future.");
|
||||
_log.Error($"Current time: {DateTime.Now}\tTarget time: {expireAt}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTimedKeepAwake(uint seconds, Action callback, Action failureCallback, bool keepDisplayOn = true)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeTimedKeepAwakeEvent());
|
||||
|
||||
CancelExistingThread();
|
||||
|
||||
_runnerThread = Task.Run(() => RunTimedJob(seconds, keepDisplayOn), _threadToken)
|
||||
.ContinueWith((result) => callback, TaskContinuationOptions.OnlyOnRanToCompletion)
|
||||
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
|
||||
}
|
||||
|
||||
private static void RunExpiringJob(DateTimeOffset expireAt, bool keepDisplayOn = false)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
// In case cancellation was already requested.
|
||||
_threadToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
success = SetAwakeStateBasedOnDisplaySetting(keepDisplayOn);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_log.Info($"Initiated indefinite keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
|
||||
_log.Info($"Initiated expirable keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
|
||||
|
||||
WaitHandle.WaitAny(new[] { _threadToken.WaitHandle });
|
||||
|
||||
return success;
|
||||
Observable.Timer(expireAt, Scheduler.CurrentThread).Subscribe(
|
||||
_ =>
|
||||
{
|
||||
_log.Info($"Completed expirable thread in {PInvoke.GetCurrentThreadId()}.");
|
||||
CancelExistingThread();
|
||||
},
|
||||
_tokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("Could not successfully set up indefinite keep awake.");
|
||||
return success;
|
||||
_log.Info("Could not successfully set up expirable keep awake.");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
// Task was clearly cancelled.
|
||||
_log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunIndefiniteJob(bool keepDisplayOn = false)
|
||||
{
|
||||
// In case cancellation was already requested.
|
||||
_threadToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
bool success = SetAwakeStateBasedOnDisplaySetting(keepDisplayOn);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_log.Info($"Initiated indefinite keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
|
||||
|
||||
WaitHandle.WaitAny(new[] { _threadToken.WaitHandle });
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("Could not successfully set up indefinite keep awake.");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
// Task was clearly cancelled.
|
||||
_log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}");
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,59 +267,38 @@ namespace Awake.Core
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RunTimedLoop(uint seconds, bool keepDisplayOn = true)
|
||||
private static void RunTimedJob(uint seconds, bool keepDisplayOn = true)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
// In case cancellation was already requested.
|
||||
_threadToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
if (keepDisplayOn)
|
||||
{
|
||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
success = SetAwakeStateBasedOnDisplaySetting(keepDisplayOn);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_log.Info($"Initiated temporary keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
|
||||
_log.Info($"Initiated timed keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
|
||||
|
||||
_timedLoopTimer = new System.Timers.Timer((seconds * 1000) + 1);
|
||||
_timedLoopTimer.Elapsed += (s, e) =>
|
||||
{
|
||||
_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;
|
||||
Observable.Timer(TimeSpan.FromSeconds(seconds), Scheduler.CurrentThread).Subscribe(
|
||||
_ =>
|
||||
{
|
||||
_log.Info($"Completed timed thread in {PInvoke.GetCurrentThreadId()}.");
|
||||
CancelExistingThread();
|
||||
},
|
||||
_tokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info("Could not set up timed keep-awake with display on.");
|
||||
return success;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
// Task was clearly cancelled.
|
||||
_log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}");
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,10 +382,12 @@ namespace Awake.Core
|
||||
|
||||
public static Dictionary<string, int> GetDefaultTrayOptions()
|
||||
{
|
||||
Dictionary<string, int> optionsList = new Dictionary<string, int>();
|
||||
optionsList.Add("30 minutes", 1800);
|
||||
optionsList.Add("1 hour", 3600);
|
||||
optionsList.Add("2 hours", 7200);
|
||||
Dictionary<string, int> optionsList = new Dictionary<string, int>
|
||||
{
|
||||
{ "30 minutes", 1800 },
|
||||
{ "1 hour", 3600 },
|
||||
{ "2 hours", 7200 },
|
||||
};
|
||||
return optionsList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// 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.Models
|
||||
{
|
||||
public struct BatteryReportingScale
|
||||
{
|
||||
public uint Granularity;
|
||||
public uint Capacity;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// 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.Models
|
||||
{
|
||||
// See: https://learn.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,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ namespace Awake.Core.Models
|
||||
TC_DISPLAY_SETTING = PInvoke.WM_USER + 1,
|
||||
TC_MODE_PASSIVE = PInvoke.WM_USER + 2,
|
||||
TC_MODE_INDEFINITE = PInvoke.WM_USER + 3,
|
||||
TC_EXIT = PInvoke.WM_USER + 4,
|
||||
TC_TIME = PInvoke.WM_USER + 5,
|
||||
TC_MODE_EXPIRABLE = PInvoke.WM_USER + 4,
|
||||
TC_EXIT = PInvoke.WM_USER + 100,
|
||||
TC_TIME = PInvoke.WM_USER + 101,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
namespace Awake.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class used to manage the system tray.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Because Awake is a console application, there is no built-in
|
||||
/// way to embed UI components so we have to heavily rely on the native Windows API.
|
||||
/// </remarks>
|
||||
internal static class TrayHelper
|
||||
{
|
||||
private static readonly Logger _log;
|
||||
@@ -89,7 +96,7 @@ namespace Awake.Core
|
||||
text,
|
||||
settings.Properties.KeepDisplayOn,
|
||||
settings.Properties.Mode,
|
||||
settings.Properties.TrayTimeShortcuts,
|
||||
settings.Properties.CustomTrayTimes,
|
||||
startedFromPowerToys);
|
||||
}
|
||||
|
||||
@@ -116,19 +123,18 @@ namespace Awake.Core
|
||||
trayTimeShortcuts.AddRange(APIHelper.GetDefaultTrayOptions());
|
||||
}
|
||||
|
||||
// TODO: Make sure that this loads from JSON instead of being hard-coded.
|
||||
var awakeTimeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false);
|
||||
for (int i = 0; i < trayTimeShortcuts.Count; i++)
|
||||
{
|
||||
PInvoke.InsertMenu(awakeTimeMenu, (uint)i, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key);
|
||||
}
|
||||
|
||||
var modeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false);
|
||||
PInvoke.InsertMenu(modeMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.PASSIVE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)");
|
||||
PInvoke.InsertMenu(modeMenu, 1, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.INDEFINITE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely");
|
||||
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_SEPARATOR, 0, string.Empty);
|
||||
|
||||
PInvoke.InsertMenu(modeMenu, 2, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP | (mode == AwakeMode.TIMED ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)awakeTimeMenu.DangerousGetHandle(), "Keep awake temporarily");
|
||||
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP, (uint)modeMenu.DangerousGetHandle(), "Mode");
|
||||
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.PASSIVE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)");
|
||||
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.INDEFINITE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely");
|
||||
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP | (mode == AwakeMode.TIMED ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)awakeTimeMenu.DangerousGetHandle(), "Keep awake on interval");
|
||||
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | MENU_ITEM_FLAGS.MF_DISABLED | (mode == AwakeMode.EXPIRABLE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_EXPIRABLE, "Keep awake until expiration date and time");
|
||||
|
||||
TrayIcon.Text = text;
|
||||
}
|
||||
|
||||
@@ -56,13 +56,13 @@ namespace Awake.Core
|
||||
// Format for the timer block:
|
||||
// TrayCommands.TC_TIME + ZERO_BASED_INDEX_IN_SETTINGS
|
||||
AwakeSettings settings = ModuleSettings.GetSettings<AwakeSettings>(InternalConstants.AppName);
|
||||
if (settings.Properties.TrayTimeShortcuts.Count == 0)
|
||||
if (settings.Properties.CustomTrayTimes.Count == 0)
|
||||
{
|
||||
settings.Properties.TrayTimeShortcuts.AddRange(APIHelper.GetDefaultTrayOptions());
|
||||
settings.Properties.CustomTrayTimes.AddRange(APIHelper.GetDefaultTrayOptions());
|
||||
}
|
||||
|
||||
int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME;
|
||||
var targetTime = settings.Properties.TrayTimeShortcuts.ElementAt(index).Value;
|
||||
var targetTime = settings.Properties.CustomTrayTimes.ElementAt(index).Value;
|
||||
TimedKeepAwakeCommandHandler(InternalConstants.AppName, targetTime);
|
||||
break;
|
||||
}
|
||||
@@ -112,8 +112,8 @@ namespace Awake.Core
|
||||
}
|
||||
|
||||
currentSettings.Properties.Mode = AwakeMode.TIMED;
|
||||
currentSettings.Properties.Hours = (uint)timeSpan.Hours;
|
||||
currentSettings.Properties.Minutes = (uint)timeSpan.Minutes;
|
||||
currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours;
|
||||
currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes;
|
||||
|
||||
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<variable name="buildId" value="LIBRARIAN_03202022" />
|
||||
<variable name="buildId" value="NOBLE_SIX_02162023" />
|
||||
|
||||
<targets async="true">
|
||||
<target name="logfile"
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Awake
|
||||
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
|
||||
// is representative of the date when the last change was made before
|
||||
// the pull request is issued.
|
||||
private static readonly string BuildId = "ARBITER_01312022";
|
||||
private static readonly string BuildId = "NOBLE_SIX_02162023";
|
||||
|
||||
private static Mutex? _mutex;
|
||||
private static FileSystemWatcher? _watcher;
|
||||
@@ -65,7 +65,7 @@ namespace Awake
|
||||
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
Exit("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.", 1, _exitSignal, true);
|
||||
Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1, _exitSignal, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Awake
|
||||
|
||||
_log.Info("Parsing parameters...");
|
||||
|
||||
var configOption = new Option<bool>(
|
||||
Option<bool> configOption = new(
|
||||
aliases: new[] { "--use-pt-config", "-c" },
|
||||
getDefaultValue: () => false,
|
||||
description: $"Specifies whether {InternalConstants.AppName} will be using the PowerToys configuration file for managing the state.")
|
||||
@@ -106,11 +106,10 @@ namespace Awake
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
},
|
||||
Required = false,
|
||||
};
|
||||
|
||||
configOption.Required = false;
|
||||
|
||||
var displayOption = new Option<bool>(
|
||||
Option<bool> displayOption = new(
|
||||
aliases: new[] { "--display-on", "-d" },
|
||||
getDefaultValue: () => true,
|
||||
description: "Determines whether the display should be kept awake.")
|
||||
@@ -119,11 +118,10 @@ namespace Awake
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
},
|
||||
Required = false,
|
||||
};
|
||||
|
||||
displayOption.Required = false;
|
||||
|
||||
var timeOption = new Option<uint>(
|
||||
Option<uint> timeOption = new(
|
||||
aliases: new[] { "--time-limit", "-t" },
|
||||
getDefaultValue: () => 0,
|
||||
description: "Determines the interval, in seconds, during which the computer is kept awake.")
|
||||
@@ -132,34 +130,45 @@ namespace Awake
|
||||
{
|
||||
Arity = ArgumentArity.ExactlyOne,
|
||||
},
|
||||
Required = false,
|
||||
};
|
||||
|
||||
timeOption.Required = false;
|
||||
|
||||
var pidOption = new Option<int>(
|
||||
Option<int> pidOption = new(
|
||||
aliases: new[] { "--pid", "-p" },
|
||||
getDefaultValue: () => 0,
|
||||
description: $"Bind the execution of {InternalConstants.AppName} to another process.")
|
||||
description: $"Bind the execution of {InternalConstants.AppName} to another process. When the process ends, the system will resume managing the current sleep/display mode.")
|
||||
{
|
||||
Argument = new Argument<int>(() => 0)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
},
|
||||
Required = false,
|
||||
};
|
||||
|
||||
pidOption.Required = false;
|
||||
Option<string> expireAtOption = new(
|
||||
aliases: new[] { "--expire-at", "-e" },
|
||||
getDefaultValue: () => string.Empty,
|
||||
description: $"Determines the end date/time when {InternalConstants.AppName} will back off and let the system manage the current sleep/display mode.")
|
||||
{
|
||||
Argument = new Argument<string>(() => string.Empty)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
},
|
||||
Required = false,
|
||||
};
|
||||
|
||||
RootCommand? rootCommand = new RootCommand
|
||||
RootCommand? rootCommand = new()
|
||||
{
|
||||
configOption,
|
||||
displayOption,
|
||||
timeOption,
|
||||
pidOption,
|
||||
expireAtOption,
|
||||
};
|
||||
|
||||
rootCommand.Description = InternalConstants.AppName;
|
||||
|
||||
rootCommand.Handler = CommandHandler.Create<bool, bool, uint, int>(HandleCommandLineArguments);
|
||||
rootCommand.Handler = CommandHandler.Create<bool, bool, uint, int, string>(HandleCommandLineArguments);
|
||||
|
||||
_log.Info("Parameter setup complete. Proceeding to the rest of the app initiation...");
|
||||
|
||||
@@ -180,7 +189,7 @@ namespace Awake
|
||||
APIHelper.CompleteExit(exitCode, exitSignal, force);
|
||||
}
|
||||
|
||||
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid)
|
||||
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt)
|
||||
{
|
||||
_handler += ExitHandler;
|
||||
APIHelper.SetConsoleControlHandler(_handler, true);
|
||||
@@ -199,6 +208,7 @@ namespace Awake
|
||||
_log.Info($"The value for --display-on is: {displayOn}");
|
||||
_log.Info($"The value for --time-limit is: {timeLimit}");
|
||||
_log.Info($"The value for --pid is: {pid}");
|
||||
_log.Info($"The value for --expire is: {expireAt}");
|
||||
|
||||
if (usePtConfig)
|
||||
{
|
||||
@@ -214,41 +224,21 @@ namespace Awake
|
||||
Exit("Received a signal to end the process. Making sure we quit...", 0, _exitSignal, true);
|
||||
}
|
||||
}).Start();
|
||||
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico"), _exitSignal);
|
||||
|
||||
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "images/awake.ico")), _exitSignal);
|
||||
|
||||
string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName);
|
||||
_log.Info($"Reading configuration file: {settingsPath}");
|
||||
|
||||
_watcher = new FileSystemWatcher
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||
Path = Path.GetDirectoryName(settingsPath),
|
||||
#pragma warning restore CS8601 // Possible null reference assignment.
|
||||
EnableRaisingEvents = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
|
||||
Filter = Path.GetFileName(settingsPath),
|
||||
};
|
||||
string? errorString = $"The settings file does not exist. Scaffolding default configuration...";
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? changedObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
h => _watcher.Changed += h,
|
||||
h => _watcher.Changed -= h);
|
||||
AwakeSettings scaffoldSettings = new AwakeSettings();
|
||||
_settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), InternalConstants.AppName);
|
||||
}
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? createdObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
cre => _watcher.Created += cre,
|
||||
cre => _watcher.Created -= cre);
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? mergedObservable = Observable.Merge(changedObservable, createdObservable);
|
||||
|
||||
mergedObservable.Throttle(TimeSpan.FromMilliseconds(25))
|
||||
.SubscribeOn(TaskPoolScheduler.Default)
|
||||
.Select(e => e.EventArgs)
|
||||
.Subscribe(HandleAwakeConfigChange);
|
||||
|
||||
TrayHelper.SetTray(InternalConstants.FullAppName, new AwakeSettings(), _startedFromPowerToys);
|
||||
|
||||
// Initially the file might not be updated, so we need to start processing
|
||||
// settings right away.
|
||||
ProcessSettings();
|
||||
ScaffoldConfiguration(settingsPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -259,15 +249,43 @@ namespace Awake
|
||||
}
|
||||
else
|
||||
{
|
||||
AwakeMode mode = timeLimit <= 0 ? AwakeMode.INDEFINITE : AwakeMode.TIMED;
|
||||
|
||||
if (mode == AwakeMode.INDEFINITE)
|
||||
// Date-based binding takes precedence over timed configuration, so we want to
|
||||
// check for that first.
|
||||
if (!string.IsNullOrWhiteSpace(expireAt))
|
||||
{
|
||||
SetupIndefiniteKeepAwake(displayOn);
|
||||
try
|
||||
{
|
||||
DateTime expirationDateTime = DateTime.Parse(expireAt);
|
||||
if (expirationDateTime > DateTime.Now)
|
||||
{
|
||||
// We want to have a dedicated expirable keep-awake logic instead of
|
||||
// converting the target date to seconds and then passing to SetupTimedKeepAwake
|
||||
// because that way we're accounting for the user potentially changing their clock
|
||||
// while Awake is running.
|
||||
SetupExpirableKeepAwake(expirationDateTime, displayOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Info($"Target date is not in the future, therefore there is nothing to wait for.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Error($"Could not parse date string {expireAt} into a viable date.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupTimedKeepAwake(timeLimit, displayOn);
|
||||
AwakeMode mode = timeLimit <= 0 ? AwakeMode.INDEFINITE : AwakeMode.TIMED;
|
||||
|
||||
if (mode == AwakeMode.INDEFINITE)
|
||||
{
|
||||
SetupIndefiniteKeepAwake(displayOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupTimedKeepAwake(timeLimit, displayOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +301,45 @@ namespace Awake
|
||||
_exitSignal.WaitOne();
|
||||
}
|
||||
|
||||
private static void ScaffoldConfiguration(string settingsPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
_watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(settingsPath)!,
|
||||
EnableRaisingEvents = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
|
||||
Filter = Path.GetFileName(settingsPath),
|
||||
};
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? changedObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
h => _watcher.Changed += h,
|
||||
h => _watcher.Changed -= h);
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? createdObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
cre => _watcher.Created += cre,
|
||||
cre => _watcher.Created -= cre);
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? mergedObservable = Observable.Merge(changedObservable, createdObservable);
|
||||
|
||||
mergedObservable.Throttle(TimeSpan.FromMilliseconds(25))
|
||||
.SubscribeOn(TaskPoolScheduler.Default)
|
||||
.Select(e => e.EventArgs)
|
||||
.Subscribe(HandleAwakeConfigChange);
|
||||
|
||||
TrayHelper.SetTray(InternalConstants.FullAppName, new AwakeSettings(), _startedFromPowerToys);
|
||||
|
||||
// Initially the file might not be updated, so we need to start processing
|
||||
// settings right away.
|
||||
ProcessSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error($"An error occurred scaffolding the configuration. Error details: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupIndefiniteKeepAwake(bool displayOn)
|
||||
{
|
||||
APIHelper.SetIndefiniteKeepAwake(LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
|
||||
@@ -303,7 +360,7 @@ namespace Awake
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
_log.Info($"Identified custom time shortcuts for the tray: {settings.Properties.TrayTimeShortcuts.Count}");
|
||||
_log.Info($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
|
||||
|
||||
switch (settings.Properties.Mode)
|
||||
{
|
||||
@@ -321,12 +378,19 @@ namespace Awake
|
||||
|
||||
case AwakeMode.TIMED:
|
||||
{
|
||||
uint computedTime = (settings.Properties.Hours * 60 * 60) + (settings.Properties.Minutes * 60);
|
||||
uint computedTime = (settings.Properties.IntervalHours * 60 * 60) + (settings.Properties.IntervalMinutes * 60);
|
||||
SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AwakeMode.EXPIRABLE:
|
||||
{
|
||||
SetupExpirableKeepAwake(settings.Properties.ExpirationDateTime, settings.Properties.KeepDisplayOn);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
string? errorMessage = "Unknown mode of operation. Check config file.";
|
||||
@@ -360,6 +424,13 @@ namespace Awake
|
||||
APIHelper.SetNoKeepAwake();
|
||||
}
|
||||
|
||||
private static void SetupExpirableKeepAwake(DateTimeOffset expireAt, bool displayOn)
|
||||
{
|
||||
_log.Info($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {displayOn}.");
|
||||
|
||||
APIHelper.SetExpirableKeepAwake(expireAt, LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
|
||||
}
|
||||
|
||||
private static void SetupTimedKeepAwake(uint time, bool displayOn)
|
||||
{
|
||||
_log.Info($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
|
||||
@@ -369,14 +440,14 @@ namespace Awake
|
||||
|
||||
private static void LogUnexpectedOrCancelledKeepAwakeThreadCompletion()
|
||||
{
|
||||
string? errorMessage = "The keep-awake thread was terminated early.";
|
||||
string? errorMessage = "The keep awake thread was terminated early.";
|
||||
_log.Info(errorMessage);
|
||||
_log.Debug(errorMessage);
|
||||
}
|
||||
|
||||
private static void LogCompletedKeepAwakeThread(bool result)
|
||||
private static void LogCompletedKeepAwakeThread()
|
||||
{
|
||||
_log.Info($"Exited keep-awake thread successfully: {result}");
|
||||
_log.Info($"Exited keep awake thread successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Awake.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class AwakeExpirableKeepAwakeEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
16
src/modules/awake/Awake/Telemetry/AwakeNoKeepAwakeEvent.cs
Normal file
16
src/modules/awake/Awake/Telemetry/AwakeNoKeepAwakeEvent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Awake.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
internal class AwakeNoKeepAwakeEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user