diff --git a/.gitignore b/.gitignore index 529e13d..b132fe8 100644 --- a/.gitignore +++ b/.gitignore @@ -362,5 +362,5 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd -release*.bat +release.cmd *.zip diff --git a/README.md b/README.md index 8cbb53c..a61ed67 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,29 @@ -# Compare Menu +# AB Menu -A Windows context menu shell extension that proxies general comparison commands to any diff application. +A Windows context menu shell extension that proxies user configured commands accepting exactly two launch arguments (thus "AB" in the name) to any application. -While many popular comparison applications such as Beyond Compare come with their own such shell extensions help improve user experience, there are good ones that still do not have. Rather than requesting each developer team to come up with their own solution, why not build a general one that can work everywhere as long as their take standard input arguments? +Among the examples of such applications are comparison tools, file copy tools and text editors for side-by-side editing. -The concept of such context menu is simple: +If the application does not come with its own shell extension, instead of having to manually type in terminal or create shortcut of it, user of this shell extension can create template of launch command with the two argument placeholders, select the item(s) in Windows Explorer and launch. -* When exactly two files or two directories are selected, a "Compare Two" command becomes available, sending the two paths to the configured diff application. -* If only one object is selected for the first time, the menu remembers its path. -* If another object is selected afterwards, the menu either sends both paths to the diff application and reset, or replaces the previously remembered path, depending on user choice. +There are multiple ways to select items: -Note that a file can't be compared to a directory. So if the types mismatch, "Compare Two" won't be available, and the only option for single select mode is replacement. +* When exactly two items (both files or both folders but not mixed) are selected, the shell extension will display the value of each argument placeholder and launch app. +* When only one item is selected for the first time, the shell extension remembers its path. +* When a different object is selected afterwards, the shell extension either launch app with the two arguments, or replaces the previously remembered first item, depending on user choice. + +Again, the two items must be both files or both folders but not mixed. ## Install * Unpack the archive. * Run install.bat to register the shell extension. -* Open config.reg and edit the application path. `\` in the path needs to be escaped as `\\`. -* The command line part should include both the full path to the comparer executable, and its arguments with `%LEFT%` and `%RIGHT%` placeholders. - * In the config.reg, we provided an example command line for [010 Editor's `-compare` parameter](https://www.sweetscape.com/010editor/manual/CommandLine.htm#-compare). +* config.reg has some examples of user configurable commands. You can edit it and import into registry. The first selected item replaces the `%FIRST%` placeholder, while the second one replaces `%SECOND%`. `\` in the path needs to be escaped as `\\`. + +## Uninstall + +* Run uninstall.bat to de-register the shell extension. +* Open Registry Editor and remove `HKEY_CURRENT_USER\Software\AB Menu`. ## Build @@ -26,11 +31,14 @@ A build script `build.ps1` is included to automate the process. It obtains depen ## Screenshots -Compare Two -![Compare Two](screenshots/compare_two.webp) +When two items are selected: + +![Two selected](screenshots/two_selected.webp) + +When the first item is selected: -Select First ![Select First](screenshots/select_first.webp) -Select Second +After the first item is confirmed, when the second item is selected: + ![Select Second](screenshots/select_second.webp) diff --git a/compare_menu.sln b/ab_menu.sln similarity index 90% rename from compare_menu.sln rename to ab_menu.sln index 52fa6b8..a12f107 100644 --- a/compare_menu.sln +++ b/ab_menu.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{4E19C66E-E508-4D7A-876B-6B1D945B7F54}") = "compare_menu", "compare_menu.vcxproj", "{57F63F56-0E76-492D-B6F6-AE1D306D4057}" +Project("{4E19C66E-E508-4D7A-876B-6B1D945B7F54}") = "ab_menu", "ab_menu.vcxproj", "{57F63F56-0E76-492D-B6F6-AE1D306D4057}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/compare_menu.vcxproj b/ab_menu.vcxproj similarity index 94% rename from compare_menu.vcxproj rename to ab_menu.vcxproj index b2f2a62..11c53a0 100644 --- a/compare_menu.vcxproj +++ b/ab_menu.vcxproj @@ -21,34 +21,33 @@ - Create - + - + - + - + - + - + {57F63F56-0E76-492D-B6F6-AE1D306D4057} @@ -118,4 +117,4 @@ - + \ No newline at end of file diff --git a/compare_menu.vcxproj.filters b/ab_menu.vcxproj.filters similarity index 87% rename from compare_menu.vcxproj.filters rename to ab_menu.vcxproj.filters index 597cd39..961de55 100644 --- a/compare_menu.vcxproj.filters +++ b/ab_menu.vcxproj.filters @@ -24,7 +24,7 @@ Source Files - + Source Files @@ -33,9 +33,6 @@ Source Files - - Source Files - Source Files @@ -50,7 +47,7 @@ Header Files - + Header Files @@ -59,20 +56,20 @@ Header Files - + Header Files - + Header Files - + Resource Files - + Resource Files @@ -80,7 +77,7 @@ - + Resource Files diff --git a/build.ps1 b/build.ps1 index 04b734e..8939ab9 100644 --- a/build.ps1 +++ b/build.ps1 @@ -7,5 +7,5 @@ Set-Location $PSScriptRoot $configuration = $args[0] $platform = $args[1] -MSBuild.exe -property:"Configuration=${configuration};Platform=${platform}" -maxCpuCount -nologo compare_menu.sln +MSBuild.exe -property:"Configuration=${configuration};Platform=${platform}" -maxCpuCount -nologo ab_menu.sln exit $LASTEXITCODE diff --git a/config.reg b/config.reg index 1ea5a33..b3df1f0 100644 Binary files a/config.reg and b/config.reg differ diff --git a/install.cmd b/install.cmd index 5bc9a63..20a004b 100644 --- a/install.cmd +++ b/install.cmd @@ -1,2 +1,2 @@ -regsvr32 "%~dp0acompare_menu_64.dll" -regsvr32 "%~dp0acompare_menu_32.dll" +regsvr32 "%~dp0ab_menu_64.dll" +regsvr32 "%~dp0ab_menu_32.dll" diff --git a/release.cmd b/release.cmd deleted file mode 100644 index 7524303..0000000 --- a/release.cmd +++ /dev/null @@ -1,10 +0,0 @@ -cd /d "%~dp0" - -del CompareMenu.zip -7z a CompareMenu.zip README.md LICENSE install.bat uninstall.bat config.reg - -cd x64\Release\ -7z a ..\..\CompareMenu.zip compare_menu_64.dll - -cd ..\..\Win32\Release\ -7z a ..\..\CompareMenu.zip compare_menu_32.dll diff --git a/screenshots/compare_two.webp b/screenshots/compare_two.webp deleted file mode 100644 index 31264f4..0000000 Binary files a/screenshots/compare_two.webp and /dev/null differ diff --git a/screenshots/select_first.webp b/screenshots/select_first.webp index 863b700..5c70976 100644 Binary files a/screenshots/select_first.webp and b/screenshots/select_first.webp differ diff --git a/screenshots/select_second.webp b/screenshots/select_second.webp index f3d89c5..1dfacfc 100644 Binary files a/screenshots/select_second.webp and b/screenshots/select_second.webp differ diff --git a/screenshots/two_selected.webp b/screenshots/two_selected.webp new file mode 100644 index 0000000..5c844dc Binary files /dev/null and b/screenshots/two_selected.webp differ diff --git a/src/compare.ico b/src/ab.ico similarity index 100% rename from src/compare.ico rename to src/ab.ico diff --git a/src/compare_menu.def b/src/ab_menu.def similarity index 100% rename from src/compare_menu.def rename to src/ab_menu.def diff --git a/src/compare_menu.rc b/src/ab_menu.rc similarity index 92% rename from src/compare_menu.rc rename to src/ab_menu.rc index 2af88cb..500c1a6 100644 --- a/src/compare_menu.rc +++ b/src/ab_menu.rc @@ -50,7 +50,7 @@ END // REGISTRY // -IDS_COMPARE_MENU REGISTRY "compare_menu.rgs" +IDS_AB_MENU REGISTRY "ab_menu.rgs" ///////////////////////////////////////////////////////////////////////////// @@ -60,7 +60,7 @@ IDS_COMPARE_MENU REGISTRY "compare_menu.rgs" // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_ICON ICON "src\compare.ico" +IDI_ICON ICON "src\\ab.ico" #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/compare_menu.rgs b/src/ab_menu.rgs similarity index 87% rename from src/compare_menu.rgs rename to src/ab_menu.rgs index 131f249..0bf276e 100644 --- a/src/compare_menu.rgs +++ b/src/ab_menu.rgs @@ -1,6 +1,6 @@ HKEY_CLASSES_ROOT { NoRemove CLSID { - ForceRemove {59D49316-D3FD-425B-A175-41DEB301AB1F} = s 'Compare Menu Shell Extension' { + ForceRemove {59D49316-D3FD-425B-A175-41DEB301AB1F} = s 'AB Menu Shell Extension' { InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } @@ -9,7 +9,7 @@ HKEY_CLASSES_ROOT { NoRemove AllFilesystemObjects { NoRemove shell { - CompareMenu { + ABMenu { val ExplorerCommandHandler = s '{59D49316-D3FD-425B-A175-41DEB301AB1F}' } } diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..ff55090 --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,210 @@ +#include "commands.h" + +#include "environment.h" + + +static constexpr const WCHAR *ARGS_FIRST_PLACEHOLDER = L"%FIRST%"; +static constexpr const WCHAR *ARGS_SECOND_PLACEHOLDER = L"%SECOND%"; + +static auto ConstructInfoTitle(const std::wstring &prefix, const ABItem &item) -> std::wstring { + std::wstring ret; + ret.append(prefix) + .append(item.name) + .append(item.isFolder ? L" (Folder)" : L" (File)"); + return ret; +} + +auto STDMETHODCALLTYPE CFirstInfoCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { + const ABItem firstItem(psiItemArray, 1); + return SHStrDupW(ConstructInfoTitle(L"First: ", firstItem).c_str(), ppszName); +} + +auto STDMETHODCALLTYPE CFirstInfoCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { + HRESULT hr; + DWORD selectedItemCount; + CheckHr(psiItemArray->GetCount(&selectedItemCount)); + + *pCmdState = selectedItemCount == 2 ? ECS_DISABLED : ECS_HIDDEN; + + return S_OK; +} + +auto STDMETHODCALLTYPE CFirstInfoCommand::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { + *pFlags = ECF_DEFAULT; + return S_OK; +} + +auto STDMETHODCALLTYPE CSecondInfoCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { + std::wstring infoTitle; + + HRESULT hr; + DWORD selectedItemCount; + CheckHr(psiItemArray->GetCount(&selectedItemCount)); + + if (selectedItemCount == 2) { + const ABItem secondItem(psiItemArray, 0); + infoTitle = ConstructInfoTitle(L"Second: ", secondItem); + } else if (Environment::INSTANCE->selectedItem.name.empty()) { + infoTitle = L"None selected"; + } else { + infoTitle = ConstructInfoTitle(L"First: ", Environment::INSTANCE->selectedItem); + } + + return SHStrDupW(infoTitle.c_str(), ppszName); +} + +auto STDMETHODCALLTYPE CSecondInfoCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { + *pCmdState = ECS_DISABLED; + return S_OK; +} + +auto STDMETHODCALLTYPE CSecondInfoCommand::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { + *pFlags = ECF_SEPARATORAFTER; + return S_OK; +} + +auto STDMETHODCALLTYPE CSelectCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { + return SHStrDupW(L"Select first item", ppszName); +} + +auto STDMETHODCALLTYPE CSelectCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { + HRESULT hr; + + DWORD itemCount; + CheckHr(psiItemArray->GetCount(&itemCount)); + + if (itemCount != 1) { + *pCmdState = ECS_HIDDEN; + } else if (Environment::INSTANCE->selectedItem.name.empty()) { + *pCmdState = ECS_ENABLED; + } else { + const ABItem currItem(psiItemArray, 0); + *pCmdState = currItem.name == Environment::INSTANCE->selectedItem.name ? ECS_HIDDEN : ECS_ENABLED; + } + + return S_OK; +} + +auto STDMETHODCALLTYPE CSelectCommand::Invoke(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__in_opt IBindCtx *pbc) -> HRESULT { + HRESULT hr; + + DWORD itemCount; + CheckHr(psiItemArray->GetCount(&itemCount)); + + if (itemCount != 1) { + return E_UNEXPECTED; + } + + CheckHr(Environment::INSTANCE->selectedItem.ExtractShellItemAt(psiItemArray, 0)); + Environment::INSTANCE->FlushSelectedItem(); + + return S_OK; +} + +auto CDiffCommand::SetCmdIndex(int cmdIndex) -> void { + _cmdIndex = cmdIndex; +} + +auto STDMETHODCALLTYPE CDiffCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { + return SHStrDupW(Environment::INSTANCE->apps[_cmdIndex].name.c_str(), ppszName); +} + +auto CDiffCommand::GetIcon(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszIcon) -> HRESULT { + return SHStrDupW(Environment::INSTANCE->apps[_cmdIndex].iconPath.c_str(), ppszIcon); +} + +auto STDMETHODCALLTYPE CDiffCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { + *pCmdState = ECS_ENABLED; + return S_OK; +} + +static auto ReplaceSubString(std::wstring &str, const WCHAR *toFind, const WCHAR *replaceWith) { + const size_t replaceLen = std::char_traits::length(toFind); + + std::size_t pos; + while ((pos = str.find(toFind)) != std::string::npos) { + str.replace(pos, replaceLen, replaceWith); + } +} + +auto STDMETHODCALLTYPE CDiffCommand::Invoke(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__in_opt IBindCtx *pbc) -> HRESULT { + HRESULT hr; + + ABItem firstItem, secondItem; + CheckHr(Environment::ExtractItems(psiItemArray, firstItem, secondItem)); + + if (hr != S_OK) { + return E_UNEXPECTED; + } + + std::wstring cmdline = Environment::INSTANCE->apps[_cmdIndex].cmdline; + ReplaceSubString(cmdline, ARGS_FIRST_PLACEHOLDER, firstItem.name.c_str()); + ReplaceSubString(cmdline, ARGS_SECOND_PLACEHOLDER, secondItem.name.c_str()); + + STARTUPINFOW si { .cb = sizeof(si) }; + PROCESS_INFORMATION pi; + + if (!CreateProcessW(nullptr, cmdline.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { + return E_FAIL; + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + Environment::INSTANCE->selectedItem.name.clear(); + Environment::INSTANCE->FlushSelectedItem(); + + return S_OK; +} + +auto STDMETHODCALLTYPE CAppSelectionCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { + return SHStrDupW(L"Execute", ppszName); +} + +auto STDMETHODCALLTYPE CAppSelectionCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { + HRESULT hr; + + ABItem firstItem, secondItem; + CheckHr(Environment::ExtractItems(psiItemArray, firstItem, secondItem)); + + *pCmdState = hr == S_OK && firstItem.name != secondItem.name && firstItem.isFolder == secondItem.isFolder ? ECS_ENABLED : ECS_HIDDEN; + return S_OK; +} + +auto STDMETHODCALLTYPE CAppSelectionCommand::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { + *pFlags = ECF_HASSUBCOMMANDS; + return S_OK; +} + +auto STDMETHODCALLTYPE CAppSelectionCommand::EnumSubCommands(__RPC__deref_out_opt IEnumExplorerCommand **ppEnum) -> HRESULT { + AddRef(); + _subMenuIndex = 0; + *ppEnum = this; + return S_OK; +} + +auto STDMETHODCALLTYPE CAppSelectionCommand::Next(_In_ ULONG celt, _Out_writes_to_(celt, *pceltFetched) IExplorerCommand **pUICommand, _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) + -> HRESULT { + if (_subMenuIndex >= Environment::INSTANCE->apps.size()) { + return E_FAIL; + } + + ULONG copied = 0; + for (ULONG i = 0; i < celt; ++i) { + if (_subMenuIndex >= Environment::INSTANCE->apps.size()) { + break; + } + + CDiffCommand::CreateInstance(&pUICommand[i]); + reinterpret_cast(pUICommand[i])->SetCmdIndex(_subMenuIndex); + + copied += 1; + _subMenuIndex += 1; + } + + if (pceltFetched != nullptr) { + *pceltFetched = copied; + } + + return S_OK; +} diff --git a/src/terminal_cmd.h b/src/commands.h similarity index 53% rename from src/terminal_cmd.h rename to src/commands.h index 05726a0..1a90db4 100644 --- a/src/terminal_cmd.h +++ b/src/commands.h @@ -3,14 +3,14 @@ #include "default.h" -class ATL_NO_VTABLE CInfoCommand +class ATL_NO_VTABLE CFirstInfoCommand : public ATL::CComObjectRoot - , public ATL::CComCoClass + , public ATL::CComCoClass , public IDefaultExplorerCommand { public: DECLARE_PROTECT_FINAL_CONSTRUCT() - BEGIN_COM_MAP(CInfoCommand) + BEGIN_COM_MAP(CFirstInfoCommand) COM_INTERFACE_ENTRY(IExplorerCommand) END_COM_MAP() @@ -20,6 +20,23 @@ class ATL_NO_VTABLE CInfoCommand auto STDMETHODCALLTYPE GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT override; }; +class ATL_NO_VTABLE CSecondInfoCommand + : public ATL::CComObjectRoot + , public ATL::CComCoClass + , public IDefaultExplorerCommand { +public: + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(CSecondInfoCommand) + COM_INTERFACE_ENTRY(IExplorerCommand) + END_COM_MAP() + +private: + auto STDMETHODCALLTYPE GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT override; + auto STDMETHODCALLTYPE GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT override; + auto STDMETHODCALLTYPE GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT override; +}; + class ATL_NO_VTABLE CSelectCommand : public ATL::CComObjectRoot , public ATL::CComCoClass @@ -59,3 +76,27 @@ class ATL_NO_VTABLE CDiffCommand int _cmdIndex; }; + +class ATL_NO_VTABLE CAppSelectionCommand + : public ATL::CComObjectRoot + , public ATL::CComCoClass + , public IDefaultExplorerCommand + , public IDefaultEnumExplorerCommand { +public: + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(CAppSelectionCommand) + COM_INTERFACE_ENTRY(IExplorerCommand) + COM_INTERFACE_ENTRY(IEnumExplorerCommand) + END_COM_MAP() + +private: + // IExplorerCommand + auto STDMETHODCALLTYPE GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT override; + auto STDMETHODCALLTYPE GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT override; + auto STDMETHODCALLTYPE GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT override; + auto STDMETHODCALLTYPE EnumSubCommands(__RPC__deref_out_opt IEnumExplorerCommand **ppEnum) -> HRESULT override; + + // IEnumExplorerCommand + auto STDMETHODCALLTYPE Next(_In_ ULONG celt, _Out_writes_to_(celt, *pceltFetched) IExplorerCommand **pUICommand, _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) -> HRESULT override; +}; diff --git a/src/comparer_cmd.cpp b/src/comparer_cmd.cpp deleted file mode 100644 index 607b342..0000000 --- a/src/comparer_cmd.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "comparer_cmd.h" - -#include "environment.h" -#include "terminal_cmd.h" - - -auto STDMETHODCALLTYPE CComparerCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { - return SHStrDupW(L"Compare", ppszName); -} - -auto STDMETHODCALLTYPE CComparerCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { - HRESULT hr; - - Item firstItem, secondItem; - CheckHr(Environment::ExtractItems(psiItemArray, firstItem, secondItem)); - - *pCmdState = hr == S_OK && firstItem.name != secondItem.name && firstItem.isFolder == secondItem.isFolder ? ECS_ENABLED : ECS_HIDDEN; - return S_OK; -} - -auto STDMETHODCALLTYPE CComparerCommand::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { - *pFlags = ECF_HASSUBCOMMANDS; - return S_OK; -} - -auto STDMETHODCALLTYPE CComparerCommand::EnumSubCommands(__RPC__deref_out_opt IEnumExplorerCommand **ppEnum) -> HRESULT { - AddRef(); - _subMenuIndex = 0; - *ppEnum = this; - return S_OK; -} - -auto STDMETHODCALLTYPE CComparerCommand::Next(_In_ ULONG celt, - _Out_writes_to_(celt, *pceltFetched) IExplorerCommand **pUICommand, - _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) -> HRESULT { - if (_subMenuIndex >= Environment::INSTANCE->comparers.size()) { - return E_FAIL; - } - - ULONG copied = 0; - for (ULONG i = 0; i < celt; ++i) { - if (_subMenuIndex >= Environment::INSTANCE->comparers.size()) { - break; - } - - CDiffCommand::CreateInstance(&pUICommand[i]); - reinterpret_cast(pUICommand[i])->SetCmdIndex(_subMenuIndex); - - copied += 1; - _subMenuIndex += 1; - } - - if (pceltFetched != nullptr) { - *pceltFetched = copied; - } - - return S_OK; -} diff --git a/src/comparer_cmd.h b/src/comparer_cmd.h deleted file mode 100644 index bf8f4c0..0000000 --- a/src/comparer_cmd.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "default.h" - - -class ATL_NO_VTABLE CComparerCommand - : public ATL::CComObjectRoot - , public ATL::CComCoClass - , public IDefaultExplorerCommand - , public IDefaultEnumExplorerCommand { -public: - DECLARE_PROTECT_FINAL_CONSTRUCT() - - BEGIN_COM_MAP(CComparerCommand) - COM_INTERFACE_ENTRY(IExplorerCommand) - COM_INTERFACE_ENTRY(IEnumExplorerCommand) - END_COM_MAP() - -private: - // IExplorerCommand - auto STDMETHODCALLTYPE GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT override; - auto STDMETHODCALLTYPE GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT override; - auto STDMETHODCALLTYPE GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT override; - auto STDMETHODCALLTYPE EnumSubCommands(__RPC__deref_out_opt IEnumExplorerCommand **ppEnum) -> HRESULT override; - - // IEnumExplorerCommand - auto STDMETHODCALLTYPE Next(_In_ ULONG celt, - _Out_writes_to_(celt, *pceltFetched) IExplorerCommand **pUICommand, - _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) -> HRESULT override; -}; diff --git a/src/environment.cpp b/src/environment.cpp index 87052b3..0623c54 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1,9 +1,9 @@ #include "environment.h" -static constexpr const WCHAR *SELECTED_ITEM_OBJECT_NAME = L"Global\\CompareMenuSelectedItem"; +static constexpr const WCHAR *SELECTED_ITEM_OBJECT_NAME = L"Global\\ABMenuSelectedItem"; -auto Environment::ExtractItems(IShellItemArray *psiItemArray, Item &firstItem, Item &secondItem) -> HRESULT { +auto Environment::ExtractItems(IShellItemArray *psiItemArray, ABItem &firstItem, ABItem &secondItem) -> HRESULT { HRESULT hr; DWORD itemCount; @@ -12,7 +12,7 @@ auto Environment::ExtractItems(IShellItemArray *psiItemArray, Item &firstItem, I return S_FALSE; } - CheckHr(secondItem.ExtractItemAt(psiItemArray, 0)); + CheckHr(secondItem.ExtractShellItemAt(psiItemArray, 0)); if (itemCount == 1) { if (Environment::INSTANCE->selectedItem.name.empty()) { @@ -21,7 +21,7 @@ auto Environment::ExtractItems(IShellItemArray *psiItemArray, Item &firstItem, I firstItem = Environment::INSTANCE->selectedItem; } else { - CheckHr(firstItem.ExtractItemAt(psiItemArray, 1)); + CheckHr(firstItem.ExtractShellItemAt(psiItemArray, 1)); } return S_OK; diff --git a/src/environment.h b/src/environment.h index 46a3472..46470b7 100644 --- a/src/environment.h +++ b/src/environment.h @@ -4,13 +4,13 @@ struct Environment { - struct Comparer { + struct App { std::wstring name; std::wstring cmdline; std::wstring iconPath; }; - static auto ExtractItems(IShellItemArray *psiItemArray, Item &firstItem, Item &secondItem) -> HRESULT; + static auto ExtractItems(IShellItemArray *psiItemArray, ABItem &firstItem, ABItem &secondItem) -> HRESULT; Environment(); ~Environment(); @@ -22,9 +22,9 @@ struct Environment { static inline Environment *INSTANCE = nullptr; std::wstring menuIconPath; - std::vector comparers; + std::vector apps; - Item selectedItem; + ABItem selectedItem; private: HANDLE _hMapFile = nullptr; diff --git a/src/item.cpp b/src/item.cpp index 03b3140..b8ce4dc 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -3,11 +3,15 @@ #include "environment.h" -Item::Item() { - name.resize(MAX_PATH); +ABItem::ABItem() { + name.reserve(MAX_PATH); } -auto Item::ExtractItemAt(IShellItemArray *itemArray, int i) -> HRESULT { +ABItem::ABItem(IShellItemArray *itemArray, int i) { + ExtractShellItemAt(itemArray, i); +} + +auto ABItem::ExtractShellItemAt(IShellItemArray *itemArray, int i) -> HRESULT { HRESULT hr; IShellItem *shellItem; diff --git a/src/item.h b/src/item.h index e21914b..22c122c 100644 --- a/src/item.h +++ b/src/item.h @@ -1,12 +1,11 @@ #pragma once -#include "pch.h" +struct ABItem { + ABItem(); + ABItem(IShellItemArray *itemArray, int i); -struct Item { - Item(); - - auto ExtractItemAt(IShellItemArray *itemArray, int i) -> HRESULT; + auto ExtractShellItemAt(IShellItemArray *itemArray, int i) -> HRESULT; std::wstring name; bool isFolder = false; diff --git a/src/menu.cpp b/src/menu.cpp index 5d51f2b..1effef5 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -1,19 +1,18 @@ #include "menu.h" -#include "comparer_cmd.h" +#include "commands.h" #include "environment.h" -#include "terminal_cmd.h" -static constexpr const WCHAR *REGISTRY_COMPARE_MENU_KEY_PREFIX = LR"(SOFTWARE\CompareMenu)"; +static constexpr const WCHAR *REGISTRY_ab_menu_KEY_PREFIX = LR"(SOFTWARE\AB Menu)"; -OBJECT_ENTRY_AUTO(__uuidof(CCompareMenu), CCompareMenu) +OBJECT_ENTRY_AUTO(__uuidof(CABMenu), CABMenu) -auto STDMETHODCALLTYPE CCompareMenu::Initialize(__RPC__in_string LPCWSTR pszCommandName, __RPC__in_opt IPropertyBag *ppb) -> HRESULT { - if (Environment::INSTANCE->comparers.empty()) { +auto STDMETHODCALLTYPE CABMenu::Initialize(__RPC__in_string LPCWSTR pszCommandName, __RPC__in_opt IPropertyBag *ppb) -> HRESULT { + if (Environment::INSTANCE->apps.empty()) { HKEY registryKey; - RegCreateKeyExW(HKEY_CURRENT_USER, REGISTRY_COMPARE_MENU_KEY_PREFIX, 0, nullptr, 0, KEY_QUERY_VALUE, nullptr, ®istryKey, nullptr); + RegCreateKeyExW(HKEY_CURRENT_USER, REGISTRY_ab_menu_KEY_PREFIX, 0, nullptr, 0, KEY_QUERY_VALUE, nullptr, ®istryKey, nullptr); LSTATUS regRet = ERROR_SUCCESS; for (int i = 0; regRet == ERROR_SUCCESS; ++i) { @@ -27,7 +26,7 @@ auto STDMETHODCALLTYPE CCompareMenu::Initialize(__RPC__in_string LPCWSTR pszComm const WCHAR *regValueStr = reinterpret_cast(regValueValue.data()); int cmdArgc; const std::wstring iconPath = std::format(L"{},0", CommandLineToArgvW(regValueStr, &cmdArgc)[0]); - Environment::INSTANCE->comparers.emplace_back(regValueName.data(), regValueStr, iconPath); + Environment::INSTANCE->apps.emplace_back(regValueName.data(), regValueStr, iconPath); } } @@ -39,53 +38,56 @@ auto STDMETHODCALLTYPE CCompareMenu::Initialize(__RPC__in_string LPCWSTR pszComm return S_OK; } -auto STDMETHODCALLTYPE CCompareMenu::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { - return SHStrDupW(L"Compare Two", ppszName); +auto STDMETHODCALLTYPE CABMenu::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { + return SHStrDupW(L"AB Menu", ppszName); } -auto STDMETHODCALLTYPE CCompareMenu::GetIcon(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszIcon) -> HRESULT { +auto STDMETHODCALLTYPE CABMenu::GetIcon(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszIcon) -> HRESULT { return SHStrDupW(Environment::INSTANCE->menuIconPath.c_str(), ppszIcon); } -auto STDMETHODCALLTYPE CCompareMenu::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { - *pCmdState = Environment::INSTANCE->comparers.empty() ? ECS_DISABLED : ECS_ENABLED; +auto STDMETHODCALLTYPE CABMenu::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { + *pCmdState = Environment::INSTANCE->apps.empty() ? ECS_DISABLED : ECS_ENABLED; return S_OK; } -auto STDMETHODCALLTYPE CCompareMenu::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { +auto STDMETHODCALLTYPE CABMenu::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { *pFlags = ECF_HASSUBCOMMANDS; return S_OK; } -auto STDMETHODCALLTYPE CCompareMenu::EnumSubCommands(__RPC__deref_out_opt IEnumExplorerCommand **ppEnum) -> HRESULT { +auto STDMETHODCALLTYPE CABMenu::EnumSubCommands(__RPC__deref_out_opt IEnumExplorerCommand **ppEnum) -> HRESULT { AddRef(); _subMenuIndex = 0; *ppEnum = this; return S_OK; } -auto STDMETHODCALLTYPE CCompareMenu::Next(_In_ ULONG celt, - _Out_writes_to_(celt, *pceltFetched) IExplorerCommand **pUICommand, - _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) -> HRESULT { - if (_subMenuIndex > 2) { +auto STDMETHODCALLTYPE CABMenu::Next(_In_ ULONG celt, + _Out_writes_to_(celt, *pceltFetched) IExplorerCommand **pUICommand, + _Out_opt_ _Deref_out_range_(0, celt) ULONG *pceltFetched) -> HRESULT { + if (_subMenuIndex > 3) { return E_FAIL; } ULONG copied = 0; for (ULONG i = 0; i < celt; ++i) { - if (_subMenuIndex > 2) { + if (_subMenuIndex > 3) { break; } switch (_subMenuIndex) { case 0: - CInfoCommand::CreateInstance(&pUICommand[i]); + CFirstInfoCommand::CreateInstance(&pUICommand[i]); break; case 1: - CSelectCommand::CreateInstance(&pUICommand[i]); + CSecondInfoCommand::CreateInstance(&pUICommand[i]); break; case 2: - CComparerCommand::CreateInstance(&pUICommand[i]); + CSelectCommand::CreateInstance(&pUICommand[i]); + break; + case 3: + CAppSelectionCommand::CreateInstance(&pUICommand[i]); break; } diff --git a/src/menu.h b/src/menu.h index 599f32b..fa04f47 100644 --- a/src/menu.h +++ b/src/menu.h @@ -3,18 +3,18 @@ #include "default.h" -class ATL_NO_VTABLE __declspec(uuid("59D49316-D3FD-425B-A175-41DEB301AB1F")) CCompareMenu +class ATL_NO_VTABLE __declspec(uuid("59D49316-D3FD-425B-A175-41DEB301AB1F")) CABMenu : public ATL::CComObjectRoot - , public ATL::CComCoClass + , public ATL::CComCoClass , public IInitializeCommand , public IDefaultExplorerCommand , public IDefaultEnumExplorerCommand { public: - DECLARE_REGISTRY_RESOURCEID(IDS_COMPARE_MENU) + DECLARE_REGISTRY_RESOURCEID(IDS_AB_MENU) DECLARE_PROTECT_FINAL_CONSTRUCT() - BEGIN_COM_MAP(CCompareMenu) + BEGIN_COM_MAP(CABMenu) COM_INTERFACE_ENTRY(IInitializeCommand) COM_INTERFACE_ENTRY(IExplorerCommand) COM_INTERFACE_ENTRY(IEnumExplorerCommand) diff --git a/src/min_windows.h b/src/min_windows.h new file mode 100644 index 0000000..ec10822 --- /dev/null +++ b/src/min_windows.h @@ -0,0 +1,39 @@ +#pragma once + +//#define NOCLIPBOARD +//#define NOCOLOR +//#define NOCTLMGR +//#define NODEFERWINDOWPOS +//#define NODRAWTEXT +//#define NOGDI +//#define NOGDICAPMASKS +//#define NOHELP +#define NOICONS +#define NOIME +#define NOKEYSTATES +//#define NOMB +#define NOMCX +//#define NOMENUS +//#define NOMETAFILE +#define NOMINMAX +//#define NOMSG +//#define NONLS +#define NOOPENFILE +#define NOPROFILER +//#define NORASTEROPS +//#define NOSCROLL +#define NOSERVICE +//#define NOSHOWWINDOW +#define NOSYSCOMMANDS +#define NOSYSMETRICS +//#define NOTEXTMETRIC +//#define NOUSER +//#define NOVIRTUALKEYCODES +#define NOWH +//#define NOWINMESSAGES +//#define NOWINOFFSETS +//#define NOWINSTYLES +#define OEMRESOURCE +#define WIN32_LEAN_AND_MEAN + +#include diff --git a/src/module.cpp b/src/module.cpp index dd854c0..d6b305e 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -1,7 +1,7 @@ #include "environment.h" -class DiffMenuModule : public ATL::CAtlDllModuleT { +class ABMenuModule : public ATL::CAtlDllModuleT { } g_module; extern "C" auto WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) -> BOOL { diff --git a/src/pch.h b/src/pch.h index 7b55c2d..e4c23d4 100644 --- a/src/pch.h +++ b/src/pch.h @@ -10,8 +10,7 @@ #include #include -#define WIN32_LEAN_AND_MEAN -#include +#include "min_windows.h" #define _ATL_APARTMENT_THREADED diff --git a/src/resource.h b/src/resource.h index e0b66ca..8bf758c 100644 --- a/src/resource.h +++ b/src/resource.h @@ -1,8 +1,8 @@ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. -// Used by compare_menu.rc +// Used by ab_menu.rc // -#define IDS_COMPARE_MENU 100 +#define IDS_AB_MENU 100 #define IDI_ICON 101 // Next default values for new objects diff --git a/src/terminal_cmd.cpp b/src/terminal_cmd.cpp deleted file mode 100644 index 045a4de..0000000 --- a/src/terminal_cmd.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "terminal_cmd.h" - -#include "environment.h" - - -static constexpr const WCHAR *COMPARER_ARGS_LEFT_PLACEHOLDER = L"%LEFT%"; -static constexpr const WCHAR *COMPARER_ARGS_RIGHT_PLACEHOLDER = L"%RIGHT%"; - -auto STDMETHODCALLTYPE CInfoCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { - std::wstring infoTitle; - - if (Environment::INSTANCE->selectedItem.name.empty()) { - infoTitle = L"None selected"; - } else { - infoTitle.append(Environment::INSTANCE->selectedItem.name).append(L" ("); - - if (Environment::INSTANCE->selectedItem.isFolder) { - infoTitle += L"Folder)"; - } else { - infoTitle += L"File)"; - } - } - - return SHStrDupW(infoTitle.c_str(), ppszName); -} - -auto STDMETHODCALLTYPE CInfoCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { - HRESULT hr; - - DWORD itemCount; - CheckHr(psiItemArray->GetCount(&itemCount)); - - *pCmdState = itemCount == 2 ? ECS_HIDDEN : ECS_DISABLED; - - return S_OK; -} - -auto STDMETHODCALLTYPE CInfoCommand::GetFlags(__RPC__out EXPCMDFLAGS *pFlags) -> HRESULT { - *pFlags = ECF_SEPARATORAFTER; - return S_OK; -} - -auto STDMETHODCALLTYPE CSelectCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { - return SHStrDupW(L"Select first item", ppszName); -} - -auto STDMETHODCALLTYPE CSelectCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { - HRESULT hr; - - DWORD itemCount; - CheckHr(psiItemArray->GetCount(&itemCount)); - - if (itemCount != 1) { - *pCmdState = ECS_HIDDEN; - } else if (Environment::INSTANCE->selectedItem.name.empty()) { - *pCmdState = ECS_ENABLED; - } else { - Item currItem; - CheckHr(currItem.ExtractItemAt(psiItemArray, 0)); - - *pCmdState = currItem.name == Environment::INSTANCE->selectedItem.name ? ECS_HIDDEN : ECS_ENABLED; - } - - return S_OK; -} - -auto STDMETHODCALLTYPE CSelectCommand::Invoke(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__in_opt IBindCtx *pbc) -> HRESULT { - HRESULT hr; - - DWORD itemCount; - CheckHr(psiItemArray->GetCount(&itemCount)); - - if (itemCount != 1) { - return E_UNEXPECTED; - } - - CheckHr(Environment::INSTANCE->selectedItem.ExtractItemAt(psiItemArray, 0)); - Environment::INSTANCE->FlushSelectedItem(); - - return S_OK; -} - -auto CDiffCommand::SetCmdIndex(int cmdIndex) -> void { - _cmdIndex = cmdIndex; -} - -auto STDMETHODCALLTYPE CDiffCommand::GetTitle(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszName) -> HRESULT { - return SHStrDupW(Environment::INSTANCE->comparers[_cmdIndex].name.c_str(), ppszName); -} - -auto CDiffCommand::GetIcon(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__deref_out_opt_string LPWSTR *ppszIcon) -> HRESULT { - return SHStrDupW(Environment::INSTANCE->comparers[_cmdIndex].iconPath.c_str(), ppszIcon); -} - -auto STDMETHODCALLTYPE CDiffCommand::GetState(__RPC__in_opt IShellItemArray *psiItemArray, BOOL fOkToBeSlow, __RPC__out EXPCMDSTATE *pCmdState) -> HRESULT { - *pCmdState = ECS_ENABLED; - return S_OK; -} - -static auto ReplaceSubString(std::wstring &str, const WCHAR *toFind, const WCHAR *replaceWith) { - const size_t replaceLen = std::char_traits::length(toFind); - - std::size_t pos; - while ((pos = str.find(toFind)) != std::string::npos) { - str.replace(pos, replaceLen, replaceWith); - } -} - -auto STDMETHODCALLTYPE CDiffCommand::Invoke(__RPC__in_opt IShellItemArray *psiItemArray, __RPC__in_opt IBindCtx *pbc) -> HRESULT { - HRESULT hr; - - Item firstItem, secondItem; - CheckHr(Environment::ExtractItems(psiItemArray, firstItem, secondItem)); - - if (hr != S_OK) { - return E_UNEXPECTED; - } - - std::wstring cmdline = Environment::INSTANCE->comparers[_cmdIndex].cmdline; - ReplaceSubString(cmdline, COMPARER_ARGS_LEFT_PLACEHOLDER, firstItem.name.c_str()); - ReplaceSubString(cmdline, COMPARER_ARGS_RIGHT_PLACEHOLDER, secondItem.name.c_str()); - - STARTUPINFOW si { .cb = sizeof(si) }; - PROCESS_INFORMATION pi; - - if (!CreateProcessW(nullptr, cmdline.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { - return E_FAIL; - } - - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - - Environment::INSTANCE->selectedItem.name.clear(); - Environment::INSTANCE->FlushSelectedItem(); - - return S_OK; -} diff --git a/uninstall.cmd b/uninstall.cmd index f936205..f8f4c3e 100644 --- a/uninstall.cmd +++ b/uninstall.cmd @@ -1,2 +1,2 @@ -regsvr32 /u "%~dp0compare_menu_64.dll" -regsvr32 /u "%~dp0compare_menu_32.dll" +regsvr32 /u "%~dp0ab_menu_64.dll" +regsvr32 /u "%~dp0ab_menu_32.dll"