diff --git a/src/Files.App.CsWin32/ManualGuid.cs b/src/Files.App.CsWin32/ManualGuid.cs index 8ab59f21ee7f..c7ce6c7433ec 100644 --- a/src/Files.App.CsWin32/ManualGuid.cs +++ b/src/Files.App.CsWin32/ManualGuid.cs @@ -41,6 +41,15 @@ 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("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 +68,9 @@ public static unsafe partial class CLSID [GuidRVAGen.Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] public static partial Guid* CLSID_ApplicationActivationManager { 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..59de0f512e0b 100644 --- a/src/Files.App.CsWin32/NativeMethods.txt +++ b/src/Files.App.CsWin32/NativeMethods.txt @@ -225,3 +225,16 @@ QITIPF_FLAGS GetKeyboardState MapVirtualKey GetKeyboardLayout +HKEY_CLASSES_ROOT +HKEY_CURRENT_USER +RegOpenKeyEx +RegEnumKeyEx +RegGetValue +IShellExtInit +IContextMenu2 +GetSubMenu +GetMenuItemCount +GetMenuItemInfo +LoadString +LoadLibrary +FreeLibrary diff --git a/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs b/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs index 790cbc17c13e..56bbd5ccdfd7 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 items = []; + 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); - } + items.Add(new WindowsFolder(driveRoot.ThisPtr)); } + + return items.ToAsyncEnumerable(); } /// 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/IWindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs index 421d7a68dddd..531226c3f111 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 : IDisposable { - ComPtr ThisPtr { get; } + IShellItem* ThisPtr { get; } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/ShellNewItem.cs b/src/Files.App.Storage/Storables/WindowsStorage/ShellNewItem.cs new file mode 100644 index 000000000000..f88cbbf5e015 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/ShellNewItem.cs @@ -0,0 +1,19 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Storage +{ + /// + /// Represents a ShellNew item with its type, file extension, description, and icon. + /// + public partial class ShellNewItem + { + public ContextMenuType Type { get; set; } = ContextMenuType.Normal; + + public uint Id { get; set; } + + public byte[]? Icon { get; set; } + + public string? Name { get; set; } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs index a6393243246f..21f2657c4a5e 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs @@ -101,7 +101,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.Get()->CopyItem(targetItem.ThisPtr, destinationFolder.ThisPtr, pszCopyName, _pProgressSink.Get()); } /// @@ -111,7 +111,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.Get()->DeleteItem(targetItem.ThisPtr, _pProgressSink.Get()); } /// @@ -124,7 +124,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.Get()->MoveItem(targetItem.ThisPtr, destinationFolder.ThisPtr, pszNewName, null); } /// @@ -138,7 +138,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.Get()->NewItem(destinationFolder.ThisPtr, (uint)fileAttributes, pszName, pszTemplateName, _pProgressSink.Get()); } /// @@ -150,7 +150,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.Get()->RenameItem(targetItem.ThisPtr, pszNewName, _pProgressSink.Get()); } /// diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs index 3ce56f786c2f..8924f0b9ae3b 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, IChildFile { - 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..ff2975cb46a6 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs @@ -10,29 +10,40 @@ namespace Files.App.Storage { [DebuggerDisplay("{" + nameof(ToString) + "()}")] - public sealed class WindowsFolder : WindowsStorable, IChildFolder + public unsafe class WindowsFolder : WindowsStorable, IChildFolder { - public WindowsFolder(ComPtr nativeObject) + internal IContextMenu* NewMenuPtr { - ThisPtr = nativeObject; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set; + } + + internal IContextMenu* ContextMenuPtr + { + [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* pItem = 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**)&pItem); 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**)&pItem); // Invalid FOLDERID; this should never happen. hr.ThrowOnFailure(); @@ -43,47 +54,35 @@ public unsafe WindowsFolder(Guid folderId) public IAsyncEnumerable GetItemsAsync(StorableType type = StorableType.All, CancellationToken cancellationToken = default) { - return GetItems().ToAsyncEnumerable(); + ComPtr pEnumShellItems = default; + HRESULT hr = ThisPtr->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return Enumerable.Empty().ToAsyncEnumerable(); - unsafe IEnumerable GetItems() + List items = []; + IShellItem* pShellItem = default; + while (pEnumShellItems.Get()->Next(1, &pShellItem).Succeeded && pShellItem is not null) { - ComPtr pEnumShellItems = default; - GetEnumerator(); + cancellationToken.ThrowIfCancellationRequested(); - ComPtr pShellItem = default; - while (GetNext() && !pShellItem.IsNull) - { - cancellationToken.ThrowIfCancellationRequested(); - var isFolder = pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER); + var isFolder = pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded && + returnedAttributes == 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; - } + if (type is StorableType.File && !isFolder) + { + items.Add(new WindowsFile(pShellItem)); } - - yield break; - - unsafe void GetEnumerator() + else if (type is StorableType.Folder && isFolder) { - HRESULT hr = ThisPtr.Get()->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf()); - hr.ThrowIfFailedOnDebug(); + items.Add(new WindowsFolder(pShellItem)); } - - unsafe bool GetNext() + else { - HRESULT hr = pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf()); - return hr.ThrowIfFailedOnDebug() == HRESULT.S_OK; + continue; } } + + return items.ToAsyncEnumerable(); } } } diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs index 3fdc51e33389..1251bd1a5607 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,53 +9,54 @@ namespace Files.App.Storage { - public abstract class WindowsStorable : IWindowsStorable, IStorableChild, IEquatable + public unsafe abstract class WindowsStorable : IWindowsStorable, IStorableChild, IEquatable { - 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 Id + => this.GetDisplayName(SIGDN.SIGDN_FILESYSPATH); - public string Name => this.GetDisplayName(SIGDN.SIGDN_PARENTRELATIVEFORUI); + public string Name + => this.GetDisplayName(SIGDN.SIGDN_PARENTRELATIVEFORUI); - public static unsafe WindowsStorable? TryParse(string parsablePath) + public static WindowsStorable? TryParse(string parsablePath) { HRESULT hr = default; - ComPtr pShellItem = default; - var IID_IShellItem = typeof(IShellItem).GUID; + IShellItem* pShellItem = default; fixed (char* pszParsablePath = parsablePath) - { - hr = PInvoke.SHCreateItemFromParsingName( - pszParsablePath, - null, - &IID_IShellItem, - (void**)pShellItem.GetAddressOf()); - } + hr = PInvoke.SHCreateItemFromParsingName(pszParsablePath, null, IID.IID_IShellItem, (void**)&pShellItem); - if (pShellItem.IsNull) + if (pShellItem is null) return null; - return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER) - ? new WindowsFolder(pShellItem) - : new WindowsFile(pShellItem); + 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); } - public static unsafe WindowsStorable? TryParse(IShellItem* ptr) + public static WindowsStorable? TryParse(IShellItem* ptr) { - ComPtr pShellItem = default; - pShellItem.Attach(ptr); + bool isFolder = ptr->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(ptr) : new WindowsFile(ptr); } - public unsafe Task GetParentAsync(CancellationToken cancellationToken = default) + public Task GetParentAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); ComPtr pParentFolder = default; - HRESULT hr = ThisPtr.Get()->GetParent(pParentFolder.GetAddressOf()); + HRESULT hr = ThisPtr->GetParent(pParentFolder.GetAddressOf()); if (hr.Failed) { if (!pParentFolder.IsNull) pParentFolder.Dispose(); @@ -79,7 +81,7 @@ public override int GetHashCode() /// public void Dispose() { - ThisPtr.Dispose(); + ThisPtr->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.Shell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs deleted file mode 100644 index c09b2fc0d103..000000000000 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Files Community -// Licensed under the MIT License. - -using System.Runtime.CompilerServices; -using System.Text; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.System.SystemServices; -using Windows.Win32.UI.Shell; -using Windows.Win32.UI.Shell.PropertiesSystem; -using Windows.Win32.UI.WindowsAndMessaging; - -namespace Files.App.Storage -{ - 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()); - - PROPERTYKEY propertyKey = default; - fixed (char* pszPropertyKey = propKey) - hr = PInvoke.PSGetPropertyKeyFromName(pszPropertyKey, &propertyKey); - - if (typeof(TValue) == typeof(string)) - { - ComHeapPtr szPropertyValue = default; - hr = pShellItem2.Get()->GetString(&propertyKey, szPropertyValue.Get()); - value = (TValue)(object)szPropertyValue.Get()->ToString(); - - return hr; - } - if (typeof(TValue) == typeof(bool)) - { - bool propertyValue = false; - hr = pShellItem2.Get()->GetBool(propertyKey, out var fPropertyValue); - propertyValue = fPropertyValue; - value = Unsafe.As(ref propertyValue); - - return hr; - } - else - { - value = default!; - return HRESULT.E_FAIL; - } - } - - 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; - } - - 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()); - - 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) - { - 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()); - HMENU hMenu = PInvoke.CreatePopupMenu(); - hr = pContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 0x7FFF, PInvoke.CMF_OPTIMIZEFORINVOKE); - - CMINVOKECOMMANDINFO cmici = default; - cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO); - cmici.nShow = (int)SHOW_WINDOW_CMD.SW_HIDE; - - fixed (byte* pszVerbName = Encoding.ASCII.GetBytes(verbName)) - { - cmici.lpVerb = new(pszVerbName); - hr = pContextMenu.Get()->InvokeCommand(cmici); - - if (!PInvoke.DestroyMenu(hMenu)) - return HRESULT.E_FAIL; - - return hr; - } - } - - public unsafe 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()); - HMENU hMenu = PInvoke.CreatePopupMenu(); - hr = pContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 0x7FFF, PInvoke.CMF_OPTIMIZEFORINVOKE); - - CMINVOKECOMMANDINFO cmici = default; - cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO); - cmici.nShow = (int)SHOW_WINDOW_CMD.SW_HIDE; - - foreach (var verbName in verbNames) - { - fixed (byte* pszVerbName = Encoding.ASCII.GetBytes(verbName)) - { - cmici.lpVerb = new(pszVerbName); - hr = pContextMenu.Get()->InvokeCommand(cmici); - - if (!PInvoke.DestroyMenu(hMenu)) - return HRESULT.E_FAIL; - - if (hr.Succeeded && earlyReturnOnSuccess) - return hr; - } - } - - return hr; - } - - public unsafe 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()); - if (hr.ThrowIfFailedOnDebug().Failed) - return hr; - - pQueryInfo.Get()->GetInfoTip((uint)QITIPF_FLAGS.QITIPF_DEFAULT, out var pszTip); - if (hr.ThrowIfFailedOnDebug().Failed) - return hr; - - tooltip = pszTip.ToString(); - PInvoke.CoTaskMemFree(pszTip); - - return HRESULT.S_OK; - } - } -} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Icon.cs similarity index 95% rename from src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs rename to src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Icon.cs index 9b1fd95a31a5..603c993003c6 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Icon.cs @@ -13,7 +13,7 @@ namespace Files.App.Storage { - public static partial class WindowsStorableHelpers + public static partial class WindowsStorageHelpers { // Fields @@ -45,7 +45,8 @@ 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; @@ -101,7 +102,7 @@ public unsafe static HRESULT TryGetThumbnail(this IWindowsStorable storable, int return HRESULT.S_OK; } - public unsafe static HRESULT TryExtractImageFromDll(this IWindowsStorable storable, int size, int index, out byte[]? imageData) + public unsafe static HRESULT TryExtractImageFromExecutable(this IWindowsStorable storable, int size, int index, out byte[]? imageData) { DllIconCache ??= []; imageData = null; @@ -270,7 +271,7 @@ public unsafe static HRESULT TrySetShortcutIcon(this IWindowsStorable storable, 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_SFUIObject, &IID_IShellLink, (void**)pShellLink.GetAddressOf()); if (hr.ThrowIfFailedOnDebug().Failed) return hr; diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.PowerShell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.PowerShell.cs similarity index 95% rename from src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.PowerShell.cs rename to src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.PowerShell.cs index fcc3da01b630..f4fbd7818413 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.PowerShell.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.PowerShell.cs @@ -3,7 +3,7 @@ namespace Files.App.Storage { - public static partial class WindowsStorableHelpers + public static partial class WindowsStorageHelpers { public static async Task TrySetShortcutIconOnPowerShellAsElevatedAsync(this IWindowsStorable storable, IWindowsStorable iconFile, int index) { diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Registry.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Registry.cs new file mode 100644 index 000000000000..c6a356a75e28 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Registry.cs @@ -0,0 +1,174 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Registry; + +namespace Files.App.Storage +{ + public unsafe static partial class WindowsStorageHelpers + { + public static bool OpenRegistryKey(HKEY hRootKey, string subKey, REG_SAM_FLAGS flags, out HKEY hKey) + { + WIN32_ERROR dwResult = default; + HKEY hResultKey = default; + + fixed (char* pszSubKey = subKey) + dwResult = PInvoke.RegOpenKeyEx(hRootKey, pszSubKey, 0, flags, &hResultKey); + + hKey = hResultKey; + + return dwResult is WIN32_ERROR.ERROR_SUCCESS; + } + + public static bool GetRegistryValue(HKEY hRootKey, string szSubKey, string szValueName, REG_ROUTINE_FLAGS dwRoutineFlags, out T? valueData) + { + valueData = default; + + WIN32_ERROR dwResult = default; + HKEY hKey = hRootKey; + + if (!string.IsNullOrEmpty(szSubKey)) + { + fixed (char* pszSubKey = szSubKey) + dwResult = PInvoke.RegOpenKeyEx(hRootKey, pszSubKey, 0, REG_SAM_FLAGS.KEY_QUERY_VALUE, &hKey); + + if (dwResult is not WIN32_ERROR.ERROR_SUCCESS) + { + if (!hKey.IsNull) PInvoke.RegCloseKey(hKey); + return false; + } + } + + REG_VALUE_TYPE dwValueType = default; + byte* pData = null; + uint cbData = 0U; + + fixed (char* pszValueName = szValueName) + { + dwResult = PInvoke.RegGetValue(hKey, default, pszValueName, dwRoutineFlags, null, null, &cbData); + + if (dwResult is WIN32_ERROR.ERROR_SUCCESS or WIN32_ERROR.ERROR_MORE_DATA) + { + if (cbData is 0U) + return false; + + pData = (byte*)NativeMemory.Alloc(cbData); + dwResult = PInvoke.RegGetValue(hKey, default, pszValueName, dwRoutineFlags, &dwValueType, pData, &cbData); + + switch (dwValueType) + { + default: + case REG_VALUE_TYPE.REG_NONE: + case REG_VALUE_TYPE.REG_BINARY: + { + byte[] byteArrayData = new byte[cbData]; + Marshal.Copy((nint)pData, byteArrayData, 0, (int)cbData); + + valueData = (T)(object)byteArrayData; + } + break; + case REG_VALUE_TYPE.REG_DWORD: + case REG_VALUE_TYPE.REG_QWORD: + { + valueData = cbData switch + { + 4U => Unsafe.As(ref *(uint*)pData), + 8U => Unsafe.As(ref *(ulong*)pData), + _ => throw new InvalidCastException($"Registry value size of data \"{nameof(pData)}\" of \"{nameof(pszValueName)}\" is invalid (size: \"{cbData}\")."), + }; + } + break; + case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: + { + var uint32Data = BinaryPrimitives.ReadUInt32BigEndian(new Span(pData, (int)cbData)); + valueData = Unsafe.As(ref uint32Data); + } + break; + case REG_VALUE_TYPE.REG_SZ: + case REG_VALUE_TYPE.REG_EXPAND_SZ: + { + valueData = (T)(object)new string((char*)pData); + } + break; + case REG_VALUE_TYPE.REG_MULTI_SZ: + { + byte* pDataPtrSeeker = pData; + uint dwSeparatorCount = 0U; + uint dwArrayIndex = 0U; + string[] stringDataArray = new string[dwSeparatorCount + 1]; + + while (pDataPtrSeeker < pData + cbData) + { + if ((char)*pDataPtrSeeker is '\0') + dwSeparatorCount++; + + pDataPtrSeeker++; + } + + // Reset pointer to the start of the data + pDataPtrSeeker = pData; + + while (pDataPtrSeeker < pData + cbData) + { + if ((char)*pDataPtrSeeker is '\0') + { + dwArrayIndex++; + continue; + } + + stringDataArray[dwArrayIndex] = new((char*)pData); + pDataPtrSeeker += stringDataArray[dwArrayIndex].Length; + } + + valueData = (T)(object)stringDataArray; + } + break; + } + + NativeMemory.Free(pData); + } + } + + if (!hKey.IsNull) PInvoke.RegCloseKey(hKey); + + return dwResult is WIN32_ERROR.ERROR_SUCCESS; + } + + public static string[] EnumerateRegistryKeyNames(HKEY hKey) + { + WIN32_ERROR dwResult = default; + uint dwIndex = 0U; + char* pszName = stackalloc char[256]; // 255 chars + null terminator + uint cchName = 256U; + string[] keyNames = []; + + while ((dwResult = PInvoke.RegEnumKeyEx(hKey, dwIndex, pszName, &cchName, null, null, null, null)) is not WIN32_ERROR.ERROR_NO_MORE_ITEMS) + { + if (dwResult is WIN32_ERROR.ERROR_SUCCESS) + { + // Double the capacity of the array if necessary + if (dwIndex >= keyNames.Length) + Array.Resize(ref keyNames, keyNames.Length < 10 ? 10 : keyNames.Length * 2); + + keyNames[dwIndex++] = new string(pszName); + } + else + { + // An error occurred, handle it accordingly + return []; + } + } + + // Fit the array if necessary + if (dwIndex < keyNames.Length) + Array.Resize(ref keyNames, (int)dwIndex); + + return keyNames; + } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Shell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Shell.cs new file mode 100644 index 000000000000..23d6a05b0371 --- /dev/null +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Shell.cs @@ -0,0 +1,256 @@ +// Copyright (c) Files Community +// 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 unsafe static partial class WindowsStorageHelpers + { + public unsafe 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()); + + PROPERTYKEY propertyKey = default; + fixed (char* pszPropertyKey = propKey) + hr = PInvoke.PSGetPropertyKeyFromName(pszPropertyKey, &propertyKey); + + if (typeof(TValue) == typeof(string)) + { + ComHeapPtr szPropertyValue = default; + hr = pShellItem2.Get()->GetString(&propertyKey, szPropertyValue.Get()); + value = (TValue)(object)szPropertyValue.Get()->ToString(); + + return hr; + } + if (typeof(TValue) == typeof(bool)) + { + bool propertyValue = false; + hr = pShellItem2.Get()->GetBool(propertyKey, out var fPropertyValue); + propertyValue = fPropertyValue; + value = Unsafe.As(ref propertyValue); + + return hr; + } + else + { + value = default!; + return HRESULT.E_FAIL; + } + } + + public unsafe static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes) + { + return storable.ThisPtr->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(attributes, out var returnedAttributes).Succeeded && + returnedAttributes == attributes; + } + + public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH) + { + using ComHeapPtr pszName = default; + 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) + { + Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA); + + using ComPtr pContextMenu = default; + 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); + + CMINVOKECOMMANDINFO cmici = default; + cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO); + cmici.nShow = (int)SHOW_WINDOW_CMD.SW_HIDE; + + fixed (byte* pszVerbName = Encoding.ASCII.GetBytes(verbName)) + { + cmici.lpVerb = new(pszVerbName); + hr = pContextMenu.Get()->InvokeCommand(cmici); + + if (!PInvoke.DestroyMenu(hMenu)) + return HRESULT.E_FAIL; + + return hr; + } + } + + public unsafe 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->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); + + CMINVOKECOMMANDINFO cmici = default; + cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO); + cmici.nShow = (int)SHOW_WINDOW_CMD.SW_HIDE; + + foreach (var verbName in verbNames) + { + fixed (byte* pszVerbName = Encoding.ASCII.GetBytes(verbName)) + { + cmici.lpVerb = new(pszVerbName); + hr = pContextMenu.Get()->InvokeCommand(cmici); + + if (!PInvoke.DestroyMenu(hMenu)) + return HRESULT.E_FAIL; + + if (hr.Succeeded && earlyReturnOnSuccess) + return hr; + } + } + + return hr; + } + + public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable, out string? tooltip) + { + tooltip = null; + + using ComPtr pQueryInfo = default; + HRESULT hr = storable.ThisPtr->BindToHandler(null, BHID.BHID_SFUIObject, IID.IID_IQueryInfo, (void**)pQueryInfo.GetAddressOf()); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + pQueryInfo.Get()->GetInfoTip((uint)QITIPF_FLAGS.QITIPF_DEFAULT, out var pszTip); + if (hr.ThrowIfFailedOnDebug().Failed) + return hr; + + tooltip = pszTip.ToString(); + PInvoke.CoTaskMemFree(pszTip); + + return HRESULT.S_OK; + } + + public static IEnumerable GetShellNewItems(this IWindowsStorable storable) + { + if (storable is not WindowsFolder folder) + return []; + + 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 []; + + folder.NewMenuPtr = pNewMenu; + + ITEMIDLIST* pFolderPidl = default; + hr = PInvoke.SHGetIDListFromObject((IUnknown*)storable.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 IWindowsStorable storable, ShellNewItem item) + { + if (storable is not WindowsFolder folder) + return false; + + HRESULT hr = default; + + if (folder.NewMenuPtr 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; + + folder.NewMenuPtr = pNewMenu; + } + + CMINVOKECOMMANDINFO cmici = default; + cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO); + cmici.lpVerb = (PCSTR)(byte*)item.Id; + cmici.nShow = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL; + + hr = folder.NewMenuPtr->InvokeCommand(&cmici); + if (hr.ThrowIfFailedOnDebug().Failed) + return false; + + return false; + } + } +} diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Storage.cs similarity index 74% rename from src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs rename to src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Storage.cs index 151cad3fbf3c..63f58f8e1178 100644 --- a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs +++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorageHelpers.Storage.cs @@ -1,6 +1,7 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using System.Runtime.InteropServices; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; @@ -8,7 +9,7 @@ namespace Files.App.Storage { - public unsafe static partial class WindowsStorableHelpers + public unsafe static partial class WindowsStorageHelpers { public static bool TryGetFileAttributes(this IWindowsStorable storable, out FILE_FLAGS_AND_ATTRIBUTES attributes) { @@ -84,5 +85,37 @@ public static bool TryShowFormatDriveDialog(HWND hWnd, uint driveLetterIndex, SH var result = PInvoke.SHFormatDrive(hWnd, driveLetterIndex, id, options); return result is 0xFFFF; } + + public static bool TryExtractStringFromExecutable(string szExecutablePath, uint dwIndex, out string extractedString) + { + extractedString = string.Empty; + + HINSTANCE hInst = default; + + fixed (char* pszExecutablePath = szExecutablePath) + { + hInst = PInvoke.LoadLibrary(pszExecutablePath); + if (hInst.IsNull) + return false; + } + + int cchExtractedString = 1024; + char* pszExtractedString = (char*)NativeMemory.Alloc((nuint)cchExtractedString); + if (pszExtractedString is null) + { + if (!hInst.IsNull) PInvoke.FreeLibrary(hInst); + return false; + } + + cchExtractedString = PInvoke.LoadString(hInst, dwIndex, pszExtractedString, cchExtractedString); + if (cchExtractedString is 0) + { + if (!hInst.IsNull) PInvoke.FreeLibrary(hInst); + if (pszExtractedString is not null) NativeMemory.Free(pszExtractedString); + return false; + } + + return true; + } } } diff --git a/src/Files.App/Extensions/ShellNewEntryExtensions.cs b/src/Files.App/Extensions/ShellNewEntryExtensions.cs index e09ad6e7fb2f..f6056ddcb45b 100644 --- a/src/Files.App/Extensions/ShellNewEntryExtensions.cs +++ b/src/Files.App/Extensions/ShellNewEntryExtensions.cs @@ -1,19 +1,12 @@ // Copyright (c) Files Community // Licensed under the MIT License. -using Files.App.Utils; -using Files.App.Helpers; -using Files.App.Utils.Shell; -using Files.Shared; -using Files.Shared.Extensions; -using System; -using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; using Windows.Storage; namespace Files.App.Extensions { + [Obsolete($"Use {nameof(WindowsStorageHelpers.GetShellNewItems)} instead.")] public static class ShellNewEntryExtensions { public static async Task> GetNewContextMenuEntries() diff --git a/src/Files.App/Extensions/Win32Extensions.cs b/src/Files.App/Extensions/Win32Extensions.cs deleted file mode 100644 index 7d27ee02c20f..000000000000 --- a/src/Files.App/Extensions/Win32Extensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Files Community -// Licensed under the MIT License. - -namespace Files.App.Extensions -{ - public static class Win32Extensions - { - public static bool IsHandleInvalid(this IntPtr handle) - { - return handle == IntPtr.Zero || handle.ToInt64() == -1; - } - } -} diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs index 7e878896a3f9..4f65fe78899e 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs @@ -14,9 +14,7 @@ using Vanara.PInvoke; using Windows.System; using Windows.Win32; -using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; -using static Vanara.PInvoke.Kernel32; using COMPRESSION_FORMAT = Windows.Win32.Storage.FileSystem.COMPRESSION_FORMAT; using HRESULT = Vanara.PInvoke.HRESULT; using HWND = Vanara.PInvoke.HWND; @@ -28,6 +26,7 @@ namespace Files.App.Helpers /// public static partial class Win32Helper { + [Obsolete($"Use {nameof(STATask)} instead.")] public static Task StartSTATask(Func func) { var taskCompletionSource = new TaskCompletionSource(); @@ -62,6 +61,7 @@ public static Task StartSTATask(Func func) return taskCompletionSource.Task; } + [Obsolete($"Use {nameof(STATask)} instead.")] public static Task StartSTATask(Action action) { var taskCompletionSource = new TaskCompletionSource(); @@ -96,6 +96,7 @@ public static Task StartSTATask(Action action) return taskCompletionSource.Task; } + [Obsolete($"Use {nameof(STATask)} instead.")] public static Task StartSTATask(Func func) { var taskCompletionSource = new TaskCompletionSource(); @@ -131,6 +132,7 @@ public static Task StartSTATask(Action action) return taskCompletionSource.Task; } + [Obsolete($"Use {nameof(STATask)} instead.")] public static Task StartSTATask(Func> func) { var taskCompletionSource = new TaskCompletionSource(); @@ -189,6 +191,7 @@ public static Task StartSTATask(Action action) return await GetUwpAssoc() ?? GetDesktopAssoc(); } + [Obsolete($"Use {nameof(WindowsStorageHelpers.TryExtractStringFromExecutable)} instead.")] public static string ExtractStringFromDLL(string file, int number) { var lib = Kernel32.LoadLibrary(file); @@ -228,12 +231,6 @@ public static string ExtractStringFromDLL(string file, int number) private static readonly object _iconOverlayLock = new object(); - /// - /// Returns overlay for given file or folder - /// - /// - /// - /// public static byte[]? GetIconOverlay(string path, bool isDirectory) { var shFileInfo = new Shell32.SHFILEINFO(); @@ -287,16 +284,7 @@ public static string ExtractStringFromDLL(string file, int number) /// /// Returns an icon if returnIconOnly is true, otherwise a thumbnail will be returned if available. /// - /// - /// - /// - /// - /// - public static byte[]? GetIcon( - string path, - int size, - bool isFolder, - IconOptions iconOptions) + public static byte[]? GetIcon(string path, int size, bool isFolder, IconOptions iconOptions) { byte[]? iconData = null; @@ -454,6 +442,7 @@ public static bool RunPowershellCommand(string command, PowerShellExecutionOptio private static readonly ConcurrentDictionary<(string File, int Index, int Size), IconFileInfo> _iconCache = new(); + [Obsolete($"Use {nameof(WindowsStorageHelpers.TryExtractImageFromExecutable)} instead.")] public static IList ExtractSelectedIconsFromDLL(string file, IList indexes, int iconSize = 48) { var iconsList = new List(); @@ -483,6 +472,7 @@ public static IList ExtractSelectedIconsFromDLL(string file, IList return iconsList; } + [Obsolete($"Use {nameof(WindowsStorageHelpers.TryExtractImageFromExecutable)} instead.")] public static IList? ExtractIconsFromDLL(string file) { var iconsList = new List(); @@ -517,6 +507,7 @@ public static IList ExtractSelectedIconsFromDLL(string file, IList return iconsList; } + [Obsolete($"Use {nameof(WindowsStorageHelpers.TrySetFolderIcon)} instead.")] public static bool SetCustomDirectoryIcon(string? folderPath, string? iconFile, int iconIndex = 0) { if (folderPath is null) @@ -537,6 +528,7 @@ public static bool SetCustomDirectoryIcon(string? folderPath, string? iconFile, return success; } + [Obsolete($"Use {nameof(WindowsStorageHelpers.TrySetShortcutIcon)} instead.")] public static bool SetCustomFileIcon(string? filePath, string? iconFile, int iconIndex = 0) { if (filePath is null) @@ -547,6 +539,7 @@ public static bool SetCustomFileIcon(string? filePath, string? iconFile, int ico return success; } + [Obsolete($"Use {nameof(WindowsStorageHelpers.TryShowFormatDriveDialog)} instead.")] public static Task OpenFormatDriveDialog(string drive) { // Format requires elevation @@ -571,6 +564,7 @@ public static Task MountVhdDisk(string vhdPath) return RunPowershellCommandAsync($"-command \"Mount-DiskImage -ImagePath '{vhdPath}'\"", PowerShellExecutionOptions.Elevated | PowerShellExecutionOptions.Hidden); } + [Obsolete($"Don't use this method any more.")] public static Bitmap? GetBitmapFromHBitmap(HBITMAP hBitmap) { try diff --git a/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs b/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs index 9a3cf4a30804..5a528256b3d2 100644 --- a/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs +++ b/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs @@ -3,9 +3,9 @@ using System.IO; using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Security; using Windows.Win32.Storage.FileSystem; -using static Files.App.Helpers.Win32Helper; -using static Files.App.Helpers.Win32PInvoke; namespace Files.App.Utils.Serialization.Implementation { @@ -13,17 +13,28 @@ internal sealed class DefaultSettingsSerializer : ISettingsSerializer { private string? _filePath; - public bool CreateFile(string path) + public unsafe bool CreateFile(string path) { PInvoke.CreateDirectoryFromApp(Path.GetDirectoryName(path), null); - var hFile = CreateFileFromApp(path, (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero); - if (hFile.IsHandleInvalid()) + HANDLE hFile = default; + fixed (char* pPath = path) { - return false; + hFile = PInvoke.CreateFile( + pPath, + (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, + FILE_SHARE_MODE.FILE_SHARE_READ, + (SECURITY_ATTRIBUTES*)null, + FILE_CREATION_DISPOSITION.OPEN_ALWAYS, + FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_BACKUP_SEMANTICS, + HANDLE.Null); } - Win32PInvoke.CloseHandle(hFile); + // File handle is invalid + if (hFile.IsNull || hFile.Value == (void*)-1) + return false; + + PInvoke.CloseHandle(hFile); _filePath = path; return true; @@ -38,14 +49,14 @@ public string ReadFromFile() { _ = _filePath ?? throw new ArgumentNullException(nameof(_filePath)); - return ReadStringFromFile(_filePath); + return Win32Helper.ReadStringFromFile(_filePath); } public bool WriteToFile(string? text) { _ = _filePath ?? throw new ArgumentNullException(nameof(_filePath)); - return WriteStringToFile(_filePath, text); + return Win32Helper.WriteStringToFile(_filePath, text); } } } diff --git a/src/Files.App/Utils/Shell/ShellNewMenuHelper.cs b/src/Files.App/Utils/Shell/ShellNewMenuHelper.cs index 9fac780426b7..e2f9f9331b96 100644 --- a/src/Files.App/Utils/Shell/ShellNewMenuHelper.cs +++ b/src/Files.App/Utils/Shell/ShellNewMenuHelper.cs @@ -12,6 +12,7 @@ namespace Files.App.Utils.Shell /// /// Provides static helper to get extension-specific shell context menu from Windows Registry. /// + [Obsolete($"Use {nameof(WindowsStorageHelpers.GetShellNewItems)} instead.")] public static class ShellNewMenuHelper { public static async Task> GetNewContextMenuEntries() diff --git a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs index 2739fb84162a..a55b15bd2034 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 @@ -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