refactor: 项目稳定性优化
This commit is contained in:
@@ -10,17 +10,13 @@ namespace TimerApp
|
|||||||
Resting
|
Resting
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ActivityMonitor
|
public sealed class ActivityMonitor : IDisposable
|
||||||
{
|
{
|
||||||
private System.Windows.Forms.Timer _timer;
|
private readonly System.Windows.Forms.Timer _timer;
|
||||||
private DateTime _lastWorkStartTime;
|
|
||||||
private TimeSpan _accumulatedWorkTime;
|
private TimeSpan _accumulatedWorkTime;
|
||||||
private DateTime _restStartTime;
|
private int _restElapsedSeconds;
|
||||||
private int _restElapsedSeconds; // 休息已过秒数(用于避免时间计算导致的跳变)
|
private bool _isPaused;
|
||||||
private bool _isPaused = false; // 暂停状态
|
private bool _disposed;
|
||||||
private DateTime _pauseStartTime; // 暂停开始时间
|
|
||||||
private TimeSpan _pauseDuration = TimeSpan.Zero; // 累计暂停时间(用于工作计时)
|
|
||||||
private int _restPauseStartSeconds = 0; // 休息暂停时的已过秒数
|
|
||||||
|
|
||||||
// 配置 (默认值)
|
// 配置 (默认值)
|
||||||
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
|
public TimeSpan WorkDuration { get; set; } = TimeSpan.FromMinutes(20);
|
||||||
@@ -31,11 +27,11 @@ namespace TimerApp
|
|||||||
public bool IsPaused { get; private set; } = false;
|
public bool IsPaused { get; private set; } = false;
|
||||||
|
|
||||||
// 事件
|
// 事件
|
||||||
public event EventHandler<TimeSpan> WorkProgressChanged; // 剩余工作时间
|
public event EventHandler<TimeSpan>? WorkProgressChanged; // 剩余工作时间
|
||||||
public event EventHandler<TimeSpan> RestProgressChanged; // 剩余休息时间
|
public event EventHandler<TimeSpan>? RestProgressChanged; // 剩余休息时间
|
||||||
public event EventHandler RestStarted;
|
public event EventHandler? RestStarted;
|
||||||
public event EventHandler RestEnded;
|
public event EventHandler? RestEnded;
|
||||||
public event EventHandler StateChanged;
|
public event EventHandler? StateChanged;
|
||||||
|
|
||||||
public ActivityMonitor()
|
public ActivityMonitor()
|
||||||
{
|
{
|
||||||
@@ -58,7 +54,6 @@ namespace TimerApp
|
|||||||
private void ResetWork()
|
private void ResetWork()
|
||||||
{
|
{
|
||||||
_accumulatedWorkTime = TimeSpan.Zero;
|
_accumulatedWorkTime = TimeSpan.Zero;
|
||||||
_lastWorkStartTime = DateTime.Now;
|
|
||||||
ChangeState(MonitorState.Idle);
|
ChangeState(MonitorState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +66,7 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Timer_Tick(object sender, EventArgs e)
|
private void Timer_Tick(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// 如果处于暂停状态,不处理计时逻辑
|
// 如果处于暂停状态,不处理计时逻辑
|
||||||
if (_isPaused)
|
if (_isPaused)
|
||||||
@@ -142,7 +137,6 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
// 从空闲变为工作
|
// 从空闲变为工作
|
||||||
ChangeState(MonitorState.Working);
|
ChangeState(MonitorState.Working);
|
||||||
_lastWorkStartTime = DateTime.Now;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 累加工作时间
|
// 累加工作时间
|
||||||
@@ -155,7 +149,6 @@ namespace TimerApp
|
|||||||
if (remainingWork <= TimeSpan.Zero)
|
if (remainingWork <= TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
// 触发休息
|
// 触发休息
|
||||||
_restStartTime = DateTime.Now;
|
|
||||||
_restElapsedSeconds = 0; // 重置休息计数器
|
_restElapsedSeconds = 0; // 重置休息计数器
|
||||||
ChangeState(MonitorState.Resting);
|
ChangeState(MonitorState.Resting);
|
||||||
RestStarted?.Invoke(this, EventArgs.Empty);
|
RestStarted?.Invoke(this, EventArgs.Empty);
|
||||||
@@ -171,7 +164,6 @@ namespace TimerApp
|
|||||||
// 用于强制重置或测试
|
// 用于强制重置或测试
|
||||||
public void ForceRest()
|
public void ForceRest()
|
||||||
{
|
{
|
||||||
_restStartTime = DateTime.Now;
|
|
||||||
_restElapsedSeconds = 0; // 重置休息计数器
|
_restElapsedSeconds = 0; // 重置休息计数器
|
||||||
ChangeState(MonitorState.Resting);
|
ChangeState(MonitorState.Resting);
|
||||||
RestStarted?.Invoke(this, EventArgs.Empty);
|
RestStarted?.Invoke(this, EventArgs.Empty);
|
||||||
@@ -198,10 +190,8 @@ namespace TimerApp
|
|||||||
public void Restart()
|
public void Restart()
|
||||||
{
|
{
|
||||||
_accumulatedWorkTime = TimeSpan.Zero;
|
_accumulatedWorkTime = TimeSpan.Zero;
|
||||||
_lastWorkStartTime = DateTime.Now;
|
|
||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
IsPaused = false;
|
IsPaused = false;
|
||||||
_pauseDuration = TimeSpan.Zero;
|
|
||||||
|
|
||||||
// Ensure timer is running
|
// Ensure timer is running
|
||||||
if (!_timer.Enabled) _timer.Start();
|
if (!_timer.Enabled) _timer.Start();
|
||||||
@@ -219,13 +209,6 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
_isPaused = true;
|
_isPaused = true;
|
||||||
IsPaused = true;
|
IsPaused = true;
|
||||||
_pauseStartTime = DateTime.Now;
|
|
||||||
|
|
||||||
// 如果正在休息,记录当前已过秒数
|
|
||||||
if (CurrentState == MonitorState.Resting)
|
|
||||||
{
|
|
||||||
_restPauseStartSeconds = _restElapsedSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
StateChanged?.Invoke(this, EventArgs.Empty);
|
StateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
@@ -238,25 +221,19 @@ namespace TimerApp
|
|||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
IsPaused = false;
|
IsPaused = false;
|
||||||
|
|
||||||
// 计算暂停时长
|
|
||||||
TimeSpan pauseTime = DateTime.Now - _pauseStartTime;
|
|
||||||
|
|
||||||
// 如果正在工作,将暂停时间累加到暂停总时长中
|
|
||||||
// 这样工作时间就不会因为暂停而减少
|
|
||||||
if (CurrentState == MonitorState.Working)
|
|
||||||
{
|
|
||||||
_pauseDuration += pauseTime;
|
|
||||||
}
|
|
||||||
// 如果正在休息,调整已过秒数,使剩余时间保持不变
|
|
||||||
else if (CurrentState == MonitorState.Resting)
|
|
||||||
{
|
|
||||||
// 保持已过秒数不变,这样恢复后剩余时间不会变化
|
|
||||||
// _restElapsedSeconds 保持不变
|
|
||||||
}
|
|
||||||
|
|
||||||
StateChanged?.Invoke(this, EventArgs.Empty);
|
StateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
RefreshStatus();
|
RefreshStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
_timer.Stop();
|
||||||
|
_timer.Tick -= Timer_Tick;
|
||||||
|
_timer.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,26 @@ namespace TimerApp
|
|||||||
public int IdleThresholdSeconds { get; set; } = 30;
|
public int IdleThresholdSeconds { get; set; } = 30;
|
||||||
public bool IsDarkMode { get; set; } = true;
|
public bool IsDarkMode { get; set; } = true;
|
||||||
|
|
||||||
private static string ConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
|
private static string LegacyConfigPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
|
||||||
|
|
||||||
|
private static string ConfigPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TimerApp");
|
||||||
|
return Path.Combine(dir, "settings.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static AppSettings Load()
|
public static AppSettings Load()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(ConfigPath))
|
string path = File.Exists(ConfigPath) ? ConfigPath : LegacyConfigPath;
|
||||||
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
string json = File.ReadAllText(ConfigPath);
|
string json = File.ReadAllText(path);
|
||||||
return JsonSerializer.Deserialize<AppSettings>(json);
|
return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -34,6 +44,7 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(ConfigPath)!);
|
||||||
string json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
|
string json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
|
||||||
File.WriteAllText(ConfigPath, json);
|
File.WriteAllText(ConfigPath, json);
|
||||||
}
|
}
|
||||||
|
|||||||
6
MainForm.Designer.cs
generated
6
MainForm.Designer.cs
generated
@@ -13,9 +13,11 @@ namespace TimerApp
|
|||||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing && (components != null))
|
if (disposing)
|
||||||
{
|
{
|
||||||
components.Dispose();
|
_monitor?.Dispose();
|
||||||
|
_restForm?.Dispose();
|
||||||
|
components?.Dispose();
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|||||||
36
MainForm.cs
36
MainForm.cs
@@ -7,9 +7,9 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
public partial class MainForm : Form
|
public partial class MainForm : Form
|
||||||
{
|
{
|
||||||
private ActivityMonitor _monitor;
|
private ActivityMonitor _monitor = null!;
|
||||||
private AppSettings _settings;
|
private AppSettings _settings;
|
||||||
private RestForm _restForm;
|
private RestForm? _restForm;
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
private Color _darkBg = Color.FromArgb(30, 30, 30);
|
private Color _darkBg = Color.FromArgb(30, 30, 30);
|
||||||
@@ -106,8 +106,8 @@ namespace TimerApp
|
|||||||
// Manual input validation
|
// Manual input validation
|
||||||
txtWork.KeyPress += ValidateDigitInput;
|
txtWork.KeyPress += ValidateDigitInput;
|
||||||
txtRest.KeyPress += ValidateDigitInput;
|
txtRest.KeyPress += ValidateDigitInput;
|
||||||
txtWork.Leave += (s, ev) => ValidateRange((TextBox)s, 1, 120);
|
txtWork.Leave += (s, ev) => ValidateRange((TextBox)s!, 1, 120);
|
||||||
txtRest.Leave += (s, ev) => ValidateRange((TextBox)s, 1, 30);
|
txtRest.Leave += (s, ev) => ValidateRange((TextBox)s!, 1, 30);
|
||||||
|
|
||||||
// Focus handling (remove custom caret)
|
// Focus handling (remove custom caret)
|
||||||
txtWork.KeyDown += TextBox_KeyDown;
|
txtWork.KeyDown += TextBox_KeyDown;
|
||||||
@@ -163,7 +163,7 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateDigitInput(object sender, KeyPressEventArgs e)
|
private void ValidateDigitInput(object? sender, KeyPressEventArgs e)
|
||||||
{
|
{
|
||||||
// Allow control keys (backspace, etc.) and digits
|
// Allow control keys (backspace, etc.) and digits
|
||||||
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
|
if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
|
||||||
@@ -216,7 +216,7 @@ namespace TimerApp
|
|||||||
|
|
||||||
// CustomCaret removed to use system caret with centered text
|
// CustomCaret removed to use system caret with centered text
|
||||||
|
|
||||||
private void TextBox_KeyDown(object sender, KeyEventArgs e)
|
private void TextBox_KeyDown(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.KeyCode == Keys.Enter)
|
if (e.KeyCode == Keys.Enter)
|
||||||
{
|
{
|
||||||
@@ -407,11 +407,11 @@ namespace TimerApp
|
|||||||
_monitor.IdleThreshold = TimeSpan.FromSeconds(_settings.IdleThresholdSeconds);
|
_monitor.IdleThreshold = TimeSpan.FromSeconds(_settings.IdleThresholdSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Monitor_StateChanged(object sender, EventArgs e)
|
private void Monitor_StateChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object, EventArgs>(Monitor_StateChanged), sender, e);
|
Invoke(new Action<object?, EventArgs>(Monitor_StateChanged), sender, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateStatusUI();
|
UpdateStatusUI();
|
||||||
@@ -456,11 +456,11 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Monitor_WorkProgressChanged(object sender, TimeSpan remaining)
|
private void Monitor_WorkProgressChanged(object? sender, TimeSpan remaining)
|
||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object, TimeSpan>(Monitor_WorkProgressChanged), sender, remaining);
|
Invoke(new Action<object?, TimeSpan>(Monitor_WorkProgressChanged), sender, remaining);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lblTimeLeft.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
|
lblTimeLeft.Text = $"{remaining.Minutes:D2}:{remaining.Seconds:D2}";
|
||||||
@@ -476,11 +476,11 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Monitor_RestStarted(object sender, EventArgs e)
|
private void Monitor_RestStarted(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object, EventArgs>(Monitor_RestStarted), sender, e);
|
Invoke(new Action<object?, EventArgs>(Monitor_RestStarted), sender, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,13 +494,13 @@ namespace TimerApp
|
|||||||
_restForm.Show();
|
_restForm.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestForm_SkipRequested(object sender, EventArgs e)
|
private void RestForm_SkipRequested(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_monitor.Stop();
|
_monitor.Stop();
|
||||||
_monitor.Start();
|
_monitor.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Monitor_RestProgressChanged(object sender, TimeSpan remaining)
|
private void Monitor_RestProgressChanged(object? sender, TimeSpan remaining)
|
||||||
{
|
{
|
||||||
if (_restForm != null && !_restForm.IsDisposed && _restForm.Visible)
|
if (_restForm != null && !_restForm.IsDisposed && _restForm.Visible)
|
||||||
{
|
{
|
||||||
@@ -508,11 +508,11 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Monitor_RestEnded(object sender, EventArgs e)
|
private void Monitor_RestEnded(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action<object, EventArgs>(Monitor_RestEnded), sender, e);
|
Invoke(new Action<object?, EventArgs>(Monitor_RestEnded), sender, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,12 +555,12 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
|
private void notifyIcon1_MouseDoubleClick(object? sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
ShowForm();
|
ShowForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItemShow_Click(object sender, EventArgs e)
|
private void toolStripMenuItemShow_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ShowForm();
|
ShowForm();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Windows.Media.Control;
|
using Windows.Media.Control;
|
||||||
|
|
||||||
namespace TimerApp
|
namespace TimerApp
|
||||||
@@ -26,22 +28,17 @@ namespace TimerApp
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static long GetIdleTime()
|
public static long GetIdleTime()
|
||||||
{
|
{
|
||||||
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
|
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO
|
||||||
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
|
{
|
||||||
lastInputInfo.dwTime = 0;
|
cbSize = (uint)Marshal.SizeOf<LASTINPUTINFO>(),
|
||||||
|
dwTime = 0
|
||||||
|
};
|
||||||
|
|
||||||
if (GetLastInputInfo(ref lastInputInfo))
|
if (GetLastInputInfo(ref lastInputInfo))
|
||||||
{
|
{
|
||||||
// Environment.TickCount 可能会在大约 24.9 天后翻转为负数,
|
uint tickCount = GetTickCount();
|
||||||
// 但 GetLastInputInfo 返回的也是 uint (DWORD),所以我们统一转为 long 处理差值
|
uint idleMs = unchecked(tickCount - lastInputInfo.dwTime);
|
||||||
// 或者直接使用 unchecked 减法处理溢出
|
return idleMs;
|
||||||
// 更稳健的做法是使用 GetTickCount64 (Vista+),但 Environment.TickCount 在 .NET Core 3.1+ 已经是 64位了(Environment.TickCount64)
|
|
||||||
// 这里为了兼容性,我们简单处理。注意 GetLastInputInfo 返回的是 uint 毫秒数。
|
|
||||||
|
|
||||||
long envTicks = Environment.TickCount;
|
|
||||||
// 处理 TickCount 翻转问题 (Environment.TickCount 是 int,GetLastInputInfo 是 uint)
|
|
||||||
// 简单的做法:
|
|
||||||
return (long)GetTickCount() - (long)lastInputInfo.dwTime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -56,27 +53,57 @@ namespace TimerApp
|
|||||||
/// <returns>如果有媒体正在播放返回 true,否则返回 false</returns>
|
/// <returns>如果有媒体正在播放返回 true,否则返回 false</returns>
|
||||||
public static bool IsMediaPlaying()
|
public static bool IsMediaPlaying()
|
||||||
{
|
{
|
||||||
try
|
EnsureMediaPlaybackStatusFresh();
|
||||||
{
|
return Volatile.Read(ref _mediaPlaying);
|
||||||
var sessionManager = GlobalSystemMediaTransportControlsSessionManager.RequestAsync().GetAwaiter().GetResult();
|
}
|
||||||
var sessions = sessionManager.GetSessions();
|
|
||||||
|
|
||||||
foreach (var session in sessions)
|
private static bool _mediaPlaying;
|
||||||
|
private static long _mediaLastUpdateMs = -1;
|
||||||
|
private static int _mediaUpdateInProgress;
|
||||||
|
|
||||||
|
private static void EnsureMediaPlaybackStatusFresh()
|
||||||
|
{
|
||||||
|
long now = Environment.TickCount64;
|
||||||
|
long last = Interlocked.Read(ref _mediaLastUpdateMs);
|
||||||
|
if (last >= 0 && now - last < 3000)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref _mediaUpdateInProgress, 1) == 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
bool playing = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var playbackInfo = session.GetPlaybackInfo();
|
var sessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||||
if (playbackInfo.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
|
var sessions = sessionManager.GetSessions();
|
||||||
|
|
||||||
|
foreach (var session in sessions)
|
||||||
{
|
{
|
||||||
return true;
|
var playbackInfo = session.GetPlaybackInfo();
|
||||||
|
if (playbackInfo.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
|
||||||
|
{
|
||||||
|
playing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
catch
|
||||||
catch
|
{
|
||||||
{
|
playing = false;
|
||||||
// 如果 API 调用失败,返回 false(保守处理)
|
}
|
||||||
return false;
|
finally
|
||||||
}
|
{
|
||||||
|
Volatile.Write(ref _mediaPlaying, playing);
|
||||||
return false;
|
Interlocked.Exchange(ref _mediaLastUpdateMs, Environment.TickCount64);
|
||||||
|
Interlocked.Exchange(ref _mediaUpdateInProgress, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
RestForm.cs
14
RestForm.cs
@@ -6,11 +6,11 @@ namespace TimerApp
|
|||||||
{
|
{
|
||||||
public class RestForm : Form
|
public class RestForm : Form
|
||||||
{
|
{
|
||||||
private Label lblMessage;
|
private Label lblMessage = null!;
|
||||||
private Label lblTimer;
|
private Label lblTimer = null!;
|
||||||
private Button btnSkip;
|
private Button btnSkip = null!;
|
||||||
|
|
||||||
public event EventHandler SkipRequested;
|
public event EventHandler? SkipRequested;
|
||||||
|
|
||||||
public RestForm()
|
public RestForm()
|
||||||
{
|
{
|
||||||
@@ -99,12 +99,12 @@ namespace TimerApp
|
|||||||
this.PerformLayout();
|
this.PerformLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestForm_Load(object sender, EventArgs e)
|
private void RestForm_Load(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
CenterControls();
|
CenterControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestForm_Resize(object sender, EventArgs e)
|
private void RestForm_Resize(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
CenterControls();
|
CenterControls();
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ namespace TimerApp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnSkip_Click(object sender, EventArgs e)
|
private void btnSkip_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
SkipRequested?.Invoke(this, EventArgs.Empty);
|
SkipRequested?.Invoke(this, EventArgs.Empty);
|
||||||
this.Close();
|
this.Close();
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ namespace TimerApp
|
|||||||
private const string MutexName = "Local\\TimerApp.SingleInstance";
|
private const string MutexName = "Local\\TimerApp.SingleInstance";
|
||||||
private const string PipeName = "TimerApp.SingleInstancePipe";
|
private const string PipeName = "TimerApp.SingleInstancePipe";
|
||||||
|
|
||||||
private readonly Mutex _mutex;
|
private readonly Mutex? _mutex;
|
||||||
|
private readonly bool _ownsMutex;
|
||||||
|
private readonly bool _enableIpc;
|
||||||
private readonly CancellationTokenSource _cts = new();
|
private readonly CancellationTokenSource _cts = new();
|
||||||
|
|
||||||
private SingleInstanceManager(Mutex mutex)
|
private SingleInstanceManager(Mutex? mutex, bool ownsMutex, bool enableIpc)
|
||||||
{
|
{
|
||||||
_mutex = mutex;
|
_mutex = mutex;
|
||||||
|
_ownsMutex = ownsMutex;
|
||||||
|
_enableIpc = enableIpc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryAcquire(out SingleInstanceManager? manager)
|
public static bool TryAcquire(out SingleInstanceManager? manager)
|
||||||
@@ -32,11 +36,12 @@ namespace TimerApp
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
manager = new SingleInstanceManager(mutex);
|
manager = new SingleInstanceManager(mutex, ownsMutex: true, enableIpc: true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
manager = new SingleInstanceManager(mutex: null, ownsMutex: false, enableIpc: false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,6 +62,7 @@ namespace TimerApp
|
|||||||
|
|
||||||
public void StartServer(Action onShowRequested)
|
public void StartServer(Action onShowRequested)
|
||||||
{
|
{
|
||||||
|
if (!_enableIpc) return;
|
||||||
Task.Run(() => ServerLoop(onShowRequested, _cts.Token));
|
Task.Run(() => ServerLoop(onShowRequested, _cts.Token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,15 +110,17 @@ namespace TimerApp
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mutex.ReleaseMutex();
|
if (_ownsMutex)
|
||||||
|
{
|
||||||
|
_mutex?.ReleaseMutex();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
_mutex.Dispose();
|
_mutex?.Dispose();
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user