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