diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 508c7f050e..4c03cb2510 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -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 diff --git a/Directory.Packages.props b/Directory.Packages.props index 874dca338a..a75cb8a869 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,7 +25,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -48,7 +48,7 @@ - + diff --git a/NOTICE.md b/NOTICE.md index 8331cf9076..f88a23ac57 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -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 diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 7735d475c7..f7e579c4b4 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -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. diff --git a/installer/PowerToysSetup/generateFileList.ps1 b/installer/PowerToysSetup/generateFileList.ps1 index 7e0d8548a0..5925ebedb2 100644 --- a/installer/PowerToysSetup/generateFileList.ps1 +++ b/installer/PowerToysSetup/generateFileList.ps1 @@ -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") diff --git a/src/common/ManagedCommon/Logger.cs b/src/common/ManagedCommon/Logger.cs index 0d6cc32f2d..ad0819b6c6 100644 --- a/src/common/ManagedCommon/Logger.cs +++ b/src/common/ManagedCommon/Logger.cs @@ -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 diff --git a/src/modules/awake/Awake/Awake.csproj b/src/modules/awake/Awake/Awake.csproj index 34c720a427..fe7e7ab9ee 100644 --- a/src/modules/awake/Awake/Awake.csproj +++ b/src/modules/awake/Awake/Awake.csproj @@ -18,6 +18,7 @@ https://awake.den.dev https://github.com/microsoft/powertoys true + true @@ -51,16 +52,16 @@ 4 + + + + - - all - - @@ -79,16 +80,17 @@ - - - PreserveNewest - - - Always + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/modules/awake/Awake/Core/APIHelper.cs b/src/modules/awake/Awake/Core/APIHelper.cs deleted file mode 100644 index f0f303db1e..0000000000 --- a/src/modules/awake/Awake/Core/APIHelper.cs +++ /dev/null @@ -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 -{ - /// - /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts - /// of the codebase. - /// - 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 }); - } - - /// - /// 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. - /// - /// Single or multiple EXECUTION_STATE entries. - /// true if successful, false if failed - 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 EnumerateWindowsForProcess(int processId) - { - var handles = new List(); - 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 windowHandles = EnumerateWindowsForProcess(Environment.ProcessId); - var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x"); - string targetClass = $"{InternalConstants.TrayWindowId}{domain}"; - - unsafe - { - var classNameLen = 256; - Span 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 GetDefaultTrayOptions() - { - Dictionary optionsList = new Dictionary - { - { "30 minutes", 1800 }, - { "1 hour", 3600 }, - { "2 hours", 7200 }, - }; - return optionsList; - } - } -} diff --git a/src/modules/awake/Awake/Core/InternalConstants.cs b/src/modules/awake/Awake/Core/Constants.cs similarity index 75% rename from src/modules/awake/Awake/Core/InternalConstants.cs rename to src/modules/awake/Awake/Core/Constants.cs index 5a120e6ebb..267197b60e 100644 --- a/src/modules/awake/Awake/Core/InternalConstants.cs +++ b/src/modules/awake/Awake/Core/Constants.cs @@ -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"; } } diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs new file mode 100644 index 0000000000..d46843226a --- /dev/null +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -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); + + /// + /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts + /// of the codebase. + /// + public class Manager + { + private static BlockingCollection _stateQueue; + + private static CancellationTokenSource _tokenSource; + + static Manager() + { + _tokenSource = new CancellationTokenSource(); + _stateQueue = new BlockingCollection(); + } + + 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 }); + } + + /// + /// 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. + /// + /// Single or multiple EXECUTION_STATE entries. + /// true if successful, false if failed + 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 EnumerateWindowsForProcess(int processId) + { + var handles = new List(); + 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 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 GetDefaultTrayOptions() + { + Dictionary optionsList = new Dictionary + { + { "30 minutes", 1800 }, + { "1 hour", 3600 }, + { "2 hours", 7200 }, + }; + return optionsList; + } + } +} diff --git a/src/modules/awake/Awake/Core/Models/BatteryReportingScale.cs b/src/modules/awake/Awake/Core/Models/BatteryReportingScale.cs new file mode 100644 index 0000000000..1520662e47 --- /dev/null +++ b/src/modules/awake/Awake/Core/Models/BatteryReportingScale.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Awake.Core.Models +{ + public struct BatteryReportingScale + { + public uint Granularity; + public uint Capacity; + } +} diff --git a/src/modules/awake/Awake/Core/Models/ControlType.cs b/src/modules/awake/Awake/Core/Models/ControlType.cs new file mode 100644 index 0000000000..7b82c7c285 --- /dev/null +++ b/src/modules/awake/Awake/Core/Models/ControlType.cs @@ -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 +{ + /// + /// The type of control signal received by the handler. + /// + /// + /// See HandlerRoutine callback function. + /// + public enum ControlType + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6, + } +} diff --git a/src/modules/awake/Awake/Core/Models/ExecutionState.cs b/src/modules/awake/Awake/Core/Models/ExecutionState.cs new file mode 100644 index 0000000000..3c5ab849f1 --- /dev/null +++ b/src/modules/awake/Awake/Core/Models/ExecutionState.cs @@ -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, + } +} diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs new file mode 100644 index 0000000000..c803941130 --- /dev/null +++ b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs @@ -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; + } +} diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerState.cs b/src/modules/awake/Awake/Core/Models/SystemPowerState.cs new file mode 100644 index 0000000000..c56036602d --- /dev/null +++ b/src/modules/awake/Awake/Core/Models/SystemPowerState.cs @@ -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 +{ + /// + /// Represents the system power state. + /// + /// + /// See System power states. + /// + public enum SystemPowerState + { + PowerSystemUnspecified = 0, + PowerSystemWorking = 1, + PowerSystemSleeping1 = 2, + PowerSystemSleeping2 = 3, + PowerSystemSleeping3 = 4, + PowerSystemHibernate = 5, + PowerSystemShutdown = 6, + PowerSystemMaximum = 7, + } +} diff --git a/src/modules/awake/Awake/Core/Models/TrayCommands.cs b/src/modules/awake/Awake/Core/Models/TrayCommands.cs index 041db7834e..7a266a93ea 100644 --- a/src/modules/awake/Awake/Core/Models/TrayCommands.cs +++ b/src/modules/awake/Awake/Core/Models/TrayCommands.cs @@ -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, } } diff --git a/src/modules/awake/Awake/Core/Native/Bridge.cs b/src/modules/awake/Awake/Core/Native/Bridge.cs new file mode 100644 index 0000000000..27eb5578d5 --- /dev/null +++ b/src/modules/awake/Awake/Core/Native/Bridge.cs @@ -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); + } +} diff --git a/src/modules/awake/Awake/Core/Native/Constants.cs b/src/modules/awake/Awake/Core/Native/Constants.cs new file mode 100644 index 0000000000..2c512864f6 --- /dev/null +++ b/src/modules/awake/Awake/Core/Native/Constants.cs @@ -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; + } +} diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 25f08b7855..110d623d75 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -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 /// 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 /// MouseEventArgs instance containing mouse click event information. 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 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); diff --git a/src/modules/awake/Awake/Core/TrayMessageFilter.cs b/src/modules/awake/Awake/Core/TrayMessageFilter.cs index f7e86d71fa..4745e26169 100644 --- a/src/modules/awake/Awake/Core/TrayMessageFilter.cs +++ b/src/modules/awake/Awake/Core/TrayMessageFilter.cs @@ -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(InternalConstants.AppName); + AwakeSettings settings = ModuleSettings!.GetSettings(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(moduleName); + currentSettings = ModuleSettings!.GetSettings(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(moduleName); + currentSettings = ModuleSettings!.GetSettings(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(moduleName); + currentSettings = ModuleSettings!.GetSettings(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(moduleName); + currentSettings = ModuleSettings!.GetSettings(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); } } } diff --git a/src/modules/awake/Awake/NLog.config b/src/modules/awake/Awake/NLog.config deleted file mode 100644 index aaa21c75c5..0000000000 --- a/src/modules/awake/Awake/NLog.config +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/modules/awake/Awake/NativeMethods.txt b/src/modules/awake/Awake/NativeMethods.txt deleted file mode 100644 index a39796d7b5..0000000000 --- a/src/modules/awake/Awake/NativeMethods.txt +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index b2f1244505..4642d4f101 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -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 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(() => false) - { - Arity = ArgumentArity.ZeroOrOne, - }, - Required = false, + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, }; Option displayOption = new( @@ -116,11 +99,8 @@ namespace Awake getDefaultValue: () => true, description: "Determines whether the display should be kept awake.") { - Argument = new Argument(() => false) - { - Arity = ArgumentArity.ZeroOrOne, - }, - Required = false, + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, }; Option 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(() => 0) - { - Arity = ArgumentArity.ExactlyOne, - }, - Required = false, + Arity = ArgumentArity.ExactlyOne, + IsRequired = false, }; Option 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(() => 0) - { - Arity = ArgumentArity.ZeroOrOne, - }, - Required = false, + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, }; Option 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.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(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(InternalConstants.AppName); + AwakeSettings settings = _settingsUtils!.GetSettings(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); } } }