diff --git a/WirelessTextSyncer.Windows/Resources/Icons/tray-connected.png b/WirelessTextSyncer.Windows/Resources/Icons/tray-connected.png new file mode 100644 index 0000000..648b515 Binary files /dev/null and b/WirelessTextSyncer.Windows/Resources/Icons/tray-connected.png differ diff --git a/WirelessTextSyncer.Windows/Resources/Icons/tray-wait.png b/WirelessTextSyncer.Windows/Resources/Icons/tray-wait.png new file mode 100644 index 0000000..8c55c9a Binary files /dev/null and b/WirelessTextSyncer.Windows/Resources/Icons/tray-wait.png differ 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 + + + + + +