[Light Switch] Follow Night Light mode (#43683)

<!-- 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
Introduces a new mode that will have Light Switch follow Windows Night
Light.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: https://github.com/microsoft/PowerToys/issues/42457
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **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: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Strictly follows the state of Night Light. When NL is on, LS will be
switch to dark mode and when NL is off, LS will switch to light mode
(with respect to the users system/app selection).

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Turn on Follow Night Light mode
Change night light!

## Notes
This commit is contained in:
Jaylyn Barbee
2025-12-10 08:15:34 -05:00
committed by GitHub
parent 97c1de8bf6
commit f822826cf1
18 changed files with 413 additions and 46 deletions

View File

@@ -144,6 +144,8 @@ BLENDFUNCTION
blittable blittable
Blockquotes Blockquotes
blt blt
bluelightreduction
bluelightreductionstate
BLURBEHIND BLURBEHIND
BLURREGION BLURREGION
bmi bmi
@@ -1115,6 +1117,7 @@ NEWPLUSSHELLEXTENSIONWIN
newrow newrow
nicksnettravels nicksnettravels
NIF NIF
nightlight
NLog NLog
NLSTEXT NLSTEXT
NMAKE NMAKE

View File

@@ -50,6 +50,7 @@ enum class ScheduleMode
Off, Off,
FixedHours, FixedHours,
SunsetToSunrise, SunsetToSunrise,
FollowNightLight,
// add more later // add more later
}; };
@@ -61,6 +62,8 @@ inline std::wstring ToString(ScheduleMode mode)
return L"SunsetToSunrise"; return L"SunsetToSunrise";
case ScheduleMode::FixedHours: case ScheduleMode::FixedHours:
return L"FixedHours"; return L"FixedHours";
case ScheduleMode::FollowNightLight:
return L"FollowNightLight";
default: default:
return L"Off"; return L"Off";
} }
@@ -72,6 +75,8 @@ inline ScheduleMode FromString(const std::wstring& str)
return ScheduleMode::SunsetToSunrise; return ScheduleMode::SunsetToSunrise;
if (str == L"FixedHours") if (str == L"FixedHours")
return ScheduleMode::FixedHours; return ScheduleMode::FixedHours;
if (str == L"FollowNightLight")
return ScheduleMode::FollowNightLight;
return ScheduleMode::Off; return ScheduleMode::Off;
} }
@@ -167,7 +172,9 @@ public:
ToString(g_settings.m_scheduleMode), ToString(g_settings.m_scheduleMode),
{ { L"Off", L"Disable the schedule" }, { { L"Off", L"Disable the schedule" },
{ L"FixedHours", L"Set hours manually" }, { L"FixedHours", L"Set hours manually" },
{ L"SunsetToSunrise", L"Use sunrise/sunset times" } }); { L"SunsetToSunrise", L"Use sunrise/sunset times" },
{ L"FollowNightLight", L"Follow Windows Night Light state" }
});
// Integer spinners // Integer spinners
settings.add_int_spinner( settings.add_int_spinner(

View File

@@ -13,10 +13,12 @@
#include <utils/logger_helper.h> #include <utils/logger_helper.h>
#include "LightSwitchStateManager.h" #include "LightSwitchStateManager.h"
#include <LightSwitchUtils.h> #include <LightSwitchUtils.h>
#include <NightLightRegistryObserver.h>
SERVICE_STATUS g_ServiceStatus = {}; SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr; SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
HANDLE g_ServiceStopEvent = nullptr; HANDLE g_ServiceStopEvent = nullptr;
static LightSwitchStateManager* g_stateManagerPtr = nullptr;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv); VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl); VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
@@ -168,7 +170,15 @@ static void DetectAndHandleExternalThemeChange(LightSwitchStateManager& stateMan
} }
// Use shared helper (handles wraparound logic) // Use shared helper (handles wraparound logic)
bool shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark); bool shouldBeLight = false;
if (s.scheduleMode == ScheduleMode::FollowNightLight)
{
shouldBeLight = !IsNightLightEnabled();
}
else
{
shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
}
// Compare current system/apps theme // Compare current system/apps theme
bool currentSystemLight = GetCurrentSystemTheme(); bool currentSystemLight = GetCurrentSystemTheme();
@@ -199,15 +209,40 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
// Initialization // Initialization
// ──────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────
static LightSwitchStateManager stateManager; static LightSwitchStateManager stateManager;
g_stateManagerPtr = &stateManager;
LightSwitchSettings::instance().InitFileWatcher(); LightSwitchSettings::instance().InitFileWatcher();
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");
HANDLE hSettingsChanged = LightSwitchSettings::instance().GetSettingsChangedEvent(); HANDLE hSettingsChanged = LightSwitchSettings::instance().GetSettingsChangedEvent();
static std::unique_ptr<NightLightRegistryObserver> g_nightLightWatcher;
LightSwitchSettings::instance().LoadSettings(); LightSwitchSettings::instance().LoadSettings();
const auto& settings = LightSwitchSettings::instance().settings(); const auto& settings = LightSwitchSettings::instance().settings();
// after loading settings:
bool nightLightNeeded = (settings.scheduleMode == ScheduleMode::FollowNightLight);
if (nightLightNeeded && !g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Starting Night Light registry watcher...");
g_nightLightWatcher = std::make_unique<NightLightRegistryObserver>(
HKEY_CURRENT_USER,
NIGHT_LIGHT_REGISTRY_PATH,
[]() {
if (g_stateManagerPtr)
g_stateManagerPtr->OnNightLightChange();
});
}
else if (!nightLightNeeded && g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Stopping Night Light registry watcher...");
g_nightLightWatcher->Stop();
g_nightLightWatcher.reset();
}
SYSTEMTIME st; SYSTEMTIME st;
GetLocalTime(&st); GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute; int nowMinutes = st.wHour * 60 + st.wMinute;
@@ -274,6 +309,31 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
ResetEvent(hSettingsChanged); ResetEvent(hSettingsChanged);
LightSwitchSettings::instance().LoadSettings(); LightSwitchSettings::instance().LoadSettings();
stateManager.OnSettingsChanged(); stateManager.OnSettingsChanged();
const auto& settings = LightSwitchSettings::instance().settings();
bool nightLightNeeded = (settings.scheduleMode == ScheduleMode::FollowNightLight);
if (nightLightNeeded && !g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Starting Night Light registry watcher...");
g_nightLightWatcher = std::make_unique<NightLightRegistryObserver>(
HKEY_CURRENT_USER,
NIGHT_LIGHT_REGISTRY_PATH,
[]() {
if (g_stateManagerPtr)
g_stateManagerPtr->OnNightLightChange();
});
stateManager.OnNightLightChange();
}
else if (!nightLightNeeded && g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Stopping Night Light registry watcher...");
g_nightLightWatcher->Stop();
g_nightLightWatcher.reset();
}
continue; continue;
} }
} }
@@ -285,6 +345,11 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
CloseHandle(hManualOverride); CloseHandle(hManualOverride);
if (hParent) if (hParent)
CloseHandle(hParent); CloseHandle(hParent);
if (g_nightLightWatcher)
{
g_nightLightWatcher->Stop();
g_nightLightWatcher.reset();
}
Logger::info(L"[LightSwitchService] Worker thread exiting cleanly."); Logger::info(L"[LightSwitchService] Worker thread exiting cleanly.");
return 0; return 0;

