[Awake]QOL changes - New build ATRIOX_04132023 (#25486)

* Update control to make interaction responsive

* Rip out NLog in favor of standard logging

* Continuing to cleanup NLog stuff

* Simplifying the code more

* Instantly let go of power settings once cancellation requested.

* Cleanup and using built-in native constructs

* Update the API

* Moving towards using a queue instead of tasks

* Code cleanup

* Thread should be flagged as background

* Clean up constants, add docs

* Code cleanup

* Cleanup

* Cleanup

* Remove unnecessary using

* Fix package definition

* Fix NuGet packages

* Update expect.txt

* Remove NLog reference and add a build update in the planning doc

* Cleanup based on report

* More cleanup

* Adding back because the word is clearly somewhere, just not anywhere
I am able to find.

* Revert .net dependency upgrades
This commit is contained in:
Den 2023-05-14 11:42:38 -07:00 committed by GitHub
parent a3227da634
commit 0c5113e908
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 740 additions and 671 deletions

View File

@ -80,6 +80,7 @@ APeriod
apidl
APIENTRY
APIIs
Apm
APPBARDATA
appdata
APPEXECLINK
@ -89,7 +90,6 @@ Applets
Applicationcan
applicationconfiguration
applicationframehost
applog
appmanifest
APPNAME
appref
@ -124,6 +124,7 @@ atlcom
atleast
atlfile
atlstr
ATRIOX
Attribs
aumid
Aut
@ -136,7 +137,7 @@ Autorun
AUTOUPDATE
AValid
awakeness
awakeversion
AWAYMODE
AYUV
azcli
azman
@ -819,6 +820,8 @@ hhk
HHmmss
HHOOK
hhx
Hiber
Hiberboot
HIBYTE
hicon
HIDEWINDOW
@ -1098,8 +1101,6 @@ LOCALSYSTEM
LOCATIONCHANGE
LOCKBYTES
LOCKTYPE
logconsole
logfile
LOGFONT
LOGFONTW
logon
@ -1134,6 +1135,7 @@ lpsz
lpt
LPTHREAD
LPTOP
lptpm
LPTSTR
LPVOID
LPW
@ -1193,6 +1195,7 @@ mediatype
mef
Mega
Melman
MENUBREAK
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
@ -1222,7 +1225,6 @@ MINIMIZEBOX
MINIMIZEEND
MINIMIZESTART
miniz
minlevel
MINORVERSION
Miracast
mjpg
@ -1334,8 +1336,9 @@ Newtonsoft
niels
nielslaute
NIF
nint
NLD
nlog
NLog
nls
NLSTEXT
NOACTIVATE
@ -1394,6 +1397,7 @@ ntdll
ntfs
NTSTATUS
nugets
nuint
nullonfailure
numberbox
NUMLOCK
@ -1450,6 +1454,7 @@ OVERLAPPEDWINDOW
overlaywindow
Oversampling
OWNDC
OWNERDRAW
Packagemanager
PACL
PAINTSTRUCT
@ -1508,7 +1513,6 @@ pgsql
pguid
pkey
PHANDLE
PHANDLER
phbm
phbmp
phwnd
@ -1908,7 +1912,6 @@ spdisp
spdlog
spdo
spec'ing
specialfolder
spesi
splitwstring
spsi

View File

@ -25,7 +25,7 @@
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.1722.45" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.0.1" />
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.0.2" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.3.230502000" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
@ -40,7 +40,7 @@
<PackageVersion Include="NLog.Schema" Version="5.0.4" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta1.20071.2" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="7.0.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageVersion Include="System.Data.OleDb" Version="7.0.0" />
@ -48,7 +48,7 @@
<PackageVersion Include="System.IO.Abstractions" Version="17.2.3" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="System.Reactive" Version="5.0.0" />
<PackageVersion Include="System.Reactive" Version="6.0.0-preview.9" />
<PackageVersion Include="System.Runtime.Caching" Version="7.0.0" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="7.0.0" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />

View File

@ -300,7 +300,7 @@ SOFTWARE.
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.1722.45
- Microsoft.Windows.CsWin32 0.2.46-beta
- Microsoft.Windows.CsWinRT 2.0.1
- Microsoft.Windows.CsWinRT 2.0.2
- Microsoft.Windows.SDK.BuildTools 10.0.22621.755
- Microsoft.WindowsAppSDK 1.3.230502000
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
@ -309,12 +309,11 @@ SOFTWARE.
- Moq 4.18.3
- MSTest.TestAdapter 3.0.1
- MSTest.TestFramework 3.0.1
- NLog 5.0.4
- NLog.Extensions.Logging 5.0.4
- NLog.Schema 5.0.4
- ScipBe.Common.Office.OneNote 3.0.1
- StyleCop.Analyzers 1.2.0-beta.435
- System.CommandLine 2.0.0-beta1.20071.2
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 7.0.0
- System.Configuration.ConfigurationManager 6.0.0
- System.Data.OleDb 7.0.0
@ -322,7 +321,7 @@ SOFTWARE.
- System.IO.Abstractions 17.2.3
- System.IO.Abstractions.TestingHelpers 17.2.3
- System.Management 7.0.0
- System.Reactive 5.0.0
- System.Reactive 6.0.0-preview.9
- System.Runtime.Caching 7.0.0
- System.ServiceProcess.ServiceController 7.0.0
- UnicodeInformation 2.6.0

View File

@ -6,13 +6,23 @@ last-update: 3-20-2022
## Builds
The build ID can be found in [`NLog.config`](https://github.com/microsoft/PowerToys/blob/2e3a2b3f96f67c7dfc72963e5135662d3230b5fe/src/modules/awake/Awake/NLog.config#L5) - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
The build ID can be found in `Program.cs` in the `BuildId` variable - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
The build ID moniker is made up of two components - a reference to a [Halo](https://en.wikipedia.org/wiki/Halo_(franchise)) character, and the date when the work on the specific build started in the format of `MMDDYYYY`.
| Build ID | Build Date |
|:----------------------------------------------------------|:-----------------|
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
| `ARBITER_01312022` | January 31, 2022 |
### `ATRIOX_04132023` (April 13, 2023)
- Moves from using `Task.Run` to spin up threads to actually using a blocking queue that properly sets thread parameters on the same thread.
- Moves back to using native Windows APIs through P/Invoke instead of using a package.
- Move away from custom logging and to built-in logging that is consistent with the rest of PowerToys.
- Updates `System.CommandLine` and `System.Reactive` to the latest preview versions of the package.
### `LIBRARIAN_03202022` (March 20, 2022)
- Changed the tray context menu to be following OS conventions instead of the style offered by Windows Forms. This introduces better support for DPI scaling and theming in the future.

View File

@ -49,7 +49,7 @@ if ($isWinAppSdkProj -eq $True) {
$fileExclusionList = @("*Test*", "*.pdb", "*.lastcodeanalysissucceeded", "createdump.exe") + $interopFilesList + $winAppSDKfilesList
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*png", "*gif", "*ico", "*cur", "*svg", "index.html", "reg.js", "monacoSpecialLanguages.js", "resources.pri", "NLog.config")
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*png", "*gif", "*ico", "*cur", "*svg", "index.html", "reg.js", "monacoSpecialLanguages.js", "resources.pri")
$dllsToIgnore = @("System.CodeDom.dll", "WindowsBase.dll")

View File

@ -5,10 +5,8 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Reflection;
using System.Runtime.Serialization;
using interop;
namespace ManagedCommon

View File

@ -18,6 +18,7 @@
<PackageProjectUrl>https://awake.den.dev</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/powertoys</RepositoryUrl>
<SelfContained>true</SelfContained>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
@ -51,16 +52,16 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" />
<ItemGroup>
<None Remove="Images\Awake.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="NLog" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.Reactive" />
<PackageReference Include="System.Runtime.Caching" />
@ -79,16 +80,17 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Images\Awake.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.CodeAnalysis.NetAnalyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -1,394 +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.
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;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Win32;
using NLog;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.FileSystem;
using Windows.Win32.System.Console;
using Windows.Win32.System.Power;
namespace Awake.Core
{
/// <summary>
/// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts
/// of the codebase.
/// </summary>
public class APIHelper
{
private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
private static readonly Logger _log;
private static CancellationTokenSource _tokenSource;
private static CancellationToken _threadToken;
private static Task? _runnerThread;
private static System.Timers.Timer _timedLoopTimer;
static APIHelper()
{
_timedLoopTimer = new System.Timers.Timer();
_log = LogManager.GetCurrentClassLogger();
_tokenSource = new CancellationTokenSource();
}
internal static void SetConsoleControlHandler(PHANDLER_ROUTINE handler, bool addHandler)
{
PInvoke.SetConsoleCtrlHandler(handler, addHandler);
}
public static void AllocateConsole()
{
_log.Debug("Bootstrapping the console allocation routine.");
PInvoke.AllocConsole();
_log.Debug($"Console allocation result: {Marshal.GetLastWin32Error()}");
var outputFilePointer = PInvoke.CreateFile("CONOUT$", FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE, FILE_SHARE_MODE.FILE_SHARE_WRITE, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, 0, null);
_log.Debug($"CONOUT creation result: {Marshal.GetLastWin32Error()}");
PInvoke.SetStdHandle(Windows.Win32.System.Console.STD_HANDLE.STD_OUTPUT_HANDLE, outputFilePointer);
_log.Debug($"SetStdHandle result: {Marshal.GetLastWin32Error()}");
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true });
}
/// <summary>
/// Sets the computer awake state using the native Win32 SetThreadExecutionState API. This
/// function is just a nice-to-have wrapper that helps avoid tracking the success or failure of
/// the call.
/// </summary>
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
/// <returns>true if successful, false if failed</returns>
private static bool SetAwakeState(EXECUTION_STATE state)
{
try
{
var stateResult = PInvoke.SetThreadExecutionState(state);
return stateResult != 0;
}
catch
{
return false;
}
}
private static bool SetAwakeStateBasedOnDisplaySetting(bool keepDisplayOn)
{
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 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(() => RunIndefiniteJob(keepDisplayOn), _threadToken)
.ContinueWith((result) => callback, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
}
catch (Exception ex)
{
_log.Error(ex.Message);
}
}
public static void SetNoKeepAwake()
{
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeNoKeepAwakeEvent());
CancelExistingThread();
}
public static void SetExpirableKeepAwake(DateTimeOffset expireAt, Action callback, Action failureCallback, bool keepDisplayOn = true)
{
PowerToysTelemetry.Log.WriteEvent(new Awake.Telemetry.AwakeExpirableKeepAwakeEvent());
CancelExistingThread();
if (expireAt > DateTime.Now && expireAt != null)
{
_runnerThread = Task.Run(() => RunExpiringJob(expireAt, keepDisplayOn), _threadToken)
.ContinueWith((result) => callback, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
}
else
{
// 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 expirable keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
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 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}");
}
}
internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false)
{
SetNoKeepAwake();
HWND windowHandle = GetHiddenWindow();
if (windowHandle != HWND.Null)
{
PInvoke.SendMessage(windowHandle, PInvoke.WM_CLOSE, 0, 0);
}
if (force)
{
PInvoke.PostQuitMessage(0);
}
try
{
exitSignal?.Set();
PInvoke.DestroyWindow(windowHandle);
}
catch (Exception ex)
{
_log.Info($"Exit signal error ${ex}");
}
}
private static void RunTimedJob(uint seconds, bool keepDisplayOn = true)
{
bool success = false;
// In case cancellation was already requested.
_threadToken.ThrowIfCancellationRequested();
try
{
success = SetAwakeStateBasedOnDisplaySetting(keepDisplayOn);
if (success)
{
_log.Info($"Initiated timed keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
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.");
}
}
catch (OperationCanceledException ex)
{
// Task was clearly cancelled.
_log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}");
}
}
public static string GetOperatingSystemBuild()
{
try
{
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BuildRegistryLocation);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
if (registryKey != null)
{
var versionString = $"{registryKey.GetValue("ProductName")} {registryKey.GetValue("DisplayVersion")} {registryKey.GetValue("BuildLabEx")}";
return versionString;
}
else
{
_log.Info("Registry key acquisition for OS failed.");
return string.Empty;
}
}
catch (Exception ex)
{
_log.Info($"Could not get registry key for the build number. Error: {ex.Message}");
return string.Empty;
}
}
[SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Function returns DWORD value that identifies the current thread, but we do not need it.")]
internal static IEnumerable<HWND> EnumerateWindowsForProcess(int processId)
{
var handles = new List<HWND>();
var hCurrentWnd = HWND.Null;
do
{
hCurrentWnd = PInvoke.FindWindowEx(HWND.Null, hCurrentWnd, null as string, null);
uint targetProcessId = 0;
unsafe
{
PInvoke.GetWindowThreadProcessId(hCurrentWnd, &targetProcessId);
}
if (targetProcessId == processId)
{
handles.Add(hCurrentWnd);
}
}
while (hCurrentWnd != IntPtr.Zero);
return handles;
}
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")]
internal static HWND GetHiddenWindow()
{
IEnumerable<HWND> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x");
string targetClass = $"{InternalConstants.TrayWindowId}{domain}";
unsafe
{
var classNameLen = 256;
Span<char> className = stackalloc char[classNameLen];
foreach (var handle in windowHandles)
{
fixed (char* ptr = className)
{
int classQueryResult = PInvoke.GetClassName(handle, ptr, classNameLen);
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase))
{
return handle;
}
}
}
}
return HWND.Null;
}
public static Dictionary<string, int> GetDefaultTrayOptions()
{
Dictionary<string, int> optionsList = new Dictionary<string, int>
{
{ "30 minutes", 1800 },
{ "1 hour", 3600 },
{ "2 hours", 7200 },
};
return optionsList;
}
}
}

