summaryrefslogtreecommitdiff
path: root/src/envshell.cpp
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2020-01-22 02:54:29 -0500
committerisanae <14251494+isanae@users.noreply.github.com>2020-02-04 03:33:22 -0500
commit2a0e78e3cf0c1106a1fb7e470148f6e0b093b2b1 (patch)
treeaa0ce69d111a22d232dc1589fa707855bddc53b0 /src/envshell.cpp
parent6005da618775d545c664371f53571a75eacab7f2 (diff)
fixed bad path for alternate origins
finished ShellMenuCollection, had to split a bunch of things
Diffstat (limited to 'src/envshell.cpp')
-rw-r--r--src/envshell.cpp499
1 files changed, 281 insertions, 218 deletions
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<IContextMenu2> m_cm2;
- COMPtr<IContextMenu3> 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<QMainWindow*>(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<WndProcFilter>(
+ [&](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<IShellItem> 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<IShellItem>(item);
-}
-
-COMPtr<IPersistIDList> 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<IPersistIDList>(idl);
-}
-
-CoTaskMemPtr<LPITEMIDLIST> ShellMenu::getIDList(IPersistIDList* pidlist)
-{
- LPITEMIDLIST absIdl = nullptr;
- auto r = pidlist->GetIDList(&absIdl);
-
- if (FAILED(r)) {
- throw MenuFailed(r, "GetIDList failed");
- }
-
- return CoTaskMemPtr<LPITEMIDLIST>(absIdl);
-}
-
std::vector<LPCITEMIDLIST> ShellMenu::createIdls(
const std::vector<QFileInfo>& files)
{
@@ -455,7 +406,7 @@ COMPtr<IShellItemArray> ShellMenu::createItemArray(
return COMPtr<IShellItemArray>(array);
}
-COMPtr<IContextMenu> ShellMenu::createContextMenu(IShellItemArray* array)
+void ShellMenu::createContextMenu(IShellItemArray* array)
{
IContextMenu* cm = nullptr;
@@ -466,10 +417,24 @@ COMPtr<IContextMenu> ShellMenu::createContextMenu(IShellItemArray* array)
throw MenuFailed(r, "BindToHandler failed");
}
- return COMPtr<IContextMenu>(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<IShellItem> ShellMenu::createShellItem(const std::wstring& path)
{
- const auto hwnd = getHWND(mw);
+ IShellItem* item = nullptr;
- auto filter = std::make_unique<WndProcFilter>(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<IShellItem>(item);
+}
+
+COMPtr<IPersistIDList> 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<IPersistIDList>(idl);
}
-void ShellMenu::invoke(
- QMainWindow* mw, const QPoint& p, int cmd, IContextMenu* cm)
+CoTaskMemPtr<LPITEMIDLIST> 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<LPITEMIDLIST>(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<WndProcFilter>(
+ [&](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