Files
PowerToys/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ThumbnailHelper.cs
Nathan Gill 84296b0d89 [CmdPal] Remove redundant flag to prevent resource leak (#39865)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Remove a redundant flag in the thumbnail helper that leads to a resource
leak.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #39824 
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

Remove the `SHGFI_ICON` flag from the call to
`NativeMethods.SHGetFileInfo`.

This flag opens a handle to the icon (`hIcon`) as well as filling the
index (`iIcon`)
([docs](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfow#shgfi_icon-0x000000100)).
This handle stored in `shinfo.hIcon` is never used by following code,
and is not freed as suggested by the documentation
([docs](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfow#remarks)).

The `SHGFI_ICON` flag also fills `shinfo.iIcon`, which is used later.
However, this is also filled when passing the flag `SHGFI_SYSICONINDEX`,
which we already pass.

Therefore, the `SHGFI_ICON` flag is redundant here, and has been
removed.

Thanks to @Androvald for finding  and suggesting the fix.
2025-06-08 12:06:01 -05:00

140 lines
4.5 KiB
C#

// 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 Task<IRandomAccessStream?> GetThumbnail(string path, bool jumbo = false)
{
var extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
try
{
return ImageExtensions.Contains(extension) ? GetImageThumbnailAsync(path) : GetFileIconStream(path, jumbo);
}
catch (Exception)
{
}
return Task.FromResult<IRandomAccessStream?>(null);
}
// these are windows constants and mangling them is goofy
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private const uint SHGFI_ICON = 0x000000100;
private const uint SHGFI_SHELLICONSIZE = 0x000000004;
private const int SHGFI_SYSICONINDEX = 0x000004000;
private const int SHIL_JUMBO = 4;
private const int ILD_TRANSPARENT = 1;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
#pragma warning restore SA1310 // Field names should not contain underscore
// This will call DestroyIcon on the hIcon passed in.
// Duplicate it if you need it again after this.
private static MemoryStream GetMemoryStreamFromIcon(IntPtr hIcon)
{
var memoryStream = new MemoryStream();
// Ensure disposing the icon before freeing the handle
using (var icon = Icon.FromHandle(hIcon))
{
icon.ToBitmap().Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
}
// Clean up the unmanaged handle without risking a use-after-free.
NativeMethods.DestroyIcon(hIcon);
memoryStream.Position = 0;
return memoryStream;
}
private static async Task<IRandomAccessStream?> GetFileIconStream(string filePath, bool jumbo)
{
nint hIcon = 0;
// If requested, look up the Jumbo icon
if (jumbo)
{
hIcon = GetLargestIcon(filePath);
}
// If we didn't want the JUMBO icon, or didn't find it, fall back to
// the normal icon lookup
if (hIcon == 0)
{
var shinfo = default(NativeMethods.SHFILEINFO);
var hr = NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SHELLICONSIZE);
if (hr == 0 || shinfo.hIcon == 0)
{
return null;
}
hIcon = shinfo.hIcon;
}
if (hIcon == 0)
{
return null;
}
var stream = new InMemoryRandomAccessStream();
using var memoryStream = GetMemoryStreamFromIcon(hIcon); // this will DestroyIcon hIcon
using var outputStream = stream.GetOutputStreamAt(0);
using (var dataWriter = new DataWriter(outputStream))
{
dataWriter.WriteBytes(memoryStream.ToArray());
await dataWriter.StoreAsync();
await dataWriter.FlushAsync();
}
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;
}
private static nint GetLargestIcon(string path)
{
var shinfo = default(NativeMethods.SHFILEINFO);
NativeMethods.SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX);
var hIcon = IntPtr.Zero;
var iID_IImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
IntPtr imageListPtr;
if (NativeMethods.SHGetImageList(SHIL_JUMBO, ref iID_IImageList, out imageListPtr) == 0 && imageListPtr != IntPtr.Zero)
{
hIcon = NativeMethods.ImageList_GetIcon(imageListPtr, shinfo.iIcon, ILD_TRANSPARENT);
}
return hIcon;
}
}