View File

@@ -76,6 +76,7 @@
<ClCompile Include="LightSwitchService.cpp" /> <ClCompile Include="LightSwitchService.cpp" />
<ClCompile Include="LightSwitchSettings.cpp" /> <ClCompile Include="LightSwitchSettings.cpp" />
<ClCompile Include="LightSwitchStateManager.cpp" /> <ClCompile Include="LightSwitchStateManager.cpp" />
<ClCompile Include="NightLightRegistryObserver.cpp" />
<ClCompile Include="SettingsConstants.cpp" /> <ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" /> <ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" /> <ClCompile Include="ThemeScheduler.cpp" />
@@ -88,6 +89,7 @@
<ClInclude Include="LightSwitchSettings.h" /> <ClInclude Include="LightSwitchSettings.h" />
<ClInclude Include="LightSwitchStateManager.h" /> <ClInclude Include="LightSwitchStateManager.h" />
<ClInclude Include="LightSwitchUtils.h" /> <ClInclude Include="LightSwitchUtils.h" />
<ClInclude Include="NightLightRegistryObserver.h" />
<ClInclude Include="SettingsConstants.h" /> <ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" /> <ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" /> <ClInclude Include="ThemeHelper.h" />

View File

@@ -36,6 +36,9 @@
<ClCompile Include="LightSwitchStateManager.cpp"> <ClCompile Include="LightSwitchStateManager.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="NightLightRegistryObserver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="ThemeScheduler.h"> <ClInclude Include="ThemeScheduler.h">
@@ -62,6 +65,9 @@
<ClInclude Include="LightSwitchUtils.h"> <ClInclude Include="LightSwitchUtils.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="NightLightRegistryObserver.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" /> <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />

