Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New+ Support for variables in template filenames (35287) #37074

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bd175a2
Add variable support - initial version without UI
cgaarden Dec 22, 2024
7e36a23
Add variable in template filename support in New+
cgaarden Dec 22, 2024
0f5a8ed
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Dec 26, 2024
bb3c47d
Merge remote-tracking branch 'upstream/main' into New+feature-35287-s…
cgaarden Jan 23, 2025
b7bf769
Several little fixes
cgaarden Jan 23, 2025
e3824f4
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Jan 24, 2025
bd13af1
Merge remote-tracking branch 'upstream/main' into New+feature-35287-s…
cgaarden Jan 25, 2025
cca4878
New+feature-35287-support-for-variables-in-templates
cgaarden Jan 25, 2025
1fc8d65
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Jan 25, 2025
51ca5b8
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Jan 25, 2025
ba4f07e
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Jan 25, 2025
a8b6a1c
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Jan 25, 2025
81240c3
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Jan 26, 2025
0b41e5b
Merge remote-tracking branch 'upstream/main' into New+feature-35287-s…
cgaarden Jan 29, 2025
d40aae2
Merge branch 'microsoft:main' into New+feature-35287-support-for-vari…
cgaarden Jan 29, 2025
1b2d01d
Merge remote-tracking branch 'upstream/main' into New+feature-35287-s…
cgaarden Feb 15, 2025
b9be940
Merge branch 'New+feature-35287-support-for-variables-in-templates' o…
cgaarden Feb 15, 2025
6b1f684
Merge branch 'main' into pr37074
jaimecbernardo Feb 25, 2025
643bec6
Fix XAML style
jaimecbernardo Feb 25, 2025
4fee67a
Merge remote-tracking branch 'upstream/main' into New+feature-35287-s…
cgaarden Feb 26, 2025
e869120
Addressed code review feedback
cgaarden Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add variable support - initial version without UI
  • Loading branch information
cgaarden committed Dec 25, 2024
commit bd175a207ff17d90175fd3e823235f3997bf2581
4 changes: 4 additions & 0 deletions src/common/GPOWrapper/GPOWrapper.cpp
Original file line number Diff line number Diff line change
@@ -228,4 +228,8 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowDataDiagnosticsValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredNewPlusReplaceVariablesValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredNewPlusReplaceVariablesValue());
}
}
1 change: 1 addition & 0 deletions src/common/GPOWrapper/GPOWrapper.h
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static winrt::hstring GPOWrapper::GetConfiguredMwbPolicyDefinedIpMappingRules();
static GpoRuleConfigured GetConfiguredNewPlusHideTemplateFilenameExtensionValue();
static GpoRuleConfigured GetAllowDataDiagnosticsValue();
static GpoRuleConfigured GetConfiguredNewPlusReplaceVariablesValue();
};
}

1 change: 1 addition & 0 deletions src/common/GPOWrapper/GPOWrapper.idl
Original file line number Diff line number Diff line change
@@ -66,6 +66,7 @@ namespace PowerToys
static String GetConfiguredMwbPolicyDefinedIpMappingRules();
static GpoRuleConfigured GetConfiguredNewPlusHideTemplateFilenameExtensionValue();
static GpoRuleConfigured GetAllowDataDiagnosticsValue();
static GpoRuleConfigured GetConfiguredNewPlusReplaceVariablesValue();
}
}
}
7 changes: 7 additions & 0 deletions src/common/utils/gpo.h
Original file line number Diff line number Diff line change
@@ -84,6 +84,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_MWB_DISABLE_USER_DEFINED_IP_MAPPING_RULES = L"MwbDisableUserDefinedIpMappingRules";
const std::wstring POLICY_MWB_POLICY_DEFINED_IP_MAPPING_RULES = L"MwbPolicyDefinedIpMappingRules";
const std::wstring POLICY_NEW_PLUS_HIDE_TEMPLATE_FILENAME_EXTENSION = L"NewPlusHideTemplateFilenameExtension";
const std::wstring POLICY_NEW_PLUS_REPLACE_VARIABLES = L"NewPlusReplaceVariables";

