[Indexer] File icons (#343)

- [x] **Closes:** #316

![image](https://github.com/user-attachments/assets/4daeff29-4f56-41c3-8648-8d866b971382)
This commit is contained in:
Seraphima Zykova
2025-02-07 17:43:40 +01:00
committed by GitHub
parent 4d696e1cfc
commit 595fd3b6ee
6 changed files with 115 additions and 3 deletions

View File

@@ -1541,6 +1541,8 @@ SHFILEOPSTRUCT
SHGDN
SHGDNF
SHGFI
SHGFIICON
SHGFILARGEICON
shinfo
shlwapi
SHNAMEMAPPING

View File

@@ -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,
});
}

View File

@@ -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)

View File

@@ -34,6 +34,7 @@
</PackageReference>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Web.WebView2" />
<PackageReference Include="System.Drawing.Common" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
</ItemGroup>

View File

@@ -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
}
}

View File

@@ -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<IRandomAccessStream> 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<IRandomAccessStream> GetImageThumbnailAsync(string filePath)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
var thumbnail = await file.GetThumbnailAsync(ThumbnailMode.PicturesView);
return thumbnail;
}
}