Skip to content

Code Quality: Improved WindowsStorables #17191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions src/Files.App.CsWin32/ComPtr`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,6 @@ public void Attach(T* other)
return (T**)Unsafe.AsPointer(ref Unsafe.AsRef(in this));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use `HRESULT As<U>(U** other)` instead.")]
public readonly ComPtr<U> As<U>() where U : unmanaged, IComIID
{
ComPtr<U> ptr = default;
((IUnknown*)_ptr)->QueryInterface((Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in U.Guid)), (void**)ptr.GetAddressOf());
return ptr;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly HRESULT As<U>(U** other) where U : unmanaged, IComIID
{
Expand All @@ -91,22 +82,6 @@ public readonly HRESULT CoCreateInstance(Guid* rclsid, IUnknown* pUnkOuter = nul
return PInvoke.CoCreateInstance(rclsid, pUnkOuter, dwClsContext, (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in T.Guid)), (void**)this.GetAddressOf());
}

// Conversion operators

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ComPtr<T>(T* other)
{
ComPtr<T> ptr = default;
ptr.Attach(other);
return ptr;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T*(ComPtr<T> other)
{
return other._ptr;
}

// Disposer

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
30 changes: 30 additions & 0 deletions src/Files.App.CsWin32/ManualGuid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory

[GuidRVAGen.Guid("00021500-0000-0000-C000-000000000046")]
public static partial Guid* IID_IQueryInfo { get; }

[GuidRVAGen.Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")]
public static partial Guid* IID_IShellItemImageFactory { get; }

[GuidRVAGen.Guid("000214F9-0000-0000-C000-000000000046")]
public static partial Guid* IID_IShellLinkW { get; }

[GuidRVAGen.Guid("B63EA76D-1F85-456F-A19C-48159EFA858B")]
public static partial Guid* IID_IShellItemArray { get; }

[GuidRVAGen.Guid("7F9185B0-CB92-43C5-80A9-92277A4F7B54")]
public static partial Guid* IID_IExecuteCommand { get; }

[GuidRVAGen.Guid("1C9CD5BB-98E9-4491-A60F-31AACC72B83C")]
public static partial Guid* IID_IObjectWithSelection { get; }

[GuidRVAGen.Guid("000214E8-0000-0000-C000-000000000046")]
public static partial Guid* IID_IShellExtInit { get; }

[GuidRVAGen.Guid("000214F4-0000-0000-C000-000000000046")]
public static partial Guid* IID_IContextMenu2 { get; }
}

public static unsafe partial class CLSID
Expand All @@ -59,6 +80,15 @@ public static unsafe partial class CLSID

[GuidRVAGen.Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
public static partial Guid* CLSID_ApplicationActivationManager { get; }

[GuidRVAGen.Guid("B455F46E-E4AF-4035-B0A4-CF18D2F6F28E")]
public static partial Guid* CLSID_PinToFrequentExecute { get; }

[GuidRVAGen.Guid("EE20EEBA-DF64-4A4E-B7BB-2D1C6B2DFCC1")]
public static partial Guid* CLSID_UnPinFromFrequentExecute { get; }

[GuidRVAGen.Guid("D969A300-E7FF-11d0-A93B-00A0C90F2719")]
public static partial Guid* CLSID_NewMenu { get; }
}

public static unsafe partial class BHID
Expand Down
11 changes: 11 additions & 0 deletions src/Files.App.CsWin32/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,14 @@ QITIPF_FLAGS
GetKeyboardState
MapVirtualKey
GetKeyboardLayout
S_FALSE
IExecuteCommand
IObjectWithSelection
SHCreateShellItemArrayFromShellItem
IShellExtInit
IContextMenu2
GetSubMenu
GetMenuItemCount
GetMenuItemInfo
IsWow64Process2
GetCurrentProcess
50 changes: 24 additions & 26 deletions src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -48,38 +48,36 @@ public IAsyncEnumerable<IStorableChild> GetQuickAccessFolderAsync(CancellationTo
/// <inheritdoc/>
public IAsyncEnumerable<IStorableChild> GetLogicalDrivesAsync(CancellationToken cancellationToken = default)
{
return GetLogicalDrives().ToAsyncEnumerable();
var availableDrives = PInvoke.GetLogicalDrives();
if (availableDrives is 0)
return Enumerable.Empty<IStorableChild>().ToAsyncEnumerable();

IEnumerable<IStorableChild> 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<IStorableChild> driveItems = [];
foreach (char letter in driveLetters)
{
cancellationToken.ThrowIfCancellationRequested();

if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
throw new InvalidOperationException();
if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
throw new InvalidOperationException();

yield return new WindowsFolder(driveRoot.ThisPtr);
}
driveItems.Add(new WindowsFolder(driveRoot.ThisPtr));
}

return driveItems.ToAsyncEnumerable();
}

/// <inheritdoc/>
Expand Down
19 changes: 19 additions & 0 deletions src/Files.App.Storage/Storables/WindowsStorage/ContextMenuItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Storage
{
/// <summary>
/// Represents a Windows Shell ContextMenu item.
/// </summary>
public partial class ContextMenuItem
{
public ContextMenuType Type { get; set; }

public uint Id { get; set; }

public byte[]? Icon { get; set; }

public string? Name { get; set; }
}
}
18 changes: 18 additions & 0 deletions src/Files.App.Storage/Storables/WindowsStorage/ContextMenuType.cs
Original file line number Diff line number Diff line change
@@ -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,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Storage
{
public interface IWindowsFile : IWindowsStorable, IChildFile
{
}
}
15 changes: 15 additions & 0 deletions src/Files.App.Storage/Storables/WindowsStorage/IWindowsFolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Windows.Win32.UI.Shell;

namespace Files.App.Storage
{
public unsafe interface IWindowsFolder : IWindowsStorable, IChildFolder
{
/// <summary>
/// Gets or sets the cached <see cref="IContextMenu"/> for the ShellNew context menu.
/// </summary>
public IContextMenu* ShellNewMenu { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Windows.Win32;
using Windows.Win32.UI.Shell;

namespace Files.App.Storage
{
public interface IWindowsStorable : IDisposable
public unsafe interface IWindowsStorable : IStorableChild, IEquatable<IWindowsStorable>, IDisposable
{
ComPtr<IShellItem> ThisPtr { get; }
IShellItem* ThisPtr { get; set; }

IContextMenu* ContextMenu { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Files.App.Storage
{
public enum JumpListDestinationType
{
Pinned,

Recent,

Frequent,
}
}
74 changes: 10 additions & 64 deletions src/Files.App.Storage/Storables/WindowsStorage/JumpListManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,83 +12,29 @@ namespace Files.App.Storage
{
public unsafe class JumpListManager : IDisposable
{
private ComPtr<ICustomDestinationList> pCustomDestinationList = default;
public string AppId { get; }

private static string? AppId
public JumpListManager(string appId)
{
get
{
PWSTR pszAppId = default;
HRESULT hr = PInvoke.GetCurrentProcessExplicitAppUserModelID(&pszAppId);
if (hr == HRESULT.E_FAIL)
hr = HRESULT.S_OK;
if (string.IsNullOrEmpty(appId))
throw new ArgumentException("App ID cannot be null or empty.", nameof(appId));

hr.ThrowIfFailedOnDebug();

return pszAppId.ToString();
}
AppId = appId;
//_jumpList = new ConcurrentDictionary<string, IObjectArray>();
}

public ConcurrentBag<JumpListItem> JumpListItems { get; private set; } = [];

public ConcurrentBag<JumpListItem> RemovedItems { get; private set; } = [];

public ConcurrentBag<JumpListItem> RejectedItems { get; private set; } = [];

// A special "Frequent" category managed by Windows
public bool ShowFrequentCategory { get; set; }

// A special "Recent" category managed by Windows
public bool ShowRecentCategory { get; set; }

private static JumpListManager? _Default = null;
public static JumpListManager Default { get; } = _Default ??= new JumpListManager();

public JumpListManager()
public IEnumerable<WindowsStorable> GetAutomaticDestinations()
{
Guid CLSID_CustomDestinationList = typeof(DestinationList).GUID;
Guid IID_ICustomDestinationList = ICustomDestinationList.IID_Guid;
HRESULT hr = PInvoke.CoCreateInstance(
&CLSID_CustomDestinationList,
null,
CLSCTX.CLSCTX_INPROC_SERVER,
&IID_ICustomDestinationList,
(void**)pCustomDestinationList.GetAddressOf());

// Should not happen but as a sanity check at an early stage
hr.ThrowOnFailure();
return [];
}

public HRESULT Save()
public IEnumerable<WindowsStorable> GetCustomDestinations()
{
Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA);

HRESULT hr = pCustomDestinationList.Get()->SetAppID(AppId);

uint cMinSlots = 0;
ComPtr<IObjectArray> pDeletedItemsObjectArray = default;
Guid IID_IObjectArray = IObjectArray.IID_Guid;

hr = pCustomDestinationList.Get()->BeginList(&cMinSlots, &IID_IObjectArray, (void**)pDeletedItemsObjectArray.GetAddressOf());

// TODO: Validate items

// TODO: Group them as categories

// TODO: Append a custom category or to the Tasks

if (ShowFrequentCategory)
pCustomDestinationList.Get()->AppendKnownCategory(KNOWNDESTCATEGORY.KDC_FREQUENT);

if (ShowRecentCategory)
pCustomDestinationList.Get()->AppendKnownCategory(KNOWNDESTCATEGORY.KDC_RECENT);

return HRESULT.S_OK;
return [];
}

public void Dispose()
{
pCustomDestinationList.Dispose();
}
}
}
Loading
Loading