summaryrefslogtreecommitdiff
path: root/src/envshell.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/envshell.cpp')
-rw-r--r--src/envshell.cpp149
1 files changed, 138 insertions, 11 deletions
diff --git a/src/envshell.cpp b/src/envshell.cpp
index f7033fba..ca392a9c 100644
--- a/src/envshell.cpp
+++ b/src/envshell.cpp
@@ -2,6 +2,7 @@
#include "env.h"
#include <log.h>
#include <utility.h>
+#include <windowsx.h>
namespace env
{
@@ -26,8 +27,8 @@ public:
class WndProcFilter : public QAbstractNativeEventFilter
{
public:
- WndProcFilter(IContextMenu* cm)
- : m_cm2(nullptr), m_cm3(nullptr)
+ WndProcFilter(QMainWindow* mw, IContextMenu* cm)
+ : m_mw(mw), m_cm(cm), m_cm2(nullptr), m_cm3(nullptr)
{
IContextMenu2* cm2 = nullptr;
if (SUCCEEDED(cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) {
@@ -40,10 +41,23 @@ public:
}
}
+ ~WndProcFilter()
+ {
+ if (auto* sb=m_mw->statusBar()) {
+ sb->clearMessage();
+ }
+ }
+
bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override
{
+ MSG* msg = (MSG*)m;
+
+ if (msg->message == WM_MENUSELECT) {
+ HANDLE_WM_MENUSELECT(msg->hwnd, msg->wParam, msg->lParam, onMenuSelect);
+ return true;
+ }
+
if (m_cm3) {
- MSG* msg = (MSG*)m;
LRESULT lresult = 0;
const auto r = m_cm3->HandleMenuMsg2(
@@ -59,8 +73,6 @@ public:
}
if (m_cm2) {
- MSG* msg = (MSG*)m;
-
const auto r = m_cm2->HandleMenuMsg(
msg->message, msg->wParam, msg->lParam);
@@ -77,8 +89,104 @@ public:
}
private:
+ QMainWindow* m_mw;
+ IContextMenu* m_cm;
COMPtr<IContextMenu2> m_cm2;
COMPtr<IContextMenu3> m_cm3;
+
+ // adapted from
+ // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723
+ //
+ void onMenuSelect(
+ HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags)
+ {
+ if (m_cm && item >= QCM_FIRST && item <= QCM_LAST) {
+ WCHAR szBuf[MAX_PATH];
+
+ const auto r = IContextMenu_GetCommandString(
+ m_cm, item - QCM_FIRST, GCS_HELPTEXTW, NULL, szBuf, MAX_PATH);
+
+ if (FAILED(r)) {
+ lstrcpynW(szBuf, L"No help available.", MAX_PATH);
+ }
+
+ if (m_mw) {
+ if (auto* sb=m_mw->statusBar()) {
+ sb->showMessage(QString::fromWCharArray(szBuf));
+ }
+ }
+ }
+ }
+
+ // adapted from
+ // https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723
+ //
+ HRESULT IContextMenu_GetCommandString(
+ IContextMenu *pcm, UINT_PTR idCmd, UINT uFlags,
+ UINT *pwReserved, LPWSTR pszName, UINT cchMax)
+ {
+ // Callers are expected to be using Unicode.
+ if (!(uFlags & GCS_UNICODE)) {
+ return E_INVALIDARG;
+ }
+
+ // Some context menu handlers have off-by-one bugs and will
+ // overflow the output buffer. Let’s artificially reduce the
+ // buffer size so a one-character overflow won’t corrupt memory.
+ if (cchMax <= 1) {
+ return E_FAIL;
+ }
+
+ cchMax--;
+
+ // First try the Unicode message. Preset the output buffer
+ // with a known value because some handlers return S_OK without
+ // doing anything.
+ pszName[0] = L'\0';
+
+ HRESULT hr = pcm->GetCommandString(
+ idCmd, uFlags, pwReserved, (LPSTR)pszName, cchMax);
+
+ if (SUCCEEDED(hr) && pszName[0] == L'\0') {
+ // Rats, a buggy IContextMenu handler that returned success
+ // even though it failed.
+ hr = E_NOTIMPL;
+ }
+
+ if (FAILED(hr)) {
+ // try again with ANSI – pad the buffer with one extra character
+ // to compensate for context menu handlers that overflow by
+ // one character.
+ LPSTR pszAnsi = (LPSTR)LocalAlloc(
+ LMEM_FIXED, (cchMax + 1) * sizeof(CHAR));
+
+ if (pszAnsi) {
+ pszAnsi[0] = '\0';
+
+ hr = pcm->GetCommandString(
+ idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, cchMax);
+
+ if (SUCCEEDED(hr) && pszAnsi[0] == '\0') {
+ // Rats, a buggy IContextMenu handler that returned success
+ // even though it failed.
+ hr = E_NOTIMPL;
+ }
+
+ if (SUCCEEDED(hr)) {
+ if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, pszName, cchMax) == 0) {
+ hr = E_FAIL;
+ }
+ }
+
+ LocalFree(pszAnsi);
+
+ } else {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+
+ return hr;
+ }
};
@@ -145,16 +253,20 @@ HMenuPtr createMenu(IContextMenu* cm)
return HMenuPtr(hmenu);
}
-int runMenu(IContextMenu* cm, HWND hwnd, HMENU menu, const QPoint& p)
+int runMenu(QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p)
{
- auto filter = std::make_unique<WndProcFilter>(cm);
+ const auto hwnd = (HWND)mw->winId();
+
+ auto filter = std::make_unique<WndProcFilter>(mw, cm);
QCoreApplication::instance()->installNativeEventFilter(filter.get());
return TrackPopupMenuEx(menu, TPM_RETURNCMD, p.x(), p.y(), hwnd, nullptr);
}
-void invoke(HWND hwnd, const QPoint& p, int cmd, IContextMenu* cm)
+void invoke(QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm)
{
+ const auto hwnd = (HWND)mw->winId();
+
CMINVOKECOMMANDINFOEX info = {};
info.cbSize = sizeof(info);
@@ -184,24 +296,39 @@ void invoke(HWND hwnd, const QPoint& p, int cmd, IContextMenu* cm)
}
}
+QMainWindow* getMainWindow(QWidget* w)
+{
+ QWidget* p = w;
+
+ while (p) {
+ if (auto* mw=dynamic_cast<QMainWindow*>(p)) {
+ return mw;
+ }
+
+ p = p->parentWidget();
+ }
+
+ return nullptr;
+}
+
void showShellMenu(QWidget* parent, const QFileInfo& file, const QPoint& pos)
{
const auto path = QDir::toNativeSeparators(file.absoluteFilePath());
try
{
+ auto* mw = getMainWindow(parent);
auto idl = getIDL(path.toStdWString().c_str());
auto [sf, childIdl] = getShellFolder(idl.get());
auto cm = getContextMenu(sf.get(), childIdl);
auto hmenu = createMenu(cm.get());
- auto hwnd = (HWND)parent->window()->winId();
- const int cmd = runMenu(cm.get(), hwnd, hmenu.get(), pos);
+ const int cmd = runMenu(mw, cm.get(), hmenu.get(), pos);
if (cmd <= 0) {
return;
}
- invoke(hwnd, pos, cmd - QCM_FIRST, cm.get());
+ invoke(mw, pos, cmd - QCM_FIRST, cm.get());
}
catch(MenuFailed& e)
{