[Awake]PROMETHEAN_09082024 - tray icon fixes (#34717)

* 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

* Logic for checks based on #34148

* Update with link to PR

* Small cleanup

* Proper task segmentation in a function

* Cleanup the code

* Fix synchronization context issue

* Update planning doc

* Test disabling caching to see if that manages to pass CI
This commit is contained in:
Den Delimarsky
2024-09-26 07:25:30 -07:00
committed by GitHub
parent 3cdb30c647
commit 49a828236a
6 changed files with 260 additions and 231 deletions

View File

@@ -32,7 +32,7 @@ parameters:
- name: enableMsBuildCaching - name: enableMsBuildCaching
type: boolean type: boolean
displayName: "Enable MSBuild Caching" displayName: "Enable MSBuild Caching"
default: true default: false
- name: runTests - name: runTests
type: boolean type: boolean
displayName: "Run Tests" displayName: "Run Tests"

View File

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

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 = "VISEGRADRELAY_08152024"; internal const string BuildId = "PROMETHEAN_09082024";
} }
} }

View File

@@ -6,17 +6,16 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
namespace Awake.Core.Models namespace Awake.Core.Threading
{ {
internal sealed class SingleThreadSynchronizationContext : SynchronizationContext internal sealed class SingleThreadSynchronizationContext : SynchronizationContext
{ {
private readonly Queue<Tuple<SendOrPostCallback, object>> queue = private readonly Queue<Tuple<SendOrPostCallback, object?>?> queue = new();
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)
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).
{ {
ArgumentNullException.ThrowIfNull(d);
lock (queue) lock (queue)
{ {
queue.Enqueue(Tuple.Create(d, state)); queue.Enqueue(Tuple.Create(d, state));
@@ -28,7 +27,7 @@ namespace Awake.Core.Models
{ {
while (true) while (true)
{ {
Tuple<SendOrPostCallback, object> work; Tuple<SendOrPostCallback, object?>? work;
lock (queue) lock (queue)
{ {
while (queue.Count == 0) while (queue.Count == 0)
@@ -44,17 +43,22 @@ namespace Awake.Core.Models
break; break;
} }
try
{
work.Item1(work.Item2); work.Item1(work.Item2);
} }
catch (Exception e)
{
Console.WriteLine("Error during execution: " + e.Message);
}
}
} }
public void EndMessageLoop() public void EndMessageLoop()
{ {
lock (queue) 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 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); Monitor.Pulse(queue);
} }
} }

View File

@@ -9,10 +9,9 @@ using System.Drawing;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Awake.Core.Models; using Awake.Core.Models;
using Awake.Core.Native; using Awake.Core.Native;
using Awake.Core.Threading;
using Awake.Properties; using Awake.Properties;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
@@ -29,13 +28,13 @@ namespace Awake.Core
internal static class TrayHelper internal static class TrayHelper
{ {
private static NotifyIconData _notifyIconData; private static NotifyIconData _notifyIconData;
private static IntPtr _trayMenu; 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 => _trayMenu; set => _trayMenu = value; }
private static IntPtr _hiddenWindowHandle;
internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; } internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; }
static TrayHelper() static TrayHelper()
@@ -46,51 +45,47 @@ namespace Awake.Core
private static void ShowContextMenu(IntPtr hWnd) private static void ShowContextMenu(IntPtr hWnd)
{ {
if (TrayMenu != IntPtr.Zero) if (TrayMenu == IntPtr.Zero)
{ {
Logger.LogError("Tried to create a context menu while the TrayMenu object is a null pointer. Normal when used in standalone mode.");
return;
}
Bridge.SetForegroundWindow(hWnd); Bridge.SetForegroundWindow(hWnd);
// Get the handle to the context menu associated with the tray icon // Get cursor position and convert it to client coordinates
IntPtr hMenu = TrayMenu;
// Get the current cursor position
Bridge.GetCursorPos(out Models.Point cursorPos); Bridge.GetCursorPos(out Models.Point cursorPos);
Bridge.ScreenToClient(hWnd, ref cursorPos); Bridge.ScreenToClient(hWnd, ref cursorPos);
MenuInfo menuInfo = new() // Set menu information
var menuInfo = new MenuInfo
{ {
CbSize = (uint)Marshal.SizeOf(typeof(MenuInfo)), CbSize = (uint)Marshal.SizeOf<MenuInfo>(),
FMask = Native.Constants.MIM_STYLE, FMask = Native.Constants.MIM_STYLE,
DwStyle = Native.Constants.MNS_AUTO_DISMISS, DwStyle = Native.Constants.MNS_AUTO_DISMISS,
}; };
Bridge.SetMenuInfo(hMenu, ref menuInfo); Bridge.SetMenuInfo(TrayMenu, ref menuInfo);
// Display the context menu at the cursor position // Display the context menu at the cursor position
Bridge.TrackPopupMenuEx( Bridge.TrackPopupMenuEx(
hMenu, TrayMenu,
Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON, Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON,
cursorPos.X, cursorPos.X,
cursorPos.Y, cursorPos.Y,
hWnd, hWnd,
IntPtr.Zero); IntPtr.Zero);
} }
else
{
// 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.");
}
}
public static void InitializeTray(Icon icon, string text) public static void InitializeTray(Icon icon, string text)
{ {
IntPtr hWnd = IntPtr.Zero; IntPtr hWnd = IntPtr.Zero;
// Start the message loop asynchronously // Start the message loop asynchronously
Task.Run(() => _mainThread = new Thread(() =>
{ {
_syncContext = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(_syncContext);
RunOnMainThread(() => RunOnMainThread(() =>
{ {
WndClassEx wcex = new() WndClassEx wcex = new()
@@ -137,24 +132,27 @@ namespace Awake.Core
Bridge.ShowWindow(hWnd, 0); // SW_HIDE Bridge.ShowWindow(hWnd, 0); // SW_HIDE
Bridge.UpdateWindow(hWnd); Bridge.UpdateWindow(hWnd);
Logger.LogInfo($"Created HWND for the window: {hWnd}");
SetShellIcon(hWnd, text, icon); SetShellIcon(hWnd, text, icon);
}); });
}).Wait();
Task.Run(() =>
{
RunOnMainThread(() => RunOnMainThread(() =>
{ {
RunMessageLoop(); RunMessageLoop();
}); });
_syncContext!.BeginMessageLoop();
}); });
_mainThread.IsBackground = true;
_mainThread.Start();
} }
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}"); if (hWnd != IntPtr.Zero && icon != null)
{
int message = Native.Constants.NIM_ADD; int message = Native.Constants.NIM_ADD;
switch (action) switch (action)
@@ -172,8 +170,6 @@ 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)),
@@ -199,6 +195,8 @@ namespace Awake.Core
if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData)) if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData))
{ {
int errorCode = Marshal.GetLastWin32Error(); 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}"); throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}");
} }
@@ -207,6 +205,11 @@ namespace Awake.Core
_notifyIconData = default; _notifyIconData = default;
} }
} }
else
{
Logger.LogInfo($"Cannot set the shell icon - parent window handle is zero or icon is not available. Text: {text} Action: {action}");
}
}
private static void RunMessageLoop() private static void RunMessageLoop()
{ {
@@ -215,6 +218,8 @@ namespace Awake.Core
Bridge.TranslateMessage(ref msg); Bridge.TranslateMessage(ref msg);
Bridge.DispatchMessage(ref msg); Bridge.DispatchMessage(ref msg);
} }
Logger.LogInfo("Message loop terminated.");
} }
private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam) private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
@@ -295,30 +300,20 @@ namespace Awake.Core
internal static void RunOnMainThread(Action action) internal static void RunOnMainThread(Action action)
{ {
var syncContext = new SingleThreadSynchronizationContext(); _syncContext!.Post(
SynchronizationContext.SetSynchronizationContext(syncContext);
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
syncContext.Post(
_ => _ =>
{ {
try try
{ {
Logger.LogInfo($"Thread execution is on: {Environment.CurrentManagedThreadId}");
action(); action();
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine("Error: " + e.Message); Console.WriteLine("Error: " + e.Message);
} }
finally
{
syncContext.EndMessageLoop();
}
}, },
null); null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
syncContext.BeginMessageLoop();
} }
internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys) internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys)
@@ -353,6 +348,7 @@ namespace Awake.Core
private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode) private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode)
{ {
TrayMenu = Bridge.CreatePopupMenu(); TrayMenu = Bridge.CreatePopupMenu();
if (TrayMenu == IntPtr.Zero) if (TrayMenu == IntPtr.Zero)
{ {
return; return;

View File

@@ -53,7 +53,6 @@ namespace Awake
{ {
_settingsUtils = new SettingsUtils(); _settingsUtils = new SettingsUtils();
LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated); LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated);
Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs"));
try try
@@ -71,17 +70,22 @@ namespace Awake
AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher;
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
{
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) 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); 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;
} }
else
if (!instantiated)
{ {
Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1);
}
Logger.LogInfo($"Launching {Core.Constants.AppName}..."); Logger.LogInfo($"Launching {Core.Constants.AppName}...");
Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
Logger.LogInfo($"Build: {Core.Constants.BuildId}"); Logger.LogInfo($"Build: {Core.Constants.BuildId}");
@@ -131,7 +135,7 @@ namespace Awake
IsRequired = false, IsRequired = false,
}; };
var parentPidOption = new Option<bool>(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_PID_OPTION) var parentPidOption = new Option<bool>(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION)
{ {
Arity = ArgumentArity.ZeroOrOne, Arity = ArgumentArity.ZeroOrOne,
IsRequired = false, IsRequired = false,
@@ -152,6 +156,8 @@ namespace Awake
return rootCommand.InvokeAsync(args).Result; return rootCommand.InvokeAsync(args).Result;
} }
}
}
private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e) private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e)
{ {
@@ -180,15 +186,11 @@ namespace Awake
if (pid == 0 && !useParentPid) if (pid == 0 && !useParentPid)
{ {
Logger.LogInfo("No PID specified. Allocating console..."); Logger.LogInfo("No PID specified. Allocating console...");
Manager.AllocateConsole(); AllocateLocalConsole();
_handler += new ConsoleEventHandler(ExitHandler);
Manager.SetConsoleControlHandler(_handler, true);
Trace.Listeners.Add(new ConsoleTraceListener());
} }
else else
{ {
Logger.LogInfo("Starting with PID binding.");
_startedFromPowerToys = true; _startedFromPowerToys = true;
} }
@@ -246,7 +248,7 @@ namespace Awake
} }
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. {ex.Message}");
} }
} }
else if (pid != 0 || useParentPid) else if (pid != 0 || useParentPid)
@@ -307,9 +309,31 @@ namespace Awake
} }
} }
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) private static void ScaffoldConfiguration(string settingsPath)
{ {
try try
{
SetupFileSystemWatcher(settingsPath);
InitializeSettings();
ProcessSettings();
}
catch (Exception ex)
{
Logger.LogError($"An error occurred scaffolding the configuration. Error details: {ex.Message}");
}
}
private static void SetupFileSystemWatcher(string settingsPath)
{ {
var directory = Path.GetDirectoryName(settingsPath)!; var directory = Path.GetDirectoryName(settingsPath)!;
var fileName = Path.GetFileName(settingsPath); var fileName = Path.GetFileName(settingsPath);
@@ -322,29 +346,23 @@ namespace Awake
Filter = fileName, Filter = fileName,
}; };
var mergedObservable = Observable.Merge( Observable.Merge(
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Changed += h, h => _watcher.Changed += h,
h => _watcher.Changed -= h), h => _watcher.Changed -= h),
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>( Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Created += h, h => _watcher.Created += h,
h => _watcher.Created -= h)); h => _watcher.Created -= h))
mergedObservable
.Throttle(TimeSpan.FromMilliseconds(25)) .Throttle(TimeSpan.FromMilliseconds(25))
.SubscribeOn(TaskPoolScheduler.Default) .SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs) .Select(e => e.EventArgs)
.Subscribe(HandleAwakeConfigChange); .Subscribe(HandleAwakeConfigChange);
var settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
TrayHelper.SetTray(settings, _startedFromPowerToys);
ProcessSettings();
} }
catch (Exception ex)
private static void InitializeSettings()
{ {
Logger.LogError($"An error occurred scaffolding the configuration. Error details: {ex.Message}"); var settings = Manager.ModuleSettings?.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
} TrayHelper.SetTray(settings, _startedFromPowerToys);
} }
private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent) private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent)
@@ -364,7 +382,9 @@ namespace Awake
{ {
try try
{ {
var settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? throw new InvalidOperationException("Settings are null."); var settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName)
?? throw new InvalidOperationException("Settings are null.");
Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}"); Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
switch (settings.Properties.Mode) switch (settings.Properties.Mode)
@@ -378,7 +398,7 @@ namespace Awake
break; break;
case AwakeMode.TIMED: 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); Manager.SetTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
break; break;