diff --git a/src/dll/Shell.vcxproj b/src/dll/Shell.vcxproj index 76e5293..fce39bb 100644 --- a/src/dll/Shell.vcxproj +++ b/src/dll/Shell.vcxproj @@ -168,6 +168,8 @@ + + @@ -185,6 +187,8 @@ + + diff --git a/src/dll/src/ClassFactory.cpp b/src/dll/src/ClassFactory.cpp new file mode 100644 index 0000000..c01a34c --- /dev/null +++ b/src/dll/src/ClassFactory.cpp @@ -0,0 +1,109 @@ +/****************************** Module Header ******************************\ +Module Name: ClassFactory.cpp +Project: CppShellExtContextMenuHandler +Copyright (c) Microsoft Corporation. + +The file implements the class factory for the FileContextMenuExt COM class. + +This source is subject to the Microsoft Public License. +See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. +All other rights reserved. + +THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +\***************************************************************************/ + +#include +#include "Include/ClassFactory.h" +#include "Include/ShellExtSelectionRetriever.h" +#include +#include +#pragma comment(lib, "shlwapi.lib") + +extern Logger &_log; + +extern long g_cDllRef; + + +ClassFactory::ClassFactory() : m_cRef(1) +{ + InterlockedIncrement(&g_cDllRef); +} + +ClassFactory::~ClassFactory() +{ + InterlockedDecrement(&g_cDllRef); +} + + +// +// IUnknown +// + +IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void **ppv) +{ + __trace(L"In ClassFactory::QueryInterface"); + static const QITAB qit[] = + { + QITABENT(ClassFactory, IClassFactory), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + + +// +// IClassFactory +// + +IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + __trace(L"In ClassFactory::CreateInstance"); + HRESULT hr = CLASS_E_NOAGGREGATION; + + // pUnkOuter is used for aggregation. We do not support it in the sample. + if (pUnkOuter == NULL) + { + hr = E_OUTOFMEMORY; + + // Create the COM component. + ShellExtSelectionRetriever *pExt = new (std::nothrow) ShellExtSelectionRetriever(); + if (pExt) + { + // Query the specified interface. + hr = pExt->QueryInterface(riid, ppv); + pExt->Release(); + } + } + + return hr; +} + +IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock) +{ + if (fLock) + { + InterlockedIncrement(&g_cDllRef); + } + else + { + InterlockedDecrement(&g_cDllRef); + } + return S_OK; +} \ No newline at end of file diff --git a/src/dll/src/ContextMenu.cpp b/src/dll/src/ContextMenu.cpp index e7239da..a608573 100644 --- a/src/dll/src/ContextMenu.cpp +++ b/src/dll/src/ContextMenu.cpp @@ -2,6 +2,7 @@ #include "Include/Theme.h" #include "Include/ContextMenu.h" #include "Include/stb_image_write.h" +#include "Include/ShellExtSelectionRetriever.h" using namespace Nilesoft::Diagnostics; #include @@ -133,7 +134,7 @@ struct DWM inline HMENU GET_HMENU(HWND hWnd) { return SendMSG(hWnd, MN_GETHMENU, 0, 0); } -auto ver = &Windows::Version::Instance(); +auto ver = &Nilesoft::Windows::Version::Instance(); #pragma endregion @@ -4211,9 +4212,12 @@ namespace Nilesoft } } - void ContextMenu::build_system_menuitems(HMENU hMenu, menuitem_t *menu, bool is_root) + void ContextMenu::build_system_menuitems(HMENU hMenu, menuitem_t *menu, bool is_root, bool skip_init) { - ::SendMessageW(hwnd.owner, WM_INITMENUPOPUP, reinterpret_cast(hMenu), 0xFFFFFFFF); + if (!skip_init) + { + ::SendMessageW(hwnd.owner, WM_INITMENUPOPUP, reinterpret_cast(hMenu), 0xFFFFFFFF); + } auto itmes_count = ::GetMenuItemCount(hMenu); @@ -4267,6 +4271,12 @@ namespace Nilesoft if(mii.cch > 0) { item->title = title.release(mii.cch).move(); + if (item->title.equals(shell_ext_selection_retriever_placeholder, false)) + { + // skip placeholder + continue; + } + item->hash = MenuItemInfo::normalize(item->title, &item->name, &item->tab, &item->length, &item->keys); item->ui = Initializer::get_muid(item->hash); @@ -4336,7 +4346,31 @@ namespace Nilesoft } } - bool ContextMenu::Initialize() + bool ContextMenu::check_shell_ext_selection_retriever_placeholder(HMENU hMenu) + { + auto itmes_count = ::GetMenuItemCount(hMenu); + + for(int i = 0; i < itmes_count; i++) + { + string title; + MENUITEMINFOW mii{ sizeof(MENUITEMINFOW) }; + mii.fMask = MenuItemInfo::FMASK; + mii.dwTypeData = title.buffer((1024)); + mii.cch = 1024; + if(::GetMenuItemInfoW(hMenu, i, true, &mii)) + { + title.release(mii.cch); + // __trace(L"In check_shell_ext_selection_retriever_placeholder: %ls", title.c_str()); + if (title.equals(shell_ext_selection_retriever_placeholder, false)) + { + return true; + } + } + } + return false; + } + + bool ContextMenu::Initialize(bool is_exclusion_test) { try { @@ -4353,9 +4387,11 @@ namespace Nilesoft Selected.Window.handle = hwnd.owner; Selected.Window.hInstance = _window.instance(); - if(!Selected.QueryShellWindow()) + bool has_shell_window = Selected.QueryShellWindow(); + + if(!has_shell_window) { - __trace(L"QueryShellWindow"); + __trace(L"No Shell window"); return false; } @@ -4390,6 +4426,13 @@ namespace Nilesoft _context.variables.runtime = &_cache->variables.runtime; _context.variables.local = nullptr; + auto prop = ::GetPropW(hwnd.owner, UxSubclass); + + // initialize popup menu to check whether to use ShellExtSelectionRetriever + if(0 == prop) { + ::SendMessageW(hwnd.owner, WM_INITMENUPOPUP, reinterpret_cast(_hMenu_original), 0xFFFFFFFF); + } + switch(Selected.Window.id) { case WINDOW_TASKBAR: @@ -4398,7 +4441,12 @@ namespace Nilesoft Selected.Directory = Path::GetKnownFolder(FOLDERID_Desktop).move(); break; default: - if(!Selected.QuerySelected()) + bool use_shell_ext_selection_retriever = check_shell_ext_selection_retriever_placeholder(_hMenu_original); + if (use_shell_ext_selection_retriever) + { + __trace(L"Menu has ShellExtSelectionRetriever placeholder"); + } + if(!Selected.QuerySelected(use_shell_ext_selection_retriever)) { __trace(L"QuerySelected"); //return false; @@ -4421,9 +4469,16 @@ namespace Nilesoft __trace(L"Selected.Preparing"); return false; } - + + if (is_exclusion_test) + { + return false; + } if(is_excluded()) + { + __trace(L"Excluded"); return false; + } hInstance = _window.instance(); @@ -4467,8 +4522,8 @@ namespace Nilesoft __system_menu_tree->type = 10; __map_system_menu[0] = __system_menu_tree; - if(0 == ::GetPropW(hwnd.owner, UxSubclass)) - build_system_menuitems(_hMenu_original, __system_menu_tree, true); + if(0 == prop) + build_system_menuitems(_hMenu_original, __system_menu_tree, true, true); if(_settings.modify_items.enabled) build_main_system_menuitems(__system_menu_tree, true); diff --git a/src/dll/src/Include/ClassFactory.h b/src/dll/src/Include/ClassFactory.h new file mode 100644 index 0000000..0f5ee54 --- /dev/null +++ b/src/dll/src/Include/ClassFactory.h @@ -0,0 +1,42 @@ +/****************************** Module Header ******************************\ +Module Name: ClassFactory.h +Project: CppShellExtContextMenuHandler +Copyright (c) Microsoft Corporation. + +The file declares the class factory for the FileContextMenuExt COM class. + +This source is subject to the Microsoft Public License. +See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL. +All other rights reserved. + +THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +\***************************************************************************/ + +#pragma once + +#include // For IClassFactory +#include + + +class ClassFactory : public IClassFactory +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IClassFactory + IFACEMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv); + IFACEMETHODIMP LockServer(BOOL fLock); + + ClassFactory(); + +protected: + ~ClassFactory(); + +private: + long m_cRef; +}; \ No newline at end of file diff --git a/src/dll/src/Include/ContextMenu.h b/src/dll/src/Include/ContextMenu.h index 3a3743b..e7e0593 100644 --- a/src/dll/src/Include/ContextMenu.h +++ b/src/dll/src/Include/ContextMenu.h @@ -730,11 +730,12 @@ plutovg_move_to(pluto, start.x, start.y); uint32_t invoke(CommandProperty *cmd_prop); bool is_excluded(); - bool Initialize(); + bool Initialize(bool is_exclusion_test = false); int Uninitialize(); int InvokeCommand(int id); - void build_system_menuitems(HMENU hMenu, menuitem_t *menu, bool is_root = false); + void build_system_menuitems(HMENU hMenu, menuitem_t *menu, bool is_root = false, bool skip_init = false); void build_main_system_menuitems(menuitem_t *menu, bool is_root = false); + bool check_shell_ext_selection_retriever_placeholder(HMENU hMenu); void backup_native_items(HMENU hMenu, uint32_t id, bool check = false); diff --git a/src/dll/src/Include/Selections.h b/src/dll/src/Include/Selections.h index db93784..b2b2f32 100644 --- a/src/dll/src/Include/Selections.h +++ b/src/dll/src/Include/Selections.h @@ -16,10 +16,10 @@ namespace Nilesoft //::{031E4825-7B94-4DC3-B131-E946B44C8DD5}\CameraRoll.library-ms /* - wnd cls=‘ShellTabWindowClass’ title=‘title*’ - wnd aaname=‘Explorer Pane’ cls=‘DirectUIHWND’ - wnd aaname=‘Shell Folder View’ cls=‘SHELLDLL_DefView’ title=‘ShellView’ - wnd aaname=‘Items View’ cls=‘DirectUIHWND’ + wnd cls=�ShellTabWindowClass� title=�title*� + wnd aaname=�Explorer Pane� cls=�DirectUIHWND� + wnd aaname=�Shell Folder View� cls=�SHELLDLL_DefView� title=�ShellView� + wnd aaname=�Items View� cls=�DirectUIHWND� */ //Windows Explorer on XP, Vista, 7, 8 @@ -320,7 +320,9 @@ namespace Nilesoft void QuerySelectionMode(); bool QueryShellWindow(); - bool QuerySelected(); + bool QuerySelectedWithShellBrowser(); + bool QuerySelectedWithShellExtSelectionRetriever(); + bool QuerySelected(bool use_shell_ext_selection_retriever); bool Preparing(); bool Parse(IShellItem *shellItem); bool Parse(FileProperties *prop); diff --git a/src/dll/src/Include/ShellExtSelectionRetriever.h b/src/dll/src/Include/ShellExtSelectionRetriever.h new file mode 100644 index 0000000..87dd5d0 --- /dev/null +++ b/src/dll/src/Include/ShellExtSelectionRetriever.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +using Microsoft::WRL::ComPtr; + +// Global structure to share data with existing Parse logic +struct GlobalSelectionContext { + bool IsBackground = false; + ComPtr ParentFolder; + ComPtr SelectionArray; +}; + +extern GlobalSelectionContext g_ShellContext; +extern const wchar_t shell_ext_selection_retriever_placeholder[]; + +class ShellExtSelectionRetriever : public IShellExtInit, public IContextMenu +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IShellExtInit + IFACEMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID); + + // IContextMenu + IFACEMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); + IFACEMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici); + IFACEMETHODIMP GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *pwReserved, LPSTR pszName, UINT cchMax); + + ShellExtSelectionRetriever(void); + +protected: + ~ShellExtSelectionRetriever(void); + +private: + // Reference count of component. + long m_cRef; +}; diff --git a/src/dll/src/Main.cpp b/src/dll/src/Main.cpp index 0c56bda..f37c379 100644 --- a/src/dll/src/Main.cpp +++ b/src/dll/src/Main.cpp @@ -3,6 +3,7 @@ #include "Library/detours.h" #include "RegistryConfig.h" #include +#include "Include/ClassFactory.h" using namespace Nilesoft::Shell; using namespace Diagnostics; @@ -158,6 +159,7 @@ std::unordered_map< uint32_t, const wchar_t *> msg_map0 = { #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) // windowsx.h HINSTANCE _hInstance{}; +long g_cDllRef = 0; Initializer _initializer; extern Logger &_log = Logger::Instance(); const Windows::Version *ver = &Windows::Version::Instance(); @@ -1140,6 +1142,19 @@ BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID) return FALSE; } +HRESULT make_class_factory_query_interface(REFIID riid, LPVOID FAR *ppv) +{ + __trace(L"In make_class_factory_query_interface"); + HRESULT hr = E_OUTOFMEMORY; + ClassFactory *pClassFactory = new ClassFactory(); + if (pClassFactory) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + return hr; +} + //IID_FolderExtensions //#pragma comment(linker, "/export:DllGetClassObject=DllGetClassObject") _Check_return_ @@ -1171,6 +1186,8 @@ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, [[maybe_unused]] _In_ REFIID riid { _loader.contextmenuhandler = true; Selections::point.GetCursorPos(); + // make class for retrieving selected objects in third-party file explorers + hr = make_class_factory_query_interface(riid, ppv); } if(!_initializer.Status.Loaded) @@ -1196,6 +1213,9 @@ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, [[maybe_unused]] _In_ REFIID riid __control_entrypoint(DllExport) STDAPI DllCanUnloadNow(void) { + if (g_cDllRef > 0) { + return S_FALSE; + } if(!_loader.explorer || !is_registered()) return S_OK; return S_FALSE; diff --git a/src/dll/src/Selections.cpp b/src/dll/src/Selections.cpp index 5dd1e11..36974ea 100644 --- a/src/dll/src/Selections.cpp +++ b/src/dll/src/Selections.cpp @@ -1,4 +1,5 @@ #include +#include "Include/ShellExtSelectionRetriever.h" //Enable Narrow Classic Context Menu on Windows 10 // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\FlightedFeatures\ImmersiveContextMenu:0 @@ -894,7 +895,7 @@ namespace Nilesoft return result; } - bool Selections::QuerySelected() + bool Selections::QuerySelectedWithShellBrowser() { try { @@ -1008,11 +1009,12 @@ namespace Nilesoft // ShellBrowser->AddRef(); - // Logger::Info(L"%x %s %x", current_window, Window::class_name(current_window).c_str(), sb); + // Logger::Info(L"In QuerySelected: current_window=%x class_name=%s", current_window, Window::class_name(current_window).c_str()); if(ShellBrowser == nullptr) return false; + if(Window.id == WINDOW_EXPLORER) { IComPtr sv; @@ -1036,7 +1038,7 @@ namespace Nilesoft Selections::GetFileProperties(si, &folderProp); IComPtr sia; - if(S_OK == fv->GetSelection(FALSE, sia)) + if(S_OK == fv->GetSelection(FALSE, sia)) // Selected are foreground objects { DWORD sel_count = 0; if(S_OK != sia->GetCount(&sel_count)) @@ -1176,6 +1178,101 @@ namespace Nilesoft return false; } + bool Selections::QuerySelectedWithShellExtSelectionRetriever() + { + __trace(L"In QuerySelectedWithShellExtSelectionRetriever"); + FileProperties folderProp; + if (!Selections::GetFileProperties(g_ShellContext.ParentFolder.Get(), &folderProp)) + { + return false; + } + + Window.id = WINDOW_EXPLORER; + Window.explorer = true; + if (!g_ShellContext.IsBackground) + { + __trace(L"In QuerySelectedWithShellExtSelectionRetriever: is foreground"); + ComPtr sia = g_ShellContext.SelectionArray; + DWORD sel_count = 0; + if(S_OK != sia->GetCount(&sel_count)) + return false; + __trace(L"In QuerySelected: get selection count = %d", sel_count); + + Items.reserve(sel_count); + + for(DWORD i = 0; i < sel_count; i++) + { + __trace(L"Parse selection item #%d", i); + IShellItem* item; + if(S_OK == sia->GetItemAt(i, &item) && item) + Parse(item); + } + + Parent = folderProp.Path; + ParentRaw = folderProp.PathRaw; + + return !Items.empty(); + } + + if(folderProp.Folder) // has background folder + { + __trace(L"In QuerySelected: folder is Folder type"); + if(folderProp.FileSystem || folderProp.FileSysAnceStor) + folderProp.Background = TRUE; + else + folderProp.Background = folderProp.DropTarget; + + if(!folderProp.Background) + { + auto h = folderProp.PathRaw.hash(); + if(h == GUID_HOME or h == GUID_QUICK_ACCESS or h == GUID_LIBRARIES) + { + + Window.id = (h == GUID_HOME) ? WINDOW_HOME : (h == GUID_QUICK_ACCESS) ? WINDOW_QUICK_ACCESS : WINDOW_LIBRARIES; + folderProp.Background = TRUE; + } + } + __trace(L"In QuerySelected: folder is background? %d", folderProp.Background); + } + + IComPtr sip; + if(S_OK == g_ShellContext.ParentFolder.Get()->GetParent(sip)) + { + FileProperties fp_parent; + if(Selections::GetFileProperties(sip, &fp_parent)) + { + //if(fpParent.IsDir) + Parent = fp_parent.Path.move(); + ParentRaw = fp_parent.PathRaw.move(); + } + } + + if(folderProp.Background) + { + __trace(L"In QuerySelected: Selected is background"); + this->Background = true; + this->Parse(&folderProp); + //return true; + } + return true; + } + + bool Selections::QuerySelected(bool use_shell_ext_selection_retriever) + { + if (QuerySelectedWithShellBrowser()) + { + return true; + } + if (use_shell_ext_selection_retriever) + { + return QuerySelectedWithShellExtSelectionRetriever(); + } + else + { + return false; + } + } + /* TrayShowDesktopButtonWClass NotificationsMenuOwner // Notification Button diff --git a/src/dll/src/ShellExtSelectionRetriever.cpp b/src/dll/src/ShellExtSelectionRetriever.cpp new file mode 100644 index 0000000..b051fad --- /dev/null +++ b/src/dll/src/ShellExtSelectionRetriever.cpp @@ -0,0 +1,232 @@ +#include +#include "Include/ShellExtSelectionRetriever.h" +#include "Include/ContextMenu.h" +#include +#include +#pragma comment(lib, "shlwapi.lib") +#include +#include + +using Microsoft::WRL::ComPtr; + +extern Logger &_log; + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +GlobalSelectionContext g_ShellContext; + +const wchar_t shell_ext_selection_retriever_placeholder[] = L"ShellExtSelectionRetriever Placeholder"; + +HRESULT GetThisPCItem(ComPtr& spItem) { + return SHCreateItemFromParsingName(L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", + NULL, IID_PPV_ARGS(&spItem)); +} + +ShellExtSelectionRetriever::ShellExtSelectionRetriever(void) + : m_cRef(1){ + InterlockedIncrement(&g_cDllRef); +} + +ShellExtSelectionRetriever::~ShellExtSelectionRetriever(void) +{ + + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +// Query to the interface the component supported. +IFACEMETHODIMP ShellExtSelectionRetriever::QueryInterface(REFIID riid, void **ppv) +{ + __trace(L"In ShellExtSelectionRetriever::QueryInterface"); + static const QITAB qit[] = + { + QITABENT(ShellExtSelectionRetriever, IContextMenu), + QITABENT(ShellExtSelectionRetriever, IShellExtInit), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +// Increase the reference count for an interface on an object. +IFACEMETHODIMP_(ULONG) ShellExtSelectionRetriever::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +// Decrease the reference count for an interface on an object. +IFACEMETHODIMP_(ULONG) ShellExtSelectionRetriever::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + + return cRef; +} + +#pragma endregion + +#pragma region IShellExtInit +IFACEMETHODIMP ShellExtSelectionRetriever::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, [[maybe_unused]] HKEY hKeyProgID) +{ + // Reset global context for the current operation + g_ShellContext = GlobalSelectionContext(); + + // 1. Check for Background Context (Right-click on folder background) + if (pDataObj == NULL && pidlFolder != NULL) + { + g_ShellContext.IsBackground = true; + __trace(L"[ShellExt] Context: BACKGROUND"); + + ComPtr spItem; + if (SUCCEEDED(SHCreateItemFromIDList(pidlFolder, IID_PPV_ARGS(&spItem)))) { + spItem.As(&g_ShellContext.ParentFolder); + + // Log background folder path + PWSTR pszPath = nullptr; + if (SUCCEEDED(g_ShellContext.ParentFolder->GetDisplayName(SIGDN_FILESYSPATH, &pszPath))) { + __trace(L"[ShellExt] Background Folder Path: %ls", pszPath); + CoTaskMemFree(pszPath); + } + } + } + // 2. Check for Selection Context (Right-click on one or more files/folders) + else if (pDataObj != NULL) + { + g_ShellContext.IsBackground = false; + __trace(L"[ShellExt] Context: SELECTION"); + + if (SUCCEEDED(SHCreateShellItemArrayFromDataObject(pDataObj, IID_PPV_ARGS(&g_ShellContext.SelectionArray)))) + { + DWORD dwCount = 0; + g_ShellContext.SelectionArray->GetCount(&dwCount); + __trace(L"[ShellExt] Total Selected Items: %u", dwCount); + + if (dwCount > 0) { + ComPtr spFirstItem; + ComPtr spFirstParent; + bool allSameParent = true; + + // Loop through all selected items for logging and parent verification + for (DWORD i = 0; i < dwCount; ++i) { + ComPtr spCurrentItem; + if (SUCCEEDED(g_ShellContext.SelectionArray->GetItemAt(i, &spCurrentItem))) { + // Log individual item path + PWSTR pszItemPath = nullptr; + // Use SIGDN_FILESYSPATH for local paths, fallback to SIGDN_NORMALDISPLAY for virtual items + if (SUCCEEDED(spCurrentItem->GetDisplayName(SIGDN_FILESYSPATH, &pszItemPath))) { + __trace(L"[ShellExt] Item[%u]: %ls", i, pszItemPath); + CoTaskMemFree(pszItemPath); + } + + __trace(L"[ShellExt] Getting parent"); + + // Parent verification logic + if (i == 0) { + if (!SUCCEEDED(spCurrentItem->GetParent(&spFirstParent))) + { + allSameParent = false; + } else { + // Log individual item path + PWSTR pszItemPath1 = nullptr; + // Use SIGDN_FILESYSPATH for local paths, fallback to SIGDN_NORMALDISPLAY for virtual items + if (SUCCEEDED(spFirstParent->GetDisplayName(SIGDN_NORMALDISPLAY, &pszItemPath1))) { + __trace(L"[ShellExt] Parent: %ls", pszItemPath1); + CoTaskMemFree(pszItemPath1); + } + } + } else if (allSameParent) { + ComPtr spCurrentParent; + int compareResult = 0; + if (FAILED(spCurrentItem->GetParent(&spCurrentParent)) || + FAILED(spFirstParent->Compare(spCurrentParent.Get(), SICHINT_CANONICAL, &compareResult)) || + compareResult != 0) + { + allSameParent = false; + } + } + } + } + + // Assign the final ParentFolder based on consistency + if (allSameParent && spFirstParent) { + spFirstParent.As(&g_ShellContext.ParentFolder); + __trace(L"[ShellExt] Parent Folder is consistent."); + } else { + __trace(L"[ShellExt] Mixed parents or virtual folder; setting Parent to 'This PC'"); + GetThisPCItem(g_ShellContext.ParentFolder); + } + } + } + } + + return S_OK; +} +#pragma endregion + +#pragma region IContextMenu +IFACEMETHODIMP ShellExtSelectionRetriever::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, [[maybe_unused]] UINT idCmdLast, UINT uFlags) +{ + // If CMF_DEFAULTONLY is set, we should not add any items + if (uFlags & CMF_DEFAULTONLY) return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0); + + HWND hWnd = GetActiveWindow(); + // Create a dummy ContextMenu to check if is excluded + Nilesoft::Shell::ContextMenu dummyContextMenu = Nilesoft::Shell::ContextMenu(hWnd, hmenu, {0, 0}); + dummyContextMenu.Initialize(true); + if (dummyContextMenu.is_excluded()) + { + __trace(L"In QueryContextMenu: is excluded."); + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0); + } + + // Insert the placeholder menu item + // idCmdFirst is the starting ID for your commands + InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_STRING, idCmdFirst, shell_ext_selection_retriever_placeholder); + + // Return the number of items added (1) + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 1); +} + + +// +// FUNCTION: ShellExtSelectionRetriever::InvokeCommand +// +// PURPOSE: This method is called when a user clicks a menu item to tell +// the handler to run the associated command. The lpcmi parameter +// points to a structure that contains the needed information. +// +IFACEMETHODIMP ShellExtSelectionRetriever::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) +{ + if (LOWORD(lpici->lpVerb) == 0) + { + __trace(L"[ShellExt] Placeholder clicked."); + return S_OK; + } + return E_FAIL; +} + + +// +// FUNCTION: CShellExtSelectionRetriever::GetCommandString +// +// PURPOSE: If a user highlights one of the items added by a context menu +// handler, the handler's IContextMenu::GetCommandString method is +// called to request a Help text string that will be displayed on +// the Windows Explorer status bar. This method can also be called +// to request the verb string that is assigned to a command. +// Either ANSI or Unicode verb strings can be requested. This +// example only implements support for the Unicode values of +// uFlags, because only those have been used in Windows Explorer +// since Windows 2000. +// +IFACEMETHODIMP ShellExtSelectionRetriever::GetCommandString([[maybe_unused]] UINT_PTR idCommand, + [[maybe_unused]] UINT uFlags, [[maybe_unused]] UINT *pwReserved, [[maybe_unused]] LPSTR pszName, [[maybe_unused]] UINT cchMax) +{ + return E_NOTIMPL; +} + +#pragma endregion