|
| 1 | +#include "pch.h" |
| 2 | +#include "LightSwitchStateManager.h" |
| 3 | +#include <logger.h> |
| 4 | +#include <LightSwitchUtils.h> |
| 5 | +#include "ThemeScheduler.h" |
| 6 | +#include <ThemeHelper.h> |
| 7 | + |
| 8 | +void ApplyTheme(bool shouldBeLight); |
| 9 | + |
| 10 | +// Constructor |
| 11 | +LightSwitchStateManager::LightSwitchStateManager() |
| 12 | +{ |
| 13 | + Logger::info(L"[LightSwitchStateManager] Initialized"); |
| 14 | +} |
| 15 | + |
| 16 | +// Called when settings.json changes |
| 17 | +void LightSwitchStateManager::OnSettingsChanged() |
| 18 | +{ |
| 19 | + std::lock_guard<std::mutex> lock(_stateMutex); |
| 20 | + Logger::info(L"[LightSwitchStateManager] Settings changed event received"); |
| 21 | + |
| 22 | + // If manual override was active, clear it so new settings take effect |
| 23 | + if (_state.isManualOverride) |
| 24 | + { |
| 25 | + Logger::info(L"[LightSwitchStateManager] Clearing manual override due to settings update."); |
| 26 | + _state.isManualOverride = false; |
| 27 | + } |
| 28 | + |
| 29 | + EvaluateAndApplyIfNeeded(); |
| 30 | +} |
| 31 | + |
| 32 | +// Called once per minute |
| 33 | +void LightSwitchStateManager::OnTick(int currentMinutes) |
| 34 | +{ |
| 35 | + std::lock_guard<std::mutex> lock(_stateMutex); |
| 36 | + Logger::debug(L"[LightSwitchStateManager] Tick received: {}", currentMinutes); |
| 37 | + EvaluateAndApplyIfNeeded(); |
| 38 | +} |
| 39 | + |
| 40 | +// Called when manual override is triggered |
| 41 | +void LightSwitchStateManager::OnManualOverride() |
| 42 | +{ |
| 43 | + std::lock_guard<std::mutex> lock(_stateMutex); |
| 44 | + Logger::info(L"[LightSwitchStateManager] Manual override triggered"); |
| 45 | + _state.isManualOverride = !_state.isManualOverride; |
| 46 | + |
| 47 | + // When entering manual override, sync internal theme state to match the current system |
| 48 | + if (_state.isManualOverride) |
| 49 | + { |
| 50 | + _state.isSystemLightActive = GetCurrentSystemTheme(); |
| 51 | + |
| 52 | + _state.isAppsLightActive = GetCurrentAppsTheme(); |
| 53 | + |
| 54 | + Logger::info(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).", |
| 55 | + (_state.isSystemLightActive ? L"light" : L"dark"), |
| 56 | + (_state.isAppsLightActive ? L"light" : L"dark")); |
| 57 | + } |
| 58 | + |
| 59 | + EvaluateAndApplyIfNeeded(); |
| 60 | +} |
| 61 | + |
| 62 | +// Helpers |
| 63 | +bool LightSwitchStateManager::CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon) |
| 64 | +{ |
| 65 | + try |
| 66 | + { |
| 67 | + double latVal = std::stod(lat); |
| 68 | + double lonVal = std::stod(lon); |
| 69 | + return !(latVal == 0 && lonVal == 0) && (latVal >= -90.0 && latVal <= 90.0) && (lonVal >= -180.0 && lonVal <= 180.0); |
| 70 | + } |
| 71 | + catch (...) |
| 72 | + { |
| 73 | + return false; |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +void LightSwitchStateManager::SyncInitialThemeState() |
| 78 | +{ |
| 79 | + std::lock_guard<std::mutex> lock(_stateMutex); |
| 80 | + _state.isSystemLightActive = GetCurrentSystemTheme(); |
| 81 | + _state.isAppsLightActive = GetCurrentAppsTheme(); |
| 82 | + Logger::info(L"[LightSwitchStateManager] Synced initial state to current system theme ({})", |
| 83 | + _state.isSystemLightActive ? L"light" : L"dark"); |
| 84 | + Logger::info(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})", |
| 85 | + _state.isAppsLightActive ? L"light" : L"dark"); |
| 86 | +} |
| 87 | + |
| 88 | +static std::pair<int, int> update_sun_times(auto& settings) |
| 89 | +{ |
| 90 | + double latitude = std::stod(settings.latitude); |
| 91 | + double longitude = std::stod(settings.longitude); |
| 92 | + |
| 93 | + SYSTEMTIME st; |
| 94 | + GetLocalTime(&st); |
| 95 | + |
| 96 | + SunTimes newTimes = CalculateSunriseSunset(latitude, longitude, st.wYear, st.wMonth, st.wDay); |
| 97 | + |
| 98 | + int newLightTime = newTimes.sunriseHour * 60 + newTimes.sunriseMinute; |
| 99 | + int newDarkTime = newTimes.sunsetHour * 60 + newTimes.sunsetMinute; |
| 100 | + |
| 101 | + try |
| 102 | + { |
| 103 | + auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch"); |
| 104 | + values.add_property(L"lightTime", newLightTime); |
| 105 | + values.add_property(L"darkTime", newDarkTime); |
| 106 | + values.save_to_settings_file(); |
| 107 | + |
| 108 | + Logger::info(L"[LightSwitchService] Updated sun times and saved to config."); |
| 109 | + } |
| 110 | + catch (const std::exception& e) |
| 111 | + { |
| 112 | + std::string msg = e.what(); |
| 113 | + std::wstring wmsg(msg.begin(), msg.end()); |
| 114 | + Logger::error(L"[LightSwitchService] Exception during sun time update: {}", wmsg); |
| 115 | + } |
| 116 | + |
| 117 | + return { newLightTime, newDarkTime }; |
| 118 | +} |
| 119 | + |
| 120 | +// Internal: decide what should happen now |
| 121 | +void LightSwitchStateManager::EvaluateAndApplyIfNeeded() |
| 122 | +{ |
| 123 | + LightSwitchSettings::instance().LoadSettings(); |
| 124 | + const auto& _currentSettings = LightSwitchSettings::settings(); |
| 125 | + auto now = GetNowMinutes(); |
| 126 | + |
| 127 | + // Early exit: OFF mode just pauses activity |
| 128 | + if (_currentSettings.scheduleMode == ScheduleMode::Off) |
| 129 | + { |
| 130 | + Logger::debug(L"[LightSwitchStateManager] Mode is OFF — pausing service logic."); |
| 131 | + _state.lastTickMinutes = now; |
| 132 | + return; |
| 133 | + } |
| 134 | + |
| 135 | + bool coordsValid = CoordinatesAreValid(_currentSettings.latitude, _currentSettings.longitude); |
| 136 | + |
| 137 | + // Handle Sun Mode recalculation |
| 138 | + if (_currentSettings.scheduleMode == ScheduleMode::SunsetToSunrise && coordsValid) |
| 139 | + { |
| 140 | + SYSTEMTIME st; |
| 141 | + GetLocalTime(&st); |
| 142 | + bool newDay = (_state.lastEvaluatedDay != st.wDay); |
| 143 | + bool modeChangedToSun = (_state.lastAppliedMode != ScheduleMode::SunsetToSunrise && |
| 144 | + _currentSettings.scheduleMode == ScheduleMode::SunsetToSunrise); |
| 145 | + |
| 146 | + if (newDay || modeChangedToSun) |
| 147 | + { |
| 148 | + Logger::info(L"[LightSwitchStateManager] Recalculating sun times (mode/day change)."); |
| 149 | + auto [newLightTime, newDarkTime] = update_sun_times(_currentSettings); |
| 150 | + _state.lastEvaluatedDay = st.wDay; |
| 151 | + _state.effectiveLightMinutes = newLightTime + _currentSettings.sunrise_offset; |
| 152 | + _state.effectiveDarkMinutes = newDarkTime + _currentSettings.sunset_offset; |
| 153 | + } |
| 154 | + else |
| 155 | + { |
| 156 | + _state.effectiveLightMinutes = _currentSettings.lightTime + _currentSettings.sunrise_offset; |
| 157 | + _state.effectiveDarkMinutes = _currentSettings.darkTime + _currentSettings.sunset_offset; |
| 158 | + } |
| 159 | + } |
| 160 | + else if (_currentSettings.scheduleMode == ScheduleMode::FixedHours) |
| 161 | + { |
| 162 | + _state.effectiveLightMinutes = _currentSettings.lightTime; |
| 163 | + _state.effectiveDarkMinutes = _currentSettings.darkTime; |
| 164 | + } |
| 165 | + |
| 166 | + // Handle manual override logic |
| 167 | + if (_state.isManualOverride) |
| 168 | + { |
| 169 | + bool crossedBoundary = false; |
| 170 | + if (_state.lastTickMinutes != -1) |
| 171 | + { |
| 172 | + int prev = _state.lastTickMinutes; |
| 173 | + |
| 174 | + // Handle midnight wraparound safely |
| 175 | + if (now < prev) |
| 176 | + { |
| 177 | + crossedBoundary = |
| 178 | + (prev <= _state.effectiveLightMinutes || now >= _state.effectiveLightMinutes) || |
| 179 | + (prev <= _state.effectiveDarkMinutes || now >= _state.effectiveDarkMinutes); |
| 180 | + } |
| 181 | + else |
| 182 | + { |
| 183 | + crossedBoundary = |
| 184 | + (prev < _state.effectiveLightMinutes && now >= _state.effectiveLightMinutes) || |
| 185 | + (prev < _state.effectiveDarkMinutes && now >= _state.effectiveDarkMinutes); |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | + if (crossedBoundary) |
| 190 | + { |
| 191 | + Logger::info(L"[LightSwitchStateManager] Manual override cleared after crossing boundary."); |
| 192 | + _state.isManualOverride = false; |
| 193 | + } |
| 194 | + else |
| 195 | + { |
| 196 | + Logger::debug(L"[LightSwitchStateManager] Manual override active — skipping auto apply."); |
| 197 | + _state.lastTickMinutes = now; |
| 198 | + return; |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + _state.lastAppliedMode = _currentSettings.scheduleMode; |
| 203 | + |
| 204 | + bool shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes); |
| 205 | + |
| 206 | + bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight); |
| 207 | + bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight); |
| 208 | + |
| 209 | + Logger::debug( |
| 210 | + L"[LightSwitchStateManager] now = {:02d}:{:02d}, light boundary = {:02d}:{:02d} ({}), dark boundary = {:02d}:{:02d} ({})", |
| 211 | + now / 60, |
| 212 | + now % 60, |
| 213 | + _state.effectiveLightMinutes / 60, |
| 214 | + _state.effectiveLightMinutes % 60, |
| 215 | + _state.effectiveLightMinutes, |
| 216 | + _state.effectiveDarkMinutes / 60, |
| 217 | + _state.effectiveDarkMinutes % 60, |
| 218 | + _state.effectiveDarkMinutes); |
| 219 | + |
| 220 | + Logger::debug("should be light = {}, apps needs change = {}, system needs change = {}", |
| 221 | + shouldBeLight ? "true" : "false", |
| 222 | + appsNeedsToChange ? "true" : "false", |
| 223 | + systemNeedsToChange ? "true" : "false"); |
| 224 | + |
| 225 | + // Only apply theme if there's a change or no override active |
| 226 | + if (!_state.isManualOverride && (appsNeedsToChange || systemNeedsToChange)) |
| 227 | + { |
| 228 | + Logger::info(L"[LightSwitchStateManager] Applying {} theme", shouldBeLight ? L"light" : L"dark"); |
| 229 | + ApplyTheme(shouldBeLight); |
| 230 | + |
| 231 | + _state.isSystemLightActive = GetCurrentSystemTheme(); |
| 232 | + _state.isAppsLightActive = GetCurrentAppsTheme(); |
| 233 | + |
| 234 | + Logger::debug(L"[LightSwitchStateManager] Synced post-apply theme state — System: {}, Apps: {}", |
| 235 | + _state.isSystemLightActive ? L"light" : L"dark", |
| 236 | + _state.isAppsLightActive ? L"light" : L"dark"); |
| 237 | + } |
| 238 | + |
| 239 | + _state.lastTickMinutes = now; |
| 240 | +} |
| 241 | + |
| 242 | + |
| 243 | + |
0 commit comments