mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 17:56:44 +02:00
[Awake] Fix for countdown timer drift (#41684)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request - Fixes the countdown timer drift issue #41671 - Includes minor refactoring to consolidate identical timer completion code in `SetExpirableKeepAwake` and `SetTimedKeepAwake`. - Removes the ~50 day restriction on timed keep-awake. The timer may now be `uint.MaxValue` seconds, or ~136 years. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #41671 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments This replaces the combined `Observable.Timer` and `Observable.Interval` timers with a single 1-second Interval timer which checks against a fixed expiry time. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Checked that: 1. The timed keep-awake works via the `--time-limit` parameter, and expiry occurs on time. 2. The countdown timer in the systray menu correctly counts down for small values: <img width="386" height="109" alt="image" src="https://github.com/user-attachments/assets/b282dfd8-38e7-48ab-b17c-99756ef73b99" /> 3. The countdown timer in the systray menu counts down for larger values than were previously possible: <img width="380" height="104" alt="image" src="https://github.com/user-attachments/assets/7a807a37-8945-4048-a86c-05e6ac9310a9" /> 4. On a heavily CPU-loaded system, the previous countdown drift does not happen. 5. The expirable keep-awake mode still functions as expected.
This commit is contained in:
@@ -281,21 +281,7 @@ namespace Awake.Core
|
||||
TimeSpan remainingTime = expireAt - DateTimeOffset.Now;
|
||||
|
||||
Observable.Timer(remainingTime).Subscribe(
|
||||
_ =>
|
||||
{
|
||||
Logger.LogInfo("Completed expirable keep-awake.");
|
||||
CancelExistingThread();
|
||||
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
SetPassiveKeepAwake();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("Exiting after expirable keep awake.");
|
||||
CompleteExit(Environment.ExitCode);
|
||||
}
|
||||
},
|
||||
_ => HandleTimerCompletion("expirable"),
|
||||
_tokenSource.Token);
|
||||
}
|
||||
|
||||
@@ -348,49 +334,46 @@ namespace Awake.Core
|
||||
|
||||
SetModeShellIcon();
|
||||
|
||||
ulong desiredDuration = (ulong)seconds * 1000;
|
||||
ulong targetDuration = Math.Min(desiredDuration, uint.MaxValue - 1) / 1000;
|
||||
var targetExpiryTime = DateTimeOffset.Now.AddSeconds(seconds);
|
||||
|
||||
if (desiredDuration > uint.MaxValue)
|
||||
{
|
||||
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<long> timerObservable = Observable.Timer(TimeSpan.FromSeconds(targetDuration));
|
||||
IObservable<long> intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable);
|
||||
IObservable<long> combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1);
|
||||
|
||||
combinedObservable.Subscribe(
|
||||
elapsedSeconds =>
|
||||
{
|
||||
TimeRemaining = (uint)targetDuration - (uint)elapsedSeconds;
|
||||
if (TimeRemaining >= 0)
|
||||
Observable.Interval(TimeSpan.FromSeconds(1))
|
||||
.Select(_ => targetExpiryTime - DateTimeOffset.Now)
|
||||
.TakeWhile(remaining => remaining.TotalSeconds > 0)
|
||||
.Subscribe(
|
||||
remainingTimeSpan =>
|
||||
{
|
||||
TimeRemaining = (uint)remainingTimeSpan.TotalSeconds;
|
||||
|
||||
TrayHelper.SetShellIcon(
|
||||
TrayHelper.WindowHandle,
|
||||
$"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{TimeSpan.FromSeconds(TimeRemaining).ToHumanReadableString()}]",
|
||||
$"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}][{ScreenStateString}][{remainingTimeSpan.ToHumanReadableString()}]",
|
||||
TrayHelper.TimedIcon,
|
||||
TrayIconAction.Update);
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
Logger.LogInfo("Completed timed thread.");
|
||||
CancelExistingThread();
|
||||
},
|
||||
_ => HandleTimerCompletion("timed"),
|
||||
_tokenSource.Token);
|
||||
}
|
||||
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
// If we're using PowerToys settings, we need to make sure that
|
||||
// we just switch over the Passive Keep-Awake.
|
||||
SetPassiveKeepAwake();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("Exiting after timed keep-awake.");
|
||||
CompleteExit(Environment.ExitCode);
|
||||
}
|
||||
},
|
||||
_tokenSource.Token);
|
||||
/// <summary>
|
||||
/// Handles the common logic that should execute when a keep-awake timer completes. Resets
|
||||
/// the application state to Passive if configured; otherwise it exits.
|
||||
/// </summary>
|
||||
private static void HandleTimerCompletion(string timerType)
|
||||
{
|
||||
Logger.LogInfo($"Completed {timerType} keep-awake.");
|
||||
CancelExistingThread();
|
||||
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
// If running under PowerToys settings, just revert to the default Passive state.
|
||||
SetPassiveKeepAwake();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If running as a standalone process, exit cleanly.
|
||||
Logger.LogInfo($"Exiting after {timerType} keep-awake.");
|
||||
CompleteExit(Environment.ExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user