diff --git a/CHANGES b/CHANGES index d979ea4b..3565199e 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Changes for 0.8.0 * Fixed issue #101: Deprecate setup.exe installer in favor of the Windows Installer (*.msi). * Fixed issue #113: Upgrading from v0.7.0 to 0.8.0 installs at the wrong location. * Fixed issue #114: Implement sa_plugin_terminate() in the C API. +* Fixed issue #118: Registry corruption while registering the extension. Changes for 0.7.0 diff --git a/appveyor.yml b/appveyor.yml index 8a118888..8b3ea2bb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,7 +17,7 @@ version: "{branch} (#{build})" branches: only: - master - - feature-issue114 + - feature-issue115 # Do not build on tags (GitHub and BitBucket) skip_tags: true diff --git a/src/shared/Win32Registry.cpp b/src/shared/Win32Registry.cpp index 29c2bf00..958772cc 100644 --- a/src/shared/Win32Registry.cpp +++ b/src/shared/Win32Registry.cpp @@ -43,6 +43,13 @@ namespace Win32Registry { + std::string safe_null(const char* value) + { + if (value) + return value; + return "(null)"; + } + bool IsIconEquals(const REGISTRY_ICON& a, const REGISTRY_ICON& b) { if (a.path == b.path && a.index == b.index) @@ -162,6 +169,19 @@ namespace Win32Registry return result; } + inline DWORD GetRelevantDataStorageSize(const DWORD& type, const DWORD& value_size) + { + if (value_size == 0) + return 0; + switch (type) + { + case REG_SZ: + case REG_EXPAND_SZ: + return value_size - 1; + default: + return value_size; + }; + } bool GetValue(const char* key_path, const char* value_name, @@ -183,20 +203,38 @@ namespace Win32Registry DWORD value_size = 0; //the size of the returned buffer in bytes. This size includes any terminating null character. RegQueryValueEx(hKey, value_name, NULL, &value_type, NULL, &value_size); - DWORD length = value_size - 1; + //allocate space for storing data value in a string + size_t string_length = GetRelevantDataStorageSize(value_type, value_size); + value.assign(string_length, 0); + bool alloc_success = (value.size() == string_length); // check that allocation worked. - //allocate space for value - if (value_size > 0 && value.assign(length, 0).size()) + if (value_size > 0 && alloc_success) { //Read the actual data of the value value_type = 0; RegQueryValueEx(hKey, value_name, NULL, &value_type, (LPBYTE)value.c_str(), &value_size); type = ConvertToPublicType(value_type); - success = (length == value.size()); + + size_t expected_size = GetRelevantDataStorageSize(value_type, value_size); + success = (value.size() == expected_size); } RegCloseKey(hKey); + + //// DEBUG + //{ + // std::string text; + // text += "success=" + ra::strings::ToString((int)success) + "\n"; + // text += "alloc_success=" + ra::strings::ToString((int)alloc_success) + "\n"; + // text += "key_path=" + safe_null(key_path) + "\n"; + // text += "value_name=" + safe_null(value_name) + "\n"; + // text += "value_size=" + ra::strings::ToString((uint32_t)value_size) + "\n"; + // if (success && (value_type == REG_SZ || value_type == REG_EXPAND_SZ)) + // text += "value=" + safe_null(value.c_str()) + "\n"; + // MessageBox(NULL, text.c_str(), __FUNCTION__ " [return]", MB_OK | MB_ICONEXCLAMATION); + //} + return success; } } diff --git a/src/shellextension/CClassFactory.cpp b/src/shellextension/CClassFactory.cpp new file mode 100644 index 00000000..4c528760 --- /dev/null +++ b/src/shellextension/CClassFactory.cpp @@ -0,0 +1,189 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#include "stdafx.h" + +#include "CClassFactory.h" +#include "CContextMenu.h" +#include "shellext.h" + +#pragma warning( push ) +#pragma warning( disable: 4355 ) // glog\install_dir\include\glog/logging.h(1167): warning C4355: 'this' : used in base member initializer list +#include +#pragma warning( pop ) + +// Constructeur de l'interface IClassFactory: +CClassFactory::CClassFactory() +{ + LOG(INFO) << __FUNCTION__ << "(), new instance " << ToHexString(this); + +#if SA_QUERYINTERFACE_IMPL == 0 + m_refCount = 0; // reference counter must be initialized to 0 even if we are actually creating an instance. A reference to this instance will be added when the instance will be queried by explorer.exe. +#elif SA_QUERYINTERFACE_IMPL == 1 + m_refCount = 1; +#endif + + // Increment the dll's reference counter. + DllAddRef(); +} + +// Destructeur de l'interface IClassFactory: +CClassFactory::~CClassFactory() +{ + LOG(INFO) << __FUNCTION__ << "(), delete instance " << ToHexString(this); + + // Decrement the dll's reference counter. + DllRelease(); +} + +HRESULT STDMETHODCALLTYPE CClassFactory::QueryInterface(REFIID riid, LPVOID FAR* ppv) +{ + std::string riid_str = GuidToInterfaceName(riid); + LOG(INFO) << __FUNCTION__ << "(), riid=" << riid_str << ", this=" << ToHexString(this); + + HRESULT hr = E_NOINTERFACE; + +#if SA_QUERYINTERFACE_IMPL == 0 + //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus + + // Always set out parameter to NULL, validating it first. + if (!ppv) + return E_INVALIDARG; + *ppv = NULL; + + //https://stackoverflow.com/questions/1742848/why-exactly-do-i-need-an-explicit-upcast-when-implementing-queryinterface-in-a + if (IsEqualGUID(riid, IID_IUnknown)) *ppv = (LPVOID)this; + if (IsEqualGUID(riid, IID_IClassFactory)) *ppv = (LPCLASSFACTORY)this; + + if (*ppv) + { + AddRef(); + hr = S_OK; + } + else + hr = E_NOINTERFACE; +#elif SA_QUERYINTERFACE_IMPL == 1 + static const QITAB qit[] = + { + QITABENT(CClassFactory, IClassFactory), + { 0, 0 } + }; + hr = QISearch(this, qit, riid, ppv); +#endif + + if (SUCCEEDED(hr)) + LOG(INFO) << __FUNCTION__ << "(), found interface " << riid_str << ", ppv=" << ToHexString(*ppv); + else + LOG(WARNING) << __FUNCTION__ << "(), unknown interface " << riid_str; + return hr; +} + +ULONG STDMETHODCALLTYPE CClassFactory::AddRef() +{ + //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus + + // Increment the object's internal counter. + return InterlockedIncrement(&m_refCount); +} + +ULONG STDMETHODCALLTYPE CClassFactory::Release() +{ + //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus + + // Decrement the object's internal counter. + LONG refCount = InterlockedDecrement(&m_refCount); + if (refCount == 0) + { + delete this; + } + return refCount; +} + +HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID FAR* ppv) +{ + std::string riid_str = GuidToInterfaceName(riid); + LOG(INFO) << __FUNCTION__ << "(), pUnkOuter=" << pUnkOuter << ", riid=" << riid_str << " this=" << ToHexString(this); + +#if SA_QUERYINTERFACE_IMPL == 0 + // Always set out parameter to NULL, validating it first. + if (!ppv) + return E_INVALIDARG; + *ppv = NULL; + + if (pUnkOuter) return CLASS_E_NOAGGREGATION; + CContextMenu* pContextMenu = new CContextMenu(); + if (!pContextMenu) return E_OUTOFMEMORY; + HRESULT hr = pContextMenu->QueryInterface(riid, ppv); + if (FAILED(hr)) + { + pContextMenu->Release(); + } +#elif SA_QUERYINTERFACE_IMPL == 1 + 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. + CContextMenu* pExt = new (std::nothrow) CContextMenu(); + if (pExt) + { + // Query the specified interface. + hr = pExt->QueryInterface(riid, ppv); + pExt->Release(); + } + } +#endif + + if (SUCCEEDED(hr)) + LOG(INFO) << __FUNCTION__ << "(), found interface " << riid_str << ", ppv=" << ToHexString(*ppv); + else + LOG(ERROR) << __FUNCTION__ << "(), failed creating interface " << riid_str; + return hr; +} + +HRESULT STDMETHODCALLTYPE CClassFactory::LockServer(BOOL bLock) +{ + LOG(INFO) << __FUNCTION__ << "(), bLock=" << (int)bLock << " this=" << ToHexString(this); + + //https://docs.microsoft.com/en-us/windows/desktop/api/unknwnbase/nf-unknwnbase-iclassfactory-lockserver + //https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-colockobjectexternal + + // Note: + // Previous implementations was blindly returning S_OK without doing anything else. This is probably a bad idea. Examples: git-for-windows/7-Zip + // Other implementations just return E_NOTIMPL to let explorer know. Examples: TortoiseGit. + // Finaly, most other implementations resolves on adding a lock on the DLL. This is better but not it does not follow the official microsoft documentation. Examples: chrdavis/SmartRename, owncloud/client, https://github.com/microsoft/Windows-classic-samples/ + + if (bLock) + { + DllAddRef(); + } + else + { + DllRelease(); + } + return S_OK; +} diff --git a/src/shellextension/CClassFactory.h b/src/shellextension/CClassFactory.h new file mode 100644 index 00000000..5157c45e --- /dev/null +++ b/src/shellextension/CClassFactory.h @@ -0,0 +1,46 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#pragma once + +#include "stdafx.h" + +class CClassFactory : public IClassFactory +{ +public: + CClassFactory(); + + //IUnknown interface + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID FAR*); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + //IClassFactory interface + HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR*); + HRESULT STDMETHODCALLTYPE LockServer(BOOL); + +private: + ~CClassFactory(); + ULONG m_refCount; +}; diff --git a/src/shellextension/CContextMenu.cpp b/src/shellextension/CContextMenu.cpp new file mode 100644 index 00000000..130ab041 --- /dev/null +++ b/src/shellextension/CContextMenu.cpp @@ -0,0 +1,731 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#include "stdafx.h" + +#include "CContextMenu.h" +#include "shellext.h" + +#pragma warning( push ) +#pragma warning( disable: 4355 ) // glog\install_dir\include\glog/logging.h(1167): warning C4355: 'this' : used in base member initializer list +#include +#pragma warning( pop ) + +#include "ErrorManager.h" +#include "Win32Registry.h" +#include "Win32Utils.h" +#include "GlogUtils.h" +#include "Unicode.h" + +#include "PropertyManager.h" +#include "ConfigManager.h" +#include "PropertyManager.h" +#include "SaUtils.h" + +#include "rapidassist/undef_windows_macros.h" +#include "rapidassist/strings.h" +#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/process_utf8.h" +#include "rapidassist/errors.h" +#include "rapidassist/user_utf8.h" +#include "rapidassist/unicode.h" +#include "rapidassist/environment.h" + +static const GUID CLSID_UNDOCUMENTED_01 = { 0x924502a7, 0xcc8e, 0x4f60, { 0xae, 0x1f, 0xf7, 0x0c, 0x0a, 0x2b, 0x7a, 0x7c } }; + +void CContextMenu::BuildMenuTree(HMENU hMenu, shellanything::Menu* menu, UINT& insert_pos, bool& next_menu_is_column) +{ + //Expanded the menu's strings + shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); + std::string title = pmgr.Expand(menu->GetName()); + std::string description = pmgr.Expand(menu->GetDescription()); + + //Get visible/enable properties based on current context. + bool menu_visible = menu->IsVisible(); + bool menu_enabled = menu->IsEnabled(); + bool menu_separator = menu->IsSeparator(); + bool menu_column = menu->IsColumnSeparator(); + + //Skip column separator, those are not menu item + if (menu_column) + { + next_menu_is_column = true; + return; + } + + //Skip this menu if not visible + if (!menu_visible) + { + menu->TruncateName(title); + LOG(INFO) << __FUNCTION__ << "(), skipped menu '" << title << "', not visible."; + return; + } + + //Validate menus integrity + const uint32_t& menu_command_id = menu->GetCommandId(); + if (menu_command_id == shellanything::Menu::INVALID_COMMAND_ID) + { + menu->TruncateName(title); + LOG(ERROR) << __FUNCTION__ << "(), menu '" << title << "' have invalid command id."; + return; + } + + // Truncate if required, issue #55. + menu->TruncateName(title); + menu->TruncateName(description); + + //convert to windows unicode... + std::wstring title_utf16 = ra::unicode::Utf8ToUnicode(title); + std::wstring desc_utf16 = ra::unicode::Utf8ToUnicode(description); + + MENUITEMINFOW menuinfo = { 0 }; + + menuinfo.cbSize = sizeof(MENUITEMINFOW); + menuinfo.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_ID | MIIM_STRING; + menuinfo.fType = (menu_separator ? MFT_SEPARATOR : MFT_STRING); + menuinfo.fType += (next_menu_is_column ? MFT_MENUBARBREAK : 0); + menuinfo.fState = (menu_enabled ? MFS_ENABLED : MFS_DISABLED); + menuinfo.wID = menu_command_id; + menuinfo.dwTypeData = (wchar_t*)title_utf16.c_str(); + menuinfo.cch = (UINT)title_utf16.size(); + + //add an icon + const shellanything::Icon& icon = menu->GetIcon(); + if (!menu_separator && icon.IsValid()) + { + shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); + std::string file_extension = pmgr.Expand(icon.GetFileExtension()); + std::string icon_filename = pmgr.Expand(icon.GetPath()); + int icon_index = icon.GetIndex(); + + //if the icon is pointing to a file extension + if (!file_extension.empty()) + { + //resolve the file extension to a system icon. + + //did we already resolved this icon? + IconMap::iterator wExtensionsIterator = m_FileExtensionCache.find(file_extension); + bool found = (wExtensionsIterator != m_FileExtensionCache.end()); + if (found) + { + //already resolved + const shellanything::Icon& resolved_icon = wExtensionsIterator->second; + icon_filename = resolved_icon.GetPath(); + icon_index = resolved_icon.GetIndex(); + } + else + { + //not found + + //make a copy of the icon and resolve the file extension to a system icon. + shellanything::Icon resolved_icon = icon; + resolved_icon.ResolveFileExtensionIcon(); + + //save the icon for a future use + m_FileExtensionCache[file_extension] = resolved_icon; + + //use the resolved icon location + icon_filename = resolved_icon.GetPath(); + icon_index = resolved_icon.GetIndex(); + } + } + + //ask the cache for an existing icon. + //this will identify the icon in the cache as "used" or "active". + HBITMAP hBitmap = m_BitmapCache.FindHandle(icon_filename, icon_index); + + //if nothing in cache, create a new one + if (hBitmap == shellanything::BitmapCache::INVALID_BITMAP_HANDLE) + { + HICON hIconLarge = NULL; + HICON hIconSmall = NULL; + + // + std::wstring icon_filename_wide = ra::unicode::Utf8ToUnicode(icon_filename); + UINT numIconInFile = ExtractIconExW(icon_filename_wide.c_str(), -1, NULL, NULL, 1); + UINT numIconLoaded = ExtractIconExW(icon_filename_wide.c_str(), icon_index, &hIconLarge, &hIconSmall, 1); + if (numIconLoaded >= 1) + { + //Find the best icon + HICON hIcon = Win32Utils::GetBestIconForMenu(hIconLarge, hIconSmall); + + //Convert the icon to a bitmap (with invisible background) + hBitmap = Win32Utils::CopyAsBitmap(hIcon); + + DestroyIcon(hIconLarge); + DestroyIcon(hIconSmall); + + //add the bitmap to the cache for future use + m_BitmapCache.AddHandle(icon_filename.c_str(), icon_index, hBitmap); + } + } + + //if a bitmap is created + if (hBitmap != shellanything::BitmapCache::INVALID_BITMAP_HANDLE) + { + //enable bitmap handling for the menu + menuinfo.fMask |= MIIM_BITMAP; + + //assign the HBITMAP to the HMENU + menuinfo.hbmpItem = hBitmap; + } + } + + //handle column separator + if (next_menu_is_column) + { + menuinfo.fType |= MFT_MENUBARBREAK; + } + next_menu_is_column = false; + + //handle submenus + if (menu->IsParentMenu()) + { + menuinfo.fMask |= MIIM_SUBMENU; + HMENU hSubMenu = CreatePopupMenu(); + + bool next_sub_menu_is_column = false; + + shellanything::Menu::MenuPtrList subs = menu->GetSubMenus(); + UINT sub_insert_pos = 0; + for (size_t i = 0; i < subs.size(); i++) + { + shellanything::Menu* submenu = subs[i]; + BuildMenuTree(hSubMenu, submenu, sub_insert_pos, next_sub_menu_is_column); + } + + menuinfo.hSubMenu = hSubMenu; + } + + BOOL result = InsertMenuItemW(hMenu, insert_pos, TRUE, &menuinfo); + insert_pos++; //next menu is below this one + + LOG(INFO) << __FUNCTION__ << "(), insert.pos=" << ra::strings::Format("%03d", insert_pos) << ", id=" << ra::strings::Format("%06d", menuinfo.wID) << ", result=" << result << ", title=" << title; +} + +void CContextMenu::BuildMenuTree(HMENU hMenu) +{ + //Bitmap ressources must be properly destroyed. + //When a menu (HMENU handle) is destroyed using win32 DestroyMenu() function, it also destroy the child menus: + //https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-destroymenu + // + //However the bitmap assigned to menus are not deleted with DestroyMenu() function. + //Bitmap is a limited resource. If you ran out of GDI resources, you may see black menus + //and Windows Explorer will have difficulties to render all the window. For details, see + //https://www.codeproject.com/Questions/1228261/Windows-shell-extension + // + //To prevent running out of bitmap ressource we use the shellanything::BitmapCache class. + //Each bitmap is identified as 'used' in CContextMenu::BuildMenuTree() with 'm_BitmapCache.FindHandle()'. + //Every 5 times the shell extension popup is displayed, we look for 'unused' bitmap and delete them. + // + + //handle destruction of old bitmap in the cache + m_BuildMenuTreeCount++; + if (m_BuildMenuTreeCount > 0 && (m_BuildMenuTreeCount % 5) == 0) + { + //every 10 calls, refresh the cache + m_BitmapCache.DestroyOldHandles(); + + //reset counters + m_BitmapCache.ResetCounters(); + } + + bool next_menu_is_column = false; + + //browse through all shellanything menus and build the win32 popup menus + + //for each configuration + shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); + shellanything::Configuration::ConfigurationPtrList configs = cmgr.GetConfigurations(); + UINT insert_pos = 0; + for (size_t i = 0; i < configs.size(); i++) + { + shellanything::Configuration* config = configs[i]; + if (config) + { + //for each menu child + shellanything::Menu::MenuPtrList menus = config->GetMenus(); + for (size_t j = 0; j < menus.size(); j++) + { + shellanything::Menu* menu = menus[j]; + + //Add this menu to the tree + BuildMenuTree(hMenu, menu, insert_pos, next_menu_is_column); + } + } + } +} + +CContextMenu::CContextMenu() +{ + LOG(INFO) << __FUNCTION__ << "(), new instance " << ToHexString(this); + +#if SA_QUERYINTERFACE_IMPL == 0 + m_refCount = 0; // reference counter must be initialized to 0 even if we are actually creating an instance. A reference to this instance will be added when the instance will be queried by explorer.exe. +#elif SA_QUERYINTERFACE_IMPL == 1 + m_refCount = 1; +#endif + + m_FirstCommandId = 0; + m_IsBackGround = false; + m_BuildMenuTreeCount = 0; + m_previousMenu = 0; + + // Increment the dll's reference counter. + DllAddRef(); +} + +CContextMenu::~CContextMenu() +{ + LOG(INFO) << __FUNCTION__ << "(), delete instance " << ToHexString(this); + + // Decrement the dll's reference counter. + DllRelease(); +} + +HRESULT STDMETHODCALLTYPE CContextMenu::QueryContextMenu(HMENU hMenu, UINT menu_index, UINT first_command_id, UINT max_command_id, UINT flags) +{ + std::string flags_str = GetQueryContextMenuFlags(flags); + std::string flags_hex = ra::strings::Format("0x%08x", flags); + + LOG(INFO) << __FUNCTION__ << "(), hMenu=0x" << ra::strings::Format("0x%08x", hMenu).c_str() << ",count=" << GetMenuItemCount(hMenu) << ", menu_index=" << menu_index << ", first_command_id=" << first_command_id << ", max_command_id=" << max_command_id << ", flags=" << flags_hex << "=(" << flags_str << ")" << " this=" << ToHexString(this); + + //https://docs.microsoft.com/en-us/windows/desktop/shell/how-to-implement-the-icontextmenu-interface + + //From this point, it is safe to use class members without other threads interference + CCriticalSectionGuard cs_guard(&m_CS); + + //Note on flags... + //Right-click on a file or directory with Windows Explorer on the right area: flags=0x00020494=132244(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_CANRENAME|CMF_ITEMMENU|CMF_ASYNCVERBSTATE) + //Right-click on the empty area with Windows Explorer on the right area: flags=0x00020424=132132(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_NODEFAULT|CMF_ASYNCVERBSTATE) + //Right-click on a directory with Windows Explorer on the left area: flags=0x00000414=001044(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_CANRENAME|CMF_ASYNCVERBSTATE) + //Right-click on a drive with Windows Explorer on the left area: flags=0x00000414=001044(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_CANRENAME|CMF_ASYNCVERBSTATE) + //Right-click on the empty area on the Desktop: flags=0x00020420=132128(dec)=(CMF_NORMAL|CMF_NODEFAULT|CMF_ASYNCVERBSTATE) + //Right-click on a directory on the Desktop: flags=0x00020490=132240(dec)=(CMF_NORMAL|CMF_CANRENAME|CMF_ITEMMENU|CMF_ASYNCVERBSTATE) + + //Filter out queries that have nothing selected. + //This can happend if user is copy & pasting files (using CTRL+C and CTRL+V) + //and if the shell extension is registered as a DragDropHandlers. + if (m_Context.GetElements().size() == 0) + { + //Don't know what to do with this + LOG(INFO) << __FUNCTION__ << "(), skipped, nothing is selected."; + return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); //nothing inserted + } + + //Filter out queries that are called twice for the same directory. + if (hMenu == m_previousMenu) + { + //Issue #6 - Right-click on a directory with Windows Explorer in the left panel shows the menus twice. + //Issue #31 - Error in logs for CContextMenu::GetCommandString(). + //Using a static variable is a poor method for solving the issue but it is a "good enough" strategy. + LOG(INFO) << __FUNCTION__ << "(), skipped, QueryContextMenu() called twice and menu is already populated once."; + return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); //nothing inserted + } + + //Remember current menu to prevent issues calling twice QueryContextMenu() + m_previousMenu = hMenu; + + //Log what is selected by the user + const shellanything::StringList& elements = m_Context.GetElements(); + size_t num_selected_total = elements.size(); + int num_files = m_Context.GetNumFiles(); + int num_directories = m_Context.GetNumDirectories(); + LOG(INFO) << __FUNCTION__ << "(), SelectionContext have " << num_selected_total << " element(s): " << num_files << " files and " << num_directories << " directories."; + + //Keep a reference the our first command id. We will need it when InvokeCommand is called. + m_FirstCommandId = first_command_id; + + //Refresh the list of loaded configuration files + shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); + cmgr.Refresh(); + + //Update all menus with the new context + //This will refresh the visibility flags which is required before calling ConfigManager::AssignCommandIds() + cmgr.Update(m_Context); + + //Assign unique command id to visible menus. Issue #5 + UINT next_command_id = cmgr.AssignCommandIds(first_command_id); + + //Build the menus + BuildMenuTree(hMenu); + + //Log information about menu statistics. + UINT menu_last_command_id = (UINT)-1; //confirmed last command id + if (next_command_id != first_command_id) + menu_last_command_id = next_command_id - 1; + UINT num_menu_items = next_command_id - first_command_id; + LOG(INFO) << __FUNCTION__ << "(), Menu: first_command_id=" << first_command_id << " menu_last_command_id=" << menu_last_command_id << " next_command_id=" << next_command_id << " num_menu_items=" << num_menu_items << ".\n"; + + //debug the constructed menu tree +#ifdef _DEBUG + std::string menu_tree = Win32Utils::GetMenuTree(hMenu, 2); + LOG(INFO) << __FUNCTION__ << "(), Menu tree:\n" << menu_tree.c_str(); +#endif + + HRESULT hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, num_menu_items); + return hr; +} + +HRESULT STDMETHODCALLTYPE CContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) +{ + //define the type of structure pointed by pici + const char* struct_name = "UNKNOWN"; + if (pici->cbSize == sizeof(CMINVOKECOMMANDINFO)) + struct_name = "CMINVOKECOMMANDINFO"; + else if (pici->cbSize == sizeof(CMINVOKECOMMANDINFOEX)) + struct_name = "CMINVOKECOMMANDINFOEX"; + + //define how we should interpret pici->lpVerb + std::string verb; + if (IS_INTRESOURCE(pici->lpVerb)) + { + // D:\Projects\ShellAnything\src\shellext.cpp(632) : warning C4311 : 'reinterpret_cast' : pointer truncation from 'LPCSTR' to 'int' + // D:\Projects\ShellAnything\src\shellext.cpp(632) : warning C4302 : 'reinterpret_cast' : truncation from 'LPCSTR' to 'int' +#pragma warning( push ) +#pragma warning( disable: 4302 ) +#pragma warning( disable: 4311 ) + verb = ra::strings::ToString(reinterpret_cast(pici->lpVerb)); +#pragma warning( pop ) + } + else + verb = pici->lpVerb; + + LOG(INFO) << __FUNCTION__ << "(), pici->cbSize=" << struct_name << ", pici->fMask=" << pici->fMask << ", pici->lpVerb=" << verb << " this=" << ToHexString(this); + + //validate + if (!IS_INTRESOURCE(pici->lpVerb)) + return E_INVALIDARG; //don't know what to do with pici->lpVerb + + UINT target_command_offset = LOWORD(pici->lpVerb); //matches the command_id offset (command id of the selected menu - command id of the first menu) + UINT target_command_id = m_FirstCommandId + target_command_offset; + + //From this point, it is safe to use class members without other threads interference + CCriticalSectionGuard cs_guard(&m_CS); + + //find the menu that is requested + shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); + shellanything::Menu* menu = cmgr.FindMenuByCommandId(target_command_id); + if (menu == NULL) + { + LOG(ERROR) << __FUNCTION__ << "(), unknown menu for pici->lpVerb=" << verb; + return E_INVALIDARG; + } + + //compute the visual menu title + shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); + std::string title = pmgr.Expand(menu->GetName()); + + //found a menu match, execute menu action + LOG(INFO) << __FUNCTION__ << "(), executing action(s) for menu '" << title.c_str() << "'..."; + + //execute actions + const shellanything::IAction::ActionPtrList& actions = menu->GetActions(); + for (size_t i = 0; i < actions.size(); i++) + { + LOG(INFO) << __FUNCTION__ << "(), executing action " << (i + 1) << " of " << actions.size() << "."; + const shellanything::IAction* action = actions[i]; + if (action) + { + ra::errors::ResetLastErrorCode(); //reset win32 error code in case the action fails. + bool success = action->Execute(m_Context); + + if (!success) + { + //try to get an error message from win32 + ra::errors::errorcode_t dwError = ra::errors::GetLastErrorCode(); + if (dwError) + { + std::string error_message = ra::errors::GetErrorCodeDescription(dwError); + LOG(ERROR) << __FUNCTION__ << "(), action #" << (i + 1) << " has failed: " << error_message; + } + else + { + //simply log an error + LOG(ERROR) << __FUNCTION__ << "(), action #" << (i + 1) << " has failed."; + } + + //stop executing the next actions + i = actions.size(); + } + } + } + + LOG(INFO) << __FUNCTION__ << "(), executing action(s) for menu '" << title.c_str() << "' completed."; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CContextMenu::GetCommandString(UINT_PTR command_id, UINT flags, UINT FAR* reserved, LPSTR pszName, UINT cchMax) +{ + std::string flags_str = GetGetCommandStringFlags(flags); + std::string flags_hex = ra::strings::Format("0x%08x", flags); + + // only show this log in verbose mode + //LOG(INFO) << __FUNCTION__ << "(), command_id=" << command_id << ", cchMax=" << cchMax << " this=" << ToHexString(this) << ", flags=" << flags_hex << ":" << flags_str; + + UINT target_command_offset = (UINT)command_id; //matches the command_id offset (command id of the selected menu substracted by command id of the first menu) + UINT target_command_id = m_FirstCommandId + target_command_offset; + + //From this point, it is safe to use class members without other threads interference + CCriticalSectionGuard cs_guard(&m_CS); + + //find the menu that is requested + shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); + shellanything::Menu* menu = cmgr.FindMenuByCommandId(target_command_id); + if (menu == NULL) + { + LOG(ERROR) << __FUNCTION__ << "(), unknown menu for command_id=" << target_command_offset << " m_FirstCommandId=" << m_FirstCommandId << " target_command_id=" << target_command_id; + return E_INVALIDARG; + } + + //compute the visual menu description + shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); + std::string description = pmgr.Expand(menu->GetDescription()); + + //convert to windows unicode... + std::wstring desc_utf16 = ra::unicode::Utf8ToUnicode(description); + std::string desc_ansi = ra::unicode::Utf8ToAnsi(description); + + //Build up tooltip string + switch (flags) + { + case GCS_HELPTEXTA: + { + //ANIS tooltip handling + lstrcpynA(pszName, desc_ansi.c_str(), cchMax); + return S_OK; + } + break; + case GCS_HELPTEXTW: + { + //UNICODE tooltip handling + lstrcpynW((LPWSTR)pszName, desc_utf16.c_str(), cchMax); + return S_OK; + } + break; + case GCS_VERBA: + { + //ANIS tooltip handling + lstrcpynA(pszName, desc_ansi.c_str(), cchMax); + return S_OK; + } + break; + case GCS_VERBW: + { + //UNICODE tooltip handling + lstrcpynW((LPWSTR)pszName, desc_utf16.c_str(), cchMax); + return S_OK; + } + break; + case GCS_VALIDATEA: + case GCS_VALIDATEW: + { + return S_OK; + } + break; + } + + LOG(ERROR) << __FUNCTION__ << "(), unknown flags: " << flags; + return S_FALSE; +} + +HRESULT STDMETHODCALLTYPE CContextMenu::Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hRegKey) +{ + LOG(INFO) << __FUNCTION__ << "(), pIDFolder=" << (void*)pIDFolder << " this=" << ToHexString(this); + + //From this point, it is safe to use class members without other threads interference + CCriticalSectionGuard cs_guard(&m_CS); + + shellanything::StringList files; + + // Cleanup + m_Context.UnregisterProperties(); //Unregister the previous context properties + m_Context.SetElements(files); + m_IsBackGround = false; + + // Did we clicked on a folder's background or the desktop directory? + if (pIDFolder) + { + LOG(INFO) << __FUNCTION__ << "(), User right-clicked on a background directory."; + + wchar_t szPath[2 * MAX_PATH] = { 0 }; + + if (SHGetPathFromIDListW(pIDFolder, szPath)) + { + if (szPath[0] != '\0') + { + std::string path_utf8 = ra::unicode::UnicodeToUtf8(szPath); + LOG(INFO) << __FUNCTION__ << "(), Found directory '" << path_utf8 << "'."; + m_IsBackGround = true; + files.push_back(path_utf8); + } + else + { + LOG(WARNING) << __FUNCTION__ << "(), found empty path in pIDFolder."; + return E_INVALIDARG; + } + } + else + { + LOG(ERROR) << __FUNCTION__ << "(), SHGetPathFromIDList() has failed."; + return E_INVALIDARG; + } + } + + // User clicked on one or more file or directory + else if (pDataObj) + { + LOG(INFO) << __FUNCTION__ << "(), User right-clicked on selected files/directories."; + + FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stg = { TYMED_HGLOBAL }; + HDROP hDropInfo; + + // The selected files are expected to be in HDROP format. + if (FAILED(pDataObj->GetData(&fmt, &stg))) + { + LOG(WARNING) << __FUNCTION__ << "(), selected files are not in HDROP format."; + return E_INVALIDARG; + } + + // Get a locked pointer to the files data. + hDropInfo = (HDROP)GlobalLock(stg.hGlobal); + if (NULL == hDropInfo) + { + ReleaseStgMedium(&stg); + LOG(ERROR) << __FUNCTION__ << "(), failed to get lock on selected files."; + return E_INVALIDARG; + } + + UINT num_files = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0); + LOG(INFO) << __FUNCTION__ << "(), User right-clicked on " << num_files << " files/directories."; + + // For each files + for (UINT i = 0; i < num_files; i++) + { + UINT length = DragQueryFileW(hDropInfo, i, NULL, 0); + + // Allocate a temporary buffer + std::wstring path(length, '\0'); + if (path.size() != length) + continue; + size_t num_characters = length + 1; + + // Copy the element into the temporary buffer + DragQueryFileW(hDropInfo, i, (wchar_t*)path.data(), (UINT)num_characters); + + //add the new file + std::string path_utf8 = ra::unicode::UnicodeToUtf8(path); + LOG(INFO) << __FUNCTION__ << "(), Found file/directory #" << ra::strings::Format("%03d", i) << ": '" << path_utf8 << "'."; + files.push_back(path_utf8); + } + GlobalUnlock(stg.hGlobal); + ReleaseStgMedium(&stg); + } + + //update the selection context + m_Context.SetElements(files); + + //Register the current context properties so that menus can display the right caption + m_Context.RegisterProperties(); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CContextMenu::QueryInterface(REFIID riid, LPVOID FAR* ppv) +{ + std::string riid_str = GuidToInterfaceName(riid); + LOG(INFO) << __FUNCTION__ << "(), riid=" << riid_str << ", this=" << ToHexString(this); + + HRESULT hr = E_NOINTERFACE; + +#if SA_QUERYINTERFACE_IMPL == 0 + //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus + + // Always set out parameter to NULL, validating it first. + if (!ppv) + return E_INVALIDARG; + *ppv = NULL; + + ////Filter out unimplemented known interfaces so they do not show as WARNINGS + //if (IsEqualGUID(riid, IID_IObjectWithSite) || //{FC4801A3-2BA9-11CF-A229-00AA003D7352} + // IsEqualGUID(riid, IID_IInternetSecurityManager) || //{79EAC9EE-BAF9-11CE-8C82-00AA004BA90B} + // IsEqualGUID(riid, IID_IContextMenu2) || //{000214f4-0000-0000-c000-000000000046} + // IsEqualGUID(riid, IID_IContextMenu3) || //{BCFCE0A0-EC17-11d0-8D10-00A0C90F2719} + // IsEqualGUID(riid, CLSID_UNDOCUMENTED_01) + // ) + //{ + // LOG(INFO) << __FUNCTION__ << "(), interface not supported " << riid_str; + // return E_NOINTERFACE; + //} + + //https://stackoverflow.com/questions/1742848/why-exactly-do-i-need-an-explicit-upcast-when-implementing-queryinterface-in-a + if (IsEqualGUID(riid, IID_IUnknown)) *ppv = (LPVOID)this; + if (IsEqualGUID(riid, IID_IShellExtInit)) *ppv = (LPSHELLEXTINIT)this; + if (IsEqualGUID(riid, IID_IContextMenu)) *ppv = (LPCONTEXTMENU)this; + + if (*ppv) + { + AddRef(); + hr = S_OK; + } + else + hr = E_NOINTERFACE; +#elif SA_QUERYINTERFACE_IMPL == 1 + static const QITAB qit[] = + { + QITABENT(CContextMenu, IShellExtInit), + QITABENT(CContextMenu, IContextMenu), + { 0, 0 }, + }; + hr = QISearch(this, qit, riid, ppv); +#endif + + if (SUCCEEDED(hr)) + LOG(INFO) << __FUNCTION__ << "(), found interface " << riid_str << ", ppv=" << ToHexString(*ppv); + else + LOG(INFO) << __FUNCTION__ << "(), unknown interface " << riid_str; + return hr; +} + +ULONG STDMETHODCALLTYPE CContextMenu::AddRef() +{ + //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus + + // Increment the object's internal counter. + return InterlockedIncrement(&m_refCount); +} + +ULONG STDMETHODCALLTYPE CContextMenu::Release() +{ + //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus + + // Decrement the object's internal counter. + LONG refCount = InterlockedDecrement(&m_refCount); + if (refCount == 0) + { + delete this; + } + return refCount; +} diff --git a/src/shellextension/CContextMenu.h b/src/shellextension/CContextMenu.h new file mode 100644 index 00000000..a7916223 --- /dev/null +++ b/src/shellextension/CContextMenu.h @@ -0,0 +1,75 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#pragma once + +#include "stdafx.h" + +#include "CCriticalSection.h" +#include "BitmapCache.h" +#include "SelectionContext.h" +#include "Menu.h" +#include "Icon.h" + +#include +#include + +class CContextMenu : public IShellExtInit, public IContextMenu +{ +public: + typedef std::map IconMap; + +public: + CContextMenu(); + + //IUnknown interface + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID FAR*); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + //IContextMenu interface + HRESULT STDMETHODCALLTYPE QueryContextMenu(HMENU hMenu, UINT menu_index, UINT first_command_id, UINT max_command_id, UINT flags); + HRESULT STDMETHODCALLTYPE InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi); + HRESULT STDMETHODCALLTYPE GetCommandString(UINT_PTR command_id, UINT flags, UINT FAR* reserved, LPSTR pszName, UINT cchMax); + + //IShellExtInit interface + HRESULT STDMETHODCALLTYPE Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID); + +protected: + ~CContextMenu(); + +private: + void BuildMenuTree(HMENU hMenu); + void BuildMenuTree(HMENU hMenu, shellanything::Menu* menu, UINT& insert_pos, bool& next_menu_is_column); + + CCriticalSection m_CS; //protects class members + ULONG m_refCount; + UINT m_FirstCommandId; + bool m_IsBackGround; + int m_BuildMenuTreeCount; //number of times that BuildMenuTree() was called + shellanything::BitmapCache m_BitmapCache; + IconMap m_FileExtensionCache; + HMENU m_previousMenu; + shellanything::SelectionContext m_Context; +}; diff --git a/src/shellextension/CCriticalSection.cpp b/src/shellextension/CCriticalSection.cpp new file mode 100644 index 00000000..5ebe9d20 --- /dev/null +++ b/src/shellextension/CCriticalSection.cpp @@ -0,0 +1,65 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#include "stdafx.h" + +#include "CCriticalSection.h" + +CCriticalSection::CCriticalSection() +{ + InitializeCriticalSection(&mCS); +} + +CCriticalSection::~CCriticalSection() +{ + DeleteCriticalSection(&mCS); +} + +void CCriticalSection::Enter() +{ + EnterCriticalSection(&mCS); +} + +void CCriticalSection::Leave() +{ + LeaveCriticalSection(&mCS); +} + +CCriticalSectionGuard::CCriticalSectionGuard(CCriticalSection* cs) +{ + mCS = cs; + if (mCS) + { + mCS->Enter(); + } +} + +CCriticalSectionGuard::~CCriticalSectionGuard() +{ + if (mCS) + { + mCS->Leave(); + } + mCS = NULL; +} diff --git a/src/shellextension/CCriticalSection.h b/src/shellextension/CCriticalSection.h new file mode 100644 index 00000000..a0a41df9 --- /dev/null +++ b/src/shellextension/CCriticalSection.h @@ -0,0 +1,50 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#pragma once + +#include "stdafx.h" + +class CCriticalSection +{ +protected: + CRITICAL_SECTION mCS; + +public: + CCriticalSection(); + virtual ~CCriticalSection(); + + void Enter(); + void Leave(); +}; + +class CCriticalSectionGuard +{ +protected: + CCriticalSection* mCS; + +public: + CCriticalSectionGuard(CCriticalSection* cs); + ~CCriticalSectionGuard(); +}; diff --git a/src/shellextension/CMakeLists.txt b/src/shellextension/CMakeLists.txt index ebec3f5a..50e27d6d 100644 --- a/src/shellextension/CMakeLists.txt +++ b/src/shellextension/CMakeLists.txt @@ -6,9 +6,18 @@ add_library(sa.shellextension SHARED ${SHELLANYTHING_VERSION_RC} ${CMAKE_SOURCE_DIR}/src/resource.rc.in ${CMAKE_SOURCE_DIR}/src/version.rc.in + CClassFactory.cpp + CClassFactory.h + CContextMenu.cpp + CContextMenu.h + CCriticalSection.cpp + CCriticalSection.h + dllmain.cpp shellext.cpp shellext.h shellext.def + stdafx.h + targetver.h ) # Force CMAKE_DEBUG_POSTFIX for executables diff --git a/src/shellextension/dllmain.cpp b/src/shellextension/dllmain.cpp new file mode 100644 index 00000000..59aa94ac --- /dev/null +++ b/src/shellextension/dllmain.cpp @@ -0,0 +1,388 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#include "stdafx.h" + +#include "CContextMenu.h" +#include "CClassFactory.h" + +#pragma warning( push ) +#pragma warning( disable: 4355 ) // glog\install_dir\include\glog/logging.h(1167): warning C4355: 'this' : used in base member initializer list +#include +#pragma warning( pop ) + +#include "ErrorManager.h" +#include "Win32Registry.h" +#include "Win32Utils.h" +#include "GlogUtils.h" + +#include "rapidassist/undef_windows_macros.h" +#include "rapidassist/strings.h" +#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/process_utf8.h" +#include "rapidassist/errors.h" +#include "rapidassist/user_utf8.h" +#include "rapidassist/unicode.h" +#include "rapidassist/environment.h" + +#include "shellanything/version.h" +#include "shellanything/config.h" + +#include "ConfigManager.h" +#include "PropertyManager.h" +#include "SaUtils.h" + +#include "shellext.h" + +//Declarations +UINT g_cRefDll = 0; // Reference counter of this DLL +HINSTANCE g_hmodDll = 0; // HINSTANCE of the DLL + +void DllAddRef(void) +{ + LOG(INFO) << __FUNCTION__ << "(), new"; + + InterlockedIncrement(&g_cRefDll); +} + +void DllRelease(void) +{ + LOG(INFO) << __FUNCTION__ << "(), delete"; + + InterlockedDecrement(&g_cRefDll); +} + +STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, LPVOID* ppv) +{ + std::string clsid_str = GuidToInterfaceName(clsid); + std::string riid_str = GuidToInterfaceName(riid); + LOG(INFO) << __FUNCTION__ << "(), clsid=" << clsid_str << ", riid=" << riid_str; + + // Always set out parameter to NULL, validating it first. + if (!ppv) + return E_INVALIDARG; + *ppv = NULL; + + HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; + + if (IsEqualGUID(clsid, CLSID_ShellAnythingMenu)) + { + hr = E_OUTOFMEMORY; + + CClassFactory* pClassFactory = new CClassFactory(); + if (pClassFactory) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + } + + if (hr == CLASS_E_CLASSNOTAVAILABLE) + LOG(ERROR) << __FUNCTION__ << "(), ClassFactory " << clsid_str << " not found!"; + else if (FAILED(hr)) + LOG(ERROR) << __FUNCTION__ << "(), unknown interface " << riid_str; + else + LOG(INFO) << __FUNCTION__ << "(), found interface " << riid_str << ", ppv=" << ToHexString(*ppv); + return hr; +} + +STDAPI DllCanUnloadNow(void) +{ + LOG(INFO) << __FUNCTION__ << "() " << GetProcessContextDesc(); + + ULONG ulRefCount = 0; + ulRefCount = InterlockedIncrement(&g_cRefDll); + ulRefCount = InterlockedDecrement(&g_cRefDll); + + if (0 == ulRefCount) + { + LOG(INFO) << __FUNCTION__ << "() -> Yes"; + return S_OK; + } + LOG(INFO) << __FUNCTION__ << "() -> No, " << ulRefCount << " instances are still in use."; + return S_FALSE; +} + +STDAPI DllRegisterServer(void) +{ + const std::string guid_str_tmp = GuidToString(CLSID_ShellAnythingMenu).c_str(); + const char* guid_str = guid_str_tmp.c_str(); + const std::string class_name_version1 = std::string(ShellExtensionClassName) + ".1"; + const std::string module_path = GetCurrentModulePath(); + + //#define TRACELINE() MessageBox(NULL, (std::string("Line: ") + ra::strings::ToString(__LINE__)).c_str(), "DllUnregisterServer() DEBUG", MB_OK); + + //Register version 1 of our class + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", class_name_version1.c_str()); + if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionDescription)) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s.1\\CLSID", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + //Register current version of our class + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionDescription)) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s\\CLSID", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s\\CurVer", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), class_name_version1.c_str())) + return E_ACCESSDENIED; + } + + // Add the CLSID of this DLL to the registry + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s", guid_str); + if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionDescription)) + return E_ACCESSDENIED; + } + + // Define the path and parameters of our DLL: + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\ProgID", guid_str); + if (!Win32Registry::CreateKey(key.c_str(), class_name_version1.c_str())) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\VersionIndependentProgID", guid_str); + if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionClassName)) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\Programmable", guid_str); + if (!Win32Registry::CreateKey(key.c_str())) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\InprocServer32", guid_str); + if (!Win32Registry::CreateKey(key.c_str(), module_path.c_str())) + return E_ACCESSDENIED; + if (!Win32Registry::SetValue(key.c_str(), "ThreadingModel", "Apartment")) + return E_ACCESSDENIED; + } + + // Register the shell extension for all the file types + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\*\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + // Register the shell extension for directories + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + // Register the shell extension for folders + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Folder\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + // Register the shell extension for the desktop or the file explorer's background + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\Background\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + // Register the shell extension for drives + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Drive\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::CreateKey(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + // Register the shell extension to the system's approved Shell Extensions + { + std::string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"; + if (!Win32Registry::CreateKey(key.c_str())) + return E_ACCESSDENIED; + if (!Win32Registry::SetValue(key.c_str(), guid_str, ShellExtensionDescription)) + return E_ACCESSDENIED; + } + + // Notify the Shell to pick the changes: + // https://docs.microsoft.com/en-us/windows/desktop/shell/reg-shell-exts#predefined-shell-objects + // Any time you create or change a Shell extension handler, it is important to notify the system that you have made a change. + // Do so by calling SHChangeNotify, specifying the SHCNE_ASSOCCHANGED event. + // If you do not call SHChangeNotify, the change might not be recognized until the system is rebooted. + SHChangeNotify(SHCNE_ASSOCCHANGED, 0, 0, 0); + + return S_OK; +} + +STDAPI DllUnregisterServer(void) +{ + const std::string guid_str_tmp = GuidToString(CLSID_ShellAnythingMenu).c_str(); + const char* guid_str = guid_str_tmp.c_str(); + const std::string class_name_version1 = std::string(ShellExtensionClassName) + ".1"; + const std::string guid_icontextmenu_tmp = GuidToString(IID_IContextMenu); + const std::string guid_icontextmenu = guid_icontextmenu_tmp.c_str(); + + //#define TRACELINE() MessageBox(NULL, (std::string("Line: ") + ra::strings::ToString(__LINE__)).c_str(), "DllUnregisterServer() DEBUG", MB_OK); + + // Removed the shell extension from the user's cached Shell Extensions + { + std::string key = "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Cached"; + std::string value = ra::strings::Format("%s {000214E4-0000-0000-C000-000000000046} 0xFFFF", guid_str); // {B0D35103-86A1-471C-A653-E130E3439A3B} {000214E4-0000-0000-C000-000000000046} 0xFFFF + if (!Win32Registry::DeleteValue(key.c_str(), value.c_str())) + return E_ACCESSDENIED; + } + + // Unregister the shell extension from the system's approved Shell Extensions + { + std::string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"; + if (!Win32Registry::DeleteValue(key.c_str(), guid_str)) + return E_ACCESSDENIED; + } + + // Unregister the shell extension for drives + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Drive\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Unregister the shell extension for the desktop or the file explorer's background + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\Background\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Unregister the shell extension for folders + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Folders\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Unregister the shell extension for directories + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Unregister the shell extension for all the file types + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\*\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Remove the CLSID of this DLL from the registry + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s", guid_str); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Unregister current and version 1 of our extension + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", class_name_version1.c_str()); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + { + std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", ShellExtensionClassName); + if (!Win32Registry::DeleteKey(key.c_str())) + return E_ACCESSDENIED; + } + + // Notify the Shell to pick the changes: + // https://docs.microsoft.com/en-us/windows/desktop/shell/reg-shell-exts#predefined-shell-objects + // Any time you create or change a Shell extension handler, it is important to notify the system that you have made a change. + // Do so by calling SHChangeNotify, specifying the SHCNE_ASSOCCHANGED event. + // If you do not call SHChangeNotify, the change might not be recognized until the system is rebooted. + SHChangeNotify(SHCNE_ASSOCCHANGED, 0, 0, 0); + + return S_OK; +} + +extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + ATTACH_HOOK_DEBUGGING; + + g_hmodDll = hInstance; + + DisableThreadLibraryCalls((HMODULE)hInstance); + + if (!shellanything::IsTestingEnvironment()) + { + // Initialize Google's logging library. + shellanything::InitLogger(); + } + + LogEnvironment(); + + // Initialize the configuration manager + InitConfigManager(); + } + else if (dwReason == DLL_PROCESS_DETACH) + { + if (!shellanything::IsTestingEnvironment()) + { + // Shutdown Google's logging library. + google::ShutdownGoogleLogging(); + } + } + return 1; +} + +//int main(int argc, char* argv[]) +//{ +// { +// HRESULT result = DllRegisterServer(); +// if (result == S_OK) +// //MessageBox(NULL, "Manual dll registration successfull", ShellExtensionClassName, MB_OK | MB_ICONINFORMATION); +// else +// //MessageBox(NULL, "Manual dll registration FAILED !", ShellExtensionClassName, MB_OK | MB_ICONERROR); +// } +// +// { +// HRESULT result = DllUnregisterServer(); +// if (result == S_OK) +// //MessageBox(NULL, "Manual dll unregistration successfull", ShellExtensionClassName, MB_OK | MB_ICONINFORMATION); +// else +// //MessageBox(NULL, "Manual dll unregistration FAILED !", ShellExtensionClassName, MB_OK | MB_ICONERROR); +// } +//} diff --git a/src/shellextension/logger_stub.h b/src/shellextension/logger_stub.h new file mode 100644 index 00000000..2b304286 --- /dev/null +++ b/src/shellextension/logger_stub.h @@ -0,0 +1,238 @@ +#pragma once + +// Stubbing code from supporting `LOG(INFO) << "my log text"` expressions + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rapidassist/strings.h" + + +enum LOG_LEVELS +{ + LOG_INFO, + LOG_WARNING, + LOG_ERROR, +}; + +enum LOG_FLAGS +{ + LOG_FLAGS_NONE = 0, + LOG_FLAGS_NEWLINE = 1, + LOG_FLAGS_LEVEL = 2, + LOG_FLAGS_TIME = 4, +}; + +const char* GetLogLevelDesc(LOG_LEVELS level) +{ + switch (level) + { + case 0: + return "INFO"; + case 1: + return "WARNING"; + case 2: + return "ERROR"; + default: + return "UNKNOWN"; + } +} + +// https://stackoverflow.com/questions/24686846/get-current-time-in-milliseconds-or-hhmmssmmm-format +std::string get_time_in_HH_MM_SS_MMM() +{ + using namespace std::chrono; + + // get current time + auto now = system_clock::now(); + + // get number of milliseconds for the current second + // (remainder after division into seconds) + auto ms = duration_cast(now.time_since_epoch()) % 1000; + + // convert to std::time_t in order to convert to std::tm (broken time) + auto timer = system_clock::to_time_t(now); + + // convert to broken time + std::tm bt = *std::localtime(&timer); + + std::ostringstream oss; + + oss << std::put_time(&bt, "%H:%M:%S"); // HH:MM:SS + oss << '.' << std::setfill('0') << std::setw(3) << ms.count(); + + return oss.str(); +} + +class LogApp +{ +private: + FILE* file; + LOG_LEVELS level; + +public: + LogApp() + { + level = LOG_INFO; + file = NULL; + InitializeLogger(); + } + ~LogApp() + { + TerminateLogger(); + } + void SetLevel(LOG_LEVELS level) + { + this->level = level; + } + LOG_LEVELS GetLevel() const + { + return level; + } + void Log(const char* value) + { + fputs(value, file); + //std::cout << value; + } + inline void Log(const std::string& value) + { + Log(value.c_str()); + } + void Log(const char* value, int flags) + { + if (file == NULL) + return; + + if (flags & LOG_FLAGS_LEVEL) + { + std::string output; + output += GetLogLevelDesc(level); + output += ": "; + + Log(output); + } + if (flags & LOG_FLAGS_TIME) + { + std::string output; + output += get_time_in_HH_MM_SS_MMM(); + output += " "; + + Log(output); + } + + Log(value); + + if (flags & LOG_FLAGS_NEWLINE) + { + Log("\n"); + } + } + inline void LogLn(const char* value, int flags) + { + std::string tmp; + tmp += value; + tmp += "\n"; + Log(tmp.c_str(), flags); + } + inline void Log(const std::string& value, int flags) + { + Log(value.c_str(), flags); + } + inline void LogLn(const std::string& value, int flags) + { + std::string tmp; + tmp += value; + tmp += "\n"; + Log(tmp, flags); + } + +private: + void InitializeLogger() + { + if (file != NULL) + return; + + DWORD wId = GetCurrentProcessId(); + char name[10240]; + sprintf(name, "C:\\Users\\Antoine\\Documents\\log.%06d.txt", wId); + + file = fopen(name, "w"); + } + + void TerminateLogger() + { + if (file == NULL) + return; + fclose(file); + file = NULL; + } +}; + +LogApp log_app; + +class Logger : public std::stringstream +{ +private: + bool first_print; +public: + Logger(LOG_LEVELS level) + { + log_app.SetLevel(level); + first_print = true; + } + Logger& operator<<(const char* right) + { + if (first_print) + { + log_app.Log("\n"); // newline from previous LOG(xxx) line + log_app.Log(right, LOG_FLAGS_LEVEL | LOG_FLAGS_TIME); + } + else + { + log_app.Log(right, 0); + } + first_print = false; + + return *this; + } + Logger& operator<<(const std::string& right) + { + (*this) << right.c_str(); + return *this; + } + Logger& operator<<(const void* right) + { + std::string tmp; + (*this) << tmp; + return *this; + } + Logger& operator<<(const UINT& right) + { + std::string tmp; + (*this) << tmp; + return *this; + } + Logger& operator<<(const ULONG& right) + { + std::string tmp; + (*this) << tmp; + return *this; + } +}; +#define LOG(expr) (Logger(LOG_##expr)) + + +namespace shellanything +{ + + void ShowErrorMessage(const std::string& title, const std::string& message) + { + } + +} //namespace shellanything diff --git a/src/shellextension/shellext.cpp b/src/shellextension/shellext.cpp index ac20718c..7481c49b 100644 --- a/src/shellextension/shellext.cpp +++ b/src/shellextension/shellext.cpp @@ -23,15 +23,8 @@ *********************************************************************************/ - //#define WIN32_LEAN_AND_MEAN 1 -#include - -#include -#include -#include -#include -#include -#include +#include "stdafx.h" +#include "shellext.h" #pragma warning( push ) #pragma warning( disable: 4355 ) // glog\install_dir\include\glog/logging.h(1167): warning C4355: 'this' : used in base member initializer list @@ -39,18 +32,11 @@ #pragma warning( pop ) #include "ErrorManager.h" -#include "shellext.h" #include "Win32Registry.h" #include "Win32Utils.h" #include "GlogUtils.h" -#include "Unicode.h" - -#undef GetEnvironmentVariable -#undef DeleteFile -#undef CreateDirectory -#undef CopyFile -#undef CreateFile +#include "rapidassist/undef_windows_macros.h" #include "rapidassist/strings.h" #include "rapidassist/filesystem_utf8.h" #include "rapidassist/process_utf8.h" @@ -60,26 +46,19 @@ #include "rapidassist/environment.h" #include "shellanything/version.h" +#include "shellanything/config.h" + #include "ConfigManager.h" #include "PropertyManager.h" #include "SaUtils.h" #include -//Declarations -UINT g_cRefDll = 0; // Reference counter of this DLL -HINSTANCE g_hmodDll = 0; // HINSTANCE of the DLL - -static const GUID CLSID_UNDOCUMENTED_01 = { 0x924502a7, 0xcc8e, 0x4f60, { 0xae, 0x1f, 0xf7, 0x0c, 0x0a, 0x2b, 0x7a, 0x7c } }; - -HMENU CContextMenu::m_previousMenu = 0; - std::string GuidToString(GUID guid) { - std::string output; - output.assign(40, 0); - sprintf_s((char*)output.c_str(), output.size(), "{%08X-%04hX-%04hX-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - return output; + char buffer[64]; + sprintf_s(buffer, sizeof(buffer), "{%08X-%04hX-%04hX-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + return buffer; } std::string GuidToInterfaceName(GUID guid) @@ -93,18 +72,44 @@ std::string GuidToInterfaceName(GUID guid) if (IsEqualGUID(guid, IID_IObjectWithSite)) return "IObjectWithSite"; //{FC4801A3-2BA9-11CF-A229-00AA003D7352} if (IsEqualGUID(guid, IID_IInternetSecurityManager)) return "IInternetSecurityManager"; //{79EAC9EE-BAF9-11CE-8C82-00AA004BA90B} + if (IsEqualGUID(guid, CLSID_ShellAnythingMenu)) return "ShellAnything"; + //unknown GUID, return the string representation: //ie: CLSID_UNDOCUMENTED_01, {924502A7-CC8E-4F60-AE1F-F70C0A2B7A7C} return GuidToString(guid); } -/// -/// Returns true if the application is run for the first time. -/// Note, for Windows users, the implementation is based on registry keys in HKEY_CURRENT_USER\Software\name\version. -/// -/// The name of the application. -/// The version of the application. -/// Returns true if the application is run for the first time. Returns false otherwise. +std::string GetProcessContextDesc() +{ + std::string desc; + desc.reserve(32); + desc += "pid="; + desc += ra::strings::ToString(ra::process::GetCurrentProcessId()); + desc += " tid="; + desc += ra::strings::ToString((uint32_t)GetCurrentThreadId()); + return desc; +} + +std::string ToHexString(void* value) +{ + size_t address = reinterpret_cast(value); + char buffer[1024]; + static bool is_32bit = (sizeof(address) == 4); + static bool is_64bit = (sizeof(address) == 8); +#ifdef _WIN32 + if (is_32bit) + sprintf(buffer, "0x%Ix", address); + else if (is_64bit) + sprintf(buffer, "0x%Ix", address); +#else + if (is_32bit) + sprintf(buffer, "0x%zx", address); + else if (is_64bit) + sprintf(buffer, "0x%zx", address); +#endif + return buffer; +} + bool IsFirstApplicationRun(const std::string& name, const std::string& version) { std::string key = ra::strings::Format("HKEY_CURRENT_USER\\Software\\%s\\%s", name.c_str(), version.c_str()); @@ -200,291 +205,7 @@ template class FlagDescriptor } }; -void CContextMenu::BuildMenuTree(HMENU hMenu, shellanything::Menu* menu, UINT& insert_pos, bool& next_menu_is_column) -{ - //Expanded the menu's strings - shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); - std::string title = pmgr.Expand(menu->GetName()); - std::string description = pmgr.Expand(menu->GetDescription()); - - //Get visible/enable properties based on current context. - bool menu_visible = menu->IsVisible(); - bool menu_enabled = menu->IsEnabled(); - bool menu_separator = menu->IsSeparator(); - bool menu_column = menu->IsColumnSeparator(); - - //Skip column separator, those are not menu item - if (menu_column) - { - next_menu_is_column = true; - return; - } - - //Skip this menu if not visible - if (!menu_visible) - { - menu->TruncateName(title); - LOG(INFO) << __FUNCTION__ << "(), skipped menu '" << title << "', not visible."; - return; - } - - //Validate menus integrity - const uint32_t& menu_command_id = menu->GetCommandId(); - if (menu_command_id == shellanything::Menu::INVALID_COMMAND_ID) - { - menu->TruncateName(title); - LOG(ERROR) << __FUNCTION__ << "(), menu '" << title << "' have invalid command id."; - return; - } - - // Truncate if required, issue #55. - menu->TruncateName(title); - menu->TruncateName(description); - - //convert to windows unicode... - std::wstring title_utf16 = ra::unicode::Utf8ToUnicode(title); - std::wstring desc_utf16 = ra::unicode::Utf8ToUnicode(description); - - MENUITEMINFOW menuinfo = { 0 }; - - menuinfo.cbSize = sizeof(MENUITEMINFOW); - menuinfo.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_ID | MIIM_STRING; - menuinfo.fType = (menu_separator ? MFT_SEPARATOR : MFT_STRING); - menuinfo.fType += (next_menu_is_column ? MFT_MENUBARBREAK : 0); - menuinfo.fState = (menu_enabled ? MFS_ENABLED : MFS_DISABLED); - menuinfo.wID = menu_command_id; - menuinfo.dwTypeData = (wchar_t*)title_utf16.c_str(); - menuinfo.cch = (UINT)title_utf16.size(); - - //add an icon - const shellanything::Icon& icon = menu->GetIcon(); - if (!menu_separator && icon.IsValid()) - { - shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); - std::string file_extension = pmgr.Expand(icon.GetFileExtension()); - std::string icon_filename = pmgr.Expand(icon.GetPath()); - int icon_index = icon.GetIndex(); - - //if the icon is pointing to a file extension - if (!file_extension.empty()) - { - //resolve the file extension to a system icon. - - //did we already resolved this icon? - IconMap::iterator wExtensionsIterator = m_FileExtensionCache.find(file_extension); - bool found = (wExtensionsIterator != m_FileExtensionCache.end()); - if (found) - { - //already resolved - const shellanything::Icon& resolved_icon = wExtensionsIterator->second; - icon_filename = resolved_icon.GetPath(); - icon_index = resolved_icon.GetIndex(); - } - else - { - //not found - - //make a copy of the icon and resolve the file extension to a system icon. - shellanything::Icon resolved_icon = icon; - resolved_icon.ResolveFileExtensionIcon(); - - //save the icon for a future use - m_FileExtensionCache[file_extension] = resolved_icon; - - //use the resolved icon location - icon_filename = resolved_icon.GetPath(); - icon_index = resolved_icon.GetIndex(); - } - } - - //ask the cache for an existing icon. - //this will identify the icon in the cache as "used" or "active". - HBITMAP hBitmap = m_BitmapCache.FindHandle(icon_filename, icon_index); - - //if nothing in cache, create a new one - if (hBitmap == shellanything::BitmapCache::INVALID_BITMAP_HANDLE) - { - HICON hIconLarge = NULL; - HICON hIconSmall = NULL; - - // - std::wstring icon_filename_wide = ra::unicode::Utf8ToUnicode(icon_filename); - UINT numIconInFile = ExtractIconExW(icon_filename_wide.c_str(), -1, NULL, NULL, 1); - UINT numIconLoaded = ExtractIconExW(icon_filename_wide.c_str(), icon_index, &hIconLarge, &hIconSmall, 1); - if (numIconLoaded >= 1) - { - //Find the best icon - HICON hIcon = Win32Utils::GetBestIconForMenu(hIconLarge, hIconSmall); - - //Convert the icon to a bitmap (with invisible background) - hBitmap = Win32Utils::CopyAsBitmap(hIcon); - - DestroyIcon(hIconLarge); - DestroyIcon(hIconSmall); - - //add the bitmap to the cache for future use - m_BitmapCache.AddHandle(icon_filename.c_str(), icon_index, hBitmap); - } - } - - //if a bitmap is created - if (hBitmap != shellanything::BitmapCache::INVALID_BITMAP_HANDLE) - { - //enable bitmap handling for the menu - menuinfo.fMask |= MIIM_BITMAP; - - //assign the HBITMAP to the HMENU - menuinfo.hbmpItem = hBitmap; - } - } - - //handle column separator - if (next_menu_is_column) - { - menuinfo.fType |= MFT_MENUBARBREAK; - } - next_menu_is_column = false; - - //handle submenus - if (menu->IsParentMenu()) - { - menuinfo.fMask |= MIIM_SUBMENU; - HMENU hSubMenu = CreatePopupMenu(); - - bool next_sub_menu_is_column = false; - - shellanything::Menu::MenuPtrList subs = menu->GetSubMenus(); - UINT sub_insert_pos = 0; - for (size_t i = 0; i < subs.size(); i++) - { - shellanything::Menu* submenu = subs[i]; - BuildMenuTree(hSubMenu, submenu, sub_insert_pos, next_sub_menu_is_column); - } - - menuinfo.hSubMenu = hSubMenu; - } - - BOOL result = InsertMenuItemW(hMenu, insert_pos, TRUE, &menuinfo); - insert_pos++; //next menu is below this one - - LOG(INFO) << __FUNCTION__ << "(), insert.pos=" << ra::strings::Format("%03d", insert_pos) << ", id=" << ra::strings::Format("%06d", menuinfo.wID) << ", result=" << result << ", title=" << title; -} - -void CContextMenu::BuildMenuTree(HMENU hMenu) -{ - //Bitmap ressources must be properly destroyed. - //When a menu (HMENU handle) is destroyed using win32 DestroyMenu() function, it also destroy the child menus: - //https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-destroymenu - // - //However the bitmap assigned to menus are not deleted with DestroyMenu() function. - //Bitmap is a limited resource. If you ran out of GDI resources, you may see black menus - //and Windows Explorer will have difficulties to render all the window. For details, see - //https://www.codeproject.com/Questions/1228261/Windows-shell-extension - // - //To prevent running out of bitmap ressource we use the shellanything::BitmapCache class. - //Each bitmap is identified as 'used' in CContextMenu::BuildMenuTree() with 'm_BitmapCache.FindHandle()'. - //Every 5 times the shell extension popup is displayed, we look for 'unused' bitmap and delete them. - // - - //handle destruction of old bitmap in the cache - m_BuildMenuTreeCount++; - if (m_BuildMenuTreeCount > 0 && (m_BuildMenuTreeCount % 5) == 0) - { - //every 10 calls, refresh the cache - m_BitmapCache.DestroyOldHandles(); - - //reset counters - m_BitmapCache.ResetCounters(); - } - - bool next_menu_is_column = false; - - //browse through all shellanything menus and build the win32 popup menus - - //for each configuration - shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); - shellanything::Configuration::ConfigurationPtrList configs = cmgr.GetConfigurations(); - UINT insert_pos = 0; - for (size_t i = 0; i < configs.size(); i++) - { - shellanything::Configuration* config = configs[i]; - if (config) - { - //for each menu child - shellanything::Menu::MenuPtrList menus = config->GetMenus(); - for (size_t j = 0; j < menus.size(); j++) - { - shellanything::Menu* menu = menus[j]; - - //Add this menu to the tree - BuildMenuTree(hMenu, menu, insert_pos, next_menu_is_column); - } - } - } -} - -CCriticalSection::CCriticalSection() -{ - InitializeCriticalSection(&mCS); -} - -CCriticalSection::~CCriticalSection() -{ - DeleteCriticalSection(&mCS); -} - -void CCriticalSection::Enter() -{ - EnterCriticalSection(&mCS); -} - -void CCriticalSection::Leave() -{ - LeaveCriticalSection(&mCS); -} - -CCriticalSectionGuard::CCriticalSectionGuard(CCriticalSection* cs) -{ - mCS = cs; - if (mCS) - { - mCS->Enter(); - } -} - -CCriticalSectionGuard::~CCriticalSectionGuard() -{ - if (mCS) - { - mCS->Leave(); - } - mCS = NULL; -} - -CContextMenu::CContextMenu() -{ - LOG(INFO) << __FUNCTION__ << "(), new"; - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - - m_cRef = 0L; - m_FirstCommandId = 0; - m_IsBackGround = false; - m_BuildMenuTreeCount = 0; - - // Increment the dll's reference counter. - InterlockedIncrement(&g_cRefDll); -} - -CContextMenu::~CContextMenu() -{ - LOG(INFO) << __FUNCTION__ << "(), delete"; - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - - // Decrement the dll's reference counter. - InterlockedDecrement(&g_cRefDll); -} - -HRESULT STDMETHODCALLTYPE CContextMenu::QueryContextMenu(HMENU hMenu, UINT menu_index, UINT first_command_id, UINT max_command_id, UINT flags) +std::string GetQueryContextMenuFlags(UINT flags) { //build function descriptor static const FlagDescriptor::FLAGS descriptors[] = { @@ -505,180 +226,10 @@ HRESULT STDMETHODCALLTYPE CContextMenu::QueryContextMenu(HMENU hMenu, UINT menu_ {NULL, NULL}, }; std::string flags_str = FlagDescriptor::ToBitString(flags, descriptors); - std::string flags_hex = ra::strings::Format("0x%08x", flags); - - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - LOG(INFO) << __FUNCTION__ << "(), hMenu=0x" << ra::strings::Format("0x%08x", hMenu).c_str() << ",count=" << GetMenuItemCount(hMenu) << ", menu_index=" << menu_index << ", first_command_id=" << first_command_id << ", max_command_id=" << max_command_id << ", flags=" << flags_hex << "=(" << flags_str << ")"; - - //https://docs.microsoft.com/en-us/windows/desktop/shell/how-to-implement-the-icontextmenu-interface - - //From this point, it is safe to use class members without other threads interference - CCriticalSectionGuard cs_guard(&m_CS); - - //Note on flags... - //Right-click on a file or directory with Windows Explorer on the right area: flags=0x00020494=132244(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_CANRENAME|CMF_ITEMMENU|CMF_ASYNCVERBSTATE) - //Right-click on the empty area with Windows Explorer on the right area: flags=0x00020424=132132(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_NODEFAULT|CMF_ASYNCVERBSTATE) - //Right-click on a directory with Windows Explorer on the left area: flags=0x00000414=001044(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_CANRENAME|CMF_ASYNCVERBSTATE) - //Right-click on a drive with Windows Explorer on the left area: flags=0x00000414=001044(dec)=(CMF_NORMAL|CMF_EXPLORE|CMF_CANRENAME|CMF_ASYNCVERBSTATE) - //Right-click on the empty area on the Desktop: flags=0x00020420=132128(dec)=(CMF_NORMAL|CMF_NODEFAULT|CMF_ASYNCVERBSTATE) - //Right-click on a directory on the Desktop: flags=0x00020490=132240(dec)=(CMF_NORMAL|CMF_CANRENAME|CMF_ITEMMENU|CMF_ASYNCVERBSTATE) - - //Filter out queries that have nothing selected. - //This can happend if user is copy & pasting files (using CTRL+C and CTRL+V) - //and if the shell extension is registered as a DragDropHandlers. - if (m_Context.GetElements().size() == 0) - { - //Don't know what to do with this - LOG(INFO) << __FUNCTION__ << "(), skipped, nothing is selected."; - return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); //nothing inserted - } - - //Filter out queries that are called twice for the same directory. - if (hMenu == CContextMenu::m_previousMenu) - { - //Issue #6 - Right-click on a directory with Windows Explorer in the left panel shows the menus twice. - //Issue #31 - Error in logs for CContextMenu::GetCommandString(). - //Using a static variable is a poor method for solving the issue but it is a "good enough" strategy. - LOG(INFO) << __FUNCTION__ << "(), skipped, QueryContextMenu() called twice and menu is already populated once."; - return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); //nothing inserted - } - - //Remember current menu to prevent issues calling twice QueryContextMenu() - m_previousMenu = hMenu; - - //MessageBox(NULL, "ATTACH NOW!", (std::string("ATTACH NOW!") + " " + __FUNCTION__).c_str(), MB_OK); - - //Log what is selected by the user - const shellanything::StringList& elements = m_Context.GetElements(); - size_t num_selected_total = elements.size(); - int num_files = m_Context.GetNumFiles(); - int num_directories = m_Context.GetNumDirectories(); - LOG(INFO) << "SelectionContext have " << num_selected_total << " element(s): " << num_files << " files and " << num_directories << " directories."; - - //Keep a reference the our first command id. We will need it when InvokeCommand is called. - m_FirstCommandId = first_command_id; - - //Refresh the list of loaded configuration files - shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); - cmgr.Refresh(); - - //Update all menus with the new context - //This will refresh the visibility flags which is required before calling ConfigManager::AssignCommandIds() - cmgr.Update(m_Context); - - //Assign unique command id to visible menus. Issue #5 - UINT next_command_id = cmgr.AssignCommandIds(first_command_id); - - //Build the menus - BuildMenuTree(hMenu); - - //Log information about menu statistics. - UINT menu_last_command_id = (UINT)-1; //confirmed last command id - if (next_command_id != first_command_id) - menu_last_command_id = next_command_id - 1; - UINT num_menu_items = next_command_id - first_command_id; - LOG(INFO) << "Menu: first_command_id=" << first_command_id << " menu_last_command_id=" << menu_last_command_id << " next_command_id=" << next_command_id << " num_menu_items=" << num_menu_items << ".\n"; - - //debug the constructed menu tree -#ifdef _DEBUG - std::string menu_tree = Win32Utils::GetMenuTree(hMenu, 2); - LOG(INFO) << "Menu tree:\n" << menu_tree.c_str(); -#endif - - HRESULT hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, num_menu_items); - return hr; + return flags_str; } -HRESULT STDMETHODCALLTYPE CContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) -{ - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - - //define the type of structure pointed by lpcmi - const char* struct_name = "UNKNOWN"; - if (lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFO)) - struct_name = "CMINVOKECOMMANDINFO"; - else if (lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFOEX)) - struct_name = "CMINVOKECOMMANDINFOEX"; - - //define how we should interpret lpcmi->lpVerb - std::string verb; - if (IS_INTRESOURCE(lpcmi->lpVerb)) - // D:\Projects\ShellAnything\src\shellext.cpp(632) : warning C4311 : 'reinterpret_cast' : pointer truncation from 'LPCSTR' to 'int' - // D:\Projects\ShellAnything\src\shellext.cpp(632) : warning C4302 : 'reinterpret_cast' : truncation from 'LPCSTR' to 'int' -#pragma warning( push ) -#pragma warning( disable: 4302 ) -#pragma warning( disable: 4311 ) - verb = ra::strings::ToString(reinterpret_cast(lpcmi->lpVerb)); -#pragma warning( pop ) - else - verb = lpcmi->lpVerb; - - LOG(INFO) << __FUNCTION__ << "(), lpcmi->cbSize=" << struct_name << ", lpcmi->fMask=" << lpcmi->fMask << ", lpcmi->lpVerb=" << verb; - - //validate - if (!IS_INTRESOURCE(lpcmi->lpVerb)) - return E_INVALIDARG; //don't know what to do with lpcmi->lpVerb - - UINT target_command_offset = LOWORD(lpcmi->lpVerb); //matches the command_id offset (command id of the selected menu - command id of the first menu) - UINT target_command_id = m_FirstCommandId + target_command_offset; - - //From this point, it is safe to use class members without other threads interference - CCriticalSectionGuard cs_guard(&m_CS); - - //find the menu that is requested - shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); - shellanything::Menu* menu = cmgr.FindMenuByCommandId(target_command_id); - if (menu == NULL) - { - LOG(ERROR) << __FUNCTION__ << "(), unknown menu for lpcmi->lpVerb=" << verb; - return E_INVALIDARG; - } - - //compute the visual menu title - shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); - std::string title = pmgr.Expand(menu->GetName()); - - //found a menu match, execute menu action - LOG(INFO) << __FUNCTION__ << "(), executing action(s) for menu '" << title.c_str() << "'..."; - - //execute actions - const shellanything::IAction::ActionPtrList& actions = menu->GetActions(); - for (size_t i = 0; i < actions.size(); i++) - { - LOG(INFO) << __FUNCTION__ << "(), executing action " << (i + 1) << " of " << actions.size() << "."; - const shellanything::IAction* action = actions[i]; - if (action) - { - ra::errors::ResetLastErrorCode(); //reset win32 error code in case the action fails. - bool success = action->Execute(m_Context); - - if (!success) - { - //try to get an error message from win32 - ra::errors::errorcode_t dwError = ra::errors::GetLastErrorCode(); - if (dwError) - { - std::string error_message = ra::errors::GetErrorCodeDescription(dwError); - LOG(ERROR) << __FUNCTION__ << "(), action #" << (i + 1) << " has failed: " << error_message; - } - else - { - //simply log an error - LOG(ERROR) << __FUNCTION__ << "(), action #" << (i + 1) << " has failed."; - } - - //stop executing the next actions - i = actions.size(); - } - } - } - - LOG(INFO) << __FUNCTION__ << "(), executing action(s) for menu '" << title.c_str() << "' completed."; - - return S_OK; -} - -HRESULT STDMETHODCALLTYPE CContextMenu::GetCommandString(UINT_PTR command_id, UINT flags, UINT FAR* reserved, LPSTR pszName, UINT cchMax) +std::string GetGetCommandStringFlags(UINT flags) { //build function descriptor static const FlagDescriptor::FLAGS descriptors[] = { @@ -692,590 +243,8 @@ HRESULT STDMETHODCALLTYPE CContextMenu::GetCommandString(UINT_PTR command_id, UI {GCS_UNICODE , "GCS_UNICODE" }, {NULL, NULL}, }; - std::string flags_str = FlagDescriptor::ToValueString(flags, descriptors); - std::string flags_hex = ra::strings::Format("0x%08x", flags); - - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - //LOG(INFO) << __FUNCTION__ << "(), command_id=" << command_id << ", reserved=" << reserved << ", pszName=" << pszName << ", cchMax=" << cchMax << ", flags=" << flags_hex << "=(" << flags_str << ")"; - - UINT target_command_offset = (UINT)command_id; //matches the command_id offset (command id of the selected menu substracted by command id of the first menu) - UINT target_command_id = m_FirstCommandId + target_command_offset; - - //From this point, it is safe to use class members without other threads interference - CCriticalSectionGuard cs_guard(&m_CS); - - //find the menu that is requested - shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); - shellanything::Menu* menu = cmgr.FindMenuByCommandId(target_command_id); - if (menu == NULL) - { - LOG(ERROR) << __FUNCTION__ << "(), unknown menu for command_id=" << target_command_offset << " m_FirstCommandId=" << m_FirstCommandId << " target_command_id=" << target_command_id; - return E_INVALIDARG; - } - - //compute the visual menu description - shellanything::PropertyManager& pmgr = shellanything::PropertyManager::GetInstance(); - std::string description = pmgr.Expand(menu->GetDescription()); - - //convert to windows unicode... - std::wstring desc_utf16 = ra::unicode::Utf8ToUnicode(description); - std::string desc_ansi = ra::unicode::Utf8ToAnsi(description); - - //Build up tooltip string - switch (flags) - { - case GCS_HELPTEXTA: - { - //ANIS tooltip handling - lstrcpynA(pszName, desc_ansi.c_str(), cchMax); - return S_OK; - } - break; - case GCS_HELPTEXTW: - { - //UNICODE tooltip handling - lstrcpynW((LPWSTR)pszName, desc_utf16.c_str(), cchMax); - return S_OK; - } - break; - case GCS_VERBA: - { - //ANIS tooltip handling - lstrcpynA(pszName, desc_ansi.c_str(), cchMax); - return S_OK; - } - break; - case GCS_VERBW: - { - //UNICODE tooltip handling - lstrcpynW((LPWSTR)pszName, desc_utf16.c_str(), cchMax); - return S_OK; - } - break; - case GCS_VALIDATEA: - case GCS_VALIDATEW: - { - return S_OK; - } - break; - } - - LOG(ERROR) << __FUNCTION__ << "(), unknown flags: " << flags; - return S_FALSE; -} - -HRESULT STDMETHODCALLTYPE CContextMenu::Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hRegKey) -{ - LOG(INFO) << __FUNCTION__ << "(), pIDFolder=" << (void*)pIDFolder; - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - - //From this point, it is safe to use class members without other threads interference - CCriticalSectionGuard cs_guard(&m_CS); - - shellanything::StringList files; - - // Cleanup - m_Context.UnregisterProperties(); //Unregister the previous context properties - m_Context.SetElements(files); - m_IsBackGround = false; - - // Did we clicked on a folder's background or the desktop directory? - if (pIDFolder) - { - LOG(INFO) << "User right-clicked on a background directory."; - - wchar_t szPath[2 * MAX_PATH] = { 0 }; - - if (SHGetPathFromIDListW(pIDFolder, szPath)) - { - if (szPath[0] != '\0') - { - std::string path_utf8 = ra::unicode::UnicodeToUtf8(szPath); - LOG(INFO) << "Found directory '" << path_utf8 << "'."; - m_IsBackGround = true; - files.push_back(path_utf8); - } - else - return E_INVALIDARG; - } - else - { - return E_INVALIDARG; - } - } - - // User clicked on one or more file or directory - else if (pDataObj) - { - LOG(INFO) << "User right-clicked on selected files/directories."; - - FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - STGMEDIUM stg = { TYMED_HGLOBAL }; - HDROP hDropInfo; - - // The selected files are expected to be in HDROP format. - if (FAILED(pDataObj->GetData(&fmt, &stg))) - return E_INVALIDARG; - - // Get a locked pointer to the files data. - hDropInfo = (HDROP)GlobalLock(stg.hGlobal); - if (NULL == hDropInfo) - { - ReleaseStgMedium(&stg); - return E_INVALIDARG; - } - - UINT num_files = DragQueryFileW(hDropInfo, 0xFFFFFFFF, NULL, 0); - LOG(INFO) << "User right-clicked on " << num_files << " files/directories."; - - // For each files - for (UINT i = 0; i < num_files; i++) - { - UINT length = DragQueryFileW(hDropInfo, i, NULL, 0); - - // Allocate a temporary buffer - std::wstring path(length, '\0'); - if (path.size() != length) - continue; - size_t num_characters = length + 1; - - // Copy the element into the temporary buffer - DragQueryFileW(hDropInfo, i, (wchar_t*)path.data(), (UINT)num_characters); - - //add the new file - std::string path_utf8 = ra::unicode::UnicodeToUtf8(path); - LOG(INFO) << "Found file/directory #" << ra::strings::Format("%03d", i) << ": '" << path_utf8 << "'."; - files.push_back(path_utf8); - } - GlobalUnlock(stg.hGlobal); - ReleaseStgMedium(&stg); - } - - //update the selection context - m_Context.SetElements(files); - - //Register the current context properties so that menus can display the right caption - m_Context.RegisterProperties(); - - return S_OK; -} - -HRESULT STDMETHODCALLTYPE CContextMenu::QueryInterface(REFIID riid, LPVOID FAR* ppvObj) -{ - //build function descriptor - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - LOG(INFO) << __FUNCTION__ << "(), riid=" << GuidToInterfaceName(riid).c_str() << ", ppvObj=" << ppvObj; - - //Filter out unimplemented know interfaces so they do not show as WARNINGS - if (IsEqualGUID(riid, IID_IObjectWithSite) || //{FC4801A3-2BA9-11CF-A229-00AA003D7352} - IsEqualGUID(riid, IID_IInternetSecurityManager) || //{79EAC9EE-BAF9-11CE-8C82-00AA004BA90B} - IsEqualGUID(riid, CLSID_UNDOCUMENTED_01) || - IsEqualGUID(riid, IID_IContextMenu2) || //{000214f4-0000-0000-c000-000000000046} - IsEqualGUID(riid, IID_IContextMenu3) //{BCFCE0A0-EC17-11d0-8D10-00A0C90F2719} - ) - { - return E_NOINTERFACE; - } - - //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus - - // Always set out parameter to NULL, validating it first. - if (!ppvObj) - return E_INVALIDARG; - - *ppvObj = NULL; - if (IsEqualGUID(riid, IID_IUnknown)) - { - *ppvObj = (LPVOID)this; - } - else if (IsEqualGUID(riid, IID_IShellExtInit)) - { - *ppvObj = (LPSHELLEXTINIT)this; - } - else if (IsEqualGUID(riid, IID_IContextMenu)) - { - *ppvObj = (LPCONTEXTMENU)this; - } - - if (*ppvObj) - { - // Increment the reference count and return the pointer. - LOG(INFO) << __FUNCTION__ << "(), found interface " << GuidToInterfaceName(riid).c_str(); - AddRef(); - return NOERROR; - } - - LOG(WARNING) << __FUNCTION__ << "(), NOT FOUND: " << GuidToInterfaceName(riid).c_str(); - return E_NOINTERFACE; -} - -ULONG STDMETHODCALLTYPE CContextMenu::AddRef() -{ - //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus - - // Increment the object's internal counter. - InterlockedIncrement(&m_cRef); - return m_cRef; -} - -ULONG STDMETHODCALLTYPE CContextMenu::Release() -{ - //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus - - // Decrement the object's internal counter. - ULONG ulRefCount = InterlockedDecrement(&m_cRef); - if (0 == m_cRef) - { - delete this; - } - return ulRefCount; -} - -// Constructeur de l'interface IClassFactory: -CClassFactory::CClassFactory() -{ - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - LOG(INFO) << __FUNCTION__ << "(), new"; - - m_cRef = 0L; - - // Increment the dll's reference counter. - InterlockedIncrement(&g_cRefDll); -} - -// Destructeur de l'interface IClassFactory: -CClassFactory::~CClassFactory() -{ - LOG(INFO) << __FUNCTION__ << "(), delete"; - - // Decrement the dll's reference counter. - InterlockedDecrement(&g_cRefDll); -} - -HRESULT STDMETHODCALLTYPE CClassFactory::QueryInterface(REFIID riid, LPVOID FAR* ppvObj) -{ - //build function descriptor - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - LOG(INFO) << __FUNCTION__ << "(), riid=" << GuidToInterfaceName(riid).c_str() << ", ppvObj=" << ppvObj; - - //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus - - // Always set out parameter to NULL, validating it first. - if (!ppvObj) - return E_INVALIDARG; - *ppvObj = NULL; - - if (IsEqualGUID(riid, IID_IUnknown)) - { - *ppvObj = (LPVOID)this; - } - else if (IsEqualGUID(riid, IID_IClassFactory)) - { - *ppvObj = (LPCLASSFACTORY)this; - } - - if (*ppvObj) - { - // Increment the reference count and return the pointer. - LOG(INFO) << __FUNCTION__ << "(), found interface " << GuidToInterfaceName(riid).c_str(); - AddRef(); - return NOERROR; - } - - LOG(WARNING) << __FUNCTION__ << "(), NOT FOUND: " << GuidToInterfaceName(riid).c_str(); - return E_NOINTERFACE; -} - -ULONG STDMETHODCALLTYPE CClassFactory::AddRef() -{ - //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus - - // Increment the object's internal counter. - InterlockedIncrement(&m_cRef); - return m_cRef; -} - -ULONG STDMETHODCALLTYPE CClassFactory::Release() -{ - //https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/implementing-iunknown-in-c-plus-plus - - // Decrement the object's internal counter. - ULONG ulRefCount = InterlockedDecrement(&m_cRef); - if (0 == m_cRef) - { - delete this; - } - return ulRefCount; -} - -HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID FAR* ppvObj) -{ - //build function descriptor - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - LOG(INFO) << __FUNCTION__ << "(), pUnkOuter=" << pUnkOuter << ", riid=" << GuidToInterfaceName(riid).c_str(); - - *ppvObj = NULL; - if (pUnkOuter) return CLASS_E_NOAGGREGATION; - CContextMenu* pContextMenu = new CContextMenu(); - if (!pContextMenu) return E_OUTOFMEMORY; - HRESULT hr = pContextMenu->QueryInterface(riid, ppvObj); - if (FAILED(hr)) - { - delete pContextMenu; - pContextMenu = NULL; - } - return hr; -} - -HRESULT STDMETHODCALLTYPE CClassFactory::LockServer(BOOL fLock) -{ - //https://docs.microsoft.com/en-us/windows/desktop/api/unknwnbase/nf-unknwnbase-iclassfactory-lockserver - //https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-colockobjectexternal - return S_OK; -} - -STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppvOut) -{ - //build function descriptor - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - LOG(INFO) << __FUNCTION__ << "(), rclsid=" << GuidToInterfaceName(rclsid).c_str() << ", riid=" << GuidToInterfaceName(riid).c_str() << ""; - - *ppvOut = NULL; - if (IsEqualGUID(rclsid, SHELLANYTHING_SHELLEXTENSION_CLSID)) - { - CClassFactory* pcf = new CClassFactory; - if (!pcf) return E_OUTOFMEMORY; - HRESULT hr = pcf->QueryInterface(riid, ppvOut); - if (FAILED(hr)) - { - LOG(ERROR) << __FUNCTION__ << "(), Interface " << GuidToInterfaceName(riid).c_str() << " not found!"; - delete pcf; - pcf = NULL; - } - LOG(INFO) << __FUNCTION__ << "(), found interface " << GuidToInterfaceName(riid).c_str(); - return hr; - } - LOG(ERROR) << __FUNCTION__ << "(), ClassFactory " << GuidToInterfaceName(rclsid).c_str() << " not found!"; - return CLASS_E_CLASSNOTAVAILABLE; -} - -STDAPI DllCanUnloadNow(void) -{ - //MessageBox(NULL, __FUNCTION__, __FUNCTION__, MB_OK); - - ULONG ulRefCount = 0; - ulRefCount = InterlockedIncrement(&g_cRefDll); - ulRefCount = InterlockedDecrement(&g_cRefDll); - - if (0 == ulRefCount) - { - LOG(INFO) << __FUNCTION__ << "() -> Yes"; - return S_OK; - } - LOG(INFO) << __FUNCTION__ << "() -> No, " << ulRefCount << " instances are still in use."; - return S_FALSE; -} - -STDAPI DllRegisterServer(void) -{ - const std::string guid_str_tmp = GuidToString(SHELLANYTHING_SHELLEXTENSION_CLSID).c_str(); - const char* guid_str = guid_str_tmp.c_str(); - const std::string class_name_version1 = std::string(ShellExtensionClassName) + ".1"; - const std::string module_path = GetCurrentModulePath(); - - //#define TRACELINE() MessageBox(NULL, (std::string("Line: ") + ra::strings::ToString(__LINE__)).c_str(), "DllUnregisterServer() DEBUG", MB_OK); - - //Register version 1 of our class - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", class_name_version1); - if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionDescription)) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s.1\\CLSID", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - //Register current version of our class - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionDescription)) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s\\CLSID", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s\\CurVer", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), class_name_version1.c_str())) - return E_ACCESSDENIED; - } - - // Add the CLSID of this DLL to the registry - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s", guid_str); - if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionDescription)) - return E_ACCESSDENIED; - } - - // Define the path and parameters of our DLL: - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\ProgID", guid_str); - if (!Win32Registry::CreateKey(key.c_str(), class_name_version1.c_str())) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\VersionIndependentProgID", guid_str); - if (!Win32Registry::CreateKey(key.c_str(), ShellExtensionClassName)) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\Programmable", guid_str); - if (!Win32Registry::CreateKey(key.c_str())) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s\\InprocServer32", guid_str); - if (!Win32Registry::CreateKey(key.c_str(), module_path.c_str())) - return E_ACCESSDENIED; - if (!Win32Registry::SetValue(key.c_str(), "ThreadingModel", "Apartment")) - return E_ACCESSDENIED; - } - - // Register the shell extension for all the file types - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\*\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - // Register the shell extension for directories - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - // Register the shell extension for folders - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Folder\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - // Register the shell extension for the desktop or the file explorer's background - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\Background\\ShellEx\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - // Register the shell extension for drives - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Drive\\ShellEx\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::CreateKey(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - // Register the shell extension to the system's approved Shell Extensions - { - std::string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"; - if (!Win32Registry::CreateKey(key.c_str())) - return E_ACCESSDENIED; - if (!Win32Registry::SetValue(key.c_str(), guid_str, ShellExtensionDescription)) - return E_ACCESSDENIED; - } - - // Notify the Shell to pick the changes: - // https://docs.microsoft.com/en-us/windows/desktop/shell/reg-shell-exts#predefined-shell-objects - // Any time you create or change a Shell extension handler, it is important to notify the system that you have made a change. - // Do so by calling SHChangeNotify, specifying the SHCNE_ASSOCCHANGED event. - // If you do not call SHChangeNotify, the change might not be recognized until the system is rebooted. - SHChangeNotify(SHCNE_ASSOCCHANGED, 0, 0, 0); - - return S_OK; -} - -STDAPI DllUnregisterServer(void) -{ - const std::string guid_str_tmp = GuidToString(SHELLANYTHING_SHELLEXTENSION_CLSID).c_str(); - const char* guid_str = guid_str_tmp.c_str(); - const std::string class_name_version1 = std::string(ShellExtensionClassName) + ".1"; - - //#define TRACELINE() MessageBox(NULL, (std::string("Line: ") + ra::strings::ToString(__LINE__)).c_str(), "DllUnregisterServer() DEBUG", MB_OK); - - // Unregister the shell extension from the system's approved Shell Extensions - { - std::string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"; - if (!Win32Registry::DeleteValue(key.c_str(), guid_str)) - return E_ACCESSDENIED; - } - - // Unregister the shell extension for drives - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Drive\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Unregister the shell extension for the desktop or the file explorer's background - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\Background\\ShellEx\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Unregister the shell extension for folders - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Folders\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Unregister the shell extension for directories - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\Directory\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Unregister the shell extension for all the file types - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\*\\shellex\\ContextMenuHandlers\\%s", ShellExtensionClassName); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Remove the CLSID of this DLL from the registry - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\CLSID\\%s", guid_str); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Unregister current and version 1 of our extension - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", class_name_version1.c_str()); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - { - std::string key = ra::strings::Format("HKEY_CLASSES_ROOT\\%s", ShellExtensionClassName); - if (!Win32Registry::DeleteKey(key.c_str())) - return E_ACCESSDENIED; - } - - // Notify the Shell to pick the changes: - // https://docs.microsoft.com/en-us/windows/desktop/shell/reg-shell-exts#predefined-shell-objects - // Any time you create or change a Shell extension handler, it is important to notify the system that you have made a change. - // Do so by calling SHChangeNotify, specifying the SHCNE_ASSOCCHANGED event. - // If you do not call SHChangeNotify, the change might not be recognized until the system is rebooted. - SHChangeNotify(SHCNE_ASSOCCHANGED, 0, 0, 0); - - return S_OK; + std::string flags_str = FlagDescriptor::ToBitString(flags, descriptors); + return flags_str; } void InstallDefaultConfigurations(const std::string& config_dir) @@ -1315,6 +284,8 @@ void InstallDefaultConfigurations(const std::string& config_dir) void LogEnvironment() { LOG(INFO) << "Enabling logging"; + LOG(INFO) << "Process id: " << ra::strings::ToString(ra::process::GetCurrentProcessId()); + LOG(INFO) << "Thread id: " << ra::strings::ToString((uint32_t)GetCurrentThreadId()); LOG(INFO) << "DLL path: " << GetCurrentModulePathUtf8(); LOG(INFO) << "EXE path: " << ra::process::GetCurrentProcessPathUtf8().c_str(); @@ -1359,49 +330,35 @@ void InitConfigManager() pmgr.SetProperty("home.directory", home_dir); } -extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +void DebugHook(const char* fname) { - if (dwReason == DLL_PROCESS_ATTACH) - { - g_hmodDll = hInstance; + std::string text; + text += fname; + text += "()\n"; - if (!shellanything::IsTestingEnvironment()) - { - // Initialize Google's logging library. - shellanything::InitLogger(); - } + uint32_t pid = GetCurrentProcessId(); + uint32_t tid = (uint32_t)GetCurrentThreadId(); - LogEnvironment(); + text += "pid="; + text += ra::strings::ToString(pid); + text += " ("; + text += ra::strings::Format("0x%x", pid); + text += ")\n"; - // Initialize the configuration manager - InitConfigManager(); - } - else if (dwReason == DLL_PROCESS_DETACH) - { - if (!shellanything::IsTestingEnvironment()) - { - // Shutdown Google's logging library. - google::ShutdownGoogleLogging(); - } - } - return 1; -} + text += "tid="; + text += ra::strings::ToString(tid); + text += " ("; + text += ra::strings::Format("0x%x", tid); + text += ")\n"; + + std::string process_path = ra::process::GetCurrentProcessPath(); + std::string process_filename = ra::filesystem::GetFilename(process_path.c_str()); + std::string caption; + caption += "ShellAnything's debuging hook"; + caption += ", "; + caption += process_filename; + caption += ", "; + caption += ra::strings::ToString(pid); -//int main(int argc, char* argv[]) -//{ -// { -// HRESULT result = DllRegisterServer(); -// if (result == S_OK) -// //MessageBox(NULL, "Manual dll registration successfull", ShellExtensionClassName, MB_OK | MB_ICONINFORMATION); -// else -// //MessageBox(NULL, "Manual dll registration FAILED !", ShellExtensionClassName, MB_OK | MB_ICONERROR); -// } -// -// { -// HRESULT result = DllUnregisterServer(); -// if (result == S_OK) -// //MessageBox(NULL, "Manual dll unregistration successfull", ShellExtensionClassName, MB_OK | MB_ICONINFORMATION); -// else -// //MessageBox(NULL, "Manual dll unregistration FAILED !", ShellExtensionClassName, MB_OK | MB_ICONERROR); -// } -//} + MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONEXCLAMATION); +} \ No newline at end of file diff --git a/src/shellextension/shellext.h b/src/shellextension/shellext.h index caf1a058..cb42bbfb 100644 --- a/src/shellextension/shellext.h +++ b/src/shellextension/shellext.h @@ -25,101 +25,41 @@ #ifndef SA_SHELLEXTENSION_H #define SA_SHELLEXTENSION_H -#include "shellanything/version.h" -#include "shellanything/config.h" - -#include "BitmapCache.h" -#include "SelectionContext.h" -#include "Menu.h" -#include "Icon.h" - -#include -#include - - //Shell extension GUID -static const GUID SHELLANYTHING_SHELLEXTENSION_CLSID = { 0xb0d35103, 0x86a1, 0x471c, { 0xa6, 0x53, 0xe1, 0x30, 0xe3, 0x43, 0x9a, 0x3b } }; //this is the CLSID (GUID) or our Shell Extension, {B0D35103-86A1-471C-A653-E130E3439A3B} -static const char* ShellExtensionClassName = "ShellExtension.ShellAnything"; //no space in string -static const char* ShellExtensionDescription = "ShellAnything Class"; - -class CCriticalSection -{ -protected: - CRITICAL_SECTION mCS; - -public: - CCriticalSection(); - virtual ~CCriticalSection(); - - void Enter(); - void Leave(); -}; +#include "stdafx.h" -class CCriticalSectionGuard -{ -protected: - CCriticalSection* mCS; +#include -public: - CCriticalSectionGuard(CCriticalSection* cs); - ~CCriticalSectionGuard(); -}; - -class CClassFactory : public IClassFactory -{ -protected: - ULONG m_cRef; - -public: - CClassFactory(); - ~CClassFactory(); - - //IUnknown interface - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID FAR*); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - - //IClassFactory interface - HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR*); - HRESULT STDMETHODCALLTYPE LockServer(BOOL); -}; - -class CContextMenu : public IContextMenu, IShellExtInit -{ -public: - typedef std::map IconMap; +#include "shellanything/version.h" +#include "shellanything/config.h" -protected: - CCriticalSection m_CS; //protects class members - ULONG m_cRef; - UINT m_FirstCommandId; - bool m_IsBackGround; - int m_BuildMenuTreeCount; //number of times that BuildMenuTree() was called - shellanything::BitmapCache m_BitmapCache; - IconMap m_FileExtensionCache; - shellanything::SelectionContext m_Context; +#ifdef SA_ENABLE_ATTACH_HOOK_DEBUGGING +#define ATTACH_HOOK_DEBUGGING DebugHook(__FUNCTION__); +#else +#define ATTACH_HOOK_DEBUGGING ; +#endif // #ifdef SA_ENABLE_ATTACH_HOOK_DEBUGGING - static HMENU m_previousMenu; +std::string GuidToString(GUID guid); +std::string GuidToInterfaceName(GUID guid); +std::string GetProcessContextDesc(); +std::string ToHexString(void* value); -public: - CContextMenu(); - ~CContextMenu(); +/// +/// Returns true if the application is run for the first time. +/// Note, for Windows users, the implementation is based on registry keys in HKEY_CURRENT_USER\Software\name\version. +/// +/// The name of the application. +/// The version of the application. +/// Returns true if the application is run for the first time. Returns false otherwise. +bool IsFirstApplicationRun(const std::string& name, const std::string& version); - //IUnknown interface - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID FAR*); - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); +std::string GetQueryContextMenuFlags(UINT flags); +std::string GetGetCommandStringFlags(UINT flags); - //IContextMenu interface - HRESULT STDMETHODCALLTYPE QueryContextMenu(HMENU hMenu, UINT menu_index, UINT first_command_id, UINT max_command_id, UINT flags); - HRESULT STDMETHODCALLTYPE InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi); - HRESULT STDMETHODCALLTYPE GetCommandString(UINT_PTR command_id, UINT flags, UINT FAR* reserved, LPSTR pszName, UINT cchMax); +void InstallDefaultConfigurations(const std::string& config_dir); - //IShellExtInit interface - HRESULT STDMETHODCALLTYPE Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID); +void LogEnvironment(); +void InitConfigManager(); -private: - void BuildMenuTree(HMENU hMenu); - void BuildMenuTree(HMENU hMenu, shellanything::Menu* menu, UINT& insert_pos, bool& next_menu_is_column); -}; +void DebugHook(const char* fname); #endif //SA_SHELLEXTENSION_H diff --git a/src/shellextension/stdafx.h b/src/shellextension/stdafx.h new file mode 100644 index 00000000..fa4939e3 --- /dev/null +++ b/src/shellextension/stdafx.h @@ -0,0 +1,66 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#pragma once + +#ifndef STRICT +#define STRICT +#endif + +#include "targetver.h" + +// Windows Header Files: +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void DllAddRef(); +void DllRelease(); + +#define INITGUID +#include + +// Shell extension CLSID / GUID +// {B0D35103-86A1-471C-A653-E130E3439A3B} +DEFINE_GUID(CLSID_ShellAnythingMenu, 0xb0d35103, 0x86a1, 0x471c, 0xa6, 0x53, 0xe1, 0x30, 0xe3, 0x43, 0x9a, 0x3b); + +static const char* ShellExtensionClassName = "ShellExtension.ShellAnything"; //no space in string +static const char* ShellExtensionDescription = "ShellAnything Class"; + +// Debugging support +//#define SA_ENABLE_ATTACH_HOOK_DEBUGGING +//#define SA_ENABLE_SCOPE_DEBUGGING + +// QueryInterface implementations +#define SA_QUERYINTERFACE_IMPL 1 diff --git a/src/shellextension/targetver.h b/src/shellextension/targetver.h new file mode 100644 index 00000000..ba18a089 --- /dev/null +++ b/src/shellextension/targetver.h @@ -0,0 +1,11 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include + +#define WINVER 0x0600 +#define _WIN32_WINNT 0x0600