From 0611966be97f4a0d184a18ab2c9cffd090e0c789 Mon Sep 17 00:00:00 2001 From: Misaka Date: Sat, 16 May 2026 21:02:30 +0800 Subject: [PATCH] Add tray status icons --- .../Resources/Icons/tray-connected.png | Bin 0 -> 1568 bytes .../Resources/Icons/tray-wait.png | Bin 0 -> 1701 bytes .../Tray/TrayApplicationContext.cs | 18 +++++-- .../Tray/TrayIconFactory.cs | 50 ++++++++++++++++++ .../WirelessTextSyncer.Windows.csproj | 7 ++- 5 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 WirelessTextSyncer.Windows/Resources/Icons/tray-connected.png create mode 100644 WirelessTextSyncer.Windows/Resources/Icons/tray-wait.png create mode 100644 WirelessTextSyncer.Windows/Tray/TrayIconFactory.cs diff --git a/WirelessTextSyncer.Windows/Resources/Icons/tray-connected.png b/WirelessTextSyncer.Windows/Resources/Icons/tray-connected.png new file mode 100644 index 0000000000000000000000000000000000000000..648b51571aa879ee94ea909c250659b467e2371a GIT binary patch literal 1568 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 literal 0 HcmV?d00001 diff --git a/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs b/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs index 5355c0c..51ed674 100644 --- a/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs +++ b/WirelessTextSyncer.Windows/Tray/TrayApplicationContext.cs @@ -8,6 +8,8 @@ public sealed class TrayApplicationContext : ApplicationContext private readonly WebSocketServerService server; private readonly DiscoveryResponderService discovery; private readonly NotifyIcon notifyIcon; + private readonly Icon connectedIcon; + private readonly Icon waitIcon; private ToolStripMenuItem? clipboardPasteMenuItem; private ToolStripMenuItem? sendInputTypingMenuItem; @@ -18,12 +20,14 @@ public sealed class TrayApplicationContext : ApplicationContext var handler = new SyncMessageHandler(keyboard); server = new WebSocketServerService(handler); - server.StatusChanged += (_, _) => UpdateTrayText(); + server.StatusChanged += (_, _) => UpdateTrayStatus(); discovery = new DiscoveryResponderService(() => server.LocalIpAddress, () => server.Port); + connectedIcon = TrayIconFactory.CreateConnectedIcon(); + waitIcon = TrayIconFactory.CreateWaitIcon(); notifyIcon = new NotifyIcon { - Icon = SystemIcons.Application, + Icon = waitIcon, Text = "WirelessTextSyncer starting...", Visible = true, ContextMenuStrip = BuildMenu() @@ -34,7 +38,7 @@ public sealed class TrayApplicationContext : ApplicationContext { server.Start(8181); discovery.Start(); - UpdateTrayText(); + UpdateTrayStatus(); } catch (Exception ex) { @@ -50,6 +54,8 @@ public sealed class TrayApplicationContext : ApplicationContext if (disposing) { notifyIcon.Dispose(); + connectedIcon.Dispose(); + waitIcon.Dispose(); discovery.Dispose(); server.Dispose(); } @@ -131,10 +137,12 @@ public sealed class TrayApplicationContext : ApplicationContext notifyIcon.ShowBalloonTip(1500); } - private void UpdateTrayText() + private void UpdateTrayStatus() { - var status = server.HasClient ? "connected" : "waiting"; + 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; } } diff --git a/WirelessTextSyncer.Windows/Tray/TrayIconFactory.cs b/WirelessTextSyncer.Windows/Tray/TrayIconFactory.cs new file mode 100644 index 0000000..7f01df3 --- /dev/null +++ b/WirelessTextSyncer.Windows/Tray/TrayIconFactory.cs @@ -0,0 +1,50 @@ +using System.Drawing.Imaging; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace WirelessTextSyncer.Windows.Tray; + +internal static class TrayIconFactory +{ + private const string ConnectedResourceName = "WirelessTextSyncer.TrayConnected.png"; + private const string WaitResourceName = "WirelessTextSyncer.TrayWait.png"; + + public static Icon CreateConnectedIcon() + { + return CreateIconFromResource(ConnectedResourceName); + } + + public static Icon CreateWaitIcon() + { + return CreateIconFromResource(WaitResourceName); + } + + private static Icon CreateIconFromResource(string resourceName) + { + var assembly = Assembly.GetExecutingAssembly(); + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Tray icon resource not found: {resourceName}"); + using var source = new Bitmap(stream); + using var bitmap = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb); + using (var graphics = Graphics.FromImage(bitmap)) + { + graphics.Clear(Color.Transparent); + graphics.DrawImage(source, 0, 0, source.Width, source.Height); + } + + var handle = bitmap.GetHicon(); + try + { + using var icon = Icon.FromHandle(handle); + return (Icon)icon.Clone(); + } + finally + { + DestroyIcon(handle); + } + } + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DestroyIcon(IntPtr handle); +} diff --git a/WirelessTextSyncer.Windows/WirelessTextSyncer.Windows.csproj b/WirelessTextSyncer.Windows/WirelessTextSyncer.Windows.csproj index 05ec6e6..eb1bb5a 100644 --- a/WirelessTextSyncer.Windows/WirelessTextSyncer.Windows.csproj +++ b/WirelessTextSyncer.Windows/WirelessTextSyncer.Windows.csproj @@ -13,4 +13,9 @@ - \ No newline at end of file + + + + + +