Skip to content

Commit ab66a18

Browse files
committed
Introduced GetShellNewItems
1 parent 011cd9d commit ab66a18

File tree

8 files changed

+114
-121
lines changed

8 files changed

+114
-121
lines changed

src/Files.App.CsWin32/ManualGuid.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory
4444

4545
[GuidRVAGen.Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")]
4646
public static partial Guid* IID_IShellItemImageFactory { get; }
47+
48+
[GuidRVAGen.Guid("000214E8-0000-0000-C000-000000000046")]
49+
public static partial Guid* IID_IShellExtInit { get; }
50+
51+
[GuidRVAGen.Guid("000214F4-0000-0000-C000-000000000046")]
52+
public static partial Guid* IID_IContextMenu2 { get; }
4753
}
4854

4955
public static unsafe partial class CLSID
@@ -62,6 +68,9 @@ public static unsafe partial class CLSID
6268

6369
[GuidRVAGen.Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
6470
public static partial Guid* CLSID_ApplicationActivationManager { get; }
71+
72+
[GuidRVAGen.Guid("D969A300-E7FF-11d0-A93B-00A0C90F2719")]
73+
public static partial Guid* CLSID_NewMenu { get; }
6574
}
6675

6776
public static unsafe partial class BHID

src/Files.App.CsWin32/NativeMethods.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,8 @@ HKEY_CURRENT_USER
230230
RegOpenKeyEx
231231
RegEnumKeyEx
232232
RegGetValue
233+
IShellExtInit
234+
IContextMenu2
235+
GetSubMenu
236+
GetMenuItemCount
237+
GetMenuItemInfo
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Storage
5+
{
6+
public enum ContextMenuType
7+
{
8+
Enabled = 0x00000000,
9+
Unchecked = 0x00000000,
10+
Unhighlighted = 0x00000000,
11+
12+
Grayed = 0x00000003,
13+
Disabled = 0x00000003,
14+
15+
Checked = 0x00000008,
16+
17+
Highlighted = 0x00000080,
18+
19+
Default = 0x00001000,
20+
}
21+
}

src/Files.App.Storage/Storables/WindowsStorage/ShellNewItem.cs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,14 @@ namespace Files.App.Storage
66
/// <summary>
77
/// Represents a ShellNew item with its type, file extension, description, and icon.
88
/// </summary>
9-
/// <remarks>
10-
/// This class maps to an undocumented struct, SHELLNEW_ITEM.
11-
/// </remarks>
12-
// TODO: Should this class be a record instead?
139
public partial class ShellNewItem
1410
{
15-
/// <summary>
16-
/// Gets the type of a new shell item.
17-
/// </summary>
18-
public ShellNewItemType Type { get; set; } = ShellNewItemType.Invalid;
11+
public ContextMenuType Type { get; set; } = ContextMenuType.Enabled;
1912

20-
/// <summary>
21-
/// Gets string file extension (e.g. ".txt").
22-
/// </summary>
23-
public string? Extension { get; set; }
13+
public uint Id { get; set; }
2414

25-
/// <summary>
26-
/// Gets string description as a string.
27-
/// </summary>
28-
public string? Description { get; set; }
29-
30-
/// <summary>
31-
/// Gets icon data as a byte array.
32-
/// </summary>
3315
public byte[]? Icon { get; set; }
16+
17+
public string? Name { get; set; }
3418
}
3519
}

src/Files.App.Storage/Storables/WindowsStorage/ShellNewItemType.cs

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/Files.App.Storage/Storables/WindowsStorage/ShellNewManager.cs

