703 lines
22 KiB
C#
703 lines
22 KiB
C#
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;
|
|
}
|
|
}
|