View File

@ -4,10 +4,11 @@
namespace Awake.Core
{
internal static class InternalConstants
internal static class Constants
{
internal const string AppName = "Awake";
internal const string FullAppName = "PowerToys " + AppName;
internal const string TrayWindowId = "WindowsForms10.Window.0.app.0.";
internal const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
}
}

View File

@ -0,0 +1,284 @@
// 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reactive.Linq;
using System.Text;
using System.Threading;
using Awake.Core.Models;
using Awake.Core.Native;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Microsoft.Win32;
namespace Awake.Core
{
public delegate bool ConsoleEventHandler(Models.ControlType ctrlType);
/// <summary>
/// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts
/// of the codebase.
/// </summary>
public class Manager
{
private static BlockingCollection<ExecutionState> _stateQueue;
private static CancellationTokenSource _tokenSource;
static Manager()
{
_tokenSource = new CancellationTokenSource();
_stateQueue = new BlockingCollection<ExecutionState>();
}
public static void StartMonitor()
{
Thread monitorThread = new(() =>
{
Thread.CurrentThread.IsBackground = true;
while (true)
{
ExecutionState state = _stateQueue.Take();
Logger.LogInfo($"Setting state to {state}");
SetAwakeState(state);
}
});
monitorThread.Start();
}
internal static void SetConsoleControlHandler(ConsoleEventHandler handler, bool addHandler)
{
Bridge.SetConsoleCtrlHandler(handler, addHandler);
}
public static void AllocateConsole()
{
Bridge.AllocConsole();
var outputFilePointer = Bridge.CreateFile("CONOUT$", Native.Constants.GENERIC_READ | Native.Constants.GENERIC_WRITE, FileShare.Write, IntPtr.Zero, FileMode.OpenOrCreate, 0, IntPtr.Zero);
Bridge.SetStdHandle(Native.Constants.STD_OUTPUT_HANDLE, outputFilePointer);
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true });
}
/// <summary>
/// Sets the computer awake state using the native Win32 SetThreadExecutionState API. This
/// function is just a nice-to-have wrapper that helps avoid tracking the success or failure of
/// the call.
/// </summary>
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
/// <returns>true if successful, false if failed</returns>
private static bool SetAwakeState(ExecutionState state)
{
try
{
var stateResult = Bridge.SetThreadExecutionState(state);
return stateResult != 0;
}
catch
{
return false;
}
}
private static ExecutionState ComputeAwakeState(bool keepDisplayOn)
{
if (keepDisplayOn)
{
return ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS;
}
else
{
return ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS;
}
}
public static void CancelExistingThread()
{
Logger.LogInfo($"Attempting to ensure that the thread is properly cleaned up...");
// Resetting the thread state.
_stateQueue.Add(ExecutionState.ES_CONTINUOUS);
// Next, make sure that any existing background threads are terminated.
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
Logger.LogInfo("Instantiating of new token source and thread token completed.");
}
public static void SetIndefiniteKeepAwake(bool keepDisplayOn = false)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent());
CancelExistingThread();
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
}
public static void SetNoKeepAwake()
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeNoKeepAwakeEvent());
CancelExistingThread();
}
public static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeExpirableKeepAwakeEvent());
CancelExistingThread();
if (expireAt > DateTime.Now && expireAt != null)
{
Logger.LogInfo($"Starting expirable log for {expireAt}");
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
Observable.Timer(expireAt).Subscribe(
_ =>
{
Logger.LogInfo($"Completed expirable keep-awake.");
CancelExistingThread();
},
_tokenSource.Token);
}
else
{
// The target date is not in the future.
Logger.LogError("The specified target date and time is not in the future.");
Logger.LogError($"Current time: {DateTime.Now}\tTarget time: {expireAt}");
}
}
public static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true)
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeTimedKeepAwakeEvent());
CancelExistingThread();
Logger.LogInfo($"Timed keep awake started for {seconds} seconds.");
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
Observable.Timer(TimeSpan.FromSeconds(seconds)).Subscribe(
_ =>
{
Logger.LogInfo($"Completed timed thread.");
CancelExistingThread();
},
_tokenSource.Token);
}
internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false)
{
SetNoKeepAwake();
IntPtr windowHandle = GetHiddenWindow();
if (windowHandle != IntPtr.Zero)
{
Bridge.SendMessage(windowHandle, Native.Constants.WM_CLOSE, 0, 0);
}
if (force)
{
Bridge.PostQuitMessage(exitCode);
}
try
{
exitSignal?.Set();
Bridge.DestroyWindow(windowHandle);
}
catch (Exception ex)
{
Logger.LogError($"Exit signal error ${ex}");
}
}
public static string GetOperatingSystemBuild()
{
try
{
RegistryKey? registryKey = Registry.LocalMachine.OpenSubKey(Constants.BuildRegistryLocation);
if (registryKey != null)
{
var versionString = $"{registryKey.GetValue("ProductName")} {registryKey.GetValue("DisplayVersion")} {registryKey.GetValue("BuildLabEx")}";
return versionString;
}
else
{
Logger.LogError("Registry key acquisition for OS failed.");
return string.Empty;
}
}
catch (Exception ex)
{
Logger.LogError($"Could not get registry key for the build number. Error: {ex.Message}");
return string.Empty;
}
}
[SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Function returns DWORD value that identifies the current thread, but we do not need it.")]
internal static IEnumerable<IntPtr> EnumerateWindowsForProcess(int processId)
{
var handles = new List<IntPtr>();
var hCurrentWnd = IntPtr.Zero;
do
{
hCurrentWnd = Bridge.FindWindowEx(IntPtr.Zero, hCurrentWnd, null as string, null);
Bridge.GetWindowThreadProcessId(hCurrentWnd, out uint targetProcessId);
if (targetProcessId == processId)
{
handles.Add(hCurrentWnd);
}
}
while (hCurrentWnd != IntPtr.Zero);
return handles;
}
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")]
internal static IntPtr GetHiddenWindow()
{
IEnumerable<IntPtr> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x");
string targetClass = $"{Constants.TrayWindowId}{domain}";
foreach (var handle in windowHandles)
{
StringBuilder className = new(256);
int classQueryResult = Bridge.GetClassName(handle, className, className.Capacity);
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase))
{
return handle;
}
}
return IntPtr.Zero;
}
public static Dictionary<string, int> GetDefaultTrayOptions()
{
Dictionary<string, int> optionsList = new Dictionary<string, int>
{
{ "30 minutes", 1800 },
{ "1 hour", 3600 },
{ "2 hours", 7200 },
};
return optionsList;
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Awake.Core.Models
{
public struct BatteryReportingScale
{
public uint Granularity;
public uint Capacity;
}
}

View File

@ -0,0 +1,21 @@
// 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
{
/// <summary>
/// The type of control signal received by the handler.
/// </summary>
/// <remarks>
/// See <see href="https://learn.microsoft.com/windows/console/handlerroutine">HandlerRoutine callback function</see>.
/// </remarks>
public enum ControlType
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT = 1,
CTRL_CLOSE_EVENT = 2,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT = 6,
}
}

