diff --git a/.pipelines/v2/ci.yml b/.pipelines/v2/ci.yml index 268bea7d11..5e7bf26676 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" diff --git a/doc/planning/awake.md b/doc/planning/awake.md index 1f42eaca83..ae01e6f85d 100644 --- a/doc/planning/awake.md +++ b/doc/planning/awake.md @@ -10,13 +10,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`. -| 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) + +>[!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) diff --git a/src/modules/awake/Awake/Core/Constants.cs b/src/modules/awake/Awake/Core/Constants.cs index db3d20d745..1bd6f0a043 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 = "VISEGRADRELAY_08152024"; + internal const string BuildId = "PROMETHEAN_09082024"; } } 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 e2ef3f92c4..4b432320d9 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; 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 @@ namespace Awake.Core.Models { while (true) { - Tuple work; + Tuple? work; lock (queue) { while (queue.Count == 0) @@ -44,7 +43,14 @@ namespace Awake.Core.Models break; } - work.Item1(work.Item2); + try + { + work.Item1(work.Item2); + } + catch (Exception e) + { + Console.WriteLine("Error during execution: " + e.Message); + } } } @@ -52,9 +58,7 @@ namespace Awake.Core.Models { 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 26539db1c8..0545f97a8f 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -9,10 +9,9 @@ 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; @@ -29,13 +28,13 @@ 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 _hiddenWindowHandle; - internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; } static TrayHelper() @@ -46,42 +45,35 @@ namespace Awake.Core 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; - - // Get the current cursor position - 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 - { - // 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."); + return; } + + Bridge.SetForegroundWindow(hWnd); + + // Get cursor position and convert it to client coordinates + Bridge.GetCursorPos(out Models.Point cursorPos); + Bridge.ScreenToClient(hWnd, ref cursorPos); + + // Set menu information + var menuInfo = new MenuInfo + { + 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) @@ -89,8 +81,11 @@ namespace Awake.Core IntPtr hWnd = IntPtr.Zero; // Start the message loop asynchronously - Task.Run(() => + _mainThread = new Thread(() => { + _syncContext = new SingleThreadSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(_syncContext); + RunOnMainThread(() => { WndClassEx wcex = new() @@ -137,74 +132,82 @@ namespace Awake.Core 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) { - 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}"); - - _notifyIconData = new NotifyIconData + switch (action) { - 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 + case TrayIconAction.Update: + message = Native.Constants.NIM_MODIFY; + break; + case TrayIconAction.Delete: + message = Native.Constants.NIM_DELETE; + break; + case TrayIconAction.Add: + default: + break; + } + + if (action == TrayIconAction.Add || action == TrayIconAction.Update) { - CbSize = Marshal.SizeOf(typeof(NotifyIconData)), - HWnd = hWnd, - UId = 1000, - UFlags = 0, - }; - } + _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) + { + _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}"); - if (action == TrayIconAction.Delete) + throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}"); + } + + 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}"); } } @@ -215,6 +218,8 @@ namespace Awake.Core 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) @@ -295,30 +300,20 @@ namespace Awake.Core 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 + _syncContext!.Post( + _ => { - action(); - } - catch (Exception e) - { - 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) @@ -353,6 +348,7 @@ namespace Awake.Core private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode) { TrayMenu = Bridge.CreatePopupMenu(); + if (TrayMenu == IntPtr.Zero) { return; diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 9ecaccd3a7..2dd22db874 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -53,7 +53,6 @@ namespace Awake { _settingsUtils = new SettingsUtils(); LockMutex = new Mutex(true, Core.Constants.AppName, out bool instantiated); - Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); try @@ -71,86 +70,93 @@ namespace Awake AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; - 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; - } - 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; } - - 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) => + else { - Trace.WriteLine($"Task scheduler error: {args.Exception.Message}"); // somebody forgot to check! - args.SetObserved(); - }; + 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 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()}"); - // 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)); + TaskScheduler.UnobservedTaskException += (sender, args) => + { + Trace.WriteLine($"Task scheduler error: {args.Exception.Message}"); // somebody forgot to check! + args.SetObserved(); + }; - Logger.LogInfo("Parsing parameters..."); + // 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)); - var configOption = new Option(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + Logger.LogInfo("Parsing parameters..."); - var displayOption = new Option(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var configOption = new Option(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var timeOption = new Option(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) - { - Arity = ArgumentArity.ExactlyOne, - IsRequired = false, - }; + var displayOption = new Option(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var pidOption = new Option(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var timeOption = new Option(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION) + { + Arity = ArgumentArity.ExactlyOne, + IsRequired = false, + }; - var expireAtOption = new Option(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION) - { - Arity = ArgumentArity.ZeroOrOne, - IsRequired = false, - }; + var pidOption = new Option(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - var parentPidOption = new Option(AliasesParentPidOption, () => true, Resources.AWAKE_CMD_PARENT_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, + }; - RootCommand? rootCommand = - [ - configOption, - displayOption, - timeOption, - pidOption, - expireAtOption, - parentPidOption, - ]; + var parentPidOption = new Option(AliasesParentPidOption, () => false, Resources.AWAKE_CMD_PARENT_PID_OPTION) + { + Arity = ArgumentArity.ZeroOrOne, + IsRequired = false, + }; - rootCommand.Description = Core.Constants.AppName; - rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption); + RootCommand? rootCommand = + [ + configOption, + displayOption, + timeOption, + pidOption, + expireAtOption, + parentPidOption, + ]; - return rootCommand.InvokeAsync(args).Result; + rootCommand.Description = Core.Constants.AppName; + rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption, parentPidOption); + + return rootCommand.InvokeAsync(args).Result; + } + } } private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e) @@ -180,15 +186,11 @@ namespace Awake 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 { + Logger.LogInfo("Starting with PID binding."); _startedFromPowerToys = true; } @@ -246,7 +248,7 @@ namespace Awake } 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) @@ -307,38 +309,22 @@ 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) { 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) @@ -347,6 +333,38 @@ namespace Awake } } + 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 @@ -364,7 +382,9 @@ namespace Awake { 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) @@ -378,7 +398,7 @@ namespace Awake 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;