Awake - VISEGRADRELAY_08152024 (#34316)

* Update with bug fixes for tray icon and support for parent process

* Process information enum

* Update the docs

* Fix spelling

* Make sure that PID is used in PT config flow
This commit is contained in:
Den Delimarsky
2024-08-20 05:24:37 -07:00
committed by GitHub
parent f8269af125
commit 808e6220bc
11 changed files with 154 additions and 51 deletions

View File

@@ -117,6 +117,7 @@
^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$ ^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$
^\Qtools/project_template/ModuleTemplate/resource.h\E$ ^\Qtools/project_template/ModuleTemplate/resource.h\E$
^doc/devdocs/akaLinks\.md$ ^doc/devdocs/akaLinks\.md$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$ ^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$
^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$ ^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$
^src/modules/MouseWithoutBorders/App/Form/.*\.resx$ ^src/modules/MouseWithoutBorders/App/Form/.*\.resx$

View File

@@ -189,7 +189,6 @@ CLIPBOARDUPDATE
CLIPCHILDREN CLIPCHILDREN
CLIPSIBLINGS CLIPSIBLINGS
closesocket closesocket
clrcall
CLSCTX CLSCTX
Clusion Clusion
cmder cmder
@@ -200,8 +199,8 @@ CMINVOKECOMMANDINFO
CMINVOKECOMMANDINFOEX CMINVOKECOMMANDINFOEX
CMock CMock
CMONITORS CMONITORS
cmph
cmpgt cmpgt
cmph
cne cne
CNF CNF
coclass coclass
@@ -247,10 +246,7 @@ countof
cph cph
CPower CPower
cppblog cppblog
cppruntime
cppstd
cppwinrt cppwinrt
CProj
createdump createdump
CREATESCHEDULEDTASK CREATESCHEDULEDTASK
CREATESTRUCT CREATESTRUCT
@@ -605,7 +601,6 @@ hmenu
hmodule hmodule
hmonitor hmonitor
homljgmgpmcbpjbnjpfijnhipfkiclkd homljgmgpmcbpjbnjpfijnhipfkiclkd
HOOKPROC
Hostbackdropbrush Hostbackdropbrush
hotkeycontrol hotkeycontrol
hotkeys hotkeys
@@ -673,7 +668,6 @@ imageresizerinput
imageresizersettings imageresizersettings
imagingdevices imagingdevices
ime ime
imperialounce
inetcpl inetcpl
Infobar Infobar
INFOEXAMPLE INFOEXAMPLE
@@ -934,7 +928,6 @@ MRT
mru mru
mrw mrw
msc msc
msclr
mscorlib mscorlib
msdata msdata
msedge msedge
@@ -1114,6 +1107,7 @@ PATINVERT
PATPAINT PATPAINT
PAUDIO PAUDIO
pbc pbc
pbi
PBlob PBlob
pcb pcb
pcch pcch
@@ -1127,6 +1121,7 @@ pdo
pdto pdto
pdtobj pdtobj
pdw pdw
Peb
pef pef
PElems PElems
Pels Pels
@@ -1506,7 +1501,6 @@ STATICEDGE
STATSTG STATSTG
stdafx stdafx
STDAPI STDAPI
stdcpp
stdcpplatest stdcpplatest
STDMETHODCALLTYPE STDMETHODCALLTYPE
STDMETHODIMP STDMETHODIMP
@@ -1674,7 +1668,6 @@ USERDATA
Userenv Userenv
USESHOWWINDOW USESHOWWINDOW
USESTDHANDLES USESTDHANDLES
usounce
USRDLL USRDLL
UType UType
uuidv uuidv
@@ -1711,6 +1704,7 @@ VIDEOINFOHEADER
viewmodel viewmodel
vih vih
VIRTUALDESK VIRTUALDESK
VISEGRADRELAY
visiblecolorformats visiblecolorformats
Visibletrue Visibletrue
visualeffects visualeffects

View File

@@ -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`. 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 |
|:----------------------------------------------------------|:-----------------| |:-------------------------------------------------------------------|:----------------|
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 | | [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 |
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 | | [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 | | [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| `ARBITER_01312022` | January 31, 2022 | | [`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) ### `DAISY023_04102024` (April 10, 2024)

View File

@@ -17,6 +17,6 @@ namespace Awake.Core
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY // Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
// is representative of the date when the last change was made before // is representative of the date when the last change was made before
// the pull request is issued. // the pull request is issued.
internal const string BuildId = "DAISY023_04102024"; internal const string BuildId = "VISEGRADRELAY_08152024";
} }
} }

View File

@@ -5,10 +5,12 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
@@ -62,7 +64,7 @@ namespace Awake.Core
{ {
Thread monitorThread = new(() => Thread monitorThread = new(() =>
{ {
Thread.CurrentThread.IsBackground = true; Thread.CurrentThread.IsBackground = false;
while (true) while (true)
{ {
ExecutionState state = _stateQueue.Take(); ExecutionState state = _stateQueue.Take();
@@ -126,10 +128,18 @@ namespace Awake.Core
_stateQueue.Add(ExecutionState.ES_CONTINUOUS); _stateQueue.Add(ExecutionState.ES_CONTINUOUS);
// Next, make sure that any existing background threads are terminated. // Next, make sure that any existing background threads are terminated.
_tokenSource.Cancel(); if (_tokenSource != null)
_tokenSource.Dispose(); {
_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."); Logger.LogInfo("Instantiating of new token source and thread token completed.");
} }
@@ -137,12 +147,11 @@ namespace Awake.Core
{ {
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent()); 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); TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update);
CancelExistingThread();
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
if (IsUsingPowerToysConfig) if (IsUsingPowerToysConfig)
{ {
try 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<ProcessBasicInformation>(), out _);
return status != 0 ? null : Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
} }
} }

View File

@@ -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;
}
}

View File

@@ -56,13 +56,13 @@ namespace Awake.Core.Native
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DestroyMenu(IntPtr hMenu); internal static extern bool DestroyMenu(IntPtr hMenu);
[DllImport("user32.dll")] [DllImport("user32.dll", SetLastError = true)]
internal static extern bool DestroyWindow(IntPtr hWnd); internal static extern bool DestroyWindow(IntPtr hWnd);
[DllImport("user32.dll")] [DllImport("user32.dll", SetLastError = true)]
internal static extern void PostQuitMessage(int nExitCode); 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); internal static extern bool Shell_NotifyIcon(int dwMessage, ref NotifyIconData pnid);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
@@ -83,14 +83,14 @@ namespace Awake.Core.Native
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] [DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out Point lpPoint); 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); 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); internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
@@ -100,7 +100,10 @@ namespace Awake.Core.Native
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetMenuInfo(IntPtr hMenu, ref MenuInfo lpcmi); 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); 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);
} }
} }

