Skip to content

Commit a78e90c

Browse files
committed
Added GetShellNewItems & InvokeShellNewItem
1 parent aec76bb commit a78e90c

File tree

6 files changed

+166
-10
lines changed

6 files changed

+166
-10
lines changed

src/Files.App.CsWin32/ManualGuid.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory
5656

5757
[GuidRVAGen.Guid("1C9CD5BB-98E9-4491-A60F-31AACC72B83C")]
5858
public static partial Guid* IID_IObjectWithSelection { get; }
59+
60+
[GuidRVAGen.Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")]
61+
public static partial Guid* IID_IShellItemImageFactory { get; }
62+
63+
[GuidRVAGen.Guid("000214E8-0000-0000-C000-000000000046")]
64+
public static partial Guid* IID_IShellExtInit { get; }
65+
66+
[GuidRVAGen.Guid("000214F4-0000-0000-C000-000000000046")]
67+
public static partial Guid* IID_IContextMenu2 { get; }
5968
}
6069

6170
public static unsafe partial class CLSID
@@ -80,6 +89,9 @@ public static unsafe partial class CLSID
8089

8190
[GuidRVAGen.Guid("EE20EEBA-DF64-4A4E-B7BB-2D1C6B2DFCC1")]
8291
public static partial Guid* CLSID_UnPinFromFrequentExecute { get; }
92+
93+
[GuidRVAGen.Guid("D969A300-E7FF-11d0-A93B-00A0C90F2719")]
94+
public static partial Guid* CLSID_NewMenu { get; }
8395
}
8496

8597
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
@@ -229,3 +229,8 @@ S_FALSE
229229
IExecuteCommand
230230
IObjectWithSelection
231231
SHCreateShellItemArrayFromShellItem
232+
IShellExtInit
233+
IContextMenu2
234+
GetSubMenu
235+
GetMenuItemCount
236+
GetMenuItemInfo
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Storage
5+
{
6+
/// <summary>
7+
/// Represents a Windows Shell ContextMenu item.
8+
/// </summary>
9+
public partial class ContextMenuItem
10+
{
11+
public ContextMenuType Type { get; set; }
12+
13+
public uint Id { get; set; }
14+
15+
public byte[]? Icon { get; set; }
16+
17+
public string? Name { get; set; }
18+
}
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Files Community
2+
// Licensed under the MIT License.
3+
4+
namespace Files.App.Storage
5+
{
6+
public enum ContextMenuType
7+
{
8+
Normal = 0x00000000,
9+
10+
Disabled = 0x00000003,
11+
12+
Checked = 0x00000008,
13+
14+
Highlighted = 0x00000080,
15+
16+
Default = 0x00001000,
17+
}
18+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ public unsafe interface IWindowsFolder : IWindowsStorable, IChildFolder
1010
/// <summary>
1111
/// Gets or sets the cached <see cref="IContextMenu"/> for the ShellNew context menu.
1212
/// </summary>
13-
public IContextMenu* ShellNewMenu { get; }
13+
public IContextMenu* ShellNewMenu { get; set; }
1414
}
1515
}

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

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
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;
89
using Windows.Win32.System.Com;
910
using Windows.Win32.System.SystemServices;
1011
using Windows.Win32.UI.Shell;
12+
using Windows.Win32.UI.Shell.Common;
1113
using Windows.Win32.UI.Shell.PropertiesSystem;
1214
using Windows.Win32.UI.WindowsAndMessaging;
1315

