diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index 79b69758a9..fd89d668a2 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 c5c0931082..2bf5ff9492 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 diff --git a/doc/planning/awake.md b/doc/planning/awake.md index cfbbbefac9..1f42eaca83 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -10,12 +10,23 @@ 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) + +>[!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) diff --git a/src/modules/awake/Awake/Core/Constants.cs b/src/modules/awake/Awake/Core/Constants.cs index 80a75dc1ed..db3d20d745 100644 --- a/src/modules/awake/Awake/Core/Constants.cs +++ b/src/modules/awake/Awake/Core/Constants.cs @@ -17,6 +17,6 @@ namespace Awake.Core // 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 c5217dcdba..f237250635 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 @@ namespace Awake.Core { Thread monitorThread = new(() => { - Thread.CurrentThread.IsBackground = true; + Thread.CurrentThread.IsBackground = false; while (true) { ExecutionState state = _stateQueue.Take(); @@ -126,10 +128,18 @@ namespace Awake.Core _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 @@ namespace Awake.Core { PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); - CancelExistingThread(); - - _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); + CancelExistingThread(); + _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); + if (IsUsingPowerToysConfig) { try @@ -417,5 +426,18 @@ namespace Awake.Core } } } + + 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/Models/ProcessBasicInformation.cs b/src/modules/awake/Awake/Core/Models/ProcessBasicInformation.cs new file mode 100644 index 0000000000..98d1c4a5b4 --- /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; + } +} diff --git a/src/modules/awake/Awake/Core/Native/Bridge.cs b/src/modules/awake/Awake/Core/Native/Bridge.cs index 2fc2864dec..4be81db855 100644 --- a/src/modules/awake/Awake/Core/Native/Bridge.cs +++ b/src/modules/awake/Awake/Core/Native/Bridge.cs @@ -56,13 +56,13 @@ namespace Awake.Core.Native [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 @@ namespace Awake.Core.Native [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 @@ namespace Awake.Core.Native [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 00000a431f..029df797d2 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -43,11 +43,6 @@ namespace Awake.Core 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 @@ namespace Awake.Core } } - private static void CreateHiddenWindow(Icon icon, string text) + public static void InitializeTray(Icon icon, string text) { IntPtr hWnd = IntPtr.Zero; @@ -143,7 +138,13 @@ namespace Awake.Core Bridge.UpdateWindow(hWnd); SetShellIcon(hWnd, text, icon); + }); + }).Wait(); + Task.Run(() => + { + RunOnMainThread(() => + { RunMessageLoop(); }); }); @@ -151,6 +152,8 @@ namespace Awake.Core 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 @@ namespace Awake.Core 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 229ad84efc..03c7d9d6bc 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -44,6 +44,7 @@ namespace Awake 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 @@ namespace Awake 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 @@ namespace Awake 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 @@ namespace Awake 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 @@ namespace Awake 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(() => @@ -209,12 +218,47 @@ namespace Awake } 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) { 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 +291,6 @@ namespace Awake } } } - - 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 0cdaf23605..be22d149d2 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -114,6 +114,15 @@ namespace Awake.Properties { } } + /// + /// 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 7ef1950128..8bafe1d4db 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