New Utility: New+ (#33136)

Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Stefan Markovic <stefan@janeasystems.com>
This commit is contained in:
Christian Gaarden Gaardmark 2024-09-19 09:12:24 -07:00 committed by GitHub
parent d7a07dc7c8
commit 3f44ad186d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
95 changed files with 3116 additions and 59 deletions

View File

@ -64,6 +64,7 @@ Essey
ethanfangg
ferraridavide
frankychen
gaardmark
gabime
Galaxi
Garside

View File

@ -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

View File

@ -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",

View File

@ -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}

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define NewPlusAssetsFiles=?>
<?define NewPlusAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\NewPlus\?>
<?define NewPlusTemplateFilesPath=$(var.BinDir)WinUI3Apps\Assets\NewPlus\Templates\?>
<?define NewPlusTemplateSubFilesPath=$(var.BinDir)WinUI3Apps\Assets\NewPlus\Templates\Example folder\?>
<Fragment>
<!-- Assets -->
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="NewPlusAssetsInstallFolder" Name="NewPlus" />
</DirectoryRef>
<DirectoryRef Id="NewPlusAssetsInstallFolder" FileSource="$(var.NewPlusAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--NewPlusAssetsFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="NewPlusComponentGroup">
<Component Id="RemoveNewPlusFolder" Guid="4189C789-56EB-409D-912E-3F4F3F4F1FFA" Directory="NewPlusAssetsInstallFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveNewPlusFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderNewPlusAssetsFolder" Directory="NewPlusAssetsInstallFolder" On="uninstall"/>
</Component>
</ComponentGroup>
<!-- Example templates -->
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="NewPlusInstallFolder" Name="NewPlus">
<Directory Id="NewPlusTemplatesInstallFolder" Name="Templates">
<Directory Id="NewPlusTemplatesSubInstallFolder" Name="Example folder"/>
</Directory>
</Directory>
</DirectoryRef>
<DirectoryRef Id="NewPlusTemplatesInstallFolder" FileSource="$(var.NewPlusTemplateFilesPath)">
<Component Id="NewPlusTemplateFiles_Component" Win64="yes" Guid="39264075-4B7F-40E3-A76F-21E68576D43E">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="NewPlusTemplateFiles_Component" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="NewPlusTemplateFiles_File_1.md" Source="$(var.NewPlusTemplateFilesPath)Any files or folders placed in the template folder are available via New+.txt" />
</Component>
</DirectoryRef>
<DirectoryRef Id="NewPlusTemplatesSubInstallFolder" FileSource="$(var.NewPlusTemplateSubFilesPath)">
<Component Id="NewPlusTemplateSubFiles_Component" Win64="yes" Guid="7618E61C-CCB8-492F-B284-E1AE2954AF0B">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="NewPlusTemplateSubFiles_Component" Value="" KeyPath="yes"/>
</RegistryKey>
<File Id="NewPlusTemplateSubFiles_File_1.md" Source="$(var.NewPlusTemplateSubFilesPath)Example txt file.txt" />
<File Id="NewPlusTemplateSubFiles_File_2.md" Source="$(var.NewPlusTemplateSubFilesPath)Another example txt file.txt" />
</Component>
</DirectoryRef>
<ComponentGroup Id="NewPlusTemplatesComponentGroup">
<Component Id="RemoveNewPlusTemplateFolder" Guid="3E9B15CA-A50C-42DA-977F-5E9914562FE7" Directory="NewPlusInstallFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveNewPlusTemplateFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderNewPlusInstallFolder" Directory="NewPlusInstallFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderNewPlusTemplatesInstallFolder" Directory="NewPlusTemplatesInstallFolder" On="uninstall"/>
<RemoveFolder Id="RemoveFolderNewPlusTemplatesSubInstallFolder" Directory="NewPlusTemplatesSubInstallFolder" On="uninstall"/>
</Component>
<ComponentRef Id="NewPlusTemplateFiles_Component" />
<ComponentRef Id="NewPlusTemplateSubFiles_Component" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureNuGetPackageBuildImports"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureNuGetPackageBuildImports" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\src\Version.props" Condition="Exists('..\..\src\Version.props')" />
<Import Project="..\wix.props" Condition="Exists('..\wix.props')" />
<PropertyGroup Condition="'$(Platform)' == 'x64'">
<DefineConstants>Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC</DefineConstants>
<!-- THIS IS AN INNER LOOP OPTIMIZATION
@ -18,7 +16,6 @@ call "..\..\..\publish.cmd" x64
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs"
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)' != 'x64'">
<DefineConstants>Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC</DefineConstants>
<PreBuildEvent>IF NOT DEFINED IsPipeline (
@ -32,31 +29,32 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
<PostBuildEvent>
call move /Y ..\..\..\AdvancedPaste.wxs.bk ..\..\..\AdvancedPaste.wxs
call move /Y ..\..\..\Awake.wxs.bk ..\..\..\Awake.wxs
call move /Y ..\..\..\BaseApplications.wxs.bk ..\..\..\BaseApplications.wxs
call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs
call move /Y ..\..\..\Core.wxs.bk ..\..\..\Core.wxs
call move /Y ..\..\..\EnvironmentVariables.wxs.bk ..\..\..\EnvironmentVariables.wxs
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs
call move /Y ..\..\..\Hosts.wxs.bk ..\..\..\Hosts.wxs
call move /Y ..\..\..\ImageResizer.wxs.bk ..\..\..\ImageResizer.wxs
call move /Y ..\..\..\KeyboardManager.wxs.bk ..\..\..\KeyboardManager.wxs
call move /Y ..\..\..\MouseWithoutBorders.wxs.bk ..\..\..\MouseWithoutBorders.wxs
call move /Y ..\..\..\Peek.wxs.bk ..\..\..\Peek.wxs
call move /Y ..\..\..\PowerRename.wxs.bk ..\..\..\PowerRename.wxs
call move /Y ..\..\..\Product.wxs.bk ..\..\..\Product.wxs
call move /Y ..\..\..\RegistryPreview.wxs.bk ..\..\..\RegistryPreview.wxs
call move /Y ..\..\..\Resources.wxs.bk ..\..\..\Resources.wxs
call move /Y ..\..\..\Run.wxs.bk ..\..\..\Run.wxs
call move /Y ..\..\..\Settings.wxs.bk ..\..\..\Settings.wxs
call move /Y ..\..\..\ShortcutGuide.wxs.bk ..\..\..\ShortcutGuide.wxs
call move /Y ..\..\..\Tools.wxs.bk ..\..\..\Tools.wxs
call move /Y ..\..\..\VideoConference.wxs.bk ..\..\..\VideoConference.wxs
call move /Y ..\..\..\WinAppSDK.wxs.bk ..\..\..\WinAppSDK.wxs
call move /Y ..\..\..\WinUI3Applications.wxs.bk ..\..\..\WinUI3Applications.wxs
</PostBuildEvent>
call move /Y ..\..\..\AdvancedPaste.wxs.bk ..\..\..\AdvancedPaste.wxs
call move /Y ..\..\..\Awake.wxs.bk ..\..\..\Awake.wxs
call move /Y ..\..\..\BaseApplications.wxs.bk ..\..\..\BaseApplications.wxs
call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs
call move /Y ..\..\..\Core.wxs.bk ..\..\..\Core.wxs
call move /Y ..\..\..\EnvironmentVariables.wxs.bk ..\..\..\EnvironmentVariables.wxs
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs
call move /Y ..\..\..\Hosts.wxs.bk ..\..\..\Hosts.wxs
call move /Y ..\..\..\ImageResizer.wxs.bk ..\..\..\ImageResizer.wxs
call move /Y ..\..\..\KeyboardManager.wxs.bk ..\..\..\KeyboardManager.wxs
call move /Y ..\..\..\MouseWithoutBorders.wxs.bk ..\..\..\MouseWithoutBorders.wxs
call move /Y ..\..\..\NewPlus.wxs.bk ..\..\..\NewPlus.wxs
call move /Y ..\..\..\Peek.wxs.bk ..\..\..\Peek.wxs
call move /Y ..\..\..\PowerRename.wxs.bk ..\..\..\PowerRename.wxs
call move /Y ..\..\..\Product.wxs.bk ..\..\..\Product.wxs
call move /Y ..\..\..\RegistryPreview.wxs.bk ..\..\..\RegistryPreview.wxs
call move /Y ..\..\..\Resources.wxs.bk ..\..\..\Resources.wxs
call move /Y ..\..\..\Run.wxs.bk ..\..\..\Run.wxs
call move /Y ..\..\..\Settings.wxs.bk ..\..\..\Settings.wxs
call move /Y ..\..\..\ShortcutGuide.wxs.bk ..\..\..\ShortcutGuide.wxs
call move /Y ..\..\..\Tools.wxs.bk ..\..\..\Tools.wxs
call move /Y ..\..\..\VideoConference.wxs.bk ..\..\..\VideoConference.wxs
call move /Y ..\..\..\WinAppSDK.wxs.bk ..\..\..\WinAppSDK.wxs
call move /Y ..\..\..\WinUI3Applications.wxs.bk ..\..\..\WinUI3Applications.wxs
</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<Name>PowerToysInstaller</Name>
@ -67,7 +65,6 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<PropertyGroup Label="UserMacros" Condition=" '$(PerUser)' != 'true' ">
<DefineConstants>$(DefineConstants);PerUser=false</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<!-- We do not support debug installer builds -->
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
@ -102,8 +99,8 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Compile Include="CustomDialogs\PTInstallDirDlg.wxs" />
<Compile Include="CustomDialogs\PTLicenseDlg.wxs" />
<Compile Include="CustomDialogs\WixUI_PTInstallDir.wxs" />
<Compile Include="NewPlus.wxs" />
<Compile Include="Product.wxs" />
<Compile Include="AdvancedPaste.wxs" />
<Compile Include="Awake.wxs" />
<Compile Include="BaseApplications.wxs" />
@ -124,9 +121,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Compile Include="VideoConference.wxs" />
<Compile Include="MouseWithoutBorders.wxs" />
<Compile Include="WinUI3Applications.wxs" />
<Compile Include="MonacoSRC.wxs" />
<Compile Include="Core.wxs" />
<Compile Include="Resources.wxs" />
<Compile Include="WinAppSDK.wxs" />
@ -184,9 +179,8 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Target Name="AfterBuild">
</Target> -->
<Target Name="BeforeBuild">
<HeatDirectory Directory="..\..\src\common\FilePreviewCommon\Assets\Monaco\monacoSRC" PreprocessorVariable="var.MonacoSRCHarvestPath" OutputFile="MonacoSRC.wxs" ComponentGroupName="MonacoSRCHeatGenerated" DirectoryRefId="MonacoPreviewHandlerMonacoSRCFolder" AutogenerateGuids="false" GenerateGuidsNow="true" ToolPath="$(WixToolPath)" RunAsSeparateProcess="true" SuppressFragments="false" SuppressRegistry="false" SuppressRootDirectory="true"/>
<HeatDirectory Directory="..\..\src\common\FilePreviewCommon\Assets\Monaco\monacoSRC" PreprocessorVariable="var.MonacoSRCHarvestPath" OutputFile="MonacoSRC.wxs" ComponentGroupName="MonacoSRCHeatGenerated" DirectoryRefId="MonacoPreviewHandlerMonacoSRCFolder" AutogenerateGuids="false" GenerateGuidsNow="true" ToolPath="$(WixToolPath)" RunAsSeparateProcess="true" SuppressFragments="false" SuppressRegistry="false" SuppressRootDirectory="true" />
</Target>
<!-- Prevents NU1503 -->
<Target Name="_IsProjectRestoreSupported" Returns="@(_ValidProjectsForRestore)">
<ItemGroup>

View File

@ -73,6 +73,8 @@
<ComponentGroupRef Id="MouseWithoutBordersComponentGroup" />
<ComponentGroupRef Id="EnvironmentVariablesComponentGroup" />
<ComponentGroupRef Id="AdvancedPasteComponentGroup" />
<ComponentGroupRef Id="NewPlusComponentGroup" />
<ComponentGroupRef Id="NewPlusTemplatesComponentGroup" />
<ComponentGroupRef Id="ResourcesComponentGroup" />
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
<ComponentGroupRef Id="ToolComponentGroup" />

View File

@ -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"

View File

@ -1148,7 +1148,7 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
try
{
// Packages to unregister
const std::vector<std::wstring> packagesToRemoveDisplayName{ { L"PowerRenameContextMenu" }, { L"ImageResizerContextMenu" }, { L"FileLocksmithContextMenu" } };
const std::vector<std::wstring> packagesToRemoveDisplayName{ { L"PowerRenameContextMenu" }, { L"ImageResizerContextMenu" }, { L"FileLocksmithContextMenu" }, { L"NewPlusContextMenu" } };
PackageManager packageManager;

View File

@ -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""""

View File

@ -176,6 +176,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredNewPlusEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredNewPlusEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredWorkspacesEnabledValue());

View File

@ -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();

View File

@ -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();

View File

@ -22,6 +22,7 @@ namespace ManagedCommon
MouseJump,
MousePointerCrosshairs,
MouseWithoutBorders,
NewPlus,
Peek,
PowerRename,
PowerLauncher,

View File

@ -3,13 +3,23 @@
#include "dwmapi.h"
#include <windows.h>
#include <vector>
#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<char>(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<int>(buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]);
return AppTheme(i);
return Theme(i);
}
void ThemeHelpers::SetImmersiveDarkMode(HWND window, bool enabled)

View File

@ -1,7 +1,7 @@
#pragma once
#include <windows.h>
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);
};

View File

@ -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();

View File

@ -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()

View File

@ -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";

View File

@ -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);

View File

@ -35,6 +35,8 @@ properties:
Enabled: false
MouseWithoutBorders:
Enabled: false
NewPlus:
Enabled: false
Peek:
Enabled: false
PowerRename:

View File

@ -35,6 +35,8 @@ properties:
Enabled: true
MouseWithoutBorders:
Enabled: true
NewPlus:
Enabled: true
Peek:
Enabled: true
PowerRename:

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.12" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.13" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyNamespaces>
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
</policyNamespaces>
<resources minRequiredRevision="1.12"/><!-- Last changed with PowerToys v0.84.0 -->
<resources minRequiredRevision="1.13"/><!-- Last changed with PowerToys v0.85.0 -->
<supportedOn>
<definitions>
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
@ -21,6 +21,7 @@
<definition name="SUPPORTED_POWERTOYS_0_81_1" displayName="$(string.SUPPORTED_POWERTOYS_0_81_1)"/>
<definition name="SUPPORTED_POWERTOYS_0_83_0" displayName="$(string.SUPPORTED_POWERTOYS_0_83_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_84_0" displayName="$(string.SUPPORTED_POWERTOYS_0_84_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_85_0" displayName="$(string.SUPPORTED_POWERTOYS_0_85_0)"/>
</definitions>
</supportedOn>
<categories>
@ -335,6 +336,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityNewPlus" class="Both" displayName="$(string.ConfigureEnabledUtilityNewPlus)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityNewPlus">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_85_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityPeek" class="Both" displayName="$(string.ConfigureEnabledUtilityPeek)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityPeek">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_70_0" />

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.12" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.13" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<displayName>PowerToys</displayName>
<description>PowerToys</description>
<resources>
@ -26,6 +26,7 @@
<string id="SUPPORTED_POWERTOYS_0_81_1">PowerToys version 0.81.1 or later</string>
<string id="SUPPORTED_POWERTOYS_0_83_0">PowerToys version 0.83.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_84_0">PowerToys version 0.84.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_85_0">PowerToys version 0.85.0 or later</string>
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">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.
<string id="ConfigureEnabledUtilityMouseJump">Mouse Jump: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMousePointerCrosshairs">Mouse Pointer Crosshairs: Configure enabled state</string>
<string id="ConfigureEnabledUtilityMouseWithoutBorders">Mouse Without Borders: Configure enabled state</string>
<string id="ConfigureEnabledUtilityNewPlus">New+: Configure enabled state</string>
<string id="ConfigureEnabledUtilityPeek">Peek: Configure enabled state</string>
<string id="ConfigureEnabledUtilityPowerRename">Power Rename: Configure enabled state</string>
<string id="ConfigureEnabledUtilityPowerLauncher">PowerToys Run: Configure enabled state</string>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" IgnorableNamespaces="uap uap2 uap3 rescap desktop desktop4 desktop5 uap10 com">
<Identity Name="Microsoft.PowerToys.NewPlusContextMenu" ProcessorArchitecture="neutral" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" />
<Properties>
<DisplayName>PowerToys New+</DisplayName>
<PublisherDisplayName>Microsoft</PublisherDisplayName>
<Logo>Assets\NewPlus\StoreLogo.png</Logo>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
</Properties>
<Resources>
<Resource Language="en-us" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18950.0" MaxVersionTested="10.0.19000.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources"/>
</Capabilities>
<Applications>
<Application Id="Microsoft.PowerToys.NewPlusContextMenu" Executable="NewPlus.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
<uap:VisualElements AppListEntry="none" DisplayName="PowerToys New+" Description="New+ File Explorer Context Menu" BackgroundColor="transparent" Square150x150Logo="Assets\NewPlus\Square150x150Logo.png" Square44x44Logo="Assets\NewPlus\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\NewPlus\Wide310x150Logo.png" Square310x310Logo="Assets\NewPlus\LargeTile.png" Square71x71Logo="Assets\NewPlus\SmallTile.png"></uap:DefaultTile>
<uap:SplashScreen Image="Assets\NewPlus\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="NewPlusCommand" Clsid="69824FC6-4660-4A09-9E7C-48DA63C6CC0F" />
</desktop5:ItemType>
<desktop5:ItemType Type="Directory\Background">
<desktop5:Verb Id="NewPlusCommand" Clsid="69824FC6-4660-4A09-9E7C-48DA63C6CC0F" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<com:Extension Category="windows.comServer" uap10:RuntimeBehavior="packagedClassicApp">
<com:ComServer>
<com:SurrogateServer DisplayName="Context menu verb handler">
<com:Class Id="69824FC6-4660-4A09-9E7C-48DA63C6CC0F" Path="PowerToys.NewPlus.ShellExtension.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
</Package>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,236 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h new.base.rc new.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{8acb33d9-c95b-47d4-8363-9731ee0930a0}</ProjectGuid>
<RootNamespace>NewPlusShellExtension</RootNamespace>
<WindowsTargetPlatformVersion>10.0.20348.0</WindowsTargetPlatformVersion>
<ProjectName>NewPlus.ShellExtension</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<TargetExt>.dll</TargetExt>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<TargetName>PowerToys.NewPlus.ShellExtension</TargetName>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\</IntDir>
<LinkIncremental />
<IgnoreImportLibrary />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<TargetName>PowerToys.NewPlus.ShellExtension</TargetName>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\</IntDir>
<LinkIncremental />
<IgnoreImportLibrary />
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;NEWPLUSCONTEXTMENU_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
<PreBuildEvent>
<Command>del $(OutDir)\NewPlusPackage.msix /q
MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;NEWPLUSCONTEXTMENU_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
<AdditionalDependencies>runtimeobject.lib;$(CoreLibraryDependencies)</AdditionalDependencies>
<IgnoreSpecificDefaultLibraries>
</IgnoreSpecificDefaultLibraries>
</Link>
<PreBuildEvent>
<Command>del $(OutDir)\NewPlusPackage.msix /q
MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="dll_main.h" />
<ClInclude Include="shell_context_menu.h" />
<ClInclude Include="shell_context_sub_menu.h" />
<ClInclude Include="shell_context_sub_menu_item.h" />
<ClInclude Include="constants.h" />
<ClInclude Include="settings.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="new_utilities.h" />
<ClInclude Include="resource.base.h" />
<ClInclude Include="template_folder.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Generated Files/resource.h" />
<ClInclude Include="template_item.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="shell_context_menu.cpp" />
<ClCompile Include="shell_context_sub_menu.cpp" />
<ClCompile Include="shell_context_sub_menu_item.cpp" />
<ClCompile Include="dll_main.cpp" />
<ClCompile Include="powertoys_module.cpp" />
<ClCompile Include="settings.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="template_folder.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="template_item.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="dll.def" />
<None Include="packages.config" />
<CopyFileToFolders Include="TemplateExamples\Any files or folders placed in the template folder are available via New+.txt">
<DeploymentContent>true</DeploymentContent>
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\NewPlus\Templates</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="TemplateExamples\Example folder\Example txt file.txt">
<DeploymentContent>true</DeploymentContent>
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\NewPlus\Templates\Example folder</DestinationFolders>
</CopyFileToFolders>
<CopyFileToFolders Include="TemplateExamples\Example folder\Another example txt file.txt">
<DeploymentContent>true</DeploymentContent>
<FileType>Document</FileType>
<DestinationFolders>$(OutDir)\Assets\NewPlus\Templates\Example folder</DestinationFolders>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\Themes\Themes.vcxproj">
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files/new.rc" />
<None Include="new.base.rc" />
</ItemGroup>
<ItemGroup>
<None Include="Assets\NewPlus\LargeTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\New_dark.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\New_light.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\Open_templates_dark.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\Open_templates_light.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\SmallTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\SplashScreen.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\Square150x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\Square44x44Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\StoreLogo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\NewPlus\Wide310x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Include="resources.resx">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<None Include="AppxManifest.xml" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="shell_context_menu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shell_context_sub_menu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shell_context_sub_menu_item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="template_folder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="template_item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dll_main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="powertoys_module.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="template_folder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="template_item.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shell_context_sub_menu_item.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shell_context_sub_menu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shell_context_menu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dll_main.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="constants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="new_utilities.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Generated Files/resource.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="resource.base.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Assets\NewPlus\SmallTile.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\SplashScreen.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Square150x150Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Square44x44Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\StoreLogo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Wide310x150Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\SmallTile.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\SplashScreen.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Square150x150Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Square44x44Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\StoreLogo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Wide310x150Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="resources.resx">
<Filter>Resource Files</Filter>
</None>
<None Include="new.base.rc">
<Filter>Resource Files</Filter>
</None>
<None Include="dll.def">
<Filter>Source Files</Filter>
</None>
<None Include="AppxManifest.xml">
<Filter>Source Files</Filter>
</None>
<None Include="Assets\NewPlus\New.ico">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\SmallTile.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\SplashScreen.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Square150x150Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Square44x44Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\StoreLogo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Wide310x150Logo.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\LargeTile.png">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\New_dark.ico">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\New_light.ico">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Open_templates_dark.ico">
<Filter>Asset Files</Filter>
</None>
<None Include="Assets\NewPlus\Open_templates_light.ico">
<Filter>Asset Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{82bf7d46-1201-4fc2-99e0-6c1f48f97f1f}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{0c915b9c-0e6a-4d16-8620-507a10fc29c9}</UniqueIdentifier>
</Filter>
<Filter Include="Asset Files">
<UniqueIdentifier>{0c64a1a0-1f9e-4663-8649-454da469d15c}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{4f319851-7d86-4180-b3a3-fd497a320c34}</UniqueIdentifier>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{a998f674-d126-488b-8457-7673fe986f94}</UniqueIdentifier>
</Filter>
<Filter Include="Template Examples">
<UniqueIdentifier>{b442cb0f-9f62-46e8-b269-fefa8ceacb21}</UniqueIdentifier>
</Filter>
<Filter Include="Template Examples\Example folder">
<UniqueIdentifier>{e7904759-7b6c-4609-9c6d-e3eae3cb866c}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files/new.rc">
<Filter>Generated Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Text Include="TemplateExamples\Example folder\Another example txt file.txt">
<Filter>Template Examples\Example folder</Filter>
</Text>
<Text Include="TemplateExamples\Example folder\Example txt file.txt">
<Filter>Template Examples\Example folder</Filter>
</Text>
<Text Include="TemplateExamples\Any files or folders placed in the template folder are available via New+.txt">
<Filter>Template Examples</Filter>
</Text>
</ItemGroup>
</Project>

View File

@ -0,0 +1 @@
Learn more about New+ by visiting https://aka.ms/PowerToysOverview_NewPlus

View File

@ -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";
}

View File

@ -0,0 +1,5 @@
LIBRARY
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllGetActivationFactory PRIVATE

View File

@ -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<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
}
STDAPI DllCanUnloadNow()
{
return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
}
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv)
{
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
}
CoCreatableClass(shell_context_menu)

View File

@ -0,0 +1,3 @@
#pragma once
extern HMODULE module_instance_handle;

View File

@ -0,0 +1,49 @@
#include <windows.h>
#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"

View File

@ -0,0 +1,177 @@
#pragma once
#include "pch.h"
#include <common/utils/process_path.h>
#include <common/utils/package.h>
#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(&current_message, window_handle, NULL, NULL, PM_REMOVE))
{
DispatchMessage(&current_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<IUnknown> site_of_folder)
{
ComPtr<IServiceProvider> service_provider;
site_of_folder->QueryInterface(IID_PPV_ARGS(&service_provider));
ComPtr<IFolderView> folder_view;
service_provider->QueryService(__uuidof(IFolderView), IID_PPV_ARGS(&folder_view));
ComPtr<IShellFolder> 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<IFolderView> folder_view)
{
ComPtr<IShellFolder> 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;
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
</packages>

View File

@ -0,0 +1,3 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"

View File

@ -0,0 +1,37 @@
// Precompiled header file.
#pragma once
#define WIN32_LEAN_AND_MEAN
#define NOMCX
#define NOHELP
#define NOCOMM
// Windows and STL
#include <Shobjidl.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <Windows.h>
#include <shlobj.h>
#include <vector>
#include <system_error>
#include <memory>
#include <iostream>
#include <atlbase.h>
#include <wrl.h>
#include <wrl/module.h>
#include <unknwn.h>
// PowerToys project common
#include <ProjectTelemetry.h>
#include <common/utils/resources.h>
#include <common/logger/logger.h>
#include <common/logger/logger_settings.h>
#include <common/utils/logger_helper.h>
#include <common/Themes/theme_helpers.h>
// New project specific
#include "dll_main.h"
#include "template_folder.h"
#include "settings.h"
#include "new_utilities.h"

View File

@ -0,0 +1,134 @@
#include "pch.h"
#include <filesystem>
#include <string>
#include <winrt/Windows.Data.Json.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/gpo.h>
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
#include <interface/powertoy_module_interface.h>
#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();
}

View File

@ -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
//////////////////////////////

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="context_menu_item_new" xml:space="preserve">
<value>New+</value>
<comment>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+</comment>
</data>
<data name="context_menu_item_open_templates" xml:space="preserve">
<value>Open templates</value>
<comment>The menu item in the context menu that enables user to open the folder that contains their templates.</comment>
</data>
<data name="default_template_sub_folder_name_where_templates_are_stored" xml:space="preserve">
<value>Templates</value>
<comment>Default subfolder name where templates are stored.</comment>
</data>
</root>

View File

@ -0,0 +1,225 @@
#include "pch.h"
#include <common/utils/gpo.h>
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.h>
#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;
}

View File

@ -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();

View File

@ -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<shell_context_sub_menu>(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

View File

@ -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<ClassicCom>,
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<IUnknown> site_of_folder;
};

View File

@ -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<IUnknown> 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<shell_context_sub_menu_item>(templates->get_template_item(i), site_of_folder));
}
// Add separator to context menu
explorer_menu_item_commands.push_back(Make<separator_context_menu_item>());
// Add "Open templates" item to context menu
explorer_menu_item_commands.push_back(Make<template_folder_context_menu_item>(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;
}

View File

@ -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<RuntimeClassFlags<ClassicCom>, IEnumExplorerCommand>
{
public:
shell_context_sub_menu(const ComPtr<IUnknown> 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<ComPtr<IExplorerCommand>> explorer_menu_item_commands;
std::vector<ComPtr<IExplorerCommand>>::const_iterator current_command;
template_folder* templates;
ComPtr<IUnknown> site_of_folder;
};

View File

@ -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<IUnknown> 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;
}

View File

@ -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<RuntimeClassFlags<ClassicCom>, IExplorerCommand>
{
public:
shell_context_sub_menu_item(const template_item* template_entry, const ComPtr<IUnknown> 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<IUnknown> 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;
};

View File

@ -0,0 +1,52 @@
#include "pch.h"
#include <shellapi.h>
#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<std::pair<std::wstring, template_item*>> dirs;
std::list<std::pair<std::wstring, template_item*>> 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;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "pch.h"
#include <filesystem>
#include <iostream>
#include <string>
#include <list>
#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<std::pair<std::wstring, template_item*>> list_of_templates;
template_item* get_template_item(const int index) const;
protected:
template_folder();
void init();
};
}

View File

@ -0,0 +1,147 @@
#include "pch.h"
#include "template_item.h"
#include <shellapi.h>
#include "new_utilities.h"
#include <cassert>
#include <thread>
#include <shellapi.h>
#include <shlobj_core.h>
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_collision_mapping*>(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<IUnknown> 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<IUnknown> 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<IServiceProvider> service_provider;
site->QueryInterface(IID_PPV_ARGS(&service_provider));
ComPtr<IFolderView> 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;
}
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "pch.h"
#include <filesystem>
#include <iostream>
#include <string>
#include <map>
using namespace Microsoft::WRL;
namespace newplus
{
class template_item
{
public:
template_item(const std::filesystem::path entry);
std::wstring get_menu_title(const bool show_extension, const bool show_starting_digits) const;
std::wstring get_target_filename(const bool include_starting_digits) const;
std::wstring get_explorer_icon() const;
std::filesystem::path copy_object_to(const HWND window_handle, const std::filesystem::path destination) const;
void enter_rename_mode(const ComPtr<IUnknown> site, const std::filesystem::path target_folder) const;
std::filesystem::path path;
private:
static void rename_on_other_thread_workaround(const ComPtr<IUnknown> site, const std::filesystem::path target_fullpath);
std::wstring remove_starting_digits_from_filename(std::wstring filename) const;
};
}

View File

@ -0,0 +1,70 @@
#include "pch.h"
#include "trace.h"
#include <common/Telemetry/ProjectTelemetry.h>
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider() noexcept
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider() noexcept
{
TraceLoggingUnregister(g_hProvider);
}
void Trace::EventToggleOnOff(_In_ const bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"NewPlus_EventToggleOnOff",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
void Trace::EventChangedTemplateLocation() noexcept
{
TraceLoggingWrite(
g_hProvider,
"NewPlus_ChangedTemplateLocation",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::EventShowTemplateItems(const size_t number_of_templates) noexcept
{
TraceLoggingWrite(
g_hProvider,
"NewPlus_EventShowTemplateItems",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(number_of_templates, "Count"));
}
void Trace::EventCopyTemplate(_In_ const std::wstring template_file_extension) noexcept
{
TraceLoggingWrite(
g_hProvider,
"NewPlus_EventCopyTemplate",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(template_file_extension.c_str(), "Ext"));
}
void Trace::EventCopyTemplateResult(_In_ const HRESULT hr) noexcept
{
TraceLoggingWrite(
g_hProvider,
"NewPlus_EventCopyTemplateResult",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingHResult(hr),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "pch.h"
class Trace
{
public:
static void RegisterProvider() noexcept;
static void UnregisterProvider() noexcept;
static void EventToggleOnOff(_In_ const bool new_enabled_state) noexcept;
static void EventChangedTemplateLocation() noexcept;
static void EventShowTemplateItems(_In_ const size_t number_of_templates) noexcept;
static void EventCopyTemplate(_In_ const std::wstring template_file_extension) noexcept;
static void EventCopyTemplateResult(_In_ const HRESULT hr) noexcept;
};

View File

@ -49,7 +49,7 @@ static ThemeListener theme_listener{};
static void handleTheme()
{
auto theme = theme_listener.AppTheme;
auto isDark = theme == AppTheme::Dark;
auto isDark = theme == Theme::Dark;
Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light");
if (hwndEditKeyboardNativeWindow != nullptr)
{
@ -137,7 +137,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan
windowClass.lpfnWndProc = EditKeyboardWindowProc;
windowClass.hInstance = hInst;
windowClass.lpszClassName = szWindowClass;
windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == AppTheme::Dark) ? 0x00000000 : 0x00FFFFFF);
windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == Theme::Dark) ? 0x00000000 : 0x00FFFFFF);
windowClass.hIcon = static_cast<HICON>(LoadImageW(
windowClass.hInstance,
MAKEINTRESOURCE(IDS_KEYBOARDMANAGER_ICON),

View File

@ -43,7 +43,7 @@ static ThemeListener theme_listener{};
static void handleTheme()
{
auto theme = theme_listener.AppTheme;
auto isDark = theme == AppTheme::Dark;
auto isDark = theme == Theme::Dark;
Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light");
if (hwndEditShortcutsNativeWindow != nullptr)
{
@ -90,7 +90,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
windowClass.lpfnWndProc = EditShortcutsWindowProc;
windowClass.hInstance = hInst;
windowClass.lpszClassName = szWindowClass;
windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == AppTheme::Dark) ? 0x00000000 : 0x00FFFFFF);
windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == Theme::Dark) ? 0x00000000 : 0x00FFFFFF);
windowClass.hIcon = static_cast<HICON>(LoadImageW(
windowClass.hInstance,
MAKEINTRESOURCE(IDS_KEYBOARDMANAGER_ICON),

View File

@ -43,7 +43,7 @@ HWND CurrentWindow;
void handleTheme()
{
auto theme = theme_listener.AppTheme;
auto isDark = theme == AppTheme::Dark;
auto isDark = theme == Theme::Dark;
Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light");
ThemeHelpers::SetImmersiveDarkMode(CurrentWindow, isDark);
}

View File

@ -154,6 +154,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"WinUI3Apps/PowerToys.FileLocksmithExt.dll",
L"WinUI3Apps/PowerToys.RegistryPreviewExt.dll",
L"WinUI3Apps/PowerToys.MeasureToolModuleInterface.dll",
L"WinUI3Apps/PowerToys.NewPlus.ShellExtension.dll",
L"WinUI3Apps/PowerToys.HostsModuleInterface.dll",
L"WinUI3Apps/PowerToys.Peek.dll",
L"WinUI3Apps/PowerToys.EnvironmentVariablesModuleInterface.dll",

View File

@ -684,6 +684,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
return "Dashboard";
case ESettingsWindowNames::AdvancedPaste:
return "AdvancedPaste";
case ESettingsWindowNames::NewPlus:
return "NewPlus";
default:
{
Logger::error(L"Can't convert ESettingsWindowNames value={} to string", static_cast<int>(value));
@ -779,6 +781,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
{
return ESettingsWindowNames::AdvancedPaste;
}
else if (value == "NewPlus")
{
return ESettingsWindowNames::NewPlus;
}
else
{
Logger::error(L"Can't convert string value={} to ESettingsWindowNames", winrt::to_hstring(value));

View File

@ -25,6 +25,7 @@ enum class ESettingsWindowNames
CropAndLock,
EnvironmentVariables,
AdvancedPaste,
NewPlus,
};
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);

View File

@ -462,6 +462,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool newPlus;
[JsonPropertyName("NewPlus")] // This key must match newplus::constants::non_localizable
public bool NewPlus
{
get => newPlus;
set
{
if (newPlus != value)
{
LogTelemetryEvent(value);
newPlus = value;
}
}
}
private bool workspaces = true;
[JsonPropertyName("Workspaces")]

View File

@ -0,0 +1,48 @@
// 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.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Settings.UI.Library.Resources;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class NewPlusSettings : ISettingsConfig
{
public const string ModuleName = "NewPlus";
public void InitializeWithDefaultSettings()
{
// This code path should never happen
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
[JsonPropertyName("HideFileExtension")]
public bool HideFileExtension { get; set; }
[JsonPropertyName("HideStartingDigits")]
public bool HideStartingDigits { get; set; }
[JsonPropertyName("TemplateLocation")]
public string TemplateLocation { get; set; }
public string GetModuleName()
{
return ModuleName;
}
public bool UpgradeSettingsConfiguration()
{
return false;
}
}
}

View File

@ -96,6 +96,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
return Directory.GetParent(settingsPath).FullName;
}
public static string GetPowerToysInstallationWinUI3AppsAssetsFolder()
{
// return .\PowerToys\WinUI3Apps\Assets
return Path.Combine(GetPowerToysInstallationFolder(), "WinUI3Apps", "Assets");
}
private static readonly global::PowerToys.Interop.LayoutMapManaged LayoutMap = new global::PowerToys.Interop.LayoutMapManaged();
public static string GetKeyName(uint key)
@ -148,6 +154,30 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
}
}
public static void CopyDirectory(string source_directory, string destination_directory, bool copy_recursively)
{
var current_directory_info = new DirectoryInfo(source_directory);
DirectoryInfo[] source_subdirectories = current_directory_info.GetDirectories();
Directory.CreateDirectory(destination_directory);
foreach (FileInfo file in current_directory_info.GetFiles())
{
string destination_file_path = Path.Combine(destination_directory, file.Name);
file.CopyTo(destination_file_path, true);
}
if (copy_recursively)
{
foreach (DirectoryInfo subdirectory in source_subdirectories)
{
string newDestinationDir = Path.Combine(destination_directory, subdirectory.Name);
CopyDirectory(subdirectory.FullName, newDestinationDir, true);
}
}
}
public static readonly uint VirtualKeyWindows = global::PowerToys.Interop.Constants.VK_WIN_BOTH;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 KiB

View File

@ -62,6 +62,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.MouseJump: return generalSettingsConfig.Enabled.MouseJump;
case ModuleType.MousePointerCrosshairs: return generalSettingsConfig.Enabled.MousePointerCrosshairs;
case ModuleType.MouseWithoutBorders: return generalSettingsConfig.Enabled.MouseWithoutBorders;
case ModuleType.NewPlus: return generalSettingsConfig.Enabled.NewPlus;
case ModuleType.Peek: return generalSettingsConfig.Enabled.Peek;
case ModuleType.PowerRename: return generalSettingsConfig.Enabled.PowerRename;
case ModuleType.PowerLauncher: return generalSettingsConfig.Enabled.PowerLauncher;
@ -95,6 +96,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.MouseJump: generalSettingsConfig.Enabled.MouseJump = isEnabled; break;
case ModuleType.MousePointerCrosshairs: generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break;
case ModuleType.MouseWithoutBorders: generalSettingsConfig.Enabled.MouseWithoutBorders = isEnabled; break;
case ModuleType.NewPlus: generalSettingsConfig.Enabled.NewPlus = isEnabled; break;
case ModuleType.Peek: generalSettingsConfig.Enabled.Peek = isEnabled; break;
case ModuleType.PowerRename: generalSettingsConfig.Enabled.PowerRename = isEnabled; break;
case ModuleType.PowerLauncher: generalSettingsConfig.Enabled.PowerLauncher = isEnabled; break;
@ -127,6 +129,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
case ModuleType.MouseJump: return GPOWrapper.GetConfiguredMouseJumpEnabledValue();
case ModuleType.MousePointerCrosshairs: return GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue();
case ModuleType.MouseWithoutBorders: return GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue();
case ModuleType.NewPlus: return GPOWrapper.GetConfiguredNewPlusEnabledValue();
case ModuleType.Peek: return GPOWrapper.GetConfiguredPeekEnabledValue();
case ModuleType.PowerRename: return GPOWrapper.GetConfiguredPowerRenameEnabledValue();
case ModuleType.PowerLauncher: return GPOWrapper.GetConfiguredPowerLauncherEnabledValue();
@ -160,6 +163,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
ModuleType.MouseJump => typeof(MouseUtilsPage),
ModuleType.MousePointerCrosshairs => typeof(MouseUtilsPage),
ModuleType.MouseWithoutBorders => typeof(MouseWithoutBordersPage),
ModuleType.NewPlus => typeof(NewPlusPage),
ModuleType.Peek => typeof(PeekPage),
ModuleType.PowerRename => typeof(PowerRenamePage),
ModuleType.PowerLauncher => typeof(PowerLauncherPage),

View File

@ -26,6 +26,11 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
}
public static string GetFolderDialog(IntPtr hwndOwner)
{
return GetFolderDialogWithFlags(hwndOwner, 0);
}
public static string GetFolderDialogWithFlags(IntPtr hwndOwner, uint ulFlags)
{
// windows MAX_PATH with long path enable can be approximated 32k char long
// allocating more than double (unicode) to hold the path
@ -37,7 +42,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
browseInfo.PidlRoot = IntPtr.Zero;
browseInfo.PszDisplayName = null;
browseInfo.LpszTitle = null;
browseInfo.UlFlags = 0;
browseInfo.UlFlags = ulFlags;
browseInfo.Lpfn = null;
browseInfo.LParam = IntPtr.Zero;
browseInfo.IImage = 0;
@ -61,5 +66,10 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
return sb.ToString();
}
public struct FolderDialogFlags
{
public const uint _BIF_NEWDIALOGSTYLE = 0x00000040;
}
}
}

View File

@ -33,5 +33,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
Workspaces,
WhatsNew,
RegistryPreview,
NewPlus,
}
}

View File

@ -23,7 +23,6 @@
<None Remove="Assets\Settings\Modules\APDialog.dark.png" />
<None Remove="Assets\Settings\Modules\APDialog.light.png" />
</ItemGroup>
<ItemGroup>
<Page Remove="SettingsXAML\App.xaml" />
</ItemGroup>

View File

@ -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

View File

@ -0,0 +1,31 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeNewPlus"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tk7controls="using:CommunityToolkit.WinUI.UI.Controls"
mc:Ignorable="d">
<!-- TODO: Create New+ overview .gif and update ref here -->
<controls:OOBEPageControl x:Uid="Oobe_NewPlus" HeroImage="ms-appx:///Assets/Settings/Modules/OOBE/NewPlus.png">
<controls:OOBEPageControl.PageContent>
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock x:Uid="Oobe_HowToUse" Style="{ThemeResource OobeSubtitleStyle}" />
<tk7controls:MarkdownTextBlock x:Uid="Oobe_NewPlus_HowToUse" Background="Transparent" />
<TextBlock x:Uid="Oobe_TipsAndTricks" Style="{ThemeResource OobeSubtitleStyle}" />
<tk7controls:MarkdownTextBlock x:Uid="Oobe_NewPlus_TipsAndTricks" Background="Transparent" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button x:Uid="OOBE_Settings" Click="SettingsLaunchButton_Click" />
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_NewPlus" Style="{StaticResource TextButtonStyle}">
<TextBlock x:Uid="NewPlus_Learn_More" TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</StackPanel>
</controls:OOBEPageControl.PageContent>
</controls:OOBEPageControl>
</Page>

View File

@ -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
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
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();
}
}
}

View File

@ -121,6 +121,10 @@
x:Uid="Shell_MouseWithoutBorders"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}"
Tag="MouseWithoutBorders" />
<NavigationViewItem
x:Uid="NewPlus_Product_Name"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}"
Tag="NewPlus" />
<NavigationViewItem
x:Uid="Shell_Peek"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Peek.png}"

View File

@ -207,6 +207,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
ModuleName = "RegistryPreview",
IsNew = true,
});
Modules.Insert((int)PowerToysModules.NewPlus, new OobePowerToysModule()
{
ModuleName = "NewPlus",
IsNew = true,
});
}
public void OnClosing()
@ -285,6 +291,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
case "Hosts": NavigationFrame.Navigate(typeof(OobeHosts)); break;
case "RegistryPreview": NavigationFrame.Navigate(typeof(OobeRegistryPreview)); break;
case "Peek": NavigationFrame.Navigate(typeof(OobePeek)); break;
case "NewPlus": NavigationFrame.Navigate(typeof(OobeNewPlus)); break;
case "Workspaces": NavigationFrame.Navigate(typeof(OobeWorkspaces)); break;
}
}

View File

@ -0,0 +1,92 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Views.NewPlusPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<controls:SettingsPageControl x:Uid="NewPlus" ModuleImageSource="ms-appx:///Assets/Settings/Modules/NewPlus.png">
<controls:SettingsPageControl.ModuleContent>
<StackPanel
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
Orientation="Vertical"
Spacing="2">
<tkcontrols:SettingsCard
x:Uid="NewPlus_Enable_Toggle"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}"
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="NewPlus_NoWindows10SupportWarning"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsWin10OrLower, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsWin10OrLower, Mode=OneWay}"
Severity="Warning" />
<InfoBar
x:Uid="GPO_SettingIsManaged"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
Severity="Informational" />
<controls:SettingsGroup x:Uid="NewPlus_Templates" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard
x:Uid="NewPlus_Templates_Location"
ActionIcon="{ui:FontIcon Glyph=&#xE8A7;}"
Command="{x:Bind ViewModel.OpenCurrentNewTemplateFolder}"
HeaderIcon="{ui:FontIcon Glyph=&#xF12B;}"
IsClickEnabled="True">
<Button
x:Uid="NewPlus_Templates_Location_Change"
Command="{x:Bind ViewModel.PickAnotherNewTemplateFolder}"
Style="{ThemeResource AccentButtonStyle}" />
<tkcontrols:SettingsCard.Description>
<StackPanel>
<TextBlock Text="{x:Bind ViewModel.TemplateLocation, Mode=OneWay}" />
<HyperlinkButton x:Uid="NewPlus_Templates_Location_Learn_More" NavigateUri="https://aka.ms/PowerToysOverview_NewPlus_TemplatesLocation" />
</StackPanel>
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="NewPlus_TemplatesNotBackupAndRestoreWarning"
IsClosable="True"
IsOpen="True"
IsTabStop="True"
Severity="Informational" />
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="NewPlus_Display_Options" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsCard x:Uid="NewPlus_Hide_File_Extension_Toggle" IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="HideFileExtensionToggle" IsOn="{x:Bind ViewModel.HideFileExtension, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="NewPlus_Hide_Starting_Digits_Toggle" IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="HideStartingDigitsToggle" IsOn="{x:Bind ViewModel.HideStartingDigits, Mode=TwoWay}" />
<tkcontrols:SettingsCard.Description>
<TextBlock x:Uid="NewPlus_Hide_Starting_Digits_Description" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</controls:SettingsGroup>
</StackPanel>
</controls:SettingsPageControl.ModuleContent>
<controls:SettingsPageControl.PrimaryLinks>
<controls:PageLink x:Uid="NewPlus_Learn_More" Link="https://aka.ms/PowerToysOverview_NewPlus" />
</controls:SettingsPageControl.PrimaryLinks>
<controls:SettingsPageControl.SecondaryLinks>
<controls:PageLink Link="https://www.linkedin.com/in/christian-gaardmark/" Text="Christian Gaardmark" />
</controls:SettingsPageControl.SecondaryLinks>
</controls:SettingsPageControl>
</Page>

View File

@ -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<GeneralSettings>.GetInstance(settings_utils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
}
public void RefreshEnabledState()
{
ViewModel.RefreshEnabledState();
}
}
}

View File

@ -180,6 +180,11 @@
helpers:NavHelper.NavigateTo="views:MouseWithoutBordersPage"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}" />
<NavigationViewItem
x:Uid="NewPlus_Product_Name"
helpers:NavHelper.NavigateTo="views:NewPlusPage"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}" />
<NavigationViewItem
x:Uid="Shell_Peek"
helpers:NavHelper.NavigateTo="views:PeekPage"

View File

@ -4276,6 +4276,88 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Automatically close the AdvancedPaste window after it loses focus</value>
<comment>AdvancedPaste is a product name, do not loc</comment>
</data>
<data name="NewPlus.ModuleTitle" xml:space="preserve">
<value>New+</value>
<comment>New+ is the name of the utility. Localize product name in accordance with Windows New</comment>
</data>
<data name="NewPlus.ModuleDescription" xml:space="preserve">
<value>Create files and folders from a personalized set of templates</value>
</data>
<data name="NewPlus_Product_Name.Content" xml:space="preserve">
<value>New+</value>
<comment>New+ is the name of the utility. Localize product name in accordance with Windows New</comment>
</data>
<data name="NewPlus_Product_Description.Description" xml:space="preserve">
<value>Create files and folders from a personalized set of templates</value>
<comment>New+ product description</comment>
</data>
<data name="NewPlus_Learn_More.Text" xml:space="preserve">
<value>Learn more about New+</value>
<comment>New+ learn more link. Localize product name in accordance with Windows New</comment>
</data>
<data name="NewPlus_Enable_Toggle.Header" xml:space="preserve">
<value>Enable New+</value>
<comment>Localize product name in accordance with Windows New</comment>
</data>
<data name="NewPlus_NoWindows10SupportWarning.Title" xml:space="preserve">
<value>New+ is not supported in Windows 10 and is not expected to work.</value>
</data>
<data name="NewPlus_TemplatesNotBackupAndRestoreWarning.Title" xml:space="preserve">
<value>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.</value>
</data>
<data name="NewPlus_Templates.Header" xml:space="preserve">
<value>Templates</value>
<comment>Templates label</comment>
</data>
<data name="NewPlus_Templates_Location.Header" xml:space="preserve">
<value>Location</value>
<comment>Templates Location label</comment>
</data>
<data name="NewPlus_Templates_Location_Path.Text" xml:space="preserve">
<value>...</value>
<comment>Do not localize</comment>
</data>
<data name="NewPlus_Templates_Location_Learn_More.Content" xml:space="preserve">
<value>Learn more about template location</value>
<comment>Read more about templates location</comment>
</data>
<data name="NewPlus_Templates_Location_Change.Content" xml:space="preserve">
<value>Change</value>
<comment>Button where user can Change the location of New templates</comment>
</data>
<data name="NewPlus_Display_Options.Header" xml:space="preserve">
<value>Display options</value>
<comment>Display options label</comment>
</data>
<data name="NewPlus_Hide_File_Extension_Toggle.Header" xml:space="preserve">
<value>Hide template filename extension</value>
<comment>Template file name extension settings toggle</comment>
</data>
<data name="NewPlus_Hide_Starting_Digits_Toggle.Header" xml:space="preserve">
<value>Hide template filename starting digits, spaces, and dots</value>
<comment>Template filename starting digits settings toggle</comment>
</data>
<data name="NewPlus_Hide_Starting_Digits_Description.Text" xml:space="preserve">
<value>This option is useful when using digits, spaces and dots at the beginning of filenames to control the display order of templates</value>
<comment>Template filename starting digits settings toggle</comment>
</data>
<data name="NewPlus.SecondaryLinksHeader" xml:space="preserve">
<value>Attribution</value>
<comment>giving credit</comment>
</data>
<data name="Oobe_NewPlus.Title" xml:space="preserve">
<value>New+</value>
<comment>New+ is the name of the utility. Localize product name in accordance with Windows New</comment>
</data>
<data name="Oobe_NewPlus.Description" xml:space="preserve">
<value>Create files and folders from a personalized set of templates.</value>
</data>
<data name="Oobe_NewPlus_HowToUse.Text" xml:space="preserve">
<value>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.</value>
</data>
<data name="Oobe_NewPlus_TipsAndTricks.Text" xml:space="preserve">
<value>You can have multiple templates of the same file type, and you can even template folders!</value>
</data>
<data name="Workspaces.ModuleDescription" xml:space="preserve">
<value>Workspaces is a quick and easy way to launch a set of applications to custom positions and configurations with one-click.</value>
</data>

View File

@ -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<DashboardModuleItem>(), // never called, all values listed above
};
}
@ -495,6 +503,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return new ObservableCollection<DashboardModuleItem>(list);
}
private ObservableCollection<DashboardModuleItem> GetModuleItemsNewPlus()
{
var list = new List<DashboardModuleItem>
{
new DashboardModuleTextItem() { Label = resourceLoader.GetString("NewPlus_Product_Description/Description") },
};
return new ObservableCollection<DashboardModuleItem>(list);
}
internal void SWVersionButtonClicked()
{
NavigationService.Navigate(typeof(GeneralPage));

View File

@ -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<GeneralSettings> settingsRepository, Func<string, int> 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<string, int> SendConfigMSG { get; }
public static NewPlusSettings LoadSettings(ISettingsUtils settingsUtils)
{
NewPlusSettings settings = null;
try
{
settings = settingsUtils.GetSettings<NewPlusSettings>(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 <Program Files>\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<string> PickFolderDialog()
{
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.GetSettingsWindow());
string pathFolder = await Task.FromResult<string>(ShellGetFolder.GetFolderDialogWithFlags(hwnd, ShellGetFolder.FolderDialogFlags._BIF_NEWDIALOGSTYLE));
return pathFolder;
}
private void SaveSettingsToJson()
{
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
}
}

View File

@ -54,7 +54,8 @@ vector<wstring> 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<wstring> GetXpathArray(wstring xpath)