View File

@@ -19,7 +19,8 @@ enum class ScheduleMode
{ {
Off, Off,
FixedHours, FixedHours,
SunsetToSunrise SunsetToSunrise,
FollowNightLight,
// Add more in the future // Add more in the future
}; };
@@ -31,6 +32,8 @@ inline std::wstring ToString(ScheduleMode mode)
return L"FixedHours"; return L"FixedHours";
case ScheduleMode::SunsetToSunrise: case ScheduleMode::SunsetToSunrise:
return L"SunsetToSunrise"; return L"SunsetToSunrise";
case ScheduleMode::FollowNightLight:
return L"FollowNightLight";
default: default:
return L"Off"; return L"Off";
} }
@@ -42,6 +45,8 @@ inline ScheduleMode FromString(const std::wstring& str)
return ScheduleMode::SunsetToSunrise; return ScheduleMode::SunsetToSunrise;
if (str == L"FixedHours") if (str == L"FixedHours")
return ScheduleMode::FixedHours; return ScheduleMode::FixedHours;
if (str == L"FollowNightLight")
return ScheduleMode::FollowNightLight;
else else
return ScheduleMode::Off; return ScheduleMode::Off;
} }

View File

@@ -31,8 +31,11 @@ void LightSwitchStateManager::OnSettingsChanged()
void LightSwitchStateManager::OnTick(int currentMinutes) void LightSwitchStateManager::OnTick(int currentMinutes)
{ {
std::lock_guard<std::mutex> lock(_stateMutex); std::lock_guard<std::mutex> lock(_stateMutex);
if (_state.lastAppliedMode != ScheduleMode::FollowNightLight)
{
EvaluateAndApplyIfNeeded(); EvaluateAndApplyIfNeeded();
} }
}
// Called when manual override is triggered // Called when manual override is triggered
void LightSwitchStateManager::OnManualOverride() void LightSwitchStateManager::OnManualOverride()
@@ -56,6 +59,36 @@ void LightSwitchStateManager::OnManualOverride()
EvaluateAndApplyIfNeeded(); EvaluateAndApplyIfNeeded();
} }
// Runs with the registry observer detects a change in Night Light settings.
void LightSwitchStateManager::OnNightLightChange()
{
std::lock_guard<std::mutex> lock(_stateMutex);
bool newNightLightState = IsNightLightEnabled();
// In Follow Night Light mode, treat a Night Light toggle as a boundary
if (_state.lastAppliedMode == ScheduleMode::FollowNightLight && _state.isManualOverride)
{
Logger::info(L"[LightSwitchStateManager] Night Light changed while manual override active; "
L"treating as a boundary and clearing manual override.");
_state.isManualOverride = false;
}
if (newNightLightState != _state.isNightLightActive)
{
Logger::info(L"[LightSwitchStateManager] Night Light toggled to {}",
newNightLightState ? L"ON" : L"OFF");
_state.isNightLightActive = newNightLightState;
}
else
{
Logger::debug(L"[LightSwitchStateManager] Night Light change event fired, but no actual change.");
}
EvaluateAndApplyIfNeeded();
}
// Helpers // Helpers
bool LightSwitchStateManager::CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon) bool LightSwitchStateManager::CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon)
{ {
@@ -194,7 +227,15 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
_state.lastAppliedMode = _currentSettings.scheduleMode; _state.lastAppliedMode = _currentSettings.scheduleMode;
bool shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes); bool shouldBeLight = false;
if (_currentSettings.scheduleMode == ScheduleMode::FollowNightLight)
{
shouldBeLight = !_state.isNightLightActive;
}
else
{
shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
}
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight); bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight); bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
@@ -227,6 +268,3 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
_state.lastTickMinutes = now; _state.lastTickMinutes = now;
} }

