From 2a0e78e3cf0c1106a1fb7e470148f6e0b093b2b1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 Jan 2020 02:54:29 -0500 Subject: fixed bad path for alternate origins finished ShellMenuCollection, had to split a bunch of things --- src/envshell.cpp | 499 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 281 insertions(+), 218 deletions(-) (limited to 'src/envshell.cpp') diff --git a/src/envshell.cpp b/src/envshell.cpp index 58948d31..e01bb804 100644 --- a/src/envshell.cpp +++ b/src/envshell.cpp @@ -62,17 +62,12 @@ struct IdlsFreer class WndProcFilter : public QAbstractNativeEventFilter { public: - WndProcFilter(QMainWindow* mw, IContextMenu* cm) - : m_mw(mw), m_cm(cm), m_cm2(nullptr), m_cm3(nullptr) - { - createInterfaces(); - } + using function_type = std::function< + bool (HWND hwnd, UINT m, WPARAM wp, LPARAM lp, LRESULT* out)>; - ~WndProcFilter() + WndProcFilter(function_type f) + : m_f(std::move(f)) { - if (auto* sb=m_mw->statusBar()) { - sb->clearMessage(); - } } bool nativeEventFilter(const QByteArray& type, void* m, long* lresultOut) override @@ -82,194 +77,116 @@ public: return false; } - if (msg->message == WM_MENUSELECT) { - HANDLE_WM_MENUSELECT(msg->hwnd, msg->wParam, msg->lParam, onMenuSelect); - return true; - } - - if (m_cm3) { - LRESULT lresult = 0; + LRESULT lr = 0; - const auto r = m_cm3->HandleMenuMsg2( - msg->message, msg->wParam, msg->lParam, &lresult); + const bool r = m_f(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lr); - if (SUCCEEDED(r)) { - if (lresultOut) { - *lresultOut = lresult; - } - - return true; - } + if (lresultOut) { + *lresultOut = lr; } - if (m_cm2) { - const auto r = m_cm2->HandleMenuMsg( - msg->message, msg->wParam, msg->lParam); - - if (SUCCEEDED(r)) { - if (lresultOut) { - *lresultOut = 0; - } - - return true; - } - } - - return false; + return r; } private: - QMainWindow* m_mw; - IContextMenu* m_cm; - COMPtr m_cm2; - COMPtr m_cm3; - - void createInterfaces() - { - if (!m_cm) { - return; - } - - IContextMenu2* cm2 = nullptr; - if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { - m_cm2.reset(cm2); - } - - IContextMenu3* cm3 = nullptr; - if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { - m_cm3.reset(cm3); - } - } + function_type m_f; +}; - // 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)); - } - } - } +HWND getHWND(QMainWindow* mw) +{ + if (mw) { + return (HWND)mw->winId(); + } else { + return 0; } +} - // 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; - } +// 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; - } + // 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--; + 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'; + // 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); + 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 (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 (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'; + if (pszAnsi) { + pszAnsi[0] = '\0'; - hr = pcm->GetCommandString( - idCmd, uFlags & ~GCS_UNICODE, pwReserved, pszAnsi, cchMax); + 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) && 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; - } + if (SUCCEEDED(hr)) { + if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, pszName, cchMax) == 0) { + hr = E_FAIL; } - - LocalFree(pszAnsi); - - } else { - hr = E_OUTOFMEMORY; } - } - - return hr; - } -}; + LocalFree(pszAnsi); -QMainWindow* getMainWindow(QWidget* w) -{ - QWidget* p = w; - - while (p) { - if (auto* mw=dynamic_cast(p)) { - return mw; + } else { + hr = E_OUTOFMEMORY; } - - p = p->parentWidget(); } - return nullptr; + return hr; } -HWND getHWND(QMainWindow* mw) + +ShellMenu::ShellMenu(QMainWindow* mw) + : m_mw(mw) { - if (mw) { - return (HWND)mw->winId(); - } else { - return 0; - } } - void ShellMenu::addFile(QFileInfo fi) { m_files.emplace_back(std::move(fi)); } -void ShellMenu::exec(QWidget* parent, const QPoint& pos) +void ShellMenu::exec(const QPoint& pos) { - auto* mw = getMainWindow(parent); HMENU menu = getMenu(); if (!menu) { return; @@ -277,12 +194,29 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) try { - const int cmd = runMenu(mw, m_cm.get(), m_menu.get(), pos); + const auto hwnd = getHWND(m_mw); + + auto filter = std::make_unique( + [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { + return wndProc(h, m, wp, lp, out); + }); + + QCoreApplication::instance()->installNativeEventFilter(filter.get()); + + const int cmd = TrackPopupMenuEx( + menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + + if (m_mw) { + if (auto* sb=m_mw->statusBar()) { + sb->clearMessage(); + } + } + if (cmd <= 0) { return; } - invoke(mw, pos, cmd - QCM_FIRST, m_cm.get()); + invoke(pos, cmd - QCM_FIRST); } catch(MenuFailed& e) { @@ -301,13 +235,67 @@ void ShellMenu::exec(QWidget* parent, const QPoint& pos) HMENU ShellMenu::getMenu() { if (!m_menu) { - createMenu(); + create(); } return m_menu.get(); } -void ShellMenu::createMenu() +bool ShellMenu::wndProc(HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) +{ + if (m == WM_MENUSELECT) { + HANDLE_WM_MENUSELECT(h, wp, lp, onMenuSelect); + return true; + } + + if (m_cm3) { + const auto r = m_cm3->HandleMenuMsg2(m, wp, lp, out); + + if (SUCCEEDED(r)) { + return true; + } + } + + if (m_cm2) { + const auto r = m_cm2->HandleMenuMsg(m, wp, lp); + + if (SUCCEEDED(r)) { + if (out) { + *out = 0; + } + + return true; + } + } + + return false; +} + +// adapted from +// https://devblogs.microsoft.com/oldnewthing/20040928-00/?p=37723 +// +void ShellMenu::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.get(), 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)); + } + } + } +} + +void ShellMenu::create() { if (m_files.empty()) { log::warn("showShellMenu(): no files given"); @@ -326,8 +314,9 @@ void ShellMenu::createMenu() IdlsFreer freer(idls); auto array = createItemArray(idls); - m_cm = createContextMenu(array.get()); - m_menu = createMenu(m_cm.get()); + + createContextMenu(array.get()); + createPopupMenu(m_cm.get()); } catch(DummyMenu& dm) { @@ -375,44 +364,6 @@ HMenuPtr ShellMenu::createDummyMenu(const QString& what) } } -COMPtr ShellMenu::createShellItem(const std::wstring& path) -{ - IShellItem* item = nullptr; - - auto r = SHCreateItemFromParsingName( - path.c_str(), nullptr, IID_IShellItem, (void**)&item); - - if (FAILED(r)) { - throw MenuFailed(r, "SHCreateItemFromParsingName failed"); - } - - return COMPtr(item); -} - -COMPtr ShellMenu::getPersistIDList(IShellItem* item) -{ - IPersistIDList* idl = nullptr; - auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); - - if (FAILED(r)) { - throw MenuFailed(r, "QueryInterface IID_IPersistIDList failed"); - } - - return COMPtr(idl); -} - -CoTaskMemPtr ShellMenu::getIDList(IPersistIDList* pidlist) -{ - LPITEMIDLIST absIdl = nullptr; - auto r = pidlist->GetIDList(&absIdl); - - if (FAILED(r)) { - throw MenuFailed(r, "GetIDList failed"); - } - - return CoTaskMemPtr(absIdl); -} - std::vector ShellMenu::createIdls( const std::vector& files) { @@ -455,7 +406,7 @@ COMPtr ShellMenu::createItemArray( return COMPtr(array); } -COMPtr ShellMenu::createContextMenu(IShellItemArray* array) +void ShellMenu::createContextMenu(IShellItemArray* array) { IContextMenu* cm = nullptr; @@ -466,10 +417,24 @@ COMPtr ShellMenu::createContextMenu(IShellItemArray* array) throw MenuFailed(r, "BindToHandler failed"); } - return COMPtr(cm); + m_cm.reset(cm); + + { + IContextMenu2* cm2 = nullptr; + if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu2, (void**)&cm2))) { + m_cm2.reset(cm2); + } + } + + { + IContextMenu3* cm3 = nullptr; + if (SUCCEEDED(m_cm->QueryInterface(IID_IContextMenu3, (void**)&cm3))) { + m_cm3.reset(cm3); + } + } } -HMenuPtr ShellMenu::createMenu(IContextMenu* cm) +void ShellMenu::createPopupMenu(IContextMenu* cm) { HMENU hmenu = CreatePopupMenu(); if (!hmenu) { @@ -484,24 +449,50 @@ HMenuPtr ShellMenu::createMenu(IContextMenu* cm) throw MenuFailed(r, "QueryContextMenu failed"); } - return HMenuPtr(hmenu); + m_menu.reset(hmenu); } -int ShellMenu::runMenu( - QMainWindow* mw, IContextMenu* cm, HMENU menu, const QPoint& p) +COMPtr ShellMenu::createShellItem(const std::wstring& path) { - const auto hwnd = getHWND(mw); + IShellItem* item = nullptr; - auto filter = std::make_unique(mw, cm); - QCoreApplication::instance()->installNativeEventFilter(filter.get()); + auto r = SHCreateItemFromParsingName( + path.c_str(), nullptr, IID_IShellItem, (void**)&item); + + if (FAILED(r)) { + throw MenuFailed(r, "SHCreateItemFromParsingName failed"); + } + + return COMPtr(item); +} + +COMPtr ShellMenu::getPersistIDList(IShellItem* item) +{ + IPersistIDList* idl = nullptr; + auto r = item->QueryInterface(IID_IPersistIDList, (void**)&idl); + + if (FAILED(r)) { + throw MenuFailed(r, "QueryInterface IID_IPersistIDList failed"); + } - return TrackPopupMenuEx(menu, TPM_RETURNCMD, p.x(), p.y(), hwnd, nullptr); + return COMPtr(idl); } -void ShellMenu::invoke( - QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm) +CoTaskMemPtr ShellMenu::getIDList(IPersistIDList* pidlist) { - const auto hwnd = getHWND(mw); + LPITEMIDLIST absIdl = nullptr; + auto r = pidlist->GetIDList(&absIdl); + + if (FAILED(r)) { + throw MenuFailed(r, "GetIDList failed"); + } + + return CoTaskMemPtr(absIdl); +} + +void ShellMenu::invoke(const QPoint& p, int cmd) +{ + const auto hwnd = getHWND(m_mw); CMINVOKECOMMANDINFOEX info = {}; @@ -525,7 +516,7 @@ void ShellMenu::invoke( info.fMask |= CMIC_MASK_CONTROL_DOWN; } - const auto r = cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info); + const auto r = m_cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info); if (FAILED(r)) { throw MenuFailed(r, fmt::format("InvokeCommand failed, verb={}", cmd)); @@ -533,12 +524,17 @@ void ShellMenu::invoke( } +ShellMenuCollection::ShellMenuCollection(QMainWindow* mw) + : m_mw(mw), m_active(nullptr) +{ +} + void ShellMenuCollection::add(QString name, ShellMenu m) { m_menus.push_back({name, std::move(m)}); } -void ShellMenuCollection::exec(QWidget* parent, const QPoint& pos) +void ShellMenuCollection::exec(const QPoint& pos) { HMENU menu = ::CreatePopupMenu(); if (!menu) { @@ -572,10 +568,77 @@ void ShellMenuCollection::exec(QWidget* parent, const QPoint& pos) } } - auto* mw = getMainWindow(parent); - auto hwnd = getHWND(mw); + auto hwnd = getHWND(m_mw); + + auto filter = std::make_unique( + [&](HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) { + return wndProc(h, m, wp, lp, out); + }); + + QCoreApplication::instance()->installNativeEventFilter(filter.get()); + + const int cmd = TrackPopupMenuEx( + menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + + if (m_mw) { + if (auto* sb=m_mw->statusBar()) { + sb->clearMessage(); + } + } + + if (cmd <= 0) { + return; + } + + if (!m_active) { + log::debug("SMC: command {} selected without active submenu"); + return; + } + + const auto realCmd = cmd - QCM_FIRST; + + log::debug("SMC: invoking {} on {}", realCmd, m_active->name); + m_active->menu.invoke(pos, realCmd); +} + +bool ShellMenuCollection::wndProc( + HWND h, UINT m, WPARAM wp, LPARAM lp, LRESULT* out) +{ + if (m == WM_MENUSELECT) { + auto* oldActive = m_active; + m_active = nullptr; - TrackPopupMenuEx(menu, TPM_RETURNCMD, pos.x(), pos.y(), hwnd, nullptr); + HANDLE_WM_MENUSELECT(h, wp, lp, onMenuSelect); + + if (!m_active && oldActive) { + // this was not a top level, forward to active + m_active = oldActive; + } else if (m_active && m_active == oldActive) { + // same top level menu was selected twice, ignore + return true; + } else if (m_active && m_active != oldActive) { + // new top level selected + log::debug("SMC: switching to {}", m_active->name); + } + } + + if (!m_active) { + // no active menu, forward it to the default handler + return false; + } + + return m_active->menu.wndProc(h, m, wp, lp, out); +} + +void ShellMenuCollection::onMenuSelect( + HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) +{ + for (auto&& m : m_menus) { + if (m.menu.getMenu() == hmenuPopup) { + m_active = &m; + break; + } + } } } // namespace -- cgit v1.3.1