Add Open in Notepad++ and static Windows shell verbs without embedding IContextMenu on right-click.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
702
src/Explorer.Windows/WindowsShellContextMenu.cs
Normal file
702
src/Explorer.Windows/WindowsShellContextMenu.cs
Normal file
@@ -0,0 +1,702 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Explorer.Application;
|
||||
using Explorer.Domain;
|
||||
using Explorer.Domain.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Explorer.Windows;
|
||||
|
||||
public sealed class WindowsShellContextMenu : IShellContextMenu
|
||||
{
|
||||
private const uint IdCmdFirst = 1;
|
||||
private const uint IdCmdLast = 0x7FFF;
|
||||
private const uint CmfNormal = 0;
|
||||
private const uint CmfExplore = 4;
|
||||
private const uint CmfCanRename = 0x10;
|
||||
private const uint CmfExtendedVerbs = 0x100;
|
||||
private const uint CmfItemMenu = 0x80;
|
||||
private const uint TpmLeftAlign = 0x0000;
|
||||
private const uint TpmRightButton = 0x0002;
|
||||
private const uint TpmReturnCmd = 0x0100;
|
||||
private const uint WmNull = 0x0000;
|
||||
private const uint WmInitMenuPopup = 0x0117;
|
||||
private const uint WmDrawItem = 0x002B;
|
||||
private const uint WmMeasureItem = 0x002C;
|
||||
private const uint WmMenuChar = 0x0120;
|
||||
private const uint WmLButtonDown = 0x0201;
|
||||
private const uint WmLButtonUp = 0x0202;
|
||||
private const uint WmRButtonDown = 0x0204;
|
||||
private const uint WmRButtonUp = 0x0205;
|
||||
private const uint WsPopup = 0x80000000;
|
||||
private const uint WsExToolwindow = 0x00000080;
|
||||
private const uint WsExNoActivate = 0x08000000;
|
||||
private const uint PmRemove = 0x0001;
|
||||
private const int GwlpWndProc = -4;
|
||||
private const int SwShowNormal = 1;
|
||||
private const int SwShowNoActivate = 8;
|
||||
private const int VkLButton = 0x01;
|
||||
private const int VkRButton = 0x02;
|
||||
private static readonly Guid ShellItemId = new("43826d1e-e718-42ee-bc55-a1e261c37bfe");
|
||||
private static readonly Guid ContextMenuId = new("000214e4-0000-0000-c000-000000000046");
|
||||
private static readonly Guid SfuiObject = new("3981e224-f559-11d3-8e3a-00c04f6837d5");
|
||||
|
||||
private readonly ILogger<WindowsShellContextMenu> _logger;
|
||||
private Dictionary<string, string>? _staticCommands;
|
||||
private IReadOnlyList<string> _paths = [];
|
||||
private nint _oldWndProc;
|
||||
private WndProc? _subclassProc;
|
||||
private IContextMenu2? _menu2;
|
||||
private IContextMenu3? _menu3;
|
||||
|
||||
public WindowsShellContextMenu(ILogger<WindowsShellContextMenu> logger) => _logger = logger;
|
||||
|
||||
public IReadOnlyList<ShellContextVerb> Query(IReadOnlyList<string> paths)
|
||||
{
|
||||
_paths = Normalize(paths);
|
||||
if (_paths.Count == 0)
|
||||
{
|
||||
_staticCommands = null;
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return ShellContextVerbFilter.Prune(QueryRegistryVerbs(_paths));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Registry shell verbs failed");
|
||||
_staticCommands = null;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryInvoke(string id, out string? error)
|
||||
{
|
||||
error = null;
|
||||
try
|
||||
{
|
||||
if (id.StartsWith("static:", StringComparison.Ordinal)
|
||||
&& _staticCommands is not null
|
||||
&& _staticCommands.TryGetValue(id, out var command)
|
||||
&& ShellVerbCommand.TrySplit(command, out var exe, out var args))
|
||||
{
|
||||
foreach (var invocation in ShellVerbCommand.ExpandInvocations(args, _paths))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = exe,
|
||||
Arguments = invocation,
|
||||
UseShellExecute = false
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
_logger.LogDebug(ex, "Shell verb invoke failed {Id}", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = "Command is no longer available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryShowFullMenu(IReadOnlyList<string> paths, int screenX, int screenY, nint ownerHwnd, out string? error)
|
||||
{
|
||||
error = null;
|
||||
var normalized = Normalize(paths);
|
||||
if (normalized.Count == 0)
|
||||
{
|
||||
error = "Select a file first.";
|
||||
return false;
|
||||
}
|
||||
|
||||
nint hMenu = 0;
|
||||
nint host = 0;
|
||||
IContextMenu? menu = null;
|
||||
try
|
||||
{
|
||||
if (!TryCreateShellMenu(normalized, out menu) || menu is null)
|
||||
{
|
||||
error = "Windows could not build the full menu.";
|
||||
return false;
|
||||
}
|
||||
|
||||
hMenu = CreatePopupMenu();
|
||||
var hr = menu.QueryContextMenu(
|
||||
hMenu,
|
||||
0,
|
||||
IdCmdFirst,
|
||||
IdCmdLast,
|
||||
CmfNormal | CmfExplore | CmfCanRename | CmfItemMenu | CmfExtendedVerbs);
|
||||
if (hr < 0)
|
||||
{
|
||||
error = "Windows could not fill the full menu.";
|
||||
return false;
|
||||
}
|
||||
|
||||
DrainPendingMouseClicks();
|
||||
WaitForMouseButtonsUp();
|
||||
|
||||
// Message-only windows cannot host a visible TrackPopupMenu. Own a tiny popup
|
||||
// from the Workbench HWND so IContextMenu2/3 messages stay off the WPF wndproc.
|
||||
host = CreateWindowEx(
|
||||
WsExToolwindow | WsExNoActivate,
|
||||
"Static",
|
||||
"",
|
||||
WsPopup,
|
||||
screenX,
|
||||
screenY,
|
||||
1,
|
||||
1,
|
||||
ownerHwnd,
|
||||
0,
|
||||
GetModuleHandle(null),
|
||||
0);
|
||||
var hwnd = host != 0 ? host : ownerHwnd;
|
||||
if (hwnd == 0)
|
||||
{
|
||||
error = "Windows could not open the full menu.";
|
||||
return false;
|
||||
}
|
||||
|
||||
_menu2 = QueryInterface<IContextMenu2>(menu);
|
||||
_menu3 = QueryInterface<IContextMenu3>(menu);
|
||||
if (host != 0)
|
||||
{
|
||||
ShowWindow(host, SwShowNoActivate);
|
||||
Subclass(host);
|
||||
}
|
||||
|
||||
SetForegroundWindow(ownerHwnd != 0 ? ownerHwnd : hwnd);
|
||||
var cmd = TrackPopupMenuEx(
|
||||
hMenu,
|
||||
TpmLeftAlign | TpmRightButton | TpmReturnCmd,
|
||||
screenX,
|
||||
screenY,
|
||||
hwnd,
|
||||
0);
|
||||
PostMessage(hwnd, WmNull, 0, 0);
|
||||
if (cmd < IdCmdFirst)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return InvokeOffset(menu, cmd - IdCmdFirst, ownerHwnd != 0 ? ownerHwnd : hwnd, out error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
_logger.LogDebug(ex, "Full shell menu failed");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (host != 0)
|
||||
{
|
||||
Unsubclass(host);
|
||||
DestroyWindow(host);
|
||||
}
|
||||
|
||||
_menu2 = null;
|
||||
_menu3 = null;
|
||||
if (hMenu != 0)
|
||||
{
|
||||
DestroyMenu(hMenu);
|
||||
}
|
||||
|
||||
if (menu is not null)
|
||||
{
|
||||
Marshal.ReleaseComObject(menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> Normalize(IReadOnlyList<string> paths)
|
||||
=> paths
|
||||
.Where(path => !string.IsNullOrWhiteSpace(path) && !LocationRoots.IsVirtual(path))
|
||||
.Select(PathRules.FromExtended)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Take(32)
|
||||
.ToList();
|
||||
|
||||
private bool InvokeOffset(IContextMenu menu, uint offset, nint hwnd, out string? error)
|
||||
{
|
||||
error = null;
|
||||
var info = new Cminvokecommandinfo
|
||||
{
|
||||
cbSize = Marshal.SizeOf<Cminvokecommandinfo>(),
|
||||
fMask = 0,
|
||||
hwnd = hwnd,
|
||||
lpVerb = (nint)offset,
|
||||
lpParameters = null,
|
||||
lpDirectory = null,
|
||||
nShow = SwShowNormal
|
||||
};
|
||||
var hr = menu.InvokeCommand(ref info);
|
||||
if (hr < 0)
|
||||
{
|
||||
error = "The Windows command failed.";
|
||||
_logger.LogDebug("IContextMenu.InvokeCommand offset={Offset} hr={Hr}", offset, hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryCreateShellMenu(IReadOnlyList<string> paths, out IContextMenu? menu)
|
||||
=> (paths.Count == 1 && TryCreateItemMenu(paths[0], out menu))
|
||||
|| TryCreateItemArrayMenu(paths, out menu);
|
||||
|
||||
private static bool TryCreateItemMenu(string path, out IContextMenu? menu)
|
||||
{
|
||||
menu = null;
|
||||
var iidItem = ShellItemId;
|
||||
if (SHCreateItemFromParsingName(path, 0, ref iidItem, out var item) != 0 || item is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return TryBindContextMenu(item, out menu);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ReleaseComObject(item);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryCreateItemArrayMenu(IReadOnlyList<string> paths, out IContextMenu? menu)
|
||||
{
|
||||
menu = null;
|
||||
var pidls = new List<nint>();
|
||||
try
|
||||
{
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (SHParseDisplayName(path, 0, out var pidl, 0, out _) != 0 || pidl == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pidls.Add(pidl);
|
||||
}
|
||||
|
||||
if (SHCreateShellItemArrayFromIDLists((uint)pidls.Count, pidls.ToArray(), out var array) != 0
|
||||
|| array is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return TryBindContextMenu(array, out menu);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.ReleaseComObject(array);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pidl in pidls)
|
||||
{
|
||||
ILFree(pidl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryBindContextMenu(IShellItem item, out IContextMenu? menu)
|
||||
{
|
||||
var bhid = SfuiObject;
|
||||
var iid = ContextMenuId;
|
||||
return TryWrapContextMenu(item.BindToHandler(0, ref bhid, ref iid, out var unk), unk, out menu);
|
||||
}
|
||||
|
||||
private static bool TryBindContextMenu(IShellItemArray array, out IContextMenu? menu)
|
||||
{
|
||||
var bhid = SfuiObject;
|
||||
var iid = ContextMenuId;
|
||||
return TryWrapContextMenu(array.BindToHandler(0, ref bhid, ref iid, out var unk), unk, out menu);
|
||||
}
|
||||
|
||||
private static bool TryWrapContextMenu(int hr, nint unk, out IContextMenu? menu)
|
||||
{
|
||||
menu = null;
|
||||
if (hr != 0 || unk == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
menu = (IContextMenu)Marshal.GetObjectForIUnknown(unk);
|
||||
return menu is not null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.Release(unk);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrainPendingMouseClicks()
|
||||
{
|
||||
while (PeekMessage(out _, 0, WmLButtonDown, WmLButtonUp, PmRemove))
|
||||
{
|
||||
}
|
||||
|
||||
while (PeekMessage(out _, 0, WmRButtonDown, WmRButtonUp, PmRemove))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void WaitForMouseButtonsUp()
|
||||
{
|
||||
var start = Environment.TickCount64;
|
||||
while (Environment.TickCount64 - start < 250
|
||||
&& ((GetAsyncKeyState(VkLButton) & 0x8000) != 0 || (GetAsyncKeyState(VkRButton) & 0x8000) != 0))
|
||||
{
|
||||
DrainPendingMouseClicks();
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<ShellContextVerb> QueryRegistryVerbs(IReadOnlyList<string> paths)
|
||||
{
|
||||
_staticCommands = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
var allFiles = paths.All(File.Exists);
|
||||
var allDirs = paths.All(Directory.Exists);
|
||||
var keys = new List<string>();
|
||||
if (allFiles)
|
||||
{
|
||||
keys.Add(@"*\shell");
|
||||
var exts = paths.Select(path => Path.GetExtension(path)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
if (exts.Count == 1 && !string.IsNullOrEmpty(exts[0]))
|
||||
{
|
||||
keys.AddRange(ProgIdShellKeys(exts[0]));
|
||||
}
|
||||
}
|
||||
else if (allDirs)
|
||||
{
|
||||
keys.Add(@"Directory\shell");
|
||||
keys.Add(@"Folder\shell");
|
||||
}
|
||||
|
||||
var items = new List<ShellContextVerb>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var key in keys)
|
||||
{
|
||||
foreach (var hive in new[] { Registry.ClassesRoot, Registry.CurrentUser })
|
||||
{
|
||||
var path = hive == Registry.CurrentUser ? @"Software\Classes\" + key : key;
|
||||
using var shell = hive.OpenSubKey(path);
|
||||
if (shell is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var name in shell.GetSubKeyNames())
|
||||
{
|
||||
using var verbKey = shell.OpenSubKey(name);
|
||||
if (verbKey is null || verbKey.GetValue("LegacyDisable") is not null
|
||||
|| verbKey.GetValue("ProgrammaticAccessOnly") is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using var commandKey = verbKey.OpenSubKey("command");
|
||||
var command = commandKey?.GetValue(null) as string;
|
||||
if (string.IsNullOrWhiteSpace(command) || commandKey?.GetValue("DelegateExecute") is not null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var label = (verbKey.GetValue("MUIVerb") as string) ?? (verbKey.GetValue(null) as string) ?? name;
|
||||
if (label.StartsWith('@'))
|
||||
{
|
||||
label = name;
|
||||
}
|
||||
|
||||
var canonical = ShellContextVerbFilter.CanonicalLabel(label);
|
||||
if (!seen.Add(canonical) || !ShellContextVerbFilter.ShouldInclude(name, label))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var id = "static:" + items.Count;
|
||||
_staticCommands[id] = command;
|
||||
items.Add(new ShellContextVerb(id, label));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ProgIdShellKeys(string extension)
|
||||
{
|
||||
yield return @"SystemFileAssociations\" + extension + @"\shell";
|
||||
using var extKey = Registry.ClassesRoot.OpenSubKey(extension);
|
||||
var progId = extKey?.GetValue(null) as string;
|
||||
if (!string.IsNullOrWhiteSpace(progId))
|
||||
{
|
||||
yield return progId + @"\shell";
|
||||
}
|
||||
}
|
||||
|
||||
private void Subclass(nint hwnd)
|
||||
{
|
||||
_subclassProc = OnSubclass;
|
||||
_oldWndProc = SetWindowLongPtr(hwnd, GwlpWndProc, Marshal.GetFunctionPointerForDelegate(_subclassProc));
|
||||
}
|
||||
|
||||
private void Unsubclass(nint hwnd)
|
||||
{
|
||||
if (hwnd == 0 || _oldWndProc == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWindowLongPtr(hwnd, GwlpWndProc, _oldWndProc);
|
||||
_oldWndProc = 0;
|
||||
_subclassProc = null;
|
||||
}
|
||||
|
||||
private nint OnSubclass(nint hWnd, uint msg, nint wParam, nint lParam)
|
||||
{
|
||||
if (_menu3 is not null
|
||||
&& msg is WmInitMenuPopup or WmDrawItem or WmMeasureItem or WmMenuChar
|
||||
&& _menu3.HandleMenuMsg2(msg, wParam, lParam, out var result) == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_menu2 is not null
|
||||
&& msg is WmInitMenuPopup or WmDrawItem or WmMeasureItem or WmMenuChar)
|
||||
{
|
||||
_menu2.HandleMenuMsg(msg, wParam, lParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CallWindowProc(_oldWndProc, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
private static T? QueryInterface<T>(object com) where T : class
|
||||
{
|
||||
var unk = Marshal.GetIUnknownForObject(com);
|
||||
try
|
||||
{
|
||||
var iid = typeof(T).GUID;
|
||||
if (Marshal.QueryInterface(unk, in iid, out var ptr) != 0 || ptr == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return (T)Marshal.GetObjectForIUnknown(ptr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.Release(ptr);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.Release(unk);
|
||||
}
|
||||
}
|
||||
|
||||
private delegate nint WndProc(nint hWnd, uint msg, nint wParam, nint lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool PostMessage(nint hWnd, uint msg, nint wParam, nint lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern short GetAsyncKeyState(int vKey);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "PeekMessageW")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool PeekMessage(out NativeMsg msg, nint hWnd, uint msgMin, uint msgMax, uint remove);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint TrackPopupMenuEx(nint hmenu, uint flags, int x, int y, nint hwnd, nint paramsEx);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
|
||||
private static extern nint SetWindowLongPtr64(nint hWnd, int nIndex, nint dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongW")]
|
||||
private static extern int SetWindowLong32(nint hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
private static nint SetWindowLongPtr(nint hWnd, int nIndex, nint value)
|
||||
=> nint.Size == 8
|
||||
? SetWindowLongPtr64(hWnd, nIndex, value)
|
||||
: SetWindowLong32(hWnd, nIndex, (int)value);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint CallWindowProc(nint prev, nint hWnd, uint msg, nint wParam, nint lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW")]
|
||||
private static extern nint CreateWindowEx(
|
||||
uint exStyle,
|
||||
string className,
|
||||
string windowName,
|
||||
uint style,
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
nint parent,
|
||||
nint menu,
|
||||
nint instance,
|
||||
nint param);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DestroyWindow(nint hWnd);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern nint GetModuleHandle(string? module);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int SHParseDisplayName(string name, nint bindCtx, out nint pidl, uint sfgaoIn, out uint sfgaoOut);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int SHCreateItemFromParsingName(
|
||||
string path,
|
||||
nint bindCtx,
|
||||
[In] ref Guid riid,
|
||||
[MarshalAs(UnmanagedType.Interface)] out IShellItem item);
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
private static extern int SHCreateShellItemArrayFromIDLists(uint cidl, [In] nint[] pidls, out IShellItemArray array);
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
private static extern void ILFree(nint pidl);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint CreatePopupMenu();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DestroyMenu(nint hMenu);
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
||||
private interface IShellItem
|
||||
{
|
||||
[PreserveSig]
|
||||
int BindToHandler(nint pbc, ref Guid bhid, ref Guid riid, out nint ppv);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("b63ea76d-1f85-456f-a19c-48159efa858b")]
|
||||
private interface IShellItemArray
|
||||
{
|
||||
[PreserveSig]
|
||||
int BindToHandler(nint pbc, ref Guid bhid, ref Guid riid, out nint ppv);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214e4-0000-0000-c000-000000000046")]
|
||||
private interface IContextMenu
|
||||
{
|
||||
[PreserveSig]
|
||||
int QueryContextMenu(nint hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int InvokeCommand(ref Cminvokecommandinfo info);
|
||||
|
||||
[PreserveSig]
|
||||
int GetCommandString(UIntPtr idCmd, uint uType, nint reserved, nint name, uint cchMax);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214f4-0000-0000-c000-000000000046")]
|
||||
private interface IContextMenu2
|
||||
{
|
||||
[PreserveSig]
|
||||
int QueryContextMenu(nint hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int InvokeCommand(ref Cminvokecommandinfo info);
|
||||
|
||||
[PreserveSig]
|
||||
int GetCommandString(UIntPtr idCmd, uint uType, nint reserved, nint name, uint cchMax);
|
||||
|
||||
[PreserveSig]
|
||||
int HandleMenuMsg(uint uMsg, nint wParam, nint lParam);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214fc-0000-0000-c000-000000000046")]
|
||||
private interface IContextMenu3
|
||||
{
|
||||
[PreserveSig]
|
||||
int QueryContextMenu(nint hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast, uint uFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int InvokeCommand(ref Cminvokecommandinfo info);
|
||||
|
||||
[PreserveSig]
|
||||
int GetCommandString(UIntPtr idCmd, uint uType, nint reserved, nint name, uint cchMax);
|
||||
|
||||
[PreserveSig]
|
||||
int HandleMenuMsg(uint uMsg, nint wParam, nint lParam);
|
||||
|
||||
[PreserveSig]
|
||||
int HandleMenuMsg2(uint uMsg, nint wParam, nint lParam, out nint result);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct NativeMsg
|
||||
{
|
||||
public nint hwnd;
|
||||
public uint message;
|
||||
public nint wParam;
|
||||
public nint lParam;
|
||||
public uint time;
|
||||
public int ptX;
|
||||
public int ptY;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
private struct Cminvokecommandinfo
|
||||
{
|
||||
public int cbSize;
|
||||
public int fMask;
|
||||
public nint hwnd;
|
||||
public nint lpVerb;
|
||||
public string? lpParameters;
|
||||
public string? lpDirectory;
|
||||
public int nShow;
|
||||
public int dwHotKey;
|
||||
public nint hIcon;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user