/ src / modules / LightSwitch / LightSwitchService / LightSwitchSettings.cpp
LightSwitchSettings.cpp
  1  #include "LightSwitchSettings.h"
  2  #include <common/utils/json.h>
  3  #include <common/SettingsAPI/settings_helpers.h>
  4  #include "SettingsObserver.h"
  5  #include <filesystem>
  6  #include <fstream>
  7  #include <logger.h>
  8  #include <LightSwitchService/trace.h>
  9  
 10  using namespace std;
 11  
 12  LightSwitchSettings& LightSwitchSettings::instance()
 13  {
 14      static LightSwitchSettings inst;
 15      return inst;
 16  }
 17  
 18  LightSwitchSettings::LightSwitchSettings()
 19  {
 20      LoadSettings();
 21  }
 22  
 23  std::wstring LightSwitchSettings::GetSettingsFileName()
 24  {
 25      return PTSettingsHelper::get_module_save_file_location(L"LightSwitch");
 26  }
 27  
 28  void LightSwitchSettings::InitFileWatcher()
 29  {
 30      if (!m_settingsChangedEvent)
 31      {
 32          m_settingsChangedEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
 33      }
 34  
 35      if (!m_settingsFileWatcher)
 36      {
 37          m_settingsFileWatcher = std::make_unique<FileWatcher>(
 38              GetSettingsFileName(),
 39              [this]() {
 40                  using namespace std::chrono;
 41  
 42                  {
 43                      std::lock_guard<std::mutex> lock(m_debounceMutex);
 44                      m_lastChangeTime = steady_clock::now();
 45                      if (m_debouncePending)
 46                          return;
 47                      m_debouncePending = true;
 48                  }
 49  
 50                  m_debounceThread = std::jthread([this](std::stop_token stop) {
 51                      using namespace std::chrono;
 52                      while (!stop.stop_requested())
 53                      {
 54                          std::this_thread::sleep_for(seconds(3));
 55  
 56                          auto elapsed = steady_clock::now() - m_lastChangeTime;
 57                          if (elapsed >= seconds(1))
 58                              break;
 59                      }
 60  
 61                      {
 62                          std::lock_guard<std::mutex> lock(m_debounceMutex);
 63                          m_debouncePending = false;
 64                      }
 65  
 66                      Logger::info(L"[LightSwitchSettings] Settings file stabilized, reloading.");
 67  
 68                      try
 69                      {
 70                          LoadSettings();
 71                          SetEvent(m_settingsChangedEvent);
 72                      }
 73                      catch (const std::exception& e)
 74                      {
 75                          std::wstring wmsg;
 76                          wmsg.assign(e.what(), e.what() + strlen(e.what()));
 77                          Logger::error(L"[LightSwitchSettings] Exception during debounced reload: {}", wmsg);
 78                      }
 79                  });
 80              });
 81      }
 82  }
 83  
 84  LightSwitchSettings::~LightSwitchSettings()
 85  {
 86      Logger::info(L"[LightSwitchSettings] Cleaning up settings resources...");
 87  
 88      // Stop and join the debounce thread (std::jthread auto-joins, but we can signal stop too)
 89      if (m_debounceThread.joinable())
 90      {
 91          m_debounceThread.request_stop();
 92      }
 93  
 94      // Release the file watcher so it closes file handles and background threads
 95      if (m_settingsFileWatcher)
 96      {
 97          m_settingsFileWatcher.reset();
 98          Logger::info(L"[LightSwitchSettings] File watcher stopped.");
 99      }
100  
101      // Close the Windows event handle
102      if (m_settingsChangedEvent)
103      {
104          CloseHandle(m_settingsChangedEvent);
105          m_settingsChangedEvent = nullptr;
106          Logger::info(L"[LightSwitchSettings] Settings changed event closed.");
107      }
108  
109      Logger::info(L"[LightSwitchSettings] Cleanup complete.");
110  }
111  
112  
113  void LightSwitchSettings::AddObserver(SettingsObserver& observer)
114  {
115      m_observers.insert(&observer);
116  }
117  
118  void LightSwitchSettings::RemoveObserver(SettingsObserver& observer)
119  {
120      m_observers.erase(&observer);
121  }
122  
123  void LightSwitchSettings::NotifyObservers(SettingId id) const
124  {
125      for (auto observer : m_observers)
126      {
127          if (observer->WantsToBeNotified(id))
128          {
129              observer->SettingsUpdate(id);
130          }
131      }
132  }
133  
134  HANDLE LightSwitchSettings::GetSettingsChangedEvent() const
135  {
136      return m_settingsChangedEvent;
137  }
138  
139  void LightSwitchSettings::LoadSettings()
140  {
141      std::lock_guard<std::mutex> guard(m_settingsMutex);
142      try
143      {
144          PowerToysSettings::PowerToyValues values =
145              PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
146  
147  
148          if (const auto jsonVal = values.get_string_value(L"scheduleMode"))
149          {
150              auto val = *jsonVal;
151              auto newMode = FromString(val);
152              if (m_settings.scheduleMode != newMode)
153              {
154                  m_settings.scheduleMode = newMode;
155                  Trace::LightSwitch::ScheduleModeToggled(val);
156                  NotifyObservers(SettingId::ScheduleMode);
157              }
158          }
159  
160          // Latitude
161          if (const auto jsonVal = values.get_string_value(L"latitude"))
162          {
163              auto val = *jsonVal;
164              if (m_settings.latitude != val)
165              {
166                  m_settings.latitude = val;
167                  NotifyObservers(SettingId::Latitude);
168              }
169          }
170  
171          // Longitude
172          if (const auto jsonVal = values.get_string_value(L"longitude"))
173          {
174              auto val = *jsonVal;
175              if (m_settings.longitude != val)
176              {
177                  m_settings.longitude = val;
178                  NotifyObservers(SettingId::Longitude);
179              }
180          }
181  
182          // LightTime
183          if (const auto jsonVal = values.get_int_value(L"lightTime"))
184          {
185              auto val = *jsonVal;
186              if (m_settings.lightTime != val)
187              {
188                  m_settings.lightTime = val;
189                  NotifyObservers(SettingId::LightTime);
190              }
191          }
192  
193          // DarkTime
194          if (const auto jsonVal = values.get_int_value(L"darkTime"))
195          {
196              auto val = *jsonVal;
197              if (m_settings.darkTime != val)
198              {
199                  m_settings.darkTime = val;
200                  NotifyObservers(SettingId::DarkTime);
201              }
202          }
203  
204          // Offset
205          if (const auto jsonVal = values.get_int_value(L"sunrise_offset")) 
206          {
207              auto val = *jsonVal;
208              if (m_settings.sunrise_offset != val)
209              {
210                  m_settings.sunrise_offset = val;
211                  NotifyObservers(SettingId::Sunrise_Offset);
212              }
213          }
214  
215          if (const auto jsonVal = values.get_int_value(L"sunset_offset"))
216          {
217              auto val = *jsonVal;
218              if (m_settings.sunset_offset != val)
219              {
220                  m_settings.sunset_offset = val;
221                  NotifyObservers(SettingId::Sunset_Offset);
222              }
223          }
224  
225          bool themeTargetChanged = false;
226  
227          // ChangeSystem
228          if (const auto jsonVal = values.get_bool_value(L"changeSystem"))
229          {
230              auto val = *jsonVal;
231              if (m_settings.changeSystem != val)
232              {
233                  m_settings.changeSystem = val;
234                  themeTargetChanged = true;
235                  NotifyObservers(SettingId::ChangeSystem);
236              }
237          }
238  
239          // ChangeApps
240          if (const auto jsonVal = values.get_bool_value(L"changeApps"))
241          {
242              auto val = *jsonVal;
243              if (m_settings.changeApps != val)
244              {
245                  m_settings.changeApps = val;
246                  themeTargetChanged = true;
247                  NotifyObservers(SettingId::ChangeApps);
248              }
249          }
250  
251          // EnableDarkModeProfile
252          if (const auto jsonVal = values.get_bool_value(L"enableDarkModeProfile"))
253          {
254              auto val = *jsonVal;
255              if (m_settings.enableDarkModeProfile != val)
256              {
257                  m_settings.enableDarkModeProfile = val;
258              }
259          }
260  
261          // EnableLightModeProfile
262          if (const auto jsonVal = values.get_bool_value(L"enableLightModeProfile"))
263          {
264              auto val = *jsonVal;
265              if (m_settings.enableLightModeProfile != val)
266              {
267                  m_settings.enableLightModeProfile = val;
268              }
269          }
270  
271          // DarkModeProfile
272          if (const auto jsonVal = values.get_string_value(L"darkModeProfile"))
273          {
274              auto val = *jsonVal;
275              if (m_settings.darkModeProfile != val)
276              {
277                  m_settings.darkModeProfile = val;
278              }
279          }
280  
281          // LightModeProfile
282          if (const auto jsonVal = values.get_string_value(L"lightModeProfile"))
283          {
284              auto val = *jsonVal;
285              if (m_settings.lightModeProfile != val)
286              {
287                  m_settings.lightModeProfile = val;
288              }
289          }
290  
291          // For ChangeSystem/ChangeApps changes, log telemetry
292          if (themeTargetChanged)
293          {
294              Trace::LightSwitch::ThemeTargetChanged(m_settings.changeApps, m_settings.changeSystem);
295          }
296      }
297      catch (...)
298      {
299          // Keeps defaults if load fails
300      }
301  }