diff --git a/ActivityMonitor.cs b/ActivityMonitor.cs index ad9cb74..615b2bf 100644 --- a/ActivityMonitor.cs +++ b/ActivityMonitor.cs @@ -18,6 +18,12 @@ namespace TimerApp private int _restElapsedSeconds; private bool _isPaused; private bool _disposed; + + // 状态检测缓存 + private int _checkTickCounter; + private const int CheckIntervalTicks = 3; // 每3秒检查一次空闲/媒体状态 + private long _cachedIdleMs; + private bool _cachedMediaPlaying; // 配置 (默认值) public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20); @@ -45,6 +51,8 @@ namespace TimerApp public void Start() { + // 启动时立即触发一次检测 + _checkTickCounter = CheckIntervalTicks; _timer.Start(); ResetWork(); } @@ -71,96 +79,116 @@ namespace TimerApp private void Timer_Tick(object? sender, EventArgs e) { - // 如果处于暂停状态,不处理计时逻辑 - if (_isPaused) + try { - return; - } - - long idleMs = NativeMethods.GetIdleTime(); - TimeSpan idleTime = TimeSpan.FromMilliseconds(idleMs); - - if (CurrentState == MonitorState.Resting) - { - // 休息模式逻辑 - // 使用计数器而不是时间差,避免秒数跳变 - _restElapsedSeconds++; - int totalRestSeconds = (int)RestDuration.TotalSeconds; - int remainingSeconds = totalRestSeconds - _restElapsedSeconds; - - if (remainingSeconds <= 0) + // 如果处于暂停状态,不处理计时逻辑 + if (_isPaused) { - // 休息结束 - RestEnded?.Invoke(this, EventArgs.Empty); - ResetWork(); // 重新开始工作周期 + return; } - else + + long idleMs; + TimeSpan idleTime; + + // 优化:降低系统API调用频率 + // 每 CheckIntervalTicks (3) 秒更新一次状态 + _checkTickCounter++; + if (_checkTickCounter >= CheckIntervalTicks) { - TimeSpan remainingRest = TimeSpan.FromSeconds(remainingSeconds); - RestProgressChanged?.Invoke(this, remainingRest); + _checkTickCounter = 0; + _cachedIdleMs = NativeMethods.GetIdleTime(); + _cachedMediaPlaying = NativeMethods.IsMediaPlaying(); } - } - else - { - // 检测是否有视频/媒体正在播放 - bool isMediaPlaying = NativeMethods.IsMediaPlaying(); - // 工作/空闲模式逻辑 - if (idleTime > IdleThreshold || isMediaPlaying) + idleMs = _cachedIdleMs; + idleTime = TimeSpan.FromMilliseconds(idleMs); + + if (CurrentState == MonitorState.Resting) { - // 用户离开了或正在播放视频 - if (CurrentState == MonitorState.Working) - { - // 如果正在工作,但离开了,暂停工作计时? - // 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积 - // 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲 - ChangeState(MonitorState.Idle); - } + // 休息模式逻辑 + // 使用计数器而不是时间差,避免秒数跳变 + _restElapsedSeconds++; + int totalRestSeconds = (int)RestDuration.TotalSeconds; + int remainingSeconds = totalRestSeconds - _restElapsedSeconds; - // 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间), - // 是否应该重置工作计时器? - // 假设用户去开会了1小时,回来应该重新计算20分钟。 - if (idleTime > RestDuration) + if (remainingSeconds <= 0) { - _accumulatedWorkTime = TimeSpan.Zero; - } - - // 如果正在播放视频,不累加工作时间,但保持当前状态 - if (isMediaPlaying && CurrentState == MonitorState.Working) - { - // 保持当前剩余时间不变(不累加,也不减少) - TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime; - WorkProgressChanged?.Invoke(this, remainingWork); - } - } - 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); + // 休息结束 + RestEnded?.Invoke(this, EventArgs.Empty); + ResetWork(); // 重新开始工作周期 } else { - WorkProgressChanged?.Invoke(this, remainingWork); + TimeSpan remainingRest = TimeSpan.FromSeconds(remainingSeconds); + RestProgressChanged?.Invoke(this, remainingRest); } } + else + { + // 使用缓存的媒体播放状态 + bool isMediaPlaying = _cachedMediaPlaying; + + // 工作/空闲模式逻辑 + if (idleTime > IdleThreshold || isMediaPlaying) + { + // 用户离开了或正在播放视频 + if (CurrentState == MonitorState.Working) + { + // 如果正在工作,但离开了,暂停工作计时? + // 简单起见,如果离开时间过长,可以视为一种“休息”,或者只是暂停累积 + // 这里我们简单处理:如果空闲时间超过阈值,状态变为空闲 + ChangeState(MonitorState.Idle); + } + + // 如果在 Idle 状态,且空闲时间非常长(比如超过了休息时间), + // 是否应该重置工作计时器? + // 假设用户去开会了1小时,回来应该重新计算20分钟。 + if (idleTime > RestDuration) + { + _accumulatedWorkTime = TimeSpan.Zero; + } + + // 如果正在播放视频,不累加工作时间,但保持当前状态 + if (isMediaPlaying && CurrentState == MonitorState.Working) + { + // 保持当前剩余时间不变(不累加,也不减少) + TimeSpan remainingWork = WorkDuration - _accumulatedWorkTime; + WorkProgressChanged?.Invoke(this, remainingWork); + } + } + 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); + } + } + } + } + catch (Exception ex) + { + Logger.LogError("Error in ActivityMonitor Timer_Tick", ex); } } diff --git a/Logger.cs b/Logger.cs new file mode 100644 index 0000000..f3dd375 --- /dev/null +++ b/Logger.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using System.Text; + +namespace TimerApp +{ + public static class Logger + { + private static readonly string LogFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "error.log"); + private static readonly object LockObj = new object(); + + public static void LogError(string message, Exception? ex = null) + { + try + { + lock (LockObj) + { + var sb = new StringBuilder(); + sb.AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] ERROR: {message}"); + if (ex != null) + { + sb.AppendLine($"Exception: {ex.Message}"); + sb.AppendLine($"StackTrace: {ex.StackTrace}"); + } + sb.AppendLine(new string('-', 50)); + + File.AppendAllText(LogFile, sb.ToString()); + } + } + catch + { + // Failed to log, nothing we can do + } + } + } +} diff --git a/Program.cs b/Program.cs index 67ceea0..7e0f444 100644 --- a/Program.cs +++ b/Program.cs @@ -5,24 +5,51 @@ static class Program /// /// The main entry point for the application. /// - [STAThread] - static void Main() - { - if (!SingleInstanceManager.TryAcquire(out var instance) || instance is null) + [STAThread] + static void Main() { - SingleInstanceManager.SignalExistingInstance(); - return; + // 设置全局异常处理 + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + Application.ThreadException += Application_ThreadException; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + if (!SingleInstanceManager.TryAcquire(out var instance) || instance is null) + { + SingleInstanceManager.SignalExistingInstance(); + return; + } + + try + { + using (instance) + { + TaskbarIntegration.InitializeProcess(); + ApplicationConfiguration.Initialize(); + TaskbarIntegration.InitializeShortcuts(); + + var mainForm = new MainForm(); + instance.StartServer(mainForm.ActivateFromExternal); + Application.Run(mainForm); + } + } + catch (Exception ex) + { + Logger.LogError("Fatal error in Main", ex); + MessageBox.Show($"程序发生严重错误即将退出:\n{ex.Message}", "TimerApp Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } - using (instance) + private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { - TaskbarIntegration.InitializeProcess(); - ApplicationConfiguration.Initialize(); - TaskbarIntegration.InitializeShortcuts(); - - var mainForm = new MainForm(); - instance.StartServer(mainForm.ActivateFromExternal); - Application.Run(mainForm); + Logger.LogError("Unhandled UI Exception", e.Exception); + // 这里可以选择不退出,或者提示用户 + // MessageBox.Show("发生未知错误,程序将尝试继续运行。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning); } - } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var ex = e.ExceptionObject as Exception; + Logger.LogError("Unhandled Domain Exception" + (e.IsTerminating ? " (Terminating)" : ""), ex); + } + }