View File

@ -0,0 +1,17 @@
// 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;
namespace Awake.Core.Models
{
[Flags]
public enum ExecutionState : uint
{
ES_AWAYMODE_REQUIRED = 0x00000040,
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001,
}
}

View File

@ -0,0 +1,70 @@
// 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.Runtime.InteropServices;
namespace Awake.Core.Models
{
public struct SystemPowerCapabilities
{
[MarshalAs(UnmanagedType.U1)]
public bool PowerButtonPresent;
[MarshalAs(UnmanagedType.U1)]
public bool SleepButtonPresent;
[MarshalAs(UnmanagedType.U1)]
public bool LidPresent;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS1;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS2;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS3;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS4;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS5;
[MarshalAs(UnmanagedType.U1)]
public bool HiberFilePresent;
[MarshalAs(UnmanagedType.U1)]
public bool FullWake;
[MarshalAs(UnmanagedType.U1)]
public bool VideoDimPresent;
[MarshalAs(UnmanagedType.U1)]
public bool ApmPresent;
[MarshalAs(UnmanagedType.U1)]
public bool UpsPresent;
[MarshalAs(UnmanagedType.U1)]
public bool ThermalControl;
[MarshalAs(UnmanagedType.U1)]
public bool ProcessorThrottle;
public byte ProcessorMinThrottle;
public byte ProcessorMaxThrottle;
[MarshalAs(UnmanagedType.U1)]
public bool FastSystemS4;
[MarshalAs(UnmanagedType.U1)]
public bool Hiberboot;
[MarshalAs(UnmanagedType.U1)]
public bool WakeAlarmPresent;
[MarshalAs(UnmanagedType.U1)]
public bool AoAc;
[MarshalAs(UnmanagedType.U1)]
public bool DiskSpinDown;
public byte HiberFileType;
[MarshalAs(UnmanagedType.U1)]
public bool AoAcConnectivitySupported;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
private readonly byte[] spare3;
[MarshalAs(UnmanagedType.U1)]
public bool SystemBatteriesPresent;
[MarshalAs(UnmanagedType.U1)]
public bool BatteriesAreShortTerm;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public BatteryReportingScale[] BatteryScale;
public SystemPowerState AcOnLineWake;
public SystemPowerState SoftLidWake;
public SystemPowerState RtcWake;
public SystemPowerState MinDeviceWakeState;
public SystemPowerState DefaultLowLatencyWake;
}
}