View File

@@ -9,6 +9,7 @@ struct LightSwitchState
bool isManualOverride = false; bool isManualOverride = false;
bool isSystemLightActive = false; bool isSystemLightActive = false;
bool isAppsLightActive = false; bool isAppsLightActive = false;
bool isNightLightActive = false;
int lastEvaluatedDay = -1; int lastEvaluatedDay = -1;
int lastTickMinutes = -1; int lastTickMinutes = -1;
@@ -32,6 +33,9 @@ public:
// Called when manual override is toggled (via shortcut or system change). // Called when manual override is toggled (via shortcut or system change).
void OnManualOverride(); void OnManualOverride();
// Called when night light changes in windows settings
void OnNightLightChange();
// Initial sync at startup to align internal state with system theme // Initial sync at startup to align internal state with system theme
void SyncInitialThemeState(); void SyncInitialThemeState();

View File

@@ -0,0 +1 @@
#include "NightLightRegistryObserver.h"

View File

@@ -0,0 +1,134 @@
#pragma once
#include <wtypes.h>
#include <string>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
class NightLightRegistryObserver
{
public:
NightLightRegistryObserver(HKEY root, const std::wstring& subkey, std::function<void()> callback) :
_root(root), _subkey(subkey), _callback(std::move(callback)), _stop(false)
{
_thread = std::thread([this]() { this->Run(); });
}
~NightLightRegistryObserver()
{
Stop();
}
void Stop()
{
_stop = true;
{
std::lock_guard<std::mutex> lock(_mutex);
if (_event)
SetEvent(_event);
}
if (_thread.joinable())
_thread.join();
std::lock_guard<std::mutex> lock(_mutex);
if (_hKey)
{
RegCloseKey(_hKey);
_hKey = nullptr;
}
if (_event)
{
CloseHandle(_event);
_event = nullptr;
}
}
private:
void Run()
{
{
std::lock_guard<std::mutex> lock(_mutex);
if (RegOpenKeyExW(_root, _subkey.c_str(), 0, KEY_NOTIFY, &_hKey) != ERROR_SUCCESS)
return;
_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if (!_event)
{
RegCloseKey(_hKey);
_hKey = nullptr;
return;
}
}
while (!_stop)
{
HKEY hKeyLocal = nullptr;
HANDLE eventLocal = nullptr;
{
std::lock_guard<std::mutex> lock(_mutex);
if (_stop)
break;
hKeyLocal = _hKey;
eventLocal = _event;
}
if (!hKeyLocal || !eventLocal)
break;
if (_stop)
break;
if (RegNotifyChangeKeyValue(hKeyLocal, FALSE, REG_NOTIFY_CHANGE_LAST_SET, eventLocal, TRUE) != ERROR_SUCCESS)
break;
DWORD wait = WaitForSingleObject(eventLocal, INFINITE);
if (_stop || wait == WAIT_FAILED)
break;
ResetEvent(eventLocal);
if (!_stop && _callback)
{
try
{
_callback();
}
catch (...)
{
}
}
}
{
std::lock_guard<std::mutex> lock(_mutex);
if (_hKey)
{
RegCloseKey(_hKey);
_hKey = nullptr;
}
if (_event)
{
CloseHandle(_event);
_event = nullptr;
}
}
}
HKEY _root;
std::wstring _subkey;
std::function<void()> _callback;
HANDLE _event = nullptr;
HKEY _hKey = nullptr;
std::thread _thread;
std::atomic<bool> _stop;
std::mutex _mutex;
};

View File

@@ -12,3 +12,6 @@ enum class SettingId
ChangeSystem, ChangeSystem,
ChangeApps ChangeApps
}; };
constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";

View File

