Skip to content

Conversation

daverayment
Copy link
Collaborator

Summary of the Pull Request

PR Checklist

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.

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:
image
  1. The countdown timer in the systray menu counts down for larger values than were previously possible:
image
  1. On a heavily CPU-loaded system, the previous countdown drift does not happen.
  2. The expirable keep-awake mode still functions as expected.

This comment has been minimized.

@niels9001 niels9001 requested review from dend and Copilot and removed request for dend September 9, 2025 07:41
@niels9001 niels9001 added this to the PowerToys 0.95 milestone Sep 9, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes countdown timer drift issues in the Awake module by replacing the combined Observable.Timer and Observable.Interval approach with a single interval timer that checks against a fixed expiry time.

  • Consolidates duplicate timer completion logic into a shared HandleTimerCompletion method
  • Removes the ~50 day timer limit, allowing up to ~136 years (uint.MaxValue seconds)
  • Fixes timer drift by using absolute time comparisons instead of cumulative intervals

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

if (TimeRemaining >= 0)
Observable.Interval(TimeSpan.FromSeconds(1))
.Select(_ => targetExpiryTime - DateTimeOffset.Now)
.TakeWhile(remaining => remaining.TotalSeconds > 0)
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition remaining.TotalSeconds > 0 will exclude the case where exactly 0 seconds remain, potentially causing the timer to miss the final update before completion. Consider using >= 0 to ensure the timer displays '0 seconds remaining' before completing.

Suggested change
.TakeWhile(remaining => remaining.TotalSeconds > 0)
.TakeWhile(remaining => remaining.TotalSeconds >= 0)

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At zero seconds, the full duration will have expired and the application will immediately end the timed mode.

The next thing that would happen is that the application would exit completely (if in "standalone" mode), or would enter the Passive mode, which updates the system tray icon straight away, removing the hover text.

The "00d 00h 00m 00s" countdown timer would likely never be seen.

{
TimeRemaining = (uint)remainingTimeSpan.TotalSeconds;
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casting TotalSeconds to uint will truncate negative values to unexpected large numbers if the timer has already expired. Since TakeWhile should prevent negative values, consider adding Math.Max(0, remainingTimeSpan.TotalSeconds) for safety.

Suggested change
TimeRemaining = (uint)remainingTimeSpan.TotalSeconds;
TimeRemaining = (uint)Math.Max(0, remainingTimeSpan.TotalSeconds);

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TakeWhile clause for the Observable.Interval is remaining.TotalSeconds > 0. It is therefore not possible for the "tick" code to be executed with a negative value for TimeRemaining.

        Observable.Interval(TimeSpan.FromSeconds(1))
            .Select(_ => targetExpiryTime - DateTimeOffset.Now)
            .TakeWhile(remaining => remaining.TotalSeconds > 0)    // condition - there must be time remaining
            .Subscribe(
                remainingTimeSpan =>
                {
                    // At this point, the seconds remaining cannot be negative.
                    TimeRemaining = (uint)remainingTimeSpan.TotalSeconds;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Awake] Timed keep-awake may show incorrect time remaining in the system tray
2 participants