Files icon indicating copy to clipboard operation
Files copied to clipboard

Feature: Add support for Windows 11 new context menu items

Open WereWolfix opened this issue 3 years ago • 9 comments

Description

I just noticed that if I right click on a folder to compress it, winrar doen't appear as option in the context menu. The only way to compress a folder is to create a winrar archive and that putt the folder.

Steps To Reproduce

  1. Install the latest version of WinRar
  2. Right click an item in Files and see that WinRar doesn't show up

Expected behavior

  • Display WinRar and other options using the new shell apis

Files Version

3.1.7.0

Windows Version

Windows 11 Insider Preview 22543.1000 (rs_prerelease)

Relevant Assets/Logs

image

WereWolfix avatar Feb 09 '22 12:02 WereWolfix

Thanks for the report. Could you also add:

  • A screenshot of explorer's context menu for the same file
  • A screenshot of explorer's "legacy" context menu (the one that appears e.g clicking Shift+F10)
  • WinRar version

Please also verify if pressing shift while right clicking makes WinRar appear on Files.

gave92 avatar Feb 09 '22 19:02 gave92

Thanks for the report. Could you also add:

  • A screenshot of explorer's context menu for the same file
  • A screenshot of explorer's "legacy" context menu (the one that appears e.g clicking Shift+F10)
  • WinRar version

Please also verify if pressing shift while right clicking makes WinRar appear on Files.

image image image

WereWolfix avatar Feb 10 '22 10:02 WereWolfix

OK that's why. I see Winrar is missing from the legacy context menu and Files does not yet support pulling items from windows 11 menu. Installing Winrar 5 could be a workaround.

gave92 avatar Feb 10 '22 17:02 gave92

Ah ok. Thank you!

WereWolfix avatar Feb 11 '22 08:02 WereWolfix

@dahall sorry to bother but in your infinite Win32 knowledge you may know this :)

Is there a way to query the new Windows 11 context menu and enumerate its items?

This is what we're using to get the context menu, but it returns only the "Windows 10" / legacy context menu items.

using var sf = new ShellFolder(@"SOME_FOLDER");
var menu = sf.GetViewObject<Shell32.IContextMenu>(null);
var hMenu = User32.CreatePopupMenu();
menu.QueryContextMenu(hMenu, ...)
...

I was led to believe that Windows 11 menu items implement IExplorerCommand so I was trying to use IExplorerCommandProvider to enumerate them, but I can't figure out how to get that interface.

using var sf = new ShellFolder(@"SOME_FOLDER");
var ecp = sf.GetViewObject<Shell32.IExplorerCommandProvider>(null); // This returns E_NOINTERFACE

gave92 avatar Feb 11 '22 22:02 gave92

I have seen the Windows sample project that successfully gets IExplorerCommandProvider by calling an IShellFolder::CreateViewObject method with it's GUID. Maybe the IShellFolder implementation for that shell item type doesn't support it. You've likely already read this documentation. Have you tried to cast your IContextMenu instance to IExplorerCommand? That's all I can think of.

dahall avatar Feb 12 '22 18:02 dahall

Can I work on this? I guess I have proof of concept.

Items that implement IExplorerCommand and that are published by apps that have Package ID are shown in the Windows 11 context menu, while items that implement IContextMenu are show in its overflow. I guess we can get a provider instance with its object interface id and retrieve a sequence of IExplorerCommand instances.

Sample 1
#define WIN32_LEAN_AND_MEAN
#include <SDKDDKVer.h>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <shobjidl_core.h>
#include <shlobj_core.h>

ATOM RegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int nCmdShow)
{
	HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
	if (FAILED(hr))
		return FALSE;

	RegisterClass(hInstance);
	if (!InitInstance(hInstance, nCmdShow))
		return FALSE;

	MSG msg;
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	CoUninitialize();
	return (int)msg.wParam;
}

ATOM RegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEXW wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = NULL;
	wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = L"DEBUG";
	wcex.hIconSm = NULL;
	return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HWND hWnd = CreateWindowW(L"DEBUG", L"Debug", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
	if (!hWnd)
		return FALSE;

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
	{
		SetWindowText(hWnd, L"Click somewhere");
	}
	break;

	case WM_LBUTTONDOWN:
	{
		CComPtr<IShellItem> item;
		// TODO: make sure this is a real file (of any type)
		SHCreateItemFromParsingName(L"C:\\Users\\onein\\Downloads\\Catppuccin_for_Visual_Studio.vsix", NULL, IID_PPV_ARGS(&item));

		DEFCONTEXTMENU cm = { 0 };
		cm.hwnd = hWnd;

		CComHeapPtr<ITEMIDLIST> pidl;
		CComPtr<IShellFolder> folder;
		CComQIPtr<IParentAndItem>(item)->GetParentAndItem(nullptr, &folder, &pidl);
		cm.psf = folder;

		LPCITEMIDLIST pidls[] = { pidl };
		cm.cidl = ARRAYSIZE(pidls);
		cm.apidl = pidls;

		CComPtr<IContextMenu> menu;
		SHCreateDefaultContextMenu(&cm, IID_PPV_ARGS(&menu));

		HMENU hMenu = CreatePopupMenu();

		// we can play with CMF verbs to vary what we'll get
		menu->QueryContextMenu(hMenu, 0, 0, SHRT_MAX, CMF_EXPLORE | CMF_CANRENAME);
		RECT Rect;
		GetWindowRect(hWnd, &Rect);
		MapWindowPoints(HWND_DESKTOP, GetParent(hWnd), (LPPOINT)&Rect, 2);
		USHORT id = TrackPopupMenu(hMenu, TPM_RETURNCMD, Rect.left + 50, Rect.top + 50, 0, hWnd, NULL);

		CMINVOKECOMMANDINFO info = { 0 };
		info.cbSize = sizeof(CMINVOKECOMMANDINFO);
		info.hwnd = hWnd;
		info.nShow = SW_SHOWNORMAL;
		info.lpVerb = MAKEINTRESOURCEA(id);
		menu->InvokeCommand(&info);

		DestroyMenu(hMenu);
	}
	break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

0x5bfa avatar May 20 '24 12:05 0x5bfa

@0x5bfa please!!! For performance reasons, I think we should continue displaying all shell extensions in the sub menu (for the time being).

yaira2 avatar May 20 '24 14:05 yaira2

My idea didn’t work, it was not proof of concept.

  • [x] ~~Instance IExplorerCommandProvider via CoCreateInstance~~
    • I find that IExplorerCommandProvider cannot be obtained from COM activator or CoCreateInstance because it must be in-process activation, which mean’s IExplorerCommandProvider has to share the implementation with the menu.
  • [ ] Use IFolderTypeDescription interface
  • [x] ~~Cast IContextMenuItem to IExplorerCommand, or find dedicated info from old context menu.~~
    • This requires to load shell context menu first.

0x5bfa avatar May 21 '24 06:05 0x5bfa