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
+
+
+
+
+
+