mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
<!-- 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.
140 lines
4.5 KiB
C#
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;
|
|
}
|
|
}
|