diff --git a/.github/actions/spell-check/allow/names.txt b/.github/actions/spell-check/allow/names.txt
index c83877e253..95c1427b9d 100644
--- a/.github/actions/spell-check/allow/names.txt
+++ b/.github/actions/spell-check/allow/names.txt
@@ -64,6 +64,7 @@ Essey
ethanfangg
ferraridavide
frankychen
+gaardmark
gabime
Galaxi
Garside
diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 458e3087b2..e7d66027ae 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -80,6 +80,8 @@ asf
AShortcut
ASingle
ASSOCCHANGED
+ASSOCF
+ASSOCSTR
ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
@@ -318,6 +320,7 @@ Dedup
DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR
DEFAULTFLAGS
+DEFAULTICON
DEFAULTONLY
DEFAULTTONEAREST
DEFAULTTONULL
@@ -332,6 +335,7 @@ deletethis
DENORMAL
depersist
deprioritized
+DESELECTOTHERS
DESKTOPABSOLUTEEDITING
DESKTOPABSOLUTEPARSING
desktopshorcutinstalled
@@ -428,6 +432,7 @@ encodedlaunch
encryptor
endpointvolume
ENDSESSION
+ENSUREVISIBLE
ENTERSIZEMOVE
ENU
EOAC
@@ -509,6 +514,7 @@ FOLDERID
folderpath
FORCEMINIMIZE
formatetc
+FORPARSING
FRAMECHANGED
frm
Froml
@@ -563,6 +569,7 @@ Hashset
hashtag
HASHVAL
HASSTRINGS
+HASSUBCOMMANDS
hbitmap
hbm
hbmp
@@ -641,6 +648,7 @@ IBeam
ICapture
IClass
ICONERROR
+ICONLOCATION
IData
IDD
IDesktop
@@ -658,6 +666,7 @@ IFACEMETHOD
IFACEMETHODIMP
IFile
IFilter
+IGNOREUNKNOWN
IGraphics
iid
Iindex
@@ -719,6 +728,7 @@ ISettings
IShell
isocpp
iss
+ISSEPARATOR
ITask
ith
ITHUMBNAIL
@@ -994,6 +1004,8 @@ newdev
NEWDIALOGSTYLE
newitem
newpath
+newplus
+NEWPLUSCONTEXTMENU
newrow
newsgroups
NIF
@@ -1006,7 +1018,10 @@ NOAGGREGATION
NOASYNC
NOCLOSEPROCESS
NOCOALESCE
+NOCOMM
+NOCONFIRMMKDIR
NOCOPYBITS
+NOCOPYSECURITYATTRIBS
nodeca
nodoc
NODRAWCAPTION
@@ -1014,6 +1029,7 @@ NODRAWICON
NOINHERITLAYOUT
NOINTERFACE
NOLINKINFO
+NOMCX
NOMINMAX
NOMIRRORBITMAP
NOMOVE
@@ -1414,6 +1430,8 @@ SHELLEXECUTEINFO
SHELLEXECUTEINFOW
shellscalingapi
SHFILEINFO
+SHFILEOPSTRUCT
+SHGDN
SHGDNF
SHGFI
shinfo
@@ -1421,6 +1439,7 @@ shldisp
shlobj
shlwapi
shmem
+SHNAMEMAPPING
shobjidl
SHORTCUTATLEAST
shortcutcontrol
@@ -1525,7 +1544,7 @@ stringtable
stringval
Strm
Strmiids
-Strret
+strret
strsafe
strutil
sttngs
@@ -1541,6 +1560,7 @@ svchost
SVGIn
SVGIO
svgz
+SVSI
SWC
SWFO
SWP
@@ -1745,6 +1765,7 @@ VSTHRD
VSTT
vswhere
Vtbl
+WANTMAPPINGHANDLE
WANTPALM
wbem
Wbemidl
@@ -1756,6 +1777,7 @@ WCE
wcex
WClass
wcsicmp
+wcsncpy
wcsnicmp
WDA
wdp
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index b873c42b90..ed5bd325a7 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -178,6 +178,9 @@
"PowerToys.MouseWithoutBordersHelper.dll",
"PowerToys.MouseWithoutBordersHelper.exe",
+ "WinUI3Apps\\PowerToys.NewPlus.ShellExtension.dll",
+ "WinUI3Apps\\NewPlusPackage.msix",
+
"PowerAccent.Core.dll",
"PowerToys.PowerAccent.dll",
"PowerToys.PowerAccent.exe",
diff --git a/PowerToys.sln b/PowerToys.sln
index d6c33873e2..6f214eb5c0 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -582,6 +582,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Sche
{020A7474-3601-4160-A159-D7B70B77B15F} = {020A7474-3601-4160-A159-D7B70B77B15F}
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension", "src\modules\NewPlus\NewShellExtensionContextMenu\NewShellExtensionContextMenu.vcxproj", "{8ACB33D9-C95B-47D4-8363-9731EE0930A0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "New+", "New+", "{CA716AE6-FE5C-40AC-BB8F-2C87912687AC}"
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "src\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workspaces", "Workspaces", "{A2221D7E-55E7-4BEA-90D1-4F162D670BBF}"
@@ -2621,6 +2625,16 @@ Global
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x64.Build.0 = Release|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x86.ActiveCfg = Release|x64
{1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x86.Build.0 = Release|x64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|ARM64.Build.0 = Debug|ARM64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|x64.ActiveCfg = Debug|x64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|x64.Build.0 = Debug|x64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|x86.ActiveCfg = Debug|x64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|ARM64.ActiveCfg = Release|ARM64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|ARM64.Build.0 = Release|ARM64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|x64.ActiveCfg = Release|x64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|x64.Build.0 = Release|x64
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|x86.ActiveCfg = Release|x64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.Build.0 = Debug|ARM64
{F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.ActiveCfg = Debug|x64
@@ -2920,6 +2934,8 @@ Global
{3A9A791E-94A9-49F8-8401-C11CE288D5FB} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
+ {8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
+ {CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
{A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
diff --git a/installer/PowerToysSetup/NewPlus.wxs b/installer/PowerToysSetup/NewPlus.wxs
new file mode 100644
index 0000000000..80fd5a94f4
--- /dev/null
+++ b/installer/PowerToysSetup/NewPlus.wxs
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj
index 6e83f2ef37..6d9ca4a6c5 100644
--- a/installer/PowerToysSetup/PowerToysInstaller.wixproj
+++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj
@@ -1,9 +1,7 @@
-
+
-
Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC
Release
@@ -102,8 +99,8 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
+
-
@@ -124,9 +121,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
-
-
@@ -184,9 +179,8 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
-->
-
+
-
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index 22940acbb5..3a30002a37 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -73,6 +73,8 @@
+
+
diff --git a/installer/PowerToysSetup/generateAllFileComponents.ps1 b/installer/PowerToysSetup/generateAllFileComponents.ps1
index 6c2f91436b..3b8bd59d3b 100644
--- a/installer/PowerToysSetup/generateAllFileComponents.ps1
+++ b/installer/PowerToysSetup/generateAllFileComponents.ps1
@@ -58,6 +58,10 @@ Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListNa
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName ImageResizerAssetsFiles -wxsFilePath $PSScriptRoot\ImageResizer.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\Assets\ImageResizer"""
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""ImageResizerAssetsFiles"" -wxsFilePath $PSScriptRoot\ImageResizer.wxs -regroot $registryroot"
+#New+
+Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName NewPlusAssetsFiles -wxsFilePath $PSScriptRoot\NewPlus.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\NewPlus"""
+Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""NewPlusAssetsFiles"" -wxsFilePath $PSScriptRoot\NewPlus.wxs -regroot $registryroot"
+
#Peek
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName PeekAssetsFiles -wxsFilePath $PSScriptRoot\Peek.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Peek\"""
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""PeekAssetsFiles"" -wxsFilePath $PSScriptRoot\Peek.wxs -regroot $registryroot"
diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp
index 6e931e4749..216a05aaab 100644
--- a/installer/PowerToysSetupCustomActions/CustomAction.cpp
+++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp
@@ -1148,7 +1148,7 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
try
{
// Packages to unregister
- const std::vector packagesToRemoveDisplayName{ { L"PowerRenameContextMenu" }, { L"ImageResizerContextMenu" }, { L"FileLocksmithContextMenu" } };
+ const std::vector packagesToRemoveDisplayName{ { L"PowerRenameContextMenu" }, { L"ImageResizerContextMenu" }, { L"FileLocksmithContextMenu" }, { L"NewPlusContextMenu" } };
PackageManager packageManager;
diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj
index b8efdc3385..b58e19460d 100644
--- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj
+++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj
@@ -60,6 +60,7 @@
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ImageResizer.wxs"" ""$(ProjectDir)..\PowerToysSetup\ImageResizer.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\KeyboardManager.wxs"" ""$(ProjectDir)..\PowerToysSetup\KeyboardManager.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\MouseWithoutBorders.wxs"" ""$(ProjectDir)..\PowerToysSetup\MouseWithoutBorders.wxs.bk""""
+ call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\NewPlus.wxs"" ""$(ProjectDir)..\PowerToysSetup\NewPlus.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Peek.wxs"" ""$(ProjectDir)..\PowerToysSetup\Peek.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\PowerRename.wxs"" ""$(ProjectDir)..\PowerToysSetup\PowerRename.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Product.wxs"" ""$(ProjectDir)..\PowerToysSetup\Product.wxs.bk""""
diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp
index 08c896d0f3..c3dd26cf8f 100644
--- a/src/common/GPOWrapper/GPOWrapper.cpp
+++ b/src/common/GPOWrapper/GPOWrapper.cpp
@@ -176,6 +176,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
}
+ GpoRuleConfigured GPOWrapper::GetConfiguredNewPlusEnabledValue()
+ {
+ return static_cast(powertoys_gpo::getConfiguredNewPlusEnabledValue());
+ }
GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue()
{
return static_cast(powertoys_gpo::getConfiguredWorkspacesEnabledValue());
diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h
index b0392cea1e..e4b12853d8 100644
--- a/src/common/GPOWrapper/GPOWrapper.h
+++ b/src/common/GPOWrapper/GPOWrapper.h
@@ -50,6 +50,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
+ static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl
index 81ff61121a..7d00378389 100644
--- a/src/common/GPOWrapper/GPOWrapper.idl
+++ b/src/common/GPOWrapper/GPOWrapper.idl
@@ -54,6 +54,7 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
+ static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue();
diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs
index 6f15bc3a15..7a1913c05c 100644
--- a/src/common/ManagedCommon/ModuleType.cs
+++ b/src/common/ManagedCommon/ModuleType.cs
@@ -22,6 +22,7 @@ namespace ManagedCommon
MouseJump,
MousePointerCrosshairs,
MouseWithoutBorders,
+ NewPlus,
Peek,
PowerRename,
PowerLauncher,
diff --git a/src/common/Themes/theme_helpers.cpp b/src/common/Themes/theme_helpers.cpp
index 4357a38228..6d18f228ba 100644
--- a/src/common/Themes/theme_helpers.cpp
+++ b/src/common/Themes/theme_helpers.cpp
@@ -3,13 +3,23 @@
#include "dwmapi.h"
#include
#include
-#pragma comment (lib,"Dwmapi.lib")
+#pragma comment(lib, "Dwmapi.lib")
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#define HKEY_WINDOWS_THEME L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"
// based on https://stackoverflow.com/questions/51334674/how-to-detect-windows-10-light-dark-mode-in-win32-application
-AppTheme ThemeHelpers::GetAppTheme()
+Theme ThemeHelpers::GetAppTheme()
+{
+ return ThemeRegistryHelper(L"AppsUseLightTheme");
+}
+
+Theme ThemeHelpers::GetSystemTheme()
+{
+ return ThemeRegistryHelper(L"SystemUsesLightTheme");
+}
+
+Theme ThemeHelpers::ThemeRegistryHelper(LPCWSTR theme_key)
{
// The value is expected to be a REG_DWORD, which is a signed 32-bit little-endian
auto buffer = std::vector(4);
@@ -17,21 +27,22 @@ AppTheme ThemeHelpers::GetAppTheme()
auto res = RegGetValueW(
HKEY_CURRENT_USER,
HKEY_WINDOWS_THEME,
- L"AppsUseLightTheme",
- RRF_RT_REG_DWORD, // expected value type
+ theme_key,
+ RRF_RT_REG_DWORD,
nullptr,
buffer.data(),
&cbData);
if (res != ERROR_SUCCESS)
{
- return AppTheme::Light;
+ // Defaulting to Light
+ return Theme::Light;
}
// convert bytes written to our buffer to an int, assuming little-endian
auto i = static_cast(buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]);
- return AppTheme(i);
+ return Theme(i);
}
void ThemeHelpers::SetImmersiveDarkMode(HWND window, bool enabled)
diff --git a/src/common/Themes/theme_helpers.h b/src/common/Themes/theme_helpers.h
index 026a971988..fd7c968ea1 100644
--- a/src/common/Themes/theme_helpers.h
+++ b/src/common/Themes/theme_helpers.h
@@ -1,7 +1,7 @@
#pragma once
#include
-enum class AppTheme
+enum class Theme
{
Dark = 0,
Light = 1
@@ -9,6 +9,10 @@ enum class AppTheme
struct ThemeHelpers
{
- static AppTheme GetAppTheme();
- static void ThemeHelpers::SetImmersiveDarkMode(HWND window, bool enabled);
+ static Theme GetAppTheme();
+ static Theme GetSystemTheme();
+ static void SetImmersiveDarkMode(HWND window, bool enabled);
+
+protected:
+ static Theme ThemeRegistryHelper(LPCWSTR theme_key);
};
\ No newline at end of file
diff --git a/src/common/Themes/theme_listener.h b/src/common/Themes/theme_listener.h
index d9c01925cf..a6ab4464fc 100644
--- a/src/common/Themes/theme_listener.h
+++ b/src/common/Themes/theme_listener.h
@@ -22,7 +22,7 @@ public:
dwThreadId = 0;
}
- AppTheme AppTheme;
+ Theme AppTheme;
void ThemeListener::AddChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelChangedHandler(THEME_HANDLE handle);
void CheckTheme();
diff --git a/src/common/Themes/windows_colors.cpp b/src/common/Themes/windows_colors.cpp
index e5a8eec732..365f875540 100644
--- a/src/common/Themes/windows_colors.cpp
+++ b/src/common/Themes/windows_colors.cpp
@@ -66,7 +66,7 @@ WindowsColors::Color WindowsColors::get_background_color()
bool WindowsColors::is_dark_mode()
{
- return ThemeHelpers::GetAppTheme() == AppTheme::Dark;
+ return ThemeHelpers::GetAppTheme() == Theme::Dark;
}
bool WindowsColors::update()
diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h
index 345286a38b..814547ef0b 100644
--- a/src/common/logger/logger_settings.h
+++ b/src/common/logger/logger_settings.h
@@ -69,6 +69,7 @@ struct LogSettings
inline const static std::string environmentVariablesLoggerName = "environment-variables";
inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt";
inline const static std::string cmdNotFoundLoggerName = "cmd-not-found";
+ inline const static std::string newLoggerName = "NewPlus";
inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher";
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt";
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h
index 8f71a21205..6fcfab48b6 100644
--- a/src/common/utils/gpo.h
+++ b/src/common/utils/gpo.h
@@ -60,6 +60,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables";
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails";
+ const std::wstring POLICY_CONFIGURE_ENABLED_NEWPLUS = L"ConfigureEnabledUtilityNewPlus";
const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces";
// The registry value names for PowerToys installer and update policies.
@@ -515,6 +516,11 @@ namespace powertoys_gpo {
return getUtilityEnabledValue(POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS);
}
+ inline gpo_rule_configured_t getConfiguredNewPlusEnabledValue()
+ {
+ return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_NEWPLUS);
+ }
+
inline gpo_rule_configured_t getConfiguredMwbClipboardSharingEnabledValue()
{
return getUtilityEnabledValue(POLICY_MWB_CLIPBOARD_SHARING_ENABLED);
diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml
index 0cfc689316..5324df75ea 100644
--- a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml
+++ b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml
@@ -35,6 +35,8 @@ properties:
Enabled: false
MouseWithoutBorders:
Enabled: false
+ NewPlus:
+ Enabled: false
Peek:
Enabled: false
PowerRename:
diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml
index c3bd636177..5fa895ddfd 100644
--- a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml
+++ b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml
@@ -35,6 +35,8 @@ properties:
Enabled: true
MouseWithoutBorders:
Enabled: true
+ NewPlus:
+ Enabled: true
Peek:
Enabled: true
PowerRename:
diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx
index a79e6f627c..9386fa6e31 100644
--- a/src/gpo/assets/PowerToys.admx
+++ b/src/gpo/assets/PowerToys.admx
@@ -1,11 +1,11 @@
-
+
-
+
@@ -21,6 +21,7 @@
+
@@ -335,6 +336,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml
index 3a616559fa..4fcc123114 100644
--- a/src/gpo/assets/en-US/PowerToys.adml
+++ b/src/gpo/assets/en-US/PowerToys.adml
@@ -1,7 +1,7 @@
-
+
PowerToys
PowerToys
@@ -26,6 +26,7 @@
PowerToys version 0.81.1 or later
PowerToys version 0.83.0 or later
PowerToys version 0.84.0 or later
+ PowerToys version 0.85.0 or later
This policy configures the enabled state for all PowerToys utilities.
@@ -217,6 +218,7 @@ If you disable or don't configure this policy, no predefined rules are applied.
Mouse Jump: Configure enabled state
Mouse Pointer Crosshairs: Configure enabled state
Mouse Without Borders: Configure enabled state
+ New+: Configure enabled state
Peek: Configure enabled state
Power Rename: Configure enabled state
PowerToys Run: Configure enabled state
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/AppxManifest.xml b/src/modules/NewPlus/NewShellExtensionContextMenu/AppxManifest.xml
new file mode 100644
index 0000000000..c8a181f8ff
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/AppxManifest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ PowerToys New+
+ Microsoft
+ Assets\NewPlus\StoreLogo.png
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/LargeTile.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/LargeTile.png
new file mode 100644
index 0000000000..f8a9f8f193
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/LargeTile.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/New_dark.ico b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/New_dark.ico
new file mode 100644
index 0000000000..dbe2f51a37
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/New_dark.ico differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/New_light.ico b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/New_light.ico
new file mode 100644
index 0000000000..5e9ae33a48
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/New_light.ico differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Open_templates_dark.ico b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Open_templates_dark.ico
new file mode 100644
index 0000000000..faf7cda080
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Open_templates_dark.ico differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Open_templates_light.ico b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Open_templates_light.ico
new file mode 100644
index 0000000000..ebd453d7f4
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Open_templates_light.ico differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/SmallTile.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/SmallTile.png
new file mode 100644
index 0000000000..8773d6366a
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/SmallTile.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/SplashScreen.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/SplashScreen.png
new file mode 100644
index 0000000000..9d421b9526
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/SplashScreen.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Square150x150Logo.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Square150x150Logo.png
new file mode 100644
index 0000000000..f8a9f8f193
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Square150x150Logo.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Square44x44Logo.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Square44x44Logo.png
new file mode 100644
index 0000000000..8773d6366a
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Square44x44Logo.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/StoreLogo.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/StoreLogo.png
new file mode 100644
index 0000000000..dfbca016bc
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/StoreLogo.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Wide310x150Logo.png b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Wide310x150Logo.png
new file mode 100644
index 0000000000..266b41d25b
Binary files /dev/null and b/src/modules/NewPlus/NewShellExtensionContextMenu/Assets/NewPlus/Wide310x150Logo.png differ
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
new file mode 100644
index 0000000000..9444e849c0
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+ 17.0
+ Win32Proj
+ {8acb33d9-c95b-47d4-8363-9731ee0930a0}
+ NewPlusShellExtension
+ 10.0.20348.0
+ NewPlus.ShellExtension
+
+
+
+ DynamicLibrary
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+ .dll
+ ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\
+ PowerToys.NewPlus.ShellExtension
+ $(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\
+
+
+
+
+ ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\
+ PowerToys.NewPlus.ShellExtension
+ $(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\
+
+
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;NEWPLUSCONTEXTMENU_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ stdcpplatest
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
+
+
+ Windows
+ true
+ false
+ dll.def
+ runtimeobject.lib;$(CoreLibraryDependencies)
+
+
+
+
+ del $(OutDir)\NewPlusPackage.msix /q
+MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv
+
+
+
+
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;NEWPLUSCONTEXTMENU_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ stdcpplatest
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
+
+
+ Windows
+ true
+ true
+ true
+ false
+ dll.def
+ runtimeobject.lib;$(CoreLibraryDependencies)
+
+
+
+
+ del $(OutDir)\NewPlusPackage.msix /q
+MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+ true
+ Document
+ $(OutDir)\Assets\NewPlus\Templates
+
+
+ true
+ Document
+ $(OutDir)\Assets\NewPlus\Templates\Example folder
+
+
+ true
+ Document
+ $(OutDir)\Assets\NewPlus\Templates\Example folder
+
+
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+ {98537082-0fdb-40de-abd8-0dc5a4269bab}
+
+
+ {cc6e41ac-8174-4e8a-8d22-85dd7f4851df}
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+ 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/NewShellExtensionContextMenu.vcxproj.filters b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters
new file mode 100644
index 0000000000..2e8323a6f9
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj.filters
@@ -0,0 +1,203 @@
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Generated Files
+
+
+ Header Files
+
+
+
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Resource Files
+
+
+ Resource Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+ Asset Files
+
+
+
+
+ {82bf7d46-1201-4fc2-99e0-6c1f48f97f1f}
+
+
+ {0c915b9c-0e6a-4d16-8620-507a10fc29c9}
+
+
+ {0c64a1a0-1f9e-4663-8649-454da469d15c}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {4f319851-7d86-4180-b3a3-fd497a320c34}
+
+
+ {a998f674-d126-488b-8457-7673fe986f94}
+
+
+ {b442cb0f-9f62-46e8-b269-fefa8ceacb21}
+
+
+ {e7904759-7b6c-4609-9c6d-e3eae3cb866c}
+
+
+
+
+ Generated Files
+
+
+
+
+ Template Examples\Example folder
+
+
+ Template Examples\Example folder
+
+
+ Template Examples
+
+
+
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Any files or folders placed in the template folder are available via New+.txt b/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Any files or folders placed in the template folder are available via New+.txt
new file mode 100644
index 0000000000..6f4b67067d
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Any files or folders placed in the template folder are available via New+.txt
@@ -0,0 +1 @@
+Learn more about New+ by visiting https://aka.ms/PowerToysOverview_NewPlus
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Example folder/Another example txt file.txt b/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Example folder/Another example txt file.txt
new file mode 100644
index 0000000000..c2e12898d5
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Example folder/Another example txt file.txt
@@ -0,0 +1 @@
+Another example txt file
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Example folder/Example txt file.txt b/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Example folder/Example txt file.txt
new file mode 100644
index 0000000000..e3c944dc54
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/TemplateExamples/Example folder/Example txt file.txt
@@ -0,0 +1 @@
+Example txt file
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/constants.h b/src/modules/NewPlus/NewShellExtensionContextMenu/constants.h
new file mode 100644
index 0000000000..48c7054d36
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/constants.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "pch.h"
+
+namespace newplus::constants::non_localizable
+{
+ constexpr WCHAR powertoy_key[] = L"NewPlus";
+
+ constexpr WCHAR powertoy_name[] = L"NewPlus";
+
+ constexpr WCHAR settings_json_data_file_path[] = L"\\settings.json";
+
+ constexpr WCHAR settings_json_key_hide_file_extension[] = L"HideFileExtension";
+
+ constexpr WCHAR settings_json_key_hide_starting_digits[] = L"HideStartingDigits";
+
+ constexpr WCHAR settings_json_key_template_location[] = L"TemplateLocation";
+
+ constexpr WCHAR context_menu_package_name[] = L"NewPlusContextMenu";
+
+ constexpr WCHAR msix_package_name[] = L"NewPlusPackage.msix";
+
+ constexpr WCHAR module_name[] = L"NewPlus.ShellExtension";
+
+ constexpr WCHAR new_icon_light_resource_relative_path[] = L"\\Assets\\NewPlus\\New_light.ico";
+
+ constexpr WCHAR new_icon_dark_resource_relative_path[] = L"\\Assets\\NewPlus\\New_dark.ico";
+
+ constexpr WCHAR open_templates_icon_light_resource_relative_path[] = L"\\Assets\\NewPlus\\Open_templates_light.ico";
+
+ constexpr WCHAR open_templates_icon_dark_resource_relative_path[] = L"\\Assets\\NewPlus\\Open_templates_dark.ico";
+
+ constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/dll.def b/src/modules/NewPlus/NewShellExtensionContextMenu/dll.def
new file mode 100644
index 0000000000..f306dc6c13
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/dll.def
@@ -0,0 +1,5 @@
+LIBRARY
+EXPORTS
+DllCanUnloadNow PRIVATE
+DllGetClassObject PRIVATE
+DllGetActivationFactory PRIVATE
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/dll_main.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/dll_main.cpp
new file mode 100644
index 0000000000..5ad3537bb2
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/dll_main.cpp
@@ -0,0 +1,41 @@
+#include "pch.h"
+
+#include "shell_context_menu.h"
+#include "dll_main.h"
+#include "trace.h"
+
+HMODULE module_instance_handle = 0;
+
+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 rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv)
+{
+ return Module::GetModule().GetClassObject(rclsid, riid, ppv);
+}
+
+CoCreatableClass(shell_context_menu)
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/dll_main.h b/src/modules/NewPlus/NewShellExtensionContextMenu/dll_main.h
new file mode 100644
index 0000000000..f6c4284a10
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/dll_main.h
@@ -0,0 +1,3 @@
+#pragma once
+
+extern HMODULE module_instance_handle;
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/new.base.rc b/src/modules/NewPlus/NewShellExtensionContextMenu/new.base.rc
new file mode 100644
index 0000000000..529f658260
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/new.base.rc
@@ -0,0 +1,49 @@
+#include
+#include "Generated Files/resource.h"
+#include "../../../common/version/version.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+#include "winres.h"
+#undef APSTUDIO_READONLY_SYMBOLS
+
+1 VERSIONINFO
+ FILEVERSION FILE_VERSION
+ PRODUCTVERSION PRODUCT_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+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.
+IDI_ICON1 ICON "Assets/NewPlus/New_light.ico"
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h
new file mode 100644
index 0000000000..55bafb8b56
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h
@@ -0,0 +1,177 @@
+#pragma once
+
+#include "pch.h"
+
+#include
+#include
+
+#include "constants.h"
+#include "settings.h"
+
+#pragma comment(lib, "Shlwapi.lib")
+
+namespace newplus::utilities
+{
+
+ inline std::wstring get_explorer_icon(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_ICONLOCATION);
+ std::wstring icon_path = shell_file_info.szDisplayName;
+ if (icon_path != L"")
+ {
+ const int icon_index = shell_file_info.iIcon;
+ std::wstring icon_resource = icon_path + std::wstring(L",") + std::to_wstring(icon_index);
+ return icon_resource;
+ }
+
+ 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;
+ return icon_resource;
+ }
+
+ inline bool is_hidden(const std::filesystem::path path)
+ {
+ const std::filesystem::path::string_type name = path.filename();
+ if (name == constants::non_localizable::desktop_ini_filename)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ inline bool wstring_same_when_comparing_ignore_case(std::wstring stringA, std::wstring stringB)
+ {
+ transform(stringA.begin(), stringA.end(), stringA.begin(), towupper);
+ transform(stringB.begin(), stringB.end(), stringB.begin(), towupper);
+
+ return (stringA == stringB);
+ }
+
+ inline void process_pending_window_messages(HWND window_handle = NULL)
+ {
+ if (window_handle == NULL)
+ {
+ window_handle = GetActiveWindow();
+ }
+
+ MSG current_message;
+ while (PeekMessage(¤t_message, window_handle, NULL, NULL, PM_REMOVE))
+ {
+ DispatchMessage(¤t_message);
+ }
+ }
+
+ inline std::wstring get_new_template_folder_location()
+ {
+ return NewSettingsInstance().GetTemplateLocation();
+ }
+
+ inline bool get_newplus_setting_hide_extension()
+ {
+ return NewSettingsInstance().GetHideFileExtension();
+ }
+
+ inline bool get_newplus_setting_hide_starting_digits()
+ {
+ return NewSettingsInstance().GetHideStartingDigits();
+ }
+
+ inline void create_folder_if_not_exist(const std::filesystem::path path)
+ {
+ std::filesystem::create_directory(path);
+ }
+
+ inline std::wstring get_new_icon_resource_filepath(const HMODULE module_instance_handle, const Theme theme)
+ {
+ auto iconResourcePath = get_module_folderpath(module_instance_handle);
+
+ if (theme == Theme::Dark)
+ {
+ iconResourcePath += constants::non_localizable::new_icon_dark_resource_relative_path;
+ }
+ else
+ {
+ // Defaulting to the Light icon
+ iconResourcePath += constants::non_localizable::new_icon_light_resource_relative_path;
+ }
+
+ return iconResourcePath;
+ }
+
+ inline std::wstring get_open_templates_icon_resource_filepath(const HMODULE module_instance_handle, const Theme theme)
+ {
+ auto iconResourcePath = get_module_folderpath(module_instance_handle);
+
+ if (theme == Theme::Dark)
+ {
+ iconResourcePath += constants::non_localizable::open_templates_icon_dark_resource_relative_path;
+ }
+ else
+ {
+ // Defaulting to the Light icon
+ iconResourcePath += constants::non_localizable::open_templates_icon_light_resource_relative_path;
+ }
+
+ return iconResourcePath;
+ }
+
+ inline void init_logger()
+ {
+ LoggerHelpers::init_logger(
+ constants::non_localizable::powertoy_name,
+ constants::non_localizable::module_name,
+ LogSettings::newLoggerName);
+ }
+
+ inline void register_msix_package()
+ {
+ if (package::IsWin11OrGreater())
+ {
+ static const auto new_dll_path = get_module_folderpath(module_instance_handle);
+ auto new_package_uri = new_dll_path + L"\\" + constants::non_localizable::msix_package_name;
+
+ if (!package::IsPackageRegistered(constants::non_localizable::context_menu_package_name))
+ {
+ package::RegisterSparsePackage(new_dll_path, new_package_uri);
+ }
+ }
+ }
+
+ inline std::wstring get_path_from_unknown_site(const ComPtr site_of_folder)
+ {
+ ComPtr service_provider;
+ site_of_folder->QueryInterface(IID_PPV_ARGS(&service_provider));
+ ComPtr folder_view;
+ service_provider->QueryService(__uuidof(IFolderView), IID_PPV_ARGS(&folder_view));
+ ComPtr shell_folder;
+ folder_view->GetFolder(IID_PPV_ARGS(&shell_folder));
+ STRRET strings_returned;
+ shell_folder->GetDisplayNameOf(0, SHGDN_FORPARSING, &strings_returned);
+ LPWSTR path;
+ StrRetToStr(&strings_returned, NULL, &path);
+ return path;
+ }
+
+ inline std::wstring get_path_from_folder_view(const ComPtr folder_view)
+ {
+ ComPtr shell_folder;
+ folder_view->GetFolder(IID_PPV_ARGS(&shell_folder));
+ STRRET strings_returned;
+ shell_folder->GetDisplayNameOf(0, SHGDN_FORPARSING, &strings_returned);
+ LPWSTR path;
+ StrRetToStr(&strings_returned, NULL, &path);
+ return path;
+ }
+
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config b/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config
new file mode 100644
index 0000000000..ff4b059648
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/pch.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/pch.cpp
new file mode 100644
index 0000000000..1a6a81e238
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/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/pch.h b/src/modules/NewPlus/NewShellExtensionContextMenu/pch.h
new file mode 100644
index 0000000000..981209e500
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/pch.h
@@ -0,0 +1,37 @@
+// 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
+
+// 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/powertoys_module.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp
new file mode 100644
index 0000000000..2962f236fc
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp
@@ -0,0 +1,134 @@
+#include "pch.h"
+
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "constants.h"
+#include "settings.h"
+#include "trace.h"
+#include "new_utilities.h"
+#include "Generated Files/resource.h"
+
+// Note: Settings are managed via Settings and UI Settings
+class NewModule : public PowertoyModuleIface
+{
+public:
+ NewModule()
+ {
+ init_settings();
+ }
+
+ virtual const wchar_t* get_name() override
+ {
+ static const std::wstring localized_context_menu_item =
+ GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_NEW, L"New+");
+
+ return localized_context_menu_item.c_str();
+ }
+
+ virtual const wchar_t* get_key() override
+ {
+ // This setting key must match EnabledModules.cs [JsonPropertyName("NewPlus")]
+ return newplus::constants::non_localizable::powertoy_key;
+ }
+
+ virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
+ {
+ return powertoys_gpo::getConfiguredNewPlusEnabledValue();
+ }
+
+ virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
+ {
+ // Not implemented as Settings are propagating via json
+ return true;
+ }
+
+ virtual void set_config(PCWSTR config) override
+ {
+ // The following just checks to see if the Template Location was changed for metrics purposes
+ // Note: We are not saving the settings here and instead relying on read/write of json in Settings App .cs code paths
+ try
+ {
+ json::JsonObject config_as_json = json::JsonValue::Parse(winrt::to_hstring(config)).GetObjectW();
+
+ const auto latest_location_value = config_as_json.GetNamedString(newplus::constants::non_localizable::settings_json_key_template_location).data();
+ const auto existing_location_value = NewSettingsInstance().GetTemplateLocation();
+
+ if (!newplus::utilities::wstring_same_when_comparing_ignore_case(latest_location_value, existing_location_value))
+ {
+ Trace::EventChangedTemplateLocation();
+ }
+ }
+ catch (std::exception& e)
+ {
+ Logger::error("Configuration parsing failed: {}", std::string{ e.what() });
+ }
+ }
+
+ virtual bool is_enabled_by_default() const override
+ {
+ return false;
+ }
+
+ virtual void enable() override
+ {
+ Logger::info("New+ enabled via Settings UI");
+
+ newplus::utilities::register_msix_package();
+
+ powertoy_new_enabled = true;
+ }
+
+ virtual void disable() override
+ {
+ Logger::info("New+ disabled via Settings UI");
+
+ powertoy_new_enabled = false;
+ }
+
+ virtual bool is_enabled() override
+ {
+ return powertoy_new_enabled;
+ }
+
+ virtual void hide_file_extension(bool hide_file_extension)
+ {
+ Logger::info("New+ hide file extension {}", hide_file_extension);
+ }
+
+ virtual void hide_starting_digits(bool hide_starting_digits)
+ {
+ Logger::info("New+ hide starting digits {}", hide_starting_digits);
+ }
+
+ virtual void template_location(std::wstring path_location)
+ {
+ Logger::info("New+ template location");
+ }
+
+ virtual void destroy() override
+ {
+ delete this;
+ }
+
+private:
+ bool powertoy_new_enabled = false;
+
+ void init_settings()
+ {
+ powertoy_new_enabled = NewSettingsInstance().GetEnabled();
+ }
+};
+
+extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
+{
+ return new NewModule();
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/resource.base.h b/src/modules/NewPlus/NewShellExtensionContextMenu/resource.base.h
new file mode 100644
index 0000000000..09a4567ccc
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/resource.base.h
@@ -0,0 +1,13 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by AlwaysOnTop.rc
+
+//////////////////////////////
+// Non-localizable
+
+#define FILE_DESCRIPTION "PowerToys.New+"
+#define INTERNAL_NAME "PowerToys.New+"
+#define ORIGINAL_FILENAME "PowerToys.NewPlus.ShellExtension.dll"
+
+// Non-localizable
+//////////////////////////////
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/resources.resx b/src/modules/NewPlus/NewShellExtensionContextMenu/resources.resx
new file mode 100644
index 0000000000..7db1823f95
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/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/settings.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/settings.cpp
new file mode 100644
index 0000000000..a7ce32998d
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/settings.cpp
@@ -0,0 +1,225 @@
+#include "pch.h"
+
+#include
+#include
+#include
+
+#include "settings.h"
+#include "constants.h"
+#include "Generated Files/resource.h"
+
+// NewSettings are stored in PowerToys/New/settings.json
+// The New PowerToy enabled state is stored in the general PowerToys/settings.json
+
+static bool LastModifiedTime(const std::wstring& file_Path, FILETIME* returned_file_timestamp)
+{
+ WIN32_FILE_ATTRIBUTE_DATA attr{};
+ if (GetFileAttributesExW(file_Path.c_str(), GetFileExInfoStandard, &attr))
+ {
+ *returned_file_timestamp = attr.ftLastWriteTime;
+ return true;
+ }
+ return false;
+}
+
+NewSettings::NewSettings()
+{
+ // New+ overall enable state is stored in the general settings json file
+ general_settings_json_file_path = PTSettingsHelper::get_powertoys_general_save_file_location();
+
+ // New+' actual settings are stored in new_settings_json_file_path
+ std::wstring settings_save_path = PTSettingsHelper::get_module_save_folder_location(newplus::constants::non_localizable::powertoy_key);
+ new_settings_json_file_path = settings_save_path + newplus::constants::non_localizable::settings_json_data_file_path;
+
+ RefreshEnabledState();
+
+ Load();
+}
+
+void NewSettings::Save()
+{
+ json::JsonObject new_settings_json_data;
+
+ new_settings_json_data.SetNamedValue(newplus::constants::non_localizable::settings_json_key_hide_file_extension,
+ json::value(new_settings.hide_file_extension));
+
+ new_settings_json_data.SetNamedValue(newplus::constants::non_localizable::settings_json_key_hide_starting_digits,
+ json::value(new_settings.hide_starting_digits));
+
+ new_settings_json_data.SetNamedValue(newplus::constants::non_localizable::settings_json_key_template_location,
+ json::value(new_settings.template_location));
+
+ json::to_file(new_settings_json_file_path, new_settings_json_data);
+
+ GetSystemTimeAsFileTime(&new_settings_last_loaded_timestamp);
+}
+
+void NewSettings::Load()
+{
+ if (!std::filesystem::exists(new_settings_json_file_path))
+ {
+ InitializeWithDefaultSettings();
+
+ Save();
+ }
+ else
+ {
+ ParseJson();
+ }
+}
+
+void NewSettings::InitializeWithDefaultSettings()
+{
+ // Init the default New settings - in case the New/settings.json doesn't exist
+ // Currently a similar defaulting logic is also in InitializeWithDefaultSettings in NewViewModel.cs
+ SetHideFileExtension(true);
+
+ SetTemplateLocation(GetTemplateLocationDefaultPath());
+}
+
+void NewSettings::RefreshEnabledState()
+{
+ // Load json general settings from data file, if it was modified since we last checked
+ FILETIME last_modified_timestamp{};
+ if (!(LastModifiedTime(general_settings_json_file_path, &last_modified_timestamp) &&
+ CompareFileTime(&last_modified_timestamp, &general_settings_last_loaded_timestamp) == 1))
+ {
+ return;
+ }
+
+ general_settings_last_loaded_timestamp = last_modified_timestamp;
+
+ auto json = json::from_file(general_settings_json_file_path);
+ if (!json)
+ {
+ return;
+ }
+
+ // Load the enabled settings for the New PowerToy via the general settings
+ const json::JsonObject& json_general_settings = json.value();
+ try
+ {
+ json::JsonObject powertoy_new_enabled_state;
+ json::get(json_general_settings, L"enabled", powertoy_new_enabled_state, json::JsonObject{});
+ json::get(powertoy_new_enabled_state, newplus::constants::non_localizable::powertoy_key, new_settings.enabled, false);
+ }
+ catch (const winrt::hresult_error&)
+ {
+ Logger::error(L"New+ unable to load enabled state from json");
+ }
+}
+
+void NewSettings::Reload()
+{
+ // Load json New settings from data file, if it was modified since we last checked.
+ FILETIME very_latest_modified_timestamp{};
+ if (LastModifiedTime(new_settings_json_file_path, &very_latest_modified_timestamp) &&
+ CompareFileTime(&very_latest_modified_timestamp, &new_settings_last_loaded_timestamp) == 1)
+ {
+ Load();
+ }
+}
+
+void NewSettings::ParseJson()
+{
+ auto json = json::from_file(new_settings_json_file_path);
+ if (json)
+ {
+ try
+ {
+ const json::JsonObject& new_settings_json = json.value();
+
+ if (json::has(new_settings_json, newplus::constants::non_localizable::settings_json_key_hide_file_extension, json::JsonValueType::Boolean))
+ {
+ new_settings.hide_file_extension = new_settings_json.GetNamedBoolean(
+ newplus::constants::non_localizable::settings_json_key_hide_file_extension);
+ }
+
+ if (json::has(new_settings_json, newplus::constants::non_localizable::settings_json_key_hide_starting_digits, json::JsonValueType::Boolean))
+ {
+ new_settings.hide_starting_digits = new_settings_json.GetNamedBoolean(
+ newplus::constants::non_localizable::settings_json_key_hide_starting_digits);
+ }
+
+ if (json::has(new_settings_json, newplus::constants::non_localizable::settings_json_key_template_location, json::JsonValueType::String))
+ {
+ new_settings.template_location = new_settings_json.GetNamedString(
+ newplus::constants::non_localizable::settings_json_key_template_location);
+ }
+ }
+ catch (const winrt::hresult_error&)
+ {
+ }
+ }
+ GetSystemTimeAsFileTime(&new_settings_last_loaded_timestamp);
+}
+
+bool NewSettings::GetEnabled()
+{
+ auto gpoSetting = powertoys_gpo::getConfiguredNewPlusEnabledValue();
+ if (gpoSetting == powertoys_gpo::gpo_rule_configured_enabled)
+ {
+ return true;
+ }
+ if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
+ {
+ return false;
+ }
+
+ Reload();
+
+ RefreshEnabledState();
+
+ return new_settings.enabled;
+}
+
+bool NewSettings::GetHideFileExtension() const
+{
+ return new_settings.hide_file_extension;
+}
+
+void NewSettings::SetHideFileExtension(const bool hide_file_extension)
+{
+ new_settings.hide_file_extension = hide_file_extension;
+}
+
+bool NewSettings::GetHideStartingDigits() const
+{
+ return new_settings.hide_starting_digits;
+}
+
+void NewSettings::SetHideStartingDigits(const bool hide_starting_digits)
+{
+ new_settings.hide_starting_digits = hide_starting_digits;
+}
+
+std::wstring NewSettings::GetTemplateLocation() const
+{
+ return new_settings.template_location;
+}
+
+void NewSettings::SetTemplateLocation(const std::wstring template_location)
+{
+ new_settings.template_location = template_location;
+}
+
+std::wstring NewSettings::GetTemplateLocationDefaultPath()
+{
+ static const std::wstring default_template_sub_folder_name =
+ GET_RESOURCE_STRING_FALLBACK(
+ IDS_DEFAULT_TEMPLATE_SUB_FOLDER_NAME_WHERE_TEMPLATES_ARE_STORED,
+ L"Templates");
+
+ static const std::wstring full_path = PTSettingsHelper::get_module_save_folder_location(
+ newplus::constants::non_localizable::powertoy_key) +
+ L"\\" + default_template_sub_folder_name;
+
+ return full_path;
+}
+
+NewSettings& NewSettingsInstance()
+{
+ static NewSettings instance;
+
+ return instance;
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/settings.h b/src/modules/NewPlus/NewShellExtensionContextMenu/settings.h
new file mode 100644
index 0000000000..545ba7b2cc
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/settings.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "pch.h"
+
+class NewSettings
+{
+public:
+ NewSettings();
+
+ bool GetEnabled();
+ bool GetHideFileExtension() const;
+ void SetHideFileExtension(const bool hide_file_extension);
+ bool GetHideStartingDigits() const;
+ void SetHideStartingDigits(const bool hide_starting_digits);
+ std::wstring GetTemplateLocation() const;
+ void SetTemplateLocation(const std::wstring template_location);
+
+ void Save();
+ void Load();
+
+private:
+ struct Settings
+ {
+ // These values are not used
+ bool enabled{ false };
+ bool hide_file_extension{ true };
+ bool hide_starting_digits{ true };
+ std::wstring template_location;
+ };
+
+ void RefreshEnabledState();
+ void InitializeWithDefaultSettings();
+ std::wstring GetTemplateLocationDefaultPath();
+
+ void Reload();
+ void ParseJson();
+
+ Settings new_settings;
+ std::wstring general_settings_json_file_path;
+ std::wstring new_settings_json_file_path;
+ FILETIME general_settings_last_loaded_timestamp{};
+ FILETIME new_settings_last_loaded_timestamp{};
+};
+
+NewSettings& NewSettingsInstance();
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.cpp
new file mode 100644
index 0000000000..b3bf2992d5
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.cpp
@@ -0,0 +1,87 @@
+#include "pch.h"
+
+#include "shell_context_menu.h"
+#include "shell_context_sub_menu.h"
+#include "shell_context_sub_menu_item.h"
+#include "template_folder.h"
+#include "new_utilities.h"
+#include "settings.h"
+#include "trace.h"
+#include "Generated Files/resource.h"
+
+using namespace Microsoft::WRL;
+using namespace newplus;
+
+#pragma region IExplorerCommand
+IFACEMETHODIMP shell_context_menu::GetTitle(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* returned_title)
+{
+ static const std::wstring localized_context_menu_item =
+ GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_NEW, L"New+");
+
+ return SHStrDup(localized_context_menu_item.c_str(), returned_title);
+}
+
+IFACEMETHODIMP shell_context_menu::GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* returned_icon)
+{
+ *returned_icon = nullptr;
+
+ static const auto icon_resource_filepath = utilities::get_new_icon_resource_filepath(module_instance_handle, ThemeHelpers::GetAppTheme());
+
+ return SHStrDup(icon_resource_filepath.c_str(), returned_icon);
+}
+
+IFACEMETHODIMP shell_context_menu::GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* returned_tool_tip)
+{
+ *returned_tool_tip = nullptr;
+ return E_NOTIMPL;
+}
+
+IFACEMETHODIMP shell_context_menu::GetCanonicalName(_Out_ GUID* returned_id)
+{
+ *returned_id = __uuidof(this);
+ return S_OK;
+}
+
+IFACEMETHODIMP shell_context_menu::GetState(_In_opt_ IShellItemArray*, _In_ BOOL, _Out_ EXPCMDSTATE* returned_state)
+{
+ if (!NewSettingsInstance().GetEnabled())
+ {
+ *returned_state = ECS_HIDDEN;
+ }
+ else
+ {
+ *returned_state = ECS_ENABLED;
+ }
+
+ return S_OK;
+}
+
+IFACEMETHODIMP shell_context_menu::Invoke(_In_opt_ IShellItemArray*, _In_opt_ IBindCtx*) noexcept
+{
+ return E_NOTIMPL;
+}
+
+IFACEMETHODIMP shell_context_menu::GetFlags(_Out_ EXPCMDFLAGS* returned_menu_item_flags)
+{
+ *returned_menu_item_flags = ECF_HASSUBCOMMANDS;
+ return S_OK;
+}
+
+IFACEMETHODIMP shell_context_menu::EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** returned_enum_commands)
+{
+ auto e = Make(site_of_folder);
+ return e->QueryInterface(IID_PPV_ARGS(returned_enum_commands));
+}
+#pragma endregion
+
+#pragma region IObjectWithSite
+IFACEMETHODIMP shell_context_menu::SetSite(_In_ IUnknown* site) noexcept
+{
+ this->site_of_folder = site;
+ return S_OK;
+}
+IFACEMETHODIMP shell_context_menu::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/shell_context_menu.h b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.h
new file mode 100644
index 0000000000..564069254d
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_menu.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "pch.h"
+
+using namespace Microsoft::WRL;
+
+#define NEW_SHELL_EXTENSION_EXPLORER_COMMAND_UUID_STR "69824FC6-4660-4A09-9E7C-48DA63C6CC0F"
+
+// File Explorer context menu "New+"
+class __declspec(uuid(NEW_SHELL_EXTENSION_EXPLORER_COMMAND_UUID_STR)) shell_context_menu final :
+ public RuntimeClass<
+ RuntimeClassFlags,
+ IExplorerCommand,
+ IObjectWithSite>
+{
+public:
+#pragma region IExplorerCommand
+ IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* returned_title);
+ IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* returned_icon);
+ IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* returned_tool_tip);
+ IFACEMETHODIMP GetCanonicalName(_Out_ GUID* returned_id);
+ IFACEMETHODIMP GetState(_In_opt_ IShellItemArray*, _In_ BOOL, _Out_ EXPCMDSTATE* returned_state);
+ IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray*, _In_opt_ IBindCtx*) noexcept;
+ IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* returned_menu_item_flags);
+ IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** returned_enum_commands);
+#pragma endregion
+
+#pragma region IObjectWithSite
+ IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept;
+
+ IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept;
+#pragma endregion
+
+protected:
+ HINSTANCE instance_handle = 0;
+ ComPtr site_of_folder;
+};
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp
new file mode 100644
index 0000000000..148dffd952
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.cpp
@@ -0,0 +1,80 @@
+#include "pch.h"
+#include "shell_context_sub_menu.h"
+#include "trace.h"
+
+using namespace Microsoft::WRL;
+
+// // Sub context menu command enumerator
+shell_context_sub_menu::shell_context_sub_menu(const ComPtr site_of_folder)
+{
+ this->site_of_folder = site_of_folder;
+
+ // Determine the New+ Template folder location
+ const std::filesystem::path 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(root);
+
+ // Scan the folder for any files and folders (the templates)
+ templates = new template_folder(root);
+ templates->rescan_template_folder();
+
+ // Add template items to context menu
+ const auto number_of_templates = templates->list_of_templates.size();
+ int index = 0;
+ for (int i = 0; i < number_of_templates; i++)
+ {
+ explorer_menu_item_commands.push_back(Make(templates->get_template_item(i), site_of_folder));
+ }
+
+ // Add separator to context menu
+ explorer_menu_item_commands.push_back(Make());
+
+ // Add "Open templates" item to context menu
+ explorer_menu_item_commands.push_back(Make(root));
+
+ current_command = explorer_menu_item_commands.cbegin();
+
+ // Log that context menu was shown and with how many items
+ Trace::EventShowTemplateItems(number_of_templates);
+}
+
+// IEnumExplorerCommand
+IFACEMETHODIMP shell_context_sub_menu::Next(ULONG celt, __out_ecount_part(celt, *pceltFetched) IExplorerCommand** apUICommand, __out_opt ULONG* pceltFetched)
+{
+ ULONG fetched{ 0 };
+
+ if (pceltFetched)
+ {
+ *pceltFetched = 0ul;
+ }
+
+ for (ULONG i = 0; (i < celt) && (current_command != explorer_menu_item_commands.cend()); i++)
+ {
+ current_command->CopyTo(&apUICommand[0]);
+ current_command++;
+ fetched++;
+ }
+
+ if (pceltFetched)
+ {
+ *pceltFetched = fetched;
+ }
+
+ return (fetched == celt) ? S_OK : S_FALSE;
+}
+
+IFACEMETHODIMP shell_context_sub_menu::Skip(ULONG)
+{
+ return E_NOTIMPL;
+}
+IFACEMETHODIMP shell_context_sub_menu::Reset()
+{
+ current_command = explorer_menu_item_commands.cbegin();
+ return S_OK;
+}
+IFACEMETHODIMP shell_context_sub_menu::Clone(__deref_out IEnumExplorerCommand** ppenum)
+{
+ *ppenum = nullptr;
+ return E_NOTIMPL;
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.h b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.h
new file mode 100644
index 0000000000..2fef78ed63
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "pch.h"
+
+#include "template_folder.h"
+#include "new_utilities.h"
+#include "shell_context_sub_menu_item.h"
+
+using namespace Microsoft::WRL;
+using namespace newplus;
+
+// // Sub context menu command enumerator
+class shell_context_sub_menu final : public RuntimeClass, IEnumExplorerCommand>
+{
+public:
+ shell_context_sub_menu(const ComPtr site_of_folder);
+
+ // IEnumExplorerCommand
+ IFACEMETHODIMP Next(ULONG celt, __out_ecount_part(celt, *pceltFetched) IExplorerCommand** apUICommand, __out_opt ULONG* pceltFetched);
+ IFACEMETHODIMP Skip(ULONG);
+ IFACEMETHODIMP Reset();
+ IFACEMETHODIMP Clone(__deref_out IEnumExplorerCommand** ppenum);
+
+protected:
+ std::vector> explorer_menu_item_commands;
+ std::vector>::const_iterator current_command;
+ template_folder* templates;
+ ComPtr site_of_folder;
+};
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp
new file mode 100644
index 0000000000..bdae885009
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.cpp
@@ -0,0 +1,160 @@
+#include "pch.h"
+#include "shell_context_sub_menu_item.h"
+
+#include "trace.h"
+#include "Generated Files/resource.h"
+
+using namespace Microsoft::WRL;
+
+// Sub context menu containing the actual list of templates
+shell_context_sub_menu_item::shell_context_sub_menu_item()
+{
+ this->template_entry = nullptr;
+}
+
+shell_context_sub_menu_item::shell_context_sub_menu_item(const template_item* template_entry, const ComPtr site_of_folder)
+{
+ this->template_entry = template_entry;
+ this->site_of_folder = site_of_folder;
+}
+
+IFACEMETHODIMP shell_context_sub_menu_item::GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* title)
+{
+ return SHStrDup(this->template_entry->get_menu_title(
+ !utilities::get_newplus_setting_hide_extension(),
+ !utilities::get_newplus_setting_hide_starting_digits()
+ ).c_str(), title);
+}
+
+IFACEMETHODIMP shell_context_sub_menu_item::GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon)
+{
+ return SHStrDup(this->template_entry->get_explorer_icon().c_str(), icon);
+}
+
+IFACEMETHODIMP shell_context_sub_menu_item::GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip)
+{
+ *infoTip = nullptr;
+ return E_NOTIMPL;
+}
+IFACEMETHODIMP shell_context_sub_menu_item::GetCanonicalName(_Out_ GUID* guidCommandName)
+{
+ *guidCommandName = GUID_NULL;
+ return S_OK;
+}
+IFACEMETHODIMP shell_context_sub_menu_item::GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL, _Out_ EXPCMDSTATE* returned_state)
+{
+ // Commented out for performance reasons
+
+ //DWORD object_count = 0;
+ //selection->GetCount(&object_count);
+
+ //if (object_count == 1)
+ //{
+ // *returned_state = ECS_ENABLED;
+ //}
+ //else
+ //{
+ // *returned_state = ECS_HIDDEN;
+ //}
+
+ *returned_state = ECS_ENABLED;
+ return S_OK;
+}
+
+IFACEMETHODIMP shell_context_sub_menu_item::Invoke(_In_opt_ IShellItemArray*, _In_opt_ IBindCtx*) noexcept
+{
+ try
+ {
+ // 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)
+ + L"\\"
+ + 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);
+
+ return S_OK;
+ }
+ catch (const std::exception& ex)
+ {
+ Trace::EventCopyTemplateResult(S_FALSE);
+ Logger::error(ex.what());
+ }
+
+ return S_FALSE;
+}
+
+IFACEMETHODIMP shell_context_sub_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags)
+{
+ *returned_flags = ECF_DEFAULT;
+ return S_OK;
+}
+
+IFACEMETHODIMP shell_context_sub_menu_item::EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
+{
+ *enumCommands = nullptr;
+ return E_NOTIMPL;
+}
+
+// Sub context menu - separator
+IFACEMETHODIMP separator_context_menu_item::GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* title)
+{
+ title = nullptr;
+
+ // NOTE: Must by S_FALSE for the separator to show up
+ return S_FALSE;
+}
+
+IFACEMETHODIMP separator_context_menu_item::GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon)
+{
+ *icon = nullptr;
+ return E_NOTIMPL;
+}
+
+IFACEMETHODIMP separator_context_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags)
+{
+ *returned_flags = ECF_ISSEPARATOR;
+ return S_OK;
+}
+
+// Sub context menu - "Open templates" New+ folder
+template_folder_context_menu_item::template_folder_context_menu_item(const std::filesystem::path shell_template_folder)
+{
+ this->shell_template_folder = shell_template_folder;
+}
+
+IFACEMETHODIMP template_folder_context_menu_item::GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name)
+{
+ static const std::wstring localized_context_menu_item =
+ GET_RESOURCE_STRING_FALLBACK(IDS_CONTEXT_MENU_ITEM_OPEN_TEMPLATES, L"Open templates");
+
+ return SHStrDup(localized_context_menu_item.c_str(), name);
+}
+
+IFACEMETHODIMP template_folder_context_menu_item::GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon)
+{
+ return SHStrDup(utilities::get_open_templates_icon_resource_filepath(module_instance_handle, ThemeHelpers::GetAppTheme()).c_str(), icon);
+}
+
+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;
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.h b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.h
new file mode 100644
index 0000000000..38d24973ed
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/shell_context_sub_menu_item.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "pch.h"
+#include "template_folder.h"
+#include "template_item.h"
+#include "new_utilities.h"
+
+using namespace Microsoft::WRL;
+using namespace newplus;
+
+// The sub-context-menu that displays the list of templates
+class shell_context_sub_menu_item : public RuntimeClass, IExplorerCommand>
+{
+public:
+ shell_context_sub_menu_item(const template_item* template_entry, const ComPtr site_of_folder);
+
+ // IExplorerCommand
+ IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* title);
+
+ IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon);
+
+ IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip);
+
+ IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName);
+
+ IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* returned_state);
+
+ IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept;
+
+ IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* returned_flags);
+
+ IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands);
+
+protected:
+ shell_context_sub_menu_item();
+ const template_item* template_entry;
+ ComPtr site_of_folder;
+};
+
+// Sub-context-menu separator between the list of templates menu-items and "Open templates" menu-item
+class separator_context_menu_item final : public shell_context_sub_menu_item
+{
+public:
+ IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* title);
+
+ IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon);
+
+ IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* returned_flags);
+};
+
+// Sub-context-menu - The "Open templates" menu-item
+class template_folder_context_menu_item final : public shell_context_sub_menu_item
+{
+public:
+ template_folder_context_menu_item(const std::filesystem::path shell_template_folder);
+
+ IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name);
+
+ IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon);
+
+ IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept;
+
+ std::filesystem::path shell_template_folder;
+};
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp
new file mode 100644
index 0000000000..b4bfdf63f5
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.cpp
@@ -0,0 +1,52 @@
+#include "pch.h"
+#include
+#include "template_folder.h"
+
+using namespace newplus;
+
+template_folder::template_folder(){};
+
+template_folder::template_folder(const std::filesystem::path newplus_template_folder)
+{
+ this->template_folder_path = newplus_template_folder;
+}
+
+void template_folder::init()
+{
+ rescan_template_folder();
+}
+
+void template_folder::rescan_template_folder()
+{
+ list_of_templates.clear();
+
+ std::list> dirs;
+ std::list> files;
+ for (const auto& entry : std::filesystem::directory_iterator(template_folder_path))
+ {
+ if (entry.is_directory())
+ {
+ dirs.push_back({ entry.path().wstring(), new template_item(entry) });
+ }
+ else
+ {
+ if (!utilities::is_hidden(entry.path()))
+ {
+ files.push_back({ entry.path().wstring(), new template_item(entry) });
+ }
+ }
+ }
+
+ // List of templates are sorted, with template-directories/folders first then followed by template-files
+ dirs.sort();
+ files.sort();
+ list_of_templates = dirs;
+ list_of_templates.splice(list_of_templates.end(), files);
+}
+
+template_item* template_folder::get_template_item(const int index) const
+{
+ auto it = list_of_templates.begin();
+ std::advance(it, index);
+ return it->second;
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h
new file mode 100644
index 0000000000..fe644be2e9
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_folder.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "pch.h"
+#include
+#include
+#include
+#include
+#include "template_item.h"
+
+namespace newplus
+{
+ class template_folder
+ {
+ public:
+ template_folder(const std::filesystem::path newplus_template_folder);
+ void rescan_template_folder();
+
+ std::filesystem::path template_folder_path;
+ std::list> list_of_templates;
+
+ template_item* get_template_item(const int index) const;
+
+ protected:
+ template_folder();
+ void init();
+ };
+
+}
\ No newline at end of file
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp
new file mode 100644
index 0000000000..3c863be18e
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp
@@ -0,0 +1,147 @@
+
+
+#include "pch.h"
+#include "template_item.h"
+#include
+#include "new_utilities.h"
+#include
+#include
+#include
+#include
+
+using namespace Microsoft::WRL;
+using namespace newplus;
+
+template_item::template_item(const std::filesystem::path entry)
+{
+ path = entry;
+}
+
+std::wstring template_item::get_menu_title(const bool show_extension, const bool show_starting_digits) const
+{
+ std::wstring title = path.filename();
+
+ if (!show_starting_digits)
+ {
+ // Hide starting digits, spaces, and .
+ title = remove_starting_digits_from_filename(title);
+ }
+
+ if (show_extension || !path.has_extension())
+ {
+ return title;
+ }
+
+ std::wstring ext = path.extension();
+ title = title.substr(0, title.length() - ext.length());
+
+ return title;
+}
+
+std::wstring template_item::get_target_filename(const bool include_starting_digits) const
+{
+ std::wstring filename = path.filename();
+
+ if (!include_starting_digits)
+ {
+ // Remove starting digits, spaces, and .
+ filename = remove_starting_digits_from_filename(filename);
+ }
+
+ return filename;
+}
+
+std::wstring template_item::remove_starting_digits_from_filename(std::wstring filename) const
+{
+ filename.erase(0, min(filename.find_first_not_of(L"0123456789 ."), filename.size()));
+
+ return filename;
+}
+
+std::wstring template_item::get_explorer_icon() const
+{
+ return utilities::get_explorer_icon(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,
+ wchar_t double_terminated_path_from[MAX_PATH + 1] = { 0 };
+ wcsncpy_s(double_terminated_path_from, this->path.c_str(), this->path.string().length());
+ double_terminated_path_from[this->path.string().length() + 1] = 0;
+
+ wchar_t double_terminated_path_to[MAX_PATH + 1] = { 0 };
+ wcsncpy_s(double_terminated_path_to, destination.c_str(), destination.string().length());
+ double_terminated_path_to[destination.string().length() + 1] = 0;
+
+ SHFILEOPSTRUCT file_operation_params = { 0 };
+ file_operation_params.wFunc = FO_COPY;
+ file_operation_params.hwnd = window_handle;
+ file_operation_params.pFrom = double_terminated_path_from;
+ file_operation_params.pTo = double_terminated_path_to;
+ file_operation_params.fFlags = FOF_RENAMEONCOLLISION | FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS | FOF_WANTMAPPINGHANDLE;
+
+ const int result = SHFileOperation(&file_operation_params);
+
+ if (!file_operation_params.hNameMappings)
+ {
+ // No file name collision on copy
+ return destination;
+ }
+
+ struct file_operation_collision_mapping
+ {
+ int index;
+ SHNAMEMAPPING* mapping;
+ };
+
+ file_operation_collision_mapping* mapping = static_cast(file_operation_params.hNameMappings);
+ SHNAMEMAPPING* map = &mapping->mapping[0];
+ std::wstring final_path(map->pszNewPath);
+
+ SHFreeNameMappings(file_operation_params.hNameMappings);
+
+ return final_path;
+}
+
+void template_item::enter_rename_mode(const ComPtr site, const std::filesystem::path target_fullpath) const
+{
+ std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, site, target_fullpath);
+ thread_for_renaming_workaround.detach();
+}
+
+void template_item::rename_on_other_thread_workaround(const ComPtr site, 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 };
+ 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;
+ }
+ }
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h
new file mode 100644
index 0000000000..ffb1e8839f
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "pch.h"
+#include
+#include
+#include
+#include
-
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
index f624ff1bba..8d38ec94c3 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
@@ -424,6 +424,7 @@ namespace Microsoft.PowerToys.Settings.UI
case "Peek": return typeof(PeekPage);
case "CropAndLock": return typeof(CropAndLockPage);
case "EnvironmentVariables": return typeof(EnvironmentVariablesPage);
+ case "NewPlus": return typeof(NewPlusPage);
case "Workspaces": return typeof(WorkspacesPage);
default:
// Fallback to Dashboard
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml
new file mode 100644
index 0000000000..5e657073f4
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs
new file mode 100644
index 0000000000..381a2b35ff
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeNewPlus.xaml.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
+using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class OobeNewPlus : Page
+ {
+ public OobePowerToysModule ViewModel { get; set; }
+
+ public OobeNewPlus()
+ {
+ this.InitializeComponent();
+ ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.NewPlus]);
+ DataContext = ViewModel;
+ }
+
+ private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ if (OobeShellPage.OpenMainWindowCallback != null)
+ {
+ OobeShellPage.OpenMainWindowCallback(typeof(NewPlusPage));
+ }
+
+ ViewModel.LogOpeningSettingsEvent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ ViewModel.LogOpeningModuleEvent();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ViewModel.LogClosingModuleEvent();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
index f582ec1566..b765b7d963 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml
@@ -121,6 +121,10 @@
x:Uid="Shell_MouseWithoutBorders"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}"
Tag="MouseWithoutBorders" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml.cs
new file mode 100644
index 0000000000..f33e7b0d4a
--- /dev/null
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using System.Windows;
+using Microsoft.PowerToys.Settings.UI.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.ViewModels;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Views
+{
+ public sealed partial class NewPlusPage : Page, IRefreshablePage
+ {
+ private NewPlusViewModel ViewModel { get; set; }
+
+ public NewPlusPage()
+ {
+ InitializeComponent();
+ var settings_utils = new SettingsUtils();
+ ViewModel = new NewPlusViewModel(settings_utils, SettingsRepository.GetInstance(settings_utils), ShellPage.SendDefaultIPCMessage);
+ DataContext = ViewModel;
+ }
+
+ public void RefreshEnabledState()
+ {
+ ViewModel.RefreshEnabledState();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
index aec022d205..4a3735c7cf 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
@@ -180,6 +180,11 @@
helpers:NavHelper.NavigateTo="views:MouseWithoutBordersPage"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}" />
+
+
Automatically close the AdvancedPaste window after it loses focus
AdvancedPaste is a product name, do not loc
+
+ New+
+ New+ is the name of the utility. Localize product name in accordance with Windows New
+
+
+ Create files and folders from a personalized set of templates
+
+
+ New+
+ New+ is the name of the utility. Localize product name in accordance with Windows New
+
+
+ Create files and folders from a personalized set of templates
+ New+ product description
+
+
+ Learn more about New+
+ New+ learn more link. Localize product name in accordance with Windows New
+
+
+ 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.
+
+
+ Templates
+ Templates label
+
+
+ Location
+ Templates Location label
+
+
+ ...
+ Do not localize
+
+
+ Learn more about template location
+ Read more about templates location
+
+
+ Change
+ Button where user can Change the location of New templates
+
+
+ Display options
+ Display options label
+
+
+ Hide template filename extension
+ Template file name extension settings toggle
+
+
+ Hide template filename starting digits, spaces, and dots
+ Template filename starting digits settings toggle
+
+
+ This option is useful when using digits, spaces and dots at the beginning of filenames to control the display order of templates
+ Template filename starting digits settings toggle
+
+
+ Attribution
+ giving credit
+
+
+ New+
+ New+ is the name of the utility. Localize product name in accordance with Windows New
+
+
+ Create files and folders from a personalized set of templates.
+
+
+ In File Explorer, right-click the desktop or a folder and via the New+ from the context menu select your template. You can add new templates by opening the template folder via "Open templates" and add new files and folders there.
+
+
+ You can have multiple templates of the same file type, and you can even template folders!
+
Workspaces is a quick and easy way to launch a set of applications to custom positions and configurations with one-click.
diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
index ffa4f4a7cf..c72a52c7f7 100644
--- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
@@ -125,6 +125,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private void EnabledChangedOnUI(DashboardListItem dashboardListItem)
{
Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, dashboardListItem.IsEnabled);
+
+ if (dashboardListItem.Tag == ModuleType.NewPlus && dashboardListItem.IsEnabled == true)
+ {
+ var settingsUtils = new SettingsUtils();
+ var settings = NewPlusViewModel.LoadSettings(settingsUtils);
+ NewPlusViewModel.CopyTemplateExamples(settings.TemplateLocation);
+ }
}
public void ModuleEnabledChangedOnSettingsPage()
@@ -178,6 +185,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
ModuleType.MeasureTool => GetModuleItemsMeasureTool(),
ModuleType.ShortcutGuide => GetModuleItemsShortcutGuide(),
ModuleType.PowerOCR => GetModuleItemsPowerOCR(),
+ ModuleType.NewPlus => GetModuleItemsNewPlus(),
_ => new ObservableCollection(), // never called, all values listed above
};
}
@@ -495,6 +503,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return new ObservableCollection(list);
}
+ private ObservableCollection GetModuleItemsNewPlus()
+ {
+ var list = new List
+ {
+ new DashboardModuleTextItem() { Label = resourceLoader.GetString("NewPlus_Product_Description/Description") },
+ };
+ return new ObservableCollection(list);
+ }
+
internal void SWVersionButtonClicked()
{
NavigationService.Navigate(typeof(GeneralPage));
diff --git a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs
new file mode 100644
index 0000000000..43b8801f84
--- /dev/null
+++ b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs
@@ -0,0 +1,256 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using Common.UI;
+using global::PowerToys.GPOWrapper;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.Library.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+using Microsoft.PowerToys.Settings.UI.Library.Utilities;
+using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
+using Windows.ApplicationModel.VoiceCommands;
+using Windows.System;
+using static Microsoft.PowerToys.Settings.UI.Helpers.ShellGetFolder;
+
+namespace Microsoft.PowerToys.Settings.UI.ViewModels
+{
+ public class NewPlusViewModel : Observable
+ {
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
+ private readonly ISettingsUtils _settingsUtils;
+
+ private NewPlusSettings Settings { get; set; }
+
+ private const string ModuleName = NewPlusSettings.ModuleName;
+
+ public NewPlusViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc)
+ {
+ _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+
+ // To obtain the general settings configurations of PowerToys Settings.
+ ArgumentNullException.ThrowIfNull(settingsRepository);
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
+ Settings = LoadSettings(settingsUtils);
+
+ // Initialize properties
+ _hideFileExtension = Settings.HideFileExtension;
+ _hideStartingDigits = Settings.HideStartingDigits;
+ _templateLocation = Settings.TemplateLocation;
+ InitializeEnabledValue();
+
+ // set the callback functions value to handle outgoing IPC message.
+ SendConfigMSG = ipcMSGCallBackFunc;
+ }
+
+ private void InitializeEnabledValue()
+ {
+ _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredNewPlusEnabledValue();
+ if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
+ {
+ // Get the enabled state from GPO.
+ _enabledStateIsGPOConfigured = true;
+ _isNewPlusEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
+ }
+ else
+ {
+ _isNewPlusEnabled = GeneralSettingsConfig.Enabled.NewPlus;
+ }
+ }
+
+ public bool IsEnabled
+ {
+ get => _isNewPlusEnabled;
+ set
+ {
+ if (_isNewPlusEnabled != value)
+ {
+ _isNewPlusEnabled = value;
+
+ GeneralSettingsConfig.Enabled.NewPlus = value;
+ OnPropertyChanged(nameof(IsEnabled));
+
+ OutGoingGeneralSettings outgoingMessage = new OutGoingGeneralSettings(GeneralSettingsConfig);
+ SendConfigMSG(outgoingMessage.ToString());
+
+ NotifySettingsChanged();
+
+ if (_isNewPlusEnabled == true)
+ {
+ CopyTemplateExamples(_templateLocation);
+ }
+ }
+ }
+ }
+
+ public bool IsWin10OrLower
+ {
+ get => !OSVersionHelper.IsWindows11();
+ }
+
+ public string TemplateLocation
+ {
+ get => _templateLocation;
+ set
+ {
+ if (_templateLocation != value)
+ {
+ _templateLocation = value;
+ Settings.TemplateLocation = value;
+ OnPropertyChanged(nameof(TemplateLocation));
+
+ NotifySettingsChanged();
+
+ SaveSettingsToJson();
+ }
+ }
+ }
+
+ public bool HideFileExtension
+ {
+ get => _hideFileExtension;
+ set
+ {
+ if (_hideFileExtension != value)
+ {
+ _hideFileExtension = value;
+ Settings.HideFileExtension = value;
+ OnPropertyChanged(nameof(HideFileExtension));
+
+ NotifySettingsChanged();
+
+ SaveSettingsToJson();
+ }
+ }
+ }
+
+ public bool HideStartingDigits
+ {
+ get => _hideStartingDigits;
+ set
+ {
+ if (_hideStartingDigits != value)
+ {
+ _hideStartingDigits = value;
+ Settings.HideStartingDigits = value;
+ OnPropertyChanged(nameof(HideStartingDigits));
+
+ NotifySettingsChanged();
+
+ SaveSettingsToJson();
+ }
+ }
+ }
+
+ public bool IsEnabledGpoConfigured
+ {
+ get => _enabledStateIsGPOConfigured;
+ }
+
+ public ButtonClickCommand OpenCurrentNewTemplateFolder => new ButtonClickCommand(OpenNewTemplateFolder);
+
+ public ButtonClickCommand PickAnotherNewTemplateFolder => new ButtonClickCommand(PickNewTemplateFolder);
+
+ private void NotifySettingsChanged()
+ {
+ // Using InvariantCulture as this is an IPC message
+ SendConfigMSG(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
+ ModuleName,
+ JsonSerializer.Serialize(Settings)));
+ }
+
+ private Func SendConfigMSG { get; }
+
+ public static NewPlusSettings LoadSettings(ISettingsUtils settingsUtils)
+ {
+ NewPlusSettings settings = null;
+
+ try
+ {
+ settings = settingsUtils.GetSettings(NewPlusSettings.ModuleName);
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Exception encountered while reading {NewPlusSettings.ModuleName} settings.", e);
+ }
+
+ return settings;
+ }
+
+ public static void CopyTemplateExamples(string templateLocation)
+ {
+ if (!Directory.Exists(templateLocation))
+ {
+ Directory.CreateDirectory(templateLocation);
+ }
+
+ if (Directory.GetFiles(templateLocation).Length == 0 && Directory.GetDirectories(templateLocation).Length == 0)
+ {
+ // No files in templateLocation directory
+ // Copy over examples files from \PowerToys\WinUI3Apps\Assets\NewPlus\Templates
+ var example_templates = Path.Combine(Helper.GetPowerToysInstallationWinUI3AppsAssetsFolder(), "NewPlus", "Templates");
+ Helper.CopyDirectory(example_templates, templateLocation, true);
+ }
+ }
+
+ private GpoRuleConfigured _enabledGpoRuleConfiguration;
+ private bool _enabledStateIsGPOConfigured;
+ private bool _isNewPlusEnabled;
+ private string _templateLocation;
+ private bool _hideFileExtension;
+ private bool _hideStartingDigits;
+
+ public void RefreshEnabledState()
+ {
+ InitializeEnabledValue();
+ OnPropertyChanged(nameof(IsEnabled));
+ }
+
+ private void OpenNewTemplateFolder()
+ {
+ var process = new ProcessStartInfo()
+ {
+ FileName = _templateLocation,
+ UseShellExecute = true,
+ };
+ Process.Start(process);
+ }
+
+ private async void PickNewTemplateFolder()
+ {
+ var newPath = await PickFolderDialog();
+ if (newPath.Length > 1)
+ {
+ TemplateLocation = newPath;
+ }
+ }
+
+ private async Task PickFolderDialog()
+ {
+ var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.GetSettingsWindow());
+ string pathFolder = await Task.FromResult(ShellGetFolder.GetFolderDialogWithFlags(hwnd, ShellGetFolder.FolderDialogFlags._BIF_NEWDIALOGSTYLE));
+ return pathFolder;
+ }
+
+ private void SaveSettingsToJson()
+ {
+ _settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
+ }
+ }
+}
diff --git a/tools/BugReportTool/BugReportTool/Main.cpp b/tools/BugReportTool/BugReportTool/Main.cpp
index 515bc17024..18a5b68889 100644
--- a/tools/BugReportTool/BugReportTool/Main.cpp
+++ b/tools/BugReportTool/BugReportTool/Main.cpp
@@ -54,7 +54,8 @@ vector filesToDelete = {
L"PowerRename\\replace-mru.json",
L"PowerRename\\search-mru.json",
L"PowerToys Run\\Settings\\UserSelectedRecord.json",
- L"PowerToys Run\\Settings\\QueryHistory.json"
+ L"PowerToys Run\\Settings\\QueryHistory.json",
+ L"NewPlus\\Templates",
};
vector GetXpathArray(wstring xpath)