From 084402a2bd1986c775762abc917889124b45afc3 Mon Sep 17 00:00:00 2001 From: Christian Gaarden Gaardmark Date: Wed, 27 Nov 2024 06:22:05 -0800 Subject: [PATCH] [New+]Windows 10 support (#35832) --- .github/actions/spell-check/expect.txt | 2 + .pipelines/ESRPSigning_core.json | 1 + PowerToys.sln | 15 + installer/PowerToysSetup/NewPlus.wxs | 14 + .../NewPlus.ShellExtension.win10.vcxproj | 152 ++++++++++ ...wPlus.ShellExtension.win10.vcxproj.filters | 116 ++++++++ .../dll.def | 6 + .../dll_main.cpp | 44 +++ .../dll_main.h | 6 + .../new.base.rc | 54 ++++ .../packages.config | 5 + .../pch.cpp | 3 + .../NewShellExtensionContextMenu.win10/pch.h | 39 +++ .../resource.base.h | 12 + .../resources.resx | 132 +++++++++ .../shell_context_menu_win10.cpp | 270 ++++++++++++++++++ .../shell_context_menu_win10.h | 45 +++ .../NewShellExtensionContextMenu.vcxproj | 3 +- ...wShellExtensionContextMenu.vcxproj.filters | 18 +- .../new_utilities.cpp | 13 + .../new_utilities.h | 256 +++++++++++++++++ .../shell_context_menu.h | 1 - .../shell_context_sub_menu.cpp | 7 +- .../shell_context_sub_menu_item.cpp | 50 +--- .../template_folder.cpp | 5 + .../template_folder.h | 2 + .../template_item.cpp | 56 ++-- .../template_item.h | 8 +- .../NewShellExtensionContextMenu/trace.cpp | 9 + .../NewShellExtensionContextMenu/trace.h | 1 + .../SettingsXAML/Views/NewPlusPage.xaml | 7 - .../Settings.UI/Strings/en-us/Resources.resw | 3 - 32 files changed, 1246 insertions(+), 109 deletions(-) create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj.filters create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll.def create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.cpp create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.h create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/new.base.rc create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.cpp create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/resource.base.h create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/resources.resx create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.cpp create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.h create mode 100644 src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.cpp diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index d657c65ff3..dc7eefe607 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -997,6 +997,7 @@ newitem newpath newplus NEWPLUSCONTEXTMENU +NEWPLUSSHELLEXTENSIONWIN newrow newsgroups NIF @@ -1168,6 +1169,7 @@ pnid Pnp Popups POPUPWINDOW +POSITIONITEM POWERRENAMECONTEXTMENU powerrenameinput POWERRENAMETEST diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index e1f0101fa2..878716c7d7 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -181,6 +181,7 @@ "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.dll", "WinUI3Apps\\NewPlusPackage.msix", + "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll", "PowerAccent.Core.dll", "PowerToys.PowerAccent.dll", diff --git a/PowerToys.sln b/PowerToys.sln index 053bfd0c73..fc40f9d7d5 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -632,6 +632,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesCsharpLibrary", "src\modules\Workspaces\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj", "{89D0E199-B17A-418C-B2F8-7375B6708357}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension.win10", "src\modules\NewPlus\NewShellExtensionContextMenu.win10\NewPlus.ShellExtension.win10.vcxproj", "{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2792,6 +2794,18 @@ Global {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.Build.0 = Release|x64 {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x86.ActiveCfg = Release|x64 {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x86.Build.0 = Release|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|ARM64.Build.0 = Debug|ARM64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x64.ActiveCfg = Debug|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x64.Build.0 = Debug|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x86.ActiveCfg = Debug|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x86.Build.0 = Debug|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|ARM64.ActiveCfg = Release|ARM64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|ARM64.Build.0 = Release|ARM64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.ActiveCfg = Release|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.Build.0 = Release|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x86.ActiveCfg = Release|x64 + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3024,6 +3038,7 @@ Global {8F021B46-362B-485C-BFBA-CCF83E820CBD} = {8F62026A-294B-41C6-8839-87463613F216} {66614C26-314C-4B91-9071-76133422CFEF} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC} {89D0E199-B17A-418C-B2F8-7375B6708357} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/NewPlus.wxs b/installer/PowerToysSetup/NewPlus.wxs index 80fd5a94f4..4dd1c67701 100644 --- a/installer/PowerToysSetup/NewPlus.wxs +++ b/installer/PowerToysSetup/NewPlus.wxs @@ -18,6 +18,19 @@ + + + + + + + + + + + + + @@ -27,6 +40,7 @@ + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj new file mode 100644 index 0000000000..6d975b3326 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj @@ -0,0 +1,152 @@ + + + + + + + + 17.0 + Win32Proj + {0db0f63a-d2f8-4da3-a650-2d0b8724218e} + NewPlusShellExtensionWin10 + 10.0.22621.0 + + + + DynamicLibrary + v143 + Unicode + + + true + + + false + true + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + PowerToys.NewPlus.ShellExtension.win10 + + + + + + Level3 + true + _DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu + false + stdcpplatest + + + Windows + true + false + runtimeobject.lib;$(CoreLibraryDependencies) + dll.def + + + + + Level3 + true + true + true + NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu + false + stdcpplatest + + + Windows + true + true + true + false + runtimeobject.lib;$(CoreLibraryDependencies) + dll.def + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + Designer + + + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {8f021b46-362b-485c-bfba-ccf83e820cbd} + + + {98537082-0fdb-40de-abd8-0dc5a4269bab} + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj.filters b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj.filters new file mode 100644 index 0000000000..25399a81dc --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj.filters @@ -0,0 +1,116 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {4cea4fff-ccef-4b62-9e46-f33da2b9a0cc} + + + + + Header Files + + + Header Files + + + Header Files + + + Generated Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Generated Files + + + + + + Source Files + + + Resource Files + + + Resource Files + + + + + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll.def b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll.def new file mode 100644 index 0000000000..74de7c3181 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll.def @@ -0,0 +1,6 @@ +LIBRARY + +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllGetActivationFactory PRIVATE diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.cpp new file mode 100644 index 0000000000..b036eb2ed0 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.cpp @@ -0,0 +1,44 @@ +#include "pch.h" + +#include "shell_context_menu_win10.h" +#include "dll_main.h" +#include "trace.h" + +#include + +HMODULE module_instance_handle = 0; +Shared::Trace::ETWTrace trace(L"NewPlusShellExtension_Win10"); + +BOOL APIENTRY DllMain(HMODULE module_handle, DWORD ul_reason_for_call, LPVOID reserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + module_instance_handle = module_handle; + Trace::RegisterProvider(); + newplus::utilities::init_logger(); + break; + + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return TRUE; +} + +STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory) +{ + return Module::GetModule().GetActivationFactory(activatableClassId, factory); +} + +STDAPI DllCanUnloadNow() +{ + return Module::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE; +} + +STDAPI DllGetClassObject(_In_ REFCLSID ref_class_id, _In_ REFIID ref_interface_id, _Outptr_ LPVOID FAR* object) +{ + return Module::GetModule().GetClassObject(ref_class_id, ref_interface_id, object); +} + +CoCreatableClass(shell_context_menu_win10) diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.h b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.h new file mode 100644 index 0000000000..c2d69eebfd --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/dll_main.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +extern HMODULE module_instance_handle; +extern Shared::Trace::ETWTrace trace; diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/new.base.rc b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/new.base.rc new file mode 100644 index 0000000000..5a35892751 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/new.base.rc @@ -0,0 +1,54 @@ +#include +#include "Generated Files/resource.h" +#include "../../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION FILE_VERSION + PRODUCTVERSION PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config new file mode 100644 index 0000000000..ff4b059648 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.cpp new file mode 100644 index 0000000000..1a6a81e238 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.cpp @@ -0,0 +1,3 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h new file mode 100644 index 0000000000..3ddfa219b2 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h @@ -0,0 +1,39 @@ +// Precompiled header file. + +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOHELP +#define NOCOMM + +// Windows and STL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace Microsoft::WRL; + +// PowerToys project common +#include +#include +#include +#include +#include +#include + +// New project specific +#include "dll_main.h" +#include "template_folder.h" +#include "settings.h" +#include "new_utilities.h" diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/resource.base.h b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/resource.base.h new file mode 100644 index 0000000000..25dc1f71b0 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/resource.base.h @@ -0,0 +1,12 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys.New+" +#define INTERNAL_NAME "PowerToys.New+" +#define ORIGINAL_FILENAME "PowerToys.NewPlus.ShellExtension.win10.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/resources.resx b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/resources.resx new file mode 100644 index 0000000000..7db1823f95 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + New+ + The main context menu item that users click on. This should be localized to match New in Windows. e.g. Danish it would become Ny+ + + + Open templates + The menu item in the context menu that enables user to open the folder that contains their templates. + + + Templates + Default subfolder name where templates are stored. + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.cpp new file mode 100644 index 0000000000..c54631df09 --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.cpp @@ -0,0 +1,270 @@ +#include "pch.h" + +#include "shell_context_menu_win10.h" +#include "shell_context_sub_menu.h" +#include "shell_context_sub_menu_item.h" +#include "new_utilities.h" +#include "settings.h" +#include "trace.h" +#include "Generated Files/resource.h" +#include +#include + +using namespace Microsoft::WRL; +using namespace newplus; + +shell_context_menu_win10::~shell_context_menu_win10() +{ + for (const auto& handle : bitmap_handles) + { + DeleteObject(handle); + } +} + +#pragma region IShellExtInit +IFACEMETHODIMP shell_context_menu_win10::Initialize(PCIDLIST_ABSOLUTE, IDataObject*, HKEY) +{ + return S_OK; +} +#pragma endregion + +#pragma region IContextMenu +IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags) +{ + if (!NewSettingsInstance().GetEnabled() + || package::IsWin11OrGreater() + ) + { + return E_FAIL; + } + + if (menu_flags & CMF_DEFAULTONLY) + { + return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); + } + + try + { + // Create the initial context popup menu containing the list of templates and open templates action + int menu_id = menu_first_cmd_id; + MENUITEMINFO newplus_main_context_menu_item; + HMENU sub_menu_of_templates = CreatePopupMenu(); + int sub_menu_index = 0; + + // Determine the New+ Template folder location + const std::filesystem::path template_folder_root = utilities::get_new_template_folder_location(); + + // Create the New+ Template folder location if it doesn't exist (very rare scenario) + utilities::create_folder_if_not_exist(template_folder_root); + + // Scan the folder for any files and folders (the templates) + templates = new template_folder(template_folder_root); + templates->rescan_template_folder(); + const auto number_of_templates = templates->list_of_templates.size(); + + // Create the New+ menu item and point to the initial context popup menu + static const std::wstring localized_context_menu_item = + GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_NEW, L"New+"); + wchar_t newplus_menu_name[20] = { 0 }; + wcscpy_s(newplus_menu_name, ARRAYSIZE(newplus_menu_name), localized_context_menu_item.c_str()); + newplus_main_context_menu_item.cbSize = sizeof(MENUITEMINFOW); + newplus_main_context_menu_item.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_SUBMENU; + newplus_main_context_menu_item.wID = menu_id; + newplus_main_context_menu_item.fType = MFT_STRING; + newplus_main_context_menu_item.dwTypeData = (PWSTR)newplus_menu_name; + newplus_main_context_menu_item.hSubMenu = sub_menu_of_templates; + const auto newplus_icon_index = 0; + + if (bitmap_handles.size() == 0) + { + const std::wstring icon_file = utilities::get_new_icon_resource_filepath( + module_instance_handle, ThemeHelpers::GetAppTheme()) + .c_str(); + HICON local_icon_handle = static_cast( + LoadImage(NULL, icon_file.c_str(), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE)); + + if (local_icon_handle) + { + bitmap_handles.push_back(CreateBitmapFromIcon(local_icon_handle)); + DestroyIcon(local_icon_handle); + } + } + if (bitmap_handles.size() > newplus_icon_index && bitmap_handles[newplus_icon_index]) + { + newplus_main_context_menu_item.fMask |= MIIM_BITMAP; + newplus_main_context_menu_item.hbmpItem = bitmap_handles[newplus_icon_index]; + } + + menu_id++; + + // Add template items to context menu + int index = 0; + for (; index < number_of_templates; index++) + { + const auto template_item = templates->get_template_item(index); + add_template_item_to_context_menu(sub_menu_of_templates, sub_menu_index, template_item, menu_id, index); + menu_id++; + sub_menu_index++; + } + + // Add separator to context menu + add_separator_to_context_menu(sub_menu_of_templates, sub_menu_index); + sub_menu_index++; + + // Add "Open templates" item to context menu + add_open_templates_to_context_menu(sub_menu_of_templates, sub_menu_index, template_folder_root, menu_id, index); + menu_id++; + + if (!InsertMenuItem(menu_handle, menu_index, TRUE, &newplus_main_context_menu_item)) + { + Logger::error(L"QueryContextMenu() failed. {}", get_last_error_or_default(GetLastError())); + return HRESULT_FROM_WIN32(GetLastError()); + } + else + { + // Return the amount if entries inserted + const auto number_of_items_inserted = menu_id - menu_first_cmd_id; + return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, number_of_items_inserted); + } + } + catch (const std::exception& ex) + { + Logger::error(ex.what()); + } + + return E_FAIL; +} + +void shell_context_menu_win10::add_open_templates_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, const std::filesystem::path& template_folder_root, int menu_id, int index) +{ + static const std::wstring localized_context_menu_item_open_templates = + GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_OPEN_TEMPLATES, L"Open templates"); + wchar_t menu_name_open[256] = { 0 }; + wcscpy_s(menu_name_open, ARRAYSIZE(menu_name_open), localized_context_menu_item_open_templates.c_str()); + const auto open_folder_item = Make(template_folder_root); + MENUITEMINFO newplus_menu_item_open_templates; + newplus_menu_item_open_templates.cbSize = sizeof(MENUITEMINFO); + newplus_menu_item_open_templates.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID; + newplus_menu_item_open_templates.wID = menu_id; + newplus_menu_item_open_templates.fType = MFT_STRING; + newplus_menu_item_open_templates.dwTypeData = (PWSTR)menu_name_open; + + const auto open_templates_icon_index = index + 1; + if (bitmap_handles.size() <= open_templates_icon_index) + { + const std::wstring icon_file = utilities::get_open_templates_icon_resource_filepath( + module_instance_handle, ThemeHelpers::GetAppTheme()) + .c_str(); + HICON open_template_icon_handle = static_cast( + LoadImage(NULL, icon_file.c_str(), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE)); + if (open_template_icon_handle) + { + bitmap_handles.push_back(CreateBitmapFromIcon(open_template_icon_handle)); + DestroyIcon(open_template_icon_handle); + } + } + if (bitmap_handles.size() > open_templates_icon_index && bitmap_handles[open_templates_icon_index]) + { + newplus_menu_item_open_templates.fMask |= MIIM_BITMAP; + newplus_menu_item_open_templates.hbmpItem = bitmap_handles[open_templates_icon_index]; + } + + InsertMenuItem(sub_menu_of_templates, sub_menu_index, TRUE, &newplus_menu_item_open_templates); +} + +void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index) +{ + MENUITEMINFO menu_item_separator; + menu_item_separator.cbSize = sizeof(MENUITEMINFO); + menu_item_separator.fMask = MIIM_FTYPE; + menu_item_separator.fType = MFT_SEPARATOR; + InsertMenuItem(sub_menu_of_templates, sub_menu_index, TRUE, &menu_item_separator); +} + +void shell_context_menu_win10::add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index) +{ + wchar_t menu_name[256] = { 0 }; + wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(!utilities::get_newplus_setting_hide_extension(), !utilities::get_newplus_setting_hide_starting_digits()).c_str()); + MENUITEMINFO newplus_menu_item_template; + newplus_menu_item_template.cbSize = sizeof(MENUITEMINFO); + newplus_menu_item_template.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_DATA; + newplus_menu_item_template.wID = menu_id; + newplus_menu_item_template.fType = MFT_STRING; + newplus_menu_item_template.dwTypeData = (PWSTR)menu_name; + const auto current_template_icon_index = index + 1; + if (bitmap_handles.size() <= current_template_icon_index) + { + HICON template_icon_handle = template_item->get_explorer_icon_handle(); + if (template_icon_handle) + { + bitmap_handles.push_back(CreateBitmapFromIcon(template_icon_handle)); + DestroyIcon(template_icon_handle); + } + } + if (bitmap_handles.size() > current_template_icon_index && bitmap_handles[current_template_icon_index]) + { + newplus_menu_item_template.fMask |= MIIM_BITMAP; + newplus_menu_item_template.hbmpItem = bitmap_handles[current_template_icon_index]; + } + + InsertMenuItem(sub_menu_of_templates, sub_menu_index, TRUE, &newplus_menu_item_template); +} + +IFACEMETHODIMP shell_context_menu_win10::InvokeCommand(CMINVOKECOMMANDINFO* params) +{ + if (!params) + { + return E_FAIL; + } + + // Get selected menu item (a template or the "Open templates" item) + const auto selected_menu_item_index = LOWORD(params->lpVerb) - 1; + if (selected_menu_item_index < 0) + { + return E_FAIL; + } + + const auto number_of_templates = templates->list_of_templates.size(); + const bool is_template_item = selected_menu_item_index < number_of_templates; + + // Save how many item templates we have so it can be sent later when we do something with New+. + // It will be sent when the user does something, similar to Windows 11 context menu. + newplus::utilities::set_saved_number_of_templates(static_cast(number_of_templates)); + + if (is_template_item) + { + // It's a template menu item + const auto template_entry = templates->get_template_item(selected_menu_item_index); + + return newplus::utilities::copy_template(template_entry, site_of_folder); + } + else + { + // It's the "Open templates" menu item + const std::filesystem::path template_folder_root = utilities::get_new_template_folder_location(); + + return newplus::utilities::open_template_folder(template_folder_root); + } + + return E_FAIL; +} + +IFACEMETHODIMP shell_context_menu_win10::GetCommandString(UINT_PTR, UINT, UINT*, CHAR*, UINT) +{ + return E_NOTIMPL; +} +#pragma endregion + +#pragma region IObjectWithSite +IFACEMETHODIMP shell_context_menu_win10::SetSite(_In_ IUnknown* site) noexcept +{ + this->site_of_folder = site; + + return S_OK; +} +IFACEMETHODIMP shell_context_menu_win10::GetSite(_In_ REFIID riid, _COM_Outptr_ void** returned_site) noexcept +{ + return this->site_of_folder.CopyTo(riid, returned_site); +} +#pragma endregion + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.h b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.h new file mode 100644 index 0000000000..f69355cccb --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/shell_context_menu_win10.h @@ -0,0 +1,45 @@ +#pragma once + +#include "pch.h" +#include + +using namespace Microsoft::WRL; + +#define NEW_SHELL_EXTENSION_EXPLORER_COMMAND_WIN10_UUID "FF90D477-E32A-4BE8-8CC5-A502A97F5401" + +// File Explorer context menu "New+" for Windows 10 +class __declspec(uuid(NEW_SHELL_EXTENSION_EXPLORER_COMMAND_WIN10_UUID)) shell_context_menu_win10 : + public RuntimeClass< + RuntimeClassFlags, + IShellExtInit, + IContextMenu, + IObjectWithSite> +{ +public: + ~shell_context_menu_win10(); + +#pragma region IShellExtInit + IFACEMETHODIMP Initialize(_In_opt_ PCIDLIST_ABSOLUTE, _In_ IDataObject*, HKEY); +#pragma endregion + +#pragma region IContextMenu + IFACEMETHODIMP QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags); + IFACEMETHODIMP InvokeCommand(CMINVOKECOMMANDINFO* pici); + IFACEMETHODIMP GetCommandString(UINT_PTR, UINT, UINT*, CHAR*, UINT); +#pragma endregion + +#pragma region IObjectWithSite + IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept; + IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept; +#pragma endregion + +protected: + void add_open_templates_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, const std::filesystem::path& template_folder_root, int menu_id, int index); + void add_separator_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index); + void add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index); + + HINSTANCE instance_handle = 0; + ComPtr site_of_folder; + newplus::template_folder* templates = nullptr; + std::vector bitmap_handles; +}; diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj index 8d2e617579..68b74d6ae1 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj @@ -128,6 +128,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv + @@ -227,7 +228,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv - + diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters index 2e8323a6f9..de0cea2017 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters @@ -31,6 +31,9 @@ Source Files + + Source Files + @@ -123,9 +126,6 @@ Source Files - - Asset Files - Asset Files @@ -190,14 +190,8 @@ - - Template Examples\Example folder - - - Template Examples\Example folder - - - Template Examples - + + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.cpp new file mode 100644 index 0000000000..d7c324e5af --- /dev/null +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.cpp @@ -0,0 +1,13 @@ +#include "pch.h" +#include "new_utilities.h" + +// HACK: Store number of templates when generating the menu entries to send later. +size_t saved_number_of_templates = -1; +size_t newplus::utilities::get_saved_number_of_templates() +{ + return saved_number_of_templates; +} +void newplus::utilities::set_saved_number_of_templates(size_t templates) +{ + saved_number_of_templates = templates; +} diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h index 7ae46635a8..adf186a509 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h @@ -7,11 +7,17 @@ #include "constants.h" #include "settings.h" +#include "template_item.h" +#include "trace.h" #pragma comment(lib, "Shlwapi.lib") +using namespace newplus; + namespace newplus::utilities { + size_t get_saved_number_of_templates(); + void set_saved_number_of_templates(size_t templates); inline std::wstring get_explorer_icon(std::filesystem::path path) { @@ -39,6 +45,33 @@ namespace newplus::utilities return icon_resource; } + inline HICON get_explorer_icon_handle(std::filesystem::path path) + { + SHFILEINFO shell_file_info = { 0 }; + const std::wstring filepath = path.wstring(); + DWORD_PTR result = SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICON); + if (shell_file_info.hIcon) + { + return shell_file_info.hIcon; + } + + WCHAR icon_resource_specifier[MAX_PATH] = { 0 }; + DWORD buffer_length = MAX_PATH; + const std::wstring extension = path.extension().wstring(); + const HRESULT hr = AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN, + ASSOCSTR_DEFAULTICON, + extension.c_str(), + NULL, + icon_resource_specifier, + &buffer_length); + const std::wstring icon_resource = icon_resource_specifier; + + const auto icon_x = GetSystemMetrics(SM_CXSMICON); + const auto icon_y = GetSystemMetrics(SM_CYSMICON); + HICON hIcon = static_cast(LoadImage(NULL, icon_resource.c_str(), IMAGE_ICON, icon_x, icon_y, LR_LOADFROMFILE)); + return hIcon; + } + inline bool is_hidden(const std::filesystem::path path) { const std::filesystem::path::string_type name = path.filename(); @@ -180,4 +213,227 @@ namespace newplus::utilities return path; } + inline bool is_desktop_folder(const std::filesystem::path target_fullpath) + { + TCHAR desktopPath[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath))) + { + return StrCmpIW(target_fullpath.c_str(), desktopPath) == 0; + } + return false; + } + + inline void explorer_enter_rename_mode(const std::filesystem::path target_fullpath_of_new_instance) + { + const std::filesystem::path path_without_new_file_or_dir = target_fullpath_of_new_instance.parent_path(); + const std::filesystem::path new_file_or_dir_without_path = target_fullpath_of_new_instance.filename(); + + ComPtr shell_windows; + + HRESULT hr; + if (FAILED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_PPV_ARGS(&shell_windows)))) + { + return; + } + + long window_handle; + ComPtr shell_window; + const bool object_created_on_desktop = is_desktop_folder(path_without_new_file_or_dir.c_str()); + if (object_created_on_desktop) + { + // Special handling for desktop folder + VARIANT empty_yet_needed_incl_init; + VariantInit(&empty_yet_needed_incl_init); + + if (FAILED(shell_windows->FindWindowSW(&empty_yet_needed_incl_init, &empty_yet_needed_incl_init, SWC_DESKTOP, &window_handle, SWFO_NEEDDISPATCH, &shell_window))) + { + return; + } + } + else + { + long count_of_shell_windows = 0; + shell_windows->get_Count(&count_of_shell_windows); + + for (long i = 0; i < count_of_shell_windows; ++i) + { + ComPtr web_browser_app; + VARIANT v; + V_VT(&v) = VT_I4; + V_I4(&v) = i; + hr = shell_windows->Item(v, &shell_window); + if (SUCCEEDED(hr) && shell_window) + { + hr = shell_window.As(&web_browser_app); + if (SUCCEEDED(hr)) + { + BSTR folder_view_location; + hr = web_browser_app->get_LocationURL(&folder_view_location); + if (SUCCEEDED(hr) && folder_view_location) + { + wchar_t path[MAX_PATH]; + DWORD pathLength = ARRAYSIZE(path); + hr = PathCreateFromUrl(folder_view_location, path, &pathLength, 0); + SysFreeString(folder_view_location); + if (SUCCEEDED(hr) && StrCmpIW(path_without_new_file_or_dir.c_str(), path) == 0) + { + break; + } + } + } + } + shell_window = nullptr; + } + } + + if (!shell_window) + { + return; + } + + ComPtr service_provider; + shell_window.As(&service_provider); + ComPtr shell_browser; + service_provider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shell_browser)); + ComPtr shell_view; + shell_browser->QueryActiveShellView(&shell_view); + ComPtr folder_view; + shell_view.As(&folder_view); + + // Find the newly created object (file or folder) + // And put object into edit mode (SVSI_EDIT) and if desktop also reposition + int number_of_objects_in_view = 0; + bool done = false; + folder_view->ItemCount(SVGIO_ALLVIEW, &number_of_objects_in_view); + for (int i = 0; i < number_of_objects_in_view && !done; ++i) + { + std::wstring path_of_item(MAX_PATH, 0); + LPITEMIDLIST shell_item_ids; + + folder_view->Item(i, &shell_item_ids); + SHGetPathFromIDList(shell_item_ids, &path_of_item[0]); + + const std::wstring current_filename = std::filesystem::path(path_of_item.c_str()).filename(); + + if (utilities::wstring_same_when_comparing_ignore_case(new_file_or_dir_without_path, current_filename)) + { + const DWORD common_select_flags = SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED; + + if (object_created_on_desktop) + { + // Newly created object is on the desktop -- reposition under mouse and enter rename mode + LPCITEMIDLIST shell_item_to_select_and_position[] = { shell_item_ids }; + POINT mouse_position; + GetCursorPos(&mouse_position); + mouse_position.x -= GetSystemMetrics(SM_CXMENUSIZE); + mouse_position.x = max(mouse_position.x, 20); + mouse_position.y -= GetSystemMetrics(SM_CXMENUSIZE)/2; + mouse_position.y = max(mouse_position.y, 20); + POINT position[] = { mouse_position }; + folder_view->SelectAndPositionItems(1, shell_item_to_select_and_position, position, common_select_flags | SVSI_POSITIONITEM); + } + else + { + // Enter rename mode + folder_view->SelectItem(i, common_select_flags); + } + done = true; + } + CoTaskMemFree(shell_item_ids); + } + } + + inline HRESULT copy_template(const template_item* template_entry, const ComPtr site_of_folder) + { + HRESULT hr = S_OK; + + try + { + Logger::info(L"Copying template"); + + if (newplus::utilities::get_saved_number_of_templates() >= 0) + { + // Log that context menu was shown and with how many items + trace.UpdateState(true); + Trace::EventShowTemplateItems(newplus::utilities::get_saved_number_of_templates()); + trace.Flush(); + trace.UpdateState(false); + } + + // Determine target path of where context menu was displayed + const auto target_path_name = utilities::get_path_from_unknown_site(site_of_folder); + + // Determine initial filename + std::filesystem::path source_fullpath = template_entry->path; + std::filesystem::path target_fullpath = std::wstring(target_path_name); + + // Only append name to target if source is not a directory + if (!utilities::is_directory(source_fullpath)) + { + target_fullpath.append(template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits())); + } + + // Copy file and determine final filename + std::filesystem::path target_final_fullpath = template_entry->copy_object_to(GetActiveWindow(), target_fullpath); + + trace.UpdateState(true); + Trace::EventCopyTemplate(target_final_fullpath.extension().c_str()); + trace.Flush(); + trace.UpdateState(false); + + // Refresh folder items + template_entry->refresh_target(target_final_fullpath); + + // Enter rename mode + template_entry->enter_rename_mode(target_final_fullpath); + } + catch (const std::exception& ex) + { + Logger::error(ex.what()); + + hr = S_FALSE; + } + + trace.UpdateState(true); + Trace::EventCopyTemplateResult(hr); + trace.Flush(); + trace.UpdateState(false); + + return hr; + } + + inline HRESULT open_template_folder(const std::filesystem::path template_folder) + { + HRESULT hr = S_OK; + + try + { + Logger::info(L"Open templates folder"); + + if (newplus::utilities::get_saved_number_of_templates() >= 0) + { + // Log that context menu was shown and with how many items + trace.UpdateState(true); + Trace::EventShowTemplateItems(newplus::utilities::get_saved_number_of_templates()); + trace.Flush(); + trace.UpdateState(false); + } + + const std::wstring verb_hardcoded_do_not_change = L"open"; + ShellExecute(nullptr, verb_hardcoded_do_not_change.c_str(), template_folder.c_str(), NULL, NULL, SW_SHOWNORMAL); + + trace.UpdateState(true); + Trace::EventOpenTemplates(); + trace.Flush(); + trace.UpdateState(false); + } + catch (const std::exception& ex) + { + Logger::error(ex.what()); + + hr = S_FALSE; + } + + return hr; + } } diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.h b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.h index 564069254d..6e74d9730d 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.h +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.h @@ -27,7 +27,6 @@ public: #pragma region IObjectWithSite IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept; - IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept; #pragma endregion diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp index 47f93beac7..921130baa4 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp @@ -1,13 +1,13 @@ #include "pch.h" #include "shell_context_sub_menu.h" #include "trace.h" +#include "new_utilities.h" using namespace Microsoft::WRL; // // Sub context menu command enumerator shell_context_sub_menu::shell_context_sub_menu(const ComPtr site_of_folder) { - trace.UpdateState(true); this->site_of_folder = site_of_folder; // Determine the New+ Template folder location @@ -36,8 +36,9 @@ shell_context_sub_menu::shell_context_sub_menu(const ComPtr site_of_fo current_command = explorer_menu_item_commands.cbegin(); - // Log that context menu was shown and with how many items - Trace::EventShowTemplateItems(number_of_templates); + // Save how many item templates we have so it can be sent later when we do something with New+. + // We don't send it here or it would send an event every time we open a context menu. + newplus::utilities::set_saved_number_of_templates(static_cast(number_of_templates)); } // IEnumExplorerCommand diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp index 12e5f9cf5c..2b1b940d65 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp @@ -63,49 +63,7 @@ IFACEMETHODIMP shell_context_sub_menu_item::GetState(_In_opt_ IShellItemArray* s IFACEMETHODIMP shell_context_sub_menu_item::Invoke(_In_opt_ IShellItemArray*, _In_opt_ IBindCtx*) noexcept { - HRESULT hr = S_OK; - try - { - trace.UpdateState(true); - - // Determine target path of where context menu was displayed - const auto target_path_name = utilities::get_path_from_unknown_site(site_of_folder); - - // Determine initial filename - std::filesystem::path source_fullpath = template_entry->path; - std::filesystem::path target_fullpath = std::wstring(target_path_name); - - // Only append name to target if source is not a directory - if (!utilities::is_directory(target_fullpath)) - { - target_fullpath.append(this->template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits())); - } - - // Copy file and determine final filename - std::filesystem::path target_final_fullpath = this->template_entry->copy_object_to(GetActiveWindow(), target_fullpath); - - Trace::EventCopyTemplate(target_final_fullpath.extension().c_str()); - - // Refresh folder items - SHChangeNotify(SHCNE_CREATE, SHCNF_PATH | SHCNF_FLUSH, target_final_fullpath.wstring().c_str(), NULL); - - // Enter rename mode - this->template_entry->enter_rename_mode(site_of_folder, target_final_fullpath); - - Trace::EventCopyTemplateResult(S_OK); - } - catch (const std::exception& ex) - { - Trace::EventCopyTemplateResult(S_FALSE); - Logger::error(ex.what()); - - hr = S_FALSE; - } - - trace.Flush(); - trace.UpdateState(false); - - return hr; + return newplus::utilities::copy_template(template_entry, site_of_folder); } IFACEMETHODIMP shell_context_sub_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags) @@ -162,9 +120,5 @@ IFACEMETHODIMP template_folder_context_menu_item::GetIcon(_In_opt_ IShellItemArr IFACEMETHODIMP template_folder_context_menu_item::Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept { - Logger::info(L"Open templates folder"); - const std::wstring verb_hardcoded_do_not_change = L"open"; - ShellExecute(nullptr, verb_hardcoded_do_not_change.c_str(), shell_template_folder.c_str(), NULL, NULL, SW_SHOWNORMAL); - - return S_OK; + return newplus::utilities::open_template_folder(shell_template_folder); } diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp index b4bfdf63f5..e52a871e80 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp @@ -11,6 +11,11 @@ template_folder::template_folder(const std::filesystem::path newplus_template_fo this->template_folder_path = newplus_template_folder; } +template_folder::~template_folder() +{ + list_of_templates.clear(); +} + void template_folder::init() { rescan_template_folder(); diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h index fe644be2e9..4ff9499819 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h @@ -13,6 +13,8 @@ namespace newplus { public: template_folder(const std::filesystem::path newplus_template_folder); + ~template_folder(); + void rescan_template_folder(); std::filesystem::path template_folder_path; diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp index e644df5c85..5de461bece 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp @@ -63,6 +63,11 @@ std::wstring template_item::get_explorer_icon() const return utilities::get_explorer_icon(path); } +HICON template_item::get_explorer_icon_handle() const +{ + return utilities::get_explorer_icon_handle(path); +} + std::filesystem::path template_item::copy_object_to(const HWND window_handle, const std::filesystem::path destination) const { // SHFILEOPSTRUCT wants the from and to paths to be terminated with two NULLs, @@ -86,6 +91,14 @@ std::filesystem::path template_item::copy_object_to(const HWND window_handle, co if (!file_operation_params.hNameMappings) { // No file name collision on copy + if (utilities::is_directory(this->path)) + { + // Append dir for consistency on directory naming inclusion for with and without collision + std::filesystem::path with_dir = destination; + with_dir /= this->path.filename(); + return with_dir; + } + return destination; } @@ -104,44 +117,23 @@ std::filesystem::path template_item::copy_object_to(const HWND window_handle, co return final_path; } -void template_item::enter_rename_mode(const ComPtr site, const std::filesystem::path target_fullpath) const +void template_item::refresh_target(const std::filesystem::path target_final_fullpath) const { - std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, site, target_fullpath); + SHChangeNotify(SHCNE_CREATE, SHCNF_PATH | SHCNF_FLUSH, target_final_fullpath.wstring().c_str(), NULL); +} + +void template_item::enter_rename_mode(const std::filesystem::path target_fullpath) const +{ + std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, target_fullpath); thread_for_renaming_workaround.detach(); } -void template_item::rename_on_other_thread_workaround(const ComPtr site, const std::filesystem::path target_fullpath) +void template_item::rename_on_other_thread_workaround(const std::filesystem::path target_fullpath) { // Have been unable to have Windows Explorer Shell enter rename mode from the main thread - // Sleep for a bit to only enter rename mode when icon has been drawn. Not strictly needed. - const std::chrono::milliseconds approx_wait_for_icon_redraw_not_needed{ 350 }; + // Sleep for a bit to only enter rename mode when icon has been drawn. + const std::chrono::milliseconds approx_wait_for_icon_redraw_not_needed{ 50 }; std::this_thread::sleep_for(std::chrono::milliseconds(approx_wait_for_icon_redraw_not_needed)); - const std::wstring filename = target_fullpath.filename(); - - ComPtr service_provider; - site->QueryInterface(IID_PPV_ARGS(&service_provider)); - ComPtr folder_view; - service_provider->QueryService(__uuidof(IFolderView), IID_PPV_ARGS(&folder_view)); - - int count = 0; - folder_view->ItemCount(SVGIO_ALLVIEW, &count); - - for (int i = 0; i < count; ++i) - { - std::wstring path_of_item(MAX_PATH, 0); - LPITEMIDLIST pidl; - - folder_view->Item(i, &pidl); - SHGetPathFromIDList(pidl, &path_of_item[0]); - CoTaskMemFree(pidl); - - std::wstring current_filename = std::filesystem::path(path_of_item.c_str()).filename(); - - if (utilities::wstring_same_when_comparing_ignore_case(filename, current_filename)) - { - folder_view->SelectItem(i, SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED); - break; - } - } + newplus::utilities::explorer_enter_rename_mode(target_fullpath); } diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h index ffb1e8839f..7b8ba78aee 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h @@ -20,15 +20,19 @@ namespace newplus std::wstring get_target_filename(const bool include_starting_digits) const; std::wstring get_explorer_icon() const; + + HICON get_explorer_icon_handle() const; std::filesystem::path copy_object_to(const HWND window_handle, const std::filesystem::path destination) const; - void enter_rename_mode(const ComPtr site, const std::filesystem::path target_folder) const; + void refresh_target(const std::filesystem::path target_final_fullpath) const; + + void enter_rename_mode(const std::filesystem::path target_fullpath) const; std::filesystem::path path; private: - static void rename_on_other_thread_workaround(const ComPtr site, const std::filesystem::path target_fullpath); + static void rename_on_other_thread_workaround(const std::filesystem::path target_fullpath); std::wstring remove_starting_digits_from_filename(std::wstring filename) const; }; diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/trace.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/trace.cpp index 3a2c8c8c91..d126c6dd02 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/trace.cpp +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/trace.cpp @@ -58,3 +58,12 @@ void Trace::EventCopyTemplateResult(_In_ const HRESULT hr) noexcept TraceLoggingHResult(hr), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); } + +void Trace::EventOpenTemplates() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "NewPlus_EventOpenTemplates", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/trace.h b/src/modules/NewPlus/NewShellExtensionContextMenu/trace.h index 7c21086642..1d0c256285 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/trace.h +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/trace.h @@ -12,4 +12,5 @@ public: static void EventShowTemplateItems(_In_ const size_t number_of_templates) noexcept; static void EventCopyTemplate(_In_ const std::wstring template_file_extension) noexcept; static void EventCopyTemplateResult(_In_ const HRESULT hr) noexcept; + static void EventOpenTemplates() noexcept; }; diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml index ef8b9c68e8..7f4e427d65 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml @@ -23,13 +23,6 @@ - - Enable New+ Localize product name in accordance with Windows New - - New+ is not supported in Windows 10 and is not expected to work. - PowerToys "Backup and Restore" feature doesn't take templates into account at this moment. If you use that feature, templates will have to be copied manually.