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.