View File

@ -0,0 +1,24 @@
// 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
{
/// <summary>
/// Represents the system power state.
/// </summary>
/// <remarks>
/// See <see href="https://learn.microsoft.com/windows/win32/power/system-power-states">System power states</see>.
/// </remarks>
public enum SystemPowerState
{
PowerSystemUnspecified = 0,
PowerSystemWorking = 1,
PowerSystemSleeping1 = 2,
PowerSystemSleeping2 = 3,
PowerSystemSleeping3 = 4,
PowerSystemHibernate = 5,
PowerSystemShutdown = 6,
PowerSystemMaximum = 7,
}
}

View File

@ -2,17 +2,15 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.Win32;
namespace Awake.Core.Models
{
internal enum TrayCommands : uint
{
TC_DISPLAY_SETTING = PInvoke.WM_USER + 1,
TC_MODE_PASSIVE = PInvoke.WM_USER + 2,
TC_MODE_INDEFINITE = PInvoke.WM_USER + 3,
TC_MODE_EXPIRABLE = PInvoke.WM_USER + 4,
TC_EXIT = PInvoke.WM_USER + 100,
TC_TIME = PInvoke.WM_USER + 101,
TC_DISPLAY_SETTING = Native.Constants.WM_USER + 1,
TC_MODE_PASSIVE = Native.Constants.WM_USER + 2,
TC_MODE_INDEFINITE = Native.Constants.WM_USER + 3,
TC_MODE_EXPIRABLE = Native.Constants.WM_USER + 4,
TC_EXIT = Native.Constants.WM_USER + 100,
TC_TIME = Native.Constants.WM_USER + 101,
}
}

