mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 19:27:56 +01: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:
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -227,6 +227,7 @@ clientside
|
||||
CLIPCHILDREN
|
||||
Clipperton
|
||||
CLIPSIBLINGS
|
||||
Cloneable
|
||||
clrcall
|
||||
Cls
|
||||
CLSCTX
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -13,25 +14,29 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
KeepDisplayOn = false;
|
||||
Mode = AwakeMode.PASSIVE;
|
||||
Hours = 0;
|
||||
Minutes = 0;
|
||||
TrayTimeShortcuts = new Dictionary<string, int>();
|
||||
IntervalHours = 0;
|
||||
IntervalMinutes = 0;
|
||||
ExpirationDateTime = DateTimeOffset.MinValue;
|
||||
CustomTrayTimes = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
[JsonPropertyName("awake_keep_display_on")]
|
||||
[JsonPropertyName("keepDisplayOn")]
|
||||
public bool KeepDisplayOn { get; set; }
|
||||
|
||||
[JsonPropertyName("awake_mode")]
|
||||
[JsonPropertyName("mode")]
|
||||
public AwakeMode Mode { get; set; }
|
||||
|
||||
[JsonPropertyName("awake_hours")]
|
||||
public uint Hours { get; set; }
|
||||
[JsonPropertyName("intervalHours")]
|
||||
public uint IntervalHours { get; set; }
|
||||
|
||||
[JsonPropertyName("awake_minutes")]
|
||||
public uint Minutes { get; set; }
|
||||
[JsonPropertyName("intervalMinutes")]
|
||||
public uint IntervalMinutes { get; set; }
|
||||
|
||||
[JsonPropertyName("tray_times")]
|
||||
public Dictionary<string, int> TrayTimeShortcuts { get; set; }
|
||||
[JsonPropertyName("expirationDateTime")]
|
||||
public DateTimeOffset ExpirationDateTime { get; set; }
|
||||
|
||||
[JsonPropertyName("customTrayTimes")]
|
||||
public Dictionary<string, int> CustomTrayTimes { get; set; }
|
||||
}
|
||||
|
||||
public enum AwakeMode
|
||||
@@ -39,5 +44,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
PASSIVE = 0,
|
||||
INDEFINITE = 1,
|
||||
TIMED = 2,
|
||||
EXPIRABLE = 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class AwakeSettings : BasePTModuleSettings, ISettingsConfig
|
||||
public class AwakeSettings : BasePTModuleSettings, ISettingsConfig, ICloneable
|
||||
{
|
||||
public const string ModuleName = "Awake";
|
||||
public const string ModuleVersion = "0.0.1";
|
||||
public const string ModuleVersion = "0.0.2";
|
||||
|
||||
public AwakeSettings()
|
||||
{
|
||||
@@ -22,6 +24,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("properties")]
|
||||
public AwakeProperties Properties { get; set; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new AwakeSettings()
|
||||
{
|
||||
Name = Name,
|
||||
Version = Version,
|
||||
Properties = new AwakeProperties()
|
||||
{
|
||||
CustomTrayTimes = Properties.CustomTrayTimes.ToDictionary(entry => entry.Key, entry => entry.Value),
|
||||
Mode = Properties.Mode,
|
||||
KeepDisplayOn = Properties.KeepDisplayOn,
|
||||
IntervalMinutes = Properties.IntervalMinutes,
|
||||
IntervalHours = Properties.IntervalHours,
|
||||
ExpirationDateTime = Properties.ExpirationDateTime,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return Name;
|
||||
|
||||
@@ -7,5 +7,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Interfaces
|
||||
public interface ISettingsRepository<T>
|
||||
{
|
||||
T SettingsConfig { get; set; }
|
||||
|
||||
bool ReloadSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
}
|
||||
|
||||
public bool ReloadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
T settingsItem = new T();
|
||||
settingsConfig = _settingsUtils.GetSettingsOrDefault<T>(settingsItem.GetModuleName());
|
||||
|
||||
SettingsConfig = settingsConfig;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Settings configurations shared across all viewmodels
|
||||
public T SettingsConfig
|
||||
{
|
||||
|
||||
@@ -48,6 +48,23 @@ namespace Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReloadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
T settingsItem = new T();
|
||||
_settingsConfig = _settingsUtils.GetSettingsOrDefault<T>(settingsItem.GetModuleName());
|
||||
|
||||
SettingsConfig = _settingsConfig;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Mock<IFile> GetModuleIOProvider(string version, string module, string fileName)
|
||||
|
||||
@@ -1812,35 +1812,41 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<data name="Awake.ModuleDescription" xml:space="preserve">
|
||||
<value>A convenient way to keep your PC awake on-demand.</value>
|
||||
</data>
|
||||
<data name="Awake_EnableAwake.Header" xml:space="preserve">
|
||||
<data name="Awake_EnableSettingsCard.Header" xml:space="preserve">
|
||||
<value>Enable Awake</value>
|
||||
<comment>Awake is a product name, do not loc</comment>
|
||||
</data>
|
||||
<data name="Awake_NoKeepAwake.Content" xml:space="preserve">
|
||||
<data name="Awake_NoKeepAwakeSelector.Content" xml:space="preserve">
|
||||
<value>Keep using the selected power plan</value>
|
||||
</data>
|
||||
<data name="Awake_IndefiniteKeepAwake.Content" xml:space="preserve">
|
||||
<data name="Awake_IndefiniteKeepAwakeSelector.Content" xml:space="preserve">
|
||||
<value>Keep awake indefinitely</value>
|
||||
</data>
|
||||
<data name="Awake_TemporaryKeepAwake.Content" xml:space="preserve">
|
||||
<value>Keep awake temporarily</value>
|
||||
<data name="Awake_TemporaryKeepAwakeSelector.Content" xml:space="preserve">
|
||||
<value>Keep awake for a time interval</value>
|
||||
</data>
|
||||
<data name="Awake_EnableDisplayKeepAwake.Header" xml:space="preserve">
|
||||
<data name="Awake_ExpirableKeepAwakeSelector.Content" xml:space="preserve">
|
||||
<value>Keep awake until expiration</value>
|
||||
</data>
|
||||
<data name="Awake_DisplaySettingsCard.Header" xml:space="preserve">
|
||||
<value>Keep screen on</value>
|
||||
</data>
|
||||
<data name="Awake_EnableDisplayKeepAwake.Description" xml:space="preserve">
|
||||
<data name="Awake_DisplaySettingsCard.Description" xml:space="preserve">
|
||||
<value>This setting is only available when keeping the PC awake</value>
|
||||
</data>
|
||||
<data name="Awake_Mode.Header" xml:space="preserve">
|
||||
<data name="Awake_ExpirationSettingsCard.Description" xml:space="preserve">
|
||||
<value>Keep custom awakeness state until a specific date and time</value>
|
||||
</data>
|
||||
<data name="Awake_ModeSettingsCard.Header" xml:space="preserve">
|
||||
<value>Mode</value>
|
||||
</data>
|
||||
<data name="Awake_Behavior_GroupSettings.Header" xml:space="preserve">
|
||||
<data name="Awake_BehaviorSettingsGroup.Header" xml:space="preserve">
|
||||
<value>Behavior</value>
|
||||
</data>
|
||||
<data name="Awake_TemporaryKeepAwake_Hours.Header" xml:space="preserve">
|
||||
<data name="Awake_IntervalHoursInput.Header" xml:space="preserve">
|
||||
<value>Hours</value>
|
||||
</data>
|
||||
<data name="Awake_TemporaryKeepAwake_Minutes.Header" xml:space="preserve">
|
||||
<data name="Awake_IntervalMinutesInput.Header" xml:space="preserve">
|
||||
<value>Minutes</value>
|
||||
</data>
|
||||
<data name="Oobe_Awake.Title" xml:space="preserve">
|
||||
@@ -1930,7 +1936,7 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<data name="SeeWhatsNew.Content" xml:space="preserve">
|
||||
<value>See what's new</value>
|
||||
</data>
|
||||
<data name="Awake_Mode.Description" xml:space="preserve">
|
||||
<data name="Awake_ModeSettingsCard.Description" xml:space="preserve">
|
||||
<value>Manage the state of your device when Awake is active</value>
|
||||
</data>
|
||||
<data name="ExcludedApps.Header" xml:space="preserve">
|
||||
@@ -2120,8 +2126,11 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<value>New size</value>
|
||||
<comment>First part of the default name of new sizes that can be added in PT's settings ui.</comment>
|
||||
</data>
|
||||
<data name="Awake_TimeBeforeAwake.Header" xml:space="preserve">
|
||||
<value>Time before returning to the previous awakeness state</value>
|
||||
<data name="Awake_IntervalSettingsCard.Header" xml:space="preserve">
|
||||
<value>Interval before returning to the previous awakeness state</value>
|
||||
</data>
|
||||
<data name="Awake_ExpirationSettingsCard.Header" xml:space="preserve">
|
||||
<value>End date and time</value>
|
||||
</data>
|
||||
<data name="MouseUtils.ModuleTitle" xml:space="preserve">
|
||||
<value>Mouse utilities</value>
|
||||
@@ -3056,4 +3065,4 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="Hosts_Toggle_LoopbackDuplicates.Header" xml:space="preserve">
|
||||
<value>Consider loopback addresses as duplicates</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@@ -4,62 +4,29 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
public class AwakeViewModel : Observable
|
||||
{
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private AwakeSettings Settings { get; set; }
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
public AwakeViewModel(ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<AwakeSettings> moduleSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
|
||||
public AwakeViewModel()
|
||||
{
|
||||
// To obtain the general settings configurations of PowerToys Settings.
|
||||
if (settingsRepository == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settingsRepository));
|
||||
}
|
||||
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
// To obtain the settings configurations of Fancy zones.
|
||||
if (moduleSettingsRepository == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleSettingsRepository));
|
||||
}
|
||||
|
||||
Settings = moduleSettingsRepository.SettingsConfig;
|
||||
|
||||
InitializeEnabledValue();
|
||||
|
||||
_keepDisplayOn = Settings.Properties.KeepDisplayOn;
|
||||
_mode = Settings.Properties.Mode;
|
||||
_hours = Settings.Properties.Hours;
|
||||
_minutes = Settings.Properties.Minutes;
|
||||
|
||||
// set the callback functions value to hangle outgoing IPC message.
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
}
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
public AwakeSettings ModuleSettings
|
||||
{
|
||||
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredAwakeEnabledValue();
|
||||
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
get => _moduleSettings;
|
||||
set
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
_enabledStateIsGPOConfigured = true;
|
||||
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.Awake;
|
||||
if (_moduleSettings != value)
|
||||
{
|
||||
_moduleSettings = value;
|
||||
RefreshModuleSettings();
|
||||
RefreshEnabledState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,13 +45,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
_isEnabled = value;
|
||||
|
||||
GeneralSettingsConfig.Enabled.Awake = value;
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
OnPropertyChanged(nameof(IsTimeConfigurationEnabled));
|
||||
OnPropertyChanged(nameof(IsScreenConfigurationPossibleEnabled));
|
||||
RefreshEnabledState();
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -93,31 +55,44 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
set
|
||||
{
|
||||
if (_enabledStateIsGPOConfigured != value)
|
||||
{
|
||||
_enabledStateIsGPOConfigured = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsExpirationConfigurationEnabled
|
||||
{
|
||||
get => ModuleSettings.Properties.Mode == AwakeMode.EXPIRABLE && IsEnabled;
|
||||
}
|
||||
|
||||
public bool IsTimeConfigurationEnabled
|
||||
{
|
||||
get => _mode == AwakeMode.TIMED && _isEnabled;
|
||||
get => ModuleSettings.Properties.Mode == AwakeMode.TIMED && IsEnabled;
|
||||
}
|
||||
|
||||
public bool IsScreenConfigurationPossibleEnabled
|
||||
{
|
||||
get => _mode != AwakeMode.PASSIVE && _isEnabled;
|
||||
get => ModuleSettings.Properties.Mode != AwakeMode.PASSIVE && IsEnabled;
|
||||
}
|
||||
|
||||
public AwakeMode Mode
|
||||
{
|
||||
get => _mode;
|
||||
get => ModuleSettings.Properties.Mode;
|
||||
set
|
||||
{
|
||||
if (_mode != value)
|
||||
if (ModuleSettings.Properties.Mode != value)
|
||||
{
|
||||
_mode = value;
|
||||
OnPropertyChanged(nameof(Mode));
|
||||
ModuleSettings.Properties.Mode = value;
|
||||
|
||||
OnPropertyChanged(nameof(IsTimeConfigurationEnabled));
|
||||
OnPropertyChanged(nameof(IsScreenConfigurationPossibleEnabled));
|
||||
OnPropertyChanged(nameof(IsExpirationConfigurationEnabled));
|
||||
|
||||
Settings.Properties.Mode = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
@@ -125,79 +100,93 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
public bool KeepDisplayOn
|
||||
{
|
||||
get => _keepDisplayOn;
|
||||
get => ModuleSettings.Properties.KeepDisplayOn;
|
||||
set
|
||||
{
|
||||
if (_keepDisplayOn != value)
|
||||
if (ModuleSettings.Properties.KeepDisplayOn != value)
|
||||
{
|
||||
_keepDisplayOn = value;
|
||||
OnPropertyChanged(nameof(KeepDisplayOn));
|
||||
|
||||
Settings.Properties.KeepDisplayOn = value;
|
||||
ModuleSettings.Properties.KeepDisplayOn = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Hours
|
||||
public uint IntervalHours
|
||||
{
|
||||
get => _hours;
|
||||
get => ModuleSettings.Properties.IntervalHours;
|
||||
set
|
||||
{
|
||||
if (_hours != value)
|
||||
if (ModuleSettings.Properties.IntervalHours != value)
|
||||
{
|
||||
_hours = value;
|
||||
OnPropertyChanged(nameof(Hours));
|
||||
|
||||
Settings.Properties.Hours = value;
|
||||
ModuleSettings.Properties.IntervalHours = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Minutes
|
||||
public uint IntervalMinutes
|
||||
{
|
||||
get => _minutes;
|
||||
get => ModuleSettings.Properties.IntervalMinutes;
|
||||
set
|
||||
{
|
||||
if (_minutes != value)
|
||||
if (ModuleSettings.Properties.IntervalMinutes != value)
|
||||
{
|
||||
_minutes = value;
|
||||
OnPropertyChanged(nameof(Minutes));
|
||||
|
||||
Settings.Properties.Minutes = value;
|
||||
ModuleSettings.Properties.IntervalMinutes = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset ExpirationDateTime
|
||||
{
|
||||
get => ModuleSettings.Properties.ExpirationDateTime;
|
||||
set
|
||||
{
|
||||
if (ModuleSettings.Properties.ExpirationDateTime != value)
|
||||
{
|
||||
ModuleSettings.Properties.ExpirationDateTime = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan ExpirationTime
|
||||
{
|
||||
get => ExpirationDateTime.TimeOfDay;
|
||||
set
|
||||
{
|
||||
if (ExpirationDateTime.TimeOfDay != value)
|
||||
{
|
||||
ExpirationDateTime = new DateTime(ExpirationDateTime.Year, ExpirationDateTime.Month, ExpirationDateTime.Day, value.Hours, value.Minutes, value.Seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
Logger.LogInfo($"Changed the property {propertyName}");
|
||||
OnPropertyChanged(propertyName);
|
||||
if (SendConfigMSG != null)
|
||||
{
|
||||
SndAwakeSettings outsettings = new SndAwakeSettings(Settings);
|
||||
SndModuleSettings<SndAwakeSettings> ipcMessage = new SndModuleSettings<SndAwakeSettings>(outsettings);
|
||||
|
||||
string targetMessage = ipcMessage.ToJsonString();
|
||||
SendConfigMSG(targetMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
InitializeEnabledValue();
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
OnPropertyChanged(nameof(IsTimeConfigurationEnabled));
|
||||
OnPropertyChanged(nameof(IsScreenConfigurationPossibleEnabled));
|
||||
OnPropertyChanged(nameof(IsExpirationConfigurationEnabled));
|
||||
}
|
||||
|
||||
public void RefreshModuleSettings()
|
||||
{
|
||||
OnPropertyChanged(nameof(Mode));
|
||||
OnPropertyChanged(nameof(KeepDisplayOn));
|
||||
OnPropertyChanged(nameof(IntervalHours));
|
||||
OnPropertyChanged(nameof(IntervalMinutes));
|
||||
OnPropertyChanged(nameof(ExpirationDateTime));
|
||||
}
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private AwakeSettings _moduleSettings;
|
||||
private bool _isEnabled;
|
||||
private uint _hours;
|
||||
private uint _minutes;
|
||||
private bool _keepDisplayOn;
|
||||
private AwakeMode _mode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI.UI"
|
||||
xmlns:viewmodels="using:Microsoft.PowerToys.Settings.UI.ViewModels"
|
||||
d:DataContext="{d:DesignInstance Type=viewmodels:AwakeViewModel}"
|
||||
AutomationProperties.LandmarkType="Main"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -22,7 +24,7 @@
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel Orientation="Vertical" ChildrenTransitions="{StaticResource SettingsCardsAnimations}">
|
||||
<labs:SettingsCard
|
||||
x:Uid="Awake_EnableAwake"
|
||||
x:Uid="Awake_EnableSettingsCard"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsAwake.png}"
|
||||
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabledGpoConfigured, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch
|
||||
@@ -37,37 +39,48 @@
|
||||
Severity="Informational" />
|
||||
|
||||
<controls:SettingsGroup
|
||||
x:Uid="Awake_Behavior_GroupSettings"
|
||||
x:Uid="Awake_BehaviorSettingsGroup"
|
||||
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
|
||||
<labs:SettingsCard
|
||||
x:Uid="Awake_Mode"
|
||||
x:Uid="Awake_ModeSettingsCard"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=}">
|
||||
<ComboBox
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SelectedIndex="{x:Bind Path=ViewModel.Mode, Mode=TwoWay, Converter={StaticResource AwakeModeToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="Awake_NoKeepAwake" />
|
||||
<ComboBoxItem x:Uid="Awake_IndefiniteKeepAwake" />
|
||||
<ComboBoxItem x:Uid="Awake_TemporaryKeepAwake" />
|
||||
<ComboBoxItem x:Uid="Awake_NoKeepAwakeSelector" />
|
||||
<ComboBoxItem x:Uid="Awake_IndefiniteKeepAwakeSelector" />
|
||||
<ComboBoxItem x:Uid="Awake_TemporaryKeepAwakeSelector" />
|
||||
<ComboBoxItem x:Uid="Awake_ExpirableKeepAwakeSelector" />
|
||||
</ComboBox>
|
||||
</labs:SettingsCard>
|
||||
|
||||
<labs:SettingsCard
|
||||
x:Uid="Awake_TimeBeforeAwake"
|
||||
x:Uid="Awake_ExpirationSettingsCard"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=}"
|
||||
Visibility="{x:Bind ViewModel.IsExpirationConfigurationEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<DatePicker Date="{x:Bind ViewModel.ExpirationDateTime, Mode=TwoWay}"></DatePicker>
|
||||
<TimePicker Margin="8,0,0,0" Time="{x:Bind ViewModel.ExpirationTime, Mode=TwoWay}" ClockIdentifier="24HourClock"></TimePicker>
|
||||
</StackPanel>
|
||||
</labs:SettingsCard>
|
||||
|
||||
<labs:SettingsCard
|
||||
x:Uid="Awake_IntervalSettingsCard"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=}"
|
||||
Visibility="{x:Bind ViewModel.IsTimeConfigurationEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<NumberBox
|
||||
x:Uid="Awake_TemporaryKeepAwake_Hours"
|
||||
x:Uid="Awake_IntervalHoursInput"
|
||||
Width="96"
|
||||
HorizontalAlignment="Left"
|
||||
LargeChange="5"
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind ViewModel.Hours, Mode=TwoWay}" />
|
||||
Value="{x:Bind ViewModel.IntervalHours, Mode=TwoWay}" />
|
||||
<NumberBox
|
||||
x:Uid="Awake_TemporaryKeepAwake_Minutes"
|
||||
x:Uid="Awake_IntervalMinutesInput"
|
||||
Width="96"
|
||||
Margin="8,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -76,16 +89,15 @@
|
||||
Minimum="0"
|
||||
SmallChange="1"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind ViewModel.Minutes, Mode=TwoWay}" />
|
||||
Value="{x:Bind ViewModel.IntervalMinutes, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</labs:SettingsCard>
|
||||
|
||||
<labs:SettingsCard
|
||||
x:Uid="Awake_EnableDisplayKeepAwake"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=}"
|
||||
x:Uid="Awake_DisplaySettingsCard"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsScreenConfigurationPossibleEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch
|
||||
x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.KeepDisplayOn, Mode=TwoWay}" />
|
||||
</labs:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
@@ -99,7 +111,7 @@
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:SettingsPageControl.SecondaryLinks>
|
||||
<controls:PageLink
|
||||
Link="https://Awake.den.dev"
|
||||
Link="https://awake.den.dev"
|
||||
Text="Den Delimarsky's work on creating Awake" />
|
||||
</controls:SettingsPageControl.SecondaryLinks>
|
||||
</controls:SettingsPageControl>
|
||||
|
||||
@@ -2,25 +2,184 @@
|
||||
// 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;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using PowerToys.GPOWrapper;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
public sealed partial class AwakePage : Page, IRefreshablePage
|
||||
{
|
||||
private readonly string _appName = "Awake";
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
|
||||
private readonly SettingsRepository<GeneralSettings> _generalSettingsRepository;
|
||||
private readonly SettingsRepository<AwakeSettings> _moduleSettingsRepository;
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IFileSystemWatcher _fileSystemWatcher;
|
||||
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
private readonly Func<string, int> _sendConfigMsg;
|
||||
|
||||
private AwakeViewModel ViewModel { get; set; }
|
||||
|
||||
public AwakePage()
|
||||
{
|
||||
var settingsUtils = new SettingsUtils();
|
||||
ViewModel = new AwakeViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<AwakeSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_fileSystem = new FileSystem();
|
||||
_settingsUtils = new SettingsUtils();
|
||||
_sendConfigMsg = ShellPage.SendDefaultIPCMessage;
|
||||
|
||||
ViewModel = new AwakeViewModel();
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
_generalSettingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
|
||||
_moduleSettingsRepository = SettingsRepository<AwakeSettings>.GetInstance(_settingsUtils);
|
||||
|
||||
// We load the view model settings first.
|
||||
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
var settingsPath = _settingsUtils.GetSettingsFilePath(_appName);
|
||||
|
||||
_fileSystemWatcher = _fileSystem.FileSystemWatcher.CreateNew();
|
||||
_fileSystemWatcher.Path = _fileSystem.Path.GetDirectoryName(settingsPath);
|
||||
_fileSystemWatcher.Filter = _fileSystem.Path.GetFileName(settingsPath);
|
||||
_fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
|
||||
_fileSystemWatcher.Changed += Settings_Changed;
|
||||
_fileSystemWatcher.EnableRaisingEvents = true;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered whenever a view model property changes. This is done in addition to the baked-in view model changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: The logic here needs to be optimized since doing string comparison on values is not ideal.
|
||||
/// </remarks>
|
||||
/// <param name="sender">Sender of the change.</param>
|
||||
/// <param name="e">Property parameter.</param>
|
||||
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (_sendConfigMsg != null)
|
||||
{
|
||||
if (e.PropertyName == "IsEnabled")
|
||||
{
|
||||
if (ViewModel.IsEnabled != _generalSettingsRepository.SettingsConfig.Enabled.Awake)
|
||||
{
|
||||
_generalSettingsRepository.SettingsConfig.Enabled.Awake = ViewModel.IsEnabled;
|
||||
|
||||
var generalSettingsMessage = new OutGoingGeneralSettings(_generalSettingsRepository.SettingsConfig).ToString();
|
||||
|
||||
Logger.LogInfo($"Saved general settings from Awake page.");
|
||||
_sendConfigMsg(generalSettingsMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ViewModel.ModuleSettings != null)
|
||||
{
|
||||
SndAwakeSettings currentSettings = new(_moduleSettingsRepository.SettingsConfig);
|
||||
SndModuleSettings<SndAwakeSettings> csIpcMessage = new(currentSettings);
|
||||
|
||||
SndAwakeSettings outSettings = new(ViewModel.ModuleSettings);
|
||||
SndModuleSettings<SndAwakeSettings> outIpcMessage = new(outSettings);
|
||||
|
||||
string csMessage = csIpcMessage.ToJsonString();
|
||||
string outMessage = outIpcMessage.ToJsonString();
|
||||
|
||||
if (!csMessage.Equals(outMessage))
|
||||
{
|
||||
Logger.LogInfo($"Saved Awake settings from Awake page.");
|
||||
_sendConfigMsg(outMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettings(ISettingsRepository<GeneralSettings> generalSettingsRepository, ISettingsRepository<AwakeSettings> moduleSettingsRepository)
|
||||
{
|
||||
if (generalSettingsRepository != null)
|
||||
{
|
||||
if (moduleSettingsRepository != null)
|
||||
{
|
||||
UpdateViewModelSettings(moduleSettingsRepository.SettingsConfig, generalSettingsRepository.SettingsConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(moduleSettingsRepository));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generalSettingsRepository));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateViewModelSettings(AwakeSettings awakeSettings, GeneralSettings generalSettings)
|
||||
{
|
||||
if (awakeSettings != null)
|
||||
{
|
||||
if (generalSettings != null)
|
||||
{
|
||||
ViewModel.IsEnabled = generalSettings.Enabled.Awake;
|
||||
ViewModel.ModuleSettings = (AwakeSettings)awakeSettings.Clone();
|
||||
|
||||
UpdateEnabledState(generalSettings.Enabled.Awake);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(generalSettings));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentNullException(nameof(awakeSettings));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tool enablement state.
|
||||
/// </summary>
|
||||
/// <param name="recommendedState">The state that is recommended for the tool, but can be overridden if a GPO policy is in place.</param>
|
||||
private void UpdateEnabledState(bool recommendedState)
|
||||
{
|
||||
var enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredAwakeEnabledValue();
|
||||
|
||||
if (enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
ViewModel.IsEnabledGpoConfigured = true;
|
||||
ViewModel.IsEnabled = enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModel.IsEnabled = recommendedState;
|
||||
}
|
||||
}
|
||||
|
||||
private void Settings_Changed(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
bool taskAdded = _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
|
||||
{
|
||||
_moduleSettingsRepository.ReloadSettings();
|
||||
LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
|
||||
});
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
ViewModel.RefreshEnabledState();
|
||||
|
||||
Reference in New Issue
Block a user