1416
namespace Files.App.Storage
1517
{
16-
public static partial class WindowsStorableHelpers
18+
public unsafe static partial class WindowsStorableHelpers
1719
{
18-
public unsafe static HRESULT GetPropertyValue<TValue>(this IWindowsStorable storable, string propKey, out TValue value)
20+
public static HRESULT GetPropertyValue<TValue>(this IWindowsStorable storable, string propKey, out TValue value)
1921
{
2022
using ComPtr<IShellItem2> pShellItem2 = default;
2123
HRESULT hr = storable.ThisPtr->QueryInterface(IID.IID_IShellItem2, (void**)pShellItem2.GetAddressOf());
@@ -47,12 +49,12 @@ public unsafe static HRESULT GetPropertyValue<TValue>(this IWindowsStorable stor
4749
}
4850
}
4951

50-
public unsafe static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes)
52+
public static bool HasShellAttributes(this IWindowsStorable storable, SFGAO_FLAGS attributes)
5153
{
5254
return storable.ThisPtr->GetAttributes(attributes, out var dwRetAttributes).Succeeded && dwRetAttributes == attributes;
5355
}
5456

55-
public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH)
57+
public static string GetDisplayName(this IWindowsStorable storable, SIGDN options = SIGDN.SIGDN_FILESYSPATH)
5658
{
5759
using ComHeapPtr<PWSTR> pszName = default;
5860
HRESULT hr = storable.ThisPtr->GetDisplayName(options, (PWSTR*)pszName.GetAddressOf());
@@ -62,7 +64,7 @@ public unsafe static string GetDisplayName(this IWindowsStorable storable, SIGDN
6264
: string.Empty;
6365
}
6466

65-
public unsafe static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable storable, string verbName)
67+
public static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable storable, string verbName)
6668
{
6769
Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA);
6870

@@ -87,7 +89,7 @@ public unsafe static HRESULT TryInvokeContextMenuVerb(this IWindowsStorable stor
8789
}
8890
}
8991

