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