diff --git a/src/Files.App.CsWin32/Extras.cs b/src/Files.App.CsWin32/Extras.cs
index e35fb4dc0f02..66443d0e1939 100644
--- a/src/Files.App.CsWin32/Extras.cs
+++ b/src/Files.App.CsWin32/Extras.cs
@@ -37,6 +37,9 @@ public static unsafe nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nInd
? (nint)_SetWindowLong(hWnd, (int)nIndex, (int)dwNewLong)
: _SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong);
}
+
+ [DllImport("shell32.dll", EntryPoint = "SHUpdateRecycleBinIcon", CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern void SHUpdateRecycleBinIcon();
}
namespace Extras
diff --git a/src/Files.App.CsWin32/HRESULT.Extensions.cs b/src/Files.App.CsWin32/HRESULT.cs
similarity index 56%
rename from src/Files.App.CsWin32/HRESULT.Extensions.cs
rename to src/Files.App.CsWin32/HRESULT.cs
index 8d8315526d71..b8415f666e0d 100644
--- a/src/Files.App.CsWin32/HRESULT.Extensions.cs
+++ b/src/Files.App.CsWin32/HRESULT.cs
@@ -1,26 +1,25 @@
// Copyright (c) Files Community
// Licensed under the MIT License.
+using System.Diagnostics;
using System.Runtime.InteropServices;
-using Windows.Win32.Foundation;
-namespace Windows.Win32
+namespace Windows.Win32.Foundation
{
- public static class HRESULTExtensions
+ [DebuggerDisplay("{" + nameof(Value) + ",h}")]
+ public readonly partial struct HRESULT
{
///
/// Throws an exception if the indicates a failure in debug mode. Otherwise, it returns the original .
///
- /// Represents the result of an operation, indicating success or failure.
/// Returns the original value regardless of the operation's success.
- public static HRESULT ThrowIfFailedOnDebug(this HRESULT hr)
+ public readonly HRESULT ThrowIfFailedOnDebug()
{
#if DEBUG
- if (hr.Failed)
- Marshal.ThrowExceptionForHR(hr.Value);
+ if (Failed) Marshal.ThrowExceptionForHR(Value);
#endif
- return hr;
+ return this;
}
}
}
diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt
index 655fad506192..f041afd1ce4e 100644
--- a/src/Files.App.CsWin32/NativeMethods.txt
+++ b/src/Files.App.CsWin32/NativeMethods.txt
@@ -172,3 +172,50 @@ FILE_FILE_COMPRESSION
WM_WINDOWPOSCHANGING
WINDOWPOS
UnregisterClass
+E_POINTER
+E_NOINTERFACE
+E_FAIL
+IShellFolder
+FILE_FLAGS_AND_ATTRIBUTES
+GetLogicalDrives
+IShellItemImageFactory
+GdipCreateBitmapFromHBITMAP
+GdipGetImageEncodersSize
+GdipGetImageEncoders
+ImageFormatPNG
+ImageFormatJPEG
+IStream
+CreateStreamOnHGlobal
+STATFLAG
+STREAM_SEEK
+GdipSaveImageToStream
+GdipGetImageRawFormat
+_TRANSFER_SOURCE_FLAGS
+E_NOTIMPL
+DeleteObject
+GdipDisposeImage
+DeleteObject
+OleInitialize
+OleUninitialize
+IShellIconOverlayManager
+SHGetFileInfo
+GetFileAttributes
+SetFileAttributes
+INVALID_FILE_ATTRIBUTES
+SHDefExtractIconW
+GdipCreateBitmapFromHICON
+SHGetSetFolderCustomSettings
+FCSM_ICONFILE
+FCS_FORCEWRITE
+IShellLinkW
+SHFormatDrive
+ITaskbarList
+ITaskbarList2
+ITaskbarList3
+ITaskbarList4
+TaskbarList
+ICustomDestinationList
+DestinationList
+IObjectArray
+GetCurrentProcessExplicitAppUserModelID
+SetCurrentProcessExplicitAppUserModelID
diff --git a/src/Files.App.Storage/Files.App.Storage.csproj b/src/Files.App.Storage/Files.App.Storage.csproj
index 637dda48c69d..8a03ac2ef60f 100644
--- a/src/Files.App.Storage/Files.App.Storage.csproj
+++ b/src/Files.App.Storage/Files.App.Storage.csproj
@@ -9,6 +9,7 @@
Debug;Release
x86;x64;arm64
win-x86;win-x64;win-arm64
+ true
@@ -16,6 +17,7 @@
+
diff --git a/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs b/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs
new file mode 100644
index 000000000000..879cdbf9f2bd
--- /dev/null
+++ b/src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using Windows.Win32;
+
+namespace Files.App.Storage.Storables
+{
+ public partial class HomeFolder : IHomeFolder
+ {
+ public string Id => "Home"; // Will be "files://Home" in the future.
+
+ public string Name => "Home";
+
+ ///
+ public async IAsyncEnumerable GetItemsAsync(StorableType type = StorableType.Folder, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ await foreach (var folder in GetQuickAccessFolderAsync(cancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ yield return folder;
+ }
+
+ await foreach (var drive in GetLogicalDrivesAsync(cancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ yield return drive;
+ }
+
+ await foreach (var location in GetNetworkLocationsAsync(cancellationToken))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ yield return location;
+ }
+ }
+
+ ///
+ public IAsyncEnumerable GetQuickAccessFolderAsync(CancellationToken cancellationToken = default)
+ {
+ IFolder folder = new WindowsFolder(new Guid("3936e9e4-d92c-4eee-a85a-bc16d5ea0819"));
+ return folder.GetItemsAsync(StorableType.Folder, cancellationToken);
+ }
+
+ ///
+ public async IAsyncEnumerable GetLogicalDrivesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var availableDrives = PInvoke.GetLogicalDrives();
+ if (availableDrives is 0)
+ yield break;
+
+ 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;
+
+ availableDrives >>= 1;
+ driveLetter++;
+ }
+
+ foreach (int letter in driveLetters)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
+ throw new InvalidOperationException();
+
+ yield return new WindowsFolder(driveRoot.ThisPtr);
+ await Task.Yield();
+ }
+
+ }
+
+ ///
+ public IAsyncEnumerable GetNetworkLocationsAsync(CancellationToken cancellationToken = default)
+ {
+ Guid FOLDERID_NetHood = new("{C5ABBF53-E17F-4121-8900-86626FC2C973}");
+ IFolder folder = new WindowsFolder(FOLDERID_NetHood);
+ return folder.GetItemsAsync(StorableType.Folder, cancellationToken);
+ }
+
+ ///
+ public IAsyncEnumerable GetRecentFilesAsync(CancellationToken cancellationToken = default)
+ {
+ Guid FOLDERID_NetHood = new("{AE50C081-EBD2-438A-8655-8A092E34987A}");
+ IFolder folder = new WindowsFolder(FOLDERID_NetHood);
+ return folder.GetItemsAsync(StorableType.Folder, cancellationToken);
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/HomeFolder/IHomeFolder.cs b/src/Files.App.Storage/Storables/HomeFolder/IHomeFolder.cs
new file mode 100644
index 000000000000..e7db124aa168
--- /dev/null
+++ b/src/Files.App.Storage/Storables/HomeFolder/IHomeFolder.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Storage.Storables
+{
+ public partial interface IHomeFolder : IFolder
+ {
+ ///
+ /// Gets quick access folders.
+ ///
+ /// The cancellation token.
+ /// A list of the collection.
+ public IAsyncEnumerable GetQuickAccessFolderAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets available logical drives.
+ ///
+ /// The cancellation token.
+ /// A list of the collection.
+ public IAsyncEnumerable GetLogicalDrivesAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets network locations(shortcuts).
+ ///
+ /// The cancellation token.
+ /// A list of the collection.
+ public IAsyncEnumerable GetNetworkLocationsAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets recent files.
+ ///
+ /// The cancellation token.
+ /// A list of the collection.
+ public IAsyncEnumerable GetRecentFilesAsync(CancellationToken cancellationToken = default);
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs
new file mode 100644
index 000000000000..788989ca8658
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/IWindowsStorable.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ public interface IWindowsStorable
+ {
+ ComPtr ThisPtr { get; }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/JumpListItem.cs b/src/Files.App.Storage/Storables/WindowsStorage/JumpListItem.cs
new file mode 100644
index 000000000000..ff20f2229405
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/JumpListItem.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Storage
+{
+ public partial class JumpListItem
+ {
+
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/JumpListItemType.cs b/src/Files.App.Storage/Storables/WindowsStorage/JumpListItemType.cs
new file mode 100644
index 000000000000..4fa801b68154
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/JumpListItemType.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Storage
+{
+ public enum JumpListItemType
+ {
+ Item,
+
+ Separator,
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs b/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs
new file mode 100644
index 000000000000..3cb1dcf27e73
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.Collections.Concurrent;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.Com;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.Shell.Common;
+
+namespace Files.App.Storage
+{
+ public unsafe class JumpListManager : IDisposable
+ {
+ private ComPtr pCustomDestinationList = default;
+
+ private static string? AppId
+ {
+ get
+ {
+ PWSTR pszAppId = default;
+ HRESULT hr = PInvoke.GetCurrentProcessExplicitAppUserModelID(&pszAppId);
+ if (hr == HRESULT.E_FAIL)
+ hr = HRESULT.S_OK;
+
+ hr.ThrowIfFailedOnDebug();
+
+ return pszAppId.ToString();
+ }
+ }
+
+ 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()
+ {
+ 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();
+ }
+
+ public HRESULT Save()
+ {
+ 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;
+ }
+
+ public void Dispose()
+ {
+ pCustomDestinationList.Dispose();
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs b/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs
new file mode 100644
index 000000000000..29a4a6819e61
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/STATask.cs
@@ -0,0 +1,147 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+
+namespace Files.App.Storage
+{
+ ///
+ /// Represents an asynchronous operation on STA.
+ ///
+ public partial class STATask
+ {
+ public static Task Run(Action action)
+ {
+ var tcs = new TaskCompletionSource();
+
+ Thread thread =
+ new(() =>
+ {
+ PInvoke.OleInitialize();
+
+ try
+ {
+ action();
+ tcs.SetResult();
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+ finally
+ {
+ PInvoke.OleUninitialize();
+ }
+ })
+ {
+ IsBackground = true,
+ Priority = ThreadPriority.Normal
+ };
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+
+ return tcs.Task;
+ }
+
+ public static Task Run(Func func)
+ {
+ var tcs = new TaskCompletionSource();
+
+ Thread thread =
+ new(() =>
+ {
+ PInvoke.OleInitialize();
+
+ try
+ {
+ tcs.SetResult(func());
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+ finally
+ {
+ PInvoke.OleUninitialize();
+ }
+ })
+ {
+ IsBackground = true,
+ Priority = ThreadPriority.Normal
+ };
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+
+ return tcs.Task;
+ }
+
+ public static Task Run(Func func)
+ {
+ var tcs = new TaskCompletionSource();
+
+ Thread thread =
+ new(async () =>
+ {
+ PInvoke.OleInitialize();
+
+ try
+ {
+ await func();
+ tcs.SetResult();
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+ finally
+ {
+ PInvoke.OleUninitialize();
+ }
+ })
+ {
+ IsBackground = true,
+ Priority = ThreadPriority.Normal
+ };
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+
+ return tcs.Task;
+ }
+
+ public static Task Run(Func> func)
+ {
+ var tcs = new TaskCompletionSource();
+
+ Thread thread =
+ new(async () =>
+ {
+ PInvoke.OleInitialize();
+
+ try
+ {
+ tcs.SetResult(await func());
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+ finally
+ {
+ PInvoke.OleUninitialize();
+ }
+ })
+ {
+ IsBackground = true,
+ Priority = ThreadPriority.Normal
+ };
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.Start();
+
+ return tcs.Task;
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/TaskbarManager.cs b/src/Files.App.Storage/Storables/WindowsStorage/TaskbarManager.cs
new file mode 100644
index 000000000000..1d8772239428
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/TaskbarManager.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.Com;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ public unsafe class TaskbarManager : IDisposable
+ {
+ private ComPtr pTaskbarList = default;
+
+ private static TaskbarManager? _Default = null;
+ public static TaskbarManager Default { get; } = _Default ??= new TaskbarManager();
+
+ public TaskbarManager()
+ {
+ Guid CLSID_TaskbarList = typeof(TaskbarList).GUID;
+ Guid IID_ITaskbarList3 = ITaskbarList3.IID_Guid;
+ HRESULT hr = PInvoke.CoCreateInstance(
+ &CLSID_TaskbarList,
+ null,
+ CLSCTX.CLSCTX_INPROC_SERVER,
+ &IID_ITaskbarList3,
+ (void**)pTaskbarList.GetAddressOf());
+
+ if (hr.ThrowIfFailedOnDebug().Succeeded)
+ hr = pTaskbarList.Get()->HrInit().ThrowIfFailedOnDebug();
+ }
+
+ public HRESULT SetProgressValue(HWND hwnd, ulong ullCompleted, ulong ullTotal)
+ {
+ return pTaskbarList.Get()->SetProgressValue(hwnd, ullCompleted, ullTotal);
+ }
+
+ public HRESULT SetProgressState(HWND hwnd, TBPFLAG tbpFlags)
+ {
+ return pTaskbarList.Get()->SetProgressState(hwnd, tbpFlags);
+ }
+
+ public void Dispose()
+ {
+ pTaskbarList.Dispose();
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs
new file mode 100644
index 000000000000..a6393243246f
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperations.cs
@@ -0,0 +1,177 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Storage.FileSystem;
+using Windows.Win32.System.Com;
+using Windows.Win32.UI.Shell;
+
+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
+ {
+ // Fields
+
+ private readonly ComPtr _pFileOperation;
+ private readonly ComPtr _pProgressSink;
+ private readonly uint _progressSinkCookie;
+
+ // Events
+
+ /// An event that is triggered when bulk operations are completed.
+ public event EventHandler? OperationsFinished;
+
+ /// An event that is triggered when an item is copied during bulk operations.
+ public event EventHandler? ItemCopied;
+
+ /// An event that is triggered when an item is deleted during bulk operations.
+ public event EventHandler? ItemDeleted;
+
+ /// An event that is triggered when an item is moved during bulk operations.
+ public event EventHandler? ItemMoved;
+
+ /// An event that is triggered when a new item is created.
+ public event EventHandler? ItemCreated;
+
+ /// An event that is triggered when an item is renamed.
+ public event EventHandler? ItemRenamed;
+
+ /// An event that occurs when an item is being copied during bulk operations.
+ public event EventHandler? ItemCopying;
+
+ /// An event that is triggered when an item is being deleted.
+ public event EventHandler? ItemDeleting;
+
+ /// An event that is triggered when an item is being moved in bulk operations.
+ public event EventHandler? ItemMoving;
+
+ /// An event that is triggered when an item is being created in bulk operations.
+ public event EventHandler? ItemCreating;
+
+ /// An event that is triggered when an item is being renamed.
+ public event EventHandler? ItemRenaming;
+
+ /// An event that is triggered when operations start.
+ public event EventHandler? OperationsStarted;
+
+ /// An event that is triggered to indicate progress updates.
+ public event ProgressChangedEventHandler? ProgressUpdated;
+
+ // Constructor
+
+ ///
+ /// Initializes a instance for file operations on Windows.
+ ///
+ /// Specifies the window handle that will own the file operation dialogs.
+ /// 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;
+
+ HRESULT hr = PInvoke.CoCreateInstance(
+ &clsid,
+ null,
+ CLSCTX.CLSCTX_LOCAL_SERVER,
+ &iid,
+ (void**)_pFileOperation.GetAddressOf())
+ .ThrowIfFailedOnDebug();
+
+ if (ownerHWnd != default)
+ hr = _pFileOperation.Get()->SetOwnerWindow(ownerHWnd).ThrowIfFailedOnDebug();
+
+ hr = _pFileOperation.Get()->SetOperationFlags(flags).ThrowIfFailedOnDebug();
+
+ _pProgressSink.Attach((IFileOperationProgressSink*)WindowsBulkOperationsSink.Create(this));
+ hr = _pFileOperation.Get()->Advise(_pProgressSink.Get(), out var progressSinkCookie).ThrowIfFailedOnDebug();
+ _progressSinkCookie = progressSinkCookie;
+ }
+
+ ///
+ /// Queues a copy operation.
+ ///
+ ///
+ ///
+ ///
+ /// If this method succeeds, it returns . Otherwise, it returns an error code.
+ 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());
+ }
+
+ ///
+ /// Queues a delete operation.
+ ///
+ ///
+ /// 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());
+ }
+
+ ///
+ /// Queues a move operation.
+ ///
+ ///
+ ///
+ ///
+ /// If this method succeeds, it returns . Otherwise, it returns an error code.
+ 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);
+ }
+
+ ///
+ /// Queues a create operation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// If this method succeeds, it returns . Otherwise, it returns an error code.
+ 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());
+ }
+
+ ///
+ /// Queues a rename operation.
+ ///
+ ///
+ ///
+ /// If this method succeeds, it returns . Otherwise, it returns an error code.
+ public unsafe HRESULT QueueRenameOperation(WindowsStorable targetItem, string newName)
+ {
+ fixed (char* pszNewName = newName)
+ return _pFileOperation.Get()->RenameItem(targetItem.ThisPtr.Get(), pszNewName, _pProgressSink.Get());
+ }
+
+ ///
+ /// Performs the all queued operations.
+ ///
+ /// If this method succeeds, it returns . Otherwise, it returns an error code.
+ public unsafe HRESULT PerformAllOperations()
+ {
+ return _pFileOperation.Get()->PerformOperations();
+ }
+
+ // Disposer
+
+ ///
+ public unsafe void Dispose()
+ {
+ if (!_pProgressSink.IsNull)
+ _pFileOperation.Get()->Unadvise(_progressSinkCookie);
+
+ _pFileOperation.Dispose();
+ _pProgressSink.Dispose();
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsEventArgs.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsEventArgs.cs
new file mode 100644
index 000000000000..771dd12dc5af
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsEventArgs.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ [DebuggerDisplay("{" + nameof(ToString) + "()}")]
+ public class WindowsBulkOperationsEventArgs(_TRANSFER_SOURCE_FLAGS flags = _TRANSFER_SOURCE_FLAGS.TSF_NORMAL, WindowsStorable? sourceItem = null, WindowsFolder? destinationFolder = null, WindowsStorable? newlyCreated = null, string? name = null, string? templateName = null, HRESULT result = default)
+ : EventArgs
+ {
+ public _TRANSFER_SOURCE_FLAGS Flags { get; set; } = flags;
+
+ public WindowsStorable? SourceItem { get; set; } = sourceItem;
+
+ public WindowsFolder? DestinationFolder { get; set; } = destinationFolder;
+
+ public WindowsStorable? NewlyCreated { get; set; } = newlyCreated;
+
+ public string? Name { get; set; } = name;
+
+ public string? TemplateName { get; set; } = templateName;
+
+ public HRESULT Result { get; protected set; } = result;
+
+ public override string ToString()
+ => $"Hr:\"{Result}\"; Src:\"{SourceItem}\"; Dst:\"{DestinationFolder}\"; New:\"{NewlyCreated}\"; Name:\"{Name}\"";
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsSink.Methods.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsSink.Methods.cs
new file mode 100644
index 000000000000..f4b9f764b87c
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsSink.Methods.cs
@@ -0,0 +1,173 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ public sealed partial class WindowsBulkOperations : IDisposable
+ {
+ private unsafe partial struct WindowsBulkOperationsSink : IFileOperationProgressSink.Interface
+ {
+ public HRESULT StartOperations()
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.OperationsStarted?.Invoke(operations, EventArgs.Empty);
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public HRESULT FinishOperations(HRESULT hrResult)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.OperationsFinished?.Invoke(operations, new(result: hrResult));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PreRenameItem(uint dwFlags, IShellItem* pSource, PCWSTR pszNewName)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemRenaming?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), null, null, pszNewName.ToString(), null, default));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PostRenameItem(uint dwFlags, IShellItem* pSource, PCWSTR pszNewName, HRESULT hrRename, IShellItem* psiNewlyCreated)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemRenamed?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), null, WindowsStorable.TryParse(psiNewlyCreated), pszNewName.ToString(), null, hrRename));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PreMoveItem(uint dwFlags, IShellItem* pSource, IShellItem* psiDestinationFolder, PCWSTR pszNewName)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemMoving?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), new WindowsFolder(psiDestinationFolder), null, pszNewName.ToString(), null, default));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PostMoveItem(uint dwFlags, IShellItem* pSource, IShellItem* psiDestinationFolder, PCWSTR pszNewName, HRESULT hrMove, IShellItem* psiNewlyCreated)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemMoved?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), new WindowsFolder(psiDestinationFolder), WindowsStorable.TryParse(psiNewlyCreated), pszNewName.ToString(), null, hrMove));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PreCopyItem(uint dwFlags, IShellItem* pSource, IShellItem* psiDestinationFolder, PCWSTR pszNewName)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemCopying?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), new WindowsFolder(psiDestinationFolder), null, pszNewName.ToString(), null, default));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PostCopyItem(uint dwFlags, IShellItem* pSource, IShellItem* psiDestinationFolder, PCWSTR pszNewName, HRESULT hrCopy, IShellItem* psiNewlyCreated)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemCopied?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), new WindowsFolder(psiDestinationFolder), WindowsStorable.TryParse(psiNewlyCreated), pszNewName.ToString(), null, hrCopy));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PreDeleteItem(uint dwFlags, IShellItem* pSource)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemDeleting?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), null, null, null, null, default));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PostDeleteItem(uint dwFlags, IShellItem* pSource, HRESULT hrDelete, IShellItem* psiNewlyCreated)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemDeleted?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, WindowsStorable.TryParse(pSource), null, WindowsStorable.TryParse(psiNewlyCreated), null, null, hrDelete));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PreNewItem(uint dwFlags, IShellItem* psiDestinationFolder, PCWSTR pszNewName)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemCreating?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, null, new WindowsFolder(psiDestinationFolder), null, pszNewName.ToString(), null, default));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public unsafe HRESULT PostNewItem(uint dwFlags, IShellItem* psiDestinationFolder, PCWSTR pszNewName, PCWSTR pszTemplateName, uint dwFileAttributes, HRESULT hrNew, IShellItem* psiNewItem)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ operations.ItemCreated?.Invoke(operations, new((_TRANSFER_SOURCE_FLAGS)dwFlags, null, new WindowsFolder(psiDestinationFolder), WindowsStorable.TryParse(psiNewItem), pszNewName.ToString(), pszTemplateName.ToString(), hrNew));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public HRESULT UpdateProgress(uint iWorkTotal, uint iWorkSoFar)
+ {
+ if (_operationsHandle.Target is WindowsBulkOperations operations)
+ {
+ var percentage = iWorkTotal is 0 ? 0 : iWorkSoFar * 100.0 / iWorkTotal;
+ operations.ProgressUpdated?.Invoke(operations, new((int)percentage, null));
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_FAIL;
+ }
+
+ public HRESULT ResetTimer()
+ {
+ return HRESULT.E_NOTIMPL;
+ }
+
+ public HRESULT PauseTimer()
+ {
+ return HRESULT.E_NOTIMPL;
+ }
+
+ public HRESULT ResumeTimer()
+ {
+ return HRESULT.E_NOTIMPL;
+ }
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsSink.VTable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsSink.VTable.cs
new file mode 100644
index 000000000000..95c309023f76
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsBulkOperationsSink.VTable.cs
@@ -0,0 +1,161 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Shell;
+using WinRT.Interop;
+
+namespace Files.App.Storage
+{
+ public sealed partial class WindowsBulkOperations : IDisposable
+ {
+ private unsafe partial struct WindowsBulkOperationsSink
+ {
+ private static readonly void** _lpPopulatedVtbl = PopulateVTable();
+
+ private void** _lpVtbl;
+ private volatile int _refCount;
+ private GCHandle _operationsHandle;
+
+ public static WindowsBulkOperationsSink* Create(WindowsBulkOperations operations)
+ {
+ WindowsBulkOperationsSink* operationsSink = (WindowsBulkOperationsSink*)NativeMemory.Alloc((nuint)sizeof(WindowsBulkOperationsSink));
+ operationsSink->_lpVtbl = _lpPopulatedVtbl;
+ operationsSink->_refCount = 1;
+ operationsSink->_operationsHandle = GCHandle.Alloc(operations);
+
+ return operationsSink;
+ }
+
+ private static void** PopulateVTable()
+ {
+ void** vtbl = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(WindowsBulkOperationsSink), sizeof(void*) * 19);
+ vtbl[0] = (delegate* unmanaged)&Vtbl.QueryInterface;
+ vtbl[1] = (delegate* unmanaged)&Vtbl.AddRef;
+ vtbl[2] = (delegate* unmanaged)&Vtbl.Release;
+ vtbl[3] = (delegate* unmanaged)&Vtbl.StartOperations;
+ vtbl[4] = (delegate* unmanaged)&Vtbl.FinishOperations;
+ vtbl[5] = (delegate* unmanaged)&Vtbl.PreRenameItem;
+ vtbl[6] = (delegate* unmanaged)&Vtbl.PostRenameItem;
+ vtbl[7] = (delegate* unmanaged)&Vtbl.PreMoveItem;
+ vtbl[8] = (delegate* unmanaged)&Vtbl.PostMoveItem;
+ vtbl[9] = (delegate* unmanaged)&Vtbl.PreCopyItem;
+ vtbl[10] = (delegate* unmanaged)&Vtbl.PostCopyItem;
+ vtbl[11] = (delegate* unmanaged)&Vtbl.PreDeleteItem;
+ vtbl[12] = (delegate* unmanaged)&Vtbl.PostDeleteItem;
+ vtbl[13] = (delegate* unmanaged)&Vtbl.PreNewItem;
+ vtbl[14] = (delegate* unmanaged)&Vtbl.PostNewItem;
+ vtbl[15] = (delegate* unmanaged)&Vtbl.UpdateProgress;
+ vtbl[16] = (delegate* unmanaged)&Vtbl.ResetTimer;
+ vtbl[17] = (delegate* unmanaged)&Vtbl.PauseTimer;
+ vtbl[18] = (delegate* unmanaged)&Vtbl.ResumeTimer;
+
+ return vtbl;
+ }
+
+ private static class Vtbl
+ {
+ [UnmanagedCallersOnly]
+ public static HRESULT QueryInterface(WindowsBulkOperationsSink* @this, Guid* riid, void** ppv)
+ {
+ if (ppv is null)
+ return HRESULT.E_POINTER;
+
+ if (riid->Equals(IID.IID_IUnknown) || riid->Equals(IFileOperationProgressSink.IID_Guid))
+ {
+ Interlocked.Increment(ref @this->_refCount);
+ *ppv = @this;
+ return HRESULT.S_OK;
+ }
+
+ return HRESULT.E_NOINTERFACE;
+ }
+
+ [UnmanagedCallersOnly]
+ public static int AddRef(WindowsBulkOperationsSink* @this)
+ => Interlocked.Increment(ref @this->_refCount);
+
+ [UnmanagedCallersOnly]
+ public static int Release(WindowsBulkOperationsSink* @this)
+ {
+ int newRefCount = Interlocked.Decrement(ref @this->_refCount);
+ if (newRefCount is 0)
+ {
+ if (@this->_operationsHandle.IsAllocated)
+ @this->_operationsHandle.Free();
+
+ NativeMemory.Free(@this);
+ }
+
+ return newRefCount;
+ }
+
+ [UnmanagedCallersOnly]
+ public static HRESULT StartOperations(WindowsBulkOperationsSink* @this)
+ => @this->StartOperations();
+
+ [UnmanagedCallersOnly]
+ public static HRESULT FinishOperations(WindowsBulkOperationsSink* @this, HRESULT hrResult)
+ => @this->FinishOperations(hrResult);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PreRenameItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, PCWSTR pszNewName)
+ => @this->PreRenameItem(dwFlags, psiItem, pszNewName);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PostRenameItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, PCWSTR pszNewName, HRESULT hrRename, IShellItem* psiNewlyCreated)
+ => @this->PostRenameItem(dwFlags, psiItem, pszNewName, hrRename, psiNewlyCreated);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PreMoveItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, IShellItem* psiDestinationFolder, PCWSTR pszNewName)
+ => @this->PreMoveItem(dwFlags, psiItem, psiDestinationFolder, pszNewName);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PostMoveItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, IShellItem* psiDestinationFolder, PCWSTR pszNewName, HRESULT hrMove, IShellItem* psiNewlyCreated)
+ => @this->PostMoveItem(dwFlags, psiItem, psiDestinationFolder, pszNewName, hrMove, psiNewlyCreated);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PreCopyItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, IShellItem* psiDestinationFolder, PCWSTR pszNewName)
+ => @this->PreCopyItem(dwFlags, psiItem, psiDestinationFolder, pszNewName);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PostCopyItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, IShellItem* psiDestinationFolder, PCWSTR pszNewName, HRESULT hrCopy, IShellItem* psiNewlyCreated)
+ => @this->PostCopyItem(dwFlags, psiItem, psiDestinationFolder, pszNewName, hrCopy, psiNewlyCreated);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PreDeleteItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem)
+ => @this->PreDeleteItem(dwFlags, psiItem);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PostDeleteItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiItem, HRESULT hrDelete, IShellItem* psiNewlyCreated)
+ => @this->PostDeleteItem(dwFlags, psiItem, hrDelete, psiNewlyCreated);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PreNewItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiDestinationFolder, PCWSTR pszNewName)
+ => @this->PreNewItem(dwFlags, psiDestinationFolder, pszNewName);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PostNewItem(WindowsBulkOperationsSink* @this, uint dwFlags, IShellItem* psiDestinationFolder, PCWSTR pszNewName, PCWSTR pszTemplateName, uint dwFileAttributes, HRESULT hrNew, IShellItem* psiNewItem)
+ => @this->PostNewItem(dwFlags, psiDestinationFolder, pszNewName, pszTemplateName, dwFileAttributes, hrNew, psiNewItem);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT UpdateProgress(WindowsBulkOperationsSink* @this, uint iWorkTotal, uint iWorkSoFar)
+ => @this->UpdateProgress(iWorkTotal, iWorkSoFar);
+
+ [UnmanagedCallersOnly]
+ public static HRESULT ResetTimer(WindowsBulkOperationsSink* @this)
+ => @this->ResetTimer();
+
+ [UnmanagedCallersOnly]
+ public static HRESULT PauseTimer(WindowsBulkOperationsSink* @this)
+ => @this->PauseTimer();
+
+ [UnmanagedCallersOnly]
+ public static HRESULT ResumeTimer(WindowsBulkOperationsSink* @this)
+ => @this->ResumeTimer();
+ }
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs
new file mode 100644
index 000000000000..ee89b06651d3
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.IO;
+using Windows.Win32;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ [DebuggerDisplay("{" + nameof(ToString) + "()}")]
+ public sealed class WindowsFile : WindowsStorable, IChildFile, IDisposable
+ {
+ public WindowsFile(ComPtr nativeObject)
+ {
+ ThisPtr = nativeObject;
+ }
+
+ public Task OpenStreamAsync(FileAccess accessMode, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ // Disposer
+
+ ///
+ public void Dispose()
+ {
+ ThisPtr.Dispose();
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs
new file mode 100644
index 000000000000..ff466953daa7
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs
@@ -0,0 +1,106 @@
+// 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;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ [DebuggerDisplay("{" + nameof(ToString) + "()")]
+ public sealed class WindowsFolder : WindowsStorable, IChildFolder, IDisposable
+ {
+ public WindowsFolder(ComPtr nativeObject)
+ {
+ ThisPtr = nativeObject;
+ }
+
+ public unsafe WindowsFolder(IShellItem* nativeObject)
+ {
+ ComPtr ptr = default;
+ ptr.Attach(nativeObject);
+ ThisPtr = ptr;
+ }
+
+ public unsafe WindowsFolder(Guid folderId)
+ {
+ Guid folderIdLocal = folderId;
+ Guid IID_IShellItem = IShellItem.IID_Guid;
+ ComPtr pItem = default;
+
+ HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderIdLocal, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, &IID_IShellItem, (void**)pItem.GetAddressOf());
+ if (hr.Succeeded)
+ {
+ ThisPtr = pItem;
+ return;
+ }
+
+ fixed (char* pszShellPath = $"Shell:::{folderId:B}")
+ hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, &IID_IShellItem, (void**)pItem.GetAddressOf());
+ if (hr.Succeeded)
+ {
+ ThisPtr = pItem;
+ return;
+ }
+ }
+
+ public async IAsyncEnumerable GetItemsAsync(StorableType type = StorableType.All, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ using ComPtr pEnumShellItems = GetEnumShellItems();
+ while (GetNext(pEnumShellItems) is { } pShellItem && !pShellItem.IsNull)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var pShellFolder = pShellItem.As();
+ var isFolder = IsFolder(pShellItem);
+
+ if (type is StorableType.File && !isFolder)
+ {
+ yield return new WindowsFile(pShellItem);
+ }
+ else if (type is StorableType.Folder && isFolder)
+ {
+ yield return new WindowsFile(pShellItem);
+ }
+ else
+ {
+ continue;
+ }
+
+ await Task.Yield();
+ }
+
+ unsafe ComPtr GetEnumShellItems()
+ {
+ ComPtr pEnumShellItems = default;
+ Guid IID_IEnumShellItems = typeof(IEnumShellItems).GUID;
+ Guid BHID_EnumItems = PInvoke.BHID_EnumItems;
+ HRESULT hr = ThisPtr.Get()->BindToHandler(null, &BHID_EnumItems, &IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf());
+ return pEnumShellItems;
+ }
+
+ unsafe ComPtr GetNext(ComPtr pEnumShellItems)
+ {
+ ComPtr pShellItem = default;
+ HRESULT hr = pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf());
+ return pShellItem;
+ }
+
+ unsafe bool IsFolder(ComPtr pShellItem)
+ {
+ return pShellItem.Get()->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var specifiedAttribute).Succeeded &&
+ specifiedAttribute is SFGAO_FLAGS.SFGAO_FOLDER;
+ }
+ }
+
+ // Disposer
+
+ ///
+ public void Dispose()
+ {
+ ThisPtr.Dispose();
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs
new file mode 100644
index 000000000000..92c45b64f90c
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorable.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.SystemServices;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ public abstract class WindowsStorable : IWindowsStorable, IStorableChild
+ {
+ public ComPtr ThisPtr { get; protected set; } = default;
+
+ public string Id => this.GetDisplayName(SIGDN.SIGDN_FILESYSPATH);
+
+ public string Name => this.GetDisplayName(SIGDN.SIGDN_PARENTRELATIVEFORUI);
+
+ public static unsafe WindowsStorable? TryParse(string parsablePath)
+ {
+ 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)
+ return null;
+
+ return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER)
+ ? new WindowsFolder(pShellItem)
+ : new WindowsFile(pShellItem);
+ }
+
+ public static unsafe WindowsStorable? TryParse(IShellItem* ptr)
+ {
+ ComPtr pShellItem = default;
+ pShellItem.Attach(ptr);
+
+ return pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER)
+ ? 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();
+
+ return Task.FromResult(null);
+ }
+
+ return Task.FromResult(new WindowsFolder(pParentFolder));
+ }
+
+ ///
+ public override string ToString()
+ {
+ return this.GetDisplayName();
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs
new file mode 100644
index 000000000000..59700673b48c
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Icon.cs
@@ -0,0 +1,259 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.Graphics.GdiPlus;
+using Windows.Win32.System.Com;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.WindowsAndMessaging;
+
+namespace Files.App.Storage
+{
+ public static partial class WindowsStorableHelpers
+ {
+ // Fields
+
+ private static (Guid Format, Guid Encorder)[]? GdiEncoders;
+ private static ConcurrentDictionary<(string, int, int), byte[]>? DllIconCache;
+
+ // Methods
+
+ ///
+ public static async Task GetThumbnailAsync(this IWindowsStorable storable, int size, SIIGBF options)
+ {
+ return await STATask.Run(() =>
+ {
+ HRESULT hr = storable.TryGetThumbnail(size, options, out var thumbnailData).ThrowIfFailedOnDebug();
+ return thumbnailData;
+ });
+ }
+
+ ///
+ /// Retrieves a thumbnail image data for the specified using .
+ ///
+ /// An object that implements and represents a shell item on Windows.
+ /// The desired size (in pixels) of the thumbnail (width and height are equal).
+ /// A combination of flags that specify how the thumbnail should be retrieved.
+ /// A byte array containing the thumbnail image in its native format (e.g., PNG, JPEG).
+ /// If the thumbnail is JPEG, this tries to decoded as a PNG instead because JPEG loses data.
+ public unsafe static HRESULT TryGetThumbnail(this IWindowsStorable storable, int size, SIIGBF options, out byte[]? thumbnailData)
+ {
+ thumbnailData = null;
+
+ using ComPtr pShellItemImageFactory = storable.ThisPtr.As();
+ if (pShellItemImageFactory.IsNull)
+ return HRESULT.E_NOINTERFACE;
+
+ // Get HBITMAP
+ HBITMAP hBitmap = default;
+ HRESULT hr = pShellItemImageFactory.Get()->GetImage(new(size, size), options, &hBitmap);
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ {
+ if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
+ return hr;
+ }
+
+ // Convert to GpBitmap of GDI+
+ GpBitmap* gpBitmap = default;
+ if (PInvoke.GdipCreateBitmapFromHBITMAP(hBitmap, HPALETTE.Null, &gpBitmap) is not Status.Ok)
+ {
+ if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
+ if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
+ return HRESULT.E_FAIL;
+ }
+
+ if (TryConvertGpBitmapToByteArray(gpBitmap, out thumbnailData))
+ {
+ if (!hBitmap.IsNull) PInvoke.DeleteObject(hBitmap);
+ return HRESULT.E_FAIL;
+ }
+
+ return HRESULT.S_OK;
+ }
+
+ public unsafe static HRESULT TryExtractImageFromDll(this IWindowsStorable storable, int size, int index, out byte[]? imageData)
+ {
+ DllIconCache ??= [];
+ imageData = null;
+
+ if (storable.ToString() is not { } path)
+ return HRESULT.E_INVALIDARG;
+
+ if (DllIconCache.TryGetValue((path, index, size), out var cachedImageData))
+ {
+ imageData = cachedImageData;
+ return HRESULT.S_OK;
+ }
+ else
+ {
+ HICON hIcon = default;
+ HRESULT hr = default;
+
+ fixed (char* pszPath = path)
+ hr = PInvoke.SHDefExtractIcon(pszPath, -1 * index, 0, &hIcon, null, (uint)size);
+
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ {
+ if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
+ return hr;
+ }
+
+ // Convert to GpBitmap of GDI+
+ GpBitmap* gpBitmap = default;
+ if (PInvoke.GdipCreateBitmapFromHICON(hIcon, &gpBitmap) is not Status.Ok)
+ {
+ if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
+ return HRESULT.E_FAIL;
+ }
+
+ if (!TryConvertGpBitmapToByteArray(gpBitmap, out imageData))
+ {
+ if (!hIcon.IsNull) PInvoke.DestroyIcon(hIcon);
+ return HRESULT.E_FAIL;
+ }
+
+ DllIconCache[(path, index, size)] = imageData;
+ PInvoke.DestroyIcon(hIcon);
+
+ return HRESULT.S_OK;
+ }
+ }
+
+ public unsafe static bool TryConvertGpBitmapToByteArray(GpBitmap* gpBitmap, out byte[]? imageData)
+ {
+ imageData = null;
+
+ // Get an encoder for PNG
+ Guid format = Guid.Empty;
+ if (PInvoke.GdipGetImageRawFormat((GpImage*)gpBitmap, &format) is not Status.Ok)
+ {
+ if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
+ return false;
+ }
+
+ Guid encoder = GetEncoderClsid(format);
+ if (format == PInvoke.ImageFormatJPEG || encoder == Guid.Empty)
+ {
+ format = PInvoke.ImageFormatPNG;
+ encoder = GetEncoderClsid(format);
+ }
+
+ using ComPtr pStream = default;
+ HRESULT hr = PInvoke.CreateStreamOnHGlobal(HGLOBAL.Null, true, pStream.GetAddressOf());
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ {
+ if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
+ return false;
+ }
+
+ if (PInvoke.GdipSaveImageToStream((GpImage*)gpBitmap, pStream.Get(), &encoder, (EncoderParameters*)null) is not Status.Ok)
+ {
+ if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
+ return false;
+ }
+
+ STATSTG stat = default;
+ hr = pStream.Get()->Stat(&stat, (uint)STATFLAG.STATFLAG_NONAME);
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ {
+ if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
+ return false;
+ }
+
+ ulong statSize = stat.cbSize & 0xFFFFFFFF;
+ byte* RawThumbnailData = (byte*)NativeMemory.Alloc((nuint)statSize);
+
+ pStream.Get()->Seek(0L, (SystemIO.SeekOrigin)STREAM_SEEK.STREAM_SEEK_SET, null);
+ hr = pStream.Get()->Read(RawThumbnailData, (uint)statSize);
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ {
+ if (gpBitmap is not null) PInvoke.GdipDisposeImage((GpImage*)gpBitmap);
+ if (RawThumbnailData is not null) NativeMemory.Free(RawThumbnailData);
+ return false;
+ }
+
+ imageData = new ReadOnlySpan(RawThumbnailData, (int)statSize / sizeof(byte)).ToArray();
+ NativeMemory.Free(RawThumbnailData);
+
+ return true;
+
+ Guid GetEncoderClsid(Guid format)
+ {
+ foreach ((Guid Format, Guid Encoder) in GetGdiEncoders())
+ if (Format == format)
+ return Encoder;
+
+ return Guid.Empty;
+ }
+
+ (Guid Format, Guid Encorder)[] GetGdiEncoders()
+ {
+ if (GdiEncoders is not null)
+ return GdiEncoders;
+
+ if (PInvoke.GdipGetImageEncodersSize(out var numEncoders, out var size) is not Status.Ok)
+ return [];
+
+ ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)NativeMemory.Alloc(size);
+
+ if (PInvoke.GdipGetImageEncoders(numEncoders, size, pImageCodecInfo) is not Status.Ok)
+ return [];
+
+ ReadOnlySpan codecs = new(pImageCodecInfo, (int)numEncoders);
+ GdiEncoders = new (Guid Format, Guid Encoder)[codecs.Length];
+ for (int index = 0; index < codecs.Length; index++)
+ GdiEncoders[index] = (codecs[index].FormatID, codecs[index].Clsid);
+
+ return GdiEncoders;
+ }
+ }
+
+ public unsafe static HRESULT TrySetFolderIcon(this IWindowsStorable storable, IWindowsStorable iconFile, int index)
+ {
+ if (storable.GetDisplayName() is not { } folderPath ||
+ iconFile.GetDisplayName() is not { } filePath)
+ return HRESULT.E_INVALIDARG;
+
+ fixed (char* pszFolderPath = folderPath, pszIconFile = filePath)
+ {
+ SHFOLDERCUSTOMSETTINGS settings = default;
+ settings.dwSize = (uint)sizeof(SHFOLDERCUSTOMSETTINGS);
+ settings.dwMask = PInvoke.FCSM_ICONFILE;
+ settings.pszIconFile = pszIconFile;
+ settings.cchIconFile = 0;
+ settings.iIconIndex = index;
+
+ HRESULT hr = PInvoke.SHGetSetFolderCustomSettings(&settings, pszFolderPath, PInvoke.FCS_FORCEWRITE);
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ return hr;
+ }
+
+ return HRESULT.S_OK;
+ }
+
+ public unsafe static HRESULT TrySetShortcutIcon(this IWindowsStorable storable, IWindowsStorable iconFile, int index)
+ {
+ if (iconFile.ToString() is not { } iconFilePath)
+ 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());
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ return hr;
+
+ fixed (char* pszIconFilePath = iconFilePath)
+ hr = pShellLink.Get()->SetIconLocation(iconFilePath, index);
+ if (hr.ThrowIfFailedOnDebug().Failed)
+ return hr;
+
+ return HRESULT.S_OK;
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.PowerShell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.PowerShell.cs
new file mode 100644
index 000000000000..fcc3da01b630
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.PowerShell.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Storage
+{
+ public static partial class WindowsStorableHelpers
+ {
+ public static async Task TrySetShortcutIconOnPowerShellAsElevatedAsync(this IWindowsStorable storable, IWindowsStorable iconFile, int index)
+ {
+ string psScript =
+ $@"$FilePath = '{storable}'
+ $IconFile = '{iconFile}'
+ $IconIndex = '{index}'
+
+ $Shell = New-Object -ComObject WScript.Shell
+ $Shortcut = $Shell.CreateShortcut($FilePath)
+ $Shortcut.IconLocation = ""$IconFile, $IconIndex""
+ $Shortcut.Save()";
+
+ var process = new Process()
+ {
+ StartInfo = new ProcessStartInfo()
+ {
+ FileName = "PowerShell.exe",
+ Arguments = $"-NoProfile -EncodedCommand {Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(psScript))}",
+ Verb = "RunAs",
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ UseShellExecute = true
+ },
+ };
+
+ process.Start();
+ await process.WaitForExitAsync();
+
+ return true;
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs
new file mode 100644
index 000000000000..375c9deb3a07
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.System.SystemServices;
+using Windows.Win32.UI.Shell;
+
+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;
+ var shellItem2Iid = typeof(IShellItem2).GUID;
+ HRESULT hr = storable.ThisPtr.Get()->QueryInterface(&shellItem2Iid, (void**)pShellItem2.GetAddressOf());
+ hr = PInvoke.PSGetPropertyKeyFromName(propKey, out var originalPathPropertyKey);
+ hr = pShellItem2.Get()->GetString(originalPathPropertyKey, out var szOriginalPath);
+
+ if (typeof(TValue) == typeof(string))
+ {
+ value = (TValue)(object)szOriginalPath.ToString();
+ 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
+ ? (*pszName.Get()).ToString()
+ : string.Empty;
+ }
+ }
+}
diff --git a/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs
new file mode 100644
index 000000000000..151cad3fbf3c
--- /dev/null
+++ b/src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Storage.cs
@@ -0,0 +1,88 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Storage.FileSystem;
+using Windows.Win32.UI.Shell;
+
+namespace Files.App.Storage
+{
+ public unsafe static partial class WindowsStorableHelpers
+ {
+ public static bool TryGetFileAttributes(this IWindowsStorable storable, out FILE_FLAGS_AND_ATTRIBUTES attributes)
+ {
+ attributes = (FILE_FLAGS_AND_ATTRIBUTES)PInvoke.GetFileAttributes(storable.GetDisplayName());
+
+ if ((uint)attributes is PInvoke.INVALID_FILE_ATTRIBUTES)
+ {
+ attributes = 0;
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ public static bool TrySetFileAttributes(this IWindowsStorable storable, FILE_FLAGS_AND_ATTRIBUTES attributes)
+ {
+ if (attributes is FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_COMPRESSED)
+ return storable.TryToggleFileCompressedAttribute(true);
+
+ if (!storable.TryGetFileAttributes(out var previousAttributes))
+ return false;
+ return PInvoke.SetFileAttributes(storable.GetDisplayName(), previousAttributes | attributes);
+ }
+
+ public static bool TryUnsetFileAttributes(this IWindowsStorable storable, FILE_FLAGS_AND_ATTRIBUTES attributes)
+ {
+ if (attributes is FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_COMPRESSED)
+ return storable.TryToggleFileCompressedAttribute(false);
+
+ if (!storable.TryGetFileAttributes(out var previousAttributes))
+ return false;
+ return PInvoke.SetFileAttributes(storable.GetDisplayName(), previousAttributes & ~attributes);
+ }
+
+ public static bool TryToggleFileCompressedAttribute(this IWindowsStorable storable, bool value)
+ {
+ // GENERIC_READ | GENERIC_WRITE flags are needed here
+ // FILE_FLAG_BACKUP_SEMANTICS is used to open directories
+ using var hFile = PInvoke.CreateFile(
+ storable.GetDisplayName(),
+ (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE | FILE_ACCESS_RIGHTS.FILE_WRITE_ATTRIBUTES),
+ FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE,
+ lpSecurityAttributes: null,
+ FILE_CREATION_DISPOSITION.OPEN_EXISTING,
+ FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL | FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_BACKUP_SEMANTICS,
+ hTemplateFile: null);
+
+ if (hFile.IsInvalid)
+ return false;
+
+ var bytesReturned = 0u;
+ var compressionFormat = value
+ ? COMPRESSION_FORMAT.COMPRESSION_FORMAT_DEFAULT
+ : COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE;
+
+ var result = PInvoke.DeviceIoControl(
+ new(hFile.DangerousGetHandle()),
+ PInvoke.FSCTL_SET_COMPRESSION,
+ &compressionFormat,
+ sizeof(ushort),
+ null,
+ 0u,
+ &bytesReturned);
+
+ return result;
+ }
+
+ public static bool TryShowFormatDriveDialog(HWND hWnd, uint driveLetterIndex, SHFMT_ID id, SHFMT_OPT options)
+ {
+ // NOTE: This calls an undocumented elevatable COM class, shell32.dll!CFormatEngine so this doesn't need to be elevated beforehand.
+ var result = PInvoke.SHFormatDrive(hWnd, driveLetterIndex, id, options);
+ return result is 0xFFFF;
+ }
+ }
+}
diff --git a/src/Files.App/GlobalUsings.cs b/src/Files.App/GlobalUsings.cs
index 4a8dae28813e..438eebbcdbea 100644
--- a/src/Files.App/GlobalUsings.cs
+++ b/src/Files.App/GlobalUsings.cs
@@ -75,6 +75,7 @@
// Files.App.Storage
+global using global::Files.App.Storage;
global using global::Files.App.Storage.Storables;
global using global::Files.App.Storage.Watchers;
diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
index c940884ccd8e..7e878896a3f9 100644
--- a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
+++ b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
@@ -319,7 +319,7 @@ public static string ExtractStringFromDLL(string file, int number)
if (iconOptions.HasFlag(IconOptions.ReturnOnlyIfCached))
flags |= Shell32.SIIGBF.SIIGBF_INCACHEONLY;
- var hres = shellFactory.GetImage(new SIZE(size, size), flags, out var hbitmap);
+ var hres = shellFactory.GetImage(new Vanara.PInvoke.SIZE(size, size), flags, out var hbitmap);
if (hres == HRESULT.S_OK)
{
using var image = GetBitmapFromHBitmap(hbitmap);
diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs
index e6f07783d534..9af7345111ec 100644
--- a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs
+++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs
@@ -490,8 +490,5 @@ public static extern int SHGetKnownFolderPath(
IntPtr hToken,
out IntPtr pszPath
);
-
- [DllImport("shell32.dll", EntryPoint = "SHUpdateRecycleBinIcon", CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern void SHUpdateRecycleBinIcon();
}
}
diff --git a/src/Files.App/Services/Storage/StorageTrashBinService.cs b/src/Files.App/Services/Storage/StorageTrashBinService.cs
index ea1a5eb17531..41fb69f46e56 100644
--- a/src/Files.App/Services/Storage/StorageTrashBinService.cs
+++ b/src/Files.App/Services/Storage/StorageTrashBinService.cs
@@ -135,7 +135,7 @@ private unsafe bool RestoreAllTrashesInternal()
hr = pFileOperation.Get()->PerformOperations();
// Reset the icon
- Win32PInvoke.SHUpdateRecycleBinIcon();
+ PInvoke.SHUpdateRecycleBinIcon();
return true;
}
diff --git a/src/Files.App/Services/Windows/WindowsJumpListService.cs b/src/Files.App/Services/Windows/WindowsJumpListService.cs
index 91df044c980e..5678331dbb3d 100644
--- a/src/Files.App/Services/Windows/WindowsJumpListService.cs
+++ b/src/Files.App/Services/Windows/WindowsJumpListService.cs
@@ -171,7 +171,7 @@ private void AddFolder(string path, string group, JumpList instance)
displayName = Path.GetFileName(path);
}
- var jumplistItem = JumpListItem.CreateWithArguments(path, displayName);
+ var jumplistItem = Windows.UI.StartScreen.JumpListItem.CreateWithArguments(path, displayName);
jumplistItem.Description = jumplistItem.Arguments ?? string.Empty;
jumplistItem.GroupName = group;
jumplistItem.Logo = new Uri("ms-appx:///Assets/FolderIcon.png");