90-
public unsafe static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable storable, string[] verbNames, bool earlyReturnOnSuccess)
92+
public static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable storable, string[] verbNames, bool earlyReturnOnSuccess)
9193
{
9294
Debug.Assert(Thread.CurrentThread.GetApartmentState() is ApartmentState.STA);
9395

@@ -118,7 +120,7 @@ public unsafe static HRESULT TryInvokeContextMenuVerbs(this IWindowsStorable sto
118120
return hr;
119121
}
120122

121-
public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable, out string? tooltip)
123+
public static HRESULT TryGetShellTooltip(this IWindowsStorable storable, out string? tooltip)
122124
{
123125
tooltip = null;
124126

@@ -137,7 +139,7 @@ public unsafe static HRESULT TryGetShellTooltip(this IWindowsStorable storable,
137139
return HRESULT.S_OK;
138140
}
139141

140-
public unsafe static HRESULT TryPinFolderToQuickAccess(this IWindowsFolder @this)
142+
public static HRESULT TryPinFolderToQuickAccess(this IWindowsFolder @this)
141143
{
142144
HRESULT hr = default;
143145

@@ -168,7 +170,7 @@ public unsafe static HRESULT TryPinFolderToQuickAccess(this IWindowsFolder @this
168170
return HRESULT.S_OK;
169171
}
170172

171-
public unsafe static HRESULT TryUnpinFolderFromQuickAccess(this IWindowsFolder @this)
173+
public static HRESULT TryUnpinFolderFromQuickAccess(this IWindowsFolder @this)
172174
{
173175
HRESULT hr = default;
174176

@@ -198,5 +200,105 @@ public unsafe static HRESULT TryUnpinFolderFromQuickAccess(this IWindowsFolder @
198200

199201
return HRESULT.S_OK;
200202
}
203+
204+
public static IEnumerable<ContextMenuItem> GetShellNewItems(this IWindowsFolder @this)
205+
{
206+
HRESULT hr = default;
207+
208+
IContextMenu* pNewMenu = default;
209+
using ComPtr<IShellExtInit> pShellExtInit = default;
210+
using ComPtr<IContextMenu2> pContextMenu2 = default;
211+
212+
hr = PInvoke.CoCreateInstance(CLSID.CLSID_NewMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)&pNewMenu);
213+
if (hr.ThrowIfFailedOnDebug().Failed)
214+
return [];
215+
216+
hr = pNewMenu->QueryInterface(IID.IID_IContextMenu2, (void**)pContextMenu2.GetAddressOf());
217+
if (hr.ThrowIfFailedOnDebug().Failed)
218+
return [];
219+
220+
hr = pNewMenu->QueryInterface(IID.IID_IShellExtInit, (void**)pShellExtInit.GetAddressOf());
221+
if (hr.ThrowIfFailedOnDebug().Failed)
222+
return [];
223+
224+
@this.ShellNewMenu = pNewMenu;
225+
226+
ITEMIDLIST* pFolderPidl = default;
227+
hr = PInvoke.SHGetIDListFromObject((IUnknown*)@this.ThisPtr, &pFolderPidl);
228+
if (hr.ThrowIfFailedOnDebug().Failed)
229+
return [];
230+
231+
hr = pShellExtInit.Get()->Initialize(pFolderPidl, null, default);
232+
if (hr.ThrowIfFailedOnDebug().Failed)
233+
return [];
234+
235+
// Inserts "New (&W)"
236+
HMENU hMenu = PInvoke.CreatePopupMenu();
237+
hr = pNewMenu->QueryContextMenu(hMenu, 0, 1, 256, 0);
238+
if (hr.ThrowIfFailedOnDebug().Failed)
239+
return [];
240+
241+
// Invokes CNewMenu::_InitMenuPopup(), which populates the hSubMenu
242+
HMENU hSubMenu = PInvoke.GetSubMenu(hMenu, 0);
243+
hr = pContextMenu2.Get()->HandleMenuMsg(PInvoke.WM_INITMENUPOPUP, (WPARAM)(nuint)hSubMenu.Value, 0);
244+
if (hr.ThrowIfFailedOnDebug().Failed)
245+
return [];
246+
247+
uint dwCount = unchecked((uint)PInvoke.GetMenuItemCount(hSubMenu));
248+
if (dwCount is unchecked((uint)-1))
249+
return [];
250+
251+
// Enumerates and populates the list
252+
List<ContextMenuItem> shellNewItems = [];
253+
for (uint dwIndex = 0; dwIndex < dwCount; dwIndex++)
254+
{
255+
MENUITEMINFOW mii = default;
256+
mii.cbSize = (uint)sizeof(MENUITEMINFOW);
257+
mii.fMask = MENU_ITEM_MASK.MIIM_STRING | MENU_ITEM_MASK.MIIM_ID | MENU_ITEM_MASK.MIIM_STATE;
258+
mii.dwTypeData = (char*)NativeMemory.Alloc(256U);
259+
mii.cch = 256;
260+
261+
if (PInvoke.GetMenuItemInfo(hSubMenu, dwIndex, true, &mii))
262+
{
263+
shellNewItems.Add(new()
264+
{
265+
Id = mii.wID,
266+
Name = mii.dwTypeData.ToString(),
267+
Type = (ContextMenuType)mii.fState,
268+
});
269+
}
270+
271+
NativeMemory.Free(mii.dwTypeData);
272+
}
273+
274+
return shellNewItems;
275+
}
276+
277+
public static bool InvokeShellNewItem(this IWindowsFolder @this, ContextMenuItem item)
278+
{
279+
HRESULT hr = default;
280+
281+
if (@this.ShellNewMenu is null)
282+
{
283+
IContextMenu* pNewMenu = default;
284+
285+
hr = PInvoke.CoCreateInstance(CLSID.CLSID_NewMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)&pNewMenu);
286+
if (hr.ThrowIfFailedOnDebug().Failed)
287+
return false;
288+
289+
@this.ShellNewMenu = pNewMenu;
290+
}
291+
292+
CMINVOKECOMMANDINFO cmici = default;
293+
cmici.cbSize = (uint)sizeof(CMINVOKECOMMANDINFO);
294+
cmici.lpVerb = (PCSTR)(byte*)item.Id;
295+
cmici.nShow = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL;
296+
297+
hr = @this.ShellNewMenu->InvokeCommand(&cmici);
298+
if (hr.ThrowIfFailedOnDebug().Failed)
299+
return false;
300+
301+
return false;
302+
}
201303
}
202304
}

0 commit comments

Comments
 (0)