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  }