diff --git a/src/Windows/Avalonia.Win32/IconImpl.cs b/src/Windows/Avalonia.Win32/IconImpl.cs index 8897fb610e..153298044a 100644 --- a/src/Windows/Avalonia.Win32/IconImpl.cs +++ b/src/Windows/Avalonia.Win32/IconImpl.cs @@ -12,6 +12,12 @@ namespace Avalonia.Win32 private static readonly int s_taskbarIconSize = Win32Platform.WindowsVersion < PlatformConstants.Windows10 ? 32 : 24; + public IconImpl(Win32Icon smallIcon, Win32Icon bigIcon) + { + _smallIcon = smallIcon; + _bigIcon = bigIcon; + } + public IconImpl(Stream smallIcon, Stream bigIcon) { _smallIcon = CreateIconImpl(smallIcon); @@ -23,6 +29,9 @@ namespace Avalonia.Win32 _smallIcon = _bigIcon = CreateIconImpl(icon); } + public Win32Icon SmallIcon => _smallIcon; + public Win32Icon BigIcon => _bigIcon; + private static Win32Icon CreateIconImpl(Stream stream) { if (stream.CanSeek) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 89ae3c5db7..2dbd283399 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1376,6 +1376,10 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool DestroyIcon(IntPtr hIcon); + [DllImport("shell32.dll", CharSet = CharSet.Unicode)] + public static extern int ExtractIconEx(string szExeFileName, int nIconIndex, out IntPtr phiconLarge, + out IntPtr phiconSmall, int nIcons); + [DllImport("user32.dll", EntryPoint = "PeekMessageW", ExactSpelling = true)] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); @@ -1824,6 +1828,28 @@ namespace Avalonia.Win32.Interop } } + [DllImport("kernel32", EntryPoint = "GetModuleFileName", CharSet=CharSet.Unicode, SetLastError = true)] + private static extern uint IntGetModuleFileName(IntPtr hModule, StringBuilder buffer, uint length); + + internal static string? GetModuleFileName(IntPtr hModule) + { + // Environment.ProcessPath is not avaialable on netstandard. + uint length; + var buffer = new StringBuilder(260); + while ((length = IntGetModuleFileName(hModule, buffer, (uint)buffer.Capacity)) >= buffer.Capacity) + { + buffer.EnsureCapacity((int)length); + } + + if (length == 0) + { + return null; + } + + buffer.Length = (int)length; + return buffer.ToString(); + } + [DllImport("kernel32", EntryPoint = "WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable); diff --git a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs index d0da2226cd..f990327917 100644 --- a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs +++ b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs @@ -11,11 +11,16 @@ namespace Avalonia.Win32.Interop; internal class Win32Icon : IDisposable { + public Win32Icon(IntPtr handle) + { + Handle = handle; + } + public Win32Icon(Bitmap bitmap, PixelPoint hotSpot = default) { Handle = CreateIcon(bitmap, hotSpot); } - + public Win32Icon(IBitmapImpl bitmap, PixelPoint hotSpot = default) { using var memoryStream = new MemoryStream(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e34a64a267..5e63f7d2dc 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -55,6 +55,8 @@ namespace Avalonia.Win32 { WindowEdge.West, HitTestValues.HTLEFT } }; + private static readonly Lazy s_defaultIconImpl = new(LoadDefaultIcon); + /// /// The Windows DPI which equates to a of 1.0. /// @@ -782,14 +784,24 @@ namespace Avalonia.Win32 private Win32Icon? LoadIcon(Icons type, uint dpi) { - if (_iconImpl == null) + if (type == Icons.ICON_SMALL2) { - return null; + type = Icons.ICON_SMALL; } - if (type == Icons.ICON_SMALL2) + if (_iconImpl == null) { - type = Icons.ICON_SMALL; + if (s_defaultIconImpl.Value is { } defaultIcon) + { + return type switch + { + Icons.ICON_SMALL => defaultIcon.SmallIcon, + Icons.ICON_BIG => defaultIcon.BigIcon, + _ => throw new NotImplementedException(), + }; + } + + return null; } var iconKey = (type, dpi); @@ -807,6 +819,20 @@ namespace Avalonia.Win32 return icon; } + private static IconImpl? LoadDefaultIcon() + { + if (GetModuleFileName(default) is { } moduleFileName) + { + var iconsCount = ExtractIconEx(moduleFileName, 0, out var largeIcon, out var smallIcon, 1); + if (iconsCount >= 1) + { + return new IconImpl(new Win32Icon(smallIcon), new Win32Icon(largeIcon)); + } + } + + return null; + } + private void RefreshIcon() { SendMessage(_hwnd, (int)WindowsMessage.WM_SETICON, (nint)Icons.ICON_SMALL, LoadIcon(Icons.ICON_SMALL, _dpi)?.Handle ?? default);