View File

@ -0,0 +1,80 @@
// 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;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Awake.Core.Models;
namespace Awake.Core.Native
{
internal sealed class Bridge
{
internal delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("Powrprof.dll", SetLastError = true)]
internal static extern bool GetPwrCapabilities(out SystemPowerCapabilities lpSystemPowerCapabilities);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler handler, bool add);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern uint GetCurrentThreadId();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPWStr)] string filename,
[MarshalAs(UnmanagedType.U4)] uint access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr CreatePopupMenu();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool InsertMenu(IntPtr hMenu, uint uPosition, uint uFlags, uint uIDNewItem, string lpNewItem);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string? className, string? windowTitle);
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, nuint wParam, nint lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool DestroyMenu(IntPtr hMenu);
[DllImport("user32.dll")]
internal static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll")]
internal static extern void PostQuitMessage(int nExitCode);
}
}

View File

@ -0,0 +1,32 @@
// 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.Native
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 API convention.")]
internal sealed class Constants
{
internal const uint WM_COMMAND = 0x111;
internal const uint WM_USER = 0x400;
internal const uint WM_GETTEXT = 0x000D;
internal const uint WM_CLOSE = 0x0010;
// Popup menu constants.
internal const uint MF_BYPOSITION = 1024;
internal const uint MF_STRING = 0;
internal const uint MF_MENUBREAK = 0x00000040;
internal const uint MF_SEPARATOR = 0x00000800;
internal const uint MF_POPUP = 0x00000010;
internal const uint MF_UNCHECKED = 0x00000000;
internal const uint MF_CHECKED = 0x00000008;
internal const uint MF_OWNERDRAW = 0x00000100;
internal const uint MF_ENABLED = 0x00000000;
internal const uint MF_DISABLED = 0x00000002;
internal const int STD_OUTPUT_HANDLE = -11;
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint GENERIC_READ = 0x80000000;
}
}

View File

