From d4b7a23e3810169b5e8322cc740c7cc7db5f198d Mon Sep 17 00:00:00 2001 From: Misaka Date: Fri, 15 May 2026 21:29:55 +0800 Subject: [PATCH] Add desktop diagnostics and resilient message handling --- .../Services/AppLogger.cs | 32 ++++++++++++++++ .../Services/KeyboardInjectionService.cs | 7 +++- .../Services/WebSocketServerService.cs | 37 ++++++++++++++++--- .../Tray/TrayApplicationContext.cs | 2 + 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 WirelessTextSyncer.Windows/Services/AppLogger.cs diff --git a/WirelessTextSyncer.Windows/Services/AppLogger.cs b/WirelessTextSyncer.Windows/Services/AppLogger.cs new file mode 100644 index 0000000..9a98e43 --- /dev/null +++ b/WirelessTextSyncer.Windows/Services/AppLogger.cs @@ -0,0 +1,32 @@ +namespace WirelessTextSyncer.Windows.Services; + +public static class AppLogger +{ + private static readonly object SyncRoot = new(); + private static readonly string LogDirectory = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "WirelessTextSyncer"); + + public static string LogPath => Path.Combine(LogDirectory, "desktop.log"); + + public static void Info(string message) + { + Write("INFO", message); + } + + public static void Error(string message, Exception? exception = null) + { + Write("ERROR", exception is null ? message : $"{message}{Environment.NewLine}{exception}"); + } + + private static void Write(string level, string message) + { + lock (SyncRoot) + { + Directory.CreateDirectory(LogDirectory); + File.AppendAllText( + LogPath, + $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff zzz} [{level}] {message}{Environment.NewLine}"); + } + } +} diff --git a/WirelessTextSyncer.Windows/Services/KeyboardInjectionService.cs b/WirelessTextSyncer.Windows/Services/KeyboardInjectionService.cs index 6d59ec7..0759b72 100644 --- a/WirelessTextSyncer.Windows/Services/KeyboardInjectionService.cs +++ b/WirelessTextSyncer.Windows/Services/KeyboardInjectionService.cs @@ -17,6 +17,7 @@ public sealed class KeyboardInjectionService : IKeyboardInjectionService return; } + AppLogger.Info($"Injecting text length: {text.Length}"); foreach (var character in text) { SendUnicode(character); @@ -25,11 +26,13 @@ public sealed class KeyboardInjectionService : IKeyboardInjectionService public void Backspace() { + AppLogger.Info("Injecting backspace."); SendVirtualKey(VirtualKeyBack); } public void Enter() { + AppLogger.Info("Injecting enter."); SendVirtualKey(VirtualKeyReturn); } @@ -54,7 +57,9 @@ public sealed class KeyboardInjectionService : IKeyboardInjectionService var sent = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf()); if (sent != inputs.Length) { - throw new InvalidOperationException("Windows did not accept all keyboard input events."); + var error = Marshal.GetLastWin32Error(); + AppLogger.Error( + $"Windows accepted {sent}/{inputs.Length} keyboard input events. Win32 error: {error}"); } } diff --git a/WirelessTextSyncer.Windows/Services/WebSocketServerService.cs b/WirelessTextSyncer.Windows/Services/WebSocketServerService.cs index cdc96a2..e3b806b 100644 --- a/WirelessTextSyncer.Windows/Services/WebSocketServerService.cs +++ b/WirelessTextSyncer.Windows/Services/WebSocketServerService.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text.Json; using Fleck; @@ -44,6 +45,7 @@ public sealed class WebSocketServerService : IDisposable { Port = port; LocalIpAddress = ResolveLocalIpAddress(); + AppLogger.Info($"Starting WebSocket server on 0.0.0.0:{Port}. Local IP: {LocalIpAddress}"); server = new WebSocketServer($"ws://0.0.0.0:{Port}"); server.Start(socket => @@ -55,6 +57,7 @@ public sealed class WebSocketServerService : IDisposable clients.Add(socket); } + AppLogger.Info($"Client connected: {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}"); StatusChanged?.Invoke(this, EventArgs.Empty); }; socket.OnClose = () => @@ -64,6 +67,12 @@ public sealed class WebSocketServerService : IDisposable clients.Remove(socket); } + AppLogger.Info($"Client disconnected: {socket.ConnectionInfo.ClientIpAddress}:{socket.ConnectionInfo.ClientPort}"); + StatusChanged?.Invoke(this, EventArgs.Empty); + }; + socket.OnError = exception => + { + AppLogger.Error("WebSocket connection error.", exception); StatusChanged?.Invoke(this, EventArgs.Empty); }; socket.OnMessage = HandleRawMessage; @@ -91,26 +100,42 @@ public sealed class WebSocketServerService : IDisposable { try { + AppLogger.Info($"Received message: {rawMessage}"); var message = JsonSerializer.Deserialize(rawMessage, JsonOptions); if (message is null) { + AppLogger.Info("Ignored empty sync message."); return; } messageHandler.Handle(message); } - catch (JsonException) + catch (JsonException exception) { - return; + AppLogger.Error($"Invalid sync message: {rawMessage}", exception); + } + catch (Exception exception) + { + AppLogger.Error($"Failed to handle sync message: {rawMessage}", exception); } } private static string ResolveLocalIpAddress() { - var host = Dns.GetHostEntry(Dns.GetHostName()); - var address = host.AddressList.FirstOrDefault( - address => address.AddressFamily == AddressFamily.InterNetwork - && !IPAddress.IsLoopback(address)); + var address = NetworkInterface.GetAllNetworkInterfaces() + .Where(networkInterface => + networkInterface.OperationalStatus == OperationalStatus.Up + && networkInterface.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Select(networkInterface => networkInterface.GetIPProperties()) + .Where(properties => properties.GatewayAddresses.Any( + gateway => gateway.Address.AddressFamily == AddressFamily.InterNetwork + && !IPAddress.Any.Equals(gateway.Address))) + .SelectMany(properties => properties.UnicastAddresses) + .Select(unicast => unicast.Address) + .FirstOrDefault(address => + address.AddressFamily == AddressFamily.InterNetwork + && !IPAddress.IsLoopback(address) + && !address.ToString().StartsWith("169.254.", StringComparison.Ordinal)); return address?.ToString() ?? "127.0.0.1"; } diff --git a/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs b/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs index 58f30ed..7b6d411 100644 --- a/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs +++ b/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs @@ -9,6 +9,7 @@ public sealed class TrayApplicationContext : ApplicationContext public TrayApplicationContext() { + AppLogger.Info("Tray application starting."); var keyboard = new KeyboardInjectionService(); var handler = new SyncMessageHandler(keyboard); @@ -31,6 +32,7 @@ public sealed class TrayApplicationContext : ApplicationContext } catch (Exception ex) { + AppLogger.Error("Failed to start tray application.", ex); notifyIcon.BalloonTipTitle = "WirelessTextSyncer"; notifyIcon.BalloonTipText = $"Failed to start: {ex.Message}"; notifyIcon.ShowBalloonTip(5000);