diff --git a/Robovoice.App/IconExtractor.cs b/Robovoice.App/IconExtractor.cs
new file mode 100644
index 0000000..d8c61bf
--- /dev/null
+++ b/Robovoice.App/IconExtractor.cs
@@ -0,0 +1,56 @@
+using System.Runtime.InteropServices;
+
+namespace Robovoice.App;
+
+internal static class IconExtractor
+{
+ private const string Source = @"%SystemRoot%\System32\mmres.dll";
+ private static readonly int[] CandidateIndices = { 5, 12 };
+
+ public static Icon? TryExtractMicrophone(int size = 32)
+ {
+ string path = Environment.ExpandEnvironmentVariables(Source);
+ if (!File.Exists(path)) return null;
+
+ foreach (int index in CandidateIndices)
+ {
+ if (ExtractIconAt(path, index, size, size) is { } icon)
+ return icon;
+ }
+ return null;
+ }
+
+ private static Icon? ExtractIconAt(string path, int index, int width, int height)
+ {
+ IntPtr[] hicons = new IntPtr[1];
+ IntPtr[] ids = new IntPtr[1];
+
+ int count = PrivateExtractIcons(path, index, width, height, hicons, ids, 1, 0);
+ if (count <= 0 || hicons[0] == IntPtr.Zero) return null;
+
+ try
+ {
+ return Icon.FromHandle(hicons[0]);
+ }
+ catch
+ {
+ DestroyIcon(hicons[0]);
+ return null;
+ }
+ }
+
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern int PrivateExtractIcons(
+ string lpszFile,
+ int nIconIndex,
+ int cxIcon,
+ int cyIcon,
+ IntPtr[] phicon,
+ IntPtr[] phiconId,
+ int nIcons,
+ int flags);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool DestroyIcon(IntPtr hIcon);
+}
diff --git a/Robovoice.App/MainForm.cs b/Robovoice.App/MainForm.cs
index 218e4b7..3eab176 100644
--- a/Robovoice.App/MainForm.cs
+++ b/Robovoice.App/MainForm.cs
@@ -29,6 +29,7 @@ internal sealed partial class MainForm : Form
_config = AppConfig.Load();
Directory.CreateDirectory(AppConfig.AppDataDir);
Directory.CreateDirectory(_voicesDir);
+ Text = $"Robovoice {AppVersion}";
Load += OnLoad;
FormClosing += OnFormClosing;
}
@@ -426,12 +427,26 @@ internal sealed partial class MainForm : Form
if (_trayInit) return;
_trayInit = true;
- _trayIcon = new NotifyIcon
+ var micIcon = IconExtractor.TryExtractMicrophone();
+ if (micIcon is not null)
{
- Icon = SystemIcons.Application,
- Text = "Robovoice",
- Visible = true,
- };
+ _trayIcon = new NotifyIcon
+ {
+ Icon = micIcon,
+ Text = $"Robovoice {AppVersion}",
+ Visible = true,
+ };
+ Icon = micIcon;
+ }
+ else
+ {
+ _trayIcon = new NotifyIcon
+ {
+ Icon = SystemIcons.Application,
+ Text = $"Robovoice {AppVersion}",
+ Visible = true,
+ };
+ }
var menu = new ContextMenuStrip();
menu.Items.Add("Show", null, (_, _) => ShowWindow());
@@ -444,6 +459,9 @@ internal sealed partial class MainForm : Form
_trayIcon.DoubleClick += (_, _) => ShowWindow();
}
+ private static string AppVersion =>
+ typeof(MainForm).Assembly.GetName().Version?.ToString() ?? "0.0";
+
private void OnResize(object? sender, EventArgs e)
{
if (WindowState == FormWindowState.Minimized && chkMinimizeToTray.Checked)
diff --git a/Robovoice.App/Robovoice.App.csproj b/Robovoice.App/Robovoice.App.csproj
index 9602244..6bf6aa7 100644
--- a/Robovoice.App/Robovoice.App.csproj
+++ b/Robovoice.App/Robovoice.App.csproj
@@ -17,6 +17,7 @@
enable
true
enable
+ 0.4.0
\ No newline at end of file