Lines changed: 0 additions & 74 deletions
This file was deleted.

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Registry.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public static bool GetRegistryValue<T>(HKEY hRootKey, string szSubKey, string sz
6262

6363
switch (dwValueType)
6464
{
65+
default:
6566
case REG_VALUE_TYPE.REG_NONE:
6667
case REG_VALUE_TYPE.REG_BINARY:
6768
{
@@ -76,8 +77,8 @@ public static bool GetRegistryValue<T>(HKEY hRootKey, string szSubKey, string sz
7677
{
7778
valueData = cbData switch
7879
{
79-
4U => valueData = Unsafe.As<uint, T>(ref *(uint*)pData),
80-
8U => valueData = Unsafe.As<ulong, T>(ref *(ulong*)pData),
80+
4U => Unsafe.As<uint, T>(ref *(uint*)pData),
81+
8U => Unsafe.As<ulong, T>(ref *(ulong*)pData),
8182
_ => throw new InvalidCastException($"Registry value size of data \"{nameof(pData)}\" of \"{nameof(pszValueName)}\" is invalid (size: \"{cbData}\")."),
8283
};
8384
}
@@ -96,32 +97,35 @@ public static bool GetRegistryValue<T>(HKEY hRootKey, string szSubKey, string sz
9697
break;
9798
case REG_VALUE_TYPE.REG_MULTI_SZ:
9899
{
99-
byte* dwPtrPosition = pData;
100+
byte* pDataPtrSeeker = pData;
100101
uint dwSeparatorCount = 0U;
102+
uint dwArrayIndex = 0U;
103+
string[] stringDataArray = new string[dwSeparatorCount + 1];
101104

102-
while (dwPtrPosition < pData + cbData)
105+
while (pDataPtrSeeker < pData + cbData)
103106
{
104-
if ((char)*dwPtrPosition is '\0')
107+
if ((char)*pDataPtrSeeker is '\0')
105108
dwSeparatorCount++;
106109

107-
dwPtrPosition++;
110+
pDataPtrSeeker++;
108111
}
109112

110-
dwPtrPosition = pData;
111-
uint dwArrayIndex = 0U;
112-
string[] stringDataArray = new string[dwSeparatorCount + 1];
113+
// Reset pointer to the start of the data
114+
pDataPtrSeeker = pData;
113115

114-
while (dwPtrPosition < pData + cbData)
116+
while (pDataPtrSeeker < pData + cbData)
115117
{
116-
if ((char)*dwPtrPosition is '\0')
118+
if ((char)*pDataPtrSeeker is '\0')
117119
{
118120
dwArrayIndex++;
119121
continue;
120122
}
121123

122124
stringDataArray[dwArrayIndex] = new((char*)pData);
123-
dwPtrPosition += stringDataArray[dwArrayIndex].Length;
125+
pDataPtrSeeker += stringDataArray[dwArrayIndex].Length;
124126
}
127+
128+
valueData = (T)(object)stringDataArray;
125129
}
126130
break;
127131
}

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22
// Licensed under the MIT License.
33

44
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
56
using System.Text;
67
using Windows.Win32;
78
using Windows.Win32.Foundation;
9+
using Windows.Win32.System.Com;
810
using Windows.Win32.System.SystemServices;
911
using Windows.Win32.UI.Shell;
12+
using Windows.Win32.UI.Shell.Common;
1013
using Windows.Win32.UI.Shell.PropertiesSystem;
1114
using Windows.Win32.UI.WindowsAndMessaging;
1215

1316
namespace Files.App.Storage
1417
{
15-
public static partial class WindowsStorableHelpers
18+
public unsafe static partial class WindowsStorableHelpers
1619
{
1720
public unsafe static HRESULT GetPropertyValue<TValue>(this IWindowsStorable storable, string propKey, out TValue value)
1821
{
@@ -143,5 +146,60 @@ public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable,
143146

144147
return HRESULT.S_OK;
145148
}
149+
150+
public static IEnumerable<ShellNewItem> GetShellNewItems(this IWindowsStorable storable)
151+
{
152+
HRESULT hr = default;
153+
154+
using ComPtr<IContextMenu> pNewMenu = default;
155+
using ComPtr<IShellExtInit> pShellExtInit = default;
156+
using ComPtr<IContextMenu2> pContextMenu2 = default;
157+
158+
// Instantiate CNewMenu as IContextMenu, IContextMenu2, and IShellExtInit
159+
hr = PInvoke.CoCreateInstance(CLSID.CLSID_NewMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)pNewMenu.GetAddressOf());
160+
hr = pNewMenu.Get()->QueryInterface(IID.IID_IContextMenu2, (void**)pContextMenu2.GetAddressOf());
161+
hr = pNewMenu.Get()->QueryInterface(IID.IID_IShellExtInit, (void**)pShellExtInit.GetAddressOf());
162+
163+
// Initialize CNewMenu with the PIDL of the folder
164+
ITEMIDLIST* pFolderPidl = default;
165+
hr = PInvoke.SHGetIDListFromObject((IUnknown*)storable.ThisPtr, &pFolderPidl);
166+
hr = pShellExtInit.Get()->Initialize(pFolderPidl, null, default);
167+
168+
// Inserts "New (&W)"
169+
HMENU hMenu = PInvoke.CreatePopupMenu();
170+
hr = pNewMenu.Get()->QueryContextMenu(hMenu, 0, 1, 256, 0);
171+
172+
// Invokes CNewMenu::_InitMenuPopup(), which populates the hSubMenu
173+
HMENU hSubMenu = PInvoke.GetSubMenu(hMenu, 0);
174+
hr = pContextMenu2.Get()->HandleMenuMsg(PInvoke.WM_INITMENUPOPUP, (WPARAM)(nuint)hSubMenu.Value, 0);
175+
176+
// Enumerate and populate the list
177+
List<ShellNewItem> shellNewItems = [];
178+
uint dwCount = (uint)PInvoke.GetMenuItemCount(hSubMenu);
179+
for (uint dwIndex = 0; dwIndex < dwCount; dwIndex++)
180+
{
181+
MENUITEMINFOW mii = default;
182+
mii.cbSize = (uint)sizeof(MENUITEMINFOW);
183+
mii.fMask = MENU_ITEM_MASK.MIIM_STRING | MENU_ITEM_MASK.MIIM_ID | MENU_ITEM_MASK.MIIM_STATE;
184+
mii.dwTypeData = (char*)NativeMemory.Alloc(256U);
185+
mii.cch = 256;
186+
187+
if (PInvoke.GetMenuItemInfo(hSubMenu, dwIndex, true, &mii))
188+
{
189+
Console.WriteLine($"{dwIndex:X}: {mii.wID:X}, {mii.fState}, {mii.dwTypeData}");
190+
191+
shellNewItems.Add(new()
192+
{
193+
Id = mii.wID,
194+
Name = mii.dwTypeData.ToString(),
195+
Type = (ContextMenuType)mii.fState,
196+
});
197+
}
198+
199+
NativeMemory.Free(mii.dwTypeData);
200+
}
201+
202+
return shellNewItems;
203+
}
146204
}
147205
}

0 commit comments

Comments
 (0)