Skip to content

Commit b14b559

Browse files
authored
Code Quality: Improved the new Windows thumbnail API (#17071)
1 parent bf553f0 commit b14b559

File tree

9 files changed

+115
-101
lines changed

9 files changed

+115
-101
lines changed

src/Files.App.CsWin32/Extras.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public static unsafe nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nInd
4040

4141
[DllImport("shell32.dll", EntryPoint = "SHUpdateRecycleBinIcon", CharSet = CharSet.Unicode, SetLastError = true)]
4242
public static extern void SHUpdateRecycleBinIcon();
43+
44+
public const int PixelFormat32bppARGB = 2498570;
4345
}
4446

4547
namespace Extras

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,6 @@ DestinationList
219219
IObjectArray
220220
GetCurrentProcessExplicitAppUserModelID
221221
SetCurrentProcessExplicitAppUserModelID
222+
GdipCreateBitmapFromScan0
223+
BITMAP
224+
GetObject

src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,40 @@ public IAsyncEnumerable<IStorableChild> GetQuickAccessFolderAsync(CancellationTo
4646
}
4747

4848
/// <inheritdoc/>
49-
public async IAsyncEnumerable<IStorableChild> GetLogicalDrivesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
49+
public IAsyncEnumerable<IStorableChild> GetLogicalDrivesAsync(CancellationToken cancellationToken = default)
5050
{
51-
var availableDrives = PInvoke.GetLogicalDrives();
52-
if (availableDrives is 0)
53-
yield break;
51+
return GetLogicalDrives().ToAsyncEnumerable();
5452

55-
int count = BitOperations.PopCount(availableDrives);
56-
var driveLetters = new char[count];
57-
58-
count = 0;
59-
char driveLetter = 'A';
60-
while (availableDrives is not 0)
53+
IEnumerable<IStorableChild> GetLogicalDrives()
6154
{
62-
if ((availableDrives & 1) is not 0)
63-
driveLetters[count++] = driveLetter;
55+
var availableDrives = PInvoke.GetLogicalDrives();
56+
if (availableDrives is 0)
57+
yield break;
6458

65-
availableDrives >>= 1;
66-
driveLetter++;
67-
}
59+
int count = BitOperations.PopCount(availableDrives);
60+
var driveLetters = new char[count];
6861

69-
foreach (int letter in driveLetters)
70-
{
71-
cancellationToken.ThrowIfCancellationRequested();
62+
count = 0;
63+
char driveLetter = 'A';
64+
while (availableDrives is not 0)
65+
{
66+
if ((availableDrives & 1) is not 0)
67+
driveLetters[count++] = driveLetter;
7268

73-
if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
74-
throw new InvalidOperationException();
69+
availableDrives >>= 1;
70+
driveLetter++;
71+
}
7572

76-
yield return new WindowsFolder(driveRoot.ThisPtr);
77-
await Task.Yield();
78-
}
73+
foreach (char letter in driveLetters)
74+
{
75+
cancellationToken.ThrowIfCancellationRequested();
76+
77+
if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
78+
throw new InvalidOperationException();
7979

80+
yield return new WindowsFolder(driveRoot.ThisPtr);
81+
}
82+
}
8083
}
8184

8285
/// <inheritdoc/>

