From 595fd3b6eef91838e293afa8910ad39b4e901345 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Fri, 7 Feb 2025 17:43:40 +0100 Subject: [PATCH] [Indexer] File icons (#343) - [x] **Closes:** #316 ![image](https://github.com/user-attachments/assets/4daeff29-4f56-41c3-8648-8d866b971382) --- .github/actions/spell-check/expect.txt | 2 + .../Pages/IndexerPage.cs | 11 ++- .../ExtViews/IconCacheService.cs | 3 +- ...t.CommandPalette.Extensions.Toolkit.csproj | 1 + .../NativeMethods.cs | 27 +++++++ .../ThumbnailHelper.cs | 74 +++++++++++++++++++ 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/NativeMethods.cs create mode 100644 src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs 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; + } +}