diff --git a/src/Files.App.CsWin32/ComPtr`1.cs b/src/Files.App.CsWin32/ComPtr`1.cs index aafa936de894..6ceb19067cf8 100644 --- a/src/Files.App.CsWin32/ComPtr`1.cs +++ b/src/Files.App.CsWin32/ComPtr`1.cs @@ -64,15 +64,6 @@ public void Attach(T* other) return (T**)Unsafe.AsPointer(ref Unsafe.AsRef(in this)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Obsolete("Use `HRESULT As(U** other)` instead.")] - public readonly ComPtr As() where U : unmanaged, IComIID - { - ComPtr ptr = default; - ((IUnknown*)_ptr)->QueryInterface((Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in U.Guid)), (void**)ptr.GetAddressOf()); - return ptr; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly HRESULT As(U** other) where U : unmanaged, IComIID { @@ -91,22 +82,6 @@ public readonly HRESULT CoCreateInstance(Guid* rclsid, IUnknown* pUnkOuter = nul return PInvoke.CoCreateInstance(rclsid, pUnkOuter, dwClsContext, (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in T.Guid)), (void**)this.GetAddressOf()); } - // Conversion operators - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ComPtr(T* other) - { - ComPtr ptr = default; - ptr.Attach(other); - return ptr; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator T*(ComPtr other) - { - return other._ptr; - } - // Disposer [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Files.App.CsWin32/ManualGuid.cs b/src/Files.App.CsWin32/ManualGuid.cs index 8ab59f21ee7f..f95973c45048 100644 --- a/src/Files.App.CsWin32/ManualGuid.cs +++ b/src/Files.App.CsWin32/ManualGuid.cs @@ -41,6 +41,27 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory [GuidRVAGen.Guid("00021500-0000-0000-C000-000000000046")] public static partial Guid* IID_IQueryInfo { get; } + + [GuidRVAGen.Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")] + public static partial Guid* IID_IShellItemImageFactory { get; } + + [GuidRVAGen.Guid("000214F9-0000-0000-C000-000000000046")] + public static partial Guid* IID_IShellLinkW { get; } + + [GuidRVAGen.Guid("B63EA76D-1F85-456F-A19C-48159EFA858B")] + public static partial Guid* IID_IShellItemArray { get; } + + [GuidRVAGen.Guid("7F9185B0-CB92-43C5-80A9-92277A4F7B54")] + public static partial Guid* IID_IExecuteCommand { get; } + + [GuidRVAGen.Guid("1C9CD5BB-98E9-4491-A60F-31AACC72B83C")] + public static partial Guid* IID_IObjectWithSelection { get; } + + [GuidRVAGen.Guid("000214E8-0000-0000-C000-000000000046")] + public static partial Guid* IID_IShellExtInit { get; } + + [GuidRVAGen.Guid("000214F4-0000-0000-C000-000000000046")] + public static partial Guid* IID_IContextMenu2 { get; } } public static unsafe partial class CLSID @@ -59,6 +80,15 @@ public static unsafe partial class CLSID [GuidRVAGen.Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] public static partial Guid* CLSID_ApplicationActivationManager { get; } + + [GuidRVAGen.Guid("B455F46E-E4AF-4035-B0A4-CF18D2F6F28E")] + public static partial Guid* CLSID_PinToFrequentExecute { get; } + + [GuidRVAGen.Guid("EE20EEBA-DF64-4A4E-B7BB-2D1C6B2DFCC1")] + public static partial Guid* CLSID_UnPinFromFrequentExecute { get; } + + [GuidRVAGen.Guid("D969A300-E7FF-11d0-A93B-00A0C90F2719")] + public static partial Guid* CLSID_NewMenu { get; } } public static unsafe partial class BHID diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index ce5524f6885e..4feabdf35b6d 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -225,3 +225,12 @@ QITIPF_FLAGS GetKeyboardState MapVirtualKey GetKeyboardLayout +S_FALSE +IExecuteCommand +IObjectWithSelection +SHCreateShellItemArrayFromShellItem +IShellExtInit +IContextMenu2 +GetSubMenu +GetMenuItemCount +GetMenuItemInfo diff --git a/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs b/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs index 790cbc17c13e..ae976667d582 100644 --- a/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs +++ b/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs @@ -7,7 +7,7 @@ namespace Files.App.Storage.Storables { - public partial class HomeFolder : IHomeFolder + public unsafe partial class HomeFolder : IHomeFolder { public string Id => "Home"; // Will be "files://Home" in the future. @@ -48,38 +48,36 @@ public IAsyncEnumerable GetQuickAccessFolderAsync(CancellationTo /// public IAsyncEnumerable GetLogicalDrivesAsync(CancellationToken cancellationToken = default) { - return GetLogicalDrives().ToAsyncEnumerable(); + var availableDrives = PInvoke.GetLogicalDrives(); + if (availableDrives is 0) + return Enumerable.Empty().ToAsyncEnumerable(); - IEnumerable GetLogicalDrives() - { - var availableDrives = PInvoke.GetLogicalDrives(); - if (availableDrives is 0) - yield break; - - int count = BitOperations.PopCount(availableDrives); - var driveLetters = new char[count]; + int count = BitOperations.PopCount(availableDrives); + var driveLetters = new char[count]; - count = 0; - char driveLetter = 'A'; - while (availableDrives is not 0) - { - if ((availableDrives & 1) is not 0) - driveLetters[count++] = driveLetter; + count = 0; + char driveLetter = 'A'; + while (availableDrives is not 0) + { + if ((availableDrives & 1) is not 0) + driveLetters[count++] = driveLetter; - availableDrives >>= 1; - driveLetter++; - } + availableDrives >>= 1; + driveLetter++; + } - foreach (char letter in driveLetters) - { - cancellationToken.ThrowIfCancellationRequested(); + List driveItems = []; + foreach (char letter in driveLetters) + { + cancellationToken.ThrowIfCancellationRequested(); - if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot) - throw new InvalidOperationException(); + if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot) + throw new InvalidOperationException(); - yield return new WindowsFolder(driveRoot.ThisPtr); - } + driveItems.Add(new WindowsFolder(driveRoot.ThisPtr)); } + + return driveItems.ToAsyncEnumerable(); } /// diff --git a/src/Files.App.Storage/Storables/WindowsStorage/ContextMenuItem.cs b/src/Files.App.Storage/Storables/WindowsStorage/ContextMenuItem.cs new file mode 100644 index 000000000000..179f6d83b75a --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/ContextMenuItem.cs @@ -0,0 +1,19 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + /// + /// Represents a Windows Shell ContextMenu item. + /// + public partial class ContextMenuItem + { + public ContextMenuType Type { get; set; } + + public uint Id { get; set; } + + public byte[]? Icon { get; set; } + + public string? Name { get; set; } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/ContextMenuType.cs b/src/Files.App.Storage/Storables/WindowsStorage/ContextMenuType.cs new file mode 100644 index 000000000000..31e3b939a30f --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/ContextMenuType.cs @@ -0,0 +1,18 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + public enum ContextMenuType + { + Normal = 0x00000000, + + Disabled = 0x00000003, + + Checked = 0x00000008, + + Highlighted = 0x00000080, + + Default = 0x00001000, + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs new file mode 100644 index 000000000000..43f30155f907 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs @@ -0,0 +1,9 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + public interface IWindowsFile : IWindowsStorable, IChildFile + { + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs new file mode 100644 index 000000000000..92160da97f48 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs @@ -0,0 +1,15 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Windows.Win32.UI.Shell; + +namespace Files.App.Storage +{ + public unsafe interface IWindowsFolder : IWindowsStorable, IChildFolder + { + /// + /// Gets or sets the cached for the ShellNew context menu. + /// + public IContextMenu* ShellNewMenu { get; set; } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs index 421d7a68dddd..c79ef3ba66cb 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs @@ -1,13 +1,14 @@ // Copyright (c) Files Community // Licensed under the MIT License. -using Windows.Win32; using Windows.Win32.UI.Shell; namespace Files.App.Storage { - public interface IWindowsStorable : IDisposable + public unsafe interface IWindowsStorable : IStorableChild, IEquatable, IDisposable { - ComPtr ThisPtr { get; } + IShellItem* ThisPtr { get; set; } + + IContextMenu* ContextMenu { get; set; } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs b/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs index 29a4a6819e61..742e856e3a84 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs @@ -1,16 +1,17 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Microsoft.Extensions.Logging; using Windows.Win32; namespace Files.App.Storage { /// - /// Represents an asynchronous operation on STA. + /// Represents a synchronous/asynchronous operation on STA. /// public partial class STATask { - public static Task Run(Action action) + public static Task Run(Action action, ILogger? logger = null) { var tcs = new TaskCompletionSource(); @@ -26,25 +27,24 @@ public static Task Run(Action action) } catch (Exception ex) { + tcs.SetResult(); + logger?.LogWarning(ex, "An exception was occurred during the execution within STA."); tcs.SetException(ex); } finally { PInvoke.OleUninitialize(); } - }) - { - IsBackground = true, - Priority = ThreadPriority.Normal - }; + }); + thread.IsBackground = true; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return tcs.Task; } - public static Task Run(Func func) + public static Task Run(Func func, ILogger? logger = null) { var tcs = new TaskCompletionSource(); @@ -59,25 +59,24 @@ public static Task Run(Func func) } catch (Exception ex) { + tcs.SetResult(default!); + logger?.LogWarning(ex, "An exception was occurred during the execution within STA."); tcs.SetException(ex); } finally { PInvoke.OleUninitialize(); } - }) - { - IsBackground = true, - Priority = ThreadPriority.Normal - }; + }); + thread.IsBackground = true; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return tcs.Task; } - public static Task Run(Func func) + public static Task Run(Func func, ILogger? logger = null) { var tcs = new TaskCompletionSource(); @@ -93,25 +92,24 @@ public static Task Run(Func func) } catch (Exception ex) { + tcs.SetResult(); + logger?.LogWarning(ex, "An exception was occurred during the execution within STA."); tcs.SetException(ex); } finally { PInvoke.OleUninitialize(); } - }) - { - IsBackground = true, - Priority = ThreadPriority.Normal - }; + }); + thread.IsBackground = true; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return tcs.Task; } - public static Task Run(Func> func) + public static Task Run(Func> func, ILogger? logger = null) { var tcs = new TaskCompletionSource(); @@ -126,18 +124,17 @@ public static Task Run(Func func) } catch (Exception ex) { + tcs.SetResult(default); + logger?.LogWarning(ex, "An exception was occurred during the execution within STA."); tcs.SetException(ex); } finally { PInvoke.OleUninitialize(); } - }) - { - IsBackground = true, - Priority = ThreadPriority.Normal - }; + }); + thread.IsBackground = true; thread.SetApartmentState(ApartmentState.STA); thread.Start(); diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs index a6393243246f..d995d20ea71b 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs @@ -12,12 +12,12 @@ namespace Files.App.Storage /// /// Handles bulk file operations in Windows, such as copy, move, delete, create, and rename, supporting progress tracking and event notifications. /// - public sealed partial class WindowsBulkOperations : IDisposable + public unsafe partial class WindowsBulkOperations : IDisposable { // Fields - private readonly ComPtr _pFileOperation; - private readonly ComPtr _pProgressSink; + private readonly IFileOperation* _pFileOperation; + private readonly IFileOperationProgressSink* _pProgressSink; private readonly uint _progressSinkCookie; // Events @@ -70,24 +70,20 @@ public sealed partial class WindowsBulkOperations : IDisposable /// Defines the behavior of the file operation, such as allowing undo and suppressing directory confirmation. public unsafe WindowsBulkOperations(HWND ownerHWnd = default, FILEOPERATION_FLAGS flags = FILEOPERATION_FLAGS.FOF_ALLOWUNDO | FILEOPERATION_FLAGS.FOF_NOCONFIRMMKDIR) { - var clsid = typeof(FileOperation).GUID; - var iid = typeof(IFileOperation).GUID; + IFileOperation* pFileOperation = null; - HRESULT hr = PInvoke.CoCreateInstance( - &clsid, - null, - CLSCTX.CLSCTX_LOCAL_SERVER, - &iid, - (void**)_pFileOperation.GetAddressOf()) - .ThrowIfFailedOnDebug(); + HRESULT hr = PInvoke.CoCreateInstance(CLSID.CLSID_FileOperation, null, CLSCTX.CLSCTX_LOCAL_SERVER, IID.IID_IFileOperation, (void**)&pFileOperation); + hr.ThrowIfFailedOnDebug(); + + _pFileOperation = pFileOperation; if (ownerHWnd != default) - hr = _pFileOperation.Get()->SetOwnerWindow(ownerHWnd).ThrowIfFailedOnDebug(); + hr = _pFileOperation->SetOwnerWindow(ownerHWnd).ThrowIfFailedOnDebug(); - hr = _pFileOperation.Get()->SetOperationFlags(flags).ThrowIfFailedOnDebug(); + hr = _pFileOperation->SetOperationFlags(flags).ThrowIfFailedOnDebug(); - _pProgressSink.Attach((IFileOperationProgressSink*)WindowsBulkOperationsSink.Create(this)); - hr = _pFileOperation.Get()->Advise(_pProgressSink.Get(), out var progressSinkCookie).ThrowIfFailedOnDebug(); + _pProgressSink = (IFileOperationProgressSink*)WindowsBulkOperationsSink.Create(this); + hr = _pFileOperation->Advise(_pProgressSink, out var progressSinkCookie).ThrowIfFailedOnDebug(); _progressSinkCookie = progressSinkCookie; } @@ -101,7 +97,7 @@ public unsafe WindowsBulkOperations(HWND ownerHWnd = default, FILEOPERATION_FLAG public unsafe HRESULT QueueCopyOperation(WindowsStorable targetItem, WindowsFolder destinationFolder, string? copyName) { fixed (char* pszCopyName = copyName) - return _pFileOperation.Get()->CopyItem(targetItem.ThisPtr.Get(), destinationFolder.ThisPtr.Get(), pszCopyName, _pProgressSink.Get()); + return _pFileOperation->CopyItem(targetItem.ThisPtr, destinationFolder.ThisPtr, pszCopyName, _pProgressSink); } /// @@ -111,7 +107,7 @@ public unsafe HRESULT QueueCopyOperation(WindowsStorable targetItem, WindowsFold /// If this method succeeds, it returns . Otherwise, it returns an error code. public unsafe HRESULT QueueDeleteOperation(WindowsStorable targetItem) { - return _pFileOperation.Get()->DeleteItem(targetItem.ThisPtr.Get(), _pProgressSink.Get()); + return _pFileOperation->DeleteItem(targetItem.ThisPtr, _pProgressSink); } /// @@ -124,7 +120,7 @@ public unsafe HRESULT QueueDeleteOperation(WindowsStorable targetItem) public unsafe HRESULT QueueMoveOperation(WindowsStorable targetItem, WindowsFolder destinationFolder, string? newName) { fixed (char* pszNewName = newName) - return _pFileOperation.Get()->MoveItem(targetItem.ThisPtr.Get(), destinationFolder.ThisPtr.Get(), pszNewName, null); + return _pFileOperation->MoveItem(targetItem.ThisPtr, destinationFolder.ThisPtr, pszNewName, null); } /// @@ -138,7 +134,7 @@ public unsafe HRESULT QueueMoveOperation(WindowsStorable targetItem, WindowsFold public unsafe HRESULT QueueCreateOperation(WindowsFolder destinationFolder, FILE_FLAGS_AND_ATTRIBUTES fileAttributes, string name, string? templateName) { fixed (char* pszName = name, pszTemplateName = templateName) - return _pFileOperation.Get()->NewItem(destinationFolder.ThisPtr.Get(), (uint)fileAttributes, pszName, pszTemplateName, _pProgressSink.Get()); + return _pFileOperation->NewItem(destinationFolder.ThisPtr, (uint)fileAttributes, pszName, pszTemplateName, _pProgressSink); } /// @@ -150,7 +146,7 @@ public unsafe HRESULT QueueCreateOperation(WindowsFolder destinationFolder, FILE public unsafe HRESULT QueueRenameOperation(WindowsStorable targetItem, string newName) { fixed (char* pszNewName = newName) - return _pFileOperation.Get()->RenameItem(targetItem.ThisPtr.Get(), pszNewName, _pProgressSink.Get()); + return _pFileOperation->RenameItem(targetItem.ThisPtr, pszNewName, _pProgressSink); } /// @@ -159,7 +155,7 @@ public unsafe HRESULT QueueRenameOperation(WindowsStorable targetItem, string ne /// If this method succeeds, it returns . Otherwise, it returns an error code. public unsafe HRESULT PerformAllOperations() { - return _pFileOperation.Get()->PerformOperations(); + return _pFileOperation->PerformOperations(); } // Disposer @@ -167,11 +163,11 @@ public unsafe HRESULT PerformAllOperations() /// public unsafe void Dispose() { - if (!_pProgressSink.IsNull) - _pFileOperation.Get()->Unadvise(_progressSinkCookie); + if (_pProgressSink is not null) + _pFileOperation->Unadvise(_progressSinkCookie); - _pFileOperation.Dispose(); - _pProgressSink.Dispose(); + _pFileOperation->Release(); + _pProgressSink->Release(); } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs index 3ce56f786c2f..93362f26a7e9 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs @@ -8,11 +8,11 @@ namespace Files.App.Storage { [DebuggerDisplay("{" + nameof(ToString) + "()}")] - public sealed class WindowsFile : WindowsStorable, IChildFile + public unsafe class WindowsFile : WindowsStorable, IWindowsFile { - public WindowsFile(ComPtr nativeObject) + public WindowsFile(IShellItem* ptr) { - ThisPtr = nativeObject; + ThisPtr = ptr; } public Task OpenStreamAsync(FileAccess accessMode, CancellationToken cancellationToken = default) diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs index f4105687184f..510300fa5f6d 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs @@ -10,80 +10,76 @@ namespace Files.App.Storage { [DebuggerDisplay("{" + nameof(ToString) + "()}")] - public sealed class WindowsFolder : WindowsStorable, IChildFolder + public unsafe class WindowsFolder : WindowsStorable, IWindowsFolder { - public WindowsFolder(ComPtr nativeObject) + /// + public IContextMenu* ShellNewMenu { - ThisPtr = nativeObject; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set; } - public unsafe WindowsFolder(IShellItem* nativeObject) + public WindowsFolder(IShellItem* ptr) { - ComPtr ptr = default; - ptr.Attach(nativeObject); ThisPtr = ptr; } - public unsafe WindowsFolder(Guid folderId) + public WindowsFolder(Guid folderId) { - ComPtr pItem = default; + IShellItem* pShellItem = default; - HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, IID.IID_IShellItem, (void**)pItem.GetAddressOf()); + HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, IID.IID_IShellItem, (void**)&pShellItem); if (hr.Failed) { fixed (char* pszShellPath = $"Shell:::{folderId:B}") - hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, IID.IID_IShellItem, (void**)pItem.GetAddressOf()); + hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, IID.IID_IShellItem, (void**)&pShellItem); // Invalid FOLDERID; this should never happen. hr.ThrowOnFailure(); } - ThisPtr = pItem; + ThisPtr = pShellItem; } public IAsyncEnumerable GetItemsAsync(StorableType type = StorableType.All, CancellationToken cancellationToken = default) { - return GetItems().ToAsyncEnumerable(); + using ComPtr pEnumShellItems = default; - unsafe IEnumerable GetItems() - { - ComPtr pEnumShellItems = default; - GetEnumerator(); + HRESULT hr = ThisPtr->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return Enumerable.Empty().ToAsyncEnumerable(); - ComPtr pShellItem = default; - while (GetNext() && !pShellItem.IsNull) - { - cancellationToken.ThrowIfCancellationRequested(); - var isFolder = pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER); - - if (type is StorableType.File && !isFolder) - { - yield return new WindowsFile(pShellItem); - } - else if (type is StorableType.Folder && isFolder) - { - yield return new WindowsFolder(pShellItem); - } - else - { - continue; - } - } + List childItems = []; - yield break; + IShellItem* pChildShellItem = null; + while ((hr = pEnumShellItems.Get()->Next(1, &pChildShellItem)) == HRESULT.S_OK) + { + bool isFolder = pChildShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var dwAttributes).Succeeded && dwAttributes is SFGAO_FLAGS.SFGAO_FOLDER; - unsafe void GetEnumerator() + if (type.HasFlag(StorableType.File) && !isFolder) { - HRESULT hr = ThisPtr.Get()->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf()); - hr.ThrowIfFailedOnDebug(); + childItems.Add(new WindowsFile(pChildShellItem)); } - - unsafe bool GetNext() + else if (type.HasFlag(StorableType.Folder) && isFolder) { - HRESULT hr = pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()); - return hr.ThrowIfFailedOnDebug() == HRESULT.S_OK; + childItems.Add(new WindowsFolder(pChildShellItem)); } } + + if (hr.ThrowIfFailedOnDebug().Failed) + return Enumerable.Empty().ToAsyncEnumerable(); + + return childItems.ToAsyncEnumerable(); + } + + public override void Dispose() + { + base.Dispose(); + + if (ShellNewMenu is not null) ShellNewMenu->Release(); } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs index 3fdc51e33389..bde2995b6490 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs @@ -1,6 +1,7 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using System.Runtime.CompilerServices; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.SystemServices; @@ -8,59 +9,59 @@ namespace Files.App.Storage { - public abstract class WindowsStorable : IWindowsStorable, IStorableChild, IEquatable + public unsafe abstract class WindowsStorable : IWindowsStorable { - public ComPtr ThisPtr { get; protected set; } + public IShellItem* ThisPtr + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set; + } + + public IContextMenu* ContextMenu + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set; + } public string Id => this.GetDisplayName(SIGDN.SIGDN_FILESYSPATH); public string Name => this.GetDisplayName(SIGDN.SIGDN_PARENTRELATIVEFORUI); - public static unsafe WindowsStorable? TryParse(string parsablePath) + public static WindowsStorable? TryParse(string szPath) { HRESULT hr = default; - ComPtr pShellItem = default; - var IID_IShellItem = typeof(IShellItem).GUID; - - fixed (char* pszParsablePath = parsablePath) - { - hr = PInvoke.SHCreateItemFromParsingName( - pszParsablePath, - null, - &IID_IShellItem, - (void**)pShellItem.GetAddressOf()); - } - - if (pShellItem.IsNull) + IShellItem* pShellItem = null; + + fixed (char* pszPath = szPath) + hr = PInvoke.SHCreateItemFromParsingName(pszPath, null, IID.IID_IShellItem, (void**)&pShellItem); + + if (pShellItem is null) return null; - return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER) - ? new WindowsFolder(pShellItem) - : new WindowsFile(pShellItem); + return TryParse(pShellItem); } - public static unsafe WindowsStorable? TryParse(IShellItem* ptr) + public static WindowsStorable? TryParse(IShellItem* pShellItem) { - ComPtr pShellItem = default; - pShellItem.Attach(ptr); + bool isFolder = pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && returnedAttributes is SFGAO_FLAGS.SFGAO_FOLDER; - return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER) - ? new WindowsFolder(pShellItem) - : new WindowsFile(pShellItem); + return isFolder ? new WindowsFolder(pShellItem) : new WindowsFile(pShellItem); } public unsafe Task GetParentAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - ComPtr pParentFolder = default; - HRESULT hr = ThisPtr.Get()->GetParent(pParentFolder.GetAddressOf()); - if (hr.Failed) - { - if (!pParentFolder.IsNull) pParentFolder.Dispose(); - + IShellItem* pParentFolder = default; + HRESULT hr = ThisPtr->GetParent(&pParentFolder); + if (hr.ThrowIfFailedOnDebug().Failed) return Task.FromResult(null); - } return Task.FromResult(new WindowsFolder(pParentFolder)); } @@ -77,9 +78,10 @@ public override int GetHashCode() } /// - public void Dispose() + public virtual void Dispose() { - ThisPtr.Dispose(); + if (ThisPtr is not null) ThisPtr->Release(); + if (ContextMenu is not null) ContextMenu->Release(); } /// @@ -94,7 +96,7 @@ public unsafe bool Equals(IWindowsStorable? other) if (other is null) return false; - return ThisPtr.Get()->Compare(other.ThisPtr.Get(), (uint)_SICHINTF.SICHINT_DISPLAY, out int order).Succeeded && order is 0; + return ThisPtr->Compare(other.ThisPtr, (uint)_SICHINTF.SICHINT_DISPLAY, out int order).Succeeded && order is 0; } public static bool operator ==(WindowsStorable left, WindowsStorable right) diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs index 9b1fd95a31a5..f50de4bbad35 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs @@ -45,7 +45,7 @@ public unsafe static HRESULT TryGetThumbnail(this IWindowsStorable storable, int thumbnailData = null; using ComPtr pShellItemImageFactory = default; - storable.ThisPtr.As(pShellItemImageFactory.GetAddressOf()); + storable.ThisPtr->QueryInterface(IID.IID_IShellItemImageFactory, (void**)pShellItemImageFactory.GetAddressOf()); if (pShellItemImageFactory.IsNull) return HRESULT.E_NOINTERFACE; @@ -267,10 +267,8 @@ public unsafe static HRESULT TrySetShortcutIcon(this IWindowsStorable storable, return HRESULT.E_INVALIDARG; using ComPtr pShellLink = default; - Guid IID_IShellLink = IShellLinkW.IID_Guid; - Guid BHID_SFUIObject = PInvoke.BHID_SFUIObject; - HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, &BHID_SFUIObject, &IID_IShellLink, (void**)pShellLink.GetAddressOf()); + HRESULT hr = storable.ThisPtr->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IShellLinkW, (void**)pShellLink.GetAddressOf()); if (hr.ThrowIfFailedOnDebug().Failed) return hr; diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs index c09b2fc0d103..f63a2502009b 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs @@ -2,22 +2,25 @@ // Licensed under the MIT License. using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Windows.Win32; using Windows.Win32.Foundation; +using Windows.Win32.System.Com; using Windows.Win32.System.SystemServices; using Windows.Win32.UI.Shell; +using Windows.Win32.UI.Shell.Common; using Windows.Win32.UI.Shell.PropertiesSystem; using Windows.Win32.UI.WindowsAndMessaging; namespace Files.App.Storage { - public static partial class WindowsStorableHelpers + public unsafe static partial class WindowsStorableHelpers { - public unsafe static HRESULT GetPropertyValue(this IWindowsStorable storable, string propKey, out TValue value) + public static HRESULT GetPropertyValue(this IWindowsStorable storable, string propKey, out TValue value) { using ComPtr pShellItem2 = default; - HRESULT hr = storable.ThisPtr.Get()->QueryInterface(IID.IID_IShellItem2, (void**)pShellItem2.GetAddressOf()); + HRESULT hr = storable.ThisPtr->QueryInterface(IID.IID_IShellItem2, (void**)pShellItem2.GetAddressOf()); PROPERTYKEY propertyKey = default; fixed (char* pszPropertyKey = propKey) @@ -33,10 +36,9 @@ public unsafe static HRESULT GetPropertyValue(this IWindowsStorable stor } if (typeof(TValue) == typeof(bool)) { - bool propertyValue = false; - hr = pShellItem2.Get()->GetBool(propertyKey, out var fPropertyValue); - propertyValue = fPropertyValue; - value = Unsafe.As(ref propertyValue); + bool fPropertyValue = false; + hr = pShellItem2.Get()->GetBool(&propertyKey, (BOOL*)&fPropertyValue); + value = Unsafe.As(ref fPropertyValue); return hr; } @@ -47,34 +49,27 @@ public unsafe static HRESULT GetPropertyValue(this IWindowsStorable stor } } - public unsafe static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes) + public static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes) { - return storable.ThisPtr.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && - returnedAttributes == attributes; + return storable.ThisPtr->GetAttributes(attributes, out var dwRetAttributes).Succeeded && dwRetAttributes == attributes; } - public unsafe static bool HasShellAttributes(this ComPtr pShellItem, SFGAO_FLAGS attributes) - { - return pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && - returnedAttributes == attributes; - } - - public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH) + public static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH) { using ComHeapPtr pszName = default; - HRESULT hr = storable.ThisPtr.Get()->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf()); + HRESULT hr = storable.ThisPtr->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf()); return hr.ThrowIfFailedOnDebug().Succeeded ? new string((char*)pszName.Get()) // this is safe as it gets memcpy'd internally : string.Empty; } - public unsafe static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable storable, string verbName) + public static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable storable, string verbName) { Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA); using ComPtr pContextMenu = default; - HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IContextMenu, (void**)pContextMenu.GetAddressOf()); + HRESULT hr = storable.ThisPtr->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IContextMenu, (void**)pContextMenu.GetAddressOf()); HMENU hMenu = PInvoke.CreatePopupMenu(); hr = pContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 0x7FFF, PInvoke.CMF_OPTIMIZEFORINVOKE); @@ -94,12 +89,12 @@ public unsafe static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable stor } } - public unsafe static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable storable, string[] verbNames, bool earlyReturnOnSuccess) + public static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable storable, string[] verbNames, bool earlyReturnOnSuccess) { Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA); using ComPtr pContextMenu = default; - HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IContextMenu, (void**)pContextMenu.GetAddressOf()); + HRESULT hr = storable.ThisPtr->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IContextMenu, (void**)pContextMenu.GetAddressOf()); HMENU hMenu = PInvoke.CreatePopupMenu(); hr = pContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 0x7FFF, PInvoke.CMF_OPTIMIZEFORINVOKE); @@ -125,12 +120,12 @@ public unsafe static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable sto return hr; } - public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable, out string? tooltip) + public static HRESULT TryGetShellTooltip(this IWindowsStorable storable, out string? tooltip) { tooltip = null; using ComPtr pQueryInfo = default; - HRESULT hr = storable.ThisPtr.Get()->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IQueryInfo, (void**)pQueryInfo.GetAddressOf()); + HRESULT hr = storable.ThisPtr->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IQueryInfo, (void**)pQueryInfo.GetAddressOf()); if (hr.ThrowIfFailedOnDebug().Failed) return hr; @@ -143,5 +138,167 @@ public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable, return HRESULT.S_OK; } + + public static HRESULT TryPinFolderToQuickAccess(this IWindowsFolder @this) + { + HRESULT hr = default; + + using ComPtr pExecuteCommand = default; + using ComPtr pObjectWithSelection = default; + + hr = PInvoke.CoCreateInstance(CLSID.CLSID_PinToFrequentExecute, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IExecuteCommand, (void**)pExecuteCommand.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + using ComPtr pShellItemArray = default; + hr = PInvoke.SHCreateShellItemArrayFromShellItem(@this.ThisPtr, IID.IID_IShellItemArray, (void**)pShellItemArray.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + hr = pExecuteCommand.Get()->QueryInterface(IID.IID_IObjectWithSelection, (void**)pObjectWithSelection.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + hr = pObjectWithSelection.Get()->SetSelection(pShellItemArray.Get()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + hr = pExecuteCommand.Get()->Execute(); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + return HRESULT.S_OK; + } + + public static HRESULT TryUnpinFolderFromQuickAccess(this IWindowsFolder @this) + { + HRESULT hr = default; + + using ComPtr pExecuteCommand = default; + using ComPtr pObjectWithSelection = default; + + hr = PInvoke.CoCreateInstance(CLSID.CLSID_UnPinFromFrequentExecute, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IExecuteCommand, (void**)pExecuteCommand.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + using ComPtr pShellItemArray = default; + hr = PInvoke.SHCreateShellItemArrayFromShellItem(@this.ThisPtr, IID.IID_IShellItemArray, (void**)pShellItemArray.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + hr = pExecuteCommand.Get()->QueryInterface(IID.IID_IObjectWithSelection, (void**)pObjectWithSelection.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + hr = pObjectWithSelection.Get()->SetSelection(pShellItemArray.Get()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + hr = pExecuteCommand.Get()->Execute(); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + return HRESULT.S_OK; + } + + public static IEnumerable GetShellNewItems(this IWindowsFolder @this) + { + HRESULT hr = default; + + IContextMenu* pNewMenu = default; + using ComPtr pShellExtInit = default; + using ComPtr pContextMenu2 = default; + + hr = PInvoke.CoCreateInstance(CLSID.CLSID_NewMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)&pNewMenu); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + hr = pNewMenu->QueryInterface(IID.IID_IContextMenu2, (void**)pContextMenu2.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + hr = pNewMenu->QueryInterface(IID.IID_IShellExtInit, (void**)pShellExtInit.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + @this.ShellNewMenu = pNewMenu; + + ITEMIDLIST* pFolderPidl = default; + hr = PInvoke.SHGetIDListFromObject((IUnknown*)@this.ThisPtr, &pFolderPidl); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + hr = pShellExtInit.Get()->Initialize(pFolderPidl, null, default); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + // Inserts "New (&W)" + HMENU hMenu = PInvoke.CreatePopupMenu(); + hr = pNewMenu->QueryContextMenu(hMenu, 0, 1, 256, 0); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + // Invokes CNewMenu::_InitMenuPopup(), which populates the hSubMenu + HMENU hSubMenu = PInvoke.GetSubMenu(hMenu, 0); + hr = pContextMenu2.Get()->HandleMenuMsg(PInvoke.WM_INITMENUPOPUP, (WPARAM)(nuint)hSubMenu.Value, 0); + if (hr.ThrowIfFailedOnDebug().Failed) + return []; + + uint dwCount = unchecked((uint)PInvoke.GetMenuItemCount(hSubMenu)); + if (dwCount is unchecked((uint)-1)) + return []; + + // Enumerates and populates the list + List shellNewItems = []; + for (uint dwIndex = 0; dwIndex < dwCount; dwIndex++) + { + MENUITEMINFOW mii = default; + mii.cbSize = (uint)sizeof(MENUITEMINFOW); + mii.fMask = MENU_ITEM_MASK.MIIM_STRING | MENU_ITEM_MASK.MIIM_ID | MENU_ITEM_MASK.MIIM_STATE; + mii.dwTypeData = (char*)NativeMemory.Alloc(256U); + mii.cch = 256; + + if (PInvoke.GetMenuItemInfo(hSubMenu, dwIndex, true, &mii)) + { + shellNewItems.Add(new() + { + Id = mii.wID, + Name = mii.dwTypeData.ToString(), + Type = (ContextMenuType)mii.fState, + }); + } + + NativeMemory.Free(mii.dwTypeData); + } + + return shellNewItems; + } + + public static bool InvokeShellNewItem(this IWindowsFolder @this, ContextMenuItem item) + { + HRESULT hr = default; + + if (@this.ShellNewMenu is null) + { + IContextMenu* pNewMenu = default; + + hr = PInvoke.CoCreateInstance(CLSID.CLSID_NewMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)&pNewMenu); + if (hr.ThrowIfFailedOnDebug().Failed) + return false; + + @this.ShellNewMenu = pNewMenu; + } + + CMINVOKECOMMANDINFO cmici = default; + cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO); + cmici.lpVerb = (PCSTR)(byte*)item.Id; + cmici.nShow = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL; + + hr = @this.ShellNewMenu->InvokeCommand(&cmici); + if (hr.ThrowIfFailedOnDebug().Failed) + return false; + + return false; + } } } diff --git a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs index 2739fb84162a..fc447eedbe95 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs @@ -198,7 +198,7 @@ public override async Task ExecutePinToSidebarCommand(WidgetCardItem? item) unsafe { - hr = PInvoke.RoGetAgileReference(AgileReferenceOptions.AGILEREFERENCE_DEFAULT, IID.IID_IShellItem, (IUnknown*)folderCardItem.Item.ThisPtr.Get(), pAgileReference.GetAddressOf()); + hr = PInvoke.RoGetAgileReference(AgileReferenceOptions.AGILEREFERENCE_DEFAULT, IID.IID_IShellItem, (IUnknown*)folderCardItem.Item.ThisPtr, pAgileReference.GetAddressOf()); } // Pin to Quick Access on Windows @@ -206,9 +206,9 @@ public override async Task ExecutePinToSidebarCommand(WidgetCardItem? item) { unsafe { - using ComPtr pShellItem = default; - hr = pAgileReference.Get()->Resolve(IID.IID_IShellItem, (void**)pShellItem.GetAddressOf()); - var windowsFile = new WindowsFile(pShellItem); + IShellItem* pShellItem = null; + hr = pAgileReference.Get()->Resolve(IID.IID_IShellItem, (void**)&pShellItem); + using var windowsFile = new WindowsFile(pShellItem); // NOTE: "pintohome" is an undocumented verb, which calls an undocumented COM class, windows.storage.dll!CPinToFrequentExecute : public IExecuteCommand, ... return windowsFile.TryInvokeContextMenuVerb("pintohome"); @@ -234,7 +234,7 @@ public override async Task ExecuteUnpinFromSidebarCommand(WidgetCardItem? item) unsafe { - hr = PInvoke.RoGetAgileReference(AgileReferenceOptions.AGILEREFERENCE_DEFAULT, IID.IID_IShellItem, (IUnknown*)folderCardItem.Item.ThisPtr.Get(), pAgileReference.GetAddressOf()); + hr = PInvoke.RoGetAgileReference(AgileReferenceOptions.AGILEREFERENCE_DEFAULT, IID.IID_IShellItem, (IUnknown*)folderCardItem.Item.ThisPtr, pAgileReference.GetAddressOf()); } // Unpin from Quick Access on Windows @@ -242,9 +242,9 @@ public override async Task ExecuteUnpinFromSidebarCommand(WidgetCardItem? item) { unsafe { - using ComPtr pShellItem = default; - hr = pAgileReference.Get()->Resolve(IID.IID_IShellItem, (void**)pShellItem.GetAddressOf()); - var windowsFile = new WindowsFile(pShellItem); + IShellItem* pShellItem = null; + hr = pAgileReference.Get()->Resolve(IID.IID_IShellItem, (void**)&pShellItem); + using var windowsFile = new WindowsFile(pShellItem); // NOTE: "unpinfromhome" is an undocumented verb, which calls an undocumented COM class, windows.storage.dll!CRemoveFromFrequentPlacesExecute : public IExecuteCommand, ... // NOTE: "remove" is for some shell folders where the "unpinfromhome" may not work