diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 16477dd311..09ecd4e4a8 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -1541,6 +1541,8 @@ SHFILEOPSTRUCT
SHGDN
SHGDNF
SHGFI
+SHGFIICON
+SHGFILARGEICON
shinfo
shlwapi
SHNAMEMAPPING
diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs
index ae7dc29a23..4b21cf25be 100644
--- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs
+++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs
@@ -12,6 +12,7 @@ using Microsoft.CmdPal.Ext.Indexer.Indexer;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
+using Windows.Storage.Streams;
namespace Microsoft.CmdPal.Ext.Indexer;
@@ -90,13 +91,21 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
while (!_searchQuery.SearchResults.IsEmpty && _searchQuery.SearchResults.TryDequeue(out result) && ++index <= limit)
{
+ IconInfo icon = null;
+ var stream = ThumbnailHelper.GetThumbnail(result.LaunchUri).Result;
+ if (stream != null)
+ {
+ var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
+ icon = new IconInfo(data, data);
+ }
+
_indexerListItems.Add(new IndexerListItem(new IndexerItem
{
FileName = result.ItemDisplayName,
FullPath = result.LaunchUri,
})
{
- Icon = new IconInfo(result.IsFolder ? "\uE838" : "\uE8E5"),
+ Icon = icon,
});
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/IconCacheService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/IconCacheService.cs
index d80647923f..148d1007a9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/IconCacheService.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/IconCacheService.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation
+// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -28,7 +28,6 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
if (!string.IsNullOrEmpty(icon.Icon))
{
var source = IconPathConverter.IconSourceMUX(icon.Icon, false);
-
return source;
}
else if (icon.Data != null)
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj
index cd3072a069..95dc53b444 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj
@@ -34,6 +34,7 @@
+
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/NativeMethods.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/NativeMethods.cs
new file mode 100644
index 0000000000..030c41d59c
--- /dev/null
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/NativeMethods.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+namespace Microsoft.CommandPalette.Extensions.Toolkit;
+
+internal sealed class NativeMethods
+{
+ [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
+ internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SHFILEINFO
+ {
+#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
+ public IntPtr hIcon;
+ public int iIcon;
+ public uint dwAttributes;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
+ public string szDisplayName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
+ public string szTypeName;
+#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter
+ }
+}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs
new file mode 100644
index 0000000000..4ec677cb3e
--- /dev/null
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Drawing;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using Windows.Storage;
+using Windows.Storage.FileProperties;
+using Windows.Storage.Streams;
+
+namespace Microsoft.CommandPalette.Extensions.Toolkit;
+
+public class ThumbnailHelper
+{
+ private static readonly string[] ImageExtensions =
+ [
+ ".png",
+ ".jpg",
+ ".jpeg",
+ ".gif",
+ ".bmp",
+ ".tiff",
+ ".ico",
+ ];
+
+ public static async Task GetThumbnail(string path)
+ {
+ var extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
+ if (ImageExtensions.Contains(extension))
+ {
+ try
+ {
+ return await GetImageThumbnailAsync(path);
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ return await Task.FromResult(GetFileIconStream(path));
+ }
+
+ private const uint SHGFIICON = 0x000000100;
+ private const uint SHGFILARGEICON = 0x000000000;
+
+ private static IRandomAccessStream GetFileIconStream(string filePath)
+ {
+ var shinfo = default(NativeMethods.SHFILEINFO);
+ NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFIICON | SHGFILARGEICON);
+
+ using var icon = Icon.FromHandle(shinfo.hIcon);
+ var stream = new InMemoryRandomAccessStream();
+ using (var memoryStream = new MemoryStream())
+ {
+ icon.ToBitmap().Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
+ memoryStream.Position = 0;
+
+ using var outputStream = stream.GetOutputStreamAt(0);
+ using var dataWriter = new DataWriter(outputStream);
+ dataWriter.WriteBytes(memoryStream.ToArray());
+ dataWriter.StoreAsync().GetAwaiter().GetResult();
+ }
+
+ return stream;
+ }
+
+ private static async Task GetImageThumbnailAsync(string filePath)
+ {
+ var file = await StorageFile.GetFileFromPathAsync(filePath);
+ var thumbnail = await file.GetThumbnailAsync(ThumbnailMode.PicturesView);
+ return thumbnail;
+ }
+}