mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
New+ Rename and Desktop icon positioning improvements (#48083)
New+ Rename and Desktop icon positioning improvements. Manually tested on Windows 11. Windows 10 updates have NOT been tested. ## Summary of the Pull Request * Obtain cursor position early in the lifecycle of the context menu * Busy wait until copy is complete and shell is aware of icon * If context menu is on desktop, reposition the icon using the cursor position obtained scaled using monitor appropriate DPI * Slight refactor to help port code from New++ to New+ ## PR Checklist - [x] Closes: #36440 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [n/a] **Localization:** All end-user-facing strings can be localized - [n/a] **Dev docs:** Added/updated - [n/a] **New binaries:** Added on the required places - [n/a] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [n/a] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [n/a] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [n/a] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [n/a] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments * n/a ## Validation Steps Performed **Windows 11** * x64: Manually tested * ARM64: Not tested **Windows 10** * Not tested --------- Co-authored-by: Boliang Zhang (from Dev Box) <bozhang@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41b6b39b-b620-4e02-b7ca-8ae470a9c1e2
This commit is contained in:
committed by
GitHub
parent
c4431304c4
commit
b605fd35c5
5
.github/actions/spell-check/allow/code.txt
vendored
5
.github/actions/spell-check/allow/code.txt
vendored
@@ -171,6 +171,7 @@ thmutil
|
||||
uriutil
|
||||
VKTAB
|
||||
wcautil
|
||||
wcsnlen
|
||||
winkey
|
||||
wininet
|
||||
WMKEYDOWN
|
||||
@@ -452,3 +453,7 @@ WIDGETBOARD
|
||||
|
||||
# URIs
|
||||
actioncenter
|
||||
|
||||
# New+ shell PIDL type names
|
||||
PCUITEMID
|
||||
PITEMID
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
@@ -85,6 +84,7 @@
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\constants.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_filesystem.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\newplus_icon_utilities.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\new_utilities.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\settings.h" />
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\shell_context_sub_menu.h" />
|
||||
@@ -101,6 +101,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\Helpers.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\newplus_icon_utilities.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\powertoys_module.cpp" />
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\settings.cpp" />
|
||||
|
||||
@@ -63,6 +63,12 @@
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\helpers_variables.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\Helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\NewShellExtensionContextMenu\newplus_icon_utilities.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
@@ -98,7 +104,10 @@
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\new_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\NewShellExtensionContextMenu\newplus_icon_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
@@ -121,5 +130,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natstepfilter" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
HMODULE module_instance_handle = 0;
|
||||
Shared::Trace::ETWTrace trace(L"NewPlusShellExtension_Win10");
|
||||
std::atomic_uint32_t active_rename_workers = 0;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE module_handle, DWORD ul_reason_for_call, LPVOID reserved)
|
||||
{
|
||||
@@ -33,7 +34,7 @@ STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IAc
|
||||
|
||||
STDAPI DllCanUnloadNow()
|
||||
{
|
||||
return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
|
||||
return Module<InProc>::GetModule().GetObjectCount() == 0 && active_rename_workers.load() == 0 ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
STDAPI DllGetClassObject(_In_ REFCLSID ref_class_id, _In_ REFIID ref_interface_id, _Outptr_ LPVOID FAR* object)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <common/Telemetry/EtwTrace/EtwTrace.h>
|
||||
|
||||
extern HMODULE module_instance_handle;
|
||||
extern Shared::Trace::ETWTrace trace;
|
||||
extern std::atomic_uint32_t active_rename_workers;
|
||||
|
||||
@@ -45,6 +45,18 @@ IFACEMETHODIMP shell_context_menu_win10::QueryContextMenu(HMENU menu_handle, UIN
|
||||
|
||||
try
|
||||
{
|
||||
// Capture mouse position now (at menu-open time) for more accurate desktop icon placement later.
|
||||
// Use {-1,-1} as the "not captured" sentinel (matching the Win11 path) because (0,0) is a valid
|
||||
// screen coordinate; only treat the position as real when GetCursorPos succeeds.
|
||||
mouse_position_at_time_of_invoke = { -1, -1 };
|
||||
const DPI_AWARENESS_CONTEXT prev_dpi_ctx = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
if (!GetCursorPos(&mouse_position_at_time_of_invoke))
|
||||
{
|
||||
mouse_position_at_time_of_invoke = { -1, -1 };
|
||||
}
|
||||
|
||||
SetThreadDpiAwarenessContext(prev_dpi_ctx);
|
||||
|
||||
// Create the initial context popup menu containing the list of templates and open templates action
|
||||
int menu_id = menu_first_cmd_id;
|
||||
MENUITEMINFO newplus_main_context_menu_item = { 0 };
|
||||
@@ -245,8 +257,7 @@ IFACEMETHODIMP shell_context_menu_win10::InvokeCommand(CMINVOKECOMMANDINFO* para
|
||||
{
|
||||
// It's a template menu item
|
||||
const auto template_entry = templates->get_template_item(selected_menu_item_index);
|
||||
|
||||
return newplus::utilities::copy_template(template_entry, site_of_folder);
|
||||
return newplus::utilities::copy_template(template_entry, site_of_folder, mouse_position_at_time_of_invoke);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -42,4 +42,5 @@ protected:
|
||||
ComPtr<IUnknown> site_of_folder;
|
||||
newplus::template_folder* templates = nullptr;
|
||||
std::vector<HBITMAP> bitmap_handles;
|
||||
POINT mouse_position_at_time_of_invoke = {-1, -1};
|
||||
};
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
@@ -124,6 +122,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
<ClInclude Include="settings.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="new_utilities.h" />
|
||||
<ClInclude Include="newplus_icon_utilities.h" />
|
||||
<ClInclude Include="RuntimeRegistration.h" />
|
||||
<ClInclude Include="resource.base.h" />
|
||||
<ClInclude Include="template_folder.h" />
|
||||
@@ -134,6 +133,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Helpers.cpp" />
|
||||
<ClCompile Include="new_utilities.cpp" />
|
||||
<ClCompile Include="newplus_icon_utilities.cpp" />
|
||||
<ClCompile Include="shell_context_menu.cpp" />
|
||||
<ClCompile Include="shell_context_sub_menu.cpp" />
|
||||
<ClCompile Include="shell_context_sub_menu_item.cpp" />
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
<ClCompile Include="new_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\powerrename\lib\Helpers.cpp">
|
||||
<ClCompile Include="newplus_icon_utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
@@ -87,6 +90,12 @@
|
||||
<ClInclude Include="RuntimeRegistration.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="newplus_icon_utilities.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
@@ -202,8 +211,14 @@
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CopyFileToFolders Include="TemplateExamples\Any files or folders placed in the template folder are available via New+.txt" />
|
||||
<CopyFileToFolders Include="TemplateExamples\Example folder\Example txt file.txt" />
|
||||
<CopyFileToFolders Include="TemplateExamples\Example folder\Another example txt file.txt" />
|
||||
<CopyFileToFolders Include="TemplateExamples\Any files or folders placed in the template folder are available via New+.txt">
|
||||
<Filter>Template Examples</Filter>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="TemplateExamples\Example folder\Example txt file.txt">
|
||||
<Filter>Template Examples\Example folder</Filter>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="TemplateExamples\Example folder\Another example txt file.txt">
|
||||
<Filter>Template Examples\Example folder</Filter>
|
||||
</CopyFileToFolders>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
HMODULE module_instance_handle = 0;
|
||||
Shared::Trace::ETWTrace trace(L"NewPlusShellExtension");
|
||||
std::atomic_uint32_t active_rename_workers = 0;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE module_handle, DWORD ul_reason_for_call, LPVOID reserved)
|
||||
{
|
||||
@@ -33,7 +34,7 @@ STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IAc
|
||||
|
||||
STDAPI DllCanUnloadNow()
|
||||
{
|
||||
return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
|
||||
return Module<InProc>::GetModule().GetObjectCount() == 0 && active_rename_workers.load() == 0 ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <common/Telemetry/EtwTrace/EtwTrace.h>
|
||||
|
||||
extern HMODULE module_instance_handle;
|
||||
extern Shared::Trace::ETWTrace trace;
|
||||
extern Shared::Trace::ETWTrace trace;
|
||||
extern std::atomic_uint32_t active_rename_workers;
|
||||
@@ -10,7 +10,9 @@
|
||||
#include "template_item.h"
|
||||
#include "trace.h"
|
||||
#include "helpers_variables.h"
|
||||
#include <shellscalingapi.h>
|
||||
|
||||
#pragma comment(lib, "Shcore.lib")
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
using namespace newplus;
|
||||
@@ -20,59 +22,6 @@ namespace newplus::utilities
|
||||
size_t get_saved_number_of_templates();
|
||||
void set_saved_number_of_templates(size_t templates);
|
||||
|
||||
inline std::wstring get_explorer_icon(std::filesystem::path path)
|
||||
{
|
||||
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 HICON get_explorer_icon_handle(std::filesystem::path path)
|
||||
{
|
||||
SHFILEINFO shell_file_info = { 0 };
|
||||
const std::wstring filepath = path.wstring();
|
||||
DWORD_PTR result = SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICON);
|
||||
if (shell_file_info.hIcon)
|
||||
{
|
||||
return shell_file_info.hIcon;
|
||||
}
|
||||
|
||||
WCHAR icon_resource_specifier[MAX_PATH] = { 0 };
|
||||
DWORD buffer_length = MAX_PATH;
|
||||
const std::wstring extension = path.extension().wstring();
|
||||
const HRESULT hr = AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN,
|
||||
ASSOCSTR_DEFAULTICON,
|
||||
extension.c_str(),
|
||||
NULL,
|
||||
icon_resource_specifier,
|
||||
&buffer_length);
|
||||
const std::wstring icon_resource = icon_resource_specifier;
|
||||
|
||||
const auto icon_x = GetSystemMetrics(SM_CXSMICON);
|
||||
const auto icon_y = GetSystemMetrics(SM_CYSMICON);
|
||||
HICON hIcon = static_cast<HICON>(LoadImage(NULL, icon_resource.c_str(), IMAGE_ICON, icon_x, icon_y, LR_LOADFROMFILE));
|
||||
return hIcon;
|
||||
}
|
||||
|
||||
inline bool wstring_same_when_comparing_ignore_case(std::wstring stringA, std::wstring stringB)
|
||||
{
|
||||
transform(stringA.begin(), stringA.end(), stringA.begin(), towupper);
|
||||
@@ -198,21 +147,21 @@ namespace newplus::utilities
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void explorer_enter_rename_mode(const std::filesystem::path target_fullpath_of_new_instance)
|
||||
inline bool explorer_enter_rename_mode_and_reposition(const std::filesystem::path target_fullpath_of_new_instance, const POINT mouse_position_at_time_of_invoke, const bool enter_rename_mode = true)
|
||||
{
|
||||
const std::filesystem::path path_without_new_file_or_dir = target_fullpath_of_new_instance.parent_path();
|
||||
const std::filesystem::path new_file_or_dir_without_path = target_fullpath_of_new_instance.filename();
|
||||
|
||||
ComPtr<IShellWindows> shell_windows;
|
||||
CComPtr<IShellWindows> shell_windows;
|
||||
|
||||
HRESULT hr;
|
||||
if (FAILED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_PPV_ARGS(&shell_windows))))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
long window_handle;
|
||||
ComPtr<IDispatch> shell_window;
|
||||
long desktop_window_handle = 0;
|
||||
CComPtr<IDispatch> shell_window;
|
||||
const bool object_created_on_desktop = is_desktop_folder(path_without_new_file_or_dir.c_str());
|
||||
if (object_created_on_desktop)
|
||||
{
|
||||
@@ -220,9 +169,9 @@ namespace newplus::utilities
|
||||
VARIANT empty_yet_needed_incl_init;
|
||||
VariantInit(&empty_yet_needed_incl_init);
|
||||
|
||||
if (FAILED(shell_windows->FindWindowSW(&empty_yet_needed_incl_init, &empty_yet_needed_incl_init, SWC_DESKTOP, &window_handle, SWFO_NEEDDISPATCH, &shell_window)))
|
||||
if (FAILED(shell_windows->FindWindowSW(&empty_yet_needed_incl_init, &empty_yet_needed_incl_init, SWC_DESKTOP, &desktop_window_handle, SWFO_NEEDDISPATCH, &shell_window)))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -232,7 +181,7 @@ namespace newplus::utilities
|
||||
|
||||
for (long i = 0; i < count_of_shell_windows; ++i)
|
||||
{
|
||||
ComPtr<IWebBrowserApp> web_browser_app;
|
||||
CComPtr<IWebBrowserApp> web_browser_app;
|
||||
VARIANT v;
|
||||
VariantInit(&v);
|
||||
V_VT(&v) = VT_I4;
|
||||
@@ -240,14 +189,14 @@ namespace newplus::utilities
|
||||
hr = shell_windows->Item(v, &shell_window);
|
||||
if (SUCCEEDED(hr) && shell_window)
|
||||
{
|
||||
hr = shell_window.As(&web_browser_app);
|
||||
hr = shell_window->QueryInterface(IID_PPV_ARGS(&web_browser_app));
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
BSTR folder_view_location;
|
||||
hr = web_browser_app->get_LocationURL(&folder_view_location);
|
||||
if (SUCCEEDED(hr) && folder_view_location)
|
||||
{
|
||||
wchar_t path[MAX_PATH];
|
||||
wchar_t path[MAX_PATH * 2];
|
||||
DWORD pathLength = ARRAYSIZE(path);
|
||||
hr = PathCreateFromUrl(folder_view_location, path, &pathLength, 0);
|
||||
SysFreeString(folder_view_location);
|
||||
@@ -264,17 +213,36 @@ namespace newplus::utilities
|
||||
|
||||
if (!shell_window)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ComPtr<IServiceProvider> service_provider;
|
||||
shell_window.As(&service_provider);
|
||||
ComPtr<IShellBrowser> shell_browser;
|
||||
service_provider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shell_browser));
|
||||
ComPtr<IShellView> shell_view;
|
||||
shell_browser->QueryActiveShellView(&shell_view);
|
||||
ComPtr<IFolderView> folder_view;
|
||||
shell_view.As(&folder_view);
|
||||
CComPtr<IServiceProvider> service_provider;
|
||||
if (FAILED(shell_window->QueryInterface(IID_PPV_ARGS(&service_provider))) || service_provider == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CComPtr<IShellBrowser> shell_browser;
|
||||
if (FAILED(service_provider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shell_browser))) || shell_browser == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CComPtr<IShellView> shell_view;
|
||||
if (FAILED(shell_browser->QueryActiveShellView(&shell_view)) || shell_view == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CComPtr<IFolderView> folder_view;
|
||||
if (FAILED(shell_view->QueryInterface(IID_PPV_ARGS(&folder_view))) || folder_view == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The folder backing the active view; used to resolve child PIDLs to their names below.
|
||||
CComPtr<IShellFolder> view_shell_folder;
|
||||
folder_view->GetFolder(IID_PPV_ARGS(&view_shell_folder));
|
||||
|
||||
// Find the newly created object (file or folder)
|
||||
// And put object into edit mode (SVSI_EDIT) and if desktop also reposition
|
||||
@@ -283,40 +251,98 @@ namespace newplus::utilities
|
||||
folder_view->ItemCount(SVGIO_ALLVIEW, &number_of_objects_in_view);
|
||||
for (int i = 0; i < number_of_objects_in_view && !done; ++i)
|
||||
{
|
||||
std::wstring path_of_item(MAX_PATH, 0);
|
||||
LPITEMIDLIST shell_item_ids;
|
||||
PITEMID_CHILD shell_item_id = nullptr;
|
||||
|
||||
folder_view->Item(i, &shell_item_ids);
|
||||
SHGetPathFromIDList(shell_item_ids, &path_of_item[0]);
|
||||
|
||||
const std::wstring current_filename = std::filesystem::path(path_of_item.c_str()).filename();
|
||||
|
||||
if (utilities::wstring_same_when_comparing_ignore_case(new_file_or_dir_without_path, current_filename))
|
||||
if (FAILED(folder_view->Item(i, &shell_item_id)) || shell_item_id == nullptr)
|
||||
{
|
||||
const DWORD common_select_flags = SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
wchar_t path_buffer[MAX_PATH * 2] = { 0 };
|
||||
|
||||
// IFolderView::Item returns a child (folder-relative) PIDL. SHGetPathFromIDList expects an
|
||||
// absolute PIDL, so ask the parent IShellFolder for the item's in-folder parsing name instead
|
||||
// of reinterpret-casting the child PIDL to absolute (which fails outside the desktop and
|
||||
// would leave the new item never matched, so rename/reposition would silently time out).
|
||||
if (view_shell_folder != nullptr)
|
||||
{
|
||||
STRRET str_ret;
|
||||
if (SUCCEEDED(view_shell_folder->GetDisplayNameOf(shell_item_id, SHGDN_INFOLDER | SHGDN_FORPARSING, &str_ret)))
|
||||
{
|
||||
StrRetToBufW(&str_ret, shell_item_id, path_buffer, ARRAYSIZE(path_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
if (path_buffer[0] == L'\0')
|
||||
{
|
||||
CoTaskMemFree(shell_item_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::wstring current_filename = std::filesystem::path(path_buffer).filename();
|
||||
|
||||
if (newplus::utilities::wstring_same_when_comparing_ignore_case(new_file_or_dir_without_path, current_filename))
|
||||
{
|
||||
const DWORD common_select_flags = (enter_rename_mode ? SVSI_EDIT : 0) | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED;
|
||||
|
||||
if (object_created_on_desktop)
|
||||
{
|
||||
// Newly created object is on the desktop -- reposition under mouse and enter rename mode
|
||||
LPCITEMIDLIST shell_item_to_select_and_position[] = { shell_item_ids };
|
||||
POINT mouse_position;
|
||||
GetCursorPos(&mouse_position);
|
||||
mouse_position.x -= GetSystemMetrics(SM_CXMENUSIZE);
|
||||
mouse_position.x = (std::max)(mouse_position.x, 20L);
|
||||
mouse_position.y -= GetSystemMetrics(SM_CXMENUSIZE)/2;
|
||||
mouse_position.y = (std::max)(mouse_position.y, 20L);
|
||||
POINT position[] = { mouse_position };
|
||||
folder_view->SelectAndPositionItems(1, shell_item_to_select_and_position, position, common_select_flags | SVSI_POSITIONITEM);
|
||||
// All coordinate work is done under per-monitor-DPI-aware context so that
|
||||
// GetCursorPos, MonitorFromPoint, ScreenToClient, and GetDpiForMonitor all
|
||||
// operate in physical screen pixels — correctly handling mixed-DPI setups
|
||||
// where the invoke monitor differs from the primary monitor.
|
||||
POINT screen_point;
|
||||
const DPI_AWARENESS_CONTEXT prev_ctx = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
if (mouse_position_at_time_of_invoke.x != -1)
|
||||
{
|
||||
screen_point = mouse_position_at_time_of_invoke;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GetCursorPos(&screen_point))
|
||||
screen_point = { 100, 100 };
|
||||
}
|
||||
|
||||
// Resolve effective DPI for the monitor the right-click was on.
|
||||
UINT invoke_dpi_x = 96;
|
||||
const HMONITOR h_monitor = MonitorFromPoint(screen_point, MONITOR_DEFAULTTONEAREST);
|
||||
if (h_monitor)
|
||||
{
|
||||
UINT invoke_dpi_y = 0;
|
||||
GetDpiForMonitor(h_monitor, MDT_EFFECTIVE_DPI, &invoke_dpi_x, &invoke_dpi_y);
|
||||
}
|
||||
|
||||
// IFolderView expects client coordinates for its view window.
|
||||
HWND folder_view_window = nullptr;
|
||||
if (SUCCEEDED(shell_view->GetWindow(&folder_view_window)) && folder_view_window != nullptr)
|
||||
{
|
||||
::ScreenToClient(folder_view_window, &screen_point);
|
||||
}
|
||||
|
||||
if (prev_ctx != nullptr)
|
||||
{
|
||||
SetThreadDpiAwarenessContext(prev_ctx);
|
||||
}
|
||||
|
||||
// Keep icon clear of the screen edge: ~30 logical pixels scaled to the invoke monitor's DPI.
|
||||
const LONG min_margin = ::MulDiv(30, static_cast<int>(invoke_dpi_x), 96);
|
||||
screen_point.x = std::max<LONG>(screen_point.x, min_margin);
|
||||
screen_point.y = std::max<LONG>(screen_point.y, min_margin);
|
||||
|
||||
POINT position[] = { screen_point };
|
||||
PCUITEMID_CHILD shell_item_to_select_and_position[] = { shell_item_id };
|
||||
done = SUCCEEDED(folder_view->SelectAndPositionItems(1, shell_item_to_select_and_position, position, common_select_flags | SVSI_POSITIONITEM));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enter rename mode
|
||||
folder_view->SelectItem(i, common_select_flags);
|
||||
done = SUCCEEDED(folder_view->SelectItem(i, common_select_flags));
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
CoTaskMemFree(shell_item_ids);
|
||||
CoTaskMemFree(shell_item_id);
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
inline void update_last_write_time(const std::filesystem::path path)
|
||||
@@ -334,7 +360,7 @@ namespace newplus::utilities
|
||||
}
|
||||
}
|
||||
|
||||
inline HRESULT copy_template(const template_item* template_entry, const ComPtr<IUnknown> site_of_folder)
|
||||
inline HRESULT copy_template(const template_item* template_entry, const ComPtr<IUnknown> site_of_folder, const POINT mouse_position_at_invoke)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
@@ -396,7 +422,7 @@ namespace newplus::utilities
|
||||
template_entry->refresh_target(target_final_fullpath);
|
||||
|
||||
// Enter rename mode
|
||||
template_entry->enter_rename_mode(target_final_fullpath);
|
||||
template_entry->enter_rename_mode(target_final_fullpath, mouse_position_at_invoke);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "pch.h"
|
||||
// pch.h first
|
||||
#include "newplus_icon_utilities.h"
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
namespace newplus::icon_utilities
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
std::wstring query_default_icon(const wchar_t* association)
|
||||
{
|
||||
DWORD buffer_length = 0;
|
||||
const HRESULT size_result = AssocQueryString(
|
||||
ASSOCF_INIT_IGNOREUNKNOWN,
|
||||
ASSOCSTR_DEFAULTICON,
|
||||
association,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&buffer_length);
|
||||
if (size_result != S_FALSE || buffer_length == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring icon_resource(buffer_length, L'\0');
|
||||
const HRESULT query_result = AssocQueryString(
|
||||
ASSOCF_INIT_IGNOREUNKNOWN,
|
||||
ASSOCSTR_DEFAULTICON,
|
||||
association,
|
||||
nullptr,
|
||||
icon_resource.data(),
|
||||
&buffer_length);
|
||||
if (FAILED(query_result))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
icon_resource.resize(wcsnlen_s(icon_resource.c_str(), icon_resource.size()));
|
||||
return icon_resource;
|
||||
}
|
||||
|
||||
HICON extract_default_icon(const wchar_t* association)
|
||||
{
|
||||
const std::wstring icon_resource = query_default_icon(association);
|
||||
if (icon_resource.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const DWORD expanded_length = ExpandEnvironmentStrings(icon_resource.c_str(), nullptr, 0);
|
||||
if (expanded_length == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::wstring icon_path(expanded_length, L'\0');
|
||||
const DWORD expand_result = ExpandEnvironmentStrings(icon_resource.c_str(), icon_path.data(), expanded_length);
|
||||
if (expand_result == 0 || expand_result > expanded_length)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int icon_index = PathParseIconLocation(icon_path.data());
|
||||
PathUnquoteSpaces(icon_path.data());
|
||||
icon_path.resize(wcsnlen_s(icon_path.c_str(), icon_path.size()));
|
||||
|
||||
HICON icon = nullptr;
|
||||
const UINT icon_size = static_cast<UINT>(GetSystemMetrics(SM_CXSMICON));
|
||||
if (FAILED(SHDefExtractIcon(icon_path.c_str(), icon_index, 0, nullptr, &icon, MAKELONG(0, icon_size))))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring get_explorer_icon(const std::filesystem::path& path, bool is_directory)
|
||||
{
|
||||
// Cache by full path — directories are excluded because their icon can change via desktop.ini
|
||||
// without a DLL reload. Extension is intentionally NOT used as the key: icons for types like .exe
|
||||
// and .lnk are per-file (the icon comes from the binary/shortcut itself), so an extension key would
|
||||
// return the first-seen file's icon for every template of that type.
|
||||
if (!is_directory)
|
||||
{
|
||||
// Explorer can call into the shell extension on multiple threads concurrently, so the
|
||||
// process-wide cache must be synchronized to avoid a data race on the unordered_map.
|
||||
// The lock is only ever held around the map lookup/insert and never while calling into the
|
||||
// shell (SHGetFileInfo/AssocQueryString), because those calls can be reentrant and would
|
||||
// otherwise risk deadlocking this non-recursive mutex on the same thread.
|
||||
static std::mutex s_icon_cache_mutex;
|
||||
static std::unordered_map<std::wstring, std::wstring> s_icon_cache;
|
||||
const std::wstring key = path.wstring();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> cache_lock(s_icon_cache_mutex);
|
||||
const auto it = s_icon_cache.find(key);
|
||||
if (it != s_icon_cache.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::wstring icon_resource;
|
||||
|
||||
SHFILEINFO shell_file_info = { 0 };
|
||||
SHGetFileInfo(key.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICONLOCATION);
|
||||
const std::wstring icon_path = shell_file_info.szDisplayName;
|
||||
if (!icon_path.empty())
|
||||
{
|
||||
icon_resource = icon_path + L"," + std::to_wstring(shell_file_info.iIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::wstring extension = path.extension().wstring();
|
||||
icon_resource = query_default_icon(extension.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> cache_lock(s_icon_cache_mutex);
|
||||
// Only cache successful (non-empty) lookups so a transient SHGetFileInfo/AssocQueryString
|
||||
// failure cannot permanently poison the cache with an empty icon for that path.
|
||||
if (!icon_resource.empty())
|
||||
{
|
||||
s_icon_cache[key] = icon_resource;
|
||||
}
|
||||
}
|
||||
|
||||
return icon_resource;
|
||||
}
|
||||
|
||||
// Directories: always read fresh from the shell
|
||||
SHFILEINFO shell_file_info = { 0 };
|
||||
const std::wstring filepath = path.wstring();
|
||||
SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICONLOCATION);
|
||||
const std::wstring icon_path = shell_file_info.szDisplayName;
|
||||
if (!icon_path.empty())
|
||||
{
|
||||
return icon_path + L"," + std::to_wstring(shell_file_info.iIcon);
|
||||
}
|
||||
|
||||
return query_default_icon(L"");
|
||||
}
|
||||
|
||||
HICON get_explorer_icon_handle(const std::filesystem::path& path)
|
||||
{
|
||||
SHFILEINFO shell_file_info = { 0 };
|
||||
const std::wstring filepath = path.wstring();
|
||||
SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICON);
|
||||
if (shell_file_info.hIcon)
|
||||
{
|
||||
return shell_file_info.hIcon;
|
||||
}
|
||||
|
||||
const std::wstring extension = path.extension().wstring();
|
||||
return extract_default_icon(extension.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
namespace newplus::icon_utilities
|
||||
{
|
||||
// is_directory=true skips the per-file icon cache (directory icons can change via desktop.ini)
|
||||
std::wstring get_explorer_icon(const std::filesystem::path& path, bool is_directory = false);
|
||||
HICON get_explorer_icon_handle(const std::filesystem::path& path);
|
||||
}
|
||||
@@ -71,7 +71,19 @@ IFACEMETHODIMP shell_context_menu::EnumSubCommands(_COM_Outptr_ IEnumExplorerCom
|
||||
{
|
||||
try
|
||||
{
|
||||
auto e = Make<shell_context_sub_menu>(site_of_folder);
|
||||
// Get the cursor position as early as possible to get as close to the point where the context menu was
|
||||
// invoked for Desktop icon placement.
|
||||
// Capture in per-monitor-DPI-aware context so the stored position is always in physical screen pixels.
|
||||
POINT cursor_position = { -1, -1 };
|
||||
const DPI_AWARENESS_CONTEXT prev_dpi_ctx = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
if (!GetCursorPos(&cursor_position))
|
||||
{
|
||||
cursor_position = { -1, -1 };
|
||||
}
|
||||
|
||||
SetThreadDpiAwarenessContext(prev_dpi_ctx);
|
||||
|
||||
auto e = Make<shell_context_sub_menu>(site_of_folder, cursor_position);
|
||||
return e->QueryInterface(IID_PPV_ARGS(returned_enum_commands));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
// // Sub context menu command enumerator
|
||||
shell_context_sub_menu::shell_context_sub_menu(const ComPtr<IUnknown> site_of_folder)
|
||||
shell_context_sub_menu::shell_context_sub_menu(const ComPtr<IUnknown> site_of_folder, const POINT mouse_position_at_time_of_context_menu)
|
||||
{
|
||||
this->site_of_folder = site_of_folder;
|
||||
|
||||
// Capture mouse position now (at menu-open time) for more accurate desktop icon placement later
|
||||
mouse_position_at_time_of_invoke = mouse_position_at_time_of_context_menu;
|
||||
|
||||
// Determine the New+ Template folder location
|
||||
const std::filesystem::path root = utilities::get_new_template_folder_location();
|
||||
|
||||
@@ -25,7 +28,7 @@ shell_context_sub_menu::shell_context_sub_menu(const ComPtr<IUnknown> site_of_fo
|
||||
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));
|
||||
explorer_menu_item_commands.push_back(Make<shell_context_sub_menu_item>(templates->get_template_item(i), site_of_folder, mouse_position_at_time_of_invoke));
|
||||
}
|
||||
|
||||
// Add separator to context menu
|
||||
|
||||
@@ -13,7 +13,7 @@ using namespace newplus;
|
||||
class shell_context_sub_menu final : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IEnumExplorerCommand>
|
||||
{
|
||||
public:
|
||||
shell_context_sub_menu(const ComPtr<IUnknown> site_of_folder);
|
||||
shell_context_sub_menu(const ComPtr<IUnknown> site_of_folder, const POINT mouse_position_at_time_of_context_menu);
|
||||
|
||||
// IEnumExplorerCommand
|
||||
IFACEMETHODIMP Next(ULONG celt, __out_ecount_part(celt, *pceltFetched) IExplorerCommand** apUICommand, __out_opt ULONG* pceltFetched);
|
||||
@@ -26,4 +26,5 @@ protected:
|
||||
std::vector<ComPtr<IExplorerCommand>>::const_iterator current_command;
|
||||
template_folder* templates;
|
||||
ComPtr<IUnknown> site_of_folder;
|
||||
POINT mouse_position_at_time_of_invoke{ -1, -1 };
|
||||
};
|
||||
|
||||
@@ -8,14 +8,13 @@ using namespace Microsoft::WRL;
|
||||
|
||||
// Sub context menu containing the actual list of templates
|
||||
shell_context_sub_menu_item::shell_context_sub_menu_item()
|
||||
: template_entry(nullptr), site_of_folder(nullptr), mouse_position_at_time_of_invoke{ -1, -1 }
|
||||
{
|
||||
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)
|
||||
shell_context_sub_menu_item::shell_context_sub_menu_item(const template_item* template_entry, const ComPtr<IUnknown> site_of_folder, const POINT mouse_position_at_invoke)
|
||||
: template_entry(template_entry), site_of_folder(site_of_folder), mouse_position_at_time_of_invoke(mouse_position_at_invoke)
|
||||
{
|
||||
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)
|
||||
@@ -64,7 +63,7 @@ IFACEMETHODIMP shell_context_sub_menu_item::GetState(_In_opt_ IShellItemArray* s
|
||||
|
||||
IFACEMETHODIMP shell_context_sub_menu_item::Invoke(_In_opt_ IShellItemArray*, _In_opt_ IBindCtx*) noexcept
|
||||
{
|
||||
return newplus::utilities::copy_template(template_entry, site_of_folder);
|
||||
return newplus::utilities::copy_template(template_entry, site_of_folder, mouse_position_at_time_of_invoke);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP shell_context_sub_menu_item::GetFlags(_Out_ EXPCMDFLAGS* returned_flags)
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace newplus;
|
||||
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);
|
||||
shell_context_sub_menu_item(const template_item* template_entry, const ComPtr<IUnknown> site_of_folder, const POINT mouse_position_at_invoke);
|
||||
|
||||
// IExplorerCommand
|
||||
IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* title);
|
||||
@@ -35,6 +35,7 @@ protected:
|
||||
shell_context_sub_menu_item();
|
||||
const template_item* template_entry;
|
||||
ComPtr<IUnknown> site_of_folder;
|
||||
POINT mouse_position_at_time_of_invoke;
|
||||
};
|
||||
|
||||
// Sub-context-menu separator between the list of templates menu-items and "Open templates" menu-item
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
#include "pch.h"
|
||||
#include "template_item.h"
|
||||
#include <shellapi.h>
|
||||
#include "newplus_icon_utilities.h"
|
||||
#include "new_utilities.h"
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <shlobj_core.h>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
using namespace newplus;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct rename_worker_context
|
||||
{
|
||||
std::filesystem::path target_fullpath;
|
||||
POINT mouse_position_at_invoke;
|
||||
HMODULE module_reference;
|
||||
};
|
||||
}
|
||||
|
||||
template_item::template_item(const std::filesystem::path entry)
|
||||
{
|
||||
path = entry;
|
||||
@@ -147,12 +157,16 @@ std::wstring template_item::remove_starting_digits_from_filename(std::wstring fi
|
||||
|
||||
std::wstring template_item::get_explorer_icon() const
|
||||
{
|
||||
return utilities::get_explorer_icon(path);
|
||||
// Use the non-throwing filesystem query: this runs while Explorer builds the context menu, so a
|
||||
// throwing directory check here could take down the shell extension. On error, treat as a file.
|
||||
std::error_code ec;
|
||||
const bool is_dir = std::filesystem::is_directory(path, ec) && !ec;
|
||||
return icon_utilities::get_explorer_icon(path, is_dir);
|
||||
}
|
||||
|
||||
HICON template_item::get_explorer_icon_handle() const
|
||||
{
|
||||
return utilities::get_explorer_icon_handle(path);
|
||||
return icon_utilities::get_explorer_icon_handle(path);
|
||||
}
|
||||
|
||||
std::filesystem::path template_item::copy_object_to(const HWND window_handle, const std::filesystem::path destination) const
|
||||
@@ -188,18 +202,103 @@ void template_item::refresh_target(const std::filesystem::path target_final_full
|
||||
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH | SHCNF_FLUSH, target_final_fullpath.wstring().c_str(), NULL);
|
||||
}
|
||||
|
||||
void template_item::enter_rename_mode(const std::filesystem::path target_fullpath) const
|
||||
void template_item::enter_rename_mode(const std::filesystem::path target_fullpath, const POINT mouse_position_at_invoke) const
|
||||
{
|
||||
std::thread thread_for_renaming_workaround(rename_on_other_thread_workaround, target_fullpath);
|
||||
thread_for_renaming_workaround.detach();
|
||||
HMODULE module_reference = nullptr;
|
||||
if (!GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
reinterpret_cast<LPCWSTR>(&module_instance_handle),
|
||||
&module_reference))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<rename_worker_context> context;
|
||||
try
|
||||
{
|
||||
context = std::make_unique<rename_worker_context>(
|
||||
target_fullpath,
|
||||
mouse_position_at_invoke,
|
||||
module_reference);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
FreeLibrary(module_reference);
|
||||
return;
|
||||
}
|
||||
|
||||
active_rename_workers.fetch_add(1);
|
||||
const HANDLE thread = CreateThread(nullptr, 0, rename_worker_thread_proc, context.get(), 0, nullptr);
|
||||
if (thread == nullptr)
|
||||
{
|
||||
active_rename_workers.fetch_sub(1);
|
||||
FreeLibrary(module_reference);
|
||||
return;
|
||||
}
|
||||
|
||||
context.release();
|
||||
CloseHandle(thread);
|
||||
}
|
||||
|
||||
void template_item::rename_on_other_thread_workaround(const std::filesystem::path target_fullpath)
|
||||
DWORD WINAPI template_item::rename_worker_thread_proc(void* parameter)
|
||||
{
|
||||
// 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.
|
||||
const std::chrono::milliseconds approx_wait_for_icon_redraw_not_needed{ 50 };
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(approx_wait_for_icon_redraw_not_needed));
|
||||
std::unique_ptr<rename_worker_context> context(static_cast<rename_worker_context*>(parameter));
|
||||
const HMODULE module_reference = context->module_reference;
|
||||
|
||||
newplus::utilities::explorer_enter_rename_mode(target_fullpath);
|
||||
rename_on_other_thread_workaround(context->target_fullpath, context->mouse_position_at_invoke);
|
||||
context.reset();
|
||||
active_rename_workers.fetch_sub(1);
|
||||
FreeLibraryAndExitThread(module_reference, 0);
|
||||
}
|
||||
|
||||
void template_item::rename_on_other_thread_workaround(const std::filesystem::path& target_fullpath, const POINT mouse_position_at_invoke)
|
||||
{
|
||||
struct worker_cleanup
|
||||
{
|
||||
bool com_initialized = false;
|
||||
|
||||
~worker_cleanup()
|
||||
{
|
||||
if (com_initialized)
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
} cleanup;
|
||||
|
||||
const HRESULT com_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (FAILED(com_result))
|
||||
{
|
||||
return;
|
||||
}
|
||||
cleanup.com_initialized = true;
|
||||
|
||||
// Have been unable to have Windows Explorer Shell enter rename mode from the main thread.
|
||||
// Poll until the item appears in the folder view so icon is positioned and rename mode is entered
|
||||
// without a jump in the positioning
|
||||
constexpr std::chrono::milliseconds initial_poll_interval{ 30 };
|
||||
constexpr std::chrono::milliseconds maximum_poll_interval{ 240 };
|
||||
constexpr std::chrono::milliseconds poll_timeout{ 2000 };
|
||||
const auto deadline = std::chrono::steady_clock::now() + poll_timeout;
|
||||
auto poll_interval = initial_poll_interval;
|
||||
|
||||
try
|
||||
{
|
||||
while (std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
if (newplus::utilities::explorer_enter_rename_mode_and_reposition(target_fullpath, mouse_position_at_invoke))
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(poll_interval);
|
||||
poll_interval = std::min(poll_interval * 2, maximum_poll_interval);
|
||||
}
|
||||
|
||||
// Final attempt: the item may have appeared during the last sleep interval (after the previous
|
||||
// attempt but before the deadline), so try once more so a just-in-time item still enters rename mode.
|
||||
newplus::utilities::explorer_enter_rename_mode_and_reposition(target_fullpath, mouse_position_at_invoke);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,13 @@ namespace newplus
|
||||
|
||||
void refresh_target(const std::filesystem::path target_final_fullpath) const;
|
||||
|
||||
void enter_rename_mode(const std::filesystem::path target_fullpath) const;
|
||||
void enter_rename_mode(const std::filesystem::path target_fullpath, const POINT mouse_position_at_invoke) const;
|
||||
|
||||
std::filesystem::path path;
|
||||
|
||||
private:
|
||||
static void rename_on_other_thread_workaround(const std::filesystem::path target_fullpath);
|
||||
static DWORD WINAPI rename_worker_thread_proc(void* parameter);
|
||||
static void rename_on_other_thread_workaround(const std::filesystem::path& target_fullpath, const POINT mouse_position_at_invoke);
|
||||
|
||||
std::wstring remove_starting_digits_from_filename(std::wstring filename) const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user