Compare commits

...

4 Commits

Author SHA1 Message Date
Gordon Lam (SH)
e8d0c1b312 Update VSCode task for streamline build + fix prompts syntax error 2026-01-08 14:47:24 +08:00
Kai Tao
9c58574484 Revert "[Light Switch] Switch desktop wallpapers with the Light/Dark mode" (#44588)
This uses IVirtualDesktopManagerInternal*, which is an undocumented
Windows Shell internal API.
These interfaces are not stable and can change across Windows updates,
so using them in PowerToys carries some long-term risk
2026-01-08 10:14:43 +08:00
Kai Tao
08d4689ec5 Add jiri and maintain latest community members (#44580)
<!-- 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
1. Add jiri
2. Update community member order by alphabetic
3. Maintain latest community state

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

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **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

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-08 09:08:03 +08:00
leileizhang
03d1dfca2d [UI tests] Fix "UI tests pipeline build failed " (#44574) 2026-01-07 08:08:55 -08:00
36 changed files with 473 additions and 1166 deletions

View File

@@ -457,7 +457,6 @@ DWMWINDOWATTRIBUTE
DWMWINDOWMAXIMIZEDCHANGE
DWORDLONG
dworigin
DWPOS
dwrite
dxgi
eab
@@ -678,7 +677,6 @@ hicon
HICONSM
HIDEREADONLY
HIDEWINDOW
hif
Hif
HIMAGELIST
himl
@@ -840,7 +838,6 @@ jpe
jpnime
Jsons
jsonval
jxl
jxr
keybd
KEYBDDATA
@@ -1467,7 +1464,6 @@ recyclebin
Redist
Reencode
REFCLSID
REFGUID
REFIID
REGCLS
regfile
@@ -1574,7 +1570,6 @@ SETBUDDYINT
SETCONTEXT
SETCURSEL
setcursor
SETDESKWALLPAPER
SETFOCUS
SETFOREGROUND
SETHOTKEY
@@ -1663,7 +1658,6 @@ SKEXP
SKIPOWNPROCESS
sku
SLGP
slideshow
sln
slnf
slnx
@@ -1902,7 +1896,6 @@ unwide
unzoom
UOffset
UOI
UPDATEINIFILE
UPDATENOW
UPDATEREGISTRY
updown

View File

@@ -48,9 +48,9 @@ This is the top-level guide for AI changes. Keep edits small, follow existing pa
- `doc/devdocs/modules/readme.md`
# Language style rules
- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`.
- Always enforce repo analyzers: `src/.editorconfig` plus any `stylecop.json`.
- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers.
- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability).
- C++ code honors `src/.clang-format` for formatting.
- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags.
- YAML files indent two spaces and add comments for complex settings while keeping keys clear.
- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer.

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
agent: 'agent'
model: GPT-5.1-Codex-Max
description: 'Generate an 80-character git commit title for the local diff.'
---

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
agent: 'agent'
model: GPT-5.1-Codex-Max
description: 'Generate a PowerToys-ready pull request description from the local diff.'
---

View File

@@ -1,7 +1,6 @@
---
mode: 'agent'
model: GPT-5-Codex (Preview)
description: " Execute the fix for a GitHub issue using the previously generated implementation plan. Apply code & tests directly in the repo. Output only a PR description (and optional manual steps)."
agent: 'agent'
model: GPT-5.1-Codex-Max
description: "Execute the fix for a GitHub issue using the previously generated implementation plan. Apply code & tests directly in the repo. Output only a PR description (and optional manual steps)."
---
# DEPENDENCY

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: GPT-5-Codex (Preview)
agent: 'agent'
model: GPT-5.1-Codex-Max
description: 'Resolve Code scanning / check-spelling comments on the active PR.'
---

View File

@@ -1,7 +1,6 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
description: "You are github issue review and planning expertise, Score (0100) and write one Implementation Plan. Outputs: overview.md, implementation-plan.md."
agent: 'agent'
model: GPT-5.1-Codex-Max
description: "You are a GitHub issue review and planning expert; score (0-100) and write one implementation plan. Outputs: overview.md, implementation-plan.md."
---
# GOAL
@@ -10,11 +9,11 @@ For **#{{issue_number}}** produce:
2) `Generated Files/issueReview/{{issue_number}}/implementation-plan.md`
## Inputs
figure out from the prompt on the
Figure out required inputs {{issue_number}} from the invocation context; if anything is missing, ask for the value or note it as a gap.
# CONTEXT (brief)
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download the image for understand the context of the issue more.
Locate source code in current workspace, but also free feel to use via `rg`/`git grep`. Link related issues/PRs.
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download images to better understand the issue context.
Locate source code in the current workspace; feel free to use `rg`/`git grep`. Link related issues and PRs.
# OVERVIEW.MD
## Summary

View File

@@ -1,6 +1,5 @@
---
mode: 'agent'
model: Claude Sonnet 4.5
agent: 'agent'
model: GPT-5.1-Codex-Max
description: "gh-driven PR review; per-step Markdown + machine-readable outputs"
---

View File

@@ -68,14 +68,13 @@ jobs:
- template: .\steps-restore-nuget.yml
- task: NuGetCommand@2
- task: MSBuild@1
displayName: Restore solution-level NuGet packages
inputs:
command: restore
feedsToUse: config
configPath: nuget.config
restoreSolution: PowerToys.slnx
restoreDirectory: '$(Build.SourcesDirectory)\packages'
solution: PowerToys.slnx
msbuildArguments: '/t:restore /p:RestorePackagesConfig=true'
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
# Build all UI test projects if no specific modules are specified
- ${{ if eq(length(parameters.uiTestModules), 0) }}:

106
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,106 @@
{
"version": "2.0.0",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": ["/d", "/c"]
}
}
},
"inputs": [
{
"id": "config",
"type": "pickString",
"description": "Configuration",
"options": ["Debug", "Release"],
"default": "Debug"
},
{
"id": "platform",
"type": "pickString",
"description": "Platform (leave empty to auto-detect host platform)",
"options": ["", "X64", "ARM64"],
"default": "X64"
},
{
"id": "msbuildExtra",
"type": "promptString",
"description": "Extra MSBuild args (optional). Example: /p:CIBuild=true /m",
"default": ""
}
],
"tasks": [
{
"label": "PT: Build (quick)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
"args": [
"-Path",
"${fileDirname}"
],
"group": { "kind": "build", "isDefault": true },
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build (with options)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
"args": [
"-Path",
"${fileDirname}",
"-Platform",
"${input:platform}",
"-Configuration",
"${input:config}",
"${input:msbuildExtra}"
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build Essentials (quick)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
"args": [],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
},
{
"label": "PT: Build Essentials (with options)",
"type": "shell",
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
"args": [
"-Platform",
"${input:platform}",
"-Configuration",
"${input:config}",
"${input:msbuildExtra}"
],
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -6,9 +6,6 @@ Names are in alphabetical order based on first name.
## High impact community members
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com)
Christian contributed New+ utility
@@ -42,6 +39,12 @@ Jay has helped triaging, discussing, creating a substantial number of issues and
### [@jefflord](https://github.com/Jjefflord) - Jeff Lord
Jeff added in multiple new features into Keyboard manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys.
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
### [@jiripolasek](https://github.com/jiripolasek) - [Jiří Polášek](https://github.com/jiripolasek)
Jiří has contributed a massive number of features and improvements to Command Palette, including drag & drop support, custom themes, Web Search enhancements, Remote Desktop extension fixes, and many UX improvements.
### [@TheJoeFin](https://github.com/TheJoeFin) - [Joe Finney](https://joefinapps.com)
Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor.
@@ -57,6 +60,9 @@ Color Picker is from Martin.
### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com)
Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility.
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
### [@pedrolamas](https://github.com/pedrolamas/) - Pedro Lamas
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
@@ -69,15 +75,12 @@ Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/m
### [@royvou](https://github.com/royvou)
Roy has helped out contributing multiple features to PowerToys Run
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
### [@ThiefZero](https://github.com/ThiefZero)
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
### [@TobiasSekan](https://github.com/TobiasSekan) - Tobias Sekan
Tobias Sekan has helped out contributing features to PowerToys Run such as Settings plugin, Registry plugin
### [@ThiefZero](https://github.com/ThiefZero)
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
## Open source projects
As PowerToys creates new utilities, some will be based off existing technology. We'll continue to do our best to contribute back to these projects but their efforts were the base of some of our projects. We want to be sure their work is directly recognized.
@@ -187,18 +190,10 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev Lead
- [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev Lead
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
- [@lei9444](https://github.com/lei9444) - Leilei Zhang - Dev
- [@shuaiyuanxx](https://github.com/shuaiyuanxx) - Shawn Yuan - Dev
- [@moooyo](https://github.com/moooyo) - Yu Leng - Dev
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@vanzue](https://github.com/vanzue) - Kai Tao - Dev
- [@zadjii-msft](https://github.com/zadjii-msft) - Mike Griese - Dev
- [@khmyznikov](https://github.com/khmyznikov) - Gleb Khmyznikov - Dev
@@ -229,3 +224,12 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev Lead
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead

View File

@@ -1,23 +0,0 @@
#pragma once
enum class SettingId
{
ScheduleMode = 0,
Latitude,
Longitude,
LightTime,
DarkTime,
Sunrise_Offset,
Sunset_Offset,
ChangeSystem,
ChangeApps,
WallpaperEnabled,
WallpaperVirtualDesktopEnabled,
WallpaperStyleLight,
WallpaperStyleDark,
WallpaperPathLight,
WallpaperPathDark
};
inline constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
inline 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

@@ -1,370 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <logger/logger_settings.h>
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include "ThemeHelper.h"
#include <wil/resource.h>
#include "SettingsConstants.h"
static auto RegKeyGuard(HKEY& hKey) noexcept
{
return wil::scope_exit([&hKey]() {
if (hKey == nullptr)
return;
if (RegCloseKey(hKey) != ERROR_SUCCESS)
std::terminate();
});
}
// Controls changing the themes.
static void ResetColorPrevalence() noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = 0; // back to default value
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetAppsTheme(bool mode) noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode) noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
Logger::info(L"[LightSwitchService] Reset ColorPrevalence to default when switching to light mode.");
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
// Can think of this as "is the current theme light?"
bool GetCurrentSystemTheme() noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme() noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
}
return value == 1; // true = light, false = dark
}
bool IsNightLightEnabled() noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
if (RegOpenKeyExW(HKEY_CURRENT_USER, NIGHT_LIGHT_REGISTRY_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)
{
return false;
}
std::vector<BYTE> data(size);
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, data.data(), &size) != ERROR_SUCCESS)
{
return false;
}
return data[23] == 0x10 && data[24] == 0x00;
}
#include <atomic>
#include <charconv>
#include <array>
#include <string>
static bool GetWindowsVersionFromRegistryInternal(int& build, int& revision) noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
{
return false;
}
wchar_t buffer[11]{};
DWORD bufferSize{ sizeof(buffer) };
if (RegGetValueW(hKey, nullptr, L"CurrentBuildNumber", RRF_RT_REG_SZ, nullptr, static_cast<void*>(buffer), &bufferSize))
{
return false;
}
char bufferA[11]{};
std::transform(std::begin(buffer), std::end(buffer), std::begin(bufferA), [](auto c) { return static_cast<char>(c); });
int bld{};
if (std::from_chars(bufferA, bufferA + sizeof(bufferA), bld).ec != std::errc{})
{
return false;
}
DWORD rev{};
DWORD revSize{ sizeof(rev) };
if (RegGetValueW(hKey, nullptr, L"UBR", RRF_RT_DWORD, nullptr, &rev, &revSize) != ERROR_SUCCESS)
{
return false;
}
revision = static_cast<int>(rev);
build = static_cast<int>(bld);
return true;
}
static bool GetWindowsVersionFromRegistry(int& build, int& revision) noexcept
{
static std::atomic<int> build_cache{};
static std::atomic<int> rev_cache{};
if (auto bld = build_cache.load(); bld != 0)
{
build = bld;
revision = rev_cache.load();
return true;
}
int bld{};
int rev{};
if (auto e = GetWindowsVersionFromRegistryInternal(bld, rev); e == false)
{
return e;
}
build = bld;
revision = rev;
rev_cache.store(rev);
// Write after rev_cache for condition
build_cache.store(bld);
return true;
}
// This function will supplement the wallpaper path setting. It does not cause the wallpaper to change, but for consistency, it is better to set it
static int SetRemainWallpaperPathRegistry(std::wstring const& wallpaperPath) noexcept
{
HKEY hKey{};
auto closeKey = RegKeyGuard(hKey);
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers", 0, KEY_WRITE, &hKey) != ERROR_SUCCESS)
{
// The key may not exist after updating Windows, so it is not an error
// The key will be created by the Settings app
return 0;
}
if (RegSetValueExW(hKey, L"CurrentWallpaperPath", 0, REG_SZ, reinterpret_cast<const BYTE*>(wallpaperPath.data()), static_cast<DWORD>((wallpaperPath.size() + 1u) * sizeof(wchar_t))) != ERROR_SUCCESS)
{
return 0x301;
}
DWORD backgroundType = 0; // 0 = picture, 1 = solid color, 2 = slideshow
if (RegSetValueExW(hKey, L"BackgroundType", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&backgroundType), static_cast<DWORD>(sizeof(DWORD))) != ERROR_SUCCESS)
{
return 0x302;
}
return 0;
}
#define WIN32_LEAN_AND_MEAN
#define COM_NO_WINDOWS_H
#include <string>
#include <unknwn.h>
#include <inspectable.h>
#include <restrictederrorinfo.h>
#include "Shobjidl.h"
#include <hstring.h>
#include <winrt/base.h>
#pragma comment(lib, "runtimeobject.lib")
// COM interface definition from https://github.com/MScholtes/VirtualDesktop
inline constexpr GUID CLSID_ImmersiveShell{ 0xC2F03A33, 0x21F5, 0x47FA, { 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39 } };
inline constexpr GUID CLSID_VirtualDesktopManagerInternal{ 0xC5E0CDCA, 0x7B6E, 0x41B2, { 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B } };
struct __declspec(novtable) __declspec(uuid("6D5140C1-7436-11CE-8034-00AA006009FA")) IServiceProvider10 : public IUnknown
{
virtual HRESULT __stdcall QueryService(REFGUID service, REFIID riid, void** obj) = 0;
};
#undef CreateDesktop
struct __declspec(novtable) __declspec(uuid("53F5CA0B-158F-4124-900C-057158060B27")) IVirtualDesktopManagerInternal24H2 : public IUnknown
{
virtual HRESULT __stdcall GetCount(int* count) = 0;
virtual HRESULT __stdcall MoveViewToDesktop(IInspectable* view, IUnknown* desktop) = 0;
virtual HRESULT __stdcall CanViewMoveDesktops(IInspectable* view, bool* result) = 0;
virtual HRESULT __stdcall GetCurrentDesktop(IUnknown** desktop) = 0;
virtual HRESULT __stdcall GetDesktops(IObjectArray** desktops) = 0;
virtual HRESULT __stdcall GetAdjacentDesktop(IUnknown* from, int direction, IUnknown** desktop) = 0;
virtual HRESULT __stdcall SwitchDesktop(IUnknown* desktop) = 0;
virtual HRESULT __stdcall SwitchDesktopAndMoveForegroundView(IUnknown* desktop) = 0;
virtual HRESULT __stdcall CreateDesktop(IUnknown** desktop) = 0;
virtual HRESULT __stdcall MoveDesktop(IUnknown* desktop, int nIndex) = 0;
virtual HRESULT __stdcall RemoveDesktop(IUnknown* desktop, IUnknown* fallback) = 0;
virtual HRESULT __stdcall FindDesktop(const GUID* desktopId, IUnknown** desktop) = 0;
virtual HRESULT __stdcall GetDesktopSwitchIncludeExcludeViews(IUnknown* desktop, IObjectArray** unknown1, IObjectArray** unknown2) = 0;
virtual HRESULT __stdcall SetDesktopName(IUnknown* desktop, HSTRING name) = 0;
virtual HRESULT __stdcall SetDesktopWallpaper(IUnknown* desktop, HSTRING path) = 0;
virtual HRESULT __stdcall UpdateWallpaperPathForAllDesktops(HSTRING path) = 0;
virtual HRESULT __stdcall CopyDesktopState(IInspectable* pView0, IInspectable* pView1) = 0;
virtual HRESULT __stdcall CreateRemoteDesktop(HSTRING path, IUnknown** desktop) = 0;
virtual HRESULT __stdcall SwitchRemoteDesktop(IUnknown* desktop, void* switchType) = 0;
virtual HRESULT __stdcall SwitchDesktopWithAnimation(IUnknown* desktop) = 0;
virtual HRESULT __stdcall GetLastActiveDesktop(IUnknown** desktop) = 0;
virtual HRESULT __stdcall WaitForAnimationToComplete() = 0;
};
// Using this method to set the wallpaper works across virtual desktops, but it does not provide the functionality to set the style
static int SetWallpaperViaIVirtualDesktopManagerInternal(const std::wstring& path) noexcept
{
int build{};
int revision{};
if (!GetWindowsVersionFromRegistry(build, revision))
{
return 0x201;
}
// Unstable Windows internal API, at least 24H2 required
if (build < 26100)
{
return 0x202;
}
auto shell = winrt::try_create_instance<IServiceProvider10>(CLSID_ImmersiveShell, CLSCTX_LOCAL_SERVER);
if (!shell)
{
return 0x203;
}
winrt::com_ptr<IVirtualDesktopManagerInternal24H2> virtualDesktopManagerInternal;
if (shell->QueryService(
CLSID_VirtualDesktopManagerInternal,
__uuidof(IVirtualDesktopManagerInternal24H2),
virtualDesktopManagerInternal.put_void()) != S_OK)
{
return 0x204;
}
if (virtualDesktopManagerInternal->UpdateWallpaperPathForAllDesktops(static_cast<HSTRING>(winrt::detach_abi(path))) != S_OK)
{
return 0x205;
}
return 0;
}
// After setting the wallpaper using this method, switching to other virtual desktops will cause the wallpaper to be restored
static int SetWallpaperViaIDesktopWallpaper(const std::wstring& path, int style) noexcept
{
auto pos = static_cast<DESKTOP_WALLPAPER_POSITION>(style);
switch (pos)
{
case DWPOS_CENTER:
case DWPOS_TILE:
case DWPOS_STRETCH:
case DWPOS_FIT:
case DWPOS_FILL:
case DWPOS_SPAN:
break;
default:
std::terminate();
}
auto desktopWallpaper = winrt::try_create_instance<IDesktopWallpaper>(__uuidof(DesktopWallpaper), CLSCTX_LOCAL_SERVER);
if (!desktopWallpaper)
{
return 0x301;
}
if (desktopWallpaper->SetPosition(pos) != S_OK)
{
return 0x302;
}
if (desktopWallpaper->SetWallpaper(nullptr, path.c_str()) != S_OK)
{
return 0x303;
}
return 0;
}
int SetDesktopWallpaper(const std::wstring& path, int style, bool virtualDesktop) noexcept
{
if (virtualDesktop)
{
if (auto e = SetWallpaperViaIVirtualDesktopManagerInternal(path); e != 0)
{
return e;
}
}
if (auto e = SetWallpaperViaIDesktopWallpaper(path, style); e != 0)
{
return e;
}
if (auto e = SetRemainWallpaperPathRegistry(path); e != 0)
{
return e;
}
return 0;
}

View File

@@ -1,9 +0,0 @@
#pragma once
#include <string>
void SetSystemTheme(bool dark) noexcept;
void SetAppsTheme(bool dark) noexcept;
bool GetCurrentSystemTheme() noexcept;
bool GetCurrentAppsTheme() noexcept;
bool IsNightLightEnabled() noexcept;
// Returned 0 indicates success; otherwise, the reason is returned, see definition
int SetDesktopWallpaper(std::wstring const& wallpaperPath, int style, bool virtualDesktop) noexcept;

View File

@@ -166,14 +166,13 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\LightSwitchCommon;..\..\..\common;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="..\LightSwitchCommon\ThemeHelper.h" />
<ClInclude Include="..\LightSwitchCommon\SettingsConstants.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
@@ -188,12 +187,7 @@
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="..\LightSwitchCommon\ThemeHelper.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
@@ -216,7 +210,6 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@@ -10,7 +10,7 @@
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\LightSwitchCommon\ThemeHelper.cpp">
<ClCompile Include="ThemeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
@@ -24,10 +24,7 @@
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\LightSwitchCommon\ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\LightSwitchCommon\SettingsConstants.h">
<ClInclude Include="ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
@@ -50,7 +47,4 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,106 @@
#include "pch.h"
#include <windows.h>
#include "ThemeHelper.h"
// Controls changing the themes.
static void ResetColorPrevalence()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = 0; // back to default value
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
bool GetCurrentSystemTheme()
{
HKEY hKey;
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme()
{
HKEY hKey;
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}

View File

@@ -0,0 +1,5 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();

View File

@@ -94,12 +94,6 @@ struct ModuleSettings
int m_sunset_offset = 0;
std::wstring m_latitude = L"0.0";
std::wstring m_longitude = L"0.0";
bool m_wallpaper = false;
bool m_wallpaper_virtual_desktop = false;
int m_wallpaper_style_light = 0;
int m_wallpaper_style_dark = 0;
std::wstring m_wallpaper_path_light;
std::wstring m_wallpaper_path_dark;
} g_settings;
class LightSwitchInterface : public PowertoyModuleIface
@@ -357,30 +351,6 @@ public:
{
g_settings.m_longitude = *v;
}
if (auto v = values.get_bool_value(L"wallpaperEnabled"))
{
g_settings.m_wallpaper = *v;
}
if (auto v = values.get_bool_value(L"wallpaperVirtualDesktopEnabled"))
{
g_settings.m_wallpaper_virtual_desktop = *v;
}
if (auto v = values.get_int_value(L"wallpaperStyleLight"))
{
g_settings.m_wallpaper_style_light = *v;
}
if (auto v = values.get_int_value(L"wallpaperStyleDark"))
{
g_settings.m_wallpaper_style_dark = *v;
}
if (auto v = values.get_string_value(L"wallpaperPathLight"))
{
g_settings.m_wallpaper_path_light = *v;
}
if (auto v = values.get_string_value(L"wallpaperPathDark"))
{
g_settings.m_wallpaper_path_dark = *v;
}
values.save_to_settings_file();
}
@@ -596,53 +566,15 @@ public:
};
static bool IsValidPath(const std::wstring& path)
{
std::error_code ec;
return !path.empty() && std::filesystem::exists(path, ec);
}
void LightSwitchInterface::ToggleTheme()
{
bool current_system_theme = GetCurrentSystemTheme();
bool current_apps_theme = GetCurrentAppsTheme();
if (g_settings.m_changeSystem)
{
SetSystemTheme(!current_system_theme);
SetSystemTheme(!GetCurrentSystemTheme());
}
if (g_settings.m_changeApps)
{
SetAppsTheme(!current_apps_theme);
}
bool new_system_theme = GetCurrentSystemTheme();
bool new_apps_theme = GetCurrentAppsTheme();
bool changeWallpaper =
g_settings.m_wallpaper &&
IsValidPath(g_settings.m_wallpaper_path_light) &&
IsValidPath(g_settings.m_wallpaper_path_dark);
// if something changed and wallpaper change is enabled and paths are valid
if ((new_system_theme != current_system_theme || new_apps_theme != current_apps_theme) && changeWallpaper)
{
bool shouldBeLight;
if (g_settings.m_changeSystem && new_system_theme != current_system_theme)
{
shouldBeLight = new_system_theme;
}
else
{
// Either apps-only changed, or system didn't move
shouldBeLight = new_apps_theme;
}
auto&& wallpaperPath = shouldBeLight ? g_settings.m_wallpaper_path_light : g_settings.m_wallpaper_path_dark;
auto style = shouldBeLight ? g_settings.m_wallpaper_style_light : g_settings.m_wallpaper_style_dark;
SetDesktopWallpaper(wallpaperPath, style, g_settings.m_wallpaper_virtual_desktop);
SetAppsTheme(!GetCurrentAppsTheme());
}
if (!m_manual_override_event_handle)
@@ -761,18 +693,6 @@ void LightSwitchInterface::init_settings()
g_settings.m_latitude = *v;
if (auto v = settings.get_string_value(L"longitude"))
g_settings.m_longitude = *v;
if (auto v = settings.get_bool_value(L"wallpaperEnabled"))
g_settings.m_wallpaper = *v;
if (auto v = settings.get_bool_value(L"wallpaperVirtualDesktopEnabled"))
g_settings.m_wallpaper_virtual_desktop = *v;
if (auto v = settings.get_int_value(L"wallpaperStyleLight"))
g_settings.m_wallpaper_style_light = *v;
if (auto v = settings.get_int_value(L"wallpaperStyleDark"))
g_settings.m_wallpaper_style_dark = *v;
if (auto v = settings.get_string_value(L"wallpaperPathLight"))
g_settings.m_wallpaper_path_light = *v;
if (auto v = settings.get_string_value(L"wallpaperPathDark"))
g_settings.m_wallpaper_path_dark = *v;
Logger::info(L"[Light Switch] init_settings: loaded successfully");
}

View File

@@ -24,7 +24,7 @@ static LightSwitchStateManager* g_stateManagerPtr = nullptr;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
void ApplyTheme(bool shouldBeLight, bool changeWallpaper);
void ApplyTheme(bool shouldBeLight);
// Entry point for the executable
int _tmain(int argc, TCHAR* argv[])
@@ -125,29 +125,9 @@ VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl)
}
}
void SetWallpaper(bool shouldBeLight)
{
const auto& settings = LightSwitchSettings::settings();
if (settings.wallpaperEnabled)
{
std::wstring const& wallpaperPath = shouldBeLight ? settings.wallpaperPathLight : settings.wallpaperPathDark;
auto style = shouldBeLight ? settings.wallpaperStyleLight : settings.wallpaperStyleDark;
if (auto e = SetDesktopWallpaper(wallpaperPath, style, settings.wallpaperVirtualDesktop) == 0)
{
Logger::info(L"[LightSwitchService] Wallpaper is changed to {}.", wallpaperPath);
}
else
{
Logger::error(L"[LightSwitchService] Failed to set wallpaper, error: {}.", e);
}
}
};
void ApplyTheme(bool shouldBeLight, bool changeWallpaper)
void ApplyTheme(bool shouldBeLight)
{
const auto& s = LightSwitchSettings::settings();
bool somethingChanged = false;
if (s.changeSystem)
{
@@ -156,7 +136,6 @@ void ApplyTheme(bool shouldBeLight, bool changeWallpaper)
{
SetSystemTheme(shouldBeLight);
Logger::info(L"[LightSwitchService] Changed system theme to {}.", shouldBeLight ? L"light" : L"dark");
somethingChanged = true;
}
}
@@ -167,15 +146,6 @@ void ApplyTheme(bool shouldBeLight, bool changeWallpaper)
{
SetAppsTheme(shouldBeLight);
Logger::info(L"[LightSwitchService] Changed apps theme to {}.", shouldBeLight ? L"light" : L"dark");
somethingChanged = true;
}
}
if (somethingChanged)
{
if (changeWallpaper)
{
SetWallpaper(shouldBeLight);
}
}
}
@@ -205,7 +175,7 @@ static void DetectAndHandleExternalThemeChange(LightSwitchStateManager& stateMan
if (s.scheduleMode == ScheduleMode::FollowNightLight)
{
shouldBeLight = !IsNightLightEnabled();
}
}
else
{
shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);

View File

@@ -53,7 +53,18 @@
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\LightSwitchCommon;.\..\;..\..\..\common;..\..\..\common\logger;..\..\..\common\utils;..\..\..\common\SettingsAPI;..\..\..\common\Telemetry;..\..\..\;..\..\..\..\deps\spdlog\include;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>
./../;
..\..\..\common;
..\..\..\common\logger;
..\..\..\common\utils;
..\..\..\common\SettingsAPI;
..\..\..\common\Telemetry;
..\..\..\;
..\..\..\..\deps\spdlog\include;
./;
%(AdditionalIncludeDirectories)
</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -66,7 +77,8 @@
<ClCompile Include="LightSwitchSettings.cpp" />
<ClCompile Include="LightSwitchStateManager.cpp" />
<ClCompile Include="NightLightRegistryObserver.cpp" />
<ClCompile Include="..\LightSwitchCommon\ThemeHelper.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" />
<ClCompile Include="trace.cpp" />
<ClCompile Include="WinHookEventIDs.cpp" />
@@ -79,9 +91,9 @@
<ClInclude Include="LightSwitchStateManager.h" />
<ClInclude Include="LightSwitchUtils.h" />
<ClInclude Include="NightLightRegistryObserver.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="..\LightSwitchCommon\ThemeHelper.h" />
<ClInclude Include="..\LightSwitchCommon\SettingsConstants.h" />
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="ThemeScheduler.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="WinHookEventIDs.h" />

View File

@@ -21,12 +21,15 @@
<ClCompile Include="ThemeScheduler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\LightSwitchCommon\ThemeHelper.cpp">
<ClCompile Include="ThemeHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LightSwitchSettings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SettingsConstants.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WinHookEventIDs.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -47,10 +50,10 @@
<ClInclude Include="ThemeHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\LightSwitchCommon\LightSwitchSettings.h">
<ClInclude Include="LightSwitchSettings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\LightSwitchCommon\SettingsConstants.h">
<ClInclude Include="SettingsConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SettingsObserver.h">

View File

@@ -253,66 +253,6 @@ void LightSwitchSettings::LoadSettings()
{
Trace::LightSwitch::ThemeTargetChanged(m_settings.changeApps, m_settings.changeSystem);
}
if (const auto jsonVal = values.get_bool_value(L"wallpaperEnabled"))
{
auto val = *jsonVal;
if (m_settings.wallpaperEnabled != val)
{
m_settings.wallpaperEnabled = val;
NotifyObservers(SettingId::WallpaperEnabled);
}
}
if (const auto jsonVal = values.get_bool_value(L"wallpaperVirtualDesktopEnabled"))
{
auto val = *jsonVal;
if (m_settings.wallpaperVirtualDesktop != val)
{
m_settings.wallpaperVirtualDesktop = val;
NotifyObservers(SettingId::WallpaperVirtualDesktopEnabled);
}
}
if (const auto jsonVal = values.get_int_value(L"wallpaperStyleLight"))
{
auto val = *jsonVal;
if (m_settings.wallpaperStyleLight != val)
{
m_settings.wallpaperStyleLight = val;
NotifyObservers(SettingId::WallpaperStyleLight);
}
}
if (const auto jsonVal = values.get_int_value(L"wallpaperStyleDark"))
{
auto val = *jsonVal;
if (m_settings.wallpaperStyleDark != val)
{
m_settings.wallpaperStyleDark = val;
NotifyObservers(SettingId::WallpaperStyleDark);
}
}
if (const auto jsonVal = values.get_string_value(L"wallpaperPathLight"))
{
auto val = *jsonVal;
if (m_settings.wallpaperPathLight != val)
{
m_settings.wallpaperPathLight = val;
NotifyObservers(SettingId::WallpaperPathLight);
}
}
if (const auto jsonVal = values.get_string_value(L"wallpaperPathDark"))
{
auto val = *jsonVal;
if (m_settings.wallpaperPathDark != val)
{
m_settings.wallpaperPathDark = val;
NotifyObservers(SettingId::WallpaperPathDark);
}
}
}
catch (...)
{

View File

@@ -67,13 +67,6 @@ struct LightSwitchConfig
bool changeSystem = false;
bool changeApps = false;
bool wallpaperEnabled = false;
bool wallpaperVirtualDesktop = false;
int wallpaperStyleLight = 0;
int wallpaperStyleDark = 0;
std::wstring wallpaperPathLight;
std::wstring wallpaperPathDark;
};
class LightSwitchSettings

View File

@@ -4,9 +4,8 @@
#include <LightSwitchUtils.h>
#include "ThemeScheduler.h"
#include <ThemeHelper.h>
#include <filesystem>
void ApplyTheme(bool shouldBeLight, bool changeWallpaper);
void ApplyTheme(bool shouldBeLight);
// Constructor
LightSwitchStateManager::LightSwitchStateManager()
@@ -148,11 +147,6 @@ static std::pair<int, int> update_sun_times(auto& settings)
return { newLightTime, newDarkTime };
}
static bool IsValidPath(const std::wstring& path)
{
return !path.empty() && std::filesystem::exists(path);
}
// Internal: decide what should happen now
void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
{
@@ -246,11 +240,6 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
bool changeWallpaper =
_currentSettings.wallpaperEnabled &&
IsValidPath(_currentSettings.wallpaperPathDark) &&
IsValidPath(_currentSettings.wallpaperPathLight);
/* Logger::debug(
L"[LightSwitchStateManager] now = {:02d}:{:02d}, light boundary = {:02d}:{:02d} ({}), dark boundary = {:02d}:{:02d} ({})",
now / 60,
@@ -271,11 +260,11 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
if (!_state.isManualOverride && (appsNeedsToChange || systemNeedsToChange))
{
Logger::info(L"[LightSwitchStateManager] Applying {} theme", shouldBeLight ? L"light" : L"dark");
ApplyTheme(shouldBeLight, changeWallpaper);
ApplyTheme(shouldBeLight);
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
}
_state.lastTickMinutes = now;
}
}

View File

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

View File

@@ -0,0 +1,17 @@
#pragma once
enum class SettingId
{
ScheduleMode = 0,
Latitude,
Longitude,
LightTime,
DarkTime,
Sunrise_Offset,
Sunset_Offset,
ChangeSystem,
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

@@ -0,0 +1,139 @@
#include <windows.h>
#include <logger/logger_settings.h>
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include "ThemeHelper.h"
#include <SettingsConstants.h>
// Controls changing the themes.
static void ResetColorPrevalence()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = 0; // back to default value
RegSetValueEx(hKey, L"ColorPrevalence", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_DWMCOLORIZATIONCOLORCHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"AppsUseLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
{
DWORD value = mode;
RegSetValueEx(hKey, L"SystemUsesLightTheme", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&value), sizeof(value));
RegCloseKey(hKey);
if (mode) // if are changing to light mode
{
ResetColorPrevalence();
Logger::info(L"[LightSwitchService] Reset ColorPrevalence to default when switching to light mode.");
}
SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, reinterpret_cast<LPARAM>(L"ImmersiveColorSet"), SMTO_ABORTIFHUNG, 5000, nullptr);
SendMessageTimeout(HWND_BROADCAST, WM_THEMECHANGED, 0, 0, SMTO_ABORTIFHUNG, 5000, nullptr);
}
}
// Can think of this as "is the current theme light?"
bool GetCurrentSystemTheme()
{
HKEY hKey;
DWORD value = 1; // default = light
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 1; // true = light, false = dark
}
bool GetCurrentAppsTheme()
{
HKEY hKey;
DWORD value = 1;
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
{
RegQueryValueEx(hKey, L"AppsUseLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
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

@@ -0,0 +1,6 @@
#pragma once
void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -17,10 +17,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public const string DefaultLatitude = "0.0";
public const string DefaultLongitude = "0.0";
public const string DefaultScheduleMode = "Off";
public const bool DefaultWallpaperEnabled = false;
public const bool DefaultWallpaperVirtualDesktopEnabled = false;
public const int DefaultWallpaperStyle = 0;
public const string DefaultWallpaperPath = "";
public static readonly HotkeySettings DefaultToggleThemeHotkey = new HotkeySettings(true, true, false, true, 0x44); // Ctrl+Win+Shift+D
public LightSwitchProperties()
@@ -34,12 +30,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
SunriseOffset = new IntProperty(DefaultSunriseOffset);
SunsetOffset = new IntProperty(DefaultSunsetOffset);
ScheduleMode = new StringProperty(DefaultScheduleMode);
WallpaperEnabled = new BoolProperty(DefaultWallpaperEnabled);
WallpaperVirtualDesktopEnabled = new BoolProperty(DefaultWallpaperVirtualDesktopEnabled);
WallpaperStyleLight = new IntProperty(DefaultWallpaperStyle);
WallpaperStyleDark = new IntProperty(DefaultWallpaperStyle);
WallpaperPathLight = new StringProperty(DefaultWallpaperPath);
WallpaperPathDark = new StringProperty(DefaultWallpaperPath);
ToggleThemeHotkey = new KeyboardKeysProperty(DefaultToggleThemeHotkey);
}
@@ -72,23 +62,5 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("toggle-theme-hotkey")]
public KeyboardKeysProperty ToggleThemeHotkey { get; set; }
[JsonPropertyName("wallpaperEnabled")]
public BoolProperty WallpaperEnabled { get; set; }
[JsonPropertyName("wallpaperVirtualDesktopEnabled")]
public BoolProperty WallpaperVirtualDesktopEnabled { get; set; }
[JsonPropertyName("wallpaperStyleLight")]
public IntProperty WallpaperStyleLight { get; set; }
[JsonPropertyName("wallpaperStyleDark")]
public IntProperty WallpaperStyleDark { get; set; }
[JsonPropertyName("wallpaperPathLight")]
public StringProperty WallpaperPathLight { get; set; }
[JsonPropertyName("wallpaperPathDark")]
public StringProperty WallpaperPathDark { get; set; }
}
}

View File

@@ -60,12 +60,6 @@ namespace Settings.UI.Library
Latitude = new StringProperty(Properties.Latitude.Value),
Longitude = new StringProperty(Properties.Longitude.Value),
ToggleThemeHotkey = new KeyboardKeysProperty(Properties.ToggleThemeHotkey.Value),
WallpaperEnabled = new BoolProperty(Properties.WallpaperEnabled.Value),
WallpaperVirtualDesktopEnabled = new BoolProperty(Properties.WallpaperVirtualDesktopEnabled.Value),
WallpaperStyleLight = new IntProperty((int)Properties.WallpaperStyleLight.Value),
WallpaperStyleDark = new IntProperty((int)Properties.WallpaperStyleDark.Value),
WallpaperPathLight = new StringProperty(Properties.WallpaperPathLight.Value),
WallpaperPathDark = new StringProperty(Properties.WallpaperPathDark.Value),
},
};
}

View File

@@ -9,7 +9,6 @@
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.PowerToys.Settings.UI.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:LightSwitchViewModel}"
@@ -18,12 +17,6 @@
<local:NavigablePage.Resources>
<converters:TimeSpanToFriendlyTimeConverter x:Key="TimeSpanToFriendlyTimeConverter" />
<converters:StringToDoubleConverter x:Key="StringToDoubleConverter" />
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<tkconverters:BoolToVisibilityConverter
x:Key="BoolToInvertedVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
</local:NavigablePage.Resources>
<Grid>
<controls:SettingsPageControl
@@ -218,124 +211,6 @@
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<tkcontrols:SettingsExpander
x:Uid="LightSwitch_WallpaperExpander"
HeaderIcon="{ui:FontIcon Glyph=&#xE7f9;}"
IsExpanded="True">
<ToggleSwitch AutomationProperties.AutomationId="Toggle_WallpaperSwitch" IsOn="{x:Bind ViewModel.IsWallpaperEnabled, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Left">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20px" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="LightSwitch_WallpaperImageUnavailable"
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Visibility="{x:Bind ViewModel.IsLightWallpaperValid, Converter={StaticResource BoolToInvertedVisibilityConverter}, Mode=OneWay}" />
<Image
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
Source="{x:Bind ViewModel.WallpaperSourceLight, Mode=OneWay}"
Tag="Light" />
<TextBlock
x:Uid="LightSwitch_WallpaperImageUnavailable"
Grid.Row="0"
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Visibility="{x:Bind ViewModel.IsDarkWallpaperValid, Converter={StaticResource BoolToInvertedVisibilityConverter}, Mode=OneWay}" />
<Image
Grid.Row="0"
Grid.Column="2"
Source="{x:Bind ViewModel.WallpaperSourceDark, Mode=OneWay}"
Tag="Dark" />
</Grid>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="LightSwitch_WallpaperSelect">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="LightSwitch_WallpaperLight"
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Button
x:Uid="LightSwitch_WallpaperBrowse"
Grid.Row="2"
Grid.Column="0"
AutomationProperties.AutomationId="Pick_ButtonLight"
Click="PickWallpaper_Click"
Tag="Light" />
<TextBlock
x:Uid="LightSwitch_WallpaperDark"
Grid.Row="2"
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Button
x:Uid="LightSwitch_WallpaperBrowse"
Grid.Row="2"
Grid.Column="2"
AutomationProperties.AutomationId="Pick_ButtonDark"
Click="PickWallpaper_Click"
Tag="Dark" />
</StackPanel>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="LightSwitch_WallpaperStyle">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock
x:Uid="LightSwitch_WallpaperLight"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<ComboBox AutomationProperties.AutomationId="Toggle_WallpaperStyleSwitchLight" SelectedIndex="{x:Bind ViewModel.WallpaperStyleLight, Mode=TwoWay}">
<ComboBoxItem x:Uid="LightSwitch_StyleCenter" AutomationProperties.AutomationId="CenterCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleTile" AutomationProperties.AutomationId="TileCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleStretch" AutomationProperties.AutomationId="StretchCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleFit" AutomationProperties.AutomationId="FitCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleFill" AutomationProperties.AutomationId="FillCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleSpan" AutomationProperties.AutomationId="SpanCBItem_StyleSwitch" />
</ComboBox>
<TextBlock
x:Uid="LightSwitch_WallpaperDark"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<ComboBox AutomationProperties.AutomationId="Toggle_WallpaperStyleSwitchDark" SelectedIndex="{x:Bind ViewModel.WallpaperStyleDark, Mode=TwoWay}">
<ComboBoxItem x:Uid="LightSwitch_StyleCenter" AutomationProperties.AutomationId="CenterCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleTile" AutomationProperties.AutomationId="TileCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleStretch" AutomationProperties.AutomationId="StretchCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleFit" AutomationProperties.AutomationId="FitCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleFill" AutomationProperties.AutomationId="FillCBItem_StyleSwitch" />
<ComboBoxItem x:Uid="LightSwitch_StyleSpan" AutomationProperties.AutomationId="SpanCBItem_StyleSwitch" />
</ComboBox>
</StackPanel>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="LightSwitch_WallpaperVirtualDesktop" Visibility="{x:Bind ViewModel.Is24H2OrLater, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneTime}">
<ToggleSwitch AutomationProperties.AutomationId="Toggle_WallpaperVirtualDesktopSwitch" IsOn="{x:Bind ViewModel.IsVirtualDesktopEnabled, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}" Visibility="{x:Bind ViewModel.Is24H2OrLater, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneTime}">
<tkcontrols:SettingsCard.Header>
<TextBlock x:Uid="LightSwitch_VirtualDesktopInfo" TextWrapping="Wrap" />
</tkcontrols:SettingsCard.Header>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Background="{ThemeResource SystemFillColorSuccessBackgroundBrush}" Visibility="{x:Bind ViewModel.Is24H2OrLater, Converter={StaticResource BoolToInvertedVisibilityConverter}, Mode=OneTime}">
<tkcontrols:SettingsCard.Header>
<TextBlock x:Uid="LightSwitch_DetectWindows24H2Info" TextWrapping="Wrap" />
</tkcontrols:SettingsCard.Header>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<!-- Force mode buttons -->
<!--<tkcontrols:SettingsCard

View File

@@ -16,13 +16,9 @@ using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.Windows.Storage.Pickers;
using PowerToys.GPOWrapper;
using Settings.UI.Library;
using Windows.Devices.Geolocation;
using Windows.Storage;
using Windows.Storage.Streams;
namespace Microsoft.PowerToys.Settings.UI.Views
{
@@ -189,7 +185,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
// need to save the values
this.ViewModel.Latitude = latitude.ToString(CultureInfo.InvariantCulture);
this.ViewModel.Longitude = longitude.ToString(CultureInfo.InvariantCulture);
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}<7D>, {this.ViewModel.Longitude}<7D>";
this.ViewModel.SyncButtonInformation = $"{this.ViewModel.Latitude}<7D>, {this.ViewModel.Longitude}<7D>";
var result = SunCalc.CalculateSunriseSunset(latitude, longitude, DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
@@ -395,50 +391,5 @@ namespace Microsoft.PowerToys.Settings.UI.Views
this.LocationWarningBar.Visibility = Visibility.Visible;
}
}
private async void PickWallpaper_Click(object sender, RoutedEventArgs e)
{
var tag = (sender as Button).Tag as string;
var fileOpenPicker = new FileOpenPicker((sender as Button).XamlRoot.ContentIslandEnvironment.AppWindowId);
string[] extensions = { ".jpg", ".jpeg", ".bmp", ".dib", ".png", ".jfif", ".jpe", ".gif", ".tif", ".tiff", ".wdp", ".heic", ".heif", ".heics", ".heifs", ".hif", ".avci", ".avcs", ".avif", ".avifs", ".jxr", ".jxl", ".webp" };
foreach (var ext in extensions)
{
fileOpenPicker.FileTypeFilter.Add(ext);
}
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
var selectedFile = await fileOpenPicker.PickSingleFileAsync();
if (selectedFile == null)
{
return;
}
if (!string.IsNullOrEmpty(ViewModel.WallpaperPathLight) && tag == "Light")
{
LightSwitchViewModel.DeleteFile(ViewModel.WallpaperPathLight);
ViewModel.WallpaperPathLight = string.Empty;
}
else if (!string.IsNullOrEmpty(ViewModel.WallpaperPathDark) && tag == "Dark")
{
LightSwitchViewModel.DeleteFile(ViewModel.WallpaperPathDark);
ViewModel.WallpaperPathDark = string.Empty;
}
var srcFile = await StorageFile.GetFileFromPathAsync(selectedFile.Path);
var settingsFolder = await StorageFolder.GetFolderFromPathAsync(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\LightSwitch");
var dstFile = await settingsFolder.CreateFileAsync($"{tag}{DateTime.Now.ToFileTime()}{srcFile.FileType}", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBufferAsync(dstFile, await FileIO.ReadBufferAsync(srcFile));
if (tag == "Light")
{
ViewModel.WallpaperPathLight = dstFile.Path;
}
else if (tag == "Dark")
{
ViewModel.WallpaperPathDark = dstFile.Path;
}
}
}
}

View File

@@ -5456,54 +5456,6 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="LightSwitch_SunsetTooltip.Text" xml:space="preserve">
<value>Sunset</value>
</data>
<data name="LightSwitch_WallpaperExpander.Header" xml:space="preserve">
<value>Wallpaper changes with the color mode</value>
</data>
<data name="LightSwitch_StyleFill.Content" xml:space="preserve">
<value>Fill</value>
</data>
<data name="LightSwitch_StyleFit.Content" xml:space="preserve">
<value>Fit</value>
</data>
<data name="LightSwitch_StyleStretch.Content" xml:space="preserve">
<value>Stretch</value>
</data>
<data name="LightSwitch_StyleTile.Content" xml:space="preserve">
<value>Tile</value>
</data>
<data name="LightSwitch_StyleCenter.Content" xml:space="preserve">
<value>Center</value>
</data>
<data name="LightSwitch_StyleSpan.Content" xml:space="preserve">
<value>Span</value>
</data>
<data name="LightSwitch_WallpaperBrowse.Content" xml:space="preserve">
<value>Browse</value>
</data>
<data name="LightSwitch_WallpaperLight.Text" xml:space="preserve">
<value>Light:</value>
</data>
<data name="LightSwitch_WallpaperDark.Text" xml:space="preserve">
<value>Dark:</value>
</data>
<data name="LightSwitch_WallpaperImageUnavailable.Text" xml:space="preserve">
<value>Image preview unavailable, may not exist or is corrupted</value>
</data>
<data name="LightSwitch_WallpaperSelect.Header" xml:space="preserve">
<value>Choose images for different modes</value>
</data>
<data name="LightSwitch_WallpaperStyle.Header" xml:space="preserve">
<value>Choose fits for your desktop images</value>
</data>
<data name="LightSwitch_WallpaperVirtualDesktop.Header" xml:space="preserve">
<value>Apply wallpaper to all virtual desktops</value>
</data>
<data name="LightSwitch_VirtualDesktopInfo.Text" xml:space="preserve">
<value>Apply wallpaper to all virtual desktops is an experimental feature, only supported on Windows 24H2 and later versions. It may become unavailable or cause errors due to Windows updates. If you find that the LightSwitch service is not running properly after enabling this feature, please disable the feature and file a bug report with your Windows version.</value>
</data>
<data name="LightSwitch_DetectWindows24H2Info.Text" xml:space="preserve">
<value>It has been detected that your Windows version is earlier than 24H2. On such versions, switching to another virtual desktop may cause the wallpaper to revert. If you are using virtual desktops, it is not recommended to enable this feature. Alternatively, upgrade your Windows version to 24H2 or later for the best experience.</value>
</data>
<data name="Close_NavViewItem.Content" xml:space="preserve">
<value>Close PowerToys</value>
<comment>Don't loc "PowerToys"</comment>

View File

@@ -5,9 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
@@ -17,12 +15,9 @@ using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Newtonsoft.Json.Linq;
using Settings.UI.Library;
using Settings.UI.Library.Helpers;
using Windows.Storage;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
@@ -51,7 +46,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
};
_toggleThemeHotkey = _moduleSettings.Properties.ToggleThemeHotkey.Value;
PropertyChanged += WallpaperPath_Changed;
}
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
@@ -530,11 +524,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(Latitude));
OnPropertyChanged(nameof(Longitude));
OnPropertyChanged(nameof(ScheduleMode));
OnPropertyChanged(nameof(IsWallpaperEnabled));
OnPropertyChanged(nameof(WallpaperPathLight));
OnPropertyChanged(nameof(WallpaperPathDark));
OnPropertyChanged(nameof(WallpaperStyleLight));
OnPropertyChanged(nameof(WallpaperStyleDark));
}
private void UpdateSunTimes(double latitude, double longitude, string city = "n/a")
@@ -585,222 +574,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public bool IsWallpaperEnabled
{
get
{
return ModuleSettings.Properties.WallpaperEnabled.Value;
}
set
{
if (ModuleSettings.Properties.WallpaperEnabled.Value != value)
{
ModuleSettings.Properties.WallpaperEnabled.Value = value;
NotifyPropertyChanged();
}
}
}
public bool IsVirtualDesktopEnabled
{
get
{
return ModuleSettings.Properties.WallpaperVirtualDesktopEnabled.Value;
}
set
{
if (ModuleSettings.Properties.WallpaperVirtualDesktopEnabled.Value != value)
{
ModuleSettings.Properties.WallpaperVirtualDesktopEnabled.Value = value;
NotifyPropertyChanged();
}
}
}
public string WallpaperPathLight
{
get
{
return ModuleSettings.Properties.WallpaperPathLight.Value;
}
set
{
if (ModuleSettings.Properties.WallpaperPathLight.Value != value)
{
ModuleSettings.Properties.WallpaperPathLight.Value = value;
NotifyPropertyChanged();
}
}
}
public string WallpaperPathDark
{
get
{
return ModuleSettings.Properties.WallpaperPathDark.Value;
}
set
{
if (ModuleSettings.Properties.WallpaperPathDark.Value != value)
{
ModuleSettings.Properties.WallpaperPathDark.Value = value;
NotifyPropertyChanged();
}
}
}
public bool IsLightWallpaperValid
{
get => _isLightWallpaperValid;
set
{
if (_isLightWallpaperValid != value)
{
_isLightWallpaperValid = value;
}
}
}
public bool IsDarkWallpaperValid
{
get => _isDarkWallpaperValid;
set
{
if (_isDarkWallpaperValid != value)
{
_isDarkWallpaperValid = value;
}
}
}
public ImageSource WallpaperSourceLight
{
get => _wallpaperSourceLight;
set
{
if (_wallpaperSourceLight != value)
{
_wallpaperSourceLight = value;
NotifyPropertyChanged();
}
}
}
public ImageSource WallpaperSourceDark
{
get => _wallpaperSourceDark;
set
{
if (_wallpaperSourceDark != value)
{
_wallpaperSourceDark = value;
NotifyPropertyChanged();
}
}
}
public int WallpaperStyleLight
{
get => ModuleSettings.Properties.WallpaperStyleLight.Value;
set
{
if (ModuleSettings.Properties.WallpaperStyleLight.Value != value)
{
ModuleSettings.Properties.WallpaperStyleLight.Value = value;
NotifyPropertyChanged();
}
}
}
public int WallpaperStyleDark
{
get => ModuleSettings.Properties.WallpaperStyleDark.Value;
set
{
if (ModuleSettings.Properties.WallpaperStyleDark.Value != value)
{
ModuleSettings.Properties.WallpaperStyleDark.Value = value;
NotifyPropertyChanged();
}
}
}
public static void DeleteFile(string path)
{
// Prevent attackers from damaging files through specially crafted JSON
var dataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\LightSwitch";
if (!string.IsNullOrEmpty(path) && path.StartsWith(dataPath, StringComparison.OrdinalIgnoreCase))
{
try
{
File.Delete(path);
}
catch (Exception)
{
}
}
}
private async void WallpaperPath_Changed(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(WallpaperPathLight))
{
var lightImage = new BitmapImage();
try
{
var lightFile = await StorageFile.GetFileFromPathAsync(WallpaperPathLight);
await lightImage.SetSourceAsync(await lightFile.OpenReadAsync()); // thrown here when the image is invalid
WallpaperSourceLight = lightImage;
IsLightWallpaperValid = true;
}
catch (Exception)
{
DeleteFile(WallpaperPathLight);
WallpaperPathLight = null;
IsLightWallpaperValid = false;
WallpaperSourceLight = null;
IsWallpaperEnabled = false;
}
}
else if (e.PropertyName == nameof(WallpaperPathDark))
{
var darkImage = new BitmapImage();
try
{
var darkFile = await StorageFile.GetFileFromPathAsync(WallpaperPathDark);
await darkImage.SetSourceAsync(await darkFile.OpenReadAsync());
WallpaperSourceDark = darkImage;
IsDarkWallpaperValid = true;
}
catch (Exception)
{
DeleteFile(WallpaperPathDark);
WallpaperPathDark = null;
IsDarkWallpaperValid = false;
WallpaperSourceDark = null;
IsWallpaperEnabled = false;
}
}
}
private int GetRegistryBuildNumber()
{
var value = Win32.Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber", string.Empty);
#pragma warning disable CA1305
return int.Parse(value as string);
#pragma warning restore CA1305
}
public bool Is24H2OrLater
{
get => GetRegistryBuildNumber() > 26100;
}
private bool _enabledStateIsGPOConfigured;
private bool _enabledGPOConfiguration;
private LightSwitchSettings _moduleSettings;
@@ -808,10 +581,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private HotkeySettings _toggleThemeHotkey;
private TimeSpan? _sunriseTimeSpan;
private TimeSpan? _sunsetTimeSpan;
private bool _isLightWallpaperValid;
private bool _isDarkWallpaperValid;
private ImageSource _wallpaperSourceLight;
private ImageSource _wallpaperSourceDark;
public ICommand ForceLightCommand { get; }

View File

@@ -11,6 +11,9 @@ Target platform (e.g., 'x64', 'arm64'). If omitted the script will try to detect
.PARAMETER Configuration
Build configuration (e.g., 'Debug', 'Release'). Default: 'Debug'.
.PARAMETER Path
Optional directory path containing projects to build. If not specified, uses the current working directory.
.PARAMETER RestoreOnly
If specified, only perform package restore for local projects and skip the build steps for a solution file (i.e. .sln).
@@ -21,6 +24,10 @@ Any remaining, positional arguments passed to the script are forwarded to MSBuil
.\tools\build\build.ps1
Builds any .sln/.csproj/.vcxproj in the current working directory (auto-detects Platform).
.EXAMPLE
.\tools\build\build.ps1 -Platform x64 -Configuration Release -Path "C:\MyProject\src"
Builds local projects in the specified directory for x64 Release.
.EXAMPLE
.\tools\build\build.ps1 -Platform x64 -Configuration Release
Builds local projects for x64 Release.
@@ -41,6 +48,7 @@ Only restores packages for local projects; ExtraArgs still forwarded to msbuild'
param (
[string]$Platform = '',
[string]$Configuration = 'Debug',
[string]$Path = '',
[switch]$RestoreOnly,
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]$ExtraArgs
@@ -78,7 +86,11 @@ if (-not $Platform -or $Platform -eq '') {
}
}
$cwd = (Get-Location).ProviderPath
$cwd = if ($Path) {
(Resolve-Path $Path).ProviderPath
} else {
(Get-Location).ProviderPath
}
$extraArgsString = $null
if ($ExtraArgs -and $ExtraArgs.Count -gt 0) { $extraArgsString = ($ExtraArgs -join ' ') }