/ src / modules / alwaysontop / AlwaysOnTop / Settings.cpp
Settings.cpp
  1  #include "pch.h"
  2  #include "Settings.h"
  3  
  4  #include <ModuleConstants.h>
  5  #include <SettingsObserver.h>
  6  #include <WinHookEventIDs.h>
  7  
  8  #include <common/SettingsAPI/settings_helpers.h>
  9  #include <common/utils/string_utils.h> // trim 
 10  
 11  namespace NonLocalizable
 12  {
 13      const static wchar_t* SettingsFileName = L"settings.json";
 14  
 15      const static wchar_t* HotkeyID = L"hotkey";
 16      const static wchar_t* SoundEnabledID = L"sound-enabled";
 17      const static wchar_t* FrameEnabledID = L"frame-enabled";
 18      const static wchar_t* FrameThicknessID = L"frame-thickness";
 19      const static wchar_t* FrameColorID = L"frame-color";
 20      const static wchar_t* FrameOpacityID = L"frame-opacity";
 21      const static wchar_t* BlockInGameModeID = L"do-not-activate-on-game-mode";
 22      const static wchar_t* ExcludedAppsID = L"excluded-apps";
 23      const static wchar_t* FrameAccentColor = L"frame-accent-color";
 24      const static wchar_t* RoundCornersEnabledID = L"round-corners-enabled";
 25  }
 26  
 27  // TODO: move to common utils
 28  inline COLORREF HexToRGB(std::wstring_view hex, const COLORREF fallbackColor = RGB(255, 255, 255))
 29  {
 30      hex = left_trim<wchar_t>(trim<wchar_t>(hex), L"#");
 31  
 32      try
 33      {
 34          const long long tmp = std::stoll(hex.data(), nullptr, 16);
 35          const BYTE nR = static_cast<BYTE>((tmp & 0xFF0000) >> 16);
 36          const BYTE nG = static_cast<BYTE>((tmp & 0xFF00) >> 8);
 37          const BYTE nB = static_cast<BYTE>((tmp & 0xFF));
 38          return RGB(nR, nG, nB);
 39      }
 40      catch (const std::exception&)
 41      {
 42          return fallbackColor;
 43      }
 44  }
 45  
 46  AlwaysOnTopSettings::AlwaysOnTopSettings()
 47  {
 48      m_uiSettings.ColorValuesChanged([&](winrt::Windows::UI::ViewManagement::UISettings const& settings,
 49                                          winrt::Windows::Foundation::IInspectable const& args)
 50      {
 51          if (m_settings.frameAccentColor)
 52          {
 53              NotifyObservers(SettingId::FrameAccentColor);
 54          }
 55      });
 56  }
 57  
 58  AlwaysOnTopSettings& AlwaysOnTopSettings::instance()
 59  {
 60      static AlwaysOnTopSettings instance;
 61      return instance;
 62  }
 63  
 64  void AlwaysOnTopSettings::InitFileWatcher()
 65  {
 66      const std::wstring& settingsFileName = GetSettingsFileName();
 67      m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
 68          PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
 69      });
 70  }
 71  
 72  std::wstring AlwaysOnTopSettings::GetSettingsFileName()
 73  {
 74      std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
 75      return saveFolderPath + L"\\" + std::wstring(NonLocalizable::SettingsFileName);
 76  }
 77  
 78  void AlwaysOnTopSettings::AddObserver(SettingsObserver& observer)
 79  {
 80      m_observers.insert(&observer);
 81  }
 82  
 83  void AlwaysOnTopSettings::RemoveObserver(SettingsObserver& observer)
 84  {
 85      auto iter = m_observers.find(&observer);
 86      if (iter != m_observers.end())
 87      {
 88          m_observers.erase(iter);
 89      }
 90  }
 91  
 92  void AlwaysOnTopSettings::LoadSettings()
 93  {
 94      try
 95      {
 96          PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(NonLocalizable::ModuleKey);
 97  
 98          if (const auto jsonVal = values.get_json(NonLocalizable::HotkeyID))
 99          {
100              auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal);
101              if (m_settings.hotkey.get_modifiers() != val.get_modifiers() || m_settings.hotkey.get_key() != val.get_key() || m_settings.hotkey.get_code() != val.get_code())
102              {
103                  m_settings.hotkey = val;
104                  NotifyObservers(SettingId::Hotkey);
105              }
106          }
107          
108          if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID))
109          {
110              auto val = *jsonVal;
111              if (m_settings.enableSound != val)
112              {
113                  m_settings.enableSound = val;
114                  NotifyObservers(SettingId::SoundEnabled);
115              }
116          }
117  
118          if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameThicknessID))
119          {
120              auto val = *jsonVal;
121              if (m_settings.frameThickness != val)
122              {
123                  m_settings.frameThickness = val;
124                  NotifyObservers(SettingId::FrameThickness);
125              }
126          }
127  
128          if (const auto jsonVal = values.get_string_value(NonLocalizable::FrameColorID))
129          {
130              auto val = HexToRGB(*jsonVal);
131              if (m_settings.frameColor != val)
132              {
133                  m_settings.frameColor = val;
134                  NotifyObservers(SettingId::FrameColor);
135              }
136          }
137  
138          if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameOpacityID))
139          {
140              auto val = *jsonVal;
141              if (m_settings.frameOpacity != val)
142              {
143                  m_settings.frameOpacity = val;
144                  NotifyObservers(SettingId::FrameOpacity);
145              }
146          }
147  
148          if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameEnabledID))
149          {
150              auto val = *jsonVal;
151              if (m_settings.enableFrame != val)
152              {
153                  m_settings.enableFrame = val;
154                  NotifyObservers(SettingId::FrameEnabled);
155              }            
156          }
157  
158          if (const auto jsonVal = values.get_bool_value(NonLocalizable::BlockInGameModeID))
159          {
160              auto val = *jsonVal;
161              if (m_settings.blockInGameMode != val)
162              {
163                  m_settings.blockInGameMode = val;
164                  NotifyObservers(SettingId::BlockInGameMode);
165              }
166          }
167  
168          if (const auto jsonVal = values.get_bool_value(NonLocalizable::RoundCornersEnabledID))
169          {
170              auto val = *jsonVal;
171              if (m_settings.roundCornersEnabled != val)
172              {
173                  m_settings.roundCornersEnabled = val;
174                  NotifyObservers(SettingId::RoundCornersEnabled);
175              }
176          }
177  
178          if (auto jsonVal = values.get_string_value(NonLocalizable::ExcludedAppsID))
179          {
180              std::wstring apps = std::move(*jsonVal);
181              std::vector<std::wstring> excludedApps;
182              auto excludedUppercase = apps;
183              CharUpperBuffW(excludedUppercase.data(), static_cast<DWORD>(excludedUppercase.length()));
184              std::wstring_view view(excludedUppercase);
185              view = left_trim<wchar_t>(trim<wchar_t>(view));
186  
187              while (!view.empty())
188              {
189                  auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
190                  excludedApps.emplace_back(view.substr(0, pos));
191                  view.remove_prefix(pos);
192                  view = left_trim<wchar_t>(trim<wchar_t>(view));
193              }
194  
195              if (m_settings.excludedApps != excludedApps)
196              {
197                  m_settings.excludedApps = excludedApps;
198                  NotifyObservers(SettingId::ExcludeApps);
199              }
200          }
201  
202          if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameAccentColor))
203          {
204              auto val = *jsonVal;
205              if (m_settings.frameAccentColor != val)
206              {
207                  m_settings.frameAccentColor = val;
208                  NotifyObservers(SettingId::FrameAccentColor);
209              }
210          }
211      }
212      catch (...)
213      {
214          // Log error message and continue with default settings.
215          Logger::error("Failed to read settings");
216          // TODO: show localized message
217      }
218  }
219  
220  void AlwaysOnTopSettings::NotifyObservers(SettingId id) const
221  {
222      for (auto observer : m_observers)
223      {
224          if (observer->WantsToBeNotified(id))
225          {
226              observer->SettingsUpdate(id);
227          }
228      }
229  }