Skip to content

Commit 4f6ecbd

Browse files
Jaylyn-Barbeeniels9001
authored andcommitted
[Light Switch] Enter latitude and longitude manually in Sunrise to sunset mode (#43276)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This PR introduces new UI to allow the users to manually enter their lat/long. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #42429 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Localization:** All end-user-facing strings can be localized - [x] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #[5979](https://github.com/MicrosoftDocs/windows-dev-docs-pr/pull/5979) --------- Co-authored-by: Niels Laute <[email protected]>
1 parent 63978c8 commit 4f6ecbd

File tree

14 files changed

+834
-531
lines changed

14 files changed

+834
-531
lines changed

src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp

Lines changed: 109 additions & 284 deletions
Large diffs are not rendered by default.

src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<ItemGroup>
7676
<ClCompile Include="LightSwitchService.cpp" />
7777
<ClCompile Include="LightSwitchSettings.cpp" />
78+
<ClCompile Include="LightSwitchStateManager.cpp" />
7879
<ClCompile Include="SettingsConstants.cpp" />
7980
<ClCompile Include="ThemeHelper.cpp" />
8081
<ClCompile Include="ThemeScheduler.cpp" />
@@ -85,6 +86,8 @@
8586
</ItemGroup>
8687
<ItemGroup>
8788
<ClInclude Include="LightSwitchSettings.h" />
89+
<ClInclude Include="LightSwitchStateManager.h" />
90+
<ClInclude Include="LightSwitchUtils.h" />
8891
<ClInclude Include="SettingsConstants.h" />
8992
<ClInclude Include="SettingsObserver.h" />
9093
<ClInclude Include="ThemeHelper.h" />

src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
<ClCompile Include="WinHookEventIDs.cpp">
3434
<Filter>Source Files</Filter>
3535
</ClCompile>
36+
<ClCompile Include="LightSwitchStateManager.cpp">
37+
<Filter>Source Files</Filter>
38+
</ClCompile>
3639
</ItemGroup>
3740
<ItemGroup>
3841
<ClInclude Include="ThemeScheduler.h">
@@ -53,6 +56,12 @@
5356
<ClInclude Include="WinHookEventIDs.h">
5457
<Filter>Header Files</Filter>
5558
</ClInclude>
59+
<ClInclude Include="LightSwitchStateManager.h">
60+
<Filter>Header Files</Filter>
61+
</ClInclude>
62+
<ClInclude Include="LightSwitchUtils.h">
63+
<Filter>Header Files</Filter>
64+
</ClInclude>
5665
</ItemGroup>
5766
<ItemGroup>
5867
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />

src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
#include <common/utils/json.h>
33
#include <common/SettingsAPI/settings_helpers.h>
44
#include "SettingsObserver.h"
5-
#include "ThemeHelper.h"
65
#include <filesystem>
76
#include <fstream>
8-
#include <WinHookEventIDs.h>
97
#include <logger.h>
108

119
using namespace std;
@@ -69,7 +67,6 @@ void LightSwitchSettings::InitFileWatcher()
6967
try
7068
{
7169
LoadSettings();
72-
ApplyThemeIfNecessary();
7370
SetEvent(m_settingsChangedEvent);
7471
}
7572
catch (const std::exception& e)
@@ -250,48 +247,3 @@ void LightSwitchSettings::LoadSettings()
250247
// Keeps defaults if load fails
251248
}
252249
}
253-
254-
void LightSwitchSettings::ApplyThemeIfNecessary()
255-
{
256-
std::lock_guard<std::mutex> guard(m_settingsMutex);
257-
258-
SYSTEMTIME st;
259-
GetLocalTime(&st);
260-
int nowMinutes = st.wHour * 60 + st.wMinute;
261-
262-
bool shouldBeLight = false;
263-
if (m_settings.lightTime < m_settings.darkTime)
264-
shouldBeLight = (nowMinutes >= m_settings.lightTime && nowMinutes < m_settings.darkTime);
265-
else
266-
shouldBeLight = (nowMinutes >= m_settings.lightTime || nowMinutes < m_settings.darkTime);
267-
268-
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
269-
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
270-
271-
if (shouldBeLight)
272-
{
273-
if (m_settings.changeSystem && !isSystemCurrentlyLight)
274-
{
275-
SetSystemTheme(true);
276-
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
277-
}
278-
if (m_settings.changeApps && !isAppsCurrentlyLight)
279-
{
280-
SetAppsTheme(true);
281-
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
282-
}
283-
}
284-
else
285-
{
286-
if (m_settings.changeSystem && isSystemCurrentlyLight)
287-
{
288-
SetSystemTheme(false);
289-
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
290-
}
291-
if (m_settings.changeApps && isAppsCurrentlyLight)
292-
{
293-
SetAppsTheme(false);
294-
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
295-
}
296-
}
297-
}

src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ class LightSwitchSettings
8181
void RemoveObserver(SettingsObserver& observer);
8282

8383
void LoadSettings();
84-
void ApplyThemeIfNecessary();
8584

8685
HANDLE GetSettingsChangedEvent() const;
8786

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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

Comments
 (0)