@@ -3,6 +3,7 @@
#include <logger/logger.h> #include <logger/logger.h>
#include <utils/logger_helper.h> #include <utils/logger_helper.h>
#include "ThemeHelper.h" #include "ThemeHelper.h"
#include <SettingsConstants.h>
// Controls changing the themes. // Controls changing the themes.
@@ -10,7 +11,7 @@ static void ResetColorPrevalence()
{ {
HKEY hKey; HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", PERSONALIZATION_REGISTRY_PATH,
0, 0,
KEY_SET_VALUE, KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS) &hKey) == ERROR_SUCCESS)
@@ -31,7 +32,7 @@ void SetAppsTheme(bool mode)
{ {
HKEY hKey; HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", PERSONALIZATION_REGISTRY_PATH,
0, 0,
KEY_SET_VALUE, KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS) &hKey) == ERROR_SUCCESS)
@@ -50,7 +51,7 @@ void SetSystemTheme(bool mode)
{ {
HKEY hKey; HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER, if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", PERSONALIZATION_REGISTRY_PATH,
0, 0,
KEY_SET_VALUE, KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS) &hKey) == ERROR_SUCCESS)
@@ -79,7 +80,7 @@ bool GetCurrentSystemTheme()
DWORD size = sizeof(value); DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER, if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", PERSONALIZATION_REGISTRY_PATH,
0, 0,
KEY_READ, KEY_READ,
&hKey) == ERROR_SUCCESS) &hKey) == ERROR_SUCCESS)
@@ -98,7 +99,7 @@ bool GetCurrentAppsTheme()
DWORD size = sizeof(value); DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER, if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", PERSONALIZATION_REGISTRY_PATH,
0, 0,
KEY_READ, KEY_READ,
&hKey) == ERROR_SUCCESS) &hKey) == ERROR_SUCCESS)
@@ -109,3 +110,30 @@ bool GetCurrentAppsTheme()
return value == 1; // true = light, false = dark return value == 1; // true = light, false = dark
} }
bool IsNightLightEnabled()
{
HKEY hKey;
const wchar_t* path = NIGHT_LIGHT_REGISTRY_PATH;
if (RegOpenKeyExW(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
// RegGetValueW will set size to the size of the data and we expect that to be at least 25 bytes (we need to access bytes 23 and 24)
DWORD size = 0;
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, nullptr, &size) != ERROR_SUCCESS || size < 25)
{
RegCloseKey(hKey);
return false;
}
std::vector<BYTE> data(size);
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, data.data(), &size) != ERROR_SUCCESS)
{
RegCloseKey(hKey);
return false;
}
RegCloseKey(hKey);
return data[23] == 0x10 && data[24] == 0x00;
}

View File