src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Files.App.Storage
88
{
9-
public interface IWindowsStorable
9+
public interface IWindowsStorable : IDisposable
1010
{
1111
ComPtr<IShellItem> ThisPtr { get; }
1212
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Files.App.Storage
99
{
1010
[DebuggerDisplay("{" + nameof(ToString) + "()}")]
11-
public sealed class WindowsFile : WindowsStorable, IChildFile, IDisposable
11+
public sealed class WindowsFile : WindowsStorable, IChildFile
1212
{
1313
public WindowsFile(ComPtr<IShellItem> nativeObject)
1414
{
@@ -19,13 +19,5 @@ public Task<Stream> OpenStreamAsync(FileAccess accessMode, CancellationToken can
1919
{
2020
throw new NotImplementedException();
2121
}
22-
23-
// Disposer
24-
25-
/// <inheritdoc/>
26-
public void Dispose()
27-
{
28-
ThisPtr.Dispose();
29-
}
3022
}
3123
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs

Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
namespace Files.App.Storage
1111
{
12-
[DebuggerDisplay("{" + nameof(ToString) + "()")]
13-
public sealed class WindowsFolder : WindowsStorable, IChildFolder, IDisposable
12+
[DebuggerDisplay("{" + nameof(ToString) + "()}")]
13+
public sealed class WindowsFolder : WindowsStorable, IChildFolder
1414
{
1515
public WindowsFolder(ComPtr<IShellItem> nativeObject)
1616
{
@@ -26,81 +26,64 @@ public unsafe WindowsFolder(IShellItem* nativeObject)
2626

2727
public unsafe WindowsFolder(Guid folderId)
2828
{
29-
Guid folderIdLocal = folderId;
30-
Guid IID_IShellItem = IShellItem.IID_Guid;
3129
ComPtr<IShellItem> pItem = default;
3230

33-
HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderIdLocal, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, &IID_IShellItem, (void**)pItem.GetAddressOf());
34-
if (hr.Succeeded)
31+
HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, IID.IID_IShellItem, (void**)pItem.GetAddressOf());
32+
if (hr.Failed)
3533
{
36-
ThisPtr = pItem;
37-
return;
38-
}
34+
fixed (char* pszShellPath = $"Shell:::{folderId:B}")
35+
hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, IID.IID_IShellItem, (void**)pItem.GetAddressOf());
3936

40-
fixed (char* pszShellPath = $"Shell:::{folderId:B}")
41-
hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, &IID_IShellItem, (void**)pItem.GetAddressOf());
42-
if (hr.Succeeded)
43-
{
44-
ThisPtr = pItem;
45-
return;
37+
// Invalid FOLDERID; this should never happen.
38+
hr.ThrowOnFailure();
4639
}
40+
41+
ThisPtr = pItem;
4742
}
4843

49-
public async IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type = StorableType.All, [EnumeratorCancellation] CancellationToken cancellationToken = default)
44+
public IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type = StorableType.All, CancellationToken cancellationToken = default)
5045
{
51-
using ComPtr<IEnumShellItems> pEnumShellItems = GetEnumShellItems();
52-
while (GetNext(pEnumShellItems) is { } pShellItem && !pShellItem.IsNull)
53-
{
54-
cancellationToken.ThrowIfCancellationRequested();
46+
return GetItems().ToAsyncEnumerable();
5547

56-
var pShellFolder = pShellItem.As<IShellFolder>();
57-
var isFolder = IsFolder(pShellItem);
48+
unsafe IEnumerable<IStorableChild> GetItems()
49+
{
50+
ComPtr<IEnumShellItems> pEnumShellItems = default;
51+
GetEnumerator();
5852

59-
if (type is StorableType.File && !isFolder)
53+
ComPtr<IShellItem> pShellItem = default;
54+
while (GetNext() && !pShellItem.IsNull)
6055
{
61-
yield return new WindowsFile(pShellItem);
56+
cancellationToken.ThrowIfCancellationRequested();
57+
var isFolder = pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER);
58+
59+
if (type is StorableType.File && !isFolder)
60+
{
61+
yield return new WindowsFile(pShellItem);
62+
}
63+
else if (type is StorableType.Folder && isFolder)
64+
{
65+
yield return new WindowsFolder(pShellItem);
66+
}
67+
else
68+
{
69+
continue;
70+
}
6271
}
63-
else if (type is StorableType.Folder && isFolder)
72+
73+
yield break;
74+
75+
unsafe void GetEnumerator()
6476
{
65-
yield return new WindowsFile(pShellItem);
77+
HRESULT hr = ThisPtr.Get()->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf());
78+
hr.ThrowIfFailedOnDebug();
6679
}
67-
else
80+
81+
unsafe bool GetNext()
6882
{
69-
continue;
83+
HRESULT hr = pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf());
84+
return hr.ThrowIfFailedOnDebug() == HRESULT.S_OK;
7085
}
71-
72-
await Task.Yield();
7386
}
74-
75-
unsafe ComPtr<IEnumShellItems> GetEnumShellItems()
76-
{
77-
ComPtr<IEnumShellItems> pEnumShellItems = default;
78-
Guid IID_IEnumShellItems = typeof(IEnumShellItems).GUID;
79-
Guid BHID_EnumItems = PInvoke.BHID_EnumItems;
80-
HRESULT hr = ThisPtr.Get()->BindToHandler(null, &BHID_EnumItems, &IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf());
81-
return pEnumShellItems;
82-
}
83-
84-
unsafe ComPtr<IShellItem> GetNext(ComPtr<IEnumShellItems> pEnumShellItems)
85-
{
86-
ComPtr<IShellItem> pShellItem = default;
87-
HRESULT hr = pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf());
88-
return pShellItem;
89-
}
90-
91-
unsafe bool IsFolder(ComPtr<IShellItem> pShellItem)
92-
{
93-
return pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var specifiedAttribute).Succeeded &&
94-
specifiedAttribute is SFGAO_FLAGS.SFGAO_FOLDER;
95-
}
96-
}
97-
98-
// Disposer
99-
100-
/// <inheritdoc/>
101-
public void Dispose()
102-
{
103-
ThisPtr.Dispose();
10487
}
10588
}
10689
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Files.App.Storage
1010
{
1111
public abstract class WindowsStorable : IWindowsStorable, IStorableChild
1212
{
13-
public ComPtr<IShellItem> ThisPtr { get; protected set; } = default;
13+
public ComPtr<IShellItem> ThisPtr { get; protected set; }
1414

1515
public string Id => this.GetDisplayName(SIGDN.SIGDN_FILESYSPATH);
1616

@@ -65,6 +65,12 @@ public abstract class WindowsStorable : IWindowsStorable, IStorableChild
6565
return Task.FromResult<IFolder?>(new WindowsFolder(pParentFolder));
6666
}
6767

68+
/// <inheritdoc/>
69+
public void Dispose()
70+
{
71+
ThisPtr.Dispose();
72+
}
73+
6874
/// <inheritdoc/>
6975
public override string ToString()
7076
{

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,46 @@ public unsafe static HRESULT TryGetThumbnail(this IWindowsStorable storable, int
5757
return hr;
5858
}
5959

60-
// Convert to GpBitmap of GDI+
60+
// Retrieve BITMAP data
61+
BITMAP bmp = default;
62+
if (PInvoke.GetObject(hBitmap, sizeof(BITMAP), &bmp) is 0)
63+
{
64+
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
65+
return HRESULT.E_FAIL;
66+
}
67+
68+
// Allocate buffer for flipped pixel data
69+
byte* flippedBits = (byte*)NativeMemory.AllocZeroed((nuint)(bmp.bmWidthBytes * bmp.bmHeight));
70+
71+
// Flip the image manually row by row
72+
for (int y = 0; y < bmp.bmHeight; y++)
73+
{
74+
Buffer.MemoryCopy(
75+
(byte*)bmp.bmBits + y * bmp.bmWidthBytes,
76+
flippedBits + (bmp.bmHeight - y - 1) * bmp.bmWidthBytes,
77+
bmp.bmWidthBytes,
78+
bmp.bmWidthBytes
79+
);
80+
}
81+
82+
// Create GpBitmap from the flipped pixel data
6183
GpBitmap* gpBitmap = default;
62-
if (PInvoke.GdipCreateBitmapFromHBITMAP(hBitmap, HPALETTE.Null, &gpBitmap) is not Status.Ok)
84+
if (PInvoke.GdipCreateBitmapFromScan0(bmp.bmWidth, bmp.bmHeight, bmp.bmWidthBytes, PInvoke.PixelFormat32bppARGB, flippedBits, &gpBitmap) != Status.Ok)
6385
{
64-
if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
86+
if (flippedBits is not null) NativeMemory.Free(flippedBits);
6587
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
6688
return HRESULT.E_FAIL;
6789
}
6890

69-
if (TryConvertGpBitmapToByteArray(gpBitmap, out thumbnailData))
91+
if (!TryConvertGpBitmapToByteArray(gpBitmap, out thumbnailData))
7092
{
7193
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
7294
return HRESULT.E_FAIL;
7395
}
7496

97+
if (flippedBits is not null) NativeMemory.Free(flippedBits);
98+
if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
99+
75100
return HRESULT.S_OK;
76101
}
77102

@@ -110,14 +135,14 @@ public unsafe static HRESULT TryExtractImageFromDll(this IWindowsStorable storab
110135
return HRESULT.E_FAIL;
111136
}
112137

113-
if (!TryConvertGpBitmapToByteArray(gpBitmap, out imageData))
138+
if (!TryConvertGpBitmapToByteArray(gpBitmap, out imageData) || imageData is null)
114139
{
115140
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
116141
return HRESULT.E_FAIL;
117142
}
118143

119144
DllIconCache[(path, index, size)] = imageData;
120-
PInvoke.DestroyIcon(hIcon);
145+
if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
121146

122147
return HRESULT.S_OK;
123148
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN
4848
HRESULT hr = storable.ThisPtr.Get()->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf());
4949

5050
return hr.ThrowIfFailedOnDebug().Succeeded
51-
? (*pszName.Get()).ToString()
51+
? new string((char*)pszName.Get()) // this is safe as it gets memcpy'd internally
5252
: string.Empty;
5353
}
5454
}

0 commit comments

Comments
 (0)