Files
TimerApp/ActivityMonitor.cs

314 lines
9.7 KiB
C#

using System;
using System.Windows.Forms;
using Microsoft.Win32;
namespace TimerApp
{
public enum MonitorState
{
Idle,
Working,
Resting
}
public sealed class ActivityMonitor : IDisposable
{
private CancellationTokenSource? _cts;
private readonly object _lock = new object();
private TimeSpan _accumulatedWorkTime;
private int _restElapsedSeconds;
private bool _isPaused;
private bool _disposed;
// 状态检测缓存
private int _checkTickCounter;
private const int CheckIntervalTicks = 3; // 每3秒检查一次空闲状态
private long _cachedIdleMs;
// 配置 (默认值)
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
public TimeSpan RestDuration { get; set; } = TimeSpan.FromMinutes(1);
public TimeSpan IdleThreshold { get; set; } = TimeSpan.FromSeconds(30);
public MonitorState CurrentState { get; private set; } = MonitorState.Idle;
public bool IsPaused
{
get { lock (_lock) return _isPaused; }
private set { lock (_lock) _isPaused = value; }
}
// 事件
public event EventHandler<TimeSpan>? WorkProgressChanged; // 剩余工作时间
public event EventHandler<TimeSpan>? RestProgressChanged; // 剩余休息时间
public event EventHandler? RestStarted;
public event EventHandler? RestEnded;
public event EventHandler? StateChanged;
public ActivityMonitor()
{
}
public void Start()
{
lock (_lock)
{
StopInternal();
_cts = new CancellationTokenSource();
var token = _cts.Token;
// 启动时立即触发一次检测
_checkTickCounter = CheckIntervalTicks;
ResetWork();
Task.Run(() => MonitorLoop(token), token);
}
}
public void Stop()
{
lock (_lock)
{
StopInternal();
}
}
private void StopInternal()
{
if (_cts != null)
{
_cts.Cancel();
_cts.Dispose();
_cts = null;
}
}
private void ResetWork()
{
// Must be called within lock
_accumulatedWorkTime = TimeSpan.Zero;
ChangeState(MonitorState.Idle);
}
private void ChangeState(MonitorState newState)
{
// Must be called within lock
if (CurrentState != newState)
{
CurrentState = newState;
StateChanged?.Invoke(this, EventArgs.Empty);
}
}
private async Task MonitorLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
await Task.Delay(1000, token);
OnTick();
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Logger.LogError("Error in ActivityMonitor MonitorLoop", ex);
}
}
}
private void OnTick()
{
// 在锁外执行耗时的系统检测
long idleMs = 0;
bool shouldUpdate = false;
lock (_lock)
{
// 如果处于暂停状态,不处理计时逻辑
if (_isPaused) return;
// 检查是否需要更新状态
_checkTickCounter++;
if (_checkTickCounter >= CheckIntervalTicks)
{
_checkTickCounter = 0;
// 需要更新,但在锁外进行
shouldUpdate = true;
}
// 如果不需要更新,直接使用缓存值
if (!shouldUpdate)
{
idleMs = _cachedIdleMs;
}
}
// 在锁外执行实际的检测
if (shouldUpdate)
{
idleMs = NativeMethods.GetIdleTime();
// 更新缓存
lock (_lock)
{
_cachedIdleMs = idleMs;
}
}
lock (_lock)
{
// 再次检查暂停状态
if (_isPaused) return;
// 使用(可能是新更新的)缓存值
idleMs = _cachedIdleMs;
TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs);
if (CurrentState == MonitorState.Resting)
{
// 休息模式逻辑
// 使用计数器而不是时间差,避免秒数跳变
_restElapsedSeconds++;
int totalRestSeconds = (int)RestDuration.TotalSeconds;
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
if (remainingSeconds <= 0)
{
// 休息结束
RestEnded?.Invoke(this, EventArgs.Empty);
ResetWork(); // 重新开始工作周期
}
else
{
TimeSpan remainingRest = TimeSpan.FromSeconds(remainingSeconds);
RestProgressChanged?.Invoke(this, remainingRest);
}
}
else
{
// 工作/空闲模式逻辑
bool isUserInactive = idleTime > IdleThreshold;
if (isUserInactive)
{
// 用户确实离开了 -> 进入空闲状态
if (CurrentState == MonitorState.Working)
{
ChangeState(MonitorState.Idle);
}
// 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间),
// 重置工作计时器
if (idleTime > RestDuration)
{
_accumulatedWorkTime = TimeSpan.Zero;
}
}
else
{
// 用户在活动
if (CurrentState == MonitorState.Idle)
{
// 从空闲变为工作
ChangeState(MonitorState.Working);
}
// 正常工作:累加时间
_accumulatedWorkTime += TimeSpan.FromSeconds(1);
// 检查是否达到工作时长
TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime;
if (remainingWork <= TimeSpan.Zero)
{
// 触发休息
_restElapsedSeconds = 0; // 重置休息计数器
ChangeState(MonitorState.Resting);
RestStarted?.Invoke(this, EventArgs.Empty);
}
else
{
WorkProgressChanged?.Invoke(this, remainingWork);
}
}
}
}
}
public void RefreshStatus()
{
lock (_lock)
{
if (CurrentState == MonitorState.Working)
{
TimeSpan remaining = WorkDuration - _accumulatedWorkTime;
WorkProgressChanged?.Invoke(this, remaining);
}
else if (CurrentState == MonitorState.Resting)
{
int totalRestSeconds = (int)RestDuration.TotalSeconds;
int remainingSeconds = totalRestSeconds - _restElapsedSeconds;
if (remainingSeconds < 0) remainingSeconds = 0;
TimeSpan remaining = TimeSpan.FromSeconds(remainingSeconds);
RestProgressChanged?.Invoke(this, remaining);
}
}
}
public void Restart()
{
lock (_lock)
{
_accumulatedWorkTime = TimeSpan.Zero;
_isPaused = false;
// Force state to Working since user manually restarted
ChangeState(MonitorState.Working);
// Immediately refresh UI to show full duration
RefreshStatus();
}
}
public void Pause()
{
lock (_lock)
{
if (!_isPaused && CurrentState != MonitorState.Idle)
{
_isPaused = true;
StateChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public void Resume()
{
lock (_lock)
{
if (_isPaused)
{
_isPaused = false;
StateChanged?.Invoke(this, EventArgs.Empty);
RefreshStatus();
}
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// SystemEvents.PowerModeChanged -= OnPowerModeChanged;
Stop();
GC.SuppressFinalize(this);
}
}
}