@ -10,13 +10,9 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Awake.Core.Models;
using Awake.Core.Native;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using NLog;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
namespace Awake.Core
{
@ -29,16 +25,14 @@ namespace Awake.Core
/// </remarks>
internal static class TrayHelper
{
private static readonly Logger _log;
private static IntPtr _trayMenu;
private static DestroyMenuSafeHandle TrayMenu { get; set; }
private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; }
private static NotifyIcon TrayIcon { get; set; }
static TrayHelper()
{
_log = LogManager.GetCurrentClassLogger();
TrayMenu = new DestroyMenuSafeHandle();
TrayIcon = new NotifyIcon();
}
@ -49,20 +43,23 @@ namespace Awake.Core
{
try
{
_log.Info("Setting up the tray.");
((NotifyIcon?)tray).Text = text;
((NotifyIcon?)tray).Icon = icon;
((NotifyIcon?)tray).ContextMenuStrip = contextMenu;
((NotifyIcon?)tray).Visible = true;
((NotifyIcon?)tray).MouseClick += TrayClickHandler;
Application.AddMessageFilter(new TrayMessageFilter(exitSignal));
Application.Run();
_log.Info("Tray setup complete.");
Logger.LogInfo("Setting up the tray.");
if (tray != null)
{
((NotifyIcon)tray).Text = text;
((NotifyIcon)tray).Icon = icon;
((NotifyIcon)tray).ContextMenuStrip = contextMenu;
((NotifyIcon)tray).Visible = true;
((NotifyIcon)tray).MouseClick += TrayClickHandler;
Application.AddMessageFilter(new TrayMessageFilter(exitSignal));
Application.Run();
Logger.LogInfo("Tray setup complete.");
}
}
catch (Exception ex)
{
_log.Error($"An error occurred initializing the tray. {ex.Message}");
_log.Error($"{ex.StackTrace}");
Logger.LogError($"An error occurred initializing the tray. {ex.Message}");
Logger.LogError($"{ex.StackTrace}");
}
},
TrayIcon);
@ -81,12 +78,12 @@ namespace Awake.Core
/// <param name="e">MouseEventArgs instance containing mouse click event information.</param>
private static void TrayClickHandler(object? sender, MouseEventArgs e)
{
HWND windowHandle = APIHelper.GetHiddenWindow();
IntPtr windowHandle = Manager.GetHiddenWindow();
if (windowHandle != HWND.Null)
if (windowHandle != IntPtr.Zero)
{
PInvoke.SetForegroundWindow(windowHandle);
PInvoke.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, null);
Bridge.SetForegroundWindow(windowHandle);
Bridge.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, IntPtr.Zero);
}
}
@ -102,46 +99,55 @@ namespace Awake.Core
public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts, bool startedFromPowerToys)
{
TrayMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu());
if (TrayMenu != IntPtr.Zero)
{
var destructionStatus = Bridge.DestroyMenu(TrayMenu);
if (destructionStatus != true)
{
Logger.LogError("Failed to destroy menu.");
}
}
if (!TrayMenu.IsInvalid)
TrayMenu = Bridge.CreatePopupMenu();
if (TrayMenu != IntPtr.Zero)
{
if (!startedFromPowerToys)
{
// If Awake is started from PowerToys, the correct way to exit it is disabling it from Settings.
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit");
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_SEPARATOR, 0, string.Empty);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit");
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
}
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (keepDisplayOn ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED) | (mode == AwakeMode.PASSIVE ? MENU_ITEM_FLAGS.MF_DISABLED : MENU_ITEM_FLAGS.MF_ENABLED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on");
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (keepDisplayOn ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED) | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on");
}
// In case there are no tray shortcuts defined for the application default to a
// reasonable initial set.
if (trayTimeShortcuts.Count == 0)
{
trayTimeShortcuts.AddRange(APIHelper.GetDefaultTrayOptions());
trayTimeShortcuts.AddRange(Manager.GetDefaultTrayOptions());
}
var awakeTimeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false);
var awakeTimeMenu = Bridge.CreatePopupMenu();
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);
Bridge.InsertMenu(awakeTimeMenu, (uint)i, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key);
}
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_SEPARATOR, 0, string.Empty);
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
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");
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)");
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.INDEFINITE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely");
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (mode == AwakeMode.TIMED ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, "Keep awake on interval");
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | Native.Constants.MF_DISABLED | (mode == AwakeMode.EXPIRABLE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_EXPIRABLE, "Keep awake until expiration date and time");
TrayIcon.Text = text;
}
private sealed class CheckButtonToolStripMenuItemAccessibleObject : ToolStripItem.ToolStripItemAccessibleObject
{
private CheckButtonToolStripMenuItem _menuItem;
private readonly CheckButtonToolStripMenuItem _menuItem;
public CheckButtonToolStripMenuItemAccessibleObject(CheckButtonToolStripMenuItem menuItem)
: base(menuItem)
@ -149,23 +155,13 @@ namespace Awake.Core
_menuItem = menuItem;
}
public override AccessibleRole Role
{
get
{
return AccessibleRole.CheckButton;
}
}
public override AccessibleRole Role => AccessibleRole.CheckButton;
public override string Name => _menuItem.Text + ", " + Role + ", " + (_menuItem.Checked ? "Checked" : "Unchecked");
}
private sealed class CheckButtonToolStripMenuItem : ToolStripMenuItem
{
public CheckButtonToolStripMenuItem()
{
}
protected override AccessibleObject CreateAccessibilityInstance()
{
return new CheckButtonToolStripMenuItemAccessibleObject(this);

View File

@ -10,9 +10,6 @@ using System.Threading;
using System.Windows.Forms;
using Awake.Core.Models;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.Win32;
#pragma warning disable CS8603 // Possible null reference return.
namespace Awake.Core
{
@ -20,7 +17,7 @@ namespace Awake.Core
{
private static SettingsUtils? _moduleSettings;
private static SettingsUtils ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
private static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
private static ManualResetEvent? _exitSignal;
@ -36,7 +33,7 @@ namespace Awake.Core
switch (m.Msg)
{
case (int)PInvoke.WM_COMMAND:
case (int)Native.Constants.WM_COMMAND:
var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF;
switch (targetCommandIndex)
{
@ -44,26 +41,26 @@ namespace Awake.Core
ExitCommandHandler(_exitSignal);
break;
case (long)TrayCommands.TC_DISPLAY_SETTING:
DisplaySettingCommandHandler(InternalConstants.AppName);
DisplaySettingCommandHandler(Constants.AppName);
break;
case (long)TrayCommands.TC_MODE_INDEFINITE:
IndefiniteKeepAwakeCommandHandler(InternalConstants.AppName);
IndefiniteKeepAwakeCommandHandler(Constants.AppName);
break;
case (long)TrayCommands.TC_MODE_PASSIVE:
PassiveKeepAwakeCommandHandler(InternalConstants.AppName);
PassiveKeepAwakeCommandHandler(Constants.AppName);
break;
case var _ when targetCommandIndex >= trayCommandsSize:
// Format for the timer block:
// TrayCommands.TC_TIME + ZERO_BASED_INDEX_IN_SETTINGS
AwakeSettings settings = ModuleSettings.GetSettings<AwakeSettings>(InternalConstants.AppName);
AwakeSettings settings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName);
if (settings.Properties.CustomTrayTimes.Count == 0)
{
settings.Properties.CustomTrayTimes.AddRange(APIHelper.GetDefaultTrayOptions());
settings.Properties.CustomTrayTimes.AddRange(Manager.GetDefaultTrayOptions());
}
int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME;
var targetTime = settings.Properties.CustomTrayTimes.ElementAt(index).Value;
TimedKeepAwakeCommandHandler(InternalConstants.AppName, targetTime);
TimedKeepAwakeCommandHandler(Constants.AppName, targetTime);
break;
}
@ -75,7 +72,7 @@ namespace Awake.Core
private static void ExitCommandHandler(ManualResetEvent? exitSignal)
{
APIHelper.CompleteExit(0, exitSignal, true);
Manager.CompleteExit(0, exitSignal, true);
}
private static void DisplaySettingCommandHandler(string moduleName)
@ -84,7 +81,7 @@ namespace Awake.Core
try
{
currentSettings = ModuleSettings.GetSettings<AwakeSettings>(moduleName);
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (FileNotFoundException)
{
@ -93,7 +90,7 @@ namespace Awake.Core
currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
private static void TimedKeepAwakeCommandHandler(string moduleName, int seconds)
@ -104,7 +101,7 @@ namespace Awake.Core
try
{
currentSettings = ModuleSettings.GetSettings<AwakeSettings>(moduleName);
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (FileNotFoundException)
{
@ -115,7 +112,7 @@ namespace Awake.Core
currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours;
currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
private static void PassiveKeepAwakeCommandHandler(string moduleName)
@ -124,7 +121,7 @@ namespace Awake.Core
try
{
currentSettings = ModuleSettings.GetSettings<AwakeSettings>(moduleName);
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (FileNotFoundException)
{
@ -133,7 +130,7 @@ namespace Awake.Core
currentSettings.Properties.Mode = AwakeMode.PASSIVE;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
private static void IndefiniteKeepAwakeCommandHandler(string moduleName)
@ -142,7 +139,7 @@ namespace Awake.Core
try
{
currentSettings = ModuleSettings.GetSettings<AwakeSettings>(moduleName);
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
}
catch (FileNotFoundException)
{
@ -151,7 +148,7 @@ namespace Awake.Core
currentSettings.Properties.Mode = AwakeMode.INDEFINITE;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
}
}
}

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variable name="buildId" value="NOBLE_SIX_02162023" />
<targets async="true">
<target name="logfile"
xsi:type="File"
fileName="${specialfolder:folder=LocalApplicationData}/Microsoft/PowerToys/Awake/Logs/${var:awakeversion}/applog_${date:format=yyyy-MM-dd_HH}_${var:buildId}.txt"
layout="[${longdate} ${level:uppercase=true} ${logger}] ${message}"
archiveEvery="Day"
archiveNumbering="Rolling"
maxArchiveFiles="30"/>
<target name="logconsole"
xsi:type="Console"
layout="[${longdate} ${level:uppercase=true}] ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logconsole" />
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>

View File

@ -1,23 +0,0 @@
AllocConsole
CreateFile
CreatePopupMenu
DestroyMenu
DestroyWindow
FindWindowEx
GetClassName
GetCurrentThreadId
GetPwrCapabilities
GetWindowThreadProcessId
HMENU
InsertMenu
PostQuitMessage
SendMessage
SetConsoleCtrlHandler
SetForegroundWindow
SetStdHandle
SetThreadExecutionState
TrackPopupMenuEx
WM_CLOSE
WM_COMMAND
WM_GETTEXT
WM_USER

View File

@ -4,7 +4,6 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
@ -17,18 +16,10 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Awake.Core;
using interop;
using Awake.Core.Models;
using Awake.Core.Native;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using NLog;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using Windows.Win32.System.Power;
using Logger = NLog.Logger;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
namespace Awake
{
@ -40,7 +31,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 = "NOBLE_SIX_02162023";
private static readonly string BuildId = "ATRIOX_04132023";
private static Mutex? _mutex;
private static FileSystemWatcher? _watcher;
@ -48,22 +39,21 @@ namespace Awake
private static bool _startedFromPowerToys;
public static Mutex LockMutex { get => _mutex; set => _mutex = value; }
private static Logger? _log;
public static Mutex? LockMutex { get => _mutex; set => _mutex = value; }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private static PHANDLER_ROUTINE _handler;
private static SYSTEM_POWER_CAPABILITIES _powerCapabilities;
private static ConsoleEventHandler _handler;
private static SystemPowerCapabilities _powerCapabilities;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private static ManualResetEvent _exitSignal = new ManualResetEvent(false);
private static int Main(string[] args)
{
// Log initialization needs to always happen before we test whether
// only one instance of Awake is running.
_log = LogManager.GetCurrentClassLogger();
_settingsUtils = new SettingsUtils();
LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated);
Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs"));
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
@ -71,20 +61,16 @@ namespace Awake
return 0;
}
LockMutex = new Mutex(true, InternalConstants.AppName, out bool instantiated);
if (!instantiated)
{
Exit(InternalConstants.AppName + " is already running! Exiting the application.", 1, _exitSignal, true);
Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1, _exitSignal, true);
}
_settingsUtils = new SettingsUtils();
_log.Info($"Launching {InternalConstants.AppName}...");
_log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
_log.Info($"Build: {BuildId}");
_log.Info($"OS: {Environment.OSVersion}");
_log.Info($"OS Build: {APIHelper.GetOperatingSystemBuild()}");
Logger.LogInfo($"Launching {Core.Constants.AppName}...");
Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
Logger.LogInfo($"Build: {BuildId}");
Logger.LogInfo($"OS: {Environment.OSVersion}");
Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}");
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
@ -94,21 +80,18 @@ namespace Awake
// To make it easier to diagnose future issues, let's get the
// system power capabilities and aggregate them in the log.
PInvoke.GetPwrCapabilities(out _powerCapabilities);
_log.Info(JsonSerializer.Serialize(_powerCapabilities));
Bridge.GetPwrCapabilities(out _powerCapabilities);
Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities));
_log.Info("Parsing parameters...");
Logger.LogInfo("Parsing parameters...");
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.")
description: $"Specifies whether {Core.Constants.AppName} will be using the PowerToys configuration file for managing the state.")
{
Argument = new Argument<bool>(() => false)
{
Arity = ArgumentArity.ZeroOrOne,
},
Required = false,
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<bool> displayOption = new(
@ -116,11 +99,8 @@ namespace Awake
getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.")
{
Argument = new Argument<bool>(() => false)
{
Arity = ArgumentArity.ZeroOrOne,
},
Required = false,
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
Option<uint> timeOption = new(
@ -128,35 +108,26 @@ namespace Awake
getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.")
{
Argument = new Argument<uint>(() => 0)
{
Arity = ArgumentArity.ExactlyOne,
},
Required = false,
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};
Option<int> pidOption = new(
aliases: new[] { "--pid", "-p" },
getDefaultValue: () => 0,
description: $"Bind the execution of {InternalConstants.AppName} to another process. When the process ends, the system will resume managing the current sleep/display mode.")
description: $"Bind the execution of {Core.Constants.AppName} to another process. When the process ends, the system will resume managing the current sleep and display state.")
{
Argument = new Argument<int>(() => 0)
{
Arity = ArgumentArity.ZeroOrOne,
},
Required = false,
Arity = ArgumentArity.ZeroOrOne,
IsRequired = 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.")
description: $"Determines the end date/time when {Core.Constants.AppName} will back off and let the system manage the current sleep and display state.")
{
Argument = new Argument<string>(() => string.Empty)
{
Arity = ArgumentArity.ZeroOrOne,
},
Required = false,
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
RootCommand? rootCommand = new()
@ -168,49 +139,58 @@ namespace Awake
expireAtOption,
};
rootCommand.Description = InternalConstants.AppName;
rootCommand.Description = Core.Constants.AppName;
rootCommand.Handler = CommandHandler.Create<bool, bool, uint, int, string>(HandleCommandLineArguments);
_log.Info("Parameter setup complete. Proceeding to the rest of the app initiation...");
rootCommand.SetHandler(
HandleCommandLineArguments,
configOption,
displayOption,
timeOption,
pidOption,
expireAtOption);
return rootCommand.InvokeAsync(args).Result;
}
private static BOOL ExitHandler(uint ctrlType)
private static bool ExitHandler(ControlType ctrlType)
{
_log.Info($"Exited through handler with control type: {ctrlType}");
Logger.LogInfo($"Exited through handler with control type: {ctrlType}");
Exit("Exiting from the internal termination handler.", Environment.ExitCode, _exitSignal);
return false;
}
private static void Exit(string message, int exitCode, ManualResetEvent exitSignal, bool force = false)
{
_log.Info(message);
Logger.LogInfo(message);
APIHelper.CompleteExit(exitCode, exitSignal, force);
Manager.CompleteExit(exitCode, exitSignal, force);
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt)
{
_handler += ExitHandler;
APIHelper.SetConsoleControlHandler(_handler, true);
if (pid == 0)
{
_log.Info("No PID specified. Allocating console...");
APIHelper.AllocateConsole();
Logger.LogInfo("No PID specified. Allocating console...");
Manager.AllocateConsole();
_handler += new ConsoleEventHandler(ExitHandler);
Manager.SetConsoleControlHandler(_handler, true);
Trace.Listeners.Add(new ConsoleTraceListener());
}
else
{
_startedFromPowerToys = true;
}
_log.Info($"The value for --use-pt-config is: {usePtConfig}");
_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}");
Logger.LogInfo($"The value for --use-pt-config is: {usePtConfig}");
Logger.LogInfo($"The value for --display-on is: {displayOn}");
Logger.LogInfo($"The value for --time-limit is: {timeLimit}");
Logger.LogInfo($"The value for --pid is: {pid}");
Logger.LogInfo($"The value for --expire-at is: {expireAt}");
// Start the monitor thread that will be used to track the current state.
Manager.StartMonitor();
if (usePtConfig)
{
@ -218,7 +198,7 @@ namespace Awake
// and instead watch for changes in the file.
try
{
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, Constants.AwakeExitEvent());
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, interop.Constants.AwakeExitEvent());
new Thread(() =>
{
if (WaitHandle.WaitAny(new WaitHandle[] { _exitSignal, eventHandle }) == 1)
@ -227,17 +207,17 @@ namespace Awake
}
}).Start();
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "images/awake.ico")), _exitSignal);
TrayHelper.InitializeTray(Core.Constants.FullAppName, new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "images/awake.ico")), _exitSignal);
string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName);
_log.Info($"Reading configuration file: {settingsPath}");
string? settingsPath = _settingsUtils!.GetSettingsFilePath(Core.Constants.AppName);
Logger.LogInfo($"Reading configuration file: {settingsPath}");
if (!File.Exists(settingsPath))
{
string? errorString = $"The settings file does not exist. Scaffolding default configuration...";
AwakeSettings scaffoldSettings = new AwakeSettings();
_settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), InternalConstants.AppName);
_settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), Core.Constants.AppName);
}
ScaffoldConfiguration(settingsPath);
@ -245,8 +225,7 @@ namespace Awake
catch (Exception ex)
{
string? errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}";
_log.Info(errorString);
_log.Debug(errorString);
Logger.LogError(errorString);
}
}
else
@ -264,16 +243,18 @@ namespace Awake
// 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.
Logger.LogInfo($"Operating in thread ID {Environment.CurrentManagedThreadId}.");
SetupExpirableKeepAwake(expirationDateTime, displayOn);
}
else
{
_log.Info($"Target date is not in the future, therefore there is nothing to wait for.");
Logger.LogInfo($"Target date is not in the future, therefore there is nothing to wait for.");
}
}
catch
catch (Exception ex)
{
_log.Error($"Could not parse date string {expireAt} into a viable date.");
Logger.LogError($"Could not parse date string {expireAt} into a viable date.");
Logger.LogError(ex.Message);
}
}
else
@ -295,7 +276,7 @@ namespace Awake
{
RunnerHelper.WaitForPowerToysRunner(pid, () =>
{
_log.Info($"Triggered PID-based exit handler for PID {pid}.");
Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}.");
Exit("Terminating from process binding hook.", 0, _exitSignal, true);
});
}
@ -330,7 +311,7 @@ namespace Awake
.Select(e => e.EventArgs)
.Subscribe(HandleAwakeConfigChange);
TrayHelper.SetTray(InternalConstants.FullAppName, new AwakeSettings(), _startedFromPowerToys);
TrayHelper.SetTray(Core.Constants.FullAppName, new AwakeSettings(), _startedFromPowerToys);
// Initially the file might not be updated, so we need to start processing
// settings right away.
@ -338,19 +319,19 @@ namespace Awake
}
catch (Exception ex)
{
_log.Error($"An error occurred scaffolding the configuration. Error details: {ex.Message}");
Logger.LogError($"An error occurred scaffolding the configuration. Error details: {ex.Message}");
}
}
private static void SetupIndefiniteKeepAwake(bool displayOn)
{
APIHelper.SetIndefiniteKeepAwake(LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
Manager.SetIndefiniteKeepAwake(displayOn);
}
private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent)
{
_log.Info("Detected a settings file change. Updating configuration...");
_log.Info("Resetting keep-awake to normal state due to settings change.");
Logger.LogInfo("Detected a settings file change. Updating configuration...");
Logger.LogInfo("Resetting keep-awake to normal state due to settings change.");
ProcessSettings();
}
@ -358,11 +339,11 @@ namespace Awake
{
try
{
AwakeSettings settings = _settingsUtils.GetSettings<AwakeSettings>(InternalConstants.AppName);
AwakeSettings settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName);
if (settings != null)
{
_log.Info($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
switch (settings.Properties.Mode)
{
@ -396,60 +377,45 @@ namespace Awake
default:
{
string? errorMessage = "Unknown mode of operation. Check config file.";
_log.Info(errorMessage);
_log.Debug(errorMessage);
Logger.LogError(errorMessage);
break;
}
}
TrayHelper.SetTray(InternalConstants.FullAppName, settings, _startedFromPowerToys);
TrayHelper.SetTray(Core.Constants.FullAppName, settings, _startedFromPowerToys);
}
else
{
string? errorMessage = "Settings are null.";
_log.Info(errorMessage);
_log.Debug(errorMessage);
Logger.LogError(errorMessage);
}
}
catch (Exception ex)
{
string? errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}";
_log.Info(errorMessage);
_log.Debug(errorMessage);
Logger.LogError(errorMessage);
}
}
private static void SetupNoKeepAwake()
{
_log.Info($"Operating in passive mode (computer's standard power plan). No custom keep awake settings enabled.");
Logger.LogInfo($"Operating in passive mode (computer's standard power plan). No custom keep awake settings enabled.");
APIHelper.SetNoKeepAwake();
Manager.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}.");
Logger.LogInfo($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {displayOn}.");
APIHelper.SetExpirableKeepAwake(expireAt, LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
Manager.SetExpirableKeepAwake(expireAt, 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}.");
Logger.LogInfo($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
APIHelper.SetTimedKeepAwake(time, LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
}
private static void LogUnexpectedOrCancelledKeepAwakeThreadCompletion()
{
string? errorMessage = "The keep awake thread was terminated early.";
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
private static void LogCompletedKeepAwakeThread()
{
_log.Info($"Exited keep awake thread successfully.");
Manager.SetTimedKeepAwake(time, displayOn);
}
}
}