mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-10 05:06:36 +02:00
[Light Switch] Refactor + cleaner behavior (#43159)
<!-- 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 also includes a refactor to the light switch service. The refactor will help me make cleaner updates in the future and makes the "state" easier to track through the service. ## Validation Completed the following steps as testing for normal behavior and edge cases: 1. Start up - Check defaults (delete settings.json files) - Ensure PowerToys properly starts/does not start the module - Ensure turning the module on and off starts/terminates the service - Ensure the settings in the settings file are reflected in the front end 2. Manual Override Activation - Ensure that pressing the shortcut key triggers an update - Ensure that pressing the shortcut key triggers a block in the schedule (Should see logs Skipping schedule due to manual override) - Ensure that changing windows settings triggers a manual override (at the next minute) - Ensure in both scenarios that the schedule is ignored until a boundary is met - Ensure that the schedule resumes following the boundary clearance. 4. New Day Detection / Sun Time Recalculation - Keep service running past midnight (or simulate date change) - Verify the last updated day is updated - Verify new sun times are accurate and set in settings. 5. Coordinates / Mode Change - Ensure that updates occur when the coordinates or mode changes. These updates should reflect the new settings. 6. Schedule Mode OFF - Turn the schedule off, check logs for notice - Ensure the shortcut still works - Ensure the schedule resumes as expected once turned back on. 8. Sleep / Hibernate Resume - Set your schedule to change themes in the next 2 minutes. - Send your machine to hibernate shutdown /h - Wake your machine after your theme should have changed and ensure Light Switch catches itself up - Repeat steps above but with a manual override triggered prior to the theme change.Ensure manual override is flushed and schedule resumes. 9. Stop and Restart behavior - Stop and restart the module. Ensure it behaves as expected. No reset settings, etc.
This commit is contained in:
@@ -11,7 +11,6 @@
|
|||||||
#include <logger/logger_settings.h>
|
#include <logger/logger_settings.h>
|
||||||
#include <logger/logger.h>
|
#include <logger/logger.h>
|
||||||
#include <utils/logger_helper.h>
|
#include <utils/logger_helper.h>
|
||||||
#include <LightSwitchServiceObserver.h>
|
|
||||||
|
|
||||||
SERVICE_STATUS g_ServiceStatus = {};
|
SERVICE_STATUS g_ServiceStatus = {};
|
||||||
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
|
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
|
||||||
@@ -20,6 +19,7 @@ extern int g_lastUpdatedDay = -1;
|
|||||||
static ScheduleMode prevMode = ScheduleMode::Off;
|
static ScheduleMode prevMode = ScheduleMode::Off;
|
||||||
static std::wstring prevLat, prevLon;
|
static std::wstring prevLat, prevLon;
|
||||||
static int prevMinutes = -1;
|
static int prevMinutes = -1;
|
||||||
|
static bool lastOverrideStatus = false;
|
||||||
|
|
||||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
|
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
|
||||||
@@ -164,48 +164,8 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
|
|
||||||
LightSwitchSettings::instance().InitFileWatcher();
|
LightSwitchSettings::instance().InitFileWatcher();
|
||||||
|
|
||||||
LightSwitchServiceObserver observer({ SettingId::LightTime,
|
|
||||||
SettingId::DarkTime,
|
|
||||||
SettingId::ScheduleMode,
|
|
||||||
SettingId::Sunrise_Offset,
|
|
||||||
SettingId::Sunset_Offset });
|
|
||||||
|
|
||||||
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||||
|
|
||||||
auto applyTheme = [](int nowMinutes, int lightMinutes, int darkMinutes, const auto& settings) {
|
|
||||||
bool isLightActive = (lightMinutes < darkMinutes) ? (nowMinutes >= lightMinutes && nowMinutes < darkMinutes) : (nowMinutes >= lightMinutes || nowMinutes < darkMinutes);
|
|
||||||
|
|
||||||
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
|
|
||||||
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
|
|
||||||
|
|
||||||
if (isLightActive)
|
|
||||||
{
|
|
||||||
if (settings.changeSystem && !isSystemCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetSystemTheme(true);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
|
|
||||||
}
|
|
||||||
if (settings.changeApps && !isAppsCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetAppsTheme(true);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (settings.changeSystem && isSystemCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetSystemTheme(false);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
|
|
||||||
}
|
|
||||||
if (settings.changeApps && isAppsCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetAppsTheme(false);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
LightSwitchSettings::instance().LoadSettings();
|
LightSwitchSettings::instance().LoadSettings();
|
||||||
auto& settings = LightSwitchSettings::instance().settings();
|
auto& settings = LightSwitchSettings::instance().settings();
|
||||||
|
|
||||||
@@ -213,22 +173,22 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
GetLocalTime(&st);
|
GetLocalTime(&st);
|
||||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||||
|
|
||||||
|
// Handle initial theme application if necessary
|
||||||
if (settings.scheduleMode != ScheduleMode::Off)
|
if (settings.scheduleMode != ScheduleMode::Off)
|
||||||
{
|
{
|
||||||
applyTheme(nowMinutes,
|
Logger::info(L"[LightSwitchService] Schedule mode is set to {}. Applying theme if necessary.", settings.scheduleMode);
|
||||||
settings.lightTime + settings.sunrise_offset,
|
LightSwitchSettings::instance().ApplyThemeIfNecessary();
|
||||||
settings.darkTime + settings.sunset_offset,
|
|
||||||
settings);
|
|
||||||
Logger::trace(L"[LightSwitchService] Initialized g_lastUpdatedDay = {}", g_lastUpdatedDay);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchService] Schedule mode is OFF - ticker suspended, waiting for manual action or mode change.");
|
Logger::info(L"[LightSwitchService] Schedule mode is set to Off.");
|
||||||
}
|
}
|
||||||
|
|
||||||
g_lastUpdatedDay = st.wDay;
|
g_lastUpdatedDay = st.wDay;
|
||||||
|
Logger::info(L"[LightSwitchService] Initializing g_lastUpdatedDay to {}.", g_lastUpdatedDay);
|
||||||
ULONGLONG lastSettingsReload = 0;
|
ULONGLONG lastSettingsReload = 0;
|
||||||
|
|
||||||
|
// ticker loop
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
|
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
|
||||||
@@ -237,13 +197,10 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
|
|
||||||
const auto& settings = LightSwitchSettings::instance().settings();
|
const auto& settings = LightSwitchSettings::instance().settings();
|
||||||
|
|
||||||
bool scheduleJustEnabled = (prevMode == ScheduleMode::Off && settings.scheduleMode != ScheduleMode::Off);
|
// If the mode is set to Off, suspend the scheduler and avoid extra work
|
||||||
prevMode = settings.scheduleMode;
|
|
||||||
|
|
||||||
// ─── Handle "Schedule Off" Mode ─────────────────────────────────────────────
|
|
||||||
if (settings.scheduleMode == ScheduleMode::Off)
|
if (settings.scheduleMode == ScheduleMode::Off)
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchService] Schedule mode OFF - suspending scheduler but keeping service alive.");
|
Logger::info(L"[LightSwitchService] Schedule mode is OFF - suspending scheduler but keeping service alive.");
|
||||||
|
|
||||||
if (!hManualOverride)
|
if (!hManualOverride)
|
||||||
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||||
@@ -283,7 +240,6 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
{
|
{
|
||||||
Logger::trace(L"[LightSwitchService] Settings change event triggered, reloading settings...");
|
Logger::trace(L"[LightSwitchService] Settings change event triggered, reloading settings...");
|
||||||
ResetEvent(LightSwitchSettings::instance().GetSettingsChangedEvent());
|
ResetEvent(LightSwitchSettings::instance().GetSettingsChangedEvent());
|
||||||
LightSwitchSettings::instance().LoadSettings();
|
|
||||||
const auto& newSettings = LightSwitchSettings::instance().settings();
|
const auto& newSettings = LightSwitchSettings::instance().settings();
|
||||||
lastSettingsReload = GetTickCount64();
|
lastSettingsReload = GetTickCount64();
|
||||||
|
|
||||||
@@ -298,73 +254,150 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Normal Schedule Loop ───────────────────────────────────────────────────
|
bool scheduleJustEnabled = (prevMode == ScheduleMode::Off && settings.scheduleMode != ScheduleMode::Off);
|
||||||
|
prevMode = settings.scheduleMode;
|
||||||
|
|
||||||
ULONGLONG nowTick = GetTickCount64();
|
ULONGLONG nowTick = GetTickCount64();
|
||||||
bool recentSettingsReload = (nowTick - lastSettingsReload < 5000);
|
bool recentSettingsReload = (nowTick - lastSettingsReload < 2000);
|
||||||
|
|
||||||
if (g_lastUpdatedDay != -1)
|
Logger::debug(L"[LightSwitchService] Current g_lastUpdatedDay value = {}.", g_lastUpdatedDay);
|
||||||
|
|
||||||
|
// Manual Override Detection Logic
|
||||||
|
bool manualOverrideActive = (hManualOverride && WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
|
||||||
|
|
||||||
|
if (manualOverrideActive != lastOverrideStatus)
|
||||||
{
|
{
|
||||||
bool manualOverrideActive = (hManualOverride && WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
|
Logger::debug(L"[LightSwitchService] Manual override active = {}", manualOverrideActive);
|
||||||
|
lastOverrideStatus = manualOverrideActive;
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.scheduleMode != ScheduleMode::Off && !recentSettingsReload && !scheduleJustEnabled)
|
if (settings.scheduleMode != ScheduleMode::Off && !recentSettingsReload && !scheduleJustEnabled && !manualOverrideActive)
|
||||||
|
{
|
||||||
|
bool currentSystemTheme = GetCurrentSystemTheme();
|
||||||
|
bool currentAppsTheme = GetCurrentAppsTheme();
|
||||||
|
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||||
|
|
||||||
|
int lightBoundary = 0;
|
||||||
|
int darkBoundary = 0;
|
||||||
|
|
||||||
|
if (settings.scheduleMode == ScheduleMode::SunsetToSunrise)
|
||||||
{
|
{
|
||||||
Logger::debug(L"[LightSwitchService] Checking if manual override is active...");
|
lightBoundary = (settings.lightTime + settings.sunrise_offset) % 1440;
|
||||||
bool manualOverrideActive = (hManualOverride && WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
|
darkBoundary = (settings.darkTime + settings.sunset_offset) % 1440;
|
||||||
Logger::debug(L"[LightSwitchService] Manual override active = {}", manualOverrideActive);
|
|
||||||
|
|
||||||
if (!manualOverrideActive)
|
|
||||||
{
|
|
||||||
bool currentSystemTheme = GetCurrentSystemTheme();
|
|
||||||
bool currentAppsTheme = GetCurrentAppsTheme();
|
|
||||||
|
|
||||||
SYSTEMTIME st;
|
|
||||||
GetLocalTime(&st);
|
|
||||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
|
||||||
|
|
||||||
bool shouldBeLight = (settings.lightTime < settings.darkTime) ? (nowMinutes >= settings.lightTime && nowMinutes < settings.darkTime) : (nowMinutes >= settings.lightTime || nowMinutes < settings.darkTime);
|
|
||||||
|
|
||||||
Logger::debug(L"[LightSwitchService] shouldBeLight = {}", shouldBeLight);
|
|
||||||
|
|
||||||
if ((settings.changeSystem && (currentSystemTheme != shouldBeLight)) ||
|
|
||||||
(settings.changeApps && (currentAppsTheme != shouldBeLight)))
|
|
||||||
{
|
|
||||||
Logger::debug(L"[LightSwitchService] External theme change detected - enabling manual override");
|
|
||||||
|
|
||||||
if (!hManualOverride)
|
|
||||||
{
|
|
||||||
hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
|
||||||
if (!hManualOverride)
|
|
||||||
hManualOverride = CreateEventW(nullptr, TRUE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hManualOverride)
|
|
||||||
{
|
|
||||||
SetEvent(hManualOverride);
|
|
||||||
Logger::info(L"[LightSwitchService] Detected manual theme change outside of LightSwitch. Triggering manual override.");
|
|
||||||
skipRest = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::debug(L"[LightSwitchService] Skipping external-change detection (schedule off, recent reload, or just enabled).");
|
lightBoundary = settings.lightTime;
|
||||||
|
darkBoundary = settings.darkTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldBeLight = (lightBoundary < darkBoundary) ? (nowMinutes >= lightBoundary && nowMinutes < darkBoundary) : (nowMinutes >= lightBoundary || nowMinutes < darkBoundary);
|
||||||
|
|
||||||
|
Logger::debug(L"[LightSwitchService] shouldBeLight = {}", shouldBeLight);
|
||||||
|
|
||||||
|
bool systemMismatch = settings.changeSystem && (currentSystemTheme != shouldBeLight);
|
||||||
|
bool appsMismatch = settings.changeApps && (currentAppsTheme != shouldBeLight);
|
||||||
|
|
||||||
|
if (systemMismatch || appsMismatch)
|
||||||
|
{
|
||||||
|
// Make sure this is not because we crossed a boundary
|
||||||
|
bool crossedBoundary = false;
|
||||||
|
if (prevMinutes != -1)
|
||||||
|
{
|
||||||
|
if (nowMinutes < prevMinutes)
|
||||||
|
{
|
||||||
|
// wrapped around midnight
|
||||||
|
crossedBoundary = (prevMinutes <= lightBoundary || nowMinutes >= lightBoundary) ||
|
||||||
|
(prevMinutes <= darkBoundary || nowMinutes >= darkBoundary);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
crossedBoundary = (prevMinutes < lightBoundary && nowMinutes >= lightBoundary) ||
|
||||||
|
(prevMinutes < darkBoundary && nowMinutes >= darkBoundary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crossedBoundary)
|
||||||
|
{
|
||||||
|
Logger::info(L"[LightSwitchService] Missed boundary detected. Applying theme instead of triggering manual override.");
|
||||||
|
LightSwitchSettings::instance().ApplyThemeIfNecessary();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::info(L"[LightSwitchService] External {} theme change detected, enabling manual override.",
|
||||||
|
systemMismatch && appsMismatch ? L"system/app" :
|
||||||
|
systemMismatch ? L"system" :
|
||||||
|
L"app");
|
||||||
|
SetEvent(hManualOverride);
|
||||||
|
skipRest = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::debug(L"[LightSwitchService] Skipping external-change detection (schedule off, recent reload, or just enabled).");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hManualOverride)
|
||||||
|
manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
|
||||||
|
|
||||||
|
if (manualOverrideActive)
|
||||||
|
{
|
||||||
|
int lightBoundary = (settings.lightTime + settings.sunrise_offset) % 1440;
|
||||||
|
int darkBoundary = (settings.darkTime + settings.sunset_offset) % 1440;
|
||||||
|
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
nowMinutes = st.wHour * 60 + st.wMinute;
|
||||||
|
|
||||||
|
bool crossedLight = false;
|
||||||
|
bool crossedDark = false;
|
||||||
|
|
||||||
|
if (prevMinutes != -1)
|
||||||
|
{
|
||||||
|
// this means we are in a new day cycle
|
||||||
|
if (nowMinutes < prevMinutes)
|
||||||
|
{
|
||||||
|
crossedLight = (prevMinutes <= lightBoundary || nowMinutes >= lightBoundary);
|
||||||
|
crossedDark = (prevMinutes <= darkBoundary || nowMinutes >= darkBoundary);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
crossedLight = (prevMinutes < lightBoundary && nowMinutes >= lightBoundary);
|
||||||
|
crossedDark = (prevMinutes < darkBoundary && nowMinutes >= darkBoundary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crossedLight || crossedDark)
|
||||||
|
{
|
||||||
|
ResetEvent(hManualOverride);
|
||||||
|
Logger::info(L"[LightSwitchService] Manual override cleared after crossing schedule boundary.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::debug(L"[LightSwitchService] Skipping schedule due to manual override");
|
||||||
|
skipRest = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Apply Schedule Logic ───────────────────────────────────────────────────
|
// Apply theme if nothing has made us skip
|
||||||
if (!skipRest)
|
if (!skipRest)
|
||||||
{
|
{
|
||||||
|
// Next two conditionals check for any updates necessary to the sun times.
|
||||||
bool modeChangedToSunset = (prevMode != settings.scheduleMode &&
|
bool modeChangedToSunset = (prevMode != settings.scheduleMode &&
|
||||||
settings.scheduleMode == ScheduleMode::SunsetToSunrise);
|
settings.scheduleMode == ScheduleMode::SunsetToSunrise);
|
||||||
bool coordsChanged = (prevLat != settings.latitude || prevLon != settings.longitude);
|
bool coordsChanged = (prevLat != settings.latitude || prevLon != settings.longitude);
|
||||||
|
|
||||||
if ((modeChangedToSunset || coordsChanged) && settings.scheduleMode == ScheduleMode::SunsetToSunrise)
|
if ((modeChangedToSunset || coordsChanged) && settings.scheduleMode == ScheduleMode::SunsetToSunrise)
|
||||||
{
|
{
|
||||||
Logger::info(L"[LightSwitchService] Mode or coordinates changed, recalculating sun times.");
|
|
||||||
update_sun_times(settings);
|
|
||||||
SYSTEMTIME st;
|
SYSTEMTIME st;
|
||||||
GetLocalTime(&st);
|
GetLocalTime(&st);
|
||||||
|
|
||||||
|
Logger::info(L"[LightSwitchService] Mode or coordinates changed, recalculating sun times.");
|
||||||
|
update_sun_times(settings);
|
||||||
g_lastUpdatedDay = st.wDay;
|
g_lastUpdatedDay = st.wDay;
|
||||||
prevMode = settings.scheduleMode;
|
prevMode = settings.scheduleMode;
|
||||||
prevLat = settings.latitude;
|
prevLat = settings.latitude;
|
||||||
@@ -383,70 +416,23 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
|||||||
Logger::info(L"[LightSwitchService] Recalculated sun times at new day boundary.");
|
Logger::info(L"[LightSwitchService] Recalculated sun times at new day boundary.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// settings after any necessary updates.
|
||||||
LightSwitchSettings::instance().LoadSettings();
|
LightSwitchSettings::instance().LoadSettings();
|
||||||
const auto& currentSettings = LightSwitchSettings::instance().settings();
|
const auto& currentSettings = LightSwitchSettings::instance().settings();
|
||||||
|
|
||||||
wchar_t msg[160];
|
wchar_t msg[160];
|
||||||
swprintf_s(msg,
|
swprintf_s(msg,
|
||||||
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d | mode=%d",
|
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d | mode=%s",
|
||||||
st.wHour,
|
st.wHour,
|
||||||
st.wMinute,
|
st.wMinute,
|
||||||
currentSettings.lightTime / 60,
|
currentSettings.lightTime / 60,
|
||||||
currentSettings.lightTime % 60,
|
currentSettings.lightTime % 60,
|
||||||
currentSettings.darkTime / 60,
|
currentSettings.darkTime / 60,
|
||||||
currentSettings.darkTime % 60,
|
currentSettings.darkTime % 60,
|
||||||
static_cast<int>(currentSettings.scheduleMode));
|
ToString(currentSettings.scheduleMode).c_str());
|
||||||
Logger::info(msg);
|
Logger::info(msg);
|
||||||
|
|
||||||
bool manualOverrideActive = false;
|
LightSwitchSettings::instance().ApplyThemeIfNecessary();
|
||||||
if (hManualOverride)
|
|
||||||
manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
|
|
||||||
|
|
||||||
if (manualOverrideActive)
|
|
||||||
{
|
|
||||||
int lightBoundary = (currentSettings.lightTime + currentSettings.sunrise_offset) % 1440;
|
|
||||||
int darkBoundary = (currentSettings.darkTime + currentSettings.sunset_offset) % 1440;
|
|
||||||
|
|
||||||
bool crossedLight = false;
|
|
||||||
bool crossedDark = false;
|
|
||||||
|
|
||||||
if (prevMinutes != -1)
|
|
||||||
{
|
|
||||||
if (nowMinutes < prevMinutes)
|
|
||||||
{
|
|
||||||
crossedLight = (prevMinutes <= lightBoundary || nowMinutes >= lightBoundary);
|
|
||||||
crossedDark = (prevMinutes <= darkBoundary || nowMinutes >= darkBoundary);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
crossedLight = (prevMinutes < lightBoundary && nowMinutes >= lightBoundary);
|
|
||||||
crossedDark = (prevMinutes < darkBoundary && nowMinutes >= darkBoundary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger::debug(L"[LightSwitchService] prevMinutes={} nowMinutes={} light={} dark={}",
|
|
||||||
prevMinutes,
|
|
||||||
nowMinutes,
|
|
||||||
lightBoundary,
|
|
||||||
darkBoundary);
|
|
||||||
|
|
||||||
if (crossedLight || crossedDark)
|
|
||||||
{
|
|
||||||
ResetEvent(hManualOverride);
|
|
||||||
Logger::info(L"[LightSwitchService] Manual override cleared after crossing schedule boundary.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::info(L"[LightSwitchService] Skipping schedule due to manual override");
|
|
||||||
skipRest = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!skipRest)
|
|
||||||
applyTheme(nowMinutes,
|
|
||||||
currentSettings.lightTime + currentSettings.sunrise_offset,
|
|
||||||
currentSettings.darkTime + currentSettings.sunset_offset,
|
|
||||||
currentSettings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Wait For Next Minute Tick Or Stop Event ────────────────────────────────
|
// ─── Wait For Next Minute Tick Or Stop Event ────────────────────────────────
|
||||||
@@ -480,54 +466,6 @@ cleanup:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyThemeNow()
|
|
||||||
{
|
|
||||||
LightSwitchSettings::instance().LoadSettings();
|
|
||||||
const auto& settings = LightSwitchSettings::instance().settings();
|
|
||||||
|
|
||||||
SYSTEMTIME st;
|
|
||||||
GetLocalTime(&st);
|
|
||||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
|
||||||
|
|
||||||
bool shouldBeLight = false;
|
|
||||||
if (settings.lightTime < settings.darkTime)
|
|
||||||
shouldBeLight = (nowMinutes >= settings.lightTime && nowMinutes < settings.darkTime);
|
|
||||||
else
|
|
||||||
shouldBeLight = (nowMinutes >= settings.lightTime || nowMinutes < settings.darkTime);
|
|
||||||
|
|
||||||
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
|
|
||||||
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
|
|
||||||
|
|
||||||
Logger::info(L"[LightSwitchService] Applying (if needed) theme immediately due to schedule change.");
|
|
||||||
|
|
||||||
if (shouldBeLight)
|
|
||||||
{
|
|
||||||
if (settings.changeSystem && !isSystemCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetSystemTheme(true);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
|
|
||||||
}
|
|
||||||
if (settings.changeApps && !isAppsCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetAppsTheme(true);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (settings.changeSystem && isSystemCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetSystemTheme(false);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
|
|
||||||
}
|
|
||||||
if (settings.changeApps && isAppsCurrentlyLight)
|
|
||||||
{
|
|
||||||
SetAppsTheme(false);
|
|
||||||
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||||
{
|
{
|
||||||
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="LightSwitchService.cpp" />
|
<ClCompile Include="LightSwitchService.cpp" />
|
||||||
<ClCompile Include="LightSwitchServiceObserver.cpp" />
|
|
||||||
<ClCompile Include="LightSwitchSettings.cpp" />
|
<ClCompile Include="LightSwitchSettings.cpp" />
|
||||||
<ClCompile Include="SettingsConstants.cpp" />
|
<ClCompile Include="SettingsConstants.cpp" />
|
||||||
<ClCompile Include="ThemeHelper.cpp" />
|
<ClCompile Include="ThemeHelper.cpp" />
|
||||||
@@ -85,7 +84,6 @@
|
|||||||
<ResourceCompile Include="LightSwitchService.rc" />
|
<ResourceCompile Include="LightSwitchService.rc" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="LightSwitchServiceObserver.h" />
|
|
||||||
<ClInclude Include="LightSwitchSettings.h" />
|
<ClInclude Include="LightSwitchSettings.h" />
|
||||||
<ClInclude Include="SettingsConstants.h" />
|
<ClInclude Include="SettingsConstants.h" />
|
||||||
<ClInclude Include="SettingsObserver.h" />
|
<ClInclude Include="SettingsObserver.h" />
|
||||||
|
|||||||
@@ -33,9 +33,6 @@
|
|||||||
<ClCompile Include="WinHookEventIDs.cpp">
|
<ClCompile Include="WinHookEventIDs.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="LightSwitchServiceObserver.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="ThemeScheduler.h">
|
<ClInclude Include="ThemeScheduler.h">
|
||||||
@@ -56,9 +53,6 @@
|
|||||||
<ClInclude Include="WinHookEventIDs.h">
|
<ClInclude Include="WinHookEventIDs.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="LightSwitchServiceObserver.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
#include "LightSwitchServiceObserver.h"
|
|
||||||
#include <logger.h>
|
|
||||||
#include "LightSwitchSettings.h"
|
|
||||||
|
|
||||||
// These are defined elsewhere in your service module (ServiceWorkerThread.cpp)
|
|
||||||
extern int g_lastUpdatedDay;
|
|
||||||
void ApplyThemeNow();
|
|
||||||
|
|
||||||
void LightSwitchServiceObserver::SettingsUpdate(SettingId id)
|
|
||||||
{
|
|
||||||
Logger::info(L"[LightSwitchService] Setting changed: {}", static_cast<int>(id));
|
|
||||||
g_lastUpdatedDay = -1;
|
|
||||||
ApplyThemeNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LightSwitchServiceObserver::WantsToBeNotified(SettingId id) const noexcept
|
|
||||||
{
|
|
||||||
switch (id)
|
|
||||||
{
|
|
||||||
case SettingId::LightTime:
|
|
||||||
case SettingId::DarkTime:
|
|
||||||
case SettingId::ScheduleMode:
|
|
||||||
case SettingId::Sunrise_Offset:
|
|
||||||
case SettingId::Sunset_Offset:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "SettingsObserver.h"
|
|
||||||
|
|
||||||
// The LightSwitchServiceObserver reacts when LightSwitchSettings changes.
|
|
||||||
class LightSwitchServiceObserver : public SettingsObserver
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit LightSwitchServiceObserver(std::unordered_set<SettingId> observedSettings) :
|
|
||||||
SettingsObserver(std::move(observedSettings))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsUpdate(SettingId id) override;
|
|
||||||
bool WantsToBeNotified(SettingId id) const noexcept override;
|
|
||||||
};
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <common/utils/json.h>
|
#include <common/utils/json.h>
|
||||||
#include <common/SettingsAPI/settings_helpers.h>
|
#include <common/SettingsAPI/settings_helpers.h>
|
||||||
#include "SettingsObserver.h"
|
#include "SettingsObserver.h"
|
||||||
|
#include "ThemeHelper.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <WinHookEventIDs.h>
|
#include <WinHookEventIDs.h>
|
||||||
@@ -38,13 +38,80 @@ void LightSwitchSettings::InitFileWatcher()
|
|||||||
m_settingsFileWatcher = std::make_unique<FileWatcher>(
|
m_settingsFileWatcher = std::make_unique<FileWatcher>(
|
||||||
GetSettingsFileName(),
|
GetSettingsFileName(),
|
||||||
[this]() {
|
[this]() {
|
||||||
Logger::info(L"[LightSwitchSettings] Settings file changed, signaling event.");
|
using namespace std::chrono;
|
||||||
LoadSettings();
|
|
||||||
SetEvent(m_settingsChangedEvent);
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_debounceMutex);
|
||||||
|
m_lastChangeTime = steady_clock::now();
|
||||||
|
if (m_debouncePending)
|
||||||
|
return;
|
||||||
|
m_debouncePending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_debounceThread = std::jthread([this](std::stop_token stop) {
|
||||||
|
using namespace std::chrono;
|
||||||
|
while (!stop.stop_requested())
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(seconds(3));
|
||||||
|
|
||||||
|
auto elapsed = steady_clock::now() - m_lastChangeTime;
|
||||||
|
if (elapsed >= seconds(1))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_debounceMutex);
|
||||||
|
m_debouncePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info(L"[LightSwitchSettings] Settings file stabilized, reloading.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadSettings();
|
||||||
|
ApplyThemeIfNecessary();
|
||||||
|
SetEvent(m_settingsChangedEvent);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::wstring wmsg;
|
||||||
|
wmsg.assign(e.what(), e.what() + strlen(e.what()));
|
||||||
|
Logger::error(L"[LightSwitchSettings] Exception during debounced reload: {}", wmsg);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LightSwitchSettings::~LightSwitchSettings()
|
||||||
|
{
|
||||||
|
Logger::info(L"[LightSwitchSettings] Cleaning up settings resources...");
|
||||||
|
|
||||||
|
// Stop and join the debounce thread (std::jthread auto-joins, but we can signal stop too)
|
||||||
|
if (m_debounceThread.joinable())
|
||||||
|
{
|
||||||
|
m_debounceThread.request_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the file watcher so it closes file handles and background threads
|
||||||
|
if (m_settingsFileWatcher)
|
||||||
|
{
|
||||||
|
m_settingsFileWatcher.reset();
|
||||||
|
Logger::info(L"[LightSwitchSettings] File watcher stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the Windows event handle
|
||||||
|
if (m_settingsChangedEvent)
|
||||||
|
{
|
||||||
|
CloseHandle(m_settingsChangedEvent);
|
||||||
|
m_settingsChangedEvent = nullptr;
|
||||||
|
Logger::info(L"[LightSwitchSettings] Settings changed event closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info(L"[LightSwitchSettings] Cleanup complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
|
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
|
||||||
{
|
{
|
||||||
m_observers.insert(&observer);
|
m_observers.insert(&observer);
|
||||||
@@ -73,6 +140,7 @@ HANDLE LightSwitchSettings::GetSettingsChangedEvent() const
|
|||||||
|
|
||||||
void LightSwitchSettings::LoadSettings()
|
void LightSwitchSettings::LoadSettings()
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_settingsMutex);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PowerToysSettings::PowerToyValues values =
|
PowerToysSettings::PowerToyValues values =
|
||||||
@@ -181,4 +249,49 @@ void LightSwitchSettings::LoadSettings()
|
|||||||
{
|
{
|
||||||
// Keeps defaults if load fails
|
// Keeps defaults if load fails
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightSwitchSettings::ApplyThemeIfNecessary()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_settingsMutex);
|
||||||
|
|
||||||
|
SYSTEMTIME st;
|
||||||
|
GetLocalTime(&st);
|
||||||
|
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||||
|
|
||||||
|
bool shouldBeLight = false;
|
||||||
|
if (m_settings.lightTime < m_settings.darkTime)
|
||||||
|
shouldBeLight = (nowMinutes >= m_settings.lightTime && nowMinutes < m_settings.darkTime);
|
||||||
|
else
|
||||||
|
shouldBeLight = (nowMinutes >= m_settings.lightTime || nowMinutes < m_settings.darkTime);
|
||||||
|
|
||||||
|
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
|
||||||
|
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
|
||||||
|
|
||||||
|
if (shouldBeLight)
|
||||||
|
{
|
||||||
|
if (m_settings.changeSystem && !isSystemCurrentlyLight)
|
||||||
|
{
|
||||||
|
SetSystemTheme(true);
|
||||||
|
Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
|
||||||
|
}
|
||||||
|
if (m_settings.changeApps && !isAppsCurrentlyLight)
|
||||||
|
{
|
||||||
|
SetAppsTheme(true);
|
||||||
|
Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_settings.changeSystem && isSystemCurrentlyLight)
|
||||||
|
{
|
||||||
|
SetSystemTheme(false);
|
||||||
|
Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
|
||||||
|
}
|
||||||
|
if (m_settings.changeApps && isAppsCurrentlyLight)
|
||||||
|
{
|
||||||
|
SetAppsTheme(false);
|
||||||
|
Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
#include <common/SettingsAPI/FileWatcher.h>
|
#include <common/SettingsAPI/FileWatcher.h>
|
||||||
#include <common/SettingsAPI/settings_objects.h>
|
#include <common/SettingsAPI/settings_objects.h>
|
||||||
#include <SettingsConstants.h>
|
#include <SettingsConstants.h>
|
||||||
@@ -78,12 +81,13 @@ public:
|
|||||||
void RemoveObserver(SettingsObserver& observer);
|
void RemoveObserver(SettingsObserver& observer);
|
||||||
|
|
||||||
void LoadSettings();
|
void LoadSettings();
|
||||||
|
void ApplyThemeIfNecessary();
|
||||||
|
|
||||||
HANDLE GetSettingsChangedEvent() const;
|
HANDLE GetSettingsChangedEvent() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LightSwitchSettings();
|
LightSwitchSettings();
|
||||||
~LightSwitchSettings() = default;
|
~LightSwitchSettings();
|
||||||
|
|
||||||
LightSwitchConfig m_settings;
|
LightSwitchConfig m_settings;
|
||||||
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
|
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
|
||||||
@@ -92,4 +96,11 @@ private:
|
|||||||
void NotifyObservers(SettingId id) const;
|
void NotifyObservers(SettingId id) const;
|
||||||
|
|
||||||
HANDLE m_settingsChangedEvent = nullptr;
|
HANDLE m_settingsChangedEvent = nullptr;
|
||||||
|
mutable std::mutex m_settingsMutex;
|
||||||
|
|
||||||
|
// Debounce state
|
||||||
|
std::atomic_bool m_debouncePending{ false };
|
||||||
|
std::mutex m_debounceMutex;
|
||||||
|
std::chrono::steady_clock::time_point m_lastChangeTime{};
|
||||||
|
std::jthread m_debounceThread;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user