@@ -3,3 +3,4 @@ void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark); void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme(); bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme(); bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -11,6 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
{ {
public const string ColorsSettings = "ms-settings:colors"; public const string ColorsSettings = "ms-settings:colors";
public const string DiagnosticsAndFeedback = "ms-settings:privacy-feedback"; public const string DiagnosticsAndFeedback = "ms-settings:privacy-feedback";
public const string NightLightSettings = "ms-settings:nightlight";
public static string AnimationsSettings => OSVersionHelper.IsWindows11() public static string AnimationsSettings => OSVersionHelper.IsWindows11()
? "ms-settings:easeofaccess-visualeffects" ? "ms-settings:easeofaccess-visualeffects"

View File

@@ -67,6 +67,10 @@
x:Uid="LightSwitch_ModeSunsetToSunrise" x:Uid="LightSwitch_ModeSunsetToSunrise"
AutomationProperties.AutomationId="SunCBItem_LightSwitch" AutomationProperties.AutomationId="SunCBItem_LightSwitch"
Tag="SunsetToSunrise" /> Tag="SunsetToSunrise" />
<ComboBoxItem
x:Uid="LightSwitch_ModeFollowNightLight"
AutomationProperties.AutomationId="FollowNightLightCBItem_LightSwitch"
Tag="FollowNightLight" />
</ComboBox> </ComboBox>
<tkcontrols:SettingsExpander.Items> <tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard <tkcontrols:SettingsCard
@@ -152,6 +156,33 @@
IsOpen="True" IsOpen="True"
Severity="Informational" /> Severity="Informational" />
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Name="FollowNightLightCard"
Padding="0"
HorizontalContentAlignment="Stretch"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
ContentAlignment="Vertical"
Visibility="Collapsed">
<InfoBar
Background="Transparent"
BorderThickness="0"
IsClosable="False"
IsOpen="True"
Severity="Informational">
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center">
<Run x:Uid="LightSwitch_FollowNightLightCardMessage" />
</TextBlock>
<HyperlinkButton
x:Uid="LightSwitch_NightLightSettingsButton"
Margin="3,0,0,0"
Padding="0"
Click="OpenNightLightSettings_Click" />
</StackPanel>
</InfoBar>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items> </tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander> </tkcontrols:SettingsExpander>
<InfoBar <InfoBar
@@ -350,13 +381,25 @@
<Setter Target="SunLocation_Card.Visibility" Value="Visible" /> <Setter Target="SunLocation_Card.Visibility" Value="Visible" />
<Setter Target="SunOffset_Card.Visibility" Value="Visible" /> <Setter Target="SunOffset_Card.Visibility" Value="Visible" />
<Setter Target="NoScheduleCard.Visibility" Value="Collapsed" /> <Setter Target="NoScheduleCard.Visibility" Value="Collapsed" />
<Setter Target="FollowNightLightCard.Visibility" Value="Collapsed" />
</VisualState.Setters> </VisualState.Setters>
</VisualState> </VisualState>
<VisualState x:Name="ManualState"> <VisualState x:Name="ManualState">
<VisualState.Setters> <VisualState.Setters>
<Setter Target="Fixed_TurnOnCard.Visibility" Value="Visible" /> <Setter Target="Fixed_TurnOnCard.Visibility" Value="Visible" />
<Setter Target="Fixed_TurnOffCard.Visibility" Value="Visible" /> <Setter Target="Fixed_TurnOffCard.Visibility" Value="Visible" />
<Setter Target="NoScheduleCard.Visibility" Value="Collapsed" /> <Setter Target="NoScheduleCard.Visibility" Value="Collapsed" />
<Setter Target="FollowNightLightCard.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="FollowNightLightState">
<VisualState.Setters>
<Setter Target="Fixed_TurnOnCard.Visibility" Value="Collapsed" />
<Setter Target="Fixed_TurnOffCard.Visibility" Value="Collapsed" />
<Setter Target="NoScheduleCard.Visibility" Value="Collapsed" />
<Setter Target="FollowNightLightCard.Visibility" Value="Visible" />
</VisualState.Setters> </VisualState.Setters>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>

View File

@@ -355,6 +355,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
VisualStateManager.GoToState(this, "SunsetToSunriseState", true); VisualStateManager.GoToState(this, "SunsetToSunriseState", true);
this.SunriseModeChartState(); this.SunriseModeChartState();
break; break;
case "FollowNightLight":
VisualStateManager.GoToState(this, "FollowNightLightState", true);
TimelineCard.Visibility = Visibility.Collapsed;
break;
default: default:
VisualStateManager.GoToState(this, "OffState", true); VisualStateManager.GoToState(this, "OffState", true);
this.TimelineCard.Visibility = Visibility.Collapsed; this.TimelineCard.Visibility = Visibility.Collapsed;
@@ -362,6 +366,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
} }
} }
private void OpenNightLightSettings_Click(object sender, RoutedEventArgs e)
{
try
{
Helpers.StartProcessHelper.Start(Helpers.StartProcessHelper.NightLightSettings);
}
catch (Exception ex)
{
Logger.LogError("Error while trying to open the system night light settings", ex);
}
}
private void SunriseModeChartState() private void SunriseModeChartState()
{ {
if (this.ViewModel.Latitude != "0.0" && this.ViewModel.Longitude != "0.0") if (this.ViewModel.Latitude != "0.0" && this.ViewModel.Longitude != "0.0")

View File

@@ -5760,4 +5760,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<value>A modern UI built with Fluent Design</value> <value>A modern UI built with Fluent Design</value>
<comment>Fluent Design is a product name, do not loc</comment> <comment>Fluent Design is a product name, do not loc</comment>
</data> </data>
<data name="LightSwitch_ModeFollowNightLight.Content" xml:space="preserve">
<value>Follow Night Light</value>
</data>
<data name="LightSwitch_NightLightSettingsButton.Content" xml:space="preserve">
<value>Personalize your Night Light settings.</value>
</data>
<data name="LightSwitch_FollowNightLightCardMessage.Text" xml:space="preserve">
<value>Following Night Light settings.</value>
</data>
</root> </root>

View File

@@ -42,6 +42,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
"Off", "Off",
"FixedHours", "FixedHours",
"SunsetToSunrise", "SunsetToSunrise",
"FollowNightLight",
}; };
_toggleThemeHotkey = _moduleSettings.Properties.ToggleThemeHotkey.Value; _toggleThemeHotkey = _moduleSettings.Properties.ToggleThemeHotkey.Value;