View File

@@ -43,11 +43,6 @@ namespace Awake.Core
HiddenWindowHandle = IntPtr.Zero; HiddenWindowHandle = IntPtr.Zero;
} }
public static void InitializeTray(string text, Icon icon)
{
CreateHiddenWindow(icon, text);
}
private static void ShowContextMenu(IntPtr hWnd) private static void ShowContextMenu(IntPtr hWnd)
{ {
if (TrayMenu != IntPtr.Zero) 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; IntPtr hWnd = IntPtr.Zero;
@@ -143,7 +138,13 @@ namespace Awake.Core
Bridge.UpdateWindow(hWnd); Bridge.UpdateWindow(hWnd);
SetShellIcon(hWnd, text, icon); SetShellIcon(hWnd, text, icon);
});
}).Wait();
Task.Run(() =>
{
RunOnMainThread(() =>
{
RunMessageLoop(); RunMessageLoop();
}); });
}); });
@@ -151,6 +152,8 @@ namespace Awake.Core
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)
{ {
Logger.LogInfo($"Setting the shell icon.\nText: {text}\nAction: {action}");
int message = Native.Constants.NIM_ADD; int message = Native.Constants.NIM_ADD;
switch (action) switch (action)
@@ -168,6 +171,8 @@ namespace Awake.Core
if (action == TrayIconAction.Add || action == TrayIconAction.Update) if (action == TrayIconAction.Add || action == TrayIconAction.Update)
{ {
Logger.LogInfo($"Adding or updating tray icon. HIcon handle is {icon?.Handle}\nHWnd: {hWnd}");
_notifyIconData = new NotifyIconData _notifyIconData = new NotifyIconData
{ {
CbSize = Marshal.SizeOf(typeof(NotifyIconData)), CbSize = Marshal.SizeOf(typeof(NotifyIconData)),

View File

@@ -44,6 +44,7 @@ namespace Awake
internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"]; internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"];
internal static readonly string[] AliasesPidOption = ["--pid", "-p"]; internal static readonly string[] AliasesPidOption = ["--pid", "-p"];
internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"]; 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 readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico"));
@@ -116,6 +117,12 @@ namespace Awake
IsRequired = false, IsRequired = false,
}; };
var parentPidOption = new Option<bool>(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};
RootCommand? rootCommand = RootCommand? rootCommand =
[ [
configOption, configOption,
@@ -123,10 +130,11 @@ namespace Awake
timeOption, timeOption,
pidOption, pidOption,
expireAtOption, expireAtOption,
parentPidOption,
]; ];
rootCommand.Description = Core.Constants.AppName; 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; return rootCommand.InvokeAsync(args).Result;
} }
@@ -153,9 +161,9 @@ namespace Awake
Manager.CompleteExit(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..."); Logger.LogInfo("No PID specified. Allocating console...");
Manager.AllocateConsole(); Manager.AllocateConsole();
@@ -175,11 +183,12 @@ namespace Awake
Logger.LogInfo($"The value for --time-limit is: {timeLimit}"); Logger.LogInfo($"The value for --time-limit is: {timeLimit}");
Logger.LogInfo($"The value for --pid is: {pid}"); Logger.LogInfo($"The value for --pid is: {pid}");
Logger.LogInfo($"The value for --expire-at is: {expireAt}"); 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. // Start the monitor thread that will be used to track the current state.
Manager.StartMonitor(); 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()); var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent());
new Thread(() => new Thread(() =>
@@ -209,12 +218,47 @@ namespace Awake
} }
ScaffoldConfiguration(settingsPath); 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) 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.\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 else
{ {
// Date-based binding takes precedence over timed configuration, so we want to // 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) private static void ScaffoldConfiguration(string settingsPath)

View File

@@ -114,6 +114,15 @@ namespace Awake.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Uses the parent process as the bound target - once the process terminates, Awake stops..
/// </summary>
internal static string AWAKE_CMD_PARENT_PID_OPTION {
get {
return ResourceManager.GetString("AWAKE_CMD_PARENT_PID_OPTION", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Exit. /// Looks up a localized string similar to Exit.
/// </summary> /// </summary>

View File

@@ -205,4 +205,7 @@
<value>s</value> <value>s</value>
<comment>Used to display number of seconds in the system tray tooltip.</comment> <comment>Used to display number of seconds in the system tray tooltip.</comment>
</data> </data>
<data name="AWAKE_CMD_PARENT_PID_OPTION" xml:space="preserve">
<value>Uses the parent process as the bound target - once the process terminates, Awake stops.</value>
</data>
</root> </root>