Add WebSocket heartbeat, client cleanup, and connection toast notifications

- Add heartbeat ping every 5 seconds to detect stale connections
- Replace clients when same IP reconnects instead of accumulating
- Remove unavailable clients on connect and heartbeat probe
- Marshal UI updates to UI thread via SynchronizationContext
- Show balloon toast on device connect/disconnect
- Add disposal safety check for UpdateTrayStatus

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-05-17 12:34:02 +08:00
parent 32ef8fb1ac
commit cbbadd547d
2 changed files with 105 additions and 2 deletions

View File

@@ -10,17 +10,21 @@ public sealed class TrayApplicationContext : ApplicationContext
private readonly NotifyIcon notifyIcon;
private readonly Icon connectedIcon;
private readonly Icon waitIcon;
private readonly SynchronizationContext uiContext;
private ToolStripMenuItem? clipboardPasteMenuItem;
private ToolStripMenuItem? sendInputTypingMenuItem;
private bool? lastConnectionState;
private bool disposed;
public TrayApplicationContext()
{
AppLogger.Info("Tray application starting.");
uiContext = SynchronizationContext.Current ?? new WindowsFormsSynchronizationContext();
keyboard = new KeyboardInjectionService();
var handler = new SyncMessageHandler(keyboard);
server = new WebSocketServerService(handler);
server.StatusChanged += (_, _) => UpdateTrayStatus();
server.StatusChanged += (_, _) => uiContext.Post(_ => UpdateTrayStatus(), null);
discovery = new DiscoveryResponderService(() => server.LocalIpAddress, () => server.Port);
connectedIcon = TrayIconFactory.CreateConnectedIcon();
waitIcon = TrayIconFactory.CreateWaitIcon();
@@ -53,6 +57,7 @@ public sealed class TrayApplicationContext : ApplicationContext
{
if (disposing)
{
disposed = true;
notifyIcon.Dispose();
connectedIcon.Dispose();
waitIcon.Dispose();
@@ -139,10 +144,32 @@ public sealed class TrayApplicationContext : ApplicationContext
private void UpdateTrayStatus()
{
if (disposed)
{
return;
}
var connected = server.HasClient;
var status = connected ? "connected" : "waiting";
var text = $"WirelessTextSyncer {server.LocalIpAddress}:{server.Port} ({status})";
notifyIcon.Icon = connected ? connectedIcon : waitIcon;
notifyIcon.Text = text.Length > 63 ? text[..63] : text;
if (lastConnectionState is not null && lastConnectionState != connected)
{
ShowConnectionToast(connected);
}
lastConnectionState = connected;
}
private void ShowConnectionToast(bool connected)
{
notifyIcon.BalloonTipTitle = "WirelessTextSyncer";
notifyIcon.BalloonTipText = connected
? $"Device connected to {server.LocalIpAddress}:{server.Port}."
: "Device disconnected. Waiting for connection.";
notifyIcon.BalloonTipIcon = connected ? ToolTipIcon.Info : ToolTipIcon.Warning;
notifyIcon.ShowBalloonTip(3000);
}
}