// Methods used for reading the registry
#pragma region ReadRegistryMethods
@@ -597,5 +598,11 @@ namespace powertoys_gpo {
{
return getConfiguredValue(POLICY_NEW_PLUS_HIDE_TEMPLATE_FILENAME_EXTENSION);
}

inline gpo_rule_configured_t getConfiguredNewPlusReplaceVariablesValue()
{
return getConfiguredValue(POLICY_NEW_PLUS_REPLACE_VARIABLES);
}

#pragma endregion IndividualModuleSettingPolicies
}
Original file line number Diff line number Diff line change
@@ -83,6 +83,8 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\NewShellExtensionContextMenu\constants.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\new_utilities.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h" />
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.h" />
@@ -97,6 +99,7 @@
<ClInclude Include="shell_context_menu_win10.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\powertoys_module.cpp" />
<ClCompile Include="..\NewShellExtensionContextMenu\settings.cpp" />
Original file line number Diff line number Diff line change
@@ -57,6 +57,12 @@
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -92,6 +98,9 @@
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files\new.rc">
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ IFACEMETHODIMP shell_context_menu_win10::Initialize(PCIDLIST_ABSOLUTE, IDataObje
IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UINT menu_index, UINT menu_first_cmd_id, UINT, UINT menu_flags)
{
if (!NewSettingsInstance().GetEnabled()
|| package::IsWin11OrGreater()
//cgaarden || package::IsWin11OrGreater()
)
{
return E_FAIL;
@@ -184,7 +184,10 @@ void shell_context_menu_win10::add_separator_to_context_menu(HMENU sub_menu_of_t
void shell_context_menu_win10::add_template_item_to_context_menu(HMENU sub_menu_of_templates, int sub_menu_index, newplus::template_item* const template_item, int menu_id, int index)
{
wchar_t menu_name[256] = { 0 };
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(!utilities::get_newplus_setting_hide_extension(), !utilities::get_newplus_setting_hide_starting_digits()).c_str());
wcscpy_s(menu_name, ARRAYSIZE(menu_name), template_item->get_menu_title(
!utilities::get_newplus_setting_hide_extension(),
!utilities::get_newplus_setting_hide_starting_digits(),
utilities::get_newplus_setting_resolve_variables()).c_str());
MENUITEMINFO newplus_menu_item_template;
newplus_menu_item_template.cbSize = sizeof(MENUITEMINFO);
newplus_menu_item_template.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_DATA;
Original file line number Diff line number Diff line change
@@ -114,6 +114,8 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="dll_main.h" />
<ClInclude Include="helpers_filesystem.h" />
<ClInclude Include="helpers_variables.h" />
<ClInclude Include="shell_context_menu.h" />
<ClInclude Include="shell_context_sub_menu.h" />
<ClInclude Include="shell_context_sub_menu_item.h" />
@@ -128,6 +130,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
<ClInclude Include="template_item.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp" />
<ClCompile Include="new_utilities.cpp" />
<ClCompile Include="shell_context_menu.cpp" />
<ClCompile Include="shell_context_sub_menu.cpp" />
Original file line number Diff line number Diff line change
@@ -34,6 +34,9 @@
<ClCompile Include="new_utilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="template_folder.h">
@@ -75,6 +78,12 @@
<ClInclude Include="resource.base.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="helpers_filesystem.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="helpers_variables.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
4 changes: 3 additions & 1 deletion src/modules/NewPlus/NewShellExtensionContextMenu/constants.h
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@ namespace newplus::constants::non_localizable

constexpr WCHAR settings_json_key_hide_starting_digits[] = L"HideStartingDigits";

constexpr WCHAR settings_json_key_replace_variables[] = L"ReplaceVariables";

constexpr WCHAR settings_json_key_template_location[] = L"TemplateLocation";

constexpr WCHAR context_menu_package_name[] = L"NewPlusContextMenu";
@@ -30,5 +32,5 @@ namespace newplus::constants::non_localizable

constexpr WCHAR open_templates_icon_dark_resource_relative_path[] = L"\\Assets\\NewPlus\\Open_templates_dark.ico";

constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
constexpr WCHAR parent_folder_name_variable[] = L"$PARENT_FOLDER_NAME";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

#include "helpers_variables.h"

namespace newplus::helpers::filesystem
{
namespace constants::non_localizable
{
constexpr WCHAR desktop_ini_filename[] = L"desktop.ini";
}

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 is_directory(const std::filesystem::path path)
{
const auto entry = std::filesystem::directory_entry(path);
return entry.is_directory();
}

inline std::wstring make_valid_filename(const std::wstring& string, const wchar_t replace_with = L' ')
{
// replace all non-filename-valid chars with replace_with wchar
std::wstring valid_filename = string;

std::replace_if(valid_filename.begin(), valid_filename.end(), [](wchar_t c) { return c == L'/' || c == L'\\' || c == L':' || c == L'*' || c == L'?' || c == L'"' || c == L'<' || c == L'>' || c == L'|'; }, replace_with);

return valid_filename;
}

inline std::wstring make_unique_path_name(const std::wstring& initial_path)
{
std::filesystem::path folder_path(initial_path);

int counter = 1;

while (std::filesystem::exists(folder_path))
{
folder_path = initial_path + L" (" + std::to_wstring(counter) + L")";
counter++;
}

return folder_path.wstring();
}
}
169 changes: 169 additions & 0 deletions src/modules/NewPlus/NewShellExtensionContextMenu/helpers_variables.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#pragma once

#include <regex>
#include "..\..\powerrename\lib\Helpers.h"
#include "helpers_filesystem.h"

#pragma comment(lib, "Pathcch.lib")

namespace newplus::helpers::variables
{
inline std::wstring resolve_an_environment_variable(const std::wstring& string)
{
std::wstring return_string = string;
wchar_t* env_variable = nullptr;

_wdupenv_s(&env_variable, nullptr, return_string.c_str());

if (env_variable != nullptr)
{
return_string = env_variable;
free(env_variable);
}

return return_string;
}

inline std::wstring resolve_date_time_variables(const std::wstring& string)
{
SYSTEMTIME now = { 0 };
GetSystemTime(&now);
wchar_t resolved_filename[MAX_PATH] = { 0 };
GetDatedFileName(resolved_filename, ARRAYSIZE(resolved_filename), string.c_str(), now);

return resolved_filename;
}

inline std::wstring replace_all_occurrences(const std::wstring& string, const std::wstring& search_for, const std::wstring& replacement)
{
std::wstring return_string = string;
size_t pos = 0;

while ((pos = return_string.find(search_for, pos)) != std::wstring::npos)
{
return_string.replace(pos, search_for.length(), replacement);
pos += replacement.length();
}

return return_string;
}

inline std::wstring resolve_environment_variables(const std::wstring& string)
{
// Do case-insensitive string replacement of environment variables being consistent with normal %eNV_VaR% behavior
std::wstring return_string = string;
const std::wregex reg_expression(L"%([^%]+)%");
std::wsmatch match;

size_t start = 0;
while (std::regex_search(return_string.cbegin() + start, return_string.cend(), match, reg_expression))
{
std::wstring env_var_name = match[1].str();
std::wstring env_var_value = resolve_an_environment_variable(env_var_name);
if (!env_var_value.empty())
{
size_t match_position = match.position(0) + start;
return_string.replace(match_position, match.length(0), env_var_value);
start = match_position + env_var_value.length();
}
else
{
start += match.position(0) + match.length(0);
}
}

return return_string;
}

inline std::wstring resolve_parent_folder(const std::wstring& string, const std::wstring& parent_folder_name)
{
// Do case-sensitive string replacement, for consistency on variables designated with $
std::wstring result = replace_all_occurrences(string, constants::non_localizable::parent_folder_name_variable, parent_folder_name);

return result;
}

inline std::filesystem::path resolve_variables_in_filename(const std::wstring& filename, const std::wstring& parent_folder_name)
{
std::wstring result;

result = resolve_date_time_variables(filename);
result = resolve_environment_variables(result);
if (!parent_folder_name.empty())
{
result = resolve_parent_folder(result, parent_folder_name);
}
result = newplus::helpers::filesystem::make_valid_filename(result);

return result;
}

inline std::filesystem::path resolve_variables_in_path(const std::filesystem::path& path)
{
// Need to resolve the whole path top-down (root to leaf), because of the support for $PARENT_FOLDER_NAME
std::filesystem::path result;
std::wstring previous_section;
std::wstring current_section;
auto path_section = path.begin();
int level = 0;

while (path_section != path.end())
{
previous_section = current_section;
current_section = path_section->wstring();

if (level <= 1)
{
// Up to and including L"x:\\"
result /= current_section;
}
else
{
// Past L"x:\\", e.g. L"x:\\level1" and beyond
result /= resolve_variables_in_filename(current_section, previous_section);
}
path_section++;
level++;
}

return result;
}

inline void resolve_variables_in_filename_and_rename_files(const std::filesystem::path& path, const bool do_rename = true)
{
// Depth first recursion, so that we start renaming the leaves, and avoid having to rescan
for (const auto& entry : std::filesystem::directory_iterator(path))
{
if (std::filesystem::is_directory(entry.status()))
{
resolve_variables_in_filename_and_rename_files(entry.path(), do_rename);
}
}

// Perform the actual rename
for (const auto& current : std::filesystem::directory_iterator(path))
{
if (!newplus::helpers::filesystem::is_hidden(current))
{
const std::filesystem::path resolved_path = resolve_variables_in_path(current.path());

// Only rename if the filename is actually different
const std::wstring non_resolved_leaf = current.path().filename();
const std::wstring resolved_leaf = resolved_path.filename();

if (StrCmpIW(non_resolved_leaf.c_str(), resolved_leaf.c_str()) != 0)
{
const std::wstring org_name = current.path();
const std::wstring new_name = current.path().parent_path() / resolved_leaf;
const std::wstring really_new_name = helpers::filesystem::make_unique_path_name(new_name);

// To aid with testing, only conditionally rename
if (do_rename)
{
std::filesystem::rename(org_name, really_new_name);
}
}
}
}
}
}
Loading
Oops, something went wrong.