new_utilities.h
1 #pragma once 2 3 #include "pch.h" 4 5 #include <common/utils/process_path.h> 6 #include <common/utils/package.h> 7 8 #include "constants.h" 9 #include "settings.h" 10 #include "template_item.h" 11 #include "trace.h" 12 #include "helpers_variables.h" 13 14 #pragma comment(lib, "Shlwapi.lib") 15 16 using namespace newplus; 17 18 namespace newplus::utilities 19 { 20 size_t get_saved_number_of_templates(); 21 void set_saved_number_of_templates(size_t templates); 22 23 inline std::wstring get_explorer_icon(std::filesystem::path path) 24 { 25 SHFILEINFO shell_file_info = { 0 }; 26 const std::wstring filepath = path.wstring(); 27 DWORD_PTR result = SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICONLOCATION); 28 std::wstring icon_path = shell_file_info.szDisplayName; 29 if (icon_path != L"") 30 { 31 const int icon_index = shell_file_info.iIcon; 32 std::wstring icon_resource = icon_path + std::wstring(L",") + std::to_wstring(icon_index); 33 return icon_resource; 34 } 35 36 WCHAR icon_resource_specifier[MAX_PATH] = { 0 }; 37 DWORD buffer_length = MAX_PATH; 38 const std::wstring extension = path.extension().wstring(); 39 const HRESULT hr = AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN, 40 ASSOCSTR_DEFAULTICON, 41 extension.c_str(), 42 NULL, 43 icon_resource_specifier, 44 &buffer_length); 45 const std::wstring icon_resource = icon_resource_specifier; 46 return icon_resource; 47 } 48 49 inline HICON get_explorer_icon_handle(std::filesystem::path path) 50 { 51 SHFILEINFO shell_file_info = { 0 }; 52 const std::wstring filepath = path.wstring(); 53 DWORD_PTR result = SHGetFileInfo(filepath.c_str(), 0, &shell_file_info, sizeof(shell_file_info), SHGFI_ICON); 54 if (shell_file_info.hIcon) 55 { 56 return shell_file_info.hIcon; 57 } 58 59 WCHAR icon_resource_specifier[MAX_PATH] = { 0 }; 60 DWORD buffer_length = MAX_PATH; 61 const std::wstring extension = path.extension().wstring(); 62 const HRESULT hr = AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN, 63 ASSOCSTR_DEFAULTICON, 64 extension.c_str(), 65 NULL, 66 icon_resource_specifier, 67 &buffer_length); 68 const std::wstring icon_resource = icon_resource_specifier; 69 70 const auto icon_x = GetSystemMetrics(SM_CXSMICON); 71 const auto icon_y = GetSystemMetrics(SM_CYSMICON); 72 HICON hIcon = static_cast<HICON>(LoadImage(NULL, icon_resource.c_str(), IMAGE_ICON, icon_x, icon_y, LR_LOADFROMFILE)); 73 return hIcon; 74 } 75 76 inline bool wstring_same_when_comparing_ignore_case(std::wstring stringA, std::wstring stringB) 77 { 78 transform(stringA.begin(), stringA.end(), stringA.begin(), towupper); 79 transform(stringB.begin(), stringB.end(), stringB.begin(), towupper); 80 81 return (stringA == stringB); 82 } 83 84 inline std::wstring get_new_template_folder_location() 85 { 86 return NewSettingsInstance().GetTemplateLocation(); 87 } 88 89 inline bool get_newplus_setting_hide_extension() 90 { 91 return NewSettingsInstance().GetHideFileExtension(); 92 } 93 94 inline bool get_newplus_setting_hide_starting_digits() 95 { 96 return NewSettingsInstance().GetHideStartingDigits(); 97 } 98 99 inline bool get_newplus_setting_resolve_variables() 100 { 101 return NewSettingsInstance().GetReplaceVariables(); 102 } 103 104 inline void create_folder_if_not_exist(const std::filesystem::path path) 105 { 106 std::filesystem::create_directory(path); 107 } 108 109 inline std::wstring get_new_icon_resource_filepath(const HMODULE module_instance_handle, const Theme theme) 110 { 111 auto iconResourcePath = get_module_folderpath(module_instance_handle); 112 113 if (theme == Theme::Dark) 114 { 115 iconResourcePath += constants::non_localizable::new_icon_dark_resource_relative_path; 116 } 117 else 118 { 119 // Defaulting to the Light icon 120 iconResourcePath += constants::non_localizable::new_icon_light_resource_relative_path; 121 } 122 123 return iconResourcePath; 124 } 125 126 inline std::wstring get_open_templates_icon_resource_filepath(const HMODULE module_instance_handle, const Theme theme) 127 { 128 auto iconResourcePath = get_module_folderpath(module_instance_handle); 129 130 if (theme == Theme::Dark) 131 { 132 iconResourcePath += constants::non_localizable::open_templates_icon_dark_resource_relative_path; 133 } 134 else 135 { 136 // Defaulting to the Light icon 137 iconResourcePath += constants::non_localizable::open_templates_icon_light_resource_relative_path; 138 } 139 140 return iconResourcePath; 141 } 142 143 inline void init_logger() 144 { 145 LoggerHelpers::init_logger( 146 constants::non_localizable::powertoy_name, 147 constants::non_localizable::module_name, 148 LogSettings::newLoggerName); 149 } 150 151 inline void register_msix_package() 152 { 153 if (package::IsWin11OrGreater()) 154 { 155 static const auto new_dll_path = get_module_folderpath(module_instance_handle); 156 auto new_package_uri = new_dll_path + L"\\" + constants::non_localizable::msix_package_name; 157 158 if (!package::IsPackageRegisteredWithPowerToysVersion(constants::non_localizable::context_menu_package_name)) 159 { 160 package::RegisterSparsePackage(new_dll_path, new_package_uri); 161 } 162 } 163 } 164 165 inline std::wstring get_path_from_unknown_site(const ComPtr<IUnknown> site_of_folder) 166 { 167 ComPtr<IServiceProvider> service_provider; 168 site_of_folder->QueryInterface(IID_PPV_ARGS(&service_provider)); 169 ComPtr<IFolderView> folder_view; 170 service_provider->QueryService(__uuidof(IFolderView), IID_PPV_ARGS(&folder_view)); 171 ComPtr<IShellFolder> shell_folder; 172 folder_view->GetFolder(IID_PPV_ARGS(&shell_folder)); 173 STRRET strings_returned; 174 shell_folder->GetDisplayNameOf(0, SHGDN_FORPARSING, &strings_returned); 175 LPWSTR path; 176 StrRetToStr(&strings_returned, NULL, &path); 177 return path; 178 } 179 180 inline std::wstring get_path_from_folder_view(const ComPtr<IFolderView> folder_view) 181 { 182 ComPtr<IShellFolder> shell_folder; 183 folder_view->GetFolder(IID_PPV_ARGS(&shell_folder)); 184 STRRET strings_returned; 185 shell_folder->GetDisplayNameOf(0, SHGDN_FORPARSING, &strings_returned); 186 LPWSTR path; 187 StrRetToStr(&strings_returned, NULL, &path); 188 return path; 189 } 190 191 inline bool is_desktop_folder(const std::filesystem::path target_fullpath) 192 { 193 TCHAR desktop_path[MAX_PATH]; 194 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktop_path))) 195 { 196 return StrCmpIW(target_fullpath.c_str(), desktop_path) == 0; 197 } 198 return false; 199 } 200 201 inline void explorer_enter_rename_mode(const std::filesystem::path target_fullpath_of_new_instance) 202 { 203 const std::filesystem::path path_without_new_file_or_dir = target_fullpath_of_new_instance.parent_path(); 204 const std::filesystem::path new_file_or_dir_without_path = target_fullpath_of_new_instance.filename(); 205 206 ComPtr<IShellWindows> shell_windows; 207 208 HRESULT hr; 209 if (FAILED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_PPV_ARGS(&shell_windows)))) 210 { 211 return; 212 } 213 214 long window_handle; 215 ComPtr<IDispatch> shell_window; 216 const bool object_created_on_desktop = is_desktop_folder(path_without_new_file_or_dir.c_str()); 217 if (object_created_on_desktop) 218 { 219 // Special handling for desktop folder 220 VARIANT empty_yet_needed_incl_init; 221 VariantInit(&empty_yet_needed_incl_init); 222 223 if (FAILED(shell_windows->FindWindowSW(&empty_yet_needed_incl_init, &empty_yet_needed_incl_init, SWC_DESKTOP, &window_handle, SWFO_NEEDDISPATCH, &shell_window))) 224 { 225 return; 226 } 227 } 228 else 229 { 230 long count_of_shell_windows = 0; 231 shell_windows->get_Count(&count_of_shell_windows); 232 233 for (long i = 0; i < count_of_shell_windows; ++i) 234 { 235 ComPtr<IWebBrowserApp> web_browser_app; 236 VARIANT v; 237 VariantInit(&v); 238 V_VT(&v) = VT_I4; 239 V_I4(&v) = i; 240 hr = shell_windows->Item(v, &shell_window); 241 if (SUCCEEDED(hr) && shell_window) 242 { 243 hr = shell_window.As(&web_browser_app); 244 if (SUCCEEDED(hr)) 245 { 246 BSTR folder_view_location; 247 hr = web_browser_app->get_LocationURL(&folder_view_location); 248 if (SUCCEEDED(hr) && folder_view_location) 249 { 250 wchar_t path[MAX_PATH]; 251 DWORD pathLength = ARRAYSIZE(path); 252 hr = PathCreateFromUrl(folder_view_location, path, &pathLength, 0); 253 SysFreeString(folder_view_location); 254 if (SUCCEEDED(hr) && StrCmpIW(path_without_new_file_or_dir.c_str(), path) == 0) 255 { 256 break; 257 } 258 } 259 } 260 } 261 shell_window = nullptr; 262 } 263 } 264 265 if (!shell_window) 266 { 267 return; 268 } 269 270 ComPtr<IServiceProvider> service_provider; 271 shell_window.As(&service_provider); 272 ComPtr<IShellBrowser> shell_browser; 273 service_provider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shell_browser)); 274 ComPtr<IShellView> shell_view; 275 shell_browser->QueryActiveShellView(&shell_view); 276 ComPtr<IFolderView> folder_view; 277 shell_view.As(&folder_view); 278 279 // Find the newly created object (file or folder) 280 // And put object into edit mode (SVSI_EDIT) and if desktop also reposition 281 int number_of_objects_in_view = 0; 282 bool done = false; 283 folder_view->ItemCount(SVGIO_ALLVIEW, &number_of_objects_in_view); 284 for (int i = 0; i < number_of_objects_in_view && !done; ++i) 285 { 286 std::wstring path_of_item(MAX_PATH, 0); 287 LPITEMIDLIST shell_item_ids; 288 289 folder_view->Item(i, &shell_item_ids); 290 SHGetPathFromIDList(shell_item_ids, &path_of_item[0]); 291 292 const std::wstring current_filename = std::filesystem::path(path_of_item.c_str()).filename(); 293 294 if (utilities::wstring_same_when_comparing_ignore_case(new_file_or_dir_without_path, current_filename)) 295 { 296 const DWORD common_select_flags = SVSI_EDIT | SVSI_SELECT | SVSI_DESELECTOTHERS | SVSI_ENSUREVISIBLE | SVSI_FOCUSED; 297 298 if (object_created_on_desktop) 299 { 300 // Newly created object is on the desktop -- reposition under mouse and enter rename mode 301 LPCITEMIDLIST shell_item_to_select_and_position[] = { shell_item_ids }; 302 POINT mouse_position; 303 GetCursorPos(&mouse_position); 304 mouse_position.x -= GetSystemMetrics(SM_CXMENUSIZE); 305 mouse_position.x = (std::max)(mouse_position.x, 20L); 306 mouse_position.y -= GetSystemMetrics(SM_CXMENUSIZE)/2; 307 mouse_position.y = (std::max)(mouse_position.y, 20L); 308 POINT position[] = { mouse_position }; 309 folder_view->SelectAndPositionItems(1, shell_item_to_select_and_position, position, common_select_flags | SVSI_POSITIONITEM); 310 } 311 else 312 { 313 // Enter rename mode 314 folder_view->SelectItem(i, common_select_flags); 315 } 316 done = true; 317 } 318 CoTaskMemFree(shell_item_ids); 319 } 320 } 321 322 inline void update_last_write_time(const std::filesystem::path path) 323 { 324 const std::filesystem::file_time_type now = std::filesystem::file_time_type::clock::now(); 325 326 std::filesystem::last_write_time(path, now); 327 328 if (std::filesystem::is_directory(path)) 329 { 330 for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) 331 { 332 std::filesystem::last_write_time(entry.path(), now); 333 } 334 } 335 } 336 337 inline HRESULT copy_template(const template_item* template_entry, const ComPtr<IUnknown> site_of_folder) 338 { 339 HRESULT hr = S_OK; 340 341 try 342 { 343 Logger::info(L"Copying template"); 344 345 if (newplus::utilities::get_saved_number_of_templates() >= 0) 346 { 347 // Log that context menu was shown and with how many items 348 trace.UpdateState(true); 349 Trace::EventShowTemplateItems(newplus::utilities::get_saved_number_of_templates()); 350 trace.Flush(); 351 trace.UpdateState(false); 352 } 353 354 // Determine target path of where context menu was displayed 355 const auto target_path_name = utilities::get_path_from_unknown_site(site_of_folder); 356 357 // Determine initial filename 358 std::filesystem::path source_fullpath = template_entry->path; 359 std::filesystem::path target_fullpath = std::wstring(target_path_name); 360 361 // Get target name without starting digits as appropriate 362 const std::wstring target_name = template_entry->get_target_filename(!utilities::get_newplus_setting_hide_starting_digits()); 363 364 // Get initial resolved name 365 target_fullpath /= target_name; 366 367 // Expand variables in name of the target path 368 if (utilities::get_newplus_setting_resolve_variables()) 369 { 370 target_fullpath = helpers::variables::resolve_variables_in_path(target_fullpath); 371 } 372 373 // See if our target already exist, and if so then generate a unique name 374 target_fullpath = helpers::filesystem::make_unique_path_name(target_fullpath); 375 376 // Finally copy file/folder/subfolders 377 std::filesystem::path target_final_fullpath = template_entry->copy_object_to(GetActiveWindow(), target_fullpath); 378 379 // Resolve variables and rename files in newly copied folders and subfolders and files 380 if (utilities::get_newplus_setting_resolve_variables() && helpers::filesystem::is_directory(target_final_fullpath)) 381 { 382 helpers::variables::resolve_variables_in_filename_and_rename_files(target_final_fullpath); 383 } 384 385 // Touch all files and set last modified to "now" 386 update_last_write_time(target_final_fullpath); 387 388 // Consider copy completed. If we do tracing after enter_rename_mode, then rename mode won't consistently work 389 trace.UpdateState(true); 390 Trace::EventCopyTemplate(target_final_fullpath.extension().c_str()); 391 Trace::EventCopyTemplateResult(hr); 392 trace.Flush(); 393 trace.UpdateState(false); 394 395 // Refresh folder items 396 template_entry->refresh_target(target_final_fullpath); 397 398 // Enter rename mode 399 template_entry->enter_rename_mode(target_final_fullpath); 400 } 401 catch (const std::exception& ex) 402 { 403 Logger::error(ex.what()); 404 405 hr = S_FALSE; 406 trace.UpdateState(true); 407 Trace::EventCopyTemplateResult(hr); 408 trace.Flush(); 409 trace.UpdateState(false); 410 } 411 412 return hr; 413 } 414 415 inline HRESULT open_template_folder(const std::filesystem::path template_folder) 416 { 417 HRESULT hr = S_OK; 418 419 try 420 { 421 Logger::info(L"Open templates folder"); 422 423 if (newplus::utilities::get_saved_number_of_templates() >= 0) 424 { 425 // Log that context menu was shown and with how many items 426 trace.UpdateState(true); 427 Trace::EventShowTemplateItems(newplus::utilities::get_saved_number_of_templates()); 428 trace.Flush(); 429 trace.UpdateState(false); 430 } 431 432 const std::wstring verb_hardcoded_do_not_change = L"open"; 433 ShellExecute(nullptr, verb_hardcoded_do_not_change.c_str(), template_folder.c_str(), NULL, NULL, SW_SHOWNORMAL); 434 435 trace.UpdateState(true); 436 Trace::EventOpenTemplates(); 437 trace.Flush(); 438 trace.UpdateState(false); 439 } 440 catch (const std::exception& ex) 441 { 442 Logger::error(ex.what()); 443 444 hr = S_FALSE; 445 } 446 447 return hr; 448 } 449 }