From f8c488331ed72ea213b568b8763be275ac24a749 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Thu, 15 Aug 2024 15:57:08 -0700 Subject: [PATCH 01/51] Update with bug fixes for tray icon and support for parent process --- doc/planning/awake.md | 17 ++++--- src/modules/awake/Awake/Core/Constants.cs | 2 +- src/modules/awake/Awake/Core/Manager.cs | 36 ++++++++++--- src/modules/awake/Awake/Core/Native/Bridge.cs | 17 ++++--- src/modules/awake/Awake/Core/TrayHelper.cs | 17 ++++--- src/modules/awake/Awake/Program.cs | 50 ++++++++++++++----- .../Awake/Properties/Resources.Designer.cs | 9 ++++ .../awake/Awake/Properties/Resources.resx | 3 ++ 8 files changed, 111 insertions(+), 40 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index cfbbbefac9fb..713a52ee1fac 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -10,12 +10,17 @@ The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it 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 | -|:----------------------------------------------------------|:-----------------| -| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | -| [`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 | +| Build ID | Build Date | +|:-------------------------------------------------------------------|:----------------| +| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 | +| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | +| [`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 | + +### `VISEGRADRELAY_08152024` (August 15, 2024) + +- Amending the native API surface to make sure that the Win32 error is set correctly. ### `DAISY023_04102024` (April 10, 2024) diff --git a/src/modules/awake/Awake/Core/Constants.cs b/src/modules/awake/Awake/Core/Constants.cs index 80a75dc1eda6..db3d20d745cd 100644 --- a/src/modules/awake/Awake/Core/Constants.cs +++ b/src/modules/awake/Awake/Core/Constants.cs @@ -17,6 +17,6 @@ internal static class Constants // 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. - internal const string BuildId = "DAISY023_04102024"; + internal const string BuildId = "VISEGRADRELAY_08152024"; } } diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index c5217dcdba31..f23725063567 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Reactive.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Threading; @@ -62,7 +64,7 @@ internal static void StartMonitor() { Thread monitorThread = new(() => { - Thread.CurrentThread.IsBackground = true; + Thread.CurrentThread.IsBackground = false; while (true) { ExecutionState state = _stateQueue.Take(); @@ -126,10 +128,18 @@ internal static void CancelExistingThread() _stateQueue.Add(ExecutionState.ES_CONTINUOUS); // Next, make sure that any existing background threads are terminated. - _tokenSource.Cancel(); - _tokenSource.Dispose(); + if (_tokenSource != null) + { + _tokenSource.Cancel(); + _tokenSource.Dispose(); + + _tokenSource = new CancellationTokenSource(); + } + else + { + Logger.LogWarning("The token source was null."); + } - _tokenSource = new CancellationTokenSource(); Logger.LogInfo("Instantiating of new token source and thread token completed."); } @@ -137,12 +147,11 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false) { PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); - CancelExistingThread(); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); + CancelExistingThread(); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); - if (IsUsingPowerToysConfig) { try @@ -417,5 +426,18 @@ internal static void SetDisplay() } } } + + public static Process? GetParentProcess() + { + return GetParentProcess(Process.GetCurrentProcess().Handle); + } + + private static Process? GetParentProcess(IntPtr handle) + { + ProcessBasicInformation pbi = default; + int status = Bridge.NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(), out _); + + return status != 0 ? null : Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32()); + } } } diff --git a/src/modules/awake/Awake/Core/Native/Bridge.cs b/src/modules/awake/Awake/Core/Native/Bridge.cs index 2fc2864dec32..4be81db855b0 100644 --- a/src/modules/awake/Awake/Core/Native/Bridge.cs +++ b/src/modules/awake/Awake/Core/Native/Bridge.cs @@ -56,13 +56,13 @@ internal static extern IntPtr CreateFile( [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DestroyMenu(IntPtr hMenu); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] internal static extern bool DestroyWindow(IntPtr hWnd); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] internal static extern void PostQuitMessage(int nExitCode); - [DllImport("shell32.dll")] + [DllImport("shell32.dll", SetLastError = true)] internal static extern bool Shell_NotifyIcon(int dwMessage, ref NotifyIconData pnid); [DllImport("user32.dll", SetLastError = true)] @@ -83,14 +83,14 @@ internal static extern IntPtr CreateFile( [DllImport("user32.dll", SetLastError = true)] internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetCursorPos(out Point lpPoint); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll", SetLastError = true)] @@ -100,7 +100,10 @@ internal static extern IntPtr CreateFile( [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetMenuInfo(IntPtr hMenu, ref MenuInfo lpcmi); - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] internal static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("ntdll.dll")] + internal static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength); } } diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 00000a431f97..029df797d2f7 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -43,11 +43,6 @@ static TrayHelper() HiddenWindowHandle = IntPtr.Zero; } - public static void InitializeTray(string text, Icon icon) - { - CreateHiddenWindow(icon, text); - } - private static void ShowContextMenu(IntPtr hWnd) { if (TrayMenu != IntPtr.Zero) @@ -88,7 +83,7 @@ private static void ShowContextMenu(IntPtr hWnd) } } - private static void CreateHiddenWindow(Icon icon, string text) + public static void InitializeTray(Icon icon, string text) { IntPtr hWnd = IntPtr.Zero; @@ -143,7 +138,13 @@ private static void CreateHiddenWindow(Icon icon, string text) Bridge.UpdateWindow(hWnd); SetShellIcon(hWnd, text, icon); + }); + }).Wait(); + Task.Run(() => + { + RunOnMainThread(() => + { RunMessageLoop(); }); }); @@ -151,6 +152,8 @@ private static void CreateHiddenWindow(Icon icon, string text) internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add) { + Logger.LogInfo($"Setting the shell icon.\nText: {text}\nAction: {action}"); + int message = Native.Constants.NIM_ADD; switch (action) @@ -168,6 +171,8 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon if (action == TrayIconAction.Add || action == TrayIconAction.Update) { + Logger.LogInfo($"Adding or updating tray icon. HIcon handle is {icon?.Handle}\nHWnd: {hWnd}"); + _notifyIconData = new NotifyIconData { CbSize = Marshal.SizeOf(typeof(NotifyIconData)), diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 229ad84efc60..d363fbddf72a 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -44,6 +44,7 @@ internal sealed class Program internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"]; internal static readonly string[] AliasesPidOption = ["--pid", "-p"]; internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"]; + internal static readonly string[] AliasesParentPidOption = ["--use-parent-pid", "-u"]; private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")); @@ -116,6 +117,12 @@ private static int Main(string[] args) IsRequired = false, }; + var parentPidOption = new Option(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; + RootCommand? rootCommand = [ configOption, @@ -123,10 +130,11 @@ private static int Main(string[] args) timeOption, pidOption, expireAtOption, + parentPidOption, ]; rootCommand.Description = Core.Constants.AppName; - rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption); + rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption); return rootCommand.InvokeAsync(args).Result; } @@ -153,9 +161,9 @@ private static void Exit(string message, int exitCode) Manager.CompleteExit(exitCode); } - private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt) + private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid) { - if (pid == 0) + if (pid == 0 && !useParentPid) { Logger.LogInfo("No PID specified. Allocating console..."); Manager.AllocateConsole(); @@ -175,11 +183,12 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool 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}"); + Logger.LogInfo($"The value for --use-parent-pid is: {useParentPid}"); // Start the monitor thread that will be used to track the current state. Manager.StartMonitor(); - TrayHelper.InitializeTray(Core.Constants.FullAppName, _defaultAwakeIcon); + TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent()); new Thread(() => @@ -215,6 +224,30 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, Logger.LogError($"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}"); } } + else if (pid != 0 || useParentPid) + { + // Second, we snap to process-based execution. Because this is something that + // is snapped to a running entity, we only want to enable the ability to set + // indefinite keep-awake with the display settings that the user wants to set. + int targetPid = pid != 0 ? pid : useParentPid ? Manager.GetParentProcess()?.Id ?? 0 : 0; + + if (targetPid != 0) + { + Logger.LogInfo($"Bound to target process: {targetPid}"); + + Manager.SetIndefiniteKeepAwake(displayOn); + + RunnerHelper.WaitForPowerToysRunner(targetPid, () => + { + Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}."); + Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0); + }); + } + else + { + Logger.LogError("Not binding to any process."); + } + } else { // Date-based binding takes precedence over timed configuration, so we want to @@ -247,15 +280,6 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, } } } - - if (pid != 0) - { - RunnerHelper.WaitForPowerToysRunner(pid, () => - { - Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}."); - Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0); - }); - } } private static void ScaffoldConfiguration(string settingsPath) diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs index 0cdaf23605cd..be22d149d21f 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -114,6 +114,15 @@ internal static string AWAKE_CMD_HELP_TIME_OPTION { } } + /// + /// Looks up a localized string similar to Uses the parent process as the bound target - once the process terminates, Awake stops.. + /// + internal static string AWAKE_CMD_PARENT_PID_OPTION { + get { + return ResourceManager.GetString("AWAKE_CMD_PARENT_PID_OPTION", resourceCulture); + } + } + /// /// Looks up a localized string similar to Exit. /// diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx index 7ef19501287c..8bafe1d4db00 100644 --- a/src/modules/awake/Awake/Properties/Resources.resx +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -205,4 +205,7 @@ s Used to display number of seconds in the system tray tooltip. + + Uses the parent process as the bound target - once the process terminates, Awake stops. + \ No newline at end of file From e9f8c23e187746b9c8c0d041b8ef378041b59996 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Thu, 15 Aug 2024 15:57:43 -0700 Subject: [PATCH 02/51] Process information enum --- .../Core/Models/ProcessBasicInformation.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/modules/awake/Awake/Core/Models/ProcessBasicInformation.cs diff --git a/src/modules/awake/Awake/Core/Models/ProcessBasicInformation.cs b/src/modules/awake/Awake/Core/Models/ProcessBasicInformation.cs new file mode 100644 index 000000000000..98d1c4a5b4cd --- /dev/null +++ b/src/modules/awake/Awake/Core/Models/ProcessBasicInformation.cs @@ -0,0 +1,20 @@ +// 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.Runtime.InteropServices; + +namespace Awake.Core.Models +{ + [StructLayout(LayoutKind.Sequential)] + internal struct ProcessBasicInformation + { + public IntPtr ExitStatus; + public IntPtr PebAddress; + public IntPtr AffinityMask; + public IntPtr BasePriority; + public IntPtr UniquePID; + public IntPtr InheritedFromUniqueProcessId; + } +} From bd07382302a17c86b42334d1740dd0e9ad26800d Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Thu, 15 Aug 2024 16:10:21 -0700 Subject: [PATCH 03/51] Update the docs --- doc/planning/awake.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 713a52ee1fac..1f42eaca8381 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -20,6 +20,12 @@ The build ID moniker is made up of two components - a reference to a [Halo](http ### `VISEGRADRELAY_08152024` (August 15, 2024) +>[!NOTE] +>See pull request: [Awake - `VISEGRADRELAY_08152024`](https://github.com/microsoft/PowerToys/pull/34316) + +- [#34148](https://github.com/microsoft/PowerToys/issues/34148) Fixes the issue where the Awake icon is not displayed. +- [#17969](https://github.com/microsoft/PowerToys/issues/17969) Add the ability to bind the process target to the parent of the Awake launcher. +- PID binding now correctly ignores irrelevant parameters (e.g., expiration, interval) and only works for indefinite periods. - Amending the native API surface to make sure that the Win32 error is set correctly. ### `DAISY023_04102024` (April 10, 2024) From 8c947952d352661fe323bc866d9f148c2ba528bd Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Thu, 15 Aug 2024 16:59:11 -0700 Subject: [PATCH 04/51] Fix spelling --- .github/actions/spell-check/excludes.txt | 1 + .github/actions/spell-check/expect.txt | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index 79b69758a96c..fd89d668a2c1 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -117,6 +117,7 @@ ^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$ ^\Qtools/project_template/ModuleTemplate/resource.h\E$ ^doc/devdocs/akaLinks\.md$ +^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/ ^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$ ^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$ ^src/modules/MouseWithoutBorders/App/Form/.*\.resx$ diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index c5c093108207..2bf5ff9492d3 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -189,7 +189,6 @@ CLIPBOARDUPDATE CLIPCHILDREN CLIPSIBLINGS closesocket -clrcall CLSCTX Clusion cmder @@ -200,8 +199,8 @@ CMINVOKECOMMANDINFO CMINVOKECOMMANDINFOEX CMock CMONITORS -cmph cmpgt +cmph cne CNF coclass @@ -247,10 +246,7 @@ countof cph CPower cppblog -cppruntime -cppstd cppwinrt -CProj createdump CREATESCHEDULEDTASK CREATESTRUCT @@ -605,7 +601,6 @@ hmenu hmodule hmonitor homljgmgpmcbpjbnjpfijnhipfkiclkd -HOOKPROC Hostbackdropbrush hotkeycontrol hotkeys @@ -673,7 +668,6 @@ imageresizerinput imageresizersettings imagingdevices ime -imperialounce inetcpl Infobar INFOEXAMPLE @@ -934,7 +928,6 @@ MRT mru mrw msc -msclr mscorlib msdata msedge @@ -1114,6 +1107,7 @@ PATINVERT PATPAINT PAUDIO pbc +pbi PBlob pcb pcch @@ -1127,6 +1121,7 @@ pdo pdto pdtobj pdw +Peb pef PElems Pels @@ -1506,7 +1501,6 @@ STATICEDGE STATSTG stdafx STDAPI -stdcpp stdcpplatest STDMETHODCALLTYPE STDMETHODIMP @@ -1674,7 +1668,6 @@ USERDATA Userenv USESHOWWINDOW USESTDHANDLES -usounce USRDLL UType uuidv @@ -1711,6 +1704,7 @@ VIDEOINFOHEADER viewmodel vih VIRTUALDESK +VISEGRADRELAY visiblecolorformats Visibletrue visualeffects From 708ef511ab593344f270f9f0f282eee664043197 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Mon, 19 Aug 2024 14:21:07 -0700 Subject: [PATCH 05/51] Make sure that PID is used in PT config flow --- src/modules/awake/Awake/Program.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index d363fbddf72a..03c7d9d6bccd 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -218,6 +218,17 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, } ScaffoldConfiguration(settingsPath); + + if (pid != 0) + { + Logger.LogInfo($"Bound to target process while also using PowerToys settings: {pid}"); + + RunnerHelper.WaitForPowerToysRunner(pid, () => + { + Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}."); + Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0); + }); + } } catch (Exception ex) { From b58398bc075a52642f3ca5e37361293a6b073822 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Sun, 8 Sep 2024 11:48:18 -0700 Subject: [PATCH 06/51] Logic for checks based on #34148 --- doc/planning/awake.md | 19 ++-- src/modules/awake/Awake/Core/Constants.cs | 2 +- src/modules/awake/Awake/Program.cs | 121 ++++++++++++---------- 3 files changed, 77 insertions(+), 65 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 1f42eaca8381..650cc203f578 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -10,13 +10,18 @@ The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it 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 | -|:-------------------------------------------------------------------|:----------------| -| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 | -| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | -| [`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 | +| Build ID | Build Date | +|:-------------------------------------------------------------------|:------------------| +| [`PROMETHEAN_09082024`](#PROMETHEAN_09082024-september-8-2024) | September 8, 2024 | +| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 | +| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | +| [`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 | + +### `PROMETHEAN_09082024` (September 8, 2024) + +- Updating the initialization logic to make sure that settings are respected for proper group policy and single-instance detection. ### `VISEGRADRELAY_08152024` (August 15, 2024) diff --git a/src/modules/awake/Awake/Core/Constants.cs b/src/modules/awake/Awake/Core/Constants.cs index db3d20d745cd..1bd6f0a04360 100644 --- a/src/modules/awake/Awake/Core/Constants.cs +++ b/src/modules/awake/Awake/Core/Constants.cs @@ -17,6 +17,6 @@ internal static class Constants // 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. - internal const string BuildId = "VISEGRADRELAY_08152024"; + internal const string BuildId = "PROMETHEAN_09082024"; } } diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 03c7d9d6bccd..8b01fcb83486 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -60,72 +60,77 @@ private static int Main(string[] args) if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); - return 0; + return 1; } - - if (!instantiated) + else { - Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); - } - - Logger.LogInfo($"Launching {Core.Constants.AppName}..."); - Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); - Logger.LogInfo($"Build: {Core.Constants.BuildId}"); - Logger.LogInfo($"OS: {Environment.OSVersion}"); - Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}"); + if (!instantiated) + { + // Awake is already running - there is no need for us to process + // anything further. + Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); + return 1; + } + else + { + Logger.LogInfo($"Launching {Core.Constants.AppName}..."); + Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); + Logger.LogInfo($"Build: {Core.Constants.BuildId}"); + Logger.LogInfo($"OS: {Environment.OSVersion}"); + Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}"); - TaskScheduler.UnobservedTaskException += (sender, args) => - { - Trace.WriteLine($"Task scheduler error: {args.Exception.Message}"); // somebody forgot to check! - args.SetObserved(); - }; + TaskScheduler.UnobservedTaskException += (sender, args) => + { + Trace.WriteLine($"Task scheduler error: {args.Exception.Message}"); // somebody forgot to check! + args.SetObserved(); + }; - // To make it easier to diagnose future issues, let's get the - // system power capabilities and aggregate them in the log. - Bridge.GetPwrCapabilities(out _powerCapabilities); - Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities)); + // To make it easier to diagnose future issues, let's get the + // system power capabilities and aggregate them in the log. + Bridge.GetPwrCapabilities(out _powerCapabilities); + Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities)); - Logger.LogInfo("Parsing parameters..."); + Logger.LogInfo("Parsing parameters..."); - var configOption = new Option(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var configOption = new Option(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var displayOption = new Option(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var displayOption = new Option(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var timeOption = new Option(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) - { - Arity = ArgumentArity.ExactlyOne, - IsRequired = false, - }; + var timeOption = new Option(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) + { + Arity = ArgumentArity.ExactlyOne, + IsRequired = false, + }; - var pidOption = new Option(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var pidOption = new Option(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var expireAtOption = new Option(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var expireAtOption = new Option(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var parentPidOption = new Option(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var parentPidOption = new Option(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - RootCommand? rootCommand = - [ - configOption, + RootCommand? rootCommand = + [ + configOption, displayOption, timeOption, pidOption, @@ -133,10 +138,12 @@ private static int Main(string[] args) parentPidOption, ]; - rootCommand.Description = Core.Constants.AppName; - rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption); + rootCommand.Description = Core.Constants.AppName; + rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption); - return rootCommand.InvokeAsync(args).Result; + return rootCommand.InvokeAsync(args).Result; + } + } } private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e) From 524c19a6228b3e54d652c7d08a1ab98fe3498871 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Sun, 8 Sep 2024 11:51:08 -0700 Subject: [PATCH 07/51] Update with link to PR --- doc/planning/awake.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 650cc203f578..0566cd86dfbc 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -21,6 +21,9 @@ The build ID moniker is made up of two components - a reference to a [Halo](http ### `PROMETHEAN_09082024` (September 8, 2024) +>[!NOTE] +>See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/34717) + - Updating the initialization logic to make sure that settings are respected for proper group policy and single-instance detection. ### `VISEGRADRELAY_08152024` (August 15, 2024) From 104120374a61919408458c61f96ac1c275e28fa3 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Sun, 8 Sep 2024 12:06:37 -0700 Subject: [PATCH 08/51] Fixes #34717 --- src/modules/awake/Awake/Program.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 8b01fcb83486..7b60ab194f54 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -122,7 +122,7 @@ private static int Main(string[] args) IsRequired = false, }; - var parentPidOption = new Option(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION) + var parentPidOption = new Option(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, @@ -131,12 +131,12 @@ private static int Main(string[] args) RootCommand? rootCommand = [ configOption, - displayOption, - timeOption, - pidOption, - expireAtOption, - parentPidOption, - ]; + displayOption, + timeOption, + pidOption, + expireAtOption, + parentPidOption, + ]; rootCommand.Description = Core.Constants.AppName; rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption); From 75125f25be9702af61e0c9b855499bffe9da0b63 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Sun, 8 Sep 2024 12:41:13 -0700 Subject: [PATCH 09/51] Small cleanup --- src/modules/awake/Awake/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 7b60ab194f54..53e3e69e7595 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -52,9 +52,7 @@ private static int Main(string[] args) { _settingsUtils = new SettingsUtils(); LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated); - Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); - AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) @@ -182,6 +180,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, } else { + Logger.LogInfo("Starting with PID binding."); _startedFromPowerToys = true; } From 14299bae24ee2367574b79ec344e54264b69ab99 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Sun, 8 Sep 2024 12:59:21 -0700 Subject: [PATCH 10/51] Proper task segmentation in a function --- src/modules/awake/Awake/Program.cs | 62 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 53e3e69e7595..a5453d5ce379 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -303,34 +303,8 @@ private static void ScaffoldConfiguration(string settingsPath) { try { - var directory = Path.GetDirectoryName(settingsPath)!; - var fileName = Path.GetFileName(settingsPath); - - _watcher = new FileSystemWatcher - { - Path = directory, - EnableRaisingEvents = true, - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime, - Filter = fileName, - }; - - var mergedObservable = Observable.Merge( - Observable.FromEventPattern( - h => _watcher.Changed += h, - h => _watcher.Changed -= h), - Observable.FromEventPattern( - h => _watcher.Created += h, - h => _watcher.Created -= h)); - - mergedObservable - .Throttle(TimeSpan.FromMilliseconds(25)) - .SubscribeOn(TaskPoolScheduler.Default) - .Select(e => e.EventArgs) - .Subscribe(HandleAwakeConfigChange); - - var settings = Manager.ModuleSettings!.GetSettings(Core.Constants.AppName) ?? new AwakeSettings(); - TrayHelper.SetTray(settings, _startedFromPowerToys); - + SetupFileSystemWatcher(settingsPath); + InitializeSettings(); ProcessSettings(); } catch (Exception ex) @@ -339,6 +313,38 @@ private static void ScaffoldConfiguration(string settingsPath) } } + private static void SetupFileSystemWatcher(string settingsPath) + { + var directory = Path.GetDirectoryName(settingsPath)!; + var fileName = Path.GetFileName(settingsPath); + + _watcher = new FileSystemWatcher + { + Path = directory, + EnableRaisingEvents = true, + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime, + Filter = fileName, + }; + + Observable.Merge( + Observable.FromEventPattern( + h => _watcher.Changed += h, + h => _watcher.Changed -= h), + Observable.FromEventPattern( + h => _watcher.Created += h, + h => _watcher.Created -= h)) + .Throttle(TimeSpan.FromMilliseconds(25)) + .SubscribeOn(TaskPoolScheduler.Default) + .Select(e => e.EventArgs) + .Subscribe(HandleAwakeConfigChange); + } + + private static void InitializeSettings() + { + var settings = Manager.ModuleSettings?.GetSettings(Core.Constants.AppName) ?? new AwakeSettings(); + TrayHelper.SetTray(settings, _startedFromPowerToys); + } + private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent) { try From 3d66e1f7d11ff3ac78622eaa7e59a9aca983ecff Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Sun, 8 Sep 2024 18:30:17 -0700 Subject: [PATCH 11/51] Cleanup the code --- src/modules/awake/Awake/Core/TrayHelper.cs | 153 ++++++++++----------- src/modules/awake/Awake/Program.cs | 37 +++-- 2 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 029df797d2f7..816f8d513c1d 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; @@ -28,13 +29,11 @@ namespace Awake.Core internal static class TrayHelper { private static NotifyIconData _notifyIconData; - private static IntPtr _trayMenu; + private static IntPtr _hiddenWindowHandle; private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; } - private static IntPtr _hiddenWindowHandle; - internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; } static TrayHelper() @@ -45,42 +44,35 @@ static TrayHelper() private static void ShowContextMenu(IntPtr hWnd) { - if (TrayMenu != IntPtr.Zero) + if (TrayMenu == IntPtr.Zero) { - Bridge.SetForegroundWindow(hWnd); - - // Get the handle to the context menu associated with the tray icon - IntPtr hMenu = TrayMenu; + Logger.LogError("Tried to create a context menu while the TrayMenu object is a null pointer. Normal when used in standalone mode."); + return; + } - // Get the current cursor position - Bridge.GetCursorPos(out Models.Point cursorPos); + Bridge.SetForegroundWindow(hWnd); - Bridge.ScreenToClient(hWnd, ref cursorPos); + // Get cursor position and convert it to client coordinates + Bridge.GetCursorPos(out Models.Point cursorPos); + Bridge.ScreenToClient(hWnd, ref cursorPos); - MenuInfo menuInfo = new() - { - CbSize = (uint)Marshal.SizeOf(typeof(MenuInfo)), - FMask = Native.Constants.MIM_STYLE, - DwStyle = Native.Constants.MNS_AUTO_DISMISS, - }; - Bridge.SetMenuInfo(hMenu, ref menuInfo); - - // Display the context menu at the cursor position - Bridge.TrackPopupMenuEx( - hMenu, - Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON, - cursorPos.X, - cursorPos.Y, - hWnd, - IntPtr.Zero); - } - else + // Set menu information + var menuInfo = new MenuInfo { - // Tray menu was not initialized. Log the issue. - // This is normal when operating in "standalone mode" - that is, detached - // from the PowerToys configuration file. - Logger.LogError("Tried to create a context menu while the TrayMenu object is a null pointer. Normal when used in standalone mode."); - } + CbSize = (uint)Marshal.SizeOf(), + FMask = Native.Constants.MIM_STYLE, + DwStyle = Native.Constants.MNS_AUTO_DISMISS, + }; + Bridge.SetMenuInfo(TrayMenu, ref menuInfo); + + // Display the context menu at the cursor position + Bridge.TrackPopupMenuEx( + TrayMenu, + Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON, + cursorPos.X, + cursorPos.Y, + hWnd, + IntPtr.Zero); } public static void InitializeTray(Icon icon, string text) @@ -152,58 +144,63 @@ public static void InitializeTray(Icon icon, string text) internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add) { - Logger.LogInfo($"Setting the shell icon.\nText: {text}\nAction: {action}"); - - int message = Native.Constants.NIM_ADD; - - switch (action) + if (hWnd != IntPtr.Zero && icon != null) { - case TrayIconAction.Update: - message = Native.Constants.NIM_MODIFY; - break; - case TrayIconAction.Delete: - message = Native.Constants.NIM_DELETE; - break; - case TrayIconAction.Add: - default: - break; - } + int message = Native.Constants.NIM_ADD; - if (action == TrayIconAction.Add || action == TrayIconAction.Update) - { - Logger.LogInfo($"Adding or updating tray icon. HIcon handle is {icon?.Handle}\nHWnd: {hWnd}"); + switch (action) + { + case TrayIconAction.Update: + message = Native.Constants.NIM_MODIFY; + break; + case TrayIconAction.Delete: + message = Native.Constants.NIM_DELETE; + break; + case TrayIconAction.Add: + default: + break; + } - _notifyIconData = new NotifyIconData + if (action == TrayIconAction.Add || action == TrayIconAction.Update) { - CbSize = Marshal.SizeOf(typeof(NotifyIconData)), - HWnd = hWnd, - UId = 1000, - UFlags = Native.Constants.NIF_ICON | Native.Constants.NIF_TIP | Native.Constants.NIF_MESSAGE, - UCallbackMessage = (int)Native.Constants.WM_USER, - HIcon = icon?.Handle ?? IntPtr.Zero, - SzTip = text, - }; - } - else if (action == TrayIconAction.Delete) - { - _notifyIconData = new NotifyIconData + _notifyIconData = new NotifyIconData + { + CbSize = Marshal.SizeOf(typeof(NotifyIconData)), + HWnd = hWnd, + UId = 1000, + UFlags = Native.Constants.NIF_ICON | Native.Constants.NIF_TIP | Native.Constants.NIF_MESSAGE, + UCallbackMessage = (int)Native.Constants.WM_USER, + HIcon = icon?.Handle ?? IntPtr.Zero, + SzTip = text, + }; + } + else if (action == TrayIconAction.Delete) { - CbSize = Marshal.SizeOf(typeof(NotifyIconData)), - HWnd = hWnd, - UId = 1000, - UFlags = 0, - }; - } + _notifyIconData = new NotifyIconData + { + CbSize = Marshal.SizeOf(typeof(NotifyIconData)), + HWnd = hWnd, + UId = 1000, + UFlags = 0, + }; + } - if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData)) - { - int errorCode = Marshal.GetLastWin32Error(); - throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}"); - } + if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData)) + { + int errorCode = Marshal.GetLastWin32Error(); + Logger.LogInfo($"Could not set the shell icon. Action: {action} and error code: {errorCode}. HIcon handle is {icon?.Handle} and HWnd is {hWnd}"); + + throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}"); + } - if (action == TrayIconAction.Delete) + if (action == TrayIconAction.Delete) + { + _notifyIconData = default; + } + } + else { - _notifyIconData = default; + Logger.LogInfo($"Cannot set the shell icon - parent window handle is zero or icon is not available. Text: {text} Action: {action}"); } } diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index a5453d5ce379..c9fdfbf1175f 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -55,18 +55,18 @@ private static int Main(string[] args) Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; - if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + if (!instantiated) { - Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); + // Awake is already running - there is no need for us to process + // anything further + Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); return 1; } else { - if (!instantiated) + if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { - // Awake is already running - there is no need for us to process - // anything further. - Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); + Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); return 1; } else @@ -171,12 +171,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, if (pid == 0 && !useParentPid) { Logger.LogInfo("No PID specified. Allocating console..."); - Manager.AllocateConsole(); - - _handler += new ConsoleEventHandler(ExitHandler); - Manager.SetConsoleControlHandler(_handler, true); - - Trace.Listeners.Add(new ConsoleTraceListener()); + AllocateLocalConsole(); } else { @@ -238,7 +233,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, } catch (Exception ex) { - Logger.LogError($"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}"); + Logger.LogError($"There was a problem with the configuration file. Make sure it exists. {ex.Message}"); } } else if (pid != 0 || useParentPid) @@ -299,6 +294,16 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, } } + private static void AllocateLocalConsole() + { + Manager.AllocateConsole(); + + _handler += new ConsoleEventHandler(ExitHandler); + Manager.SetConsoleControlHandler(_handler, true); + + Trace.Listeners.Add(new ConsoleTraceListener()); + } + private static void ScaffoldConfiguration(string settingsPath) { try @@ -362,7 +367,9 @@ private static void ProcessSettings() { try { - var settings = _settingsUtils!.GetSettings(Core.Constants.AppName) ?? throw new InvalidOperationException("Settings are null."); + var settings = _settingsUtils!.GetSettings(Core.Constants.AppName) + ?? throw new InvalidOperationException("Settings are null."); + Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}"); switch (settings.Properties.Mode) @@ -376,7 +383,7 @@ private static void ProcessSettings() break; case AwakeMode.TIMED: - uint computedTime = (settings.Properties.IntervalHours * 60 * 60) + (settings.Properties.IntervalMinutes * 60); + uint computedTime = (settings.Properties.IntervalHours * 3600) + (settings.Properties.IntervalMinutes * 60); Manager.SetTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn); break; From de3c047b79491e2115085afd4615d8c98427dcee Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 25 Sep 2024 21:13:26 -0700 Subject: [PATCH 12/51] Fix synchronization context issue --- .../SingleThreadSynchronizationContext.cs | 24 ++++---- src/modules/awake/Awake/Core/TrayHelper.cs | 59 +++++++++---------- 2 files changed, 43 insertions(+), 40 deletions(-) rename src/modules/awake/Awake/Core/{Models => Threading}/SingleThreadSynchronizationContext.cs (61%) diff --git a/src/modules/awake/Awake/Core/Models/SingleThreadSynchronizationContext.cs b/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs similarity index 61% rename from src/modules/awake/Awake/Core/Models/SingleThreadSynchronizationContext.cs rename to src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs index e2ef3f92c41c..4b432320d906 100644 --- a/src/modules/awake/Awake/Core/Models/SingleThreadSynchronizationContext.cs +++ b/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs @@ -6,17 +6,16 @@ using System.Collections.Generic; using System.Threading; -namespace Awake.Core.Models +namespace Awake.Core.Threading { internal sealed class SingleThreadSynchronizationContext : SynchronizationContext { - private readonly Queue> queue = - new(); + private readonly Queue?> queue = new(); -#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). - public override void Post(SendOrPostCallback d, object state) -#pragma warning restore CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). + public override void Post(SendOrPostCallback d, object? state) { + ArgumentNullException.ThrowIfNull(d); + lock (queue) { queue.Enqueue(Tuple.Create(d, state)); @@ -28,7 +27,7 @@ public void BeginMessageLoop() { while (true) { - Tuple work; + Tuple? work; lock (queue) { while (queue.Count == 0) @@ -44,7 +43,14 @@ public void BeginMessageLoop() break; } - work.Item1(work.Item2); + try + { + work.Item1(work.Item2); + } + catch (Exception e) + { + Console.WriteLine("Error during execution: " + e.Message); + } } } @@ -52,9 +58,7 @@ public void EndMessageLoop() { lock (queue) { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. queue.Enqueue(null); // Signal the end of the message loop -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. Monitor.Pulse(queue); } } diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 34c9b83226f4..0545f97a8f65 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -5,15 +5,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; - using Awake.Core.Models; using Awake.Core.Native; +using Awake.Core.Threading; using Awake.Properties; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; @@ -32,6 +30,8 @@ internal static class TrayHelper private static NotifyIconData _notifyIconData; private static IntPtr _trayMenu; private static IntPtr _hiddenWindowHandle; + private static SingleThreadSynchronizationContext? _syncContext; + private static Thread? _mainThread; private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; } @@ -81,8 +81,11 @@ public static void InitializeTray(Icon icon, string text) IntPtr hWnd = IntPtr.Zero; // Start the message loop asynchronously - Task.Run(() => + _mainThread = new Thread(() => { + _syncContext = new SingleThreadSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(_syncContext); + RunOnMainThread(() => { WndClassEx wcex = new() @@ -129,18 +132,21 @@ public static void InitializeTray(Icon icon, string text) Bridge.ShowWindow(hWnd, 0); // SW_HIDE Bridge.UpdateWindow(hWnd); + Logger.LogInfo($"Created HWND for the window: {hWnd}"); SetShellIcon(hWnd, text, icon); }); - }).Wait(); - Task.Run(() => - { RunOnMainThread(() => { RunMessageLoop(); }); + + _syncContext!.BeginMessageLoop(); }); + + _mainThread.IsBackground = true; + _mainThread.Start(); } internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add) @@ -212,6 +218,8 @@ private static void RunMessageLoop() Bridge.TranslateMessage(ref msg); Bridge.DispatchMessage(ref msg); } + + Logger.LogInfo("Message loop terminated."); } private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam) @@ -292,30 +300,20 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar internal static void RunOnMainThread(Action action) { - var syncContext = new SingleThreadSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(syncContext); - -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - syncContext.Post( - _ => - { - try - { - action(); - } - catch (Exception e) + _syncContext!.Post( + _ => { - Console.WriteLine("Error: " + e.Message); - } - finally - { - syncContext.EndMessageLoop(); - } - }, - null); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - syncContext.BeginMessageLoop(); + try + { + Logger.LogInfo($"Thread execution is on: {Environment.CurrentManagedThreadId}"); + action(); + } + catch (Exception e) + { + Console.WriteLine("Error: " + e.Message); + } + }, + null); } internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys) @@ -350,6 +348,7 @@ private static void ClearExistingTrayMenu() private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode) { TrayMenu = Bridge.CreatePopupMenu(); + if (TrayMenu == IntPtr.Zero) { return; From 39bc1d37e9428d4a0e678ee13c08ed68e3abc05f Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 25 Sep 2024 21:34:53 -0700 Subject: [PATCH 13/51] Update planning doc --- doc/planning/awake.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 0566cd86dfbc..ae01e6f85db5 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -25,6 +25,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http >See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/34717) - Updating the initialization logic to make sure that settings are respected for proper group policy and single-instance detection. +- [#34148] Fixed a bug from the previous release that incorrectly synchronized threads for shell icon creation and initialized parent PID when it was not parented. ### `VISEGRADRELAY_08152024` (August 15, 2024) From 7ca8a2bb3cbc53d601f6729827a98460b1726a39 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Thu, 26 Sep 2024 13:45:49 +0100 Subject: [PATCH 14/51] Test disabling caching to see if that manages to pass CI --- .pipelines/v2/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/v2/ci.yml b/.pipelines/v2/ci.yml index 268bea7d11ba..5e7bf266765e 100644 --- a/.pipelines/v2/ci.yml +++ b/.pipelines/v2/ci.yml @@ -32,7 +32,7 @@ parameters: - name: enableMsBuildCaching type: boolean displayName: "Enable MSBuild Caching" - default: true + default: false - name: runTests type: boolean displayName: "Run Tests" From c4d7e6d0a22706d98a0b80e5ea9ab93ed97dd5cb Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 27 Sep 2024 09:11:31 -0700 Subject: [PATCH 15/51] Cleanup to make sure that we're logging things properly. --- src/modules/awake/Awake/Core/Manager.cs | 1 - .../Core/Threading/SingleThreadSynchronizationContext.cs | 6 ++++-- src/modules/awake/Awake/Program.cs | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index ba69f9c8df5c..f23725063567 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -14,7 +14,6 @@ using System.Text; using System.Text.Json; using System.Threading; - using Awake.Core.Models; using Awake.Core.Native; using Awake.Properties; diff --git a/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs b/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs index 4b432320d906..04c28dfd34b8 100644 --- a/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs +++ b/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Threading; +using ManagedCommon; namespace Awake.Core.Threading { @@ -49,7 +50,7 @@ public void BeginMessageLoop() } catch (Exception e) { - Console.WriteLine("Error during execution: " + e.Message); + Logger.LogError("Error during execution of the STS context message loop: " + e.Message); } } } @@ -58,7 +59,8 @@ public void EndMessageLoop() { lock (queue) { - queue.Enqueue(null); // Signal the end of the message loop + // Signal the end of the message loop + queue.Enqueue(null); Monitor.Pulse(queue); } } diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 2dd22db874bb..d1311ef51f03 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -15,7 +15,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; - using Awake.Core; using Awake.Core.Models; using Awake.Core.Native; From ff9b82b9044b16ed9ced6cddc20535607c9a7f23 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 27 Sep 2024 09:15:15 -0700 Subject: [PATCH 16/51] Update ci.yml --- .pipelines/v2/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/v2/ci.yml b/.pipelines/v2/ci.yml index 5e7bf266765e..268bea7d11ba 100644 --- a/.pipelines/v2/ci.yml +++ b/.pipelines/v2/ci.yml @@ -32,7 +32,7 @@ parameters: - name: enableMsBuildCaching type: boolean displayName: "Enable MSBuild Caching" - default: false + default: true - name: runTests type: boolean displayName: "Run Tests" From 95d93368d0136f709d138cda87e25e6426a4fa4b Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Fri, 27 Sep 2024 18:30:55 +0100 Subject: [PATCH 17/51] Disable cache to pass CI --- .pipelines/v2/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/v2/ci.yml b/.pipelines/v2/ci.yml index 268bea7d11ba..5e7bf266765e 100644 --- a/.pipelines/v2/ci.yml +++ b/.pipelines/v2/ci.yml @@ -32,7 +32,7 @@ parameters: - name: enableMsBuildCaching type: boolean displayName: "Enable MSBuild Caching" - default: true + default: false - name: runTests type: boolean displayName: "Run Tests" From 21bff772fe27d83c114cbd8c390b7e513c70e0d1 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Thu, 21 Nov 2024 18:06:04 -0800 Subject: [PATCH 18/51] Retry logic --- src/modules/awake/Awake/Core/TrayHelper.cs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 0545f97a8f65..0cf495466084 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -192,12 +192,24 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon }; } - if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData)) + for (int attempt = 1; attempt <= 3; attempt++) { - int errorCode = Marshal.GetLastWin32Error(); - Logger.LogInfo($"Could not set the shell icon. Action: {action} and error code: {errorCode}. HIcon handle is {icon?.Handle} and HWnd is {hWnd}"); + if (Bridge.Shell_NotifyIcon(message, ref _notifyIconData)) + { + break; + } + else + { + int errorCode = Marshal.GetLastWin32Error(); + Logger.LogInfo($"Could not set the shell icon. Action: {action}, error code: {errorCode}. HIcon handle is {icon?.Handle} and HWnd is {hWnd}"); + + if (attempt == 3) + { + throw new Win32Exception(errorCode, $"Failed to change tray icon after 3 attempts. Action: {action} and error code: {errorCode}"); + } - throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}"); + Thread.Sleep(100); + } } if (action == TrayIconAction.Delete) From cc21240dd8d910381f5758318aab05c0a009a7e2 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 13:10:41 -0800 Subject: [PATCH 19/51] Cleanup --- .../SingleThreadSynchronizationContext.cs | 2 -- src/modules/awake/Awake/Core/TrayHelper.cs | 16 +++++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs b/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs index f12045db8e2b..04c28dfd34b8 100644 --- a/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs +++ b/src/modules/awake/Awake/Core/Threading/SingleThreadSynchronizationContext.cs @@ -7,8 +7,6 @@ using System.Threading; using ManagedCommon; -using ManagedCommon; - namespace Awake.Core.Threading { internal sealed class SingleThreadSynchronizationContext : SynchronizationContext diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 16cc0045e9c2..1651a0949bca 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -29,14 +29,12 @@ namespace Awake.Core internal static class TrayHelper { private static NotifyIconData _notifyIconData; - private static IntPtr _trayMenu; - private static IntPtr _hiddenWindowHandle; private static SingleThreadSynchronizationContext? _syncContext; private static Thread? _mainThread; - private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; } + private static IntPtr TrayMenu { get; set; } - internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; } + internal static IntPtr HiddenWindowHandle { get; private set; } static TrayHelper() { @@ -59,7 +57,7 @@ private static void ShowContextMenu(IntPtr hWnd) Bridge.ScreenToClient(hWnd, ref cursorPos); // Set menu information - var menuInfo = new MenuInfo + MenuInfo menuInfo = new() { CbSize = (uint)Marshal.SizeOf(), FMask = Native.Constants.MIM_STYLE, @@ -169,7 +167,7 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon break; } - if (action == TrayIconAction.Add || action == TrayIconAction.Update) + if (action is TrayIconAction.Add or TrayIconAction.Update) { _notifyIconData = new NotifyIconData { @@ -240,7 +238,7 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar switch (message) { case Native.Constants.WM_USER: - if (lParam == (IntPtr)Native.Constants.WM_LBUTTONDOWN || lParam == (IntPtr)Native.Constants.WM_RBUTTONDOWN) + if (lParam is Native.Constants.WM_LBUTTONDOWN or Native.Constants.WM_RBUTTONDOWN) { // Show the context menu associated with the tray icon ShowContextMenu(hWnd); @@ -404,13 +402,13 @@ private static void EnsureDefaultTrayTimeShortcuts(Dictionary trayT private static void CreateAwakeTimeSubMenu(Dictionary trayTimeShortcuts, bool isChecked = false) { - var awakeTimeMenu = Bridge.CreatePopupMenu(); + nint awakeTimeMenu = Bridge.CreatePopupMenu(); for (int i = 0; i < trayTimeShortcuts.Count; i++) { Bridge.InsertMenu(awakeTimeMenu, (uint)i, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key); } - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (isChecked == true ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (isChecked ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL); } private static void InsertAwakeModeMenuItems(AwakeMode mode) From 763a5376a8714edb0ffcff6f501844d9f796c61f Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 13:38:28 -0800 Subject: [PATCH 20/51] Code cleanup --- .../awake/Awake/Core/ExtensionMethods.cs | 2 +- src/modules/awake/Awake/Core/Manager.cs | 40 +++++++++---------- .../Core/Models/SystemPowerCapabilities.cs | 2 - src/modules/awake/Awake/Program.cs | 28 ++++++------- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/modules/awake/Awake/Core/ExtensionMethods.cs b/src/modules/awake/Awake/Core/ExtensionMethods.cs index 4435e6e428c9..626b9c6443ab 100644 --- a/src/modules/awake/Awake/Core/ExtensionMethods.cs +++ b/src/modules/awake/Awake/Core/ExtensionMethods.cs @@ -14,7 +14,7 @@ public static void AddRange(this ICollection target, IEnumerable source ArgumentNullException.ThrowIfNull(target); ArgumentNullException.ThrowIfNull(source); - foreach (var element in source) + foreach (T? element in source) { target.Add(element); } diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index f23725063567..f84b3516301f 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -32,9 +32,7 @@ namespace Awake.Core /// public class Manager { - private static bool _isUsingPowerToysConfig; - - internal static bool IsUsingPowerToysConfig { get => _isUsingPowerToysConfig; set => _isUsingPowerToysConfig = value; } + internal static bool IsUsingPowerToysConfig { get; set; } private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES); private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS); @@ -49,9 +47,7 @@ public class Manager private static CancellationTokenSource _tokenSource; - private static SettingsUtils? _moduleSettings; - - internal static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; } + internal static SettingsUtils? ModuleSettings { get; set; } static Manager() { @@ -86,7 +82,7 @@ internal 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); + nint 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); @@ -104,7 +100,7 @@ private static bool SetAwakeState(ExecutionState state) { try { - var stateResult = Bridge.SetThreadExecutionState(state); + ExecutionState stateResult = Bridge.SetThreadExecutionState(state); return stateResult != 0; } catch @@ -156,8 +152,8 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false) { try { - var currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); - var settingsChanged = currentSettings.Properties.Mode != AwakeMode.INDEFINITE || + AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); + bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.INDEFINITE || currentSettings.Properties.KeepDisplayOn != keepDisplayOn; if (settingsChanged) @@ -217,8 +213,8 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis { try { - var currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); - var settingsChanged = currentSettings.Properties.Mode != AwakeMode.EXPIRABLE || + AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); + bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.EXPIRABLE || currentSettings.Properties.ExpirationDateTime != expireAt || currentSettings.Properties.KeepDisplayOn != keepDisplayOn; @@ -250,15 +246,15 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", _timedIcon, TrayIconAction.Update); - var timerObservable = Observable.Timer(TimeSpan.FromSeconds(seconds)); - var intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable); + IObservable timerObservable = Observable.Timer(TimeSpan.FromSeconds(seconds)); + IObservable intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable); - var combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1); + IObservable combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1); combinedObservable.Subscribe( elapsedSeconds => { - var timeRemaining = seconds - (uint)elapsedSeconds; + uint timeRemaining = seconds - (uint)elapsedSeconds; if (timeRemaining >= 0) { TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}", _timedIcon, TrayIconAction.Update); @@ -287,9 +283,9 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) { try { - var currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); - var timeSpan = TimeSpan.FromSeconds(seconds); - var settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED || + AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); + TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); + bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED || currentSettings.Properties.IntervalHours != (uint)timeSpan.Hours || currentSettings.Properties.IntervalMinutes != (uint)timeSpan.Minutes; @@ -343,7 +339,7 @@ internal static string GetOperatingSystemBuild() if (registryKey != null) { - var versionString = $"{registryKey.GetValue("ProductName")} {registryKey.GetValue("DisplayVersion")} {registryKey.GetValue("BuildLabEx")}"; + string versionString = $"{registryKey.GetValue("ProductName")} {registryKey.GetValue("DisplayVersion")} {registryKey.GetValue("BuildLabEx")}"; return versionString; } else @@ -392,7 +388,7 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true) { try { - var currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); + AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); if (currentSettings.Properties.Mode != AwakeMode.PASSIVE) { @@ -416,7 +412,7 @@ internal static void SetDisplay() { try { - var currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); + AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); } diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs index c80394113085..e444c42c2359 100644 --- a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs +++ b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs @@ -53,8 +53,6 @@ public struct SystemPowerCapabilities 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)] diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index a75fa2974005..da82157bc5fe 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -27,14 +27,14 @@ namespace Awake { internal sealed class Program { - private static Mutex? _mutex; + private static readonly ETWTrace _etwTrace = new(); + private static FileSystemWatcher? _watcher; private static SettingsUtils? _settingsUtils; - private static ETWTrace _etwTrace = new ETWTrace(); private static bool _startedFromPowerToys; - public static Mutex? LockMutex { get => _mutex; set => _mutex = value; } + public static Mutex? LockMutex { get; set; } #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. private static ConsoleEventHandler _handler; @@ -106,37 +106,37 @@ private static int Main(string[] args) Logger.LogInfo("Parsing parameters..."); - var configOption = new Option(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) + Option configOption = new(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - var displayOption = new Option(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) + Option displayOption = new(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - var timeOption = new Option(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) + Option timeOption = new(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) { Arity = ArgumentArity.ExactlyOne, IsRequired = false, }; - var pidOption = new Option(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) + Option pidOption = new(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - var expireAtOption = new Option(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) + Option expireAtOption = new(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - var parentPidOption = new Option(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION) + Option parentPidOption = new(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, @@ -208,7 +208,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); - var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent()); + EventWaitHandle eventHandle = new(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent()); new Thread(() => { WaitHandle.WaitAny([eventHandle]); @@ -337,8 +337,8 @@ private static void ScaffoldConfiguration(string settingsPath) private static void SetupFileSystemWatcher(string settingsPath) { - var directory = Path.GetDirectoryName(settingsPath)!; - var fileName = Path.GetFileName(settingsPath); + string directory = Path.GetDirectoryName(settingsPath)!; + string fileName = Path.GetFileName(settingsPath); _watcher = new FileSystemWatcher { @@ -363,7 +363,7 @@ private static void SetupFileSystemWatcher(string settingsPath) private static void InitializeSettings() { - var settings = Manager.ModuleSettings?.GetSettings(Core.Constants.AppName) ?? new AwakeSettings(); + AwakeSettings settings = Manager.ModuleSettings?.GetSettings(Core.Constants.AppName) ?? new AwakeSettings(); TrayHelper.SetTray(settings, _startedFromPowerToys); } @@ -384,7 +384,7 @@ private static void ProcessSettings() { try { - var settings = _settingsUtils!.GetSettings(Core.Constants.AppName) + AwakeSettings settings = _settingsUtils!.GetSettings(Core.Constants.AppName) ?? throw new InvalidOperationException("Settings are null."); Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}"); From 00ad336ff592aeb7dc33c5eb0f02f242c1a7b23d Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 14:25:38 -0800 Subject: [PATCH 21/51] Fixes #35848 --- src/modules/awake/Awake/Core/Manager.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index f84b3516301f..dbbc707145da 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -10,6 +10,7 @@ using System.Globalization; using System.IO; using System.Reactive.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; @@ -233,9 +234,9 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis } } - internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) + internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, [CallerMemberName] string callerName = "") { - Logger.LogInfo($"Timed keep-awake. Expected runtime: {seconds} seconds with display on setting set to {keepDisplayOn}."); + Logger.LogInfo($"Timed keep-awake invoked by {callerName}. Expected runtime: {seconds} seconds with display on setting set to {keepDisplayOn}."); PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeTimedKeepAwakeEvent()); @@ -262,7 +263,7 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) }, () => { - Console.WriteLine("Completed timed thread."); + Logger.LogInfo("Completed timed thread."); CancelExistingThread(); if (IsUsingPowerToysConfig) @@ -285,15 +286,19 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) { AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); + + uint totalHours = (uint)timeSpan.TotalHours; + uint remainingMinutes = (uint)(timeSpan.TotalMinutes % 60); + bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED || - currentSettings.Properties.IntervalHours != (uint)timeSpan.Hours || - currentSettings.Properties.IntervalMinutes != (uint)timeSpan.Minutes; + currentSettings.Properties.IntervalHours != totalHours || + currentSettings.Properties.IntervalMinutes != remainingMinutes; if (settingsChanged) { currentSettings.Properties.Mode = AwakeMode.TIMED; - currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours; - currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes; + currentSettings.Properties.IntervalHours = totalHours; + currentSettings.Properties.IntervalMinutes = remainingMinutes; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); } } From c6f75b9212cc2c9fdc784985b237b8cb8daa0083 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 14:34:36 -0800 Subject: [PATCH 22/51] Update notes and codename --- doc/planning/awake.md | 11 ++++++++++- src/modules/awake/Awake/Core/Constants.cs | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index ae01e6f85db5..1ad80bdd8b2f 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -10,8 +10,9 @@ The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it 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 | +| Build ID | Build Date | |:-------------------------------------------------------------------|:------------------| +| [`NEWKONA_11272024`](#NEWKONA_11272024-november-27-2024) | November 27, 2024 | | [`PROMETHEAN_09082024`](#PROMETHEAN_09082024-september-8-2024) | September 8, 2024 | | [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 | | [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | @@ -19,6 +20,14 @@ The build ID moniker is made up of two components - a reference to a [Halo](http | [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 | | `ARBITER_01312022` | January 31, 2022 | +### `NEWKONA_11272024` (November 27, 2024) + +>[!NOTE] +>See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/36049) + +- [#35250] Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. +- [#35848] Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. + ### `PROMETHEAN_09082024` (September 8, 2024) >[!NOTE] diff --git a/src/modules/awake/Awake/Core/Constants.cs b/src/modules/awake/Awake/Core/Constants.cs index 1bd6f0a04360..83da448c980d 100644 --- a/src/modules/awake/Awake/Core/Constants.cs +++ b/src/modules/awake/Awake/Core/Constants.cs @@ -17,6 +17,6 @@ internal static class Constants // 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. - internal const string BuildId = "PROMETHEAN_09082024"; + internal const string BuildId = "NEWKONA_11272024"; } } From 37f9625e21bb486d677aee6a356e5fb87c9cd707 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 14:38:14 -0800 Subject: [PATCH 23/51] After third attempt, log error instead of throwing exception --- src/modules/awake/Awake/Core/TrayHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 1651a0949bca..164bb3d999a3 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -204,7 +204,8 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon if (attempt == 3) { - throw new Win32Exception(errorCode, $"Failed to change tray icon after 3 attempts. Action: {action} and error code: {errorCode}"); + Logger.LogError($"Failed to change tray icon after 3 attempts. Action: {action} and error code: {errorCode}"); + break; } Thread.Sleep(100); From 9c4abbda4de0bacb03666e2127068e8ecb6a2b3c Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 14:50:41 -0800 Subject: [PATCH 24/51] More cleanup to avoid double execution --- src/modules/awake/Awake/Core/Manager.cs | 136 ++++++++++++++---------- 1 file changed, 78 insertions(+), 58 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index dbbc707145da..1625efe8770c 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -140,14 +140,13 @@ internal static void CancelExistingThread() Logger.LogInfo("Instantiating of new token source and thread token completed."); } - internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false) + internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, [CallerMemberName] string callerName = "") { - PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); + Logger.LogInfo($"Indefinite keep-awake invoked by {callerName}."); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); CancelExistingThread(); - _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); if (IsUsingPowerToysConfig) { @@ -162,6 +161,11 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false) currentSettings.Properties.Mode = AwakeMode.INDEFINITE; currentSettings.Properties.KeepDisplayOn = keepDisplayOn; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); + + // We return here because when the settings are save, they will be automatically + // processed. That means that when they are processed, the indefinite keep-awake will kick-in properly + // and we avoid double execution. + return; } } catch (Exception ex) @@ -169,16 +173,47 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false) Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}"); } } + + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); + + _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); } - internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true) + internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true, [CallerMemberName] string callerName = "") { - Logger.LogInfo($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {keepDisplayOn}."); - + Logger.LogInfo($"Expirable keep-awake invoked by {callerName}. Expected expiration date/time: {expireAt} with display on setting set to {keepDisplayOn}."); PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeExpirableKeepAwakeEvent()); CancelExistingThread(); + if (IsUsingPowerToysConfig) + { + try + { + AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); + bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.EXPIRABLE || + currentSettings.Properties.ExpirationDateTime != expireAt || + currentSettings.Properties.KeepDisplayOn != keepDisplayOn; + + if (settingsChanged) + { + currentSettings.Properties.Mode = AwakeMode.EXPIRABLE; + currentSettings.Properties.KeepDisplayOn = keepDisplayOn; + currentSettings.Properties.ExpirationDateTime = expireAt; + ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); + + // We return here because when the settings are save, they will be automatically + // processed. That means that when they are processed, the expirable keep-awake will kick-in properly + // and we avoid double execution. + return; + } + } + catch (Exception ex) + { + Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}"); + } + } + if (expireAt > DateTimeOffset.Now) { Logger.LogInfo($"Starting expirable log for {expireAt}"); @@ -209,40 +244,48 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis Logger.LogError("The specified target date and time is not in the future."); Logger.LogError($"Current time: {DateTimeOffset.Now}\tTarget time: {expireAt}"); } + } + + internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, [CallerMemberName] string callerName = "") + { + Logger.LogInfo($"Timed keep-awake invoked by {callerName}. Expected runtime: {seconds} seconds with display on setting set to {keepDisplayOn}."); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeTimedKeepAwakeEvent()); + + CancelExistingThread(); if (IsUsingPowerToysConfig) { try { AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); - bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.EXPIRABLE || - currentSettings.Properties.ExpirationDateTime != expireAt || - currentSettings.Properties.KeepDisplayOn != keepDisplayOn; + TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); + + uint totalHours = (uint)timeSpan.TotalHours; + uint remainingMinutes = (uint)(timeSpan.TotalMinutes % 60); + + bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED || + currentSettings.Properties.IntervalHours != totalHours || + currentSettings.Properties.IntervalMinutes != remainingMinutes; if (settingsChanged) { - currentSettings.Properties.Mode = AwakeMode.EXPIRABLE; - currentSettings.Properties.KeepDisplayOn = keepDisplayOn; - currentSettings.Properties.ExpirationDateTime = expireAt; + currentSettings.Properties.Mode = AwakeMode.TIMED; + currentSettings.Properties.IntervalHours = totalHours; + currentSettings.Properties.IntervalMinutes = remainingMinutes; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); + + // We return here because when the settings are save, they will be automatically + // processed. That means that when they are processed, the timed keep-awake will kick-in properly + // and we avoid double execution. + return; } } catch (Exception ex) { - Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}"); + Logger.LogError($"Failed to handle timed keep awake command: {ex.Message}"); } } - } - - internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, [CallerMemberName] string callerName = "") - { - Logger.LogInfo($"Timed keep-awake invoked by {callerName}. Expected runtime: {seconds} seconds with display on setting set to {keepDisplayOn}."); - - PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeTimedKeepAwakeEvent()); - CancelExistingThread(); - - Logger.LogInfo($"Timed keep awake started for {seconds} seconds."); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", _timedIcon, TrayIconAction.Update); @@ -279,34 +322,6 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, } }, _tokenSource.Token); - - if (IsUsingPowerToysConfig) - { - try - { - AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); - TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); - - uint totalHours = (uint)timeSpan.TotalHours; - uint remainingMinutes = (uint)(timeSpan.TotalMinutes % 60); - - bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED || - currentSettings.Properties.IntervalHours != totalHours || - currentSettings.Properties.IntervalMinutes != remainingMinutes; - - if (settingsChanged) - { - currentSettings.Properties.Mode = AwakeMode.TIMED; - currentSettings.Properties.IntervalHours = totalHours; - currentSettings.Properties.IntervalMinutes = remainingMinutes; - ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); - } - } - catch (Exception ex) - { - Logger.LogError($"Failed to handle timed keep awake command: {ex.Message}"); - } - } } /// @@ -379,16 +394,13 @@ internal static Dictionary GetDefaultTrayOptions() /// Resets the computer to standard power settings. /// /// In certain cases, such as exits, we want to make sure that settings are not reset for the passive mode but rather retained based on previous execution. Default is to save settings, but otherwise it can be overridden. - internal static void SetPassiveKeepAwake(bool updateSettings = true) + internal static void SetPassiveKeepAwake(bool updateSettings = true, [CallerMemberName] string callerName = "") { - Logger.LogInfo($"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). Invoked by {callerName}. No custom keep awake settings enabled."); PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeNoKeepAwakeEvent()); CancelExistingThread(); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", _disabledIcon, TrayIconAction.Update); - if (IsUsingPowerToysConfig && updateSettings) { try @@ -399,6 +411,11 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true) { currentSettings.Properties.Mode = AwakeMode.PASSIVE; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); + + // We return here because when the settings are save, they will be automatically + // processed. That means that when they are processed, the passive keep-awake will kick-in properly + // and we avoid double execution. + return; } } catch (Exception ex) @@ -406,13 +423,16 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true) Logger.LogError($"Failed to reset Awake mode: {ex.Message}"); } } + + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", _disabledIcon, TrayIconAction.Update); } /// /// Sets the display settings. /// - internal static void SetDisplay() + internal static void SetDisplay([CallerMemberName] string callerName = "") { + Logger.LogInfo($"Setting display configuration from settings. Invoked by {callerName}."); if (IsUsingPowerToysConfig) { try From e35f2b16f9464d2d3f875a685ede77112df0ac85 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 14:55:10 -0800 Subject: [PATCH 25/51] Add expected word --- .github/actions/spell-check/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index dc7eefe60754..437f57b434b3 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -994,6 +994,7 @@ newdev NEWDIALOGSTYLE NEWFILE newitem +NEWKONA newpath newplus NEWPLUSCONTEXTMENU From c1dcff0b86298fddd744435023882c66bbd2375c Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 15:52:32 -0800 Subject: [PATCH 26/51] Safeguards for bad values for timed keep-awake --- src/modules/awake/Awake/Core/Manager.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 1625efe8770c..110b1c623da5 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -174,6 +174,8 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, [CallerM } } + Logger.LogInfo($"Indefinite keep-awake starting..."); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); @@ -214,6 +216,8 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis } } + Logger.LogInfo($"Expirable keep-awake starting..."); + if (expireAt > DateTimeOffset.Now) { Logger.LogInfo($"Starting expirable log for {expireAt}"); @@ -286,19 +290,28 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, } } + Logger.LogInfo($"Timed keep-awake starting..."); + _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", _timedIcon, TrayIconAction.Update); - IObservable timerObservable = Observable.Timer(TimeSpan.FromSeconds(seconds)); - IObservable intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable); + ulong desiredDuration = (ulong)seconds * 1000; + ulong targetDuration = Math.Min(desiredDuration, uint.MaxValue - 1) / 1000; + if (desiredDuration > uint.MaxValue) + { + Logger.LogInfo($"The desired interval of {seconds}s ({desiredDuration}ms) exceeds the limit. Defaulting to maximum possible value: {targetDuration}s."); + } + + IObservable timerObservable = Observable.Timer(TimeSpan.FromSeconds(targetDuration)); + IObservable intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable); IObservable combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1); combinedObservable.Subscribe( elapsedSeconds => { - uint timeRemaining = seconds - (uint)elapsedSeconds; + uint timeRemaining = (uint)targetDuration - (uint)elapsedSeconds; if (timeRemaining >= 0) { TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}", _timedIcon, TrayIconAction.Update); @@ -424,6 +437,8 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true, [CallerMemb } } + Logger.LogInfo($"Passive keep-awake starting..."); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", _disabledIcon, TrayIconAction.Update); } From 88008ee65d76a9e674aab5d80938a7df16c5aacf Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 16:14:46 -0800 Subject: [PATCH 27/51] More updates to make sure I am using uint --- doc/planning/awake.md | 1 + src/modules/awake/Awake/Core/Manager.cs | 4 ++-- src/modules/awake/Awake/Core/TrayHelper.cs | 8 ++++---- src/settings-ui/Settings.UI.Library/AwakeProperties.cs | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 1ad80bdd8b2f..f23522c36b7c 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -27,6 +27,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http - [#35250] Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. - [#35848] Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. +- Added configuration safeguards to make sure that invalid values for timed keep-awake times do not result in exceptions. ### `PROMETHEAN_09082024` (September 8, 2024) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 110b1c623da5..99b2efb631ec 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -392,9 +392,9 @@ internal static string GetOperatingSystemBuild() /// Generates the default system tray options in situations where no custom options are provided. /// /// Returns a dictionary of default Awake timed interval options. - internal static Dictionary GetDefaultTrayOptions() + internal static Dictionary GetDefaultTrayOptions() { - Dictionary optionsList = new() + Dictionary optionsList = new() { { string.Format(CultureInfo.InvariantCulture, AwakeMinutes, 30), 1800 }, { string.Format(CultureInfo.InvariantCulture, AwakeHours, 1), 3600 }, diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 164bb3d999a3..e152c302d62c 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -293,7 +293,7 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar } int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME; - uint targetTime = (uint)settings.Properties.CustomTrayTimes.ElementAt(index).Value; + uint targetTime = settings.Properties.CustomTrayTimes.ElementAt(index).Value; Manager.SetTimedKeepAwake(targetTime, keepDisplayOn: settings.Properties.KeepDisplayOn); } @@ -337,7 +337,7 @@ internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys) startedFromPowerToys); } - public static void SetTray(bool keepDisplayOn, AwakeMode mode, Dictionary trayTimeShortcuts, bool startedFromPowerToys) + public static void SetTray(bool keepDisplayOn, AwakeMode mode, Dictionary trayTimeShortcuts, bool startedFromPowerToys) { ClearExistingTrayMenu(); CreateNewTrayMenu(startedFromPowerToys, keepDisplayOn, mode); @@ -393,7 +393,7 @@ private static void InsertSeparator(int position) Bridge.InsertMenu(TrayMenu, (uint)position, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty); } - private static void EnsureDefaultTrayTimeShortcuts(Dictionary trayTimeShortcuts) + private static void EnsureDefaultTrayTimeShortcuts(Dictionary trayTimeShortcuts) { if (trayTimeShortcuts.Count == 0) { @@ -401,7 +401,7 @@ private static void EnsureDefaultTrayTimeShortcuts(Dictionary trayT } } - private static void CreateAwakeTimeSubMenu(Dictionary trayTimeShortcuts, bool isChecked = false) + private static void CreateAwakeTimeSubMenu(Dictionary trayTimeShortcuts, bool isChecked = false) { nint awakeTimeMenu = Bridge.CreatePopupMenu(); for (int i = 0; i < trayTimeShortcuts.Count; i++) diff --git a/src/settings-ui/Settings.UI.Library/AwakeProperties.cs b/src/settings-ui/Settings.UI.Library/AwakeProperties.cs index 4a329918f66e..7fd08be47624 100644 --- a/src/settings-ui/Settings.UI.Library/AwakeProperties.cs +++ b/src/settings-ui/Settings.UI.Library/AwakeProperties.cs @@ -38,7 +38,7 @@ public AwakeProperties() public DateTimeOffset ExpirationDateTime { get; set; } [JsonPropertyName("customTrayTimes")] - [CmdConfigureIgnoreAttribute] - public Dictionary CustomTrayTimes { get; set; } + [CmdConfigureIgnore] + public Dictionary CustomTrayTimes { get; set; } } } From 77ca495ec8e5aa76971b0f0be125de7efad8067e Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 16:20:43 -0800 Subject: [PATCH 28/51] Update error message --- src/modules/awake/Awake/Core/Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 99b2efb631ec..56bd61137b69 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -301,7 +301,7 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, if (desiredDuration > uint.MaxValue) { - Logger.LogInfo($"The desired interval of {seconds}s ({desiredDuration}ms) exceeds the limit. Defaulting to maximum possible value: {targetDuration}s."); + Logger.LogInfo($"The desired interval of {seconds} seconds ({desiredDuration}ms) exceeds the limit. Defaulting to maximum possible value: {targetDuration} seconds. Read more about existing limits in the official documentation: https://aka.ms/powertoys/awake"); } IObservable timerObservable = Observable.Timer(TimeSpan.FromSeconds(targetDuration)); From baf4c5c247e3ba042b3bf1feec3b162cc21da7a6 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 27 Nov 2024 16:52:58 -0800 Subject: [PATCH 29/51] Update packages --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a0d29eb31107..4d137dff9f8c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -26,7 +26,7 @@ - + @@ -45,7 +45,7 @@ TODO: in Common.Dotnet.CsWinRT.props, on upgrade, verify RemoveCsWinRTPackageAnalyzer is no longer needed. This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail. --> - + From 7253c508b8e6427e4c8f08043f80e4844f6593c8 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Tue, 3 Dec 2024 09:31:04 +0000 Subject: [PATCH 30/51] Fix notice and revert CsWinRT upgrade --- Directory.Packages.props | 2 +- NOTICE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b5b4c613e62d..f15a66621ee1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,7 +45,7 @@ TODO: in Common.Dotnet.CsWinRT.props, on upgrade, verify RemoveCsWinRTPackageAnalyzer is no longer needed. This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail. --> - + diff --git a/NOTICE.md b/NOTICE.md index 9573caebb676..fbad63bf5dfd 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1318,7 +1318,7 @@ EXHIBIT A -Mozilla Public License. - Mages 2.0.2 - Markdig.Signed 0.34.0 - MessagePack 2.5.187 -- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0-preview.24508.2 +- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0 - Microsoft.Data.Sqlite 9.0.0 - Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16 - Microsoft.Extensions.DependencyInjection 9.0.0 From a92f3a46dbc95976bd4482dc2da40a27c70a8077 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Tue, 3 Dec 2024 16:35:15 -0800 Subject: [PATCH 31/51] Codename update --- doc/planning/awake.md | 6 +++--- src/modules/awake/Awake/Core/Constants.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index f23522c36b7c..d1e74177aa8f 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -12,7 +12,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http | Build ID | Build Date | |:-------------------------------------------------------------------|:------------------| -| [`NEWKONA_11272024`](#NEWKONA_11272024-november-27-2024) | November 27, 2024 | +| [`TILLSON_11272024`](#TILLSON_11272024-november-27-2024) | November 27, 2024 | | [`PROMETHEAN_09082024`](#PROMETHEAN_09082024-september-8-2024) | September 8, 2024 | | [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 | | [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | @@ -20,10 +20,10 @@ The build ID moniker is made up of two components - a reference to a [Halo](http | [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 | | `ARBITER_01312022` | January 31, 2022 | -### `NEWKONA_11272024` (November 27, 2024) +### `TILLSON_11272024` (November 27, 2024) >[!NOTE] ->See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/36049) +>See pull request: [Awake - `TILLSON_11272024`](https://github.com/microsoft/PowerToys/pull/36049) - [#35250] Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. - [#35848] Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. diff --git a/src/modules/awake/Awake/Core/Constants.cs b/src/modules/awake/Awake/Core/Constants.cs index 83da448c980d..d6864712eef3 100644 --- a/src/modules/awake/Awake/Core/Constants.cs +++ b/src/modules/awake/Awake/Core/Constants.cs @@ -17,6 +17,6 @@ internal static class Constants // 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. - internal const string BuildId = "NEWKONA_11272024"; + internal const string BuildId = "TILLSON_11272024"; } } From d415ae29c17eaa2d71140e3383a59a8ac57d8c54 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Tue, 3 Dec 2024 21:38:25 -0800 Subject: [PATCH 32/51] Update expect.txt --- .github/actions/spell-check/expect.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index e4943963e5fd..3c4ed463261a 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -996,7 +996,6 @@ newdev NEWDIALOGSTYLE NEWFILE newitem -NEWKONA newpath newplus NEWPLUSCONTEXTMENU @@ -1612,6 +1611,7 @@ THISCOMPONENT THotkey thumbcache TILEDWINDOW +TILLSON timedate timediff timeunion From d60c05446f8e0c4aed081ef0491d729201a96124 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 12:09:34 -0800 Subject: [PATCH 33/51] Update the struct --- .../Core/Models/SystemPowerCapabilities.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs index e444c42c2359..fba9a5f33d90 100644 --- a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs +++ b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs @@ -6,59 +6,94 @@ namespace Awake.Core.Models { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 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 ProcessorThrottleScale; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] Spare2; + 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; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Spare3; + public byte HiberFileType; + [MarshalAs(UnmanagedType.U1)] public bool AoAcConnectivitySupported; + [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; From c196d287490908ad4665a3788662a2773229b463 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 15:47:48 -0800 Subject: [PATCH 34/51] Ensuring we're properly awaiting tray initialization --- src/modules/awake/Awake/Core/TrayHelper.cs | 107 ++++++++++++--------- src/modules/awake/Awake/Program.cs | 8 +- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index e152c302d62c..f3e8c26f884b 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -7,9 +7,10 @@ using System.ComponentModel; using System.Drawing; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; - +using System.Threading.Tasks; using Awake.Core.Models; using Awake.Core.Native; using Awake.Core.Threading; @@ -75,8 +76,10 @@ private static void ShowContextMenu(IntPtr hWnd) IntPtr.Zero); } - public static void InitializeTray(Icon icon, string text) + public static Task InitializeTray(Icon icon, string text) { + TaskCompletionSource trayInitialized = new(); + IntPtr hWnd = IntPtr.Zero; // Start the message loop asynchronously @@ -87,53 +90,63 @@ public static void InitializeTray(Icon icon, string text) RunOnMainThread(() => { - WndClassEx wcex = new() + try { - CbSize = (uint)Marshal.SizeOf(typeof(WndClassEx)), - Style = 0, - LpfnWndProc = Marshal.GetFunctionPointerForDelegate(WndProc), - CbClsExtra = 0, - CbWndExtra = 0, - HInstance = Marshal.GetHINSTANCE(typeof(Program).Module), - HIcon = IntPtr.Zero, - HCursor = IntPtr.Zero, - HbrBackground = IntPtr.Zero, - LpszMenuName = string.Empty, - LpszClassName = Constants.TrayWindowId, - HIconSm = IntPtr.Zero, - }; + WndClassEx wcex = new() + { + CbSize = (uint)Marshal.SizeOf(typeof(WndClassEx)), + Style = 0, + LpfnWndProc = Marshal.GetFunctionPointerForDelegate(WndProc), + CbClsExtra = 0, + CbWndExtra = 0, + HInstance = Marshal.GetHINSTANCE(typeof(Program).Module), + HIcon = IntPtr.Zero, + HCursor = IntPtr.Zero, + HbrBackground = IntPtr.Zero, + LpszMenuName = string.Empty, + LpszClassName = Constants.TrayWindowId, + HIconSm = IntPtr.Zero, + }; + + Bridge.RegisterClassEx(ref wcex); + + hWnd = Bridge.CreateWindowEx( + 0, + Constants.TrayWindowId, + text, + 0x00CF0000 | 0x00000001 | 0x00000008, // WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MINIMIZEBOX + 0, + 0, + 0, + 0, + unchecked(-3), + IntPtr.Zero, + Marshal.GetHINSTANCE(typeof(Program).Module), + IntPtr.Zero); + + if (hWnd == IntPtr.Zero) + { + int errorCode = Marshal.GetLastWin32Error(); + throw new Win32Exception(errorCode, "Failed to add tray icon. Error code: " + errorCode); + } - Bridge.RegisterClassEx(ref wcex); - - hWnd = Bridge.CreateWindowEx( - 0, - Constants.TrayWindowId, - text, - 0x00CF0000 | 0x00000001 | 0x00000008, // WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MINIMIZEBOX - 0, - 0, - 0, - 0, - unchecked(-3), - IntPtr.Zero, - Marshal.GetHINSTANCE(typeof(Program).Module), - IntPtr.Zero); - - if (hWnd == IntPtr.Zero) - { - int errorCode = Marshal.GetLastWin32Error(); - throw new Win32Exception(errorCode, "Failed to add tray icon. Error code: " + errorCode); - } + // Keep this as a reference because we will need it when we update + // the tray icon in the future. + HiddenWindowHandle = hWnd; - // Keep this as a reference because we will need it when we update - // the tray icon in the future. - HiddenWindowHandle = hWnd; + Bridge.ShowWindow(hWnd, 0); // SW_HIDE + Bridge.UpdateWindow(hWnd); + Logger.LogInfo($"Created HWND for the window: {hWnd}"); - Bridge.ShowWindow(hWnd, 0); // SW_HIDE - Bridge.UpdateWindow(hWnd); - Logger.LogInfo($"Created HWND for the window: {hWnd}"); + SetShellIcon(hWnd, text, icon); - SetShellIcon(hWnd, text, icon); + trayInitialized.SetResult(true); + } + catch (Exception ex) + { + Logger.LogError($"Failed to properly initialize the tray. {ex.Message}"); + trayInitialized.SetException(ex); + } }); RunOnMainThread(() => @@ -146,10 +159,14 @@ public static void InitializeTray(Icon icon, string text) _mainThread.IsBackground = true; _mainThread.Start(); + + return trayInitialized.Task; } - internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add) + internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add, [CallerMemberName] string callerName = "") { + Logger.LogInfo($"Attempting to set the shell icon. Invoked by {callerName}."); + if (hWnd != IntPtr.Zero && icon != null) { int message = Native.Constants.NIM_ADD; diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index da82157bc5fe..3b707d0ba9c2 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -183,7 +183,7 @@ private static void Exit(string message, int exitCode) Manager.CompleteExit(exitCode); } - private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid) + private static async void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid) { if (pid == 0 && !useParentPid) { @@ -206,7 +206,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, // Start the monitor thread that will be used to track the current state. Manager.StartMonitor(); - TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); + await TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); EventWaitHandle eventHandle = new(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent()); new Thread(() => @@ -218,7 +218,8 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, if (usePtConfig) { // Configuration file is used, therefore we disregard any other command-line parameter - // and instead watch for changes in the file. + // and instead watch for changes in the file. This is used as a priority against all other arguments, + // so if --use-pt-config is applied the rest of the arguments are irrelevant. Manager.IsUsingPowerToysConfig = true; try @@ -258,6 +259,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, // Second, we snap to process-based execution. Because this is something that // is snapped to a running entity, we only want to enable the ability to set // indefinite keep-awake with the display settings that the user wants to set. + // In this context, manual (explicit) PID takes precedence over parent PID. int targetPid = pid != 0 ? pid : useParentPid ? Manager.GetParentProcess()?.Id ?? 0 : 0; if (targetPid != 0) From 3bc40c7f576c02079280191583edddd187332197 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 16:00:14 -0800 Subject: [PATCH 35/51] Update to make sure tray reflects the bound process --- src/modules/awake/Awake/Core/Manager.cs | 18 ++++++++++++------ src/modules/awake/Awake/Program.cs | 2 +- .../Awake/Properties/Resources.Designer.cs | 9 +++++++++ .../awake/Awake/Properties/Resources.resx | 4 ++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 56bd61137b69..a613283da679 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -140,10 +140,8 @@ internal static void CancelExistingThread() Logger.LogInfo("Instantiating of new token source and thread token completed."); } - internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, [CallerMemberName] string callerName = "") + internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int processId = 0, [CallerMemberName] string callerName = "") { - Logger.LogInfo($"Indefinite keep-awake invoked by {callerName}."); - PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); CancelExistingThread(); @@ -170,13 +168,21 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, [CallerM } catch (Exception ex) { - Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}"); + Logger.LogError($"Failed to handle indefinite keep awake command invoked by {callerName}: {ex.Message}"); } } - Logger.LogInfo($"Indefinite keep-awake starting..."); + Logger.LogInfo($"Indefinite keep-awake starting, invoked by {callerName}..."); + + string processText = processId == 0 + ? string.Empty + : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING} {processId}"; - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); + TrayHelper.SetShellIcon( + TrayHelper.HiddenWindowHandle, + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}]", + _indefiniteIcon, + TrayIconAction.Update); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); } diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 3b707d0ba9c2..ed68ce704a01 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -266,7 +266,7 @@ private static async void HandleCommandLineArguments(bool usePtConfig, bool disp { Logger.LogInfo($"Bound to target process: {targetPid}"); - Manager.SetIndefiniteKeepAwake(displayOn); + Manager.SetIndefiniteKeepAwake(displayOn, targetPid); RunnerHelper.WaitForPowerToysRunner(targetPid, () => { diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs index be22d149d21f..77056f6ace78 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -285,6 +285,15 @@ internal static string AWAKE_TRAY_TEXT_OFF { } } + /// + /// Looks up a localized string similar to Bound To. + /// + internal static string AWAKE_TRAY_TEXT_PID_BINDING { + get { + return ResourceManager.GetString("AWAKE_TRAY_TEXT_PID_BINDING", resourceCulture); + } + } + /// /// Looks up a localized string similar to Interval. /// diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx index 8bafe1d4db00..6a3dfd665b87 100644 --- a/src/modules/awake/Awake/Properties/Resources.resx +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -208,4 +208,8 @@ Uses the parent process as the bound target - once the process terminates, Awake stops. + + Bound To + Describes the process ID Awake is bound to when running. + \ No newline at end of file From 223b930180910bde27ab20b2dd9c3c7f476ce321 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 16:38:57 -0800 Subject: [PATCH 36/51] Cleanup, proper JSON serialization for logs. --- .../Core/Models/SystemPowerCapabilities.cs | 10 ++++++ src/modules/awake/Awake/Core/TrayHelper.cs | 8 ++--- src/modules/awake/Awake/Program.cs | 35 ++++++++++--------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs index fba9a5f33d90..228758d0e2bb 100644 --- a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs +++ b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; +using System.Text.Json.Serialization; namespace Awake.Core.Models { @@ -94,10 +95,19 @@ public struct SystemPowerCapabilities [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public BatteryReportingScale[] BatteryScale; + [JsonConverter(typeof(JsonStringEnumConverter))] public SystemPowerState AcOnLineWake; + + [JsonConverter(typeof(JsonStringEnumConverter))] public SystemPowerState SoftLidWake; + + [JsonConverter(typeof(JsonStringEnumConverter))] public SystemPowerState RtcWake; + + [JsonConverter(typeof(JsonStringEnumConverter))] public SystemPowerState MinDeviceWakeState; + + [JsonConverter(typeof(JsonStringEnumConverter))] public SystemPowerState DefaultLowLatencyWake; } } diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index f3e8c26f884b..f581e898d7ba 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -94,7 +94,7 @@ public static Task InitializeTray(Icon icon, string text) { WndClassEx wcex = new() { - CbSize = (uint)Marshal.SizeOf(typeof(WndClassEx)), + CbSize = (uint)Marshal.SizeOf(), Style = 0, LpfnWndProc = Marshal.GetFunctionPointerForDelegate(WndProc), CbClsExtra = 0, @@ -188,7 +188,7 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon { _notifyIconData = new NotifyIconData { - CbSize = Marshal.SizeOf(typeof(NotifyIconData)), + CbSize = Marshal.SizeOf(), HWnd = hWnd, UId = 1000, UFlags = Native.Constants.NIF_ICON | Native.Constants.NIF_TIP | Native.Constants.NIF_MESSAGE, @@ -201,7 +201,7 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon { _notifyIconData = new NotifyIconData { - CbSize = Marshal.SizeOf(typeof(NotifyIconData)), + CbSize = Marshal.SizeOf(), HWnd = hWnd, UId = 1000, UFlags = 0, @@ -268,7 +268,7 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar Bridge.PostQuitMessage(0); break; case Native.Constants.WM_COMMAND: - int trayCommandsSize = Enum.GetNames(typeof(TrayCommands)).Length; + int trayCommandsSize = Enum.GetNames().Length; long targetCommandIndex = wParam.ToInt64() & 0xFFFF; diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index ed68ce704a01..61fcc9493d9a 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -27,7 +27,16 @@ namespace Awake { internal sealed class Program { + private static readonly string[] _aliasesConfigOption = ["--use-pt-config", "-c"]; + private static readonly string[] _aliasesDisplayOption = ["--display-on", "-d"]; + private static readonly string[] _aliasesTimeOption = ["--time-limit", "-t"]; + private static readonly string[] _aliasesPidOption = ["--pid", "-p"]; + private static readonly string[] _aliasesExpireAtOption = ["--expire-at", "-e"]; + private static readonly string[] _aliasesParentPidOption = ["--use-parent-pid", "-u"]; + + private static readonly JsonSerializerOptions _serializerOptions = new() { IncludeFields = true }; private static readonly ETWTrace _etwTrace = new(); + private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")); private static FileSystemWatcher? _watcher; private static SettingsUtils? _settingsUtils; @@ -41,19 +50,13 @@ internal sealed class Program private static SystemPowerCapabilities _powerCapabilities; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - internal static readonly string[] AliasesConfigOption = ["--use-pt-config", "-c"]; - internal static readonly string[] AliasesDisplayOption = ["--display-on", "-d"]; - internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"]; - internal static readonly string[] AliasesPidOption = ["--pid", "-p"]; - internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"]; - internal static readonly string[] AliasesParentPidOption = ["--use-parent-pid", "-u"]; - - private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")); - private static int Main(string[] args) { _settingsUtils = new SettingsUtils(); + LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated); + AppDomain.CurrentDomain.ProcessExit += (_, _) => TrayHelper.RunOnMainThread(() => LockMutex?.ReleaseMutex()); + Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); try @@ -102,41 +105,41 @@ private static int Main(string[] args) // To make it easier to diagnose future issues, let's get the // system power capabilities and aggregate them in the log. Bridge.GetPwrCapabilities(out _powerCapabilities); - Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities)); + Logger.LogInfo(JsonSerializer.Serialize(_powerCapabilities, _serializerOptions)); Logger.LogInfo("Parsing parameters..."); - Option configOption = new(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) + Option configOption = new(_aliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - Option displayOption = new(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) + Option displayOption = new(_aliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - Option timeOption = new(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) + Option timeOption = new(_aliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) { Arity = ArgumentArity.ExactlyOne, IsRequired = false, }; - Option pidOption = new(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) + Option pidOption = new(_aliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - Option expireAtOption = new(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) + Option expireAtOption = new(_aliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, }; - Option parentPidOption = new(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION) + Option parentPidOption = new(_aliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION) { Arity = ArgumentArity.ZeroOrOne, IsRequired = false, From f54405423083697a82079f36c779e2b1348e1430 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 16:52:12 -0800 Subject: [PATCH 37/51] Not needed. --- src/modules/awake/Awake/Core/TrayHelper.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index f581e898d7ba..5482e14798c2 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -165,8 +165,6 @@ public static Task InitializeTray(Icon icon, string text) internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add, [CallerMemberName] string callerName = "") { - Logger.LogInfo($"Attempting to set the shell icon. Invoked by {callerName}."); - if (hWnd != IntPtr.Zero && icon != null) { int message = Native.Constants.NIM_ADD; From 60adac18bd8b9e0af3e157f3acdfe9c8bb2708b2 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 17:34:39 -0800 Subject: [PATCH 38/51] Add command validation logic --- src/modules/awake/Awake/Core/TrayHelper.cs | 4 +-- src/modules/awake/Awake/Program.cs | 35 ++++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 5482e14798c2..129145f58620 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -215,11 +215,11 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon else { int errorCode = Marshal.GetLastWin32Error(); - Logger.LogInfo($"Could not set the shell icon. Action: {action}, error code: {errorCode}. HIcon handle is {icon?.Handle} and HWnd is {hWnd}"); + Logger.LogInfo($"Could not set the shell icon. Action: {action}, error code: {errorCode}. HIcon handle is {icon?.Handle} and HWnd is {hWnd}. Invoked by {callerName}."); if (attempt == 3) { - Logger.LogError($"Failed to change tray icon after 3 attempts. Action: {action} and error code: {errorCode}"); + Logger.LogError($"Failed to change tray icon after 3 attempts. Action: {action} and error code: {errorCode}. Invoked by {callerName}."); break; } diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 61fcc9493d9a..ca369b154a0d 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -4,6 +4,7 @@ using System; using System.CommandLine; +using System.CommandLine.Parsing; using System.Diagnostics; using System.Drawing; using System.Globalization; @@ -55,7 +56,6 @@ private static int Main(string[] args) _settingsUtils = new SettingsUtils(); LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated); - AppDomain.CurrentDomain.ProcessExit += (_, _) => TrayHelper.RunOnMainThread(() => LockMutex?.ReleaseMutex()); Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); @@ -64,7 +64,7 @@ private static int Main(string[] args) string appLanguage = LanguageHelper.LoadLanguage(); if (!string.IsNullOrEmpty(appLanguage)) { - System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage); + Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage); } } catch (CultureNotFoundException ex) @@ -145,6 +145,36 @@ private static int Main(string[] args) IsRequired = false, }; + timeOption.AddValidator(result => + { + if (result.Tokens.Count != 0 && !uint.TryParse(result.Tokens[0].Value, out _)) + { + const string errorMessage = "Interval in --time-limit could not be parsed correctly. Check that the value is valid and doesn't exceed 4,294,967,295."; + Logger.LogError(errorMessage); + result.ErrorMessage = errorMessage; + } + }); + + pidOption.AddValidator(result => + { + if (result.Tokens.Count != 0 && !int.TryParse(result.Tokens[0].Value, out _)) + { + const string errorMessage = "PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits."; + Logger.LogError(errorMessage); + result.ErrorMessage = errorMessage; + } + }); + + expireAtOption.AddValidator(result => + { + if (result.Tokens.Count != 0 && !DateTimeOffset.TryParse(result.Tokens[0].Value, out _)) + { + const string errorMessage = "Date and time value in --expire-at could not be parsed correctly. Check that the value is valid date and time. Refer to https://aka.ms/powertoys/awake for format examples."; + Logger.LogError(errorMessage); + result.ErrorMessage = errorMessage; + } + }); + RootCommand? rootCommand = [ configOption, @@ -210,6 +240,7 @@ private static async void HandleCommandLineArguments(bool usePtConfig, bool disp Manager.StartMonitor(); await TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); + AppDomain.CurrentDomain.ProcessExit += (_, _) => TrayHelper.RunOnMainThread(() => LockMutex?.ReleaseMutex()); EventWaitHandle eventHandle = new(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent()); new Thread(() => From f958b1cf2dba32afc3fe664ba917ce3e2cf70a8b Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 17:38:57 -0800 Subject: [PATCH 39/51] Moving the initialization logic earlier --- doc/planning/awake.md | 3 +++ src/modules/awake/Awake/Program.cs | 15 +++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index d1e74177aa8f..9b465d1df010 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -28,6 +28,9 @@ The build ID moniker is made up of two components - a reference to a [Halo](http - [#35250] Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. - [#35848] Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. - Added configuration safeguards to make sure that invalid values for timed keep-awake times do not result in exceptions. +- Updated the tray initialization logic, making sure we wait for it to be properly created before setting icons. +- Expanded logging capabilities to track invoking functions. +- Added command validation logic to make sure that incorrect command line arguments display an error. ### `PROMETHEAN_09082024` (September 8, 2024) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index ca369b154a0d..4fca7a108e1c 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -51,7 +51,7 @@ internal sealed class Program 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 int Main(string[] args) + private static async Task Main(string[] args) { _settingsUtils = new SettingsUtils(); @@ -72,6 +72,8 @@ private static int Main(string[] args) Logger.LogError("CultureNotFoundException: " + ex.Message); } + await TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); + AppDomain.CurrentDomain.ProcessExit += (_, _) => TrayHelper.RunOnMainThread(() => LockMutex?.ReleaseMutex()); AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; if (!instantiated) @@ -149,7 +151,7 @@ private static int Main(string[] args) { if (result.Tokens.Count != 0 && !uint.TryParse(result.Tokens[0].Value, out _)) { - const string errorMessage = "Interval in --time-limit could not be parsed correctly. Check that the value is valid and doesn't exceed 4,294,967,295."; + string errorMessage = $"Interval in --time-limit could not be parsed correctly. Check that the value is valid and doesn't exceed 4,294,967,295. Value used: {result.Tokens[0].Value}."; Logger.LogError(errorMessage); result.ErrorMessage = errorMessage; } @@ -159,7 +161,7 @@ private static int Main(string[] args) { if (result.Tokens.Count != 0 && !int.TryParse(result.Tokens[0].Value, out _)) { - const string errorMessage = "PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits."; + string errorMessage = $"PID value in --pid could not be parsed correctly. Check that the value is valid and falls within the boundaries of Windows PID process limits. Value used: {result.Tokens[0].Value}."; Logger.LogError(errorMessage); result.ErrorMessage = errorMessage; } @@ -169,7 +171,7 @@ private static int Main(string[] args) { if (result.Tokens.Count != 0 && !DateTimeOffset.TryParse(result.Tokens[0].Value, out _)) { - const string errorMessage = "Date and time value in --expire-at could not be parsed correctly. Check that the value is valid date and time. Refer to https://aka.ms/powertoys/awake for format examples."; + string errorMessage = $"Date and time value in --expire-at could not be parsed correctly. Check that the value is valid date and time. Refer to https://aka.ms/powertoys/awake for format examples. Value used: {result.Tokens[0].Value}."; Logger.LogError(errorMessage); result.ErrorMessage = errorMessage; } @@ -216,7 +218,7 @@ private static void Exit(string message, int exitCode) Manager.CompleteExit(exitCode); } - private static async void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid) + private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt, bool useParentPid) { if (pid == 0 && !useParentPid) { @@ -239,9 +241,6 @@ private static async void HandleCommandLineArguments(bool usePtConfig, bool disp // Start the monitor thread that will be used to track the current state. Manager.StartMonitor(); - await TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); - AppDomain.CurrentDomain.ProcessExit += (_, _) => TrayHelper.RunOnMainThread(() => LockMutex?.ReleaseMutex()); - EventWaitHandle eventHandle = new(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent()); new Thread(() => { From a9fca0c54313705ad667a6f286cc558c1f9cb39f Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 17:45:45 -0800 Subject: [PATCH 40/51] Make sure we show the display state in the tooltip --- doc/planning/awake.md | 1 + src/modules/awake/Awake/Core/Manager.cs | 6 +++--- src/modules/awake/Awake/Properties/Resources.Designer.cs | 9 +++++++++ src/modules/awake/Awake/Properties/Resources.resx | 4 ++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 9b465d1df010..8f28685c900d 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -31,6 +31,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http - Updated the tray initialization logic, making sure we wait for it to be properly created before setting icons. - Expanded logging capabilities to track invoking functions. - Added command validation logic to make sure that incorrect command line arguments display an error. +- Display state now shown in the tray tooltip. ### `PROMETHEAN_09082024` (September 8, 2024) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index a613283da679..703704623f5e 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -180,7 +180,7 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int proc TrayHelper.SetShellIcon( TrayHelper.HiddenWindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}]", + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}]\n{Resources.AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON}: {keepDisplayOn}", _indefiniteIcon, TrayIconAction.Update); @@ -229,7 +229,7 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis Logger.LogInfo($"Starting expirable log for {expireAt}"); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION} - {expireAt}]", _expirableIcon, TrayIconAction.Update); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION} - {expireAt}]\n{Resources.AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON}: {keepDisplayOn}", _expirableIcon, TrayIconAction.Update); Observable.Timer(expireAt - DateTimeOffset.Now).Subscribe( _ => @@ -300,7 +300,7 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", _timedIcon, TrayIconAction.Update); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{Resources.AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON}: {keepDisplayOn}", _timedIcon, TrayIconAction.Update); ulong desiredDuration = (ulong)seconds * 1000; ulong targetDuration = Math.Min(desiredDuration, uint.MaxValue - 1) / 1000; diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs index 77056f6ace78..56579f9e40c0 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -276,6 +276,15 @@ internal static string AWAKE_TRAY_TEXT_INDEFINITE { } } + /// + /// Looks up a localized string similar to Keep display on. + /// + internal static string AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON { + get { + return ResourceManager.GetString("AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON", resourceCulture); + } + } + /// /// Looks up a localized string similar to Passive. /// diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx index 6a3dfd665b87..3e5918358460 100644 --- a/src/modules/awake/Awake/Properties/Resources.resx +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -212,4 +212,8 @@ Bound To Describes the process ID Awake is bound to when running. + + Keep display on + Used to label whether the display is on in the tray. + \ No newline at end of file From ffbddca16d7b19227b14a29f6ed0e9e28f0278f6 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Wed, 4 Dec 2024 17:48:22 -0800 Subject: [PATCH 41/51] Update tray string --- src/modules/awake/Awake/Core/Manager.cs | 2 +- src/modules/awake/Awake/Properties/Resources.Designer.cs | 2 +- src/modules/awake/Awake/Properties/Resources.resx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 703704623f5e..74d2dde4521f 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -176,7 +176,7 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int proc string processText = processId == 0 ? string.Empty - : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING} {processId}"; + : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING}: {processId}"; TrayHelper.SetShellIcon( TrayHelper.HiddenWindowHandle, diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs index 56579f9e40c0..0dee09fe3f9b 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -295,7 +295,7 @@ internal static string AWAKE_TRAY_TEXT_OFF { } /// - /// Looks up a localized string similar to Bound To. + /// Looks up a localized string similar to Bound to. /// internal static string AWAKE_TRAY_TEXT_PID_BINDING { get { diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx index 3e5918358460..35d4485ca6f1 100644 --- a/src/modules/awake/Awake/Properties/Resources.resx +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -209,7 +209,7 @@ Uses the parent process as the bound target - once the process terminates, Awake stops. - Bound To + Bound to Describes the process ID Awake is bound to when running. From 9596adee68a7c9af9a078c9ea321437eb551b97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=94=90?= Date: Fri, 6 Dec 2024 09:46:40 -0800 Subject: [PATCH 42/51] Update src/modules/awake/Awake/Core/Manager.cs Co-authored-by: Jaime Bernardo --- src/modules/awake/Awake/Core/Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 74d2dde4521f..75b89cc83794 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -160,7 +160,7 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int proc currentSettings.Properties.KeepDisplayOn = keepDisplayOn; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); - // We return here because when the settings are save, they will be automatically + // We return here because when the settings are saved, they will be automatically // processed. That means that when they are processed, the indefinite keep-awake will kick-in properly // and we avoid double execution. return; From 16260d805d70baf686475c8d9a6e7b6ceddb460c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=94=90?= Date: Fri, 6 Dec 2024 09:46:49 -0800 Subject: [PATCH 43/51] Update src/modules/awake/Awake/Core/Manager.cs Co-authored-by: Jaime Bernardo --- src/modules/awake/Awake/Core/Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 75b89cc83794..b5821e323f59 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -210,7 +210,7 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis currentSettings.Properties.ExpirationDateTime = expireAt; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); - // We return here because when the settings are save, they will be automatically + // We return here because when the settings are saved, they will be automatically // processed. That means that when they are processed, the expirable keep-awake will kick-in properly // and we avoid double execution. return; From 65a97dfdea89db3645b20a91b664dccdfa61b1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=94=90?= Date: Fri, 6 Dec 2024 09:47:00 -0800 Subject: [PATCH 44/51] Update src/modules/awake/Awake/Core/Manager.cs Co-authored-by: Jaime Bernardo --- src/modules/awake/Awake/Core/Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index b5821e323f59..15c105a5bd38 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -431,7 +431,7 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true, [CallerMemb currentSettings.Properties.Mode = AwakeMode.PASSIVE; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); - // We return here because when the settings are save, they will be automatically + // We return here because when the settings are saved, they will be automatically // processed. That means that when they are processed, the passive keep-awake will kick-in properly // and we avoid double execution. return; From 866c77fefa33fbc091c2c3fe6cef8e0bd3f6f4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Den=20Delimarsky=20=F0=9F=94=90?= Date: Fri, 6 Dec 2024 09:47:10 -0800 Subject: [PATCH 45/51] Update src/modules/awake/Awake/Core/Manager.cs Co-authored-by: Jaime Bernardo --- src/modules/awake/Awake/Core/Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 15c105a5bd38..d356d91f2730 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -284,7 +284,7 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, currentSettings.Properties.IntervalMinutes = remainingMinutes; ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); - // We return here because when the settings are save, they will be automatically + // We return here because when the settings are saved, they will be automatically // processed. That means that when they are processed, the timed keep-awake will kick-in properly // and we avoid double execution. return; From 1c9781101e935c8787ca405ddd08b9ac5e15d1bb Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 6 Dec 2024 11:49:02 -0800 Subject: [PATCH 46/51] Update logic for icon resets --- src/modules/awake/Awake/Core/Manager.cs | 121 +++++++++++++----- src/modules/awake/Awake/Core/Native/Bridge.cs | 3 + .../awake/Awake/Core/Native/Constants.cs | 1 + src/modules/awake/Awake/Core/TrayHelper.cs | 29 ++++- src/modules/awake/Awake/Program.cs | 3 +- .../Awake/Properties/Resources.Designer.cs | 27 ++-- .../awake/Awake/Properties/Resources.resx | 8 +- 7 files changed, 145 insertions(+), 47 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 74d2dde4521f..5611c8aa0791 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -6,7 +6,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; using System.Globalization; using System.IO; using System.Reactive.Linq; @@ -25,7 +24,7 @@ namespace Awake.Core { - public delegate bool ConsoleEventHandler(Models.ControlType ctrlType); + public delegate bool ConsoleEventHandler(ControlType ctrlType); /// /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts @@ -35,20 +34,22 @@ public class Manager { internal static bool IsUsingPowerToysConfig { get; set; } - private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES); - private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS); + internal static SettingsUtils? ModuleSettings { get; set; } - private static readonly BlockingCollection _stateQueue; + private static AwakeMode CurrentOperatingMode { get; set; } - // Core icons used for the tray - private static readonly Icon _timedIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/timed.ico")); - private static readonly Icon _expirableIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/expirable.ico")); - private static readonly Icon _indefiniteIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/indefinite.ico")); - private static readonly Icon _disabledIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/disabled.ico")); + private static bool IsDisplayOn { get; set; } - private static CancellationTokenSource _tokenSource; + private static string ScreenStateString => IsDisplayOn ? Resources.AWAKE_SCREEN_ON : Resources.AWAKE_SCREEN_OFF; - internal static SettingsUtils? ModuleSettings { get; set; } + private static int ProcessId { get; set; } + + private static DateTimeOffset ExpireAt { get; set; } + + private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES); + private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS); + private static readonly BlockingCollection _stateQueue; + private static CancellationTokenSource _tokenSource; static Manager() { @@ -140,6 +141,59 @@ internal static void CancelExistingThread() Logger.LogInfo("Instantiating of new token source and thread token completed."); } + internal static void SetModeShellIcon(bool forceAdd = false) + { + switch (CurrentOperatingMode) + { + case AwakeMode.INDEFINITE: + { + string processText = ProcessId == 0 + ? string.Empty + : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING}: {ProcessId}"; + + TrayHelper.SetShellIcon( + TrayHelper.WindowHandle, + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}][{ScreenStateString}]", + TrayHelper.IndefiniteIcon, + forceAdd ? TrayIconAction.Add : TrayIconAction.Update); + } + + break; + case AwakeMode.PASSIVE: + { + TrayHelper.SetShellIcon( + TrayHelper.WindowHandle, + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", + TrayHelper.DisabledIcon, + forceAdd ? TrayIconAction.Add : TrayIconAction.Update); + } + + break; + + case AwakeMode.EXPIRABLE: + { + TrayHelper.SetShellIcon( + TrayHelper.WindowHandle, + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION}][{ScreenStateString}][{ExpireAt:yyyy-MM-dd HH:mm:ss}]", + TrayHelper.ExpirableIcon, + forceAdd ? TrayIconAction.Add : TrayIconAction.Update); + } + + break; + + case AwakeMode.TIMED: + { + TrayHelper.SetShellIcon( + TrayHelper.WindowHandle, + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}]", + TrayHelper.TimedIcon, + forceAdd ? TrayIconAction.Add : TrayIconAction.Update); + } + + break; + } + } + internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int processId = 0, [CallerMemberName] string callerName = "") { PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); @@ -174,17 +228,13 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int proc Logger.LogInfo($"Indefinite keep-awake starting, invoked by {callerName}..."); - string processText = processId == 0 - ? string.Empty - : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING}: {processId}"; + _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon( - TrayHelper.HiddenWindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}]\n{Resources.AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON}: {keepDisplayOn}", - _indefiniteIcon, - TrayIconAction.Update); + IsDisplayOn = keepDisplayOn; + CurrentOperatingMode = AwakeMode.INDEFINITE; + ProcessId = processId; - _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); + SetModeShellIcon(); } internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true, [CallerMemberName] string callerName = "") @@ -229,7 +279,11 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis Logger.LogInfo($"Starting expirable log for {expireAt}"); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION} - {expireAt}]\n{Resources.AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON}: {keepDisplayOn}", _expirableIcon, TrayIconAction.Update); + IsDisplayOn = keepDisplayOn; + CurrentOperatingMode = AwakeMode.EXPIRABLE; + ExpireAt = expireAt; + + SetModeShellIcon(); Observable.Timer(expireAt - DateTimeOffset.Now).Subscribe( _ => @@ -300,7 +354,10 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{Resources.AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON}: {keepDisplayOn}", _timedIcon, TrayIconAction.Update); + IsDisplayOn = keepDisplayOn; + CurrentOperatingMode = AwakeMode.TIMED; + + SetModeShellIcon(); ulong desiredDuration = (ulong)seconds * 1000; ulong targetDuration = Math.Min(desiredDuration, uint.MaxValue - 1) / 1000; @@ -320,7 +377,11 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, uint timeRemaining = (uint)targetDuration - (uint)elapsedSeconds; if (timeRemaining >= 0) { - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}", _timedIcon, TrayIconAction.Update); + TrayHelper.SetShellIcon( + TrayHelper.WindowHandle, + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}]", + TrayHelper.TimedIcon, + TrayIconAction.Update); } }, () => @@ -351,15 +412,15 @@ internal static void CompleteExit(int exitCode) { SetPassiveKeepAwake(updateSettings: false); - if (TrayHelper.HiddenWindowHandle != IntPtr.Zero) + if (TrayHelper.WindowHandle != IntPtr.Zero) { // Delete the icon. - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, string.Empty, null, TrayIconAction.Delete); + TrayHelper.SetShellIcon(TrayHelper.WindowHandle, string.Empty, null, TrayIconAction.Delete); // Close the message window that we used for the tray. - Bridge.SendMessage(TrayHelper.HiddenWindowHandle, Native.Constants.WM_CLOSE, 0, 0); + Bridge.SendMessage(TrayHelper.WindowHandle, Native.Constants.WM_CLOSE, 0, 0); - Bridge.DestroyWindow(TrayHelper.HiddenWindowHandle); + Bridge.DestroyWindow(TrayHelper.WindowHandle); } Bridge.PostQuitMessage(exitCode); @@ -445,7 +506,9 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true, [CallerMemb Logger.LogInfo($"Passive keep-awake starting..."); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", _disabledIcon, TrayIconAction.Update); + CurrentOperatingMode = AwakeMode.PASSIVE; + + SetModeShellIcon(); } /// diff --git a/src/modules/awake/Awake/Core/Native/Bridge.cs b/src/modules/awake/Awake/Core/Native/Bridge.cs index 0d527594d0be..44812a4ef7ed 100644 --- a/src/modules/awake/Awake/Core/Native/Bridge.cs +++ b/src/modules/awake/Awake/Core/Native/Bridge.cs @@ -106,5 +106,8 @@ internal static extern IntPtr CreateFile( [DllImport("ntdll.dll")] internal static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + internal static extern int RegisterWindowMessage(string lpString); } } diff --git a/src/modules/awake/Awake/Core/Native/Constants.cs b/src/modules/awake/Awake/Core/Native/Constants.cs index ca24ef43bf8f..8598854ba2fd 100644 --- a/src/modules/awake/Awake/Core/Native/Constants.cs +++ b/src/modules/awake/Awake/Core/Native/Constants.cs @@ -11,6 +11,7 @@ internal sealed class Constants internal const uint WM_COMMAND = 0x0111; internal const uint WM_USER = 0x0400U; internal const uint WM_CLOSE = 0x0010; + internal const int WM_CREATE = 0x0001; internal const int WM_DESTROY = 0x0002; internal const int WM_LBUTTONDOWN = 0x0201; internal const int WM_RBUTTONDOWN = 0x0204; diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 129145f58620..37e2de8e48aa 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -32,15 +33,22 @@ internal static class TrayHelper private static NotifyIconData _notifyIconData; private static SingleThreadSynchronizationContext? _syncContext; private static Thread? _mainThread; + private static uint _taskbarCreatedMessage; private static IntPtr TrayMenu { get; set; } - internal static IntPtr HiddenWindowHandle { get; private set; } + internal static IntPtr WindowHandle { get; private set; } + + internal static readonly Icon DefaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")); + internal static readonly Icon TimedIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/timed.ico")); + internal static readonly Icon ExpirableIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/expirable.ico")); + internal static readonly Icon IndefiniteIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/indefinite.ico")); + internal static readonly Icon DisabledIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/disabled.ico")); static TrayHelper() { TrayMenu = IntPtr.Zero; - HiddenWindowHandle = IntPtr.Zero; + WindowHandle = IntPtr.Zero; } private static void ShowContextMenu(IntPtr hWnd) @@ -119,7 +127,7 @@ public static Task InitializeTray(Icon icon, string text) 0, 0, 0, - unchecked(-3), + IntPtr.Zero, IntPtr.Zero, Marshal.GetHINSTANCE(typeof(Program).Module), IntPtr.Zero); @@ -132,7 +140,7 @@ public static Task InitializeTray(Icon icon, string text) // Keep this as a reference because we will need it when we update // the tray icon in the future. - HiddenWindowHandle = hWnd; + WindowHandle = hWnd; Bridge.ShowWindow(hWnd, 0); // SW_HIDE Bridge.UpdateWindow(hWnd); @@ -260,6 +268,13 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar ShowContextMenu(hWnd); } + break; + + case Native.Constants.WM_CREATE: + { + _taskbarCreatedMessage = (uint)Bridge.RegisterWindowMessage("TaskbarCreated"); + } + break; case Native.Constants.WM_DESTROY: // Clean up resources when the window is destroyed @@ -318,6 +333,12 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar break; default: + if (message == _taskbarCreatedMessage) + { + Logger.LogInfo("Taskbar re-created"); + Manager.SetModeShellIcon(forceAdd: true); + } + // Let the default window procedure handle other messages return Bridge.DefWindowProc(hWnd, message, wParam, lParam); } diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 4fca7a108e1c..6135e699e634 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -37,7 +37,6 @@ internal sealed class Program private static readonly JsonSerializerOptions _serializerOptions = new() { IncludeFields = true }; private static readonly ETWTrace _etwTrace = new(); - private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")); private static FileSystemWatcher? _watcher; private static SettingsUtils? _settingsUtils; @@ -72,7 +71,7 @@ private static async Task Main(string[] args) Logger.LogError("CultureNotFoundException: " + ex.Message); } - await TrayHelper.InitializeTray(_defaultAwakeIcon, Core.Constants.FullAppName); + await TrayHelper.InitializeTray(TrayHelper.DefaultAwakeIcon, Core.Constants.FullAppName); AppDomain.CurrentDomain.ProcessExit += (_, _) => TrayHelper.RunOnMainThread(() => LockMutex?.ReleaseMutex()); AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs index 0dee09fe3f9b..1e2b941a6b06 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -259,29 +259,38 @@ internal static string AWAKE_OFF { } /// - /// Looks up a localized string similar to Expiring. + /// Looks up a localized string similar to Off. /// - internal static string AWAKE_TRAY_TEXT_EXPIRATION { + internal static string AWAKE_SCREEN_OFF { get { - return ResourceManager.GetString("AWAKE_TRAY_TEXT_EXPIRATION", resourceCulture); + return ResourceManager.GetString("AWAKE_SCREEN_OFF", resourceCulture); } } /// - /// Looks up a localized string similar to Indefinite. + /// Looks up a localized string similar to On. /// - internal static string AWAKE_TRAY_TEXT_INDEFINITE { + internal static string AWAKE_SCREEN_ON { get { - return ResourceManager.GetString("AWAKE_TRAY_TEXT_INDEFINITE", resourceCulture); + return ResourceManager.GetString("AWAKE_SCREEN_ON", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expiring. + /// + internal static string AWAKE_TRAY_TEXT_EXPIRATION { + get { + return ResourceManager.GetString("AWAKE_TRAY_TEXT_EXPIRATION", resourceCulture); } } /// - /// Looks up a localized string similar to Keep display on. + /// Looks up a localized string similar to Indefinite. /// - internal static string AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON { + internal static string AWAKE_TRAY_TEXT_INDEFINITE { get { - return ResourceManager.GetString("AWAKE_TRAY_TEXT_KEEP_DISPLAY_ON", resourceCulture); + return ResourceManager.GetString("AWAKE_TRAY_TEXT_INDEFINITE", resourceCulture); } } diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx index 35d4485ca6f1..375ac385d1f4 100644 --- a/src/modules/awake/Awake/Properties/Resources.resx +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -212,8 +212,10 @@ Bound to Describes the process ID Awake is bound to when running. - - Keep display on - Used to label whether the display is on in the tray. + + On + + + Off \ No newline at end of file From 9fe6c090b603fde70fa10498868b59779cf5d96d Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 6 Dec 2024 11:53:34 -0800 Subject: [PATCH 47/51] Update doc --- doc/planning/awake.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 8f28685c900d..3f94da021121 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -27,6 +27,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http - [#35250] Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. - [#35848] Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. +- [#34716] Properly recover the state icon in the tray after an `explorer.exe` crash. - Added configuration safeguards to make sure that invalid values for timed keep-awake times do not result in exceptions. - Updated the tray initialization logic, making sure we wait for it to be properly created before setting icons. - Expanded logging capabilities to track invoking functions. From fca5d53c7ff50748d8b165ba32622a632e9f49da Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 6 Dec 2024 11:56:02 -0800 Subject: [PATCH 48/51] Simplify function for setting mode shell icon --- src/modules/awake/Awake/Core/Manager.cs | 58 ++++++++++--------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index c63a4b6798bf..4e0c75d857e7 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.Globalization; using System.IO; using System.Reactive.Linq; @@ -143,55 +144,40 @@ internal static void CancelExistingThread() internal static void SetModeShellIcon(bool forceAdd = false) { + string iconText = string.Empty; + Icon? icon = null; + switch (CurrentOperatingMode) { case AwakeMode.INDEFINITE: - { - string processText = ProcessId == 0 - ? string.Empty - : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING}: {ProcessId}"; - - TrayHelper.SetShellIcon( - TrayHelper.WindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}][{ScreenStateString}]", - TrayHelper.IndefiniteIcon, - forceAdd ? TrayIconAction.Add : TrayIconAction.Update); - } - + string processText = ProcessId == 0 + ? string.Empty + : $" - {Resources.AWAKE_TRAY_TEXT_PID_BINDING}: {ProcessId}"; + iconText = $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}{processText}][{ScreenStateString}]"; + icon = TrayHelper.IndefiniteIcon; break; - case AwakeMode.PASSIVE: - { - TrayHelper.SetShellIcon( - TrayHelper.WindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", - TrayHelper.DisabledIcon, - forceAdd ? TrayIconAction.Add : TrayIconAction.Update); - } + case AwakeMode.PASSIVE: + iconText = $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]"; + icon = TrayHelper.DisabledIcon; break; case AwakeMode.EXPIRABLE: - { - TrayHelper.SetShellIcon( - TrayHelper.WindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION}][{ScreenStateString}][{ExpireAt:yyyy-MM-dd HH:mm:ss}]", - TrayHelper.ExpirableIcon, - forceAdd ? TrayIconAction.Add : TrayIconAction.Update); - } - + iconText = $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION}][{ScreenStateString}][{ExpireAt:yyyy-MM-dd HH:mm:ss}]"; + icon = TrayHelper.ExpirableIcon; break; case AwakeMode.TIMED: - { - TrayHelper.SetShellIcon( - TrayHelper.WindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}]", - TrayHelper.TimedIcon, - forceAdd ? TrayIconAction.Add : TrayIconAction.Update); - } - + iconText = $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}]"; + icon = TrayHelper.TimedIcon; break; } + + TrayHelper.SetShellIcon( + TrayHelper.WindowHandle, + iconText, + icon, + forceAdd ? TrayIconAction.Add : TrayIconAction.Update); } internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int processId = 0, [CallerMemberName] string callerName = "") From 84e143fc53deb02d20d89f1c16d625391126e737 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 6 Dec 2024 12:08:59 -0800 Subject: [PATCH 49/51] Issues should be properly linked --- doc/planning/awake.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 3f94da021121..3c3235eeac3e 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -25,9 +25,9 @@ The build ID moniker is made up of two components - a reference to a [Halo](http >[!NOTE] >See pull request: [Awake - `TILLSON_11272024`](https://github.com/microsoft/PowerToys/pull/36049) -- [#35250] Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. -- [#35848] Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. -- [#34716] Properly recover the state icon in the tray after an `explorer.exe` crash. +- [#35250](https://github.com/microsoft/PowerToys/issues/35250) Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray. +- [#35848](https://github.com/microsoft/PowerToys/issues/35848) Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes. +- [#34716](https://github.com/microsoft/PowerToys/issues/34716) Properly recover the state icon in the tray after an `explorer.exe` crash. - Added configuration safeguards to make sure that invalid values for timed keep-awake times do not result in exceptions. - Updated the tray initialization logic, making sure we wait for it to be properly created before setting icons. - Expanded logging capabilities to track invoking functions. @@ -40,7 +40,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http >See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/34717) - Updating the initialization logic to make sure that settings are respected for proper group policy and single-instance detection. -- [#34148] Fixed a bug from the previous release that incorrectly synchronized threads for shell icon creation and initialized parent PID when it was not parented. +- [#34148](https://github.com/microsoft/PowerToys/issues/34148) Fixed a bug from the previous release that incorrectly synchronized threads for shell icon creation and initialized parent PID when it was not parented. ### `VISEGRADRELAY_08152024` (August 15, 2024) From d4fe624b45fc696013356171e6b1e6e1cb7b8188 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 6 Dec 2024 12:20:22 -0800 Subject: [PATCH 50/51] Minor cleanup --- src/modules/awake/Awake/Core/Manager.cs | 43 ++++++++++++------------- src/modules/awake/Awake/Program.cs | 1 - 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 4e0c75d857e7..62d63f25de14 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -121,25 +121,24 @@ private static ExecutionState ComputeAwakeState(bool keepDisplayOn) internal static void CancelExistingThread() { - Logger.LogInfo($"Attempting to ensure that the thread is properly cleaned up..."); + Logger.LogInfo("Ensuring the thread is properly cleaned up..."); - // Resetting the thread state. + // Reset the thread state and handle cancellation. _stateQueue.Add(ExecutionState.ES_CONTINUOUS); - // Next, make sure that any existing background threads are terminated. if (_tokenSource != null) { _tokenSource.Cancel(); _tokenSource.Dispose(); - - _tokenSource = new CancellationTokenSource(); } else { - Logger.LogWarning("The token source was null."); + Logger.LogWarning("Token source is null."); } - Logger.LogInfo("Instantiating of new token source and thread token completed."); + _tokenSource = new CancellationTokenSource(); + + Logger.LogInfo("New token source and thread token instantiated."); } internal static void SetModeShellIcon(bool forceAdd = false) @@ -260,21 +259,27 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis Logger.LogInfo($"Expirable keep-awake starting..."); - if (expireAt > DateTimeOffset.Now) + if (expireAt <= DateTimeOffset.Now) { - Logger.LogInfo($"Starting expirable log for {expireAt}"); - _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); + Logger.LogError($"The specified target date and time is not in the future. Current time: {DateTimeOffset.Now}, Target time: {expireAt}"); + return; + } - IsDisplayOn = keepDisplayOn; - CurrentOperatingMode = AwakeMode.EXPIRABLE; - ExpireAt = expireAt; + Logger.LogInfo($"Starting expirable log for {expireAt}"); + _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - SetModeShellIcon(); + IsDisplayOn = keepDisplayOn; + CurrentOperatingMode = AwakeMode.EXPIRABLE; + ExpireAt = expireAt; - Observable.Timer(expireAt - DateTimeOffset.Now).Subscribe( + SetModeShellIcon(); + + TimeSpan remainingTime = expireAt - DateTimeOffset.Now; + + Observable.Timer(remainingTime).Subscribe( _ => { - Logger.LogInfo($"Completed expirable keep-awake."); + Logger.LogInfo("Completed expirable keep-awake."); CancelExistingThread(); if (IsUsingPowerToysConfig) @@ -288,12 +293,6 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis } }, _tokenSource.Token); - } - else - { - Logger.LogError("The specified target date and time is not in the future."); - Logger.LogError($"Current time: {DateTimeOffset.Now}\tTarget time: {expireAt}"); - } } internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, [CallerMemberName] string callerName = "") diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 6135e699e634..23882b3018ba 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -6,7 +6,6 @@ using System.CommandLine; using System.CommandLine.Parsing; using System.Diagnostics; -using System.Drawing; using System.Globalization; using System.IO; using System.Linq; From f51cb0a1ef4058b1624302c2dffcb632ff9eae96 Mon Sep 17 00:00:00 2001 From: Den Delimarsky Date: Fri, 6 Dec 2024 13:21:27 -0800 Subject: [PATCH 51/51] Update timed behavior --- doc/planning/awake.md | 1 + src/modules/awake/Awake/Core/Manager.cs | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 3c3235eeac3e..d6ccd6808f39 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -33,6 +33,7 @@ The build ID moniker is made up of two components - a reference to a [Halo](http - Expanded logging capabilities to track invoking functions. - Added command validation logic to make sure that incorrect command line arguments display an error. - Display state now shown in the tray tooltip. +- When timed mode is used, changing the display setting will no longer reset the timer. ### `PROMETHEAN_09082024` (September 8, 2024) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 62d63f25de14..4ce733dfc3e3 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -41,6 +41,8 @@ public class Manager private static bool IsDisplayOn { get; set; } + private static uint TimeRemaining { get; set; } + private static string ScreenStateString => IsDisplayOn ? Resources.AWAKE_SCREEN_ON : Resources.AWAKE_SCREEN_OFF; private static int ProcessId { get; set; } @@ -310,7 +312,7 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); uint totalHours = (uint)timeSpan.TotalHours; - uint remainingMinutes = (uint)(timeSpan.TotalMinutes % 60); + uint remainingMinutes = (uint)Math.Ceiling(timeSpan.TotalMinutes % 60); bool settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED || currentSettings.Properties.IntervalHours != totalHours || @@ -359,12 +361,12 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true, combinedObservable.Subscribe( elapsedSeconds => { - uint timeRemaining = (uint)targetDuration - (uint)elapsedSeconds; - if (timeRemaining >= 0) + TimeRemaining = (uint)targetDuration - (uint)elapsedSeconds; + if (TimeRemaining >= 0) { TrayHelper.SetShellIcon( TrayHelper.WindowHandle, - $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}]", + $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{TimeSpan.FromSeconds(TimeRemaining).ToHumanReadableString()}]", TrayHelper.TimedIcon, TrayIconAction.Update); } @@ -508,6 +510,18 @@ internal static void SetDisplay([CallerMemberName] string callerName = "") { AwakeSettings currentSettings = ModuleSettings!.GetSettings(Constants.AppName) ?? new AwakeSettings(); currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn; + + // We want to make sure that if the display setting changes (e.g., through the tray) + // then we do not reset the counter from zero. Because the settings are only storing + // hours and minutes, we round up the minutes value up when changes occur. + if (CurrentOperatingMode == AwakeMode.TIMED && TimeRemaining > 0) + { + TimeSpan timeSpan = TimeSpan.FromSeconds(TimeRemaining); + + currentSettings.Properties.IntervalHours = (uint)timeSpan.TotalHours; + currentSettings.Properties.IntervalMinutes = (uint)Math.Ceiling(timeSpan.TotalMinutes % 60); + } + ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName); } catch (Exception ex)