From 90fb8176dfd6269df4c255417fb9e5c56b8c2fb1 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 14 Jun 2025 19:20:40 +0900 Subject: [PATCH 1/5] Improved WindowsStorable implementations --- src/Files.App.CsWin32/ComPtr`1.cs | 25 ------- src/Files.App.CsWin32/ManualGuid.cs | 6 ++ src/Files.App.CsWin32/NativeMethods.txt | 1 + .../Storables/HomeFolder/HomeFolder.cs | 50 +++++++------ .../Storables/WindowsStorage/IWindowsFile.cs | 9 +++ .../WindowsStorage/IWindowsFolder.cs | 9 +++ .../WindowsStorage/IWindowsStorable.cs | 5 +- .../WindowsStorage/WindowsBulkOperations.cs | 48 ++++++------- .../Storables/WindowsStorage/WindowsFile.cs | 6 +- .../Storables/WindowsStorage/WindowsFolder.cs | 72 +++++++------------ .../WindowsStorage/WindowsStorable.cs | 60 +++++++--------- .../WindowsStorableHelpers.Icon.cs | 6 +- .../WindowsStorableHelpers.Shell.cs | 26 +++---- .../Widgets/QuickAccessWidgetViewModel.cs | 16 ++--- 14 files changed, 146 insertions(+), 193 deletions(-) create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/IWindowsFile.cs create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs 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..521fe6b3d6a4 100644 --- a/src/Files.App.CsWin32/ManualGuid.cs +++ b/src/Files.App.CsWin32/ManualGuid.cs @@ -41,6 +41,12 @@ 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; } } public static unsafe partial class CLSID diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index ce5524f6885e..fe13428f12a4 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -225,3 +225,4 @@ QITIPF_FLAGS GetKeyboardState MapVirtualKey GetKeyboardLayout +S_FALSE 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/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..8bdc62b64a14 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs @@ -0,0 +1,9 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + public interface IWindowsFolder : IWindowsStorable, IChildFolder + { + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs index 421d7a68dddd..11de2fb78624 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs @@ -1,13 +1,12 @@ // 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; } } } 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..e16b6ebedf74 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs @@ -1,7 +1,6 @@ // 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; @@ -10,80 +9,59 @@ namespace Files.App.Storage { [DebuggerDisplay("{" + nameof(ToString) + "()}")] - public sealed class WindowsFolder : WindowsStorable, IChildFolder + public unsafe class WindowsFolder : WindowsStorable, IWindowsFolder { - public WindowsFolder(ComPtr nativeObject) + public WindowsFolder(IShellItem* ptr) { - ThisPtr = nativeObject; - } - - public unsafe WindowsFolder(IShellItem* nativeObject) - { - 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); + List childItems = []; - 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; - } - } - - 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(); } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs index 3fdc51e33389..eba9e3e98944 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,50 @@ 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)] + protected 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 == 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)); } @@ -79,7 +71,7 @@ public override int GetHashCode() /// public void Dispose() { - ThisPtr.Dispose(); + ThisPtr->Release(); } /// @@ -94,7 +86,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..fba237e27594 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs @@ -17,7 +17,7 @@ public static partial class WindowsStorableHelpers public unsafe 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 +33,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; } @@ -49,20 +48,13 @@ public unsafe static HRESULT GetPropertyValue(this IWindowsStorable stor public unsafe static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes) { - return storable.ThisPtr.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && - returnedAttributes == 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; + return storable.ThisPtr->GetAttributes(attributes, out var dwRetAttributes).Succeeded && dwRetAttributes == attributes; } public unsafe 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 @@ -74,7 +66,7 @@ public unsafe static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable stor 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); @@ -99,7 +91,7 @@ public unsafe static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable sto 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); @@ -130,7 +122,7 @@ public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable, 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; 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 From c525ec96fdaace84badfac9b3ca4a6201eb91099 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sun, 15 Jun 2025 03:30:58 +0900 Subject: [PATCH 2/5] Implemented GetShellNewItems & InvokeShellNewItem --- src/Files.App.CsWin32/ManualGuid.cs | 27 +++ src/Files.App.CsWin32/NativeMethods.txt | 8 + .../WindowsStorage/ContextMenuItem.cs | 19 ++ .../WindowsStorage/ContextMenuType.cs | 18 ++ .../WindowsStorage/IWindowsFolder.cs | 8 +- .../WindowsStorage/IWindowsStorable.cs | 2 + .../Storables/WindowsStorage/WindowsFolder.cs | 18 ++ .../WindowsStorage/WindowsStorable.cs | 16 +- .../WindowsStorableHelpers.Shell.cs | 179 +++++++++++++++++- 9 files changed, 284 insertions(+), 11 deletions(-) create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/ContextMenuItem.cs create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/ContextMenuType.cs diff --git a/src/Files.App.CsWin32/ManualGuid.cs b/src/Files.App.CsWin32/ManualGuid.cs index 521fe6b3d6a4..bd360e69af75 100644 --- a/src/Files.App.CsWin32/ManualGuid.cs +++ b/src/Files.App.CsWin32/ManualGuid.cs @@ -47,6 +47,24 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory [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("BCC18B79-BA16-442F-80C4-8A59C30C463B")] + public static partial Guid* IID_IShellItemImageFactory { 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 @@ -65,6 +83,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 fe13428f12a4..4feabdf35b6d 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -226,3 +226,11 @@ GetKeyboardState MapVirtualKey GetKeyboardLayout S_FALSE +IExecuteCommand +IObjectWithSelection +SHCreateShellItemArrayFromShellItem +IShellExtInit +IContextMenu2 +GetSubMenu +GetMenuItemCount +GetMenuItemInfo 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/IWindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs index 8bdc62b64a14..92160da97f48 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs @@ -1,9 +1,15 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Windows.Win32.UI.Shell; + namespace Files.App.Storage { - public interface IWindowsFolder : IWindowsStorable, IChildFolder + 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 11de2fb78624..1cd69e32a44c 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs @@ -8,5 +8,7 @@ namespace Files.App.Storage public unsafe interface IWindowsStorable : IStorableChild, IEquatable, IDisposable { IShellItem* ThisPtr { get; } + + IContextMenu* ContextMenu { get; } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs index e16b6ebedf74..ee30f9eebe6f 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.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; @@ -11,6 +12,16 @@ namespace Files.App.Storage [DebuggerDisplay("{" + nameof(ToString) + "()}")] public unsafe class WindowsFolder : WindowsStorable, IWindowsFolder { + /// + public IContextMenu* ShellNewMenu + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected internal set; + } + public WindowsFolder(IShellItem* ptr) { ThisPtr = ptr; @@ -63,5 +74,12 @@ public IAsyncEnumerable GetItemsAsync(StorableType type = Storab 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 eba9e3e98944..c01104a08d5b 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs @@ -17,7 +17,16 @@ public IShellItem* ThisPtr get; [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected set; + protected internal set; + } + + public IContextMenu* ContextMenu + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected internal set; } public string Id => this.GetDisplayName(SIGDN.SIGDN_FILESYSPATH); @@ -69,9 +78,10 @@ public override int GetHashCode() } /// - public void Dispose() + public virtual void Dispose() { - ThisPtr->Release(); + if (ThisPtr is not null) ThisPtr->Release(); + if (ContextMenu is not null) ContextMenu->Release(); } /// diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs index fba237e27594..f63a2502009b 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs @@ -2,19 +2,22 @@ // 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->QueryInterface(IID.IID_IShellItem2, (void**)pShellItem2.GetAddressOf()); @@ -46,12 +49,12 @@ 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->GetAttributes(attributes, out var dwRetAttributes).Succeeded && dwRetAttributes == 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->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf()); @@ -61,7 +64,7 @@ public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN : 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); @@ -86,7 +89,7 @@ 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); @@ -117,7 +120,7 @@ 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; @@ -135,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; + } } } From dd62ca48413d237c5ac8ffde4f13699f20960953 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Fri, 20 Jun 2025 02:17:01 +0900 Subject: [PATCH 3/5] Improved STATask --- src/Files.App.CsWin32/ManualGuid.cs | 3 -- .../WindowsStorage/IWindowsStorable.cs | 4 +- .../Storables/WindowsStorage/STATask.cs | 47 +++++++++---------- .../Storables/WindowsStorage/WindowsFolder.cs | 2 +- .../WindowsStorage/WindowsStorable.cs | 6 +-- 5 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/Files.App.CsWin32/ManualGuid.cs b/src/Files.App.CsWin32/ManualGuid.cs index bd360e69af75..f95973c45048 100644 --- a/src/Files.App.CsWin32/ManualGuid.cs +++ b/src/Files.App.CsWin32/ManualGuid.cs @@ -57,9 +57,6 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory [GuidRVAGen.Guid("1C9CD5BB-98E9-4491-A60F-31AACC72B83C")] public static partial Guid* IID_IObjectWithSelection { get; } - [GuidRVAGen.Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")] - public static partial Guid* IID_IShellItemImageFactory { get; } - [GuidRVAGen.Guid("000214E8-0000-0000-C000-000000000046")] public static partial Guid* IID_IShellExtInit { get; } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs index 1cd69e32a44c..c79ef3ba66cb 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs @@ -7,8 +7,8 @@ namespace Files.App.Storage { public unsafe interface IWindowsStorable : IStorableChild, IEquatable, IDisposable { - IShellItem* ThisPtr { get; } + IShellItem* ThisPtr { get; set; } - IContextMenu* ContextMenu { get; } + 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/WindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs index ee30f9eebe6f..510300fa5f6d 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs @@ -19,7 +19,7 @@ public IContextMenu* ShellNewMenu get; [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected internal set; + set; } public WindowsFolder(IShellItem* ptr) diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs index c01104a08d5b..bde2995b6490 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs @@ -17,7 +17,7 @@ public IShellItem* ThisPtr get; [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected internal set; + set; } public IContextMenu* ContextMenu @@ -26,7 +26,7 @@ public IContextMenu* ContextMenu get; [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected internal set; + set; } public string Id => this.GetDisplayName(SIGDN.SIGDN_FILESYSPATH); @@ -49,7 +49,7 @@ public IContextMenu* ContextMenu public static WindowsStorable? TryParse(IShellItem* pShellItem) { - bool isFolder = pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && returnedAttributes == SFGAO_FLAGS.SFGAO_FOLDER; + bool isFolder = pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && returnedAttributes is SFGAO_FLAGS.SFGAO_FOLDER; return isFolder ? new WindowsFolder(pShellItem) : new WindowsFile(pShellItem); } From b91982ca3ba02b70764a3267b0c4c6d04b33c4a9 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Fri, 27 Jun 2025 19:21:32 +0900 Subject: [PATCH 4/5] Implemented SystemTrayManager --- .../WindowsStorage/SystemTrayManager.cs | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/SystemTrayManager.cs diff --git a/src/Files.App.Storage/Storables/WindowsStorage/SystemTrayManager.cs b/src/Files.App.Storage/Storables/WindowsStorage/SystemTrayManager.cs new file mode 100644 index 000000000000..9ab0741ccf82 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/SystemTrayManager.cs @@ -0,0 +1,158 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Files.App.Storage +{ + /// + /// Exposes a manager to create or delete an system tray icon you provide. + /// + public unsafe partial class SystemTrayManager : IDisposable + { + string _szWndClassName = null!; + string _szToolTip = null!; + HICON _hIcon = default; + private Guid _id; + private uint _dwCallbackMsgId; + Action _callback = null!; + + private HWND _hWnd = default; + private WNDPROC? _wndProc; + private bool _isInitialized; + private uint _dwTaskbarRestartMsgId; + private bool _isShown; + + public void Initialize(string szWndClassName, string szToolTip, HICON hIcon, Guid id, uint dwCallbackMsgId, Action callback) + { + _szWndClassName = szWndClassName; + _szToolTip = szToolTip; + _hIcon = hIcon; + _id = id; + _dwCallbackMsgId = dwCallbackMsgId; + _callback = callback; + + _isInitialized = true; + } + + public bool CreateIcon() + { + // Not expected usage + if (!_isInitialized) + throw new InvalidOperationException($"{nameof(SystemTrayManager)} is not initialized. Call {nameof(Initialize)}() before using this method."); + + if (_hWnd.IsNull) + _hWnd = CreateIconWindow(_szWndClassName); + + NOTIFYICONDATAW data = default; + data.cbSize = (uint)sizeof(NOTIFYICONDATAW); + data.hWnd = _hWnd; + data.uCallbackMessage = _dwCallbackMsgId; + data.guidItem = _id; + data.hIcon = _hIcon; + data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP | NOTIFY_ICON_DATA_FLAGS.NIF_GUID | NOTIFY_ICON_DATA_FLAGS.NIF_SHOWTIP; + data.szTip = _szToolTip; + data.Anonymous.uVersion = 4u; + + if (_isShown) + { + return PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, &data); + } + else + { + bool fRes = PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, &data); + if (!fRes) return false; + + fRes = PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, &data); + if (!fRes) return false; + + fRes = PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_SETVERSION, &data); + if (!fRes) return false; + + _isShown = true; + + return true; + } + } + + public bool DeleteIcon() + { + if (_isShown) + { + NOTIFYICONDATAW data = default; + data.cbSize = (uint)sizeof(NOTIFYICONDATAW); + data.hWnd = _hWnd; + data.guidItem = _id; + data.uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_GUID; + data.Anonymous.uVersion = 4u; + + return PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, &data); + } + + return true; + } + + private HWND CreateIconWindow(string szWndClassName) + { + fixed (char* pszWndClassName = szWndClassName) + { + _wndProc ??= new(WndProc); + + WNDCLASSEXW wndClass = default; + + wndClass.cbSize = (uint)sizeof(WNDCLASSEXW); + wndClass.style = WNDCLASS_STYLES.CS_DBLCLKS; + wndClass.hInstance = PInvoke.GetModuleHandle(default(PCWSTR)); + wndClass.lpszClassName = pszWndClassName; + wndClass.lpfnWndProc = (delegate* unmanaged[Stdcall]) + Marshal.GetFunctionPointerForDelegate(_wndProc); + + PInvoke.RegisterClassEx(&wndClass); + + _dwTaskbarRestartMsgId = PInvoke.RegisterWindowMessage("TaskbarCreated"); + + return PInvoke.CreateWindowEx( + WINDOW_EX_STYLE.WS_EX_LEFT, pszWndClassName, default, + WINDOW_STYLE.WS_OVERLAPPED, 0, 0, 1, 1, HWND.Null, HMENU.Null, HINSTANCE.Null, null); + } + } + + private LRESULT WndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam) + { + if (uMsg == _dwCallbackMsgId) + { + _callback((uint)(lParam.Value & 0xFFFF)); + + return default; + } + else if (uMsg is PInvoke.WM_DESTROY) + { + DeleteIcon(); + + return default; + } + else if (uMsg == _dwTaskbarRestartMsgId && _isInitialized) + { + DeleteIcon(); + CreateIcon(); + } + + return PInvoke.DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + public void Dispose() + { + if (!_hWnd.IsNull) + PInvoke.DestroyWindow(_hWnd); + + if (!_hIcon.IsNull) + PInvoke.DestroyIcon(_hIcon); + + _wndProc = null; + } + } +} From 4dfbaae3168ecee7846d1862d46e9b5a2a1c2c3a Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 30 Jun 2025 00:30:01 +0900 Subject: [PATCH 5/5] Update --- src/Files.App.CsWin32/NativeMethods.txt | 2 + .../WindowsStorage/JumpListDestinationType.cs | 17 +++++ .../WindowsStorage/JumpListManager.cs | 74 +++---------------- .../WindowsStorableHelpers.Process.cs | 28 +++++++ .../WindowsStorableHelpers.Storage.cs | 7 ++ .../Helpers/Win32/Win32Helper.Process.cs | 2 + 6 files changed, 66 insertions(+), 64 deletions(-) create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/JumpListDestinationType.cs create mode 100644 src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Process.cs diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt index 4feabdf35b6d..294dcf68ab36 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -234,3 +234,5 @@ IContextMenu2 GetSubMenu GetMenuItemCount GetMenuItemInfo +IsWow64Process2 +GetCurrentProcess diff --git a/src/Files.App.Storage/Storables/WindowsStorage/JumpListDestinationType.cs b/src/Files.App.Storage/Storables/WindowsStorage/JumpListDestinationType.cs new file mode 100644 index 000000000000..e176cc787ea2 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/JumpListDestinationType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Files.App.Storage +{ + public enum JumpListDestinationType + { + Pinned, + + Recent, + + Frequent, + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs b/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs index 3cb1dcf27e73..1f239a8ea144 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs @@ -12,83 +12,29 @@ namespace Files.App.Storage { public unsafe class JumpListManager : IDisposable { - private ComPtr pCustomDestinationList = default; + public string AppId { get; } - private static string? AppId + public JumpListManager(string appId) { - get - { - PWSTR pszAppId = default; - HRESULT hr = PInvoke.GetCurrentProcessExplicitAppUserModelID(&pszAppId); - if (hr == HRESULT.E_FAIL) - hr = HRESULT.S_OK; + if (string.IsNullOrEmpty(appId)) + throw new ArgumentException("App ID cannot be null or empty.", nameof(appId)); - hr.ThrowIfFailedOnDebug(); - - return pszAppId.ToString(); - } + AppId = appId; + //_jumpList = new ConcurrentDictionary(); } - public ConcurrentBag JumpListItems { get; private set; } = []; - - public ConcurrentBag RemovedItems { get; private set; } = []; - - public ConcurrentBag RejectedItems { get; private set; } = []; - - // A special "Frequent" category managed by Windows - public bool ShowFrequentCategory { get; set; } - - // A special "Recent" category managed by Windows - public bool ShowRecentCategory { get; set; } - - private static JumpListManager? _Default = null; - public static JumpListManager Default { get; } = _Default ??= new JumpListManager(); - - public JumpListManager() + public IEnumerable GetAutomaticDestinations() { - Guid CLSID_CustomDestinationList = typeof(DestinationList).GUID; - Guid IID_ICustomDestinationList = ICustomDestinationList.IID_Guid; - HRESULT hr = PInvoke.CoCreateInstance( - &CLSID_CustomDestinationList, - null, - CLSCTX.CLSCTX_INPROC_SERVER, - &IID_ICustomDestinationList, - (void**)pCustomDestinationList.GetAddressOf()); - - // Should not happen but as a sanity check at an early stage - hr.ThrowOnFailure(); + return []; } - public HRESULT Save() + public IEnumerable GetCustomDestinations() { - Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA); - - HRESULT hr = pCustomDestinationList.Get()->SetAppID(AppId); - - uint cMinSlots = 0; - ComPtr pDeletedItemsObjectArray = default; - Guid IID_IObjectArray = IObjectArray.IID_Guid; - - hr = pCustomDestinationList.Get()->BeginList(&cMinSlots, &IID_IObjectArray, (void**)pDeletedItemsObjectArray.GetAddressOf()); - - // TODO: Validate items - - // TODO: Group them as categories - - // TODO: Append a custom category or to the Tasks - - if (ShowFrequentCategory) - pCustomDestinationList.Get()->AppendKnownCategory(KNOWNDESTCATEGORY.KDC_FREQUENT); - - if (ShowRecentCategory) - pCustomDestinationList.Get()->AppendKnownCategory(KNOWNDESTCATEGORY.KDC_RECENT); - - return HRESULT.S_OK; + return []; } public void Dispose() { - pCustomDestinationList.Dispose(); } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Process.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Process.cs new file mode 100644 index 000000000000..724434b5f082 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Process.cs @@ -0,0 +1,28 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.SystemInformation; + +namespace Files.App.Storage +{ + public unsafe static partial class WindowsStorableHelpers + { + public static bool IsOnArmProcessor() + { + IMAGE_FILE_MACHINE dwMachineType = default; + + // Assumes the current process token has "PROCESS_QUERY_INFORMATION" or "PROCESS_QUERY_LIMITED_INFORMATION" access right + bool fResult = PInvoke.IsWow64Process2(PInvoke.GetCurrentProcess(), null, &dwMachineType); + if (!fResult) + Debug.WriteLine($"{nameof(PInvoke.IsWow64Process2)} has failed."); + + return dwMachineType is + IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_THUMB or + IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_ARMNT or + IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_ARM64 or + IMAGE_FILE_MACHINE.IMAGE_FILE_MACHINE_ARM; + } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs index 151cad3fbf3c..d3ad8823351a 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs @@ -84,5 +84,12 @@ public static bool TryShowFormatDriveDialog(HWND hWnd, uint driveLetterIndex, SH var result = PInvoke.SHFormatDrive(hWnd, driveLetterIndex, id, options); return result is 0xFFFF; } + + public static bool TryRenameVolumeLabel(string path, string newLabel) + { + // TODO: Use shell32.dll!CMountPointRename (CLSID: 60173D16-A550-47f0-A14B-C6F9E4DA0831, IID: 92F8D886-AB61-4113-BD4F-2E894397386F) + + return false; + } } } diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Process.cs b/src/Files.App/Helpers/Win32/Win32Helper.Process.cs index 1f73eeb9d795..2134889f8206 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.Process.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.Process.cs @@ -13,6 +13,7 @@ namespace Files.App.Helpers public static partial class Win32Helper { private static bool? isRunningOnArm = null; + [Obsolete] public static bool IsRunningOnArm { get @@ -158,6 +159,7 @@ public static List WhoIsLocking(string[] resources) return processes; } + [Obsolete] private static bool IsArmProcessor() { var handle = Process.GetCurrentProcess().Handle;