/ tools / module_loader / src / SettingsLoader.cpp
SettingsLoader.cpp
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  #include "SettingsLoader.h"
  6  #include <iostream>
  7  #include <fstream>
  8  #include <sstream>
  9  #include <filesystem>
 10  #include <Shlobj.h>
 11  
 12  SettingsLoader::SettingsLoader()
 13  {
 14  }
 15  
 16  SettingsLoader::~SettingsLoader()
 17  {
 18  }
 19  
 20  std::wstring SettingsLoader::GetPowerToysSettingsRoot() const
 21  {
 22      // Get %LOCALAPPDATA%
 23      PWSTR localAppDataPath = nullptr;
 24      HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataPath);
 25      
 26      if (FAILED(hr) || !localAppDataPath)
 27      {
 28          std::wcerr << L"Error: Failed to get LOCALAPPDATA path\n";
 29          return L"";
 30      }
 31  
 32      std::wstring result(localAppDataPath);
 33      CoTaskMemFree(localAppDataPath);
 34  
 35      // Append PowerToys directory
 36      result += L"\\Microsoft\\PowerToys";
 37      return result;
 38  }
 39  
 40  std::wstring SettingsLoader::GetSettingsPath(const std::wstring& moduleName) const
 41  {
 42      std::wstring root = GetPowerToysSettingsRoot();
 43      if (root.empty())
 44      {
 45          return L"";
 46      }
 47  
 48      // Construct path: %LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\settings.json
 49      std::wstring settingsPath = root + L"\\" + moduleName + L"\\settings.json";
 50      return settingsPath;
 51  }
 52  
 53  std::wstring SettingsLoader::ReadFileContents(const std::wstring& filePath) const
 54  {
 55      std::wifstream file(filePath, std::ios::binary);
 56      if (!file.is_open())
 57      {
 58          std::wcerr << L"Error: Could not open file: " << filePath << L"\n";
 59          return L"";
 60      }
 61  
 62      // Read the entire file
 63      std::wstringstream buffer;
 64      buffer << file.rdbuf();
 65      
 66      return buffer.str();
 67  }
 68  
 69  std::wstring SettingsLoader::LoadSettings(const std::wstring& moduleName, const std::wstring& moduleDllPath)
 70  {
 71      const std::wstring powerToysPrefix = L"PowerToys.";
 72      
 73      // Build list of possible module name variations to try
 74      std::vector<std::wstring> moduleNameVariants;
 75      
 76      // Try exact name first
 77      moduleNameVariants.push_back(moduleName);
 78      
 79      // If doesn't start with "PowerToys.", try adding it
 80      if (moduleName.find(powerToysPrefix) != 0)
 81      {
 82          moduleNameVariants.push_back(powerToysPrefix + moduleName);
 83      }
 84      // If starts with "PowerToys.", try without it
 85      else
 86      {
 87          moduleNameVariants.push_back(moduleName.substr(powerToysPrefix.length()));
 88      }
 89  
 90      // FIRST: Try same directory as the module DLL
 91      if (!moduleDllPath.empty())
 92      {
 93          std::filesystem::path dllPath(moduleDllPath);
 94          std::filesystem::path dllDirectory = dllPath.parent_path();
 95          
 96          std::wstring localSettingsPath = (dllDirectory / L"settings.json").wstring();
 97          std::wcout << L"Trying settings path (module directory): " << localSettingsPath << L"\n";
 98          
 99          if (std::filesystem::exists(localSettingsPath))
100          {
101              std::wstring contents = ReadFileContents(localSettingsPath);
102              if (!contents.empty())
103              {
104                  std::wcout << L"Settings file loaded from module directory (" << contents.size() << L" characters)\n";
105                  return contents;
106              }
107          }
108      }
109  
110      // SECOND: Try standard PowerToys settings locations
111      for (const auto& variant : moduleNameVariants)
112      {
113          std::wstring settingsPath = GetSettingsPath(variant);
114          
115          std::wcout << L"Trying settings path: " << settingsPath << L"\n";
116  
117          // Check if file exists (case-sensitive path)
118          if (std::filesystem::exists(settingsPath))
119          {
120              std::wstring contents = ReadFileContents(settingsPath);
121              if (!contents.empty())
122              {
123                  std::wcout << L"Settings file loaded (" << contents.size() << L" characters)\n";
124                  return contents;
125              }
126          }
127          else
128          {
129              // Try case-insensitive search in the parent directory
130              std::wstring root = GetPowerToysSettingsRoot();
131              if (!root.empty() && std::filesystem::exists(root))
132              {
133                  try
134                  {
135                      // Search for a directory that matches case-insensitively
136                      for (const auto& entry : std::filesystem::directory_iterator(root))
137                      {
138                          if (entry.is_directory())
139                          {
140                              std::wstring dirName = entry.path().filename().wstring();
141                              
142                              // Case-insensitive comparison
143                              if (_wcsicmp(dirName.c_str(), variant.c_str()) == 0)
144                              {
145                                  std::wstring actualSettingsPath = entry.path().wstring() + L"\\settings.json";
146                                  std::wcout << L"Found case-insensitive match: " << actualSettingsPath << L"\n";
147                                  
148                                  if (std::filesystem::exists(actualSettingsPath))
149                                  {
150                                      std::wstring contents = ReadFileContents(actualSettingsPath);
151                                      if (!contents.empty())
152                                      {
153                                          std::wcout << L"Settings file loaded (" << contents.size() << L" characters)\n";
154                                          return contents;
155                                      }
156                                  }
157                              }
158                          }
159                      }
160                  }
161                  catch (const std::filesystem::filesystem_error& e)
162                  {
163                      std::wcerr << L"Error searching directory: " << e.what() << L"\n";
164                  }
165              }
166          }
167      }
168  
169      std::wcerr << L"Error: Settings file not found in any expected location:\n";
170      if (!moduleDllPath.empty())
171      {
172          std::filesystem::path dllPath(moduleDllPath);
173          std::filesystem::path dllDirectory = dllPath.parent_path();
174          std::wcerr << L"  - " << (dllDirectory / L"settings.json").wstring() << L" (module directory)\n";
175      }
176      for (const auto& variant : moduleNameVariants)
177      {
178          std::wcerr << L"  - " << GetSettingsPath(variant) << L"\n";
179      }
180      
181      return L"";
182  }