Compare commits
145 Commits
user/yeela
...
jay/ls-ui-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
237d1d5537 | ||
|
|
7524dc9a79 | ||
|
|
7d91089180 | ||
|
|
8f691c99d0 | ||
|
|
59cdd6e359 | ||
|
|
2c8fe39458 | ||
|
|
c51e69a8f2 | ||
|
|
ef7d154f79 | ||
|
|
551f92ef63 | ||
|
|
0e26f43df9 | ||
|
|
329a84942c | ||
|
|
2995659e8a | ||
|
|
544aca6273 | ||
|
|
4cc8d05b7d | ||
|
|
8bb96d1ea0 | ||
|
|
af8bc74d5d | ||
|
|
da250b2264 | ||
|
|
0bcbd07f5c | ||
|
|
ca65453ec5 | ||
|
|
831b4b23c8 | ||
|
|
eed4033ade | ||
|
|
0cb9b35dfa | ||
|
|
14efadcab7 | ||
|
|
a5901e02a2 | ||
|
|
c432f3741f | ||
|
|
3fbf10a946 | ||
|
|
f6d7c5aefe | ||
|
|
f2352bcedf | ||
|
|
52f44c5693 | ||
|
|
23da7de971 | ||
|
|
03760fa182 | ||
|
|
0f0285c364 | ||
|
|
107967aa45 | ||
|
|
9283f99a31 | ||
|
|
8073be34b6 | ||
|
|
d13216eb01 | ||
|
|
54887c7aa1 | ||
|
|
131b8c6743 | ||
|
|
77aa112aca | ||
|
|
eb41935c50 | ||
|
|
6fee7eb55f | ||
|
|
e5ddd39d0f | ||
|
|
0fb9333c69 | ||
|
|
d6c6395b80 | ||
|
|
e894982069 | ||
|
|
8d8ed80fdb | ||
|
|
02e87f8e78 | ||
|
|
c27b0fff9c | ||
|
|
b585fa4534 | ||
|
|
467b66d313 | ||
|
|
94be1f2be7 | ||
|
|
e9bcce7ec5 | ||
|
|
b0373362ee | ||
|
|
f0cc35c1c1 | ||
|
|
9a44c6e4ee | ||
|
|
ae00b77f8a | ||
|
|
6bfb11eafb | ||
|
|
e65d8fc2ed | ||
|
|
c1510f2075 | ||
|
|
7de9686a38 | ||
|
|
b6406eaa7e | ||
|
|
ad4ef1a8c3 | ||
|
|
f7139b88b2 | ||
|
|
d34ba803be | ||
|
|
a3a8c90df9 | ||
|
|
c8d9a8ccb6 | ||
|
|
a5dffc6ee3 | ||
|
|
2ebe234dca | ||
|
|
c68bafcd1f | ||
|
|
c4a4c1d659 | ||
|
|
f83e5ff218 | ||
|
|
0462089c62 | ||
|
|
af83f7d3ad | ||
|
|
d10ecd8a13 | ||
|
|
e7ecaafdc1 | ||
|
|
432120f56b | ||
|
|
88f66169cc | ||
|
|
1c4503753e | ||
|
|
0810db222d | ||
|
|
1dac59c9c3 | ||
|
|
2c434d33f2 | ||
|
|
083ff01f47 | ||
|
|
b958f546b9 | ||
|
|
69b624991f | ||
|
|
d4b379d90a | ||
|
|
6c031d3b47 | ||
|
|
bccaaad247 | ||
|
|
9c4a713791 | ||
|
|
1be874eade | ||
|
|
565ffb9084 | ||
|
|
afd32d0196 | ||
|
|
e80f38a302 | ||
|
|
ee2dffc6a4 | ||
|
|
fee2d0ab81 | ||
|
|
d403d5a7fb | ||
|
|
289f47c1ea | ||
|
|
6bbfdd9a93 | ||
|
|
47102583af | ||
|
|
52ff8bea34 | ||
|
|
a3441eecc6 | ||
|
|
88cf1ecaec | ||
|
|
a1b09180d8 | ||
|
|
6907f26243 | ||
|
|
911a4e1009 | ||
|
|
44be38e9b6 | ||
|
|
00d15ba780 | ||
|
|
b1b1791489 | ||
|
|
05ae7129aa | ||
|
|
4b015605a1 | ||
|
|
fa29bebec3 | ||
|
|
9816d6fc05 | ||
|
|
323ddfdf55 | ||
|
|
ae6187101f | ||
|
|
be105b5e27 | ||
|
|
8aedc4a61d | ||
|
|
9c82281bb1 | ||
|
|
df74f6e3c7 | ||
|
|
887e552d43 | ||
|
|
df61b2863e | ||
|
|
9f581101a8 | ||
|
|
db41f61010 | ||
|
|
e28e4582c9 | ||
|
|
abc7f3f3fb | ||
|
|
039991bc4a | ||
|
|
a857cc688b | ||
|
|
1be5e5931a | ||
|
|
03fafa747f | ||
|
|
62b4075349 | ||
|
|
cc42876c01 | ||
|
|
2d30fe2ec2 | ||
|
|
247cc47491 | ||
|
|
7de506010e | ||
|
|
736a04f65c | ||
|
|
b66b44cc49 | ||
|
|
f55f465c83 | ||
|
|
30e6215003 | ||
|
|
bdafb0e38a | ||
|
|
50c5d577bc | ||
|
|
a9e838ae1d | ||
|
|
c47ff9cd55 | ||
|
|
104d4fd6a0 | ||
|
|
37242fbb4d | ||
|
|
2c39113914 | ||
|
|
bf07c11640 | ||
|
|
2c6a8bac27 |
13
.github/actions/spell-check/expect.txt
vendored
@@ -3,6 +3,7 @@ abcdefghjkmnpqrstuvxyz
|
||||
abgr
|
||||
ABlocked
|
||||
ABOUTBOX
|
||||
ABORTIFHUNG
|
||||
Abug
|
||||
Acceleratorkeys
|
||||
ACCEPTFILES
|
||||
@@ -77,6 +78,7 @@ appwiz
|
||||
appxpackage
|
||||
APSTUDIO
|
||||
AQS
|
||||
Aquadrant
|
||||
ARandom
|
||||
ARCHITEW
|
||||
ARemapped
|
||||
@@ -454,6 +456,7 @@ encryptor
|
||||
ENDSESSION
|
||||
ENSUREVISIBLE
|
||||
ENTERSIZEMOVE
|
||||
ENTRYW
|
||||
ENU
|
||||
environmentvariables
|
||||
EOAC
|
||||
@@ -571,6 +574,7 @@ GETDESKWALLPAPER
|
||||
GETDLGCODE
|
||||
GETDPISCALEDSIZE
|
||||
getfilesiginforedist
|
||||
geolocator
|
||||
GETHOTKEY
|
||||
GETICON
|
||||
GETMINMAXINFO
|
||||
@@ -826,6 +830,7 @@ lastcodeanalysissucceeded
|
||||
LASTEXITCODE
|
||||
LAYOUTRTL
|
||||
LCh
|
||||
lbl
|
||||
lcid
|
||||
LCIDTo
|
||||
lcl
|
||||
@@ -844,6 +849,7 @@ LIBID
|
||||
LIMITSIZE
|
||||
LIMITTEXT
|
||||
lindex
|
||||
lightswitch
|
||||
linkid
|
||||
LINKOVERLAY
|
||||
LINQTo
|
||||
@@ -867,10 +873,13 @@ logon
|
||||
LOGMSG
|
||||
LOGPIXELSX
|
||||
LOGPIXELSY
|
||||
lng
|
||||
LOn
|
||||
lon
|
||||
longdate
|
||||
LONGNAMES
|
||||
lowlevel
|
||||
lquadrant
|
||||
LOWORD
|
||||
lparam
|
||||
LPBITMAPINFOHEADER
|
||||
@@ -1383,6 +1392,7 @@ quickaccent
|
||||
QUNS
|
||||
RAII
|
||||
RAlt
|
||||
RAquadrant
|
||||
randi
|
||||
rasterization
|
||||
Rasterize
|
||||
@@ -1681,6 +1691,7 @@ Subdomain
|
||||
SUBMODULEUPDATE
|
||||
subresource
|
||||
Superbar
|
||||
suntimes
|
||||
sut
|
||||
svchost
|
||||
SVGIn
|
||||
@@ -1746,6 +1757,7 @@ tgz
|
||||
themeresources
|
||||
THH
|
||||
THICKFRAME
|
||||
THEMECHANGED
|
||||
THISCOMPONENT
|
||||
throughs
|
||||
thumbnailhotkey
|
||||
@@ -1762,6 +1774,7 @@ tkconverters
|
||||
tlb
|
||||
tlbimp
|
||||
tlc
|
||||
tmain
|
||||
TNP
|
||||
TOGGLEEASYMOUSE
|
||||
Toolhelp
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
"PowerToys.ImageResizerContextMenu.dll",
|
||||
"ImageResizerContextMenuPackage.msix",
|
||||
|
||||
"PowerToys.LightSwitchModuleInterface.dll",
|
||||
"LightSwitchService\\PowerToys.LightSwitchService.exe",
|
||||
|
||||
"PowerToys.KeyboardManager.dll",
|
||||
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
|
||||
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
|
||||
|
||||
@@ -5,11 +5,13 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}
|
||||
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}
|
||||
{0B593A6C-4143-4337-860E-DB5710FB87DB} = {0B593A6C-4143-4337-860E-DB5710FB87DB}
|
||||
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
|
||||
{217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA}
|
||||
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166} = {38177D56-6AD1-4ADF-88C9-2843A7932166}
|
||||
{48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227}
|
||||
{51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}
|
||||
@@ -793,6 +795,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LightSwitch", "LightSwitch", "{5B201255-53C8-490B-A34F-01F05D48A477}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "src\modules\LightSwitch\LightSwitchModuleInterface\LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchService", "src\modules\LightSwitch\LightSwitchService\LightSwitchService.vcxproj", "{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
|
||||
@@ -811,6 +819,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3DCCD936-D085-4869-A1DE-CA6A64152C94}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightSwitch.UITests", "src\modules\LightSwitch\Tests\LightSwitch.UITests\LightSwitch.UITests.csproj", "{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj", "{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -2891,6 +2903,22 @@ Global
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.Build.0 = Debug|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.ActiveCfg = Release|x64
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.Build.0 = Debug|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.ActiveCfg = Release|x64
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.Build.0 = Release|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2947,6 +2975,18 @@ Global
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|x64.Build.0 = Debug|x64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|x64.ActiveCfg = Release|x64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|x64.Build.0 = Release|x64
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|x64.Deploy.0 = Release|x64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -3268,6 +3308,9 @@ Global
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{5B201255-53C8-490B-A34F-01F05D48A477} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{38177D56-6AD1-4ADF-88C9-2843A7932166} = {5B201255-53C8-490B-A34F-01F05D48A477}
|
||||
{08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {5B201255-53C8-490B-A34F-01F05D48A477}
|
||||
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
|
||||
{9605B84E-FAC4-477B-B9EC-0753177EE6A8} = {557C4636-D7E1-4838-A504-7D19B725EE95}
|
||||
@@ -3277,6 +3320,8 @@ Global
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{3DCCD936-D085-4869-A1DE-CA6A64152C94} = {5B201255-53C8-490B-A34F-01F05D48A477}
|
||||
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F} = {3DCCD936-D085-4869-A1DE-CA6A64152C94}
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
107
doc/devdocs/modules/lightswitch.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Light Switch
|
||||
|
||||
[Public Overview – Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/light-switch)
|
||||
|
||||
## Quick Links
|
||||
|
||||
* [All Issues](https://github.com/microsoft/PowerToys/issues?q=is%3Aissue%20state%3Aopen%20label%3AProduct-LightSwitch)
|
||||
* [Bugs](https://github.com/microsoft/PowerToys/issues?q=is%3Aissue%20state%3Aopen%20label%3AProduct-LightSwitch%20label%3AIssue-Bug)
|
||||
* [Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3AProduct-LightSwitch)
|
||||
|
||||
## Overview
|
||||
|
||||
The **Light Switch** module lets users automatically transition between light and dark mode using a timed schedule or a keyboard shortcut.
|
||||
|
||||
## Features
|
||||
|
||||
* Set custom times to start and stop dark mode.
|
||||
* Use geolocation to determine local sunrise and sunset times.
|
||||
* Apply offsets in sunrise mode (e.g., 15 minutes before sunset).
|
||||
* Quickly toggle between modes with a keyboard shortcut (`Ctrl+Shift+Win+D` by default).
|
||||
* Choose whether theme changes apply to:
|
||||
|
||||
* Apps only
|
||||
* System only
|
||||
* Both apps and system
|
||||
|
||||
## Architecture
|
||||
|
||||
### Main Components
|
||||
|
||||
* **Shortcut/Hotkey**
|
||||
Listens for a hotkey event. Calling `onHotkey()` flips the theme flags.
|
||||
|
||||
> **Note:** Using the shortcut overrides the current schedule until the next transition event.
|
||||
|
||||
* **LightSwitchService**
|
||||
Reads settings and applies theming. Runs a check every minute to ensure the state is correct.
|
||||
|
||||
* **SettingsXAML/LightSwitch**
|
||||
Provides the settings UI for configuring schedules, syncing location, and customizing shortcuts.
|
||||
|
||||
* **Settings.UI/ViewModels/LightSwitchViewModel.cs**
|
||||
Handles updates to the settings file and communicates changes to the front end.
|
||||
|
||||
* **modules/LightSwitch/Tests**
|
||||
Contains UI tests that verify interactions between the settings UI, system state, and `settings.json`.
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. User configures settings in the UI (default: manual mode, light mode from 06:00–18:00).
|
||||
2. Every minute, the service checks the time.
|
||||
|
||||
* If it’s not a threshold, the service sleeps until the next minute.
|
||||
* If it matches a threshold, the service applies the theme based on settings and returns to sleep.
|
||||
3. At **midnight**, when in *Sunrise to Sunset* mode, the service updates daily sunrise and sunset times.
|
||||
4. If the machine was asleep during a scheduled event, the service applies the correct settings at the next check.
|
||||
|
||||
## User Interface
|
||||
|
||||
The module’s settings are exposed in the PowerToys Settings UI. Options include:
|
||||
|
||||
* Shortcut customization
|
||||
* Mode selection (Manual or Sunrise to Sunset)
|
||||
* Manual start/stop times (manual mode only)
|
||||
* Automatic sunrise/sunset calculation (location-based)
|
||||
* Time offsets (sunrise mode)
|
||||
* Target scope (system, apps, or both)
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* Visual Studio 2019 or later
|
||||
* Windows 10 SDK
|
||||
* PowerToys repository cloned from GitHub
|
||||
|
||||
### Building and Testing
|
||||
|
||||
1. Clone the repo:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/PowerToys.git
|
||||
```
|
||||
2. Initialize submodules:
|
||||
|
||||
```sh
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
3. Build the solution:
|
||||
|
||||
```sh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
|
||||
```
|
||||
|
||||
> Note: This may take some time.
|
||||
4. Set `runner` as the startup project and press **F5**.
|
||||
5. Enable Light Switch in PowerToys Settings.
|
||||
6. To debug the service:
|
||||
|
||||
* Press `Ctrl+Alt+P` or go to **Debug > Attach to Process**.
|
||||
* Select `LightSwitchService.exe` and click **Attach**.
|
||||
* You can now set breakpoints in the service files.
|
||||
7. To debug the Settings UI:
|
||||
|
||||
* Set the startup project to `PowerToys.Settings` and press **F5**.
|
||||
* Note: Light Switch settings will not persist in this mode (they depend on the service executable).
|
||||
* Alternatively, you can attach `PowerToys.Settings.exe` to the debugger while `runner` is running to test the full flow with breakpoints.
|
||||
BIN
doc/images/icons/Light Switch.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
doc/images/overview/LightSwitch_large.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
doc/images/overview/LightSwitch_small.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
doc/images/overview/Original/Light Switch.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
@@ -1283,7 +1283,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
}
|
||||
processes.resize(bytes / sizeof(processes[0]));
|
||||
|
||||
std::array<std::wstring_view, 41> processesToTerminate = {
|
||||
std::array<std::wstring_view, 42> processesToTerminate = {
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.Settings.exe",
|
||||
L"PowerToys.AdvancedPaste.exe",
|
||||
@@ -1298,6 +1298,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.Hosts.exe",
|
||||
L"PowerToys.PowerRename.exe",
|
||||
L"PowerToys.ImageResizer.exe",
|
||||
L"PowerToys.LightSwitchService.exe",
|
||||
L"PowerToys.GcodeThumbnailProvider.exe",
|
||||
L"PowerToys.BgcodeThumbnailProvider.exe",
|
||||
L"PowerToys.PdfThumbnailProvider.exe",
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Hosts.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Hosts.wxs.bk""""
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\ImageResizer.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\ImageResizer.wxs.bk""""
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\KeyboardManager.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\KeyboardManager.wxs.bk""""
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\LightSwitch.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\LightSwitch.wxs.bk""""
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\MouseWithoutBorders.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\MouseWithoutBorders.wxs.bk""""
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\NewPlus.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\NewPlus.wxs.bk""""
|
||||
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Peek.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Peek.wxs.bk""""
|
||||
|
||||
35
installer/PowerToysSetupVNext/LightSwitch.wxs
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
|
||||
<?include $(sys.CURRENTDIR)\Common.wxi?>
|
||||
|
||||
<?define LightSwitchFiles=?>
|
||||
<?define LightSwitchFilesPath=$(var.BinDir)\LightSwitchService\?>
|
||||
|
||||
<Fragment>
|
||||
<!-- Light Switch background service -->
|
||||
|
||||
<!-- Create a directory for the service binaries -->
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Directory Id="LightSwitchServiceFolder" Name="LightSwitchService" />
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- File components generated by generateAllFileComponents.ps1 -->
|
||||
<DirectoryRef Id="LightSwitchServiceFolder" FileSource="$(var.LightSwitchFilesPath)">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--LightSwitchFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- Group to include the service + cleanup on uninstall -->
|
||||
<ComponentGroup Id="LightSwitchComponentGroup">
|
||||
|
||||
<!-- Ensures folder removal on uninstall -->
|
||||
<Component Id="RemoveLightSwitchServiceFolder" Guid="C1E2F2ED-34A2-4EB0-8E17-DC0535F50F9D" Directory="INSTALLFOLDER">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveLightSwitchServiceFolder" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveFolderLightSwitchServiceFolder" Directory="LightSwitchServiceFolder" On="uninstall" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -41,6 +41,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
|
||||
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs
|
||||
call move /Y ..\..\..\Hosts.wxs.bk ..\..\..\Hosts.wxs
|
||||
call move /Y ..\..\..\LightSwitch.wxs.bk ..\..\..\LightSwitch.wxs
|
||||
call move /Y ..\..\..\ImageResizer.wxs.bk ..\..\..\ImageResizer.wxs
|
||||
call move /Y ..\..\..\KeyboardManager.wxs.bk ..\..\..\KeyboardManager.wxs
|
||||
call move /Y ..\..\..\MouseWithoutBorders.wxs.bk ..\..\..\MouseWithoutBorders.wxs
|
||||
@@ -114,6 +115,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
<Compile Include="FileLocksmith.wxs" />
|
||||
<Compile Include="Hosts.wxs" />
|
||||
<Compile Include="ImageResizer.wxs" />
|
||||
<Compile Include="LightSwitch.wxs" />
|
||||
<Compile Include="KeyboardManager.wxs" />
|
||||
<Compile Include="Peek.wxs" />
|
||||
<Compile Include="PowerRename.wxs" />
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<ComponentGroupRef Id="HostsComponentGroup" />
|
||||
<ComponentGroupRef Id="ImageResizerComponentGroup" />
|
||||
<ComponentGroupRef Id="KeyboardManagerComponentGroup" />
|
||||
<ComponentGroupRef Id="LightSwitchComponentGroup" />
|
||||
<ComponentGroupRef Id="PeekComponentGroup" />
|
||||
<ComponentGroupRef Id="PowerRenameComponentGroup" />
|
||||
<ComponentGroupRef Id="RegistryPreviewComponentGroup" />
|
||||
|
||||
@@ -182,6 +182,10 @@ Generate-FileComponents -fileListName "HostsAssetsFiles" -wxsFilePath $PSScriptR
|
||||
Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFilePath $PSScriptRoot\ImageResizer.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ImageResizer"
|
||||
Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs -regroot $registryroot
|
||||
|
||||
# Light Switch Service
|
||||
Generate-FileList -fileDepsJson "" -fileListName LightSwitchFiles -wxsFilePath $PSScriptRoot\LightSwitch.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\LightSwitchService"
|
||||
Generate-FileComponents -fileListName "LightSwitchFiles" -wxsFilePath $PSScriptRoot\LightSwitch.wxs -regroot $registryroot
|
||||
|
||||
#New+
|
||||
Generate-FileList -fileDepsJson "" -fileListName NewPlusAssetsFiles -wxsFilePath $PSScriptRoot\NewPlus.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\NewPlus"
|
||||
Generate-FileComponents -fileListName "NewPlusAssetsFiles" -wxsFilePath $PSScriptRoot\NewPlus.wxs -regroot $registryroot
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Common.UI
|
||||
Awake,
|
||||
ColorPicker,
|
||||
CmdNotFound,
|
||||
LightSwitch,
|
||||
FancyZones,
|
||||
FileLocksmith,
|
||||
Run,
|
||||
@@ -60,6 +61,8 @@ namespace Common.UI
|
||||
return "ColorPicker";
|
||||
case SettingsWindow.CmdNotFound:
|
||||
return "CmdNotFound";
|
||||
case SettingsWindow.LightSwitch:
|
||||
return "LightSwitch";
|
||||
case SettingsWindow.FancyZones:
|
||||
return "FancyZones";
|
||||
case SettingsWindow.FileLocksmith:
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredCropAndLockEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredLightSwitchEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredCmdPalEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace ManagedCommon
|
||||
Hosts,
|
||||
ImageResizer,
|
||||
KeyboardManager,
|
||||
LightSwitch,
|
||||
MouseHighlighter,
|
||||
MouseJump,
|
||||
MousePointerCrosshairs,
|
||||
|
||||
@@ -81,6 +81,14 @@ namespace Microsoft.PowerToys.UITest
|
||||
get { return this.windowsElement?.Selected ?? false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the UI element is visible to the user.
|
||||
/// </summary>
|
||||
public bool Displayed
|
||||
{
|
||||
get { return this.windowsElement?.Displayed ?? false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Rect of the UI element.
|
||||
/// </summary>
|
||||
@@ -329,7 +337,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Send Key of the element.
|
||||
/// </summary>
|
||||
/// <param name="key">The Key to Send.</param>
|
||||
protected void SendKeys(string key)
|
||||
public void SendKeys(string key)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
@@ -369,5 +377,19 @@ namespace Microsoft.PowerToys.UITest
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method SaveToPngFile with parameter: path = {path}");
|
||||
this.windowsElement.GetScreenshot().SaveAsFile(path);
|
||||
}
|
||||
|
||||
public void EnsureVisible(Element scrollViewer, int maxScrolls = 10)
|
||||
{
|
||||
int count = 0;
|
||||
if (scrollViewer.WindowsElement != null)
|
||||
{
|
||||
while (!this.windowsElement!.Displayed && count < maxScrolls)
|
||||
{
|
||||
scrollViewer.WindowsElement.SendKeys(OpenQA.Selenium.Keys.PageDown);
|
||||
Task.Delay(250).Wait();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
PowerRename,
|
||||
CommandPalette,
|
||||
ScreenRuler,
|
||||
LightSwitch,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +107,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
[PowerToysModule.PowerRename] = new ModuleInfo("PowerToys.PowerRename.exe", "PowerRename", "WinUI3Apps"),
|
||||
[PowerToysModule.CommandPalette] = new ModuleInfo("Microsoft.CmdPal.UI.exe", "PowerToys Command Palette", "WinUI3Apps\\CmdPal"),
|
||||
[PowerToysModule.ScreenRuler] = new ModuleInfo("PowerToys.MeasureToolUI.exe", "PowerToys.ScreenRuler", "WinUI3Apps"),
|
||||
[PowerToysModule.LightSwitch] = new ModuleInfo("PowerToys.LightSwitch.exe", "PowerToys.LightSwitch", "LightSwitchService"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ struct LogSettings
|
||||
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
|
||||
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.log";
|
||||
inline const static std::string zoomItLoggerName = "zoom-it";
|
||||
inline const static std::string lightSwitchLoggerName = "light-switch";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
LogSettings();
|
||||
|
||||
@@ -257,7 +257,9 @@ inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params,
|
||||
exec_info.nShow = SW_HIDE;
|
||||
}
|
||||
|
||||
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
|
||||
BOOL result = ShellExecuteExW(&exec_info);
|
||||
|
||||
return result ? exec_info.hProcess : nullptr;
|
||||
}
|
||||
|
||||
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CMD_NOT_FOUND = L"ConfigureEnabledUtilityCmdNotFound";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
|
||||
@@ -295,6 +296,11 @@ namespace powertoys_gpo
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredLightSwitchEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);
|
||||
|
||||
@@ -137,6 +137,16 @@
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityLightSwitch" class="Both" displayName="$(string.ConfigureEnabledUtilityLightSwitch)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityLightSwitch">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_95_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="ConfigureEnabledUtilityEnvironmentVariables" class="Both" displayName="$(string.ConfigureEnabledUtilityEnvironmentVariables)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityEnvironmentVariables">
|
||||
<parentCategory ref="PowerToys" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_75_0" />
|
||||
|
||||
@@ -245,6 +245,7 @@ If you don't configure this policy, the user will be able to control the setting
|
||||
<string id="ConfigureEnabledUtilityCmdNotFound">Command Not Found: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityCmdPal">CmdPal: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityCropAndLock">Crop And Lock: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityLightSwitch">Light Switch: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityEnvironmentVariables">Environment Variables: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFancyZones">FancyZones: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFileLocksmith">File Locksmith: Configure enabled state</string>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,1,0,0
|
||||
PRODUCTVERSION 0,1,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Company Name"
|
||||
VALUE "FileDescription", "Light Switch Module"
|
||||
VALUE "FileVersion", "0.1.0.0"
|
||||
VALUE "InternalName", "Light Switch"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2019 Company Name"
|
||||
VALUE "OriginalFilename", "PowerToys.LightSwitchModuleInterface.dll"
|
||||
VALUE "ProductName", "Light Switch"
|
||||
VALUE "ProductVersion", "0.1.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,225 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{38177d56-6ad1-4adf-88c9-2843a7932166}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>LightSwitchModuleInterface</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>LightSwitchModuleInterface</ProjectName>
|
||||
<TargetName>PowerToys.LightSwitchModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(CoreLibraryDependencies);%(AdditionalDependencies);advapi32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="ThemeHelper.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeHelper.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LightSwitchModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj">
|
||||
<Project>{4aed67b6-55fd-486f-b917-e543dee2cb3c}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<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">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeHelper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThemeHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{bbf22ac8-46f8-4206-b44b-9c3897e99ce5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{530ed784-9a70-46a0-8fb6-20d5dee4f7d3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{da1cb871-86d3-414c-adf5-a7e9f2077d2f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LightSwitchModuleInterface.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "pch.h"
|
||||
#include <windows.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
// Controls changing the themes.
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
void SetSystemTheme(bool dark);
|
||||
void SetAppsTheme(bool dark);
|
||||
bool GetCurrentSystemTheme();
|
||||
bool GetCurrentAppsTheme();
|
||||
570
src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp
Normal file
@@ -0,0 +1,570 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_TOGGLE_THEME_HOTKEY[] = L"toggle-theme-hotkey";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
}
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// The PowerToy name that will be shown in the settings.
|
||||
const static wchar_t* MODULE_NAME = L"LightSwitch";
|
||||
// Add a description that will we shown in the module settings page.
|
||||
const static wchar_t* MODULE_DESC = L"This is a module that allows you to control light/dark theming via set times, sun rise, or directly invoking the change.";
|
||||
|
||||
enum class ScheduleMode
|
||||
{
|
||||
FixedHours,
|
||||
SunsetToSunrise,
|
||||
// add more later
|
||||
};
|
||||
|
||||
inline std::wstring ToString(ScheduleMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ScheduleMode::SunsetToSunrise:
|
||||
return L"SunsetToSunrise";
|
||||
case ScheduleMode::FixedHours:
|
||||
default:
|
||||
return L"FixedHours";
|
||||
}
|
||||
}
|
||||
|
||||
inline ScheduleMode FromString(const std::wstring& str)
|
||||
{
|
||||
if (str == L"SunsetToSunrise")
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
return ScheduleMode::FixedHours;
|
||||
}
|
||||
|
||||
// These are the properties shown in the Settings page.
|
||||
struct ModuleSettings
|
||||
{
|
||||
bool m_changeSystem = true;
|
||||
bool m_changeApps = true;
|
||||
ScheduleMode m_scheduleMode = ScheduleMode::FixedHours;
|
||||
int m_lightTime = 480;
|
||||
int m_darkTime = 1200;
|
||||
int m_sunrise_offset = 0;
|
||||
int m_sunset_offset = 0;
|
||||
std::wstring m_latitude = L"0.0";
|
||||
std::wstring m_longitude = L"0.0";
|
||||
} g_settings;
|
||||
|
||||
class LightSwitchInterface : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
|
||||
HANDLE m_process{ nullptr };
|
||||
HANDLE m_force_light_event_handle;
|
||||
HANDLE m_force_dark_event_handle;
|
||||
HANDLE m_manual_override_event_handle;
|
||||
|
||||
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
|
||||
|
||||
Hotkey m_toggle_theme_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'D' };
|
||||
|
||||
void init_settings();
|
||||
|
||||
public:
|
||||
LightSwitchInterface()
|
||||
{
|
||||
LoggerHelpers::init_logger(L"LightSwitch", L"ModuleInterface", LogSettings::lightSwitchLoggerName);
|
||||
|
||||
m_force_light_event_handle = CreateDefaultEvent(L"POWERTOYS_LIGHTSWITCH_FORCE_LIGHT");
|
||||
m_force_dark_event_handle = CreateDefaultEvent(L"POWERTOYS_LIGHTSWITCH_FORCE_DARK");
|
||||
m_manual_override_event_handle = CreateDefaultEvent(L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||
|
||||
init_settings();
|
||||
};
|
||||
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return L"LightSwitch";
|
||||
}
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
virtual void destroy() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
// Return the display name of the powertoy, this will be cached by the runner
|
||||
virtual const wchar_t* get_name() override
|
||||
{
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
// Return the configured status for the gpo policy for the module
|
||||
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
|
||||
{
|
||||
return powertoys_gpo::getConfiguredLightSwitchEnabledValue();
|
||||
}
|
||||
|
||||
// Return JSON with the configuration options.
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
|
||||
// Create a Settings object with your module name
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
settings.set_overview_link(L"https://aka.ms/powertoys");
|
||||
|
||||
// Boolean toggles
|
||||
settings.add_bool_toggle(
|
||||
L"changeSystem",
|
||||
L"Change System Theme",
|
||||
g_settings.m_changeSystem);
|
||||
|
||||
settings.add_bool_toggle(
|
||||
L"changeApps",
|
||||
L"Change Apps Theme",
|
||||
g_settings.m_changeApps);
|
||||
|
||||
settings.add_choice_group(
|
||||
L"scheduleMode",
|
||||
L"Theme schedule mode",
|
||||
ToString(g_settings.m_scheduleMode),
|
||||
{ { L"FixedHours", L"Set hours manually" },
|
||||
{ L"SunsetToSunrise", L"Use sunrise/sunset times" } });
|
||||
|
||||
// Integer spinners
|
||||
settings.add_int_spinner(
|
||||
L"lightTime",
|
||||
L"Time to switch to light theme (minutes after midnight).",
|
||||
g_settings.m_lightTime,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
settings.add_int_spinner(
|
||||
L"darkTime",
|
||||
L"Time to switch to dark theme (minutes after midnight).",
|
||||
g_settings.m_darkTime,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
settings.add_int_spinner(
|
||||
L"sunrise_offset",
|
||||
L"Time to offset turning on your light theme.",
|
||||
g_settings.m_sunrise_offset,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
settings.add_int_spinner(
|
||||
L"sunset_offset",
|
||||
L"Time to offset turning on your dark theme.",
|
||||
g_settings.m_sunset_offset,
|
||||
0,
|
||||
1439,
|
||||
1);
|
||||
|
||||
// Strings for latitude and longitude
|
||||
settings.add_string(
|
||||
L"latitude",
|
||||
L"Your latitude in decimal degrees (e.g. 39.95).",
|
||||
g_settings.m_latitude);
|
||||
|
||||
settings.add_string(
|
||||
L"longitude",
|
||||
L"Your longitude in decimal degrees (e.g. -75.16).",
|
||||
g_settings.m_longitude);
|
||||
|
||||
// One-shot actions (buttons)
|
||||
settings.add_custom_action(
|
||||
L"forceLight",
|
||||
L"Switch immediately to light theme",
|
||||
L"Force Light",
|
||||
L"{}");
|
||||
|
||||
settings.add_custom_action(
|
||||
L"forceDark",
|
||||
L"Switch immediately to dark theme",
|
||||
L"Force Dark",
|
||||
L"{}");
|
||||
|
||||
// Hotkeys
|
||||
PowerToysSettings::HotkeyObject dm_hk = PowerToysSettings::HotkeyObject::from_settings(
|
||||
m_toggle_theme_hotkey.win,
|
||||
m_toggle_theme_hotkey.ctrl,
|
||||
m_toggle_theme_hotkey.alt,
|
||||
m_toggle_theme_hotkey.shift,
|
||||
m_toggle_theme_hotkey.key);
|
||||
|
||||
settings.add_hotkey(
|
||||
L"toggle-theme-hotkey",
|
||||
L"Shortcut to toggle theme immediately",
|
||||
dm_hk);
|
||||
|
||||
// Serialize to buffer for the PowerToys runner
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
// Signal from the Settings editor to call a custom action.
|
||||
// This can be used to spawn more complex editors.
|
||||
void call_custom_action(const wchar_t* action) override
|
||||
{
|
||||
try
|
||||
{
|
||||
auto action_object = PowerToysSettings::CustomActionObject::from_json_string(action);
|
||||
|
||||
if (action_object.get_name() == L"forceLight")
|
||||
{
|
||||
Logger::info(L"[Light Switch] Custom action triggered: Force Light");
|
||||
SetSystemTheme(true);
|
||||
SetAppsTheme(true);
|
||||
}
|
||||
else if (action_object.get_name() == L"forceDark")
|
||||
{
|
||||
Logger::info(L"[Light Switch] Custom action triggered: Force Dark");
|
||||
SetSystemTheme(false);
|
||||
SetAppsTheme(false);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error(L"[Light Switch] Invalid custom action JSON");
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the runner to pass the updated settings values as a serialized JSON.
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
|
||||
parse_hotkey(values);
|
||||
|
||||
if (auto v = values.get_bool_value(L"changeSystem"))
|
||||
{
|
||||
g_settings.m_changeSystem = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_bool_value(L"changeApps"))
|
||||
{
|
||||
g_settings.m_changeApps = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_string_value(L"scheduleMode"))
|
||||
{
|
||||
g_settings.m_scheduleMode = FromString(*v);
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"lightTime"))
|
||||
{
|
||||
g_settings.m_lightTime = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"darkTime"))
|
||||
{
|
||||
g_settings.m_darkTime = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"sunrise_offset"))
|
||||
{
|
||||
g_settings.m_sunrise_offset = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_int_value(L"m_sunset_offset"))
|
||||
{
|
||||
g_settings.m_sunset_offset = *v;
|
||||
}
|
||||
|
||||
if (auto v = values.get_string_value(L"latitude"))
|
||||
{
|
||||
g_settings.m_latitude = *v;
|
||||
}
|
||||
if (auto v = values.get_string_value(L"longitude"))
|
||||
{
|
||||
g_settings.m_longitude = *v;
|
||||
}
|
||||
|
||||
values.save_to_settings_file();
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Logger::error("[Light Switch] set_config: Failed to parse or apply config.");
|
||||
}
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
Logger::info(L"Enabling Light Switch module...");
|
||||
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
std::wstring args = L"--pid " + std::to_wstring(powertoys_pid);
|
||||
std::wstring exe_name = L"LightSwitchService\\PowerToys.LightSwitchService.exe";
|
||||
|
||||
std::wstring resolved_path(MAX_PATH, L'\0');
|
||||
DWORD result = SearchPathW(
|
||||
nullptr,
|
||||
exe_name.c_str(),
|
||||
nullptr,
|
||||
static_cast<DWORD>(resolved_path.size()),
|
||||
resolved_path.data(),
|
||||
nullptr);
|
||||
|
||||
if (result == 0 || result >= resolved_path.size())
|
||||
{
|
||||
Logger::error(
|
||||
L"Failed to locate Light Switch executable named '{}' at location '{}'",
|
||||
exe_name,
|
||||
resolved_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
resolved_path.resize(result);
|
||||
Logger::debug(L"Resolved executable path: {}", resolved_path);
|
||||
|
||||
std::wstring command_line = L"\"" + resolved_path + L"\" " + args;
|
||||
|
||||
STARTUPINFO si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
if (!CreateProcessW(
|
||||
resolved_path.c_str(),
|
||||
command_line.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
TRUE,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&si,
|
||||
&pi))
|
||||
{
|
||||
Logger::error(L"Failed to launch Light Switch process. {}", get_last_error_or_default(GetLastError()));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info(L"Light Switch process launched successfully (PID: {}).", pi.dwProcessId);
|
||||
m_process = pi.hProcess;
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
virtual void disable()
|
||||
{
|
||||
Logger::info("Light Switch disabling");
|
||||
m_enabled = false;
|
||||
|
||||
if (m_process)
|
||||
{
|
||||
constexpr DWORD timeout_ms = 1500;
|
||||
DWORD result = WaitForSingleObject(m_process, timeout_ms);
|
||||
|
||||
if (result == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::warn("Light Switch: Process didn't exit in time. Forcing termination.");
|
||||
TerminateProcess(m_process, 0);
|
||||
}
|
||||
|
||||
CloseHandle(m_manual_override_event_handle);
|
||||
m_manual_override_event_handle = nullptr;
|
||||
|
||||
CloseHandle(m_process);
|
||||
m_process = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
Hotkey _temp_toggle_theme;
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_TOGGLE_THEME_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
|
||||
_temp_toggle_theme.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
_temp_toggle_theme.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
_temp_toggle_theme.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
_temp_toggle_theme.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
_temp_toggle_theme.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
m_toggle_theme_hotkey = _temp_toggle_theme;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize Light Switch force dark mode shortcut from settings. Value will keep unchanged.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("Light Switch settings are empty");
|
||||
}
|
||||
}
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (hotkeys && buffer_size >= 1)
|
||||
{
|
||||
hotkeys[0] = m_toggle_theme_hotkey;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"Light Switch hotkey pressed");
|
||||
if (!is_process_running())
|
||||
{
|
||||
enable();
|
||||
}
|
||||
else if (hotkeyId == 0)
|
||||
{
|
||||
// get current will return true if in light mode, otherwise false
|
||||
Logger::info(L"[Light Switch] Hotkey triggered: Toggle Theme");
|
||||
if (g_settings.m_changeSystem)
|
||||
{
|
||||
SetSystemTheme(!GetCurrentSystemTheme());
|
||||
}
|
||||
if (g_settings.m_changeApps)
|
||||
{
|
||||
SetAppsTheme(!GetCurrentAppsTheme());
|
||||
}
|
||||
|
||||
if (m_manual_override_event_handle)
|
||||
{
|
||||
SetEvent(m_manual_override_event_handle);
|
||||
Logger::debug(L"[Light Switch] Manual override event set");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_process_running()
|
||||
{
|
||||
return WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
};
|
||||
|
||||
std::wstring utf8_to_wstring(const std::string& str)
|
||||
{
|
||||
if (str.empty())
|
||||
return std::wstring();
|
||||
|
||||
int size_needed = MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
str.c_str(),
|
||||
static_cast<int>(str.size()),
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
std::wstring wstr(size_needed, 0);
|
||||
|
||||
MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
str.c_str(),
|
||||
static_cast<int>(str.size()),
|
||||
&wstr[0],
|
||||
size_needed);
|
||||
|
||||
return wstr;
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
void LightSwitchInterface::init_settings()
|
||||
{
|
||||
Logger::info(L"[Light Switch] init_settings: starting to load settings for module");
|
||||
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues settings =
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(get_name());
|
||||
|
||||
parse_hotkey(settings);
|
||||
|
||||
if (auto v = settings.get_bool_value(L"changeSystem"))
|
||||
g_settings.m_changeSystem = *v;
|
||||
if (auto v = settings.get_bool_value(L"changeApps"))
|
||||
g_settings.m_changeApps = *v;
|
||||
if (auto v = settings.get_string_value(L"scheduleMode"))
|
||||
g_settings.m_scheduleMode = FromString(*v);
|
||||
if (auto v = settings.get_int_value(L"lightTime"))
|
||||
g_settings.m_lightTime = *v;
|
||||
if (auto v = settings.get_int_value(L"darkTime"))
|
||||
g_settings.m_darkTime = *v;
|
||||
if (auto v = settings.get_int_value(L"sunrise_offset"))
|
||||
g_settings.m_sunrise_offset = *v;
|
||||
if (auto v = settings.get_int_value(L"sunset_offset"))
|
||||
g_settings.m_sunset_offset = *v;
|
||||
if (auto v = settings.get_string_value(L"latitude"))
|
||||
g_settings.m_latitude = *v;
|
||||
if (auto v = settings.get_string_value(L"longitude"))
|
||||
g_settings.m_longitude = *v;
|
||||
|
||||
Logger::info(L"[Light Switch] init_settings: loaded successfully");
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
Logger::error(L"[Light Switch] init_settings: hresult_error 0x{:08X} - {}", e.code(), e.message().c_str());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::wstring whatStr = utf8_to_wstring(e.what());
|
||||
Logger::error(L"[Light Switch] init_settings: std::exception - {}", whatStr);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error(L"[Light Switch] init_settings: unknown exception while loading settings");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new LightSwitchInterface();
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#include "pch.h"
|
||||
#pragma comment(lib, "windowsapp")
|
||||
14
src/modules/LightSwitch/LightSwitchModuleInterface/pch.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/gpo.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.System.h>
|
||||
#include <winrt/Windows.Globalization.h>
|
||||
#include <winrt/Windows.ApplicationModel.h>
|
||||
#include <winrt/Windows.ApplicationModel.Core.h>
|
||||
30
src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
#include <TraceLoggingProvider.h>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::MyEvent()
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"PowerToyName_MyEvent",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
15
src/modules/LightSwitch/LightSwitchModuleInterface/trace.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
#include <common/telemetry/ProjectTelemetry.h>
|
||||
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hProvider);
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
static void MyEvent();
|
||||
};
|
||||
BIN
src/modules/LightSwitch/LightSwitchService/LightSwitch.ico
Normal file
|
After Width: | Height: | Size: 88 KiB |
@@ -0,0 +1,295 @@
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include "ThemeScheduler.h"
|
||||
#include "ThemeHelper.h"
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <LightSwitchSettings.h>
|
||||
#include <common/utils/gpo.h>
|
||||
|
||||
SERVICE_STATUS g_ServiceStatus = {};
|
||||
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
|
||||
HANDLE g_ServiceStopEvent = nullptr;
|
||||
static int g_lastUpdatedDay = -1;
|
||||
|
||||
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
|
||||
|
||||
// Entry point for the executable
|
||||
int _tmain(int argc, TCHAR* argv[])
|
||||
{
|
||||
DWORD parentPid = 0;
|
||||
bool debug = false;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (_tcscmp(argv[i], _T("--debug")) == 0)
|
||||
debug = true;
|
||||
else if (_tcscmp(argv[i], _T("--pid")) == 0 && i + 1 < argc)
|
||||
parentPid = _tstoi(argv[++i]);
|
||||
}
|
||||
|
||||
// Try to connect to SCM
|
||||
wchar_t serviceName[] = L"LightSwitchService";
|
||||
SERVICE_TABLE_ENTRYW table[] = { { serviceName, ServiceMain }, { nullptr, nullptr } };
|
||||
|
||||
if (!StartServiceCtrlDispatcherW(table))
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) // not launched by SCM
|
||||
{
|
||||
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
HANDLE hThread = CreateThread(
|
||||
nullptr, 0, ServiceWorkerThread, reinterpret_cast<void*>(static_cast<ULONG_PTR>(parentPid)), 0, nullptr);
|
||||
|
||||
// Wait so the process stays alive
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
CloseHandle(hThread);
|
||||
CloseHandle(g_ServiceStopEvent);
|
||||
return 0;
|
||||
}
|
||||
return static_cast<int>(err);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called when the service is launched by Windows
|
||||
VOID WINAPI ServiceMain(DWORD, LPTSTR*)
|
||||
{
|
||||
g_StatusHandle = RegisterServiceCtrlHandler(_T("LightSwitchService"), ServiceCtrlHandler);
|
||||
if (!g_StatusHandle)
|
||||
return;
|
||||
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!g_ServiceStopEvent)
|
||||
{
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = GetLastError();
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
SECURITY_ATTRIBUTES sa{ sizeof(sa) };
|
||||
sa.bInheritHandle = FALSE;
|
||||
sa.lpSecurityDescriptor = nullptr;
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
HANDLE hThread = CreateThread(nullptr, 0, ServiceWorkerThread, nullptr, 0, nullptr);
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
CloseHandle(hThread);
|
||||
|
||||
CloseHandle(g_ServiceStopEvent);
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
}
|
||||
|
||||
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl)
|
||||
{
|
||||
switch (dwCtrl)
|
||||
{
|
||||
case SERVICE_CONTROL_STOP:
|
||||
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
|
||||
break;
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
// Signal the service to stop
|
||||
SetEvent(g_ServiceStopEvent);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_sun_times(auto& settings)
|
||||
{
|
||||
double latitude = std::stod(settings.latitude);
|
||||
double longitude = std::stod(settings.longitude);
|
||||
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
|
||||
SunTimes newTimes = CalculateSunriseSunset(latitude, longitude, st.wYear, st.wMonth, st.wDay);
|
||||
|
||||
int newLightTime = newTimes.sunriseHour * 60 + newTimes.sunriseMinute;
|
||||
int newDarkTime = newTimes.sunsetHour * 60 + newTimes.sunsetMinute;
|
||||
|
||||
auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
|
||||
values.add_property(L"lightTime", newLightTime);
|
||||
values.add_property(L"darkTime", newDarkTime);
|
||||
values.save_to_settings_file();
|
||||
|
||||
OutputDebugString(L"[LightSwitchService] Updated sun times and saved to config.\n");
|
||||
}
|
||||
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
{
|
||||
DWORD parentPid = static_cast<DWORD>(reinterpret_cast<ULONG_PTR>(lpParam));
|
||||
HANDLE hParent = nullptr;
|
||||
if (parentPid)
|
||||
hParent = OpenProcess(SYNCHRONIZE, FALSE, parentPid);
|
||||
|
||||
OutputDebugString(L"[LightSwitchService] Worker thread starting...\n");
|
||||
|
||||
// Initialize settings system
|
||||
LightSwitchSettings::instance().InitFileWatcher();
|
||||
|
||||
// Open the manual override event created by the module interface
|
||||
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
|
||||
|
||||
auto applyTheme = [](int nowMinutes, int lightMinutes, int darkMinutes, const auto& settings) {
|
||||
bool isLightActive = false;
|
||||
|
||||
if (lightMinutes < darkMinutes)
|
||||
{
|
||||
// Normal case: sunrise < sunset
|
||||
isLightActive = (nowMinutes >= lightMinutes && nowMinutes < darkMinutes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wraparound case: e.g. light at 21:00, dark at 06:00
|
||||
isLightActive = (nowMinutes >= lightMinutes || nowMinutes < darkMinutes);
|
||||
}
|
||||
|
||||
bool isSystemCurrentlyLight = GetCurrentSystemTheme();
|
||||
bool isAppsCurrentlyLight = GetCurrentAppsTheme();
|
||||
|
||||
if (isLightActive)
|
||||
{
|
||||
if (settings.changeSystem && !isSystemCurrentlyLight)
|
||||
SetSystemTheme(true);
|
||||
if (settings.changeApps && !isAppsCurrentlyLight)
|
||||
SetAppsTheme(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (settings.changeSystem && isSystemCurrentlyLight)
|
||||
SetSystemTheme(false);
|
||||
if (settings.changeApps && isAppsCurrentlyLight)
|
||||
SetAppsTheme(false);
|
||||
}
|
||||
};
|
||||
|
||||
// --- At service start: immediately honor the schedule ---
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
applyTheme(nowMinutes, settings.lightTime + settings.sunrise_offset, settings.darkTime + settings.sunset_offset, settings);
|
||||
}
|
||||
|
||||
// --- Main loop: wakes once per minute or stop/parent death ---
|
||||
for (;;)
|
||||
{
|
||||
HANDLE waits[2] = { g_ServiceStopEvent, hParent };
|
||||
DWORD count = hParent ? 2 : 1;
|
||||
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
int nowMinutes = st.wHour * 60 + st.wMinute;
|
||||
|
||||
LightSwitchSettings::instance().LoadSettings();
|
||||
const auto& settings = LightSwitchSettings::instance().settings();
|
||||
|
||||
// Refresh suntimes at day boundary
|
||||
if (g_lastUpdatedDay != st.wDay)
|
||||
{
|
||||
update_sun_times(settings);
|
||||
g_lastUpdatedDay = st.wDay;
|
||||
|
||||
OutputDebugString(L"[LightSwitchService] Recalculated sun times at new day boundary.\n");
|
||||
}
|
||||
|
||||
wchar_t msg[160];
|
||||
swprintf_s(msg,
|
||||
L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d\n",
|
||||
st.wHour,
|
||||
st.wMinute,
|
||||
settings.lightTime / 60,
|
||||
settings.lightTime % 60,
|
||||
settings.darkTime / 60,
|
||||
settings.darkTime % 60);
|
||||
OutputDebugString(msg);
|
||||
|
||||
// --- Manual override check ---
|
||||
bool manualOverrideActive = false;
|
||||
if (hManualOverride)
|
||||
{
|
||||
manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
if (manualOverrideActive)
|
||||
{
|
||||
// Did we hit a scheduled boundary? (reset override at boundary)
|
||||
if (nowMinutes == (settings.lightTime + settings.sunrise_offset) % 1440 ||
|
||||
nowMinutes == (settings.darkTime + settings.sunset_offset) % 1440)
|
||||
{
|
||||
ResetEvent(hManualOverride);
|
||||
OutputDebugString(L"[LightSwitchService] Manual override cleared at boundary\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugString(L"[LightSwitchService] Skipping schedule due to manual override\n");
|
||||
goto sleep_until_next_minute;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply theme logic (only runs if no manual override or override just cleared)
|
||||
applyTheme(nowMinutes, settings.lightTime + settings.sunrise_offset, settings.darkTime + settings.sunset_offset, settings);
|
||||
|
||||
sleep_until_next_minute:
|
||||
GetLocalTime(&st);
|
||||
int msToNextMinute = (60 - st.wSecond) * 1000 - st.wMilliseconds;
|
||||
if (msToNextMinute < 50)
|
||||
msToNextMinute = 50;
|
||||
|
||||
DWORD wait = WaitForMultipleObjects(count, waits, FALSE, msToNextMinute);
|
||||
if (wait == WAIT_OBJECT_0) // stop event
|
||||
break;
|
||||
if (hParent && wait == WAIT_OBJECT_0 + 1) // parent exited
|
||||
break;
|
||||
}
|
||||
|
||||
if (hManualOverride)
|
||||
CloseHandle(hManualOverride);
|
||||
if (hParent)
|
||||
CloseHandle(hParent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
{
|
||||
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||
{
|
||||
wchar_t msg[160];
|
||||
swprintf_s(
|
||||
msg,
|
||||
L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.\n");
|
||||
OutputDebugString(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
int rc = _tmain(argc, argv); // reuse your existing logic
|
||||
LocalFree(argv);
|
||||
return rc;
|
||||
}
|
||||
BIN
src/modules/LightSwitch/LightSwitchService/LightSwitchService.rc
Normal file
@@ -0,0 +1,219 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{08e71c67-6a7e-4ca1-b04e-2fb336410bac}</ProjectGuid>
|
||||
<RootNamespace>LightSwitchService</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>LightSwitchService</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
|
||||
<TargetName>PowerToys.LightSwitchService</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>
|
||||
./../;
|
||||
..\..\..\common\Telemetry;
|
||||
..\..\..\common;
|
||||
..\..\..\;
|
||||
..\..\..\..\deps\spdlog\include;
|
||||
./;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>Advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\notifications\notifications.vcxproj">
|
||||
<Project>{1d5be09d-78c0-4fd7-af00-ae7c1af7c525}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
|
||||
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="LightSwitchService.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="LightSwitchSettings.cpp" />
|
||||
<ClCompile Include="SettingsConstants.cpp" />
|
||||
<ClCompile Include="ThemeHelper.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeScheduler.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WinHookEventIDs.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="LightSwitchService.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="LightSwitchSettings.h" />
|
||||
<ClInclude Include="SettingsConstants.h" />
|
||||
<ClInclude Include="SettingsObserver.h" />
|
||||
<ClInclude Include="ThemeHelper.h" />
|
||||
<ClInclude Include="ThemeScheduler.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WinHookEventIDs.h" />
|
||||
</ItemGroup>
|
||||
<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">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="LightSwitchService.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeScheduler.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThemeHelper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\settings_helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\settings_objects.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\common\SettingsAPI\FileWatcher.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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ThemeScheduler.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThemeHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LightSwitchSettings.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsConstants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SettingsObserver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WinHookEventIDs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "LightSwitchSettings.h"
|
||||
#include <common/utils/json.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include "SettingsObserver.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <WinHookEventIDs.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
LightSwitchSettings& LightSwitchSettings::instance()
|
||||
{
|
||||
static LightSwitchSettings inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
LightSwitchSettings::LightSwitchSettings()
|
||||
{
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
std::wstring LightSwitchSettings::GetSettingsFileName()
|
||||
{
|
||||
return PTSettingsHelper::get_module_save_file_location(L"LightSwitch");
|
||||
}
|
||||
|
||||
void LightSwitchSettings::InitFileWatcher()
|
||||
{
|
||||
const std::wstring& settingsFileName = GetSettingsFileName();
|
||||
m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
|
||||
PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
|
||||
});
|
||||
}
|
||||
|
||||
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
|
||||
{
|
||||
m_observers.insert(&observer);
|
||||
}
|
||||
|
||||
void LightSwitchSettings::RemoveObserver(SettingsObserver& observer)
|
||||
{
|
||||
m_observers.erase(&observer);
|
||||
}
|
||||
|
||||
void LightSwitchSettings::NotifyObservers(SettingId id) const
|
||||
{
|
||||
for (auto observer : m_observers)
|
||||
{
|
||||
if (observer->WantsToBeNotified(id))
|
||||
{
|
||||
observer->SettingsUpdate(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightSwitchSettings::LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues values =
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
|
||||
|
||||
|
||||
if (const auto jsonVal = values.get_string_value(L"scheduleMode"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
auto newMode = FromString(val);
|
||||
if (m_settings.scheduleMode != newMode)
|
||||
{
|
||||
m_settings.scheduleMode = newMode;
|
||||
NotifyObservers(SettingId::ScheduleMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Latitude
|
||||
if (const auto jsonVal = values.get_string_value(L"latitude"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.latitude != val)
|
||||
{
|
||||
m_settings.latitude = val;
|
||||
NotifyObservers(SettingId::Latitude);
|
||||
}
|
||||
}
|
||||
|
||||
// Longitude
|
||||
if (const auto jsonVal = values.get_string_value(L"longitude"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.longitude != val)
|
||||
{
|
||||
m_settings.longitude = val;
|
||||
NotifyObservers(SettingId::Longitude);
|
||||
}
|
||||
}
|
||||
|
||||
// LightTime
|
||||
if (const auto jsonVal = values.get_int_value(L"lightTime"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.lightTime != val)
|
||||
{
|
||||
m_settings.lightTime = val;
|
||||
NotifyObservers(SettingId::LightTime);
|
||||
}
|
||||
}
|
||||
|
||||
// DarkTime
|
||||
if (const auto jsonVal = values.get_int_value(L"darkTime"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.darkTime != val)
|
||||
{
|
||||
m_settings.darkTime = val;
|
||||
NotifyObservers(SettingId::DarkTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Offset
|
||||
if (const auto jsonVal = values.get_int_value(L"sunrise_offset"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.sunrise_offset != val)
|
||||
{
|
||||
m_settings.sunrise_offset = val;
|
||||
NotifyObservers(SettingId::Sunrise_Offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto jsonVal = values.get_int_value(L"sunset_offset"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.sunset_offset != val)
|
||||
{
|
||||
m_settings.sunset_offset = val;
|
||||
NotifyObservers(SettingId::Sunset_Offset);
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeSystem
|
||||
if (const auto jsonVal = values.get_bool_value(L"changeSystem"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.changeSystem != val)
|
||||
{
|
||||
m_settings.changeSystem = val;
|
||||
NotifyObservers(SettingId::ChangeSystem);
|
||||
}
|
||||
}
|
||||
|
||||
// ChangeApps
|
||||
if (const auto jsonVal = values.get_bool_value(L"changeApps"))
|
||||
{
|
||||
auto val = *jsonVal;
|
||||
if (m_settings.changeApps != val)
|
||||
{
|
||||
m_settings.changeApps = val;
|
||||
NotifyObservers(SettingId::ChangeApps);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keeps defaults if load fails
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <windows.h>
|
||||
|
||||
#include <common/SettingsAPI/FileWatcher.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <SettingsConstants.h>
|
||||
|
||||
class SettingsObserver;
|
||||
|
||||
enum class ScheduleMode
|
||||
{
|
||||
FixedHours,
|
||||
SunsetToSunrise
|
||||
// Add more in the future
|
||||
};
|
||||
|
||||
inline std::wstring ToString(ScheduleMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ScheduleMode::FixedHours:
|
||||
return L"FixedHours";
|
||||
case ScheduleMode::SunsetToSunrise:
|
||||
return L"SunsetToSunrise";
|
||||
default:
|
||||
return L"FixedHours";
|
||||
}
|
||||
}
|
||||
|
||||
inline ScheduleMode FromString(const std::wstring& str)
|
||||
{
|
||||
if (str == L"SunsetToSunrise")
|
||||
return ScheduleMode::SunsetToSunrise;
|
||||
else
|
||||
return ScheduleMode::FixedHours;
|
||||
}
|
||||
|
||||
struct LightSwitchConfig
|
||||
{
|
||||
ScheduleMode scheduleMode = ScheduleMode::FixedHours;
|
||||
|
||||
std::wstring latitude = L"0.0";
|
||||
std::wstring longitude = L"0.0";
|
||||
|
||||
// Stored as minutes since midnight
|
||||
int lightTime = 8 * 60; // 08:00 default
|
||||
int darkTime = 20 * 60; // 20:00 default
|
||||
|
||||
int sunrise_offset = 0;
|
||||
int sunset_offset = 0;
|
||||
|
||||
bool changeSystem = false;
|
||||
bool changeApps = false;
|
||||
};
|
||||
|
||||
class LightSwitchSettings
|
||||
{
|
||||
public:
|
||||
static LightSwitchSettings& instance();
|
||||
|
||||
static inline const LightSwitchConfig& settings()
|
||||
{
|
||||
return instance().m_settings;
|
||||
}
|
||||
|
||||
void InitFileWatcher();
|
||||
static std::wstring GetSettingsFileName();
|
||||
|
||||
void AddObserver(SettingsObserver& observer);
|
||||
void RemoveObserver(SettingsObserver& observer);
|
||||
|
||||
void LoadSettings();
|
||||
|
||||
private:
|
||||
LightSwitchSettings();
|
||||
~LightSwitchSettings() = default;
|
||||
|
||||
LightSwitchConfig m_settings;
|
||||
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
|
||||
std::unordered_set<SettingsObserver*> m_observers;
|
||||
|
||||
void NotifyObservers(SettingId id) const;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
#include "SettingsConstants.h"
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
enum class SettingId
|
||||
{
|
||||
ScheduleMode = 0,
|
||||
Latitude,
|
||||
Longitude,
|
||||
LightTime,
|
||||
DarkTime,
|
||||
Sunrise_Offset,
|
||||
Sunset_Offset,
|
||||
ChangeSystem,
|
||||
ChangeApps
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include "SettingsConstants.h"
|
||||
|
||||
class LightSwitchSettings;
|
||||
|
||||
class SettingsObserver
|
||||
{
|
||||
public:
|
||||
SettingsObserver(std::unordered_set<SettingId> observedSettings) :
|
||||
m_observedSettings(std::move(observedSettings))
|
||||
{
|
||||
LightSwitchSettings::instance().AddObserver(*this);
|
||||
}
|
||||
|
||||
virtual ~SettingsObserver()
|
||||
{
|
||||
LightSwitchSettings::instance().RemoveObserver(*this);
|
||||
}
|
||||
|
||||
// Override this in your class to respond to updates
|
||||
virtual void SettingsUpdate(SettingId type) {}
|
||||
|
||||
bool WantsToBeNotified(SettingId type) const noexcept
|
||||
{
|
||||
return m_observedSettings.contains(type);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_set<SettingId> m_observedSettings;
|
||||
};
|
||||
81
src/modules/LightSwitch/LightSwitchService/ThemeHelper.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <windows.h>
|
||||
#include "ThemeHelper.h"
|
||||
|
||||
// Controls changing the themes.
|
||||
|
||||
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);
|
||||
|
||||
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,
|
||||
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
|
||||
}
|
||||
5
src/modules/LightSwitch/LightSwitchService/ThemeHelper.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
void SetSystemTheme(bool dark);
|
||||
void SetAppsTheme(bool dark);
|
||||
bool GetCurrentSystemTheme();
|
||||
bool GetCurrentAppsTheme();
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "ThemeScheduler.h"
|
||||
#include <utility>
|
||||
|
||||
SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day)
|
||||
{
|
||||
double zenith = 90.833;
|
||||
int N1 = static_cast<int>(floor(275.0 * month / 9.0));
|
||||
int N2 = static_cast<int>(floor((static_cast<double>(month) + 9) / 12.0));
|
||||
int N3 = static_cast<int>(floor((1.0 + floor((year - 4.0 * floor(year / 4.0) + 2.0) / 3.0))));
|
||||
int N = N1 - (N2 * N3) + day - 30;
|
||||
|
||||
auto calcTime = [&](bool sunrise) -> double {
|
||||
double lngHour = longitude / 15.0;
|
||||
double t = sunrise ? N + ((6 - lngHour) / 24) : N + ((18 - lngHour) / 24);
|
||||
|
||||
double M = (0.9856 * t) - 3.289;
|
||||
double L = M + (1.916 * sin(deg2rad(M))) + (0.020 * sin(2 * deg2rad(M))) + 282.634;
|
||||
if (L < 0)
|
||||
L += 360;
|
||||
if (L > 360)
|
||||
L -= 360;
|
||||
|
||||
double RA = rad2deg(atan(0.91764 * tan(deg2rad(L))));
|
||||
if (RA < 0)
|
||||
RA += 360;
|
||||
if (RA > 360)
|
||||
RA -= 360;
|
||||
|
||||
double Lquadrant = floor(L / 90) * 90;
|
||||
double RAquadrant = floor(RA / 90) * 90;
|
||||
RA = RA + (Lquadrant - RAquadrant);
|
||||
RA /= 15;
|
||||
|
||||
double sinDec = 0.39782 * sin(deg2rad(L));
|
||||
double cosDec = cos(asin(sinDec));
|
||||
|
||||
double cosH = (cos(deg2rad(zenith)) - (sinDec * sin(deg2rad(latitude)))) / (cosDec * cos(deg2rad(latitude)));
|
||||
if (cosH > 1 || cosH < -1)
|
||||
return -1;
|
||||
|
||||
double H = sunrise ? 360 - rad2deg(acos(cosH)) : rad2deg(acos(cosH));
|
||||
H /= 15;
|
||||
|
||||
double T = H + RA - (0.06571 * t) - 6.622;
|
||||
double UT = T - lngHour;
|
||||
while (UT < 0)
|
||||
UT += 24;
|
||||
while (UT >= 24)
|
||||
UT -= 24;
|
||||
|
||||
return UT;
|
||||
};
|
||||
|
||||
double riseUT = calcTime(true);
|
||||
double setUT = calcTime(false);
|
||||
|
||||
auto toLocal = [](double UT) {
|
||||
TIME_ZONE_INFORMATION tz;
|
||||
DWORD state = GetTimeZoneInformation(&tz);
|
||||
double totalBias = tz.Bias;
|
||||
|
||||
if (state == TIME_ZONE_ID_DAYLIGHT)
|
||||
totalBias += tz.DaylightBias;
|
||||
else if (state == TIME_ZONE_ID_STANDARD)
|
||||
totalBias += tz.StandardBias;
|
||||
|
||||
double biasHours = -(totalBias / 60.0);
|
||||
double localTime = UT + biasHours;
|
||||
|
||||
while (localTime < 0)
|
||||
localTime += 24;
|
||||
while (localTime >= 24)
|
||||
localTime -= 24;
|
||||
|
||||
int hour = static_cast<int>(localTime);
|
||||
int minute = static_cast<int>((localTime - hour) * 60);
|
||||
return std::pair<int, int>{ hour, minute };
|
||||
};
|
||||
|
||||
auto [riseHour, riseMinute] = toLocal(riseUT);
|
||||
auto [setHour, setMinute] = toLocal(setUT);
|
||||
|
||||
SunTimes result;
|
||||
result.sunriseHour = riseHour;
|
||||
result.sunriseMinute = riseMinute;
|
||||
result.sunsetHour = setHour;
|
||||
result.sunsetMinute = setMinute;
|
||||
return result;
|
||||
}
|
||||
25
src/modules/LightSwitch/LightSwitchService/ThemeScheduler.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <windows.h>
|
||||
|
||||
// Struct to hold calculated sunrise/sunset times
|
||||
struct SunTimes
|
||||
{
|
||||
int sunriseHour;
|
||||
int sunriseMinute;
|
||||
int sunsetHour;
|
||||
int sunsetMinute;
|
||||
};
|
||||
|
||||
constexpr double PI = 3.14159265358979323846;
|
||||
constexpr double deg2rad(double deg)
|
||||
{
|
||||
return deg * PI / 180.0;
|
||||
}
|
||||
constexpr double rad2deg(double rad)
|
||||
{
|
||||
return rad * 180.0 / PI;
|
||||
}
|
||||
|
||||
SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day);
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
#include "WinHookEventIDs.h"
|
||||
#include <wtypes.h>
|
||||
#include <mutex>
|
||||
|
||||
UINT WM_PRIV_SETTINGS_CHANGED = 0;
|
||||
|
||||
std::once_flag init_flag;
|
||||
|
||||
void InitializeWinhookEventIds()
|
||||
{
|
||||
std::call_once(init_flag, [&] {
|
||||
WM_PRIV_SETTINGS_CHANGED = RegisterWindowMessage(L"{11978F7B-221A-4E65-B9A9-693F7D6E4B25}");
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
extern UINT WM_PRIV_SETTINGS_CHANGED; // Scheduled when a watched settings file is updated
|
||||
|
||||
void InitializeWinhookEventIds();
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
</packages>
|
||||
16
src/modules/LightSwitch/LightSwitchService/resource.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by LightSwitchService.rc
|
||||
//
|
||||
#define IDI_ICON1 101
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 102
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>PowerToys.LightSwitch.UITests</RootNamespace>
|
||||
<AssemblyName>LightSwitch.UITests</AssemblyName>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\LightSwitch.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="ad22010c-e33e-4459-848e-a1ed976bfd3b"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ad22010c-e33e-4459-848e-a1ed976bfd3b" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>LightSwitch.UITests</DisplayName>
|
||||
<PublisherDisplayName>Microsoft</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="LightSwitch.UITests"
|
||||
Description="LightSwitch.UITests"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace LightSwitch.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestGeolocation : UITestBase
|
||||
{
|
||||
public TestGeolocation()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("LightSwitch.Geolocation")]
|
||||
[TestCategory("Location")]
|
||||
public void TestGeolocationUpdate()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "geolocation test");
|
||||
TestHelper.PerformGeolocationTest(this);
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
439
src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
Normal file
@@ -0,0 +1,439 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace LightSwitch.UITests
|
||||
{
|
||||
internal sealed class TestHelper
|
||||
{
|
||||
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };
|
||||
|
||||
/// <summary>
|
||||
/// Performs common test initialization: navigate to settings, enable toggle, verify shortcut
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
/// <param name="testName">Name of the test for assertions</param>
|
||||
/// <returns>The activation keys for the test</returns>
|
||||
public static Key[] InitializeTest(UITestBase testBase, string testName)
|
||||
{
|
||||
LaunchFromSetting(testBase);
|
||||
|
||||
var toggleSwitch = SetLightSwitchToggle(testBase, enable: true);
|
||||
Assert.IsTrue(
|
||||
toggleSwitch.IsOn,
|
||||
$"Light Switch toggle switch should be ON for {testName}");
|
||||
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
Assert.IsNotNull(activationKeys, "Should be able to read activation shortcut");
|
||||
Assert.IsTrue(activationKeys.Length > 0, "Activation shortcut should contain at least one key");
|
||||
|
||||
return activationKeys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the Light Switch settings page
|
||||
/// </summary>
|
||||
public static void LaunchFromSetting(UITestBase testBase)
|
||||
{
|
||||
var lightSwitch = testBase.Session.FindAll<NavigationViewItem>(By.AccessibilityId("LightSwitchNavItem"));
|
||||
|
||||
if (lightSwitch.Count == 0)
|
||||
{
|
||||
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("SystemToolsNavItem"), 5000).Click(msPostAction: 500);
|
||||
}
|
||||
|
||||
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("LightSwitchNavItem"), 5000).Click(msPostAction: 500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Light Switch enable toggle switch to the specified state
|
||||
/// </summary>
|
||||
public static ToggleSwitch SetLightSwitchToggle(UITestBase testBase, bool enable)
|
||||
{
|
||||
var toggleSwitch = testBase.Session.Find<ToggleSwitch>(By.AccessibilityId("Toggle_LightSwitch"), 5000);
|
||||
|
||||
if (toggleSwitch.IsOn != enable)
|
||||
{
|
||||
toggleSwitch.Click(msPreAction: 1000, msPostAction: 2000);
|
||||
}
|
||||
|
||||
if (toggleSwitch.IsOn != enable)
|
||||
{
|
||||
testBase.Session.SendKey(Key.Space, msPreAction: 0, msPostAction: 2000);
|
||||
}
|
||||
|
||||
return toggleSwitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the current activation shortcut from the ShortcutControl
|
||||
/// </summary>
|
||||
public static Key[] ReadActivationShortcut(UITestBase testBase)
|
||||
{
|
||||
var shortcutCard = testBase.Session.Find<Element>(By.AccessibilityId("Shortcut_LightSwitch"), 5000);
|
||||
var shortcutButton = shortcutCard.Find<Element>(By.AccessibilityId("EditButton"), 5000);
|
||||
return ParseShortcutText(shortcutButton.HelpText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse shortcut text like "Win + Ctrl + Shift + M" into Key array
|
||||
/// </summary>
|
||||
private static Key[] ParseShortcutText(string shortcutText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutText))
|
||||
{
|
||||
return new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.D };
|
||||
}
|
||||
|
||||
var keys = new List<Key>();
|
||||
var parts = shortcutText.Split(ShortcutSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var cleanPart = part.Trim().ToLowerInvariant();
|
||||
var key = cleanPart switch
|
||||
{
|
||||
"win" or "windows" => Key.Win,
|
||||
"ctrl" or "control" => Key.Ctrl,
|
||||
"shift" => Key.Shift,
|
||||
"alt" => Key.Alt,
|
||||
_ when cleanPart.Length == 1 && char.IsLetter(cleanPart[0]) &&
|
||||
cleanPart[0] >= 'a' && cleanPart[0] <= 'z' =>
|
||||
(Key)Enum.Parse(typeof(Key), cleanPart.ToUpperInvariant()),
|
||||
_ => (Key?)null,
|
||||
};
|
||||
|
||||
if (key.HasValue)
|
||||
{
|
||||
keys.Add(key.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return keys.Count > 0 ? keys.ToArray() : new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.D };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs common test cleanup: close LightSwitch task
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
public static void CleanupTest(UITestBase testBase)
|
||||
{
|
||||
// TODO: Make sure the task kills?
|
||||
// CloseLightSwitch(testBase);
|
||||
|
||||
// Ensure we're attached to settings after cleanup
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors - this is just cleanup
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update time test operation
|
||||
/// </summary>
|
||||
public static void PerformUpdateTimeTest(UITestBase testBase)
|
||||
{
|
||||
// Make sure in manual mode
|
||||
var modeCombobox = testBase.Session.Find<Element>(By.AccessibilityId("ModeSelection_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(modeCombobox, "Mode combobox not found.");
|
||||
|
||||
var neededTabs = 6;
|
||||
|
||||
if (modeCombobox.Text != "Manual")
|
||||
{
|
||||
modeCombobox.Click();
|
||||
var manualListItem = testBase.Session.Find<Element>(By.AccessibilityId("ManualCBItem_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(manualListItem, "Manual combobox item not found.");
|
||||
manualListItem.Click();
|
||||
neededTabs = 1;
|
||||
}
|
||||
|
||||
Assert.AreEqual("Manual", modeCombobox.Text, "Mode combobox should be set to Manual.");
|
||||
|
||||
var timeline = testBase.Session.Find<Element>(By.AccessibilityId("Timeline_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(timeline, "Timeline not found.");
|
||||
|
||||
var helpText = timeline.GetAttribute("HelpText");
|
||||
string originalEndValue = GetHelpTextValue(helpText, "End");
|
||||
|
||||
for (int i = 0; i < neededTabs; i++)
|
||||
{
|
||||
testBase.Session.SendKeys(Key.Tab);
|
||||
}
|
||||
|
||||
testBase.Session.SendKeys(Key.Enter);
|
||||
testBase.Session.SendKeys(Key.Up);
|
||||
testBase.Session.SendKeys(Key.Enter);
|
||||
|
||||
helpText = timeline.GetAttribute("HelpText");
|
||||
string updatedEndValue = GetHelpTextValue(helpText, "End");
|
||||
|
||||
Assert.AreNotEqual(originalEndValue, updatedEndValue, "Timeline end time should have been updated.");
|
||||
|
||||
helpText = timeline.GetAttribute("HelpText");
|
||||
string originalStartValue = GetHelpTextValue(helpText, "Start");
|
||||
|
||||
testBase.Session.SendKeys(Key.Tab);
|
||||
testBase.Session.SendKeys(Key.Enter);
|
||||
testBase.Session.SendKeys(Key.Up);
|
||||
testBase.Session.SendKeys(Key.Enter);
|
||||
|
||||
helpText = timeline.GetAttribute("HelpText");
|
||||
string updatedStartValue = GetHelpTextValue(helpText, "Start");
|
||||
|
||||
Assert.AreNotEqual(originalStartValue, updatedStartValue, "Timeline start time should have been updated.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update geolocation test operation
|
||||
/// </summary>
|
||||
public static void PerformUserSelectedLocationTest(UITestBase testBase)
|
||||
{
|
||||
// Make sure in sun time mode
|
||||
var modeCombobox = testBase.Session.Find<Element>(By.AccessibilityId("ModeSelection_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(modeCombobox, "Mode combobox not found.");
|
||||
|
||||
if (modeCombobox.Text != "Sunset to sunrise")
|
||||
{
|
||||
modeCombobox.Click();
|
||||
var sunriseListItem = testBase.Session.Find<Element>(By.AccessibilityId("SunCBItem_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(sunriseListItem, "Sunrise combobox item not found.");
|
||||
sunriseListItem.Click();
|
||||
}
|
||||
|
||||
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
|
||||
|
||||
var setLocationButton = testBase.Session.Find<Element>(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(setLocationButton, "Set location button not found.");
|
||||
setLocationButton.Click();
|
||||
|
||||
var autoSuggestTextbox = testBase.Session.Find<Element>(By.AccessibilityId("CitySearchBox_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(autoSuggestTextbox, "City search box not found.");
|
||||
autoSuggestTextbox.Click();
|
||||
autoSuggestTextbox.SendKeys("Seattle");
|
||||
autoSuggestTextbox.SendKeys(OpenQA.Selenium.Keys.Down);
|
||||
autoSuggestTextbox.SendKeys(OpenQA.Selenium.Keys.Enter);
|
||||
|
||||
var latLong = testBase.Session.Find<Element>(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
|
||||
|
||||
var sunrise = testBase.Session.Find<Element>(By.AccessibilityId("SunriseText_LightSwitch"), 5000);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(sunrise.Text));
|
||||
|
||||
var sunset = testBase.Session.Find<Element>(By.AccessibilityId("SunsetText_LightSwitch"), 5000);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(sunset.Text));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update geolocation test operation
|
||||
/// </summary>
|
||||
public static void PerformGeolocationTest(UITestBase testBase)
|
||||
{
|
||||
// Make sure in sun time mode
|
||||
var modeCombobox = testBase.Session.Find<Element>(By.AccessibilityId("ModeSelection_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(modeCombobox, "Mode combobox not found.");
|
||||
|
||||
if (modeCombobox.Text != "Sunset to sunrise")
|
||||
{
|
||||
modeCombobox.Click();
|
||||
var sunriseListItem = testBase.Session.Find<Element>(By.AccessibilityId("SunCBItem_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(sunriseListItem, "Sunrise combobox item not found.");
|
||||
sunriseListItem.Click();
|
||||
}
|
||||
|
||||
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
|
||||
|
||||
// Click the select city button
|
||||
var setLocationButton = testBase.Session.Find<Element>(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(setLocationButton, "Set location button not found.");
|
||||
setLocationButton.Click(msPostAction: 8000);
|
||||
|
||||
var latLong = testBase.Session.Find<Element>(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
|
||||
|
||||
var sunrise = testBase.Session.Find<Element>(By.AccessibilityId("SunriseText_LightSwitch"), 5000);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(sunrise.Text));
|
||||
|
||||
var sunset = testBase.Session.Find<Element>(By.AccessibilityId("SunsetText_LightSwitch"), 5000);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(sunset.Text));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a update time test operation
|
||||
/// </summary>
|
||||
public static void PerformOffsetTest(UITestBase testBase)
|
||||
{
|
||||
// Make sure in sun time mode
|
||||
var modeCombobox = testBase.Session.Find<Element>(By.AccessibilityId("ModeSelection_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(modeCombobox, "Mode combobox not found.");
|
||||
|
||||
if (modeCombobox.Text != "Sunset to sunrise")
|
||||
{
|
||||
modeCombobox.Click();
|
||||
var sunriseListItem = testBase.Session.Find<Element>(By.AccessibilityId("SunCBItem_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(sunriseListItem, "Sunrise combobox item not found.");
|
||||
sunriseListItem.Click();
|
||||
}
|
||||
|
||||
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
|
||||
|
||||
// Testing sunrise offset
|
||||
var sunriseOffset = testBase.Session.Find<Element>(By.AccessibilityId("SunriseOffset_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(sunriseOffset, "Sunrise offset number box not found.");
|
||||
|
||||
var timeline = testBase.Session.Find<Element>(By.AccessibilityId("Timeline_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(timeline, "Timeline not found.");
|
||||
|
||||
var helpText = timeline.GetAttribute("HelpText");
|
||||
string originalStartValue = GetHelpTextValue(helpText, "Start");
|
||||
|
||||
sunriseOffset.Click();
|
||||
testBase.Session.SendKeys(Key.Up);
|
||||
|
||||
helpText = timeline.GetAttribute("HelpText");
|
||||
string updatedStartValue = GetHelpTextValue(helpText, "Start");
|
||||
|
||||
Assert.AreNotEqual(originalStartValue, updatedStartValue, "Timeline start time should have been updated.");
|
||||
|
||||
// Testing sunset offset
|
||||
var sunsetOffset = testBase.Session.Find<Element>(By.AccessibilityId("SunsetOffset_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(sunsetOffset, "Sunrise offset number box not found.");
|
||||
|
||||
helpText = timeline.GetAttribute("HelpText");
|
||||
string originalEndValue = GetHelpTextValue(helpText, "End");
|
||||
|
||||
sunsetOffset.Click();
|
||||
testBase.Session.SendKeys(Key.Up);
|
||||
|
||||
helpText = timeline.GetAttribute("HelpText");
|
||||
string updatedEndValue = GetHelpTextValue(helpText, "End");
|
||||
|
||||
Assert.AreNotEqual(originalEndValue, updatedEndValue, "Timeline end time should have been updated.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a test for shortcut changing themes
|
||||
/// </summary>
|
||||
public static void PerformShortcutTest(UITestBase testBase, Key[] activationKeys)
|
||||
{
|
||||
// Test when both are checked
|
||||
var systemCheckbox = testBase.Session.Find<Element>(By.AccessibilityId("ChangeSystemCheckbox_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(systemCheckbox, "System checkbox not found.");
|
||||
|
||||
var scrollViewer = testBase.Session.Find<Element>(By.AccessibilityId("PageScrollViewer"));
|
||||
systemCheckbox.EnsureVisible(scrollViewer);
|
||||
|
||||
int neededTabs = 10;
|
||||
|
||||
if (!systemCheckbox.Selected)
|
||||
{
|
||||
for (int i = 0; i < neededTabs; i++)
|
||||
{
|
||||
testBase.Session.SendKeys(Key.Tab);
|
||||
}
|
||||
|
||||
systemCheckbox.Click();
|
||||
}
|
||||
|
||||
Assert.IsTrue(systemCheckbox.Selected, "System checkbox should be checked.");
|
||||
|
||||
var appsCheckbox = testBase.Session.Find<Element>(By.AccessibilityId("ChangeAppsCheckbox_LightSwitch"), 5000);
|
||||
Assert.IsNotNull(appsCheckbox, "Apps checkbox not found.");
|
||||
|
||||
if (!appsCheckbox.Selected)
|
||||
{
|
||||
appsCheckbox.Click();
|
||||
}
|
||||
|
||||
Assert.IsTrue(appsCheckbox.Selected, "Apps checkbox should be checked.");
|
||||
|
||||
var systemBeforeValue = GetSystemTheme();
|
||||
var appsBeforeValue = GetAppsTheme();
|
||||
|
||||
testBase.Session.SendKeys(activationKeys);
|
||||
Task.Delay(5000).Wait();
|
||||
|
||||
var systemAfterValue = GetSystemTheme();
|
||||
var appsAfterValue = GetAppsTheme();
|
||||
|
||||
Assert.AreNotEqual(systemBeforeValue, systemAfterValue, "System theme should have changed.");
|
||||
Assert.AreNotEqual(appsBeforeValue, appsAfterValue, "Apps theme should have changed.");
|
||||
|
||||
// Test with nothing checked
|
||||
if (systemCheckbox.Selected)
|
||||
{
|
||||
systemCheckbox.Click();
|
||||
}
|
||||
|
||||
if (appsCheckbox.Selected)
|
||||
{
|
||||
appsCheckbox.Click();
|
||||
}
|
||||
|
||||
Assert.IsFalse(systemCheckbox.Selected, "System checkbox should be unchecked.");
|
||||
Assert.IsFalse(appsCheckbox.Selected, "Apps checkbox should be unchecked.");
|
||||
|
||||
var noneSystemBeforeValue = GetSystemTheme();
|
||||
var noneAppsBeforeValue = GetAppsTheme();
|
||||
|
||||
testBase.Session.SendKeys(activationKeys);
|
||||
Task.Delay(5000).Wait();
|
||||
|
||||
var noneSystemAfterValue = GetSystemTheme();
|
||||
var noneAppsAfterValue = GetAppsTheme();
|
||||
|
||||
Assert.AreEqual(noneSystemBeforeValue, noneSystemAfterValue, "System theme should not have changed.");
|
||||
Assert.AreEqual(noneAppsBeforeValue, noneAppsAfterValue, "Apps theme should not have changed.");
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
private static int GetSystemTheme()
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (key is null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int)key.GetValue("SystemUsesLightTheme", 1);
|
||||
}
|
||||
|
||||
private static int GetAppsTheme()
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (key is null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int)key.GetValue("AppsUseLightTheme", 1);
|
||||
}
|
||||
|
||||
private static string GetHelpTextValue(string helpText, string key)
|
||||
{
|
||||
foreach (var part in helpText.Split(';'))
|
||||
{
|
||||
var kv = part.Split('=');
|
||||
if (kv.Length == 2 && kv[0] == key)
|
||||
{
|
||||
return kv[1];
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace LightSwitch.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestOffset : UITestBase
|
||||
{
|
||||
public TestOffset()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("LightSwitch.Offset")]
|
||||
[TestCategory("Time")]
|
||||
public void TestTimeOffset()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "offset test");
|
||||
TestHelper.PerformOffsetTest(this);
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace LightSwitch.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestShortcut : UITestBase
|
||||
{
|
||||
public TestShortcut()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("LightSwitch.TestShortcut")]
|
||||
[TestCategory("Shortcut")]
|
||||
public void TestLightSwitchShortcut()
|
||||
{
|
||||
var activationKeys = TestHelper.InitializeTest(this, "light switch shortcut test");
|
||||
TestHelper.PerformShortcutTest(this, activationKeys);
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace LightSwitch.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestUpdateManualTime : UITestBase
|
||||
{
|
||||
public TestUpdateManualTime()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("LightSwitch.UpdateManualTime")]
|
||||
[TestCategory("Time")]
|
||||
public void TestUpdateTime()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "update manual time test");
|
||||
TestHelper.PerformUpdateTimeTest(this);
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="LightSwitch.UITests.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -177,6 +177,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"PowerToys.WorkspacesModuleInterface.dll",
|
||||
L"PowerToys.CmdPalModuleInterface.dll",
|
||||
L"PowerToys.ZoomItModuleInterface.dll",
|
||||
L"PowerToys.LightSwitchModuleInterface.dll",
|
||||
};
|
||||
|
||||
for (auto moduleSubdir : knownModules)
|
||||
|
||||
@@ -757,6 +757,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
|
||||
return "ColorPicker";
|
||||
case ESettingsWindowNames::CmdNotFound:
|
||||
return "CmdNotFound";
|
||||
case ESettingsWindowNames::LightSwitch:
|
||||
return "LightSwitch";
|
||||
case ESettingsWindowNames::FancyZones:
|
||||
return "FancyZones";
|
||||
case ESettingsWindowNames::FileLocksmith:
|
||||
@@ -842,6 +844,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
|
||||
{
|
||||
return ESettingsWindowNames::CmdNotFound;
|
||||
}
|
||||
else if (value == "LightSwitch")
|
||||
{
|
||||
return ESettingsWindowNames::LightSwitch;
|
||||
}
|
||||
else if (value == "FancyZones")
|
||||
{
|
||||
return ESettingsWindowNames::FancyZones;
|
||||
|
||||
@@ -10,6 +10,7 @@ enum class ESettingsWindowNames
|
||||
Awake,
|
||||
ColorPicker,
|
||||
CmdNotFound,
|
||||
LightSwitch,
|
||||
FancyZones,
|
||||
FileLocksmith,
|
||||
Run,
|
||||
|
||||
@@ -513,6 +513,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool lightSwitch;
|
||||
|
||||
[JsonPropertyName("LightSwitch")]
|
||||
public bool LightSwitch
|
||||
{
|
||||
get => lightSwitch;
|
||||
set
|
||||
{
|
||||
if (lightSwitch != value)
|
||||
{
|
||||
LogTelemetryEvent(value);
|
||||
lightSwitch = value;
|
||||
NotifyChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyChange()
|
||||
{
|
||||
notifyEnabledChangedAction?.Invoke();
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Settings.UI.Library.Helpers
|
||||
{
|
||||
public class SearchLocation
|
||||
{
|
||||
public string City { get; set; }
|
||||
|
||||
public string Country { get; set; }
|
||||
|
||||
public double Latitude { get; set; }
|
||||
|
||||
public double Longitude { get; set; }
|
||||
|
||||
public SearchLocation(string city, string country, double latitude, double longitude)
|
||||
{
|
||||
City = city;
|
||||
Country = country;
|
||||
Latitude = latitude;
|
||||
Longitude = longitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Settings.UI.Library.Helpers;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
{
|
||||
public static class SearchLocationLoader
|
||||
{
|
||||
private static readonly List<SearchLocation> LocationDataList = new List<SearchLocation>();
|
||||
|
||||
public static IEnumerable<SearchLocation> GetAll()
|
||||
{
|
||||
return LocationDataList
|
||||
.GroupBy(l => $"{l.Country}|{l.City}|{l.Latitude.ToString(CultureInfo.InvariantCulture)}|{l.Longitude.ToString(CultureInfo.InvariantCulture)}")
|
||||
.Select(g => g.First())
|
||||
.OrderBy(l => l.Country, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(l => l.City, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
src/settings-ui/Settings.UI.Library/Helpers/SunCalc.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
|
||||
{
|
||||
public static class SunCalc
|
||||
{
|
||||
public static SunTimes CalculateSunriseSunset(double latitude, double longitude, int year, int month, int day)
|
||||
{
|
||||
double zenith = 90.833; // official sunrise/sunset
|
||||
|
||||
int n1 = (int)Math.Floor(275.0 * month / 9.0);
|
||||
int n2 = (int)Math.Floor((month + 9.0) / 12.0);
|
||||
int n3 = (int)Math.Floor(1.0 + Math.Floor((year - (4.0 * Math.Floor(year / 4.0)) + 2.0) / 3.0));
|
||||
int n = n1 - (n2 * n3) + day - 30;
|
||||
|
||||
double? riseUT = CalcTime(isSunrise: true);
|
||||
double? setUT = CalcTime(isSunrise: false);
|
||||
|
||||
var riseLocal = ToLocal(riseUT, year, month, day);
|
||||
var setLocal = ToLocal(setUT, year, month, day);
|
||||
|
||||
var result = new SunTimes
|
||||
{
|
||||
HasSunrise = riseLocal.HasValue,
|
||||
HasSunset = setLocal.HasValue,
|
||||
SunriseHour = riseLocal?.Hour ?? -1,
|
||||
SunriseMinute = riseLocal?.Minute ?? -1,
|
||||
SunsetHour = setLocal?.Hour ?? -1,
|
||||
SunsetMinute = setLocal?.Minute ?? -1,
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
// Local functions
|
||||
double? CalcTime(bool isSunrise)
|
||||
{
|
||||
double lngHour = longitude / 15.0;
|
||||
double t = isSunrise ? n + ((6 - lngHour) / 24.0) : n + ((18 - lngHour) / 24.0);
|
||||
|
||||
double m1 = (0.9856 * t) - 3.289;
|
||||
double l = m1 + (1.916 * Math.Sin(Deg2Rad(m1))) + (0.020 * Math.Sin(2 * Deg2Rad(m1))) + 282.634;
|
||||
l = NormalizeDegrees(l);
|
||||
|
||||
double rA = Rad2Deg(Math.Atan(0.91764 * Math.Tan(Deg2Rad(l))));
|
||||
rA = NormalizeDegrees(rA);
|
||||
|
||||
double lquadrant = Math.Floor(l / 90.0) * 90.0;
|
||||
double rAquadrant = Math.Floor(rA / 90.0) * 90.0;
|
||||
rA = rA + (lquadrant - rAquadrant);
|
||||
rA /= 15.0;
|
||||
|
||||
double sinDec = 0.39782 * Math.Sin(Deg2Rad(l));
|
||||
double cosDec = Math.Cos(Math.Asin(sinDec));
|
||||
|
||||
double cosH = (Math.Cos(Deg2Rad(zenith)) - (sinDec * Math.Sin(Deg2Rad(latitude))))
|
||||
/ (cosDec * Math.Cos(Deg2Rad(latitude)));
|
||||
|
||||
if (cosH > 1.0 || cosH < -1.0)
|
||||
{
|
||||
// Sun never rises or never sets on this date at this location
|
||||
return null;
|
||||
}
|
||||
|
||||
double h = isSunrise ? 360.0 - Rad2Deg(Math.Acos(cosH)) : Rad2Deg(Math.Acos(cosH));
|
||||
h /= 15.0;
|
||||
|
||||
double t1 = h + rA - (0.06571 * t) - 6.622;
|
||||
double uT = t1 - lngHour;
|
||||
uT = NormalizeHours(uT);
|
||||
|
||||
return uT;
|
||||
}
|
||||
|
||||
static (int Hour, int Minute)? ToLocal(double? ut, int y, int m, int d)
|
||||
{
|
||||
if (!ut.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert fractional hours to hh:mm with proper rounding
|
||||
int hours = (int)Math.Floor(ut.Value);
|
||||
int minutes = (int)((ut.Value - hours) * 60.0);
|
||||
|
||||
// Normalize minute overflow
|
||||
if (minutes == 60)
|
||||
{
|
||||
minutes = 0;
|
||||
hours = (hours + 1) % 24;
|
||||
}
|
||||
|
||||
// Build a UTC DateTime on the given date
|
||||
var utc = new DateTime(y, m, d, hours, minutes, 0, DateTimeKind.Utc);
|
||||
|
||||
// Convert to local time using system time zone rules for that date
|
||||
var local = TimeZoneInfo.ConvertTimeFromUtc(utc, TimeZoneInfo.Local);
|
||||
|
||||
return (local.Hour, local.Minute);
|
||||
}
|
||||
|
||||
static double Deg2Rad(double deg) => deg * Math.PI / 180.0;
|
||||
static double Rad2Deg(double rad) => rad * 180.0 / Math.PI;
|
||||
|
||||
static double NormalizeDegrees(double angle)
|
||||
{
|
||||
angle %= 360.0;
|
||||
if (angle < 0)
|
||||
{
|
||||
angle += 360.0;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
static double NormalizeHours(double hours)
|
||||
{
|
||||
hours %= 24.0;
|
||||
if (hours < 0)
|
||||
{
|
||||
hours += 24.0;
|
||||
}
|
||||
|
||||
return hours;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/settings-ui/Settings.UI.Library/Helpers/SunTimes.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
|
||||
{
|
||||
public struct SunTimes
|
||||
{
|
||||
public int SunriseHour;
|
||||
public int SunriseMinute;
|
||||
public int SunsetHour;
|
||||
public int SunsetMinute;
|
||||
public string Text;
|
||||
|
||||
public bool HasSunrise;
|
||||
public bool HasSunset;
|
||||
}
|
||||
}
|
||||
66
src/settings-ui/Settings.UI.Library/LightSwitchProperties.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class LightSwitchProperties
|
||||
{
|
||||
public const bool DefaultChangeSystem = true;
|
||||
public const bool DefaultChangeApps = true;
|
||||
public const int DefaultLightTime = 480;
|
||||
public const int DefaultDarkTime = 1200;
|
||||
public const int DefaultSunriseOffset = 0;
|
||||
public const int DefaultSunsetOffset = 0;
|
||||
public const string DefaultLatitude = "0.0";
|
||||
public const string DefaultLongitude = "0.0";
|
||||
public const string DefaultScheduleMode = "FixedHours";
|
||||
public static readonly HotkeySettings DefaultToggleThemeHotkey = new HotkeySettings(true, true, false, true, 0x44); // Ctrl+Win+Shift+D
|
||||
|
||||
public LightSwitchProperties()
|
||||
{
|
||||
ChangeSystem = new BoolProperty(DefaultChangeSystem);
|
||||
ChangeApps = new BoolProperty(DefaultChangeApps);
|
||||
LightTime = new IntProperty(DefaultLightTime);
|
||||
DarkTime = new IntProperty(DefaultDarkTime);
|
||||
Latitude = new StringProperty(DefaultLatitude);
|
||||
Longitude = new StringProperty(DefaultLongitude);
|
||||
SunriseOffset = new IntProperty(DefaultSunriseOffset);
|
||||
SunsetOffset = new IntProperty(DefaultSunsetOffset);
|
||||
ScheduleMode = new StringProperty(DefaultScheduleMode);
|
||||
ToggleThemeHotkey = new KeyboardKeysProperty(DefaultToggleThemeHotkey);
|
||||
}
|
||||
|
||||
[JsonPropertyName("changeSystem")]
|
||||
public BoolProperty ChangeSystem { get; set; }
|
||||
|
||||
[JsonPropertyName("changeApps")]
|
||||
public BoolProperty ChangeApps { get; set; }
|
||||
|
||||
[JsonPropertyName("lightTime")]
|
||||
public IntProperty LightTime { get; set; }
|
||||
|
||||
[JsonPropertyName("darkTime")]
|
||||
public IntProperty DarkTime { get; set; }
|
||||
|
||||
[JsonPropertyName("sunrise_offset")]
|
||||
public IntProperty SunriseOffset { get; set; }
|
||||
|
||||
[JsonPropertyName("sunset_offset")]
|
||||
public IntProperty SunsetOffset { get; set; }
|
||||
|
||||
[JsonPropertyName("latitude")]
|
||||
public StringProperty Latitude { get; set; }
|
||||
|
||||
[JsonPropertyName("longitude")]
|
||||
public StringProperty Longitude { get; set; }
|
||||
|
||||
[JsonPropertyName("scheduleMode")]
|
||||
public StringProperty ScheduleMode { get; set; }
|
||||
|
||||
[JsonPropertyName("toggle-theme-hotkey")]
|
||||
public KeyboardKeysProperty ToggleThemeHotkey { get; set; }
|
||||
}
|
||||
}
|
||||
58
src/settings-ui/Settings.UI.Library/LightSwitchSettings.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Settings.UI.Library
|
||||
{
|
||||
public class LightSwitchSettings : BasePTModuleSettings, ISettingsConfig, ICloneable
|
||||
{
|
||||
public const string ModuleName = "LightSwitch";
|
||||
|
||||
public LightSwitchSettings()
|
||||
{
|
||||
Name = ModuleName;
|
||||
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
Properties = new LightSwitchProperties();
|
||||
}
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public LightSwitchProperties Properties { get; set; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new LightSwitchSettings()
|
||||
{
|
||||
Name = Name,
|
||||
Version = Version,
|
||||
Properties = new LightSwitchProperties()
|
||||
{
|
||||
ChangeSystem = new BoolProperty(Properties.ChangeSystem.Value),
|
||||
ChangeApps = new BoolProperty(Properties.ChangeApps.Value),
|
||||
ScheduleMode = new StringProperty(Properties.ScheduleMode.Value),
|
||||
LightTime = new IntProperty((int)Properties.LightTime.Value),
|
||||
DarkTime = new IntProperty((int)Properties.DarkTime.Value),
|
||||
SunriseOffset = new IntProperty((int)Properties.SunriseOffset.Value),
|
||||
SunsetOffset = new IntProperty((int)Properties.SunsetOffset.Value),
|
||||
Latitude = new StringProperty(Properties.Latitude.Value),
|
||||
Longitude = new StringProperty(Properties.Longitude.Value),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public bool UpgradeSettingsConfiguration()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class SndLightSwitchSettings
|
||||
{
|
||||
[JsonPropertyName("LightSwitch")]
|
||||
public LightSwitchSettings Settings { get; set; }
|
||||
|
||||
public SndLightSwitchSettings()
|
||||
{
|
||||
}
|
||||
|
||||
public SndLightSwitchSettings(LightSwitchSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 228 KiB |
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Converters
|
||||
{
|
||||
public partial class EnumToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value == null || parameter == null)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
string enumString = value.ToString();
|
||||
string targetString = parameter.ToString();
|
||||
|
||||
return enumString.Equals(targetString, StringComparison.OrdinalIgnoreCase)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Converters;
|
||||
|
||||
public sealed partial class TimeSpanToFriendlyTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is TimeSpan time)
|
||||
{
|
||||
return TimeSpanHelper.Convert(time);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) => new NotImplementedException();
|
||||
}
|
||||
@@ -52,6 +52,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
case ModuleType.CmdPal: return generalSettingsConfig.Enabled.CmdPal;
|
||||
case ModuleType.ColorPicker: return generalSettingsConfig.Enabled.ColorPicker;
|
||||
case ModuleType.CropAndLock: return generalSettingsConfig.Enabled.CropAndLock;
|
||||
case ModuleType.LightSwitch: return generalSettingsConfig.Enabled.LightSwitch;
|
||||
case ModuleType.EnvironmentVariables: return generalSettingsConfig.Enabled.EnvironmentVariables;
|
||||
case ModuleType.FancyZones: return generalSettingsConfig.Enabled.FancyZones;
|
||||
case ModuleType.FileLocksmith: return generalSettingsConfig.Enabled.FileLocksmith;
|
||||
@@ -88,6 +89,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
case ModuleType.CmdPal: generalSettingsConfig.Enabled.CmdPal = isEnabled; break;
|
||||
case ModuleType.ColorPicker: generalSettingsConfig.Enabled.ColorPicker = isEnabled; break;
|
||||
case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break;
|
||||
case ModuleType.LightSwitch: generalSettingsConfig.Enabled.LightSwitch = isEnabled; break;
|
||||
case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break;
|
||||
case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break;
|
||||
case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break;
|
||||
@@ -159,6 +161,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
ModuleType.CmdPal => typeof(CmdPalPage),
|
||||
ModuleType.ColorPicker => typeof(ColorPickerPage),
|
||||
ModuleType.CropAndLock => typeof(CropAndLockPage),
|
||||
ModuleType.LightSwitch => typeof(LightSwitchPage),
|
||||
ModuleType.EnvironmentVariables => typeof(EnvironmentVariablesPage),
|
||||
ModuleType.FancyZones => typeof(FancyZonesPage),
|
||||
ModuleType.FileLocksmith => typeof(FileLocksmithPage),
|
||||
|
||||
38
src/settings-ui/Settings.UI/Helpers/TimeSpanHelper.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
|
||||
public static class TimeSpanHelper
|
||||
{
|
||||
public static string Convert(TimeSpan? time)
|
||||
{
|
||||
if (time is not TimeSpan ts)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// If user passed in a negative TimeSpan, normalize
|
||||
if (ts < TimeSpan.Zero)
|
||||
{
|
||||
ts = ts.Duration();
|
||||
}
|
||||
|
||||
// Map the TimeSpan to a DateTime on today's date
|
||||
var dt = DateTime.Today.Add(ts);
|
||||
|
||||
// This pattern automatically respects system 12/24-hour setting
|
||||
string pattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
|
||||
|
||||
return dt.ToString(pattern, CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
|
||||
FileExplorer,
|
||||
ImageResizer,
|
||||
KBM,
|
||||
LightSwitch,
|
||||
MouseUtils,
|
||||
MouseWithoutBorders,
|
||||
Peek,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Settings\Modules\APDialog.dark.png" />
|
||||
<None Remove="Assets\Settings\Modules\APDialog.light.png" />
|
||||
<None Remove="Assets\Settings\Modules\LightSwitch.png" />
|
||||
<None Remove="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml" />
|
||||
<None Remove="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml" />
|
||||
<None Remove="SettingsXAML\Controls\KeyVisual\KeyCharPresenter.xaml" />
|
||||
@@ -156,7 +157,9 @@
|
||||
</None>
|
||||
<None Update="Assets\Settings\Scripts\DisableModule.ps1">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None> <Page Update="SettingsXAML\Controls\TitleBar\TitleBar.xaml">
|
||||
</None>
|
||||
<Page Update="SettingsXAML\Controls\TitleBar\TitleBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="SettingsXAML\Controls\KeyVisual\KeyCharPresenter.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -170,6 +173,12 @@
|
||||
<Page Update="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="SettingsXAML\Controls\Timeline\TimelineStyles.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="SettingsXAML\Controls\Timeline\Timeline.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
|
||||
@@ -22,6 +23,7 @@ namespace Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
[JsonSerializable(typeof(FileLocksmithSettings))]
|
||||
[JsonSerializable(typeof(FindMyMouseSettings))]
|
||||
[JsonSerializable(typeof(IList<PowerToysReleaseInfo>))]
|
||||
[JsonSerializable(typeof(LightSwitchSettings))]
|
||||
[JsonSerializable(typeof(MeasureToolSettings))]
|
||||
[JsonSerializable(typeof(MouseHighlighterSettings))]
|
||||
[JsonSerializable(typeof(MouseJumpSettings))]
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<ResourceDictionary Source="/SettingsXAML/Styles/InfoBadge.xaml" />
|
||||
<ResourceDictionary Source="/SettingsXAML/Themes/Colors.xaml" />
|
||||
<ResourceDictionary Source="/SettingsXAML/Themes/Generic.xaml" />
|
||||
<ResourceDictionary Source="/SettingsXAML/Controls/Timeline/TimelineStyles.xaml" />
|
||||
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@@ -417,6 +417,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
case "Awake": return typeof(AwakePage);
|
||||
case "CmdNotFound": return typeof(CmdNotFoundPage);
|
||||
case "ColorPicker": return typeof(ColorPickerPage);
|
||||
case "LightSwitch": return typeof(LightSwitchPage);
|
||||
case "FancyZones": return typeof(FancyZonesPage);
|
||||
case "FileLocksmith": return typeof(FileLocksmithPage);
|
||||
case "Run": return typeof(PowerLauncherPage);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{x:Bind ModuleTitle}" />
|
||||
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ScrollViewer Grid.Row="1" AutomationProperties.AutomationId="PageScrollViewer">
|
||||
<Grid
|
||||
Padding="0,0,20,48"
|
||||
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Controls.Timeline"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Canvas
|
||||
x:Name="HeaderCanvas"
|
||||
Grid.Row="0"
|
||||
Height="24" />
|
||||
|
||||
<!-- Timeline (bands + ticks + labels) -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<Canvas
|
||||
x:Name="TimelineCanvas"
|
||||
Height="36"
|
||||
Loaded="TimelineCanvas_Loaded" />
|
||||
</Border>
|
||||
|
||||
<!-- Below-chart annotations (sunrise/sunset panels + major labels) -->
|
||||
<Canvas
|
||||
x:Name="AnnotationCanvas"
|
||||
Grid.Row="2"
|
||||
Height="32" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,664 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
{
|
||||
public sealed partial class Timeline : UserControl
|
||||
{
|
||||
public TimeSpan StartTime
|
||||
{
|
||||
get => (TimeSpan)GetValue(StartTimeProperty);
|
||||
set => SetValue(StartTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StartTimeProperty = DependencyProperty.Register(nameof(StartTime), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: new TimeSpan(22, 0, 0), OnTimeChanged));
|
||||
|
||||
public TimeSpan EndTime
|
||||
{
|
||||
get => (TimeSpan)GetValue(EndTimeProperty);
|
||||
set => SetValue(EndTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty EndTimeProperty = DependencyProperty.Register(nameof(EndTime), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: new TimeSpan(7, 0, 0), OnTimeChanged));
|
||||
|
||||
public TimeSpan? Sunrise
|
||||
{
|
||||
get => (TimeSpan?)GetValue(SunriseProperty);
|
||||
set => SetValue(SunriseProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SunriseProperty = DependencyProperty.Register(nameof(Sunrise), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: null, OnTimeChanged));
|
||||
|
||||
public TimeSpan? Sunset
|
||||
{
|
||||
get => (TimeSpan?)GetValue(SunsetProperty);
|
||||
set => SetValue(SunsetProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SunsetProperty = DependencyProperty.Register(nameof(Sunset), typeof(TimeSpan), typeof(Timeline), new PropertyMetadata(defaultValue: null, OnTimeChanged));
|
||||
|
||||
private readonly List<int> _tickHours = new();
|
||||
|
||||
// Locale 24h/12h
|
||||
private readonly bool _is24h = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains('H');
|
||||
|
||||
// Visuals
|
||||
private readonly List<Line> _ticks = new();
|
||||
private readonly List<TextBlock> _majorTickBottomLabels = new(); // 00,06,12,18,24 (below)
|
||||
|
||||
private readonly List<Border> _darkRects = new(); // up to 2 (wrap)
|
||||
private readonly List<Border> _lightRects = new(); // up to 2 (complement)
|
||||
|
||||
private TextBlock _startEdgeLabel; // top-of-chart
|
||||
private TextBlock _endEdgeLabel;
|
||||
|
||||
private Line _sunriseTick;
|
||||
private Line _sunsetTick;
|
||||
|
||||
// Add/replace these constants (top of your class)
|
||||
private const int TickHourStep = 2; // <-- every 2 hours
|
||||
|
||||
private StackPanel _sunrisePanel; // icon + time (below chart)
|
||||
private StackPanel _sunsetPanel;
|
||||
|
||||
public Timeline()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.Loaded += Timeline_Loaded;
|
||||
this.IsEnabledChanged += Timeline_IsEnabledChanged;
|
||||
}
|
||||
|
||||
protected override AutomationPeer OnCreateAutomationPeer()
|
||||
{
|
||||
return new TimelineAutomationPeer(this);
|
||||
}
|
||||
|
||||
private void Timeline_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckEnabledState();
|
||||
}
|
||||
|
||||
private void Timeline_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
CheckEnabledState();
|
||||
}
|
||||
|
||||
private void CheckEnabledState()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
this.Opacity = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Opacity = 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((Timeline)d).Setup();
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
EnsureBands();
|
||||
EnsureTicks();
|
||||
EnsureStartEndEdgeLabels();
|
||||
EnsureSunriseSunsetTicks();
|
||||
EnsureSunPanels();
|
||||
EnsureMajorTickLabels();
|
||||
UpdateAll();
|
||||
}
|
||||
|
||||
private void TimelineCanvas_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// SizeChanged wiring here (as requested)
|
||||
HeaderCanvas.SizeChanged += (_, __) => UpdateAll();
|
||||
TimelineCanvas.SizeChanged += (_, __) => UpdateAll();
|
||||
AnnotationCanvas.SizeChanged += (_, __) => UpdateAll();
|
||||
Setup();
|
||||
}
|
||||
|
||||
private void UpdateAll()
|
||||
{
|
||||
UpdateBandsLayout();
|
||||
UpdateTicksLayout();
|
||||
UpdateStartEndEdgeLabelsLayout();
|
||||
UpdateSunriseSunsetTicksLayout();
|
||||
UpdateSunPanelsLayout();
|
||||
UpdateMajorTickLabelsLayout();
|
||||
AutomationProperties.SetHelpText(
|
||||
this,
|
||||
$"Start={StartTime};End={EndTime};Sunrise={Sunrise};Sunset={Sunset}");
|
||||
}
|
||||
|
||||
// ===== Ticks =====
|
||||
private void EnsureTicks()
|
||||
{
|
||||
if (_ticks.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tickHours.Clear();
|
||||
|
||||
// Build ticks at 0,2,4,...,24 but skip the first/last MAJOR ticks (0 and 24)
|
||||
for (int hour = 0; hour <= 24; hour += TickHourStep)
|
||||
{
|
||||
bool isMajor = hour % 6 == 0;
|
||||
if (isMajor && (hour == 0 || hour == 24))
|
||||
{
|
||||
continue; // skip first/last major ticks
|
||||
}
|
||||
|
||||
var line = new Line
|
||||
{
|
||||
Style = (Style)Application.Current.Resources[isMajor ? "MajorHourTickStyle" : "HourTickStyle"],
|
||||
};
|
||||
|
||||
Canvas.SetZIndex(line, 0); // above bands (adjust if needed)
|
||||
|
||||
_ticks.Add(line);
|
||||
_tickHours.Add(hour);
|
||||
|
||||
// If you actually want these IN the chart, use TimelineCanvas instead:
|
||||
AnnotationCanvas.Children.Add(line); // or TimelineCanvas.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTicksLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
double h = TimelineCanvas.ActualHeight; // keeping your offset
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double minorLen = h * 0.1;
|
||||
double majorLen = h * 0.2;
|
||||
|
||||
for (int i = 0; i < _ticks.Count; i++)
|
||||
{
|
||||
int hour = _tickHours[i];
|
||||
double x = Math.Round((hour / 24.0) * w);
|
||||
|
||||
var line = _ticks[i];
|
||||
double len = (hour % 6 == 0) ? majorLen : minorLen;
|
||||
|
||||
line.X1 = x;
|
||||
line.Y1 = 0;
|
||||
line.X2 = x;
|
||||
line.Y2 = len;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Bands (Dark + Light) =====
|
||||
private void EnsureBands()
|
||||
{
|
||||
if (_darkRects.Count == 0)
|
||||
{
|
||||
_darkRects.Add(MakeBandRect(isDark: false));
|
||||
_darkRects.Add(MakeBandRect(isDark: false));
|
||||
}
|
||||
|
||||
if (_lightRects.Count == 0)
|
||||
{
|
||||
_lightRects.Add(MakeBandRect(isDark: true));
|
||||
_lightRects.Add(MakeBandRect(isDark: true));
|
||||
}
|
||||
}
|
||||
|
||||
private Border MakeBandRect(bool isDark)
|
||||
{
|
||||
var r = new Border();
|
||||
if (isDark)
|
||||
{
|
||||
r.Style = (Style)Application.Current.Resources["DarkBandStyle"];
|
||||
FontIcon icon = new FontIcon();
|
||||
icon.Style = (Style)Application.Current.Resources["DarkBandIconStyle"];
|
||||
r.Child = icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.Style = (Style)Application.Current.Resources["LightBandStyle"];
|
||||
}
|
||||
|
||||
Canvas.SetZIndex(r, 5); // below ticks/labels
|
||||
TimelineCanvas.Children.Add(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private void UpdateBandsLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
double h = TimelineCanvas.ActualHeight;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var r in _darkRects)
|
||||
{
|
||||
r.Height = h;
|
||||
Canvas.SetTop(r, 0);
|
||||
}
|
||||
|
||||
foreach (var r in _lightRects)
|
||||
{
|
||||
r.Height = h;
|
||||
Canvas.SetTop(r, 0);
|
||||
}
|
||||
|
||||
var darkRanges = ToRanges(StartTime, EndTime); // 1 or 2 segments
|
||||
var lightRanges = ComplementRanges(darkRanges); // 0..2
|
||||
|
||||
LayoutRangeRects(_darkRects, darkRanges, w);
|
||||
LayoutRangeRects(_lightRects, lightRanges, w);
|
||||
}
|
||||
|
||||
private static void LayoutRangeRects(List<Border> rects, List<(TimeSpan Start, TimeSpan End)> ranges, double width)
|
||||
{
|
||||
for (int i = 0; i < rects.Count; i++)
|
||||
{
|
||||
if (i < ranges.Count)
|
||||
{
|
||||
var (start, end) = ranges[i];
|
||||
double x = Math.Round((start.TotalHours / 24.0) * width);
|
||||
double x2 = Math.Round((end.TotalHours / 24.0) * width);
|
||||
|
||||
var r = rects[i];
|
||||
Canvas.SetLeft(r, x);
|
||||
r.Width = Math.Max(0, x2 - x);
|
||||
r.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
rects[i].Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<(TimeSpan Start, TimeSpan End)> ToRanges(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
// Full day
|
||||
if (start == end)
|
||||
{
|
||||
return new() { (TimeSpan.Zero, TimeSpan.FromHours(24)) };
|
||||
}
|
||||
|
||||
if (start < end)
|
||||
{
|
||||
return new() { (start, end) };
|
||||
}
|
||||
|
||||
// Wraps midnight
|
||||
return new()
|
||||
{
|
||||
(start, TimeSpan.FromHours(24)),
|
||||
(TimeSpan.Zero, end),
|
||||
};
|
||||
}
|
||||
|
||||
private static List<(TimeSpan Start, TimeSpan End)> ComplementRanges(List<(TimeSpan Start, TimeSpan End)> dark)
|
||||
{
|
||||
var res = new List<(TimeSpan, TimeSpan)>();
|
||||
|
||||
// If dark covers the full day, there is no light
|
||||
if (dark.Count == 1 && dark[0].Start == TimeSpan.Zero && dark[0].End == TimeSpan.FromHours(24))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (dark.Count == 1)
|
||||
{
|
||||
var (ds, de) = dark[0];
|
||||
if (ds > TimeSpan.Zero)
|
||||
{
|
||||
res.Add((TimeSpan.Zero, ds));
|
||||
}
|
||||
|
||||
if (de < TimeSpan.FromHours(24))
|
||||
{
|
||||
res.Add((de, TimeSpan.FromHours(24)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// dark[0] = [a,24), dark[1] = [0,b) => single light [b,a)
|
||||
var a = dark[0].Start;
|
||||
var b = dark[1].End;
|
||||
res.Add((b, a));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// ===== Start & End labels (TOP of chart, ABOVE rectangles) =====
|
||||
private void EnsureStartEndEdgeLabels()
|
||||
{
|
||||
if (_startEdgeLabel == null)
|
||||
{
|
||||
_startEdgeLabel = new TextBlock { Style = (Style)Application.Current.Resources["EdgeLabelStyle"] };
|
||||
HeaderCanvas.Children.Add(_startEdgeLabel);
|
||||
Canvas.SetZIndex(_startEdgeLabel, 25);
|
||||
}
|
||||
|
||||
if (_endEdgeLabel == null)
|
||||
{
|
||||
_endEdgeLabel = new TextBlock { Style = (Style)Application.Current.Resources["EdgeLabelStyle"] };
|
||||
HeaderCanvas.Children.Add(_endEdgeLabel);
|
||||
Canvas.SetZIndex(_endEdgeLabel, 25);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStartEndEdgeLabelsLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
if (w <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_startEdgeLabel.Text = TimeSpanHelper.Convert(StartTime);
|
||||
_endEdgeLabel.Text = TimeSpanHelper.Convert(EndTime);
|
||||
|
||||
PlaceTopLabelAtTime(_startEdgeLabel, StartTime, w);
|
||||
PlaceTopLabelAtTime(_endEdgeLabel, EndTime, w);
|
||||
}
|
||||
|
||||
private void PlaceTopLabelAtTime(TextBlock tb, TimeSpan t, double timelineWidth)
|
||||
{
|
||||
double x = Math.Round((t.TotalHours / 24.0) * timelineWidth);
|
||||
double textW = MeasureTextWidth(tb);
|
||||
double desiredLeft = x - (textW / 2.0);
|
||||
|
||||
Canvas.SetLeft(tb, Clamp(desiredLeft, 0, timelineWidth - textW));
|
||||
Canvas.SetTop(tb, 0);
|
||||
tb.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// ===== Sunrise/Sunset ticks on chart =====
|
||||
private void EnsureSunriseSunsetTicks()
|
||||
{
|
||||
if (_sunriseTick == null)
|
||||
{
|
||||
_sunriseTick = new Line { Style = (Style)Application.Current.Resources["SunRiseMarkerTickStyle"] };
|
||||
TimelineCanvas.Children.Add(_sunriseTick);
|
||||
Canvas.SetZIndex(_sunriseTick, 12);
|
||||
}
|
||||
|
||||
if (_sunsetTick == null)
|
||||
{
|
||||
_sunsetTick = new Line { Style = (Style)Application.Current.Resources["SunSetMarkerTickStyle"] };
|
||||
TimelineCanvas.Children.Add(_sunsetTick);
|
||||
Canvas.SetZIndex(_sunsetTick, 12);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSunriseSunsetTicksLayout()
|
||||
{
|
||||
double w = TimelineCanvas.ActualWidth;
|
||||
double h = TimelineCanvas.ActualHeight + 24;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void Place(Line tick, TimeSpan t)
|
||||
{
|
||||
double x = Math.Round((t.TotalHours / 24.0) * w);
|
||||
tick.X1 = x;
|
||||
tick.X2 = x;
|
||||
tick.Y1 = 0;
|
||||
tick.Y2 = h;
|
||||
}
|
||||
|
||||
if (_sunriseTick != null)
|
||||
{
|
||||
if (Sunrise.HasValue)
|
||||
{
|
||||
Place(_sunriseTick, Sunrise.Value);
|
||||
_sunriseTick.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sunriseTick.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (_sunsetTick != null)
|
||||
{
|
||||
if (Sunset.HasValue)
|
||||
{
|
||||
Place(_sunsetTick, Sunset.Value);
|
||||
_sunsetTick.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sunsetTick.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Sunrise/Sunset panels (below chart) =====
|
||||
private void EnsureSunPanels()
|
||||
{
|
||||
if (_sunrisePanel == null)
|
||||
{
|
||||
_sunrisePanel = MakeSunPanel("\uEC8A");
|
||||
AnnotationCanvas.Children.Add(_sunrisePanel);
|
||||
}
|
||||
|
||||
if (_sunsetPanel == null)
|
||||
{
|
||||
_sunsetPanel = MakeSunPanel("\uED3A");
|
||||
AnnotationCanvas.Children.Add(_sunsetPanel);
|
||||
}
|
||||
}
|
||||
|
||||
private StackPanel MakeSunPanel(string iconEmoji)
|
||||
{
|
||||
var icon = new FontIcon { Glyph = iconEmoji, Style = (Style)Application.Current.Resources["SunIconStyle"] };
|
||||
var sp = new StackPanel { Orientation = Orientation.Vertical, Spacing = 2 };
|
||||
sp.Children.Add(icon);
|
||||
return sp;
|
||||
}
|
||||
|
||||
private void UpdateSunPanelsLayout()
|
||||
{
|
||||
double timelineW = TimelineCanvas.ActualWidth;
|
||||
double annotationW = AnnotationCanvas.ActualWidth;
|
||||
if (annotationW <= 0)
|
||||
{
|
||||
annotationW = timelineW;
|
||||
}
|
||||
|
||||
if (timelineW <= 0 || annotationW <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void Place(StackPanel sp, TimeSpan t)
|
||||
{
|
||||
double panelW = MeasureElementWidth(sp);
|
||||
double xTimeline = Math.Round((t.TotalHours / 24.0) * timelineW);
|
||||
double left = Clamp(xTimeline - (panelW / 2.0), 0, annotationW - panelW);
|
||||
Canvas.SetLeft(sp, left);
|
||||
Canvas.SetTop(sp, 8);
|
||||
}
|
||||
|
||||
if (_sunrisePanel != null)
|
||||
{
|
||||
if (Sunrise.HasValue)
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunrisePanel, $"Sunrise: {TimeSpanHelper.Convert(Sunrise.Value)}");
|
||||
_sunrisePanel.Visibility = Visibility.Visible;
|
||||
Place(_sunrisePanel, Sunrise.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunrisePanel, null);
|
||||
_sunrisePanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (_sunsetPanel != null)
|
||||
{
|
||||
if (Sunset.HasValue)
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunsetPanel, $"Sunset: {TimeSpanHelper.Convert(Sunset.Value)}");
|
||||
_sunsetPanel.Visibility = Visibility.Visible;
|
||||
Place(_sunsetPanel, Sunset.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolTipService.SetToolTip(_sunsetPanel, null);
|
||||
_sunsetPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Major labels BELOW chart (00,06,12,18,24) =====
|
||||
private void EnsureMajorTickLabels()
|
||||
{
|
||||
if (_majorTickBottomLabels.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Includes 24:00 at end
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var tb = new TextBlock { Style = (Style)Application.Current.Resources["MajorTickLabelStyle"] };
|
||||
Canvas.SetZIndex(tb, 5); // on annotation canvas
|
||||
_majorTickBottomLabels.Add(tb);
|
||||
AnnotationCanvas.Children.Add(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMajorTickLabelsLayout()
|
||||
{
|
||||
double timelineW = TimelineCanvas.ActualWidth;
|
||||
double annotationW = AnnotationCanvas.ActualWidth;
|
||||
if (annotationW <= 0)
|
||||
{
|
||||
annotationW = timelineW;
|
||||
}
|
||||
|
||||
if (timelineW <= 0 || annotationW <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int[] hours = { 0, 6, 12, 18, 24 };
|
||||
|
||||
// 1) Place labels first
|
||||
for (int i = 0; i < hours.Length; i++)
|
||||
{
|
||||
var tb = _majorTickBottomLabels[i];
|
||||
var t = TimeSpan.FromHours(hours[i]);
|
||||
tb.Text = TimeSpanHelper.Convert(t);
|
||||
|
||||
double xTimeline = Math.Round((t.TotalHours / 24.0) * timelineW);
|
||||
double textW = MeasureTextWidth(tb);
|
||||
double left = xTimeline - (textW / 2.0);
|
||||
|
||||
// Middle ones (06, 12) exact center; edges clamp inside canvas
|
||||
if (i == 1 || i == 2)
|
||||
{
|
||||
Canvas.SetLeft(tb, left);
|
||||
}
|
||||
else
|
||||
{
|
||||
Canvas.SetLeft(tb, Clamp(left, 0, annotationW - textW));
|
||||
}
|
||||
|
||||
Canvas.SetTop(tb, 8); // your existing baseline below chart
|
||||
tb.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 2) Compute sunrise/sunset occupied horizontal ranges (if present)
|
||||
(double Left, double Right)? sunriseBounds = null;
|
||||
(double Left, double Right)? sunsetBounds = null;
|
||||
|
||||
if (Sunrise.HasValue && _sunrisePanel != null)
|
||||
{
|
||||
sunriseBounds = GetAnnotationBoundsForTime(Sunrise.Value, timelineW, annotationW, _sunrisePanel);
|
||||
}
|
||||
|
||||
if (Sunset.HasValue && _sunsetPanel != null)
|
||||
{
|
||||
sunsetBounds = GetAnnotationBoundsForTime(Sunset.Value, timelineW, annotationW, _sunsetPanel);
|
||||
}
|
||||
|
||||
// 3) Hide any label that intersects the sunrise/sunset panel bounds
|
||||
for (int i = 0; i < hours.Length; i++)
|
||||
{
|
||||
var tb = _majorTickBottomLabels[i];
|
||||
if (tb.Visibility != Visibility.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lbl = GetLabelBounds(tb);
|
||||
|
||||
bool hide =
|
||||
(sunriseBounds.HasValue && Intersects(lbl, sunriseBounds.Value)) ||
|
||||
(sunsetBounds.HasValue && Intersects(lbl, sunsetBounds.Value)); // include sunset too; remove if you only want sunrise
|
||||
|
||||
tb.Visibility = hide ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Utilities =====
|
||||
private static double Clamp(double v, double min, double max) => Math.Max(min, Math.Min(max, v));
|
||||
|
||||
private static double MeasureElementWidth(FrameworkElement el)
|
||||
{
|
||||
el.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
return el.DesiredSize.Width;
|
||||
}
|
||||
|
||||
private static double MeasureTextWidth(TextBlock tb)
|
||||
{
|
||||
tb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
return tb.DesiredSize.Width;
|
||||
}
|
||||
|
||||
private static bool Intersects((double Left, double Right) a, (double Left, double Right) b, double pad = 4)
|
||||
{
|
||||
// Horizontal overlap with padding
|
||||
return !(a.Right + pad <= b.Left || b.Right + pad <= a.Left);
|
||||
}
|
||||
|
||||
private (double Left, double Right) GetAnnotationBoundsForTime(TimeSpan t, double timelineW, double annotationW, FrameworkElement element)
|
||||
{
|
||||
// Compute the *actual* left/right the panel will occupy in AnnotationCanvas
|
||||
double panelW = MeasureElementWidth(element);
|
||||
double xTimeline = Math.Round((t.TotalHours / 24.0) * timelineW);
|
||||
double left = Clamp(xTimeline - (panelW / 2.0), 0, annotationW - panelW);
|
||||
return (left, left + panelW);
|
||||
}
|
||||
|
||||
private (double Left, double Right) GetLabelBounds(TextBlock tb)
|
||||
{
|
||||
double w = MeasureTextWidth(tb);
|
||||
double left = Canvas.GetLeft(tb);
|
||||
return (left, left + w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
{
|
||||
public partial class TimelineAutomationPeer : FrameworkElementAutomationPeer
|
||||
{
|
||||
public TimelineAutomationPeer(Timeline owner)
|
||||
: base(owner)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string GetClassNameCore() => "Timeline";
|
||||
|
||||
protected override AutomationControlType GetAutomationControlTypeCore()
|
||||
=> AutomationControlType.Custom;
|
||||
|
||||
protected override string GetAutomationIdCore()
|
||||
{
|
||||
var owner = (Timeline)Owner;
|
||||
var id = AutomationProperties.GetAutomationId(owner);
|
||||
return string.IsNullOrEmpty(id) ? base.GetAutomationIdCore() : id;
|
||||
}
|
||||
|
||||
protected override string GetNameCore()
|
||||
{
|
||||
var owner = (Timeline)Owner;
|
||||
var name = AutomationProperties.GetName(owner);
|
||||
return !string.IsNullOrEmpty(name)
|
||||
? name
|
||||
: $"Timeline from {owner.StartTime} to {owner.EndTime}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="HourTickStyle" TargetType="Line">
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="MajorHourTickStyle"
|
||||
BasedOn="{StaticResource HourTickStyle}"
|
||||
TargetType="Line">
|
||||
<Setter Property="StrokeThickness" Value="2" />
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SunRiseMarkerTickStyle" TargetType="Line">
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
<Setter Property="StrokeDashArray" Value="2,2" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style x:Key="SunSetMarkerTickStyle" TargetType="Line">
|
||||
<Setter Property="Stroke" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="StrokeThickness" Value="1" />
|
||||
<Setter Property="StrokeDashArray" Value="2,2" />
|
||||
</Style>
|
||||
|
||||
<!-- ===== Text / Labels ===== -->
|
||||
<Style x:Key="EdgeLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||
<Setter Property="IsHitTestVisible" Value="False" />
|
||||
</Style>
|
||||
|
||||
<!-- Below-chart labels for 00/06/12/18/24 -->
|
||||
<Style x:Key="MajorTickLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="IsHitTestVisible" Value="False" />
|
||||
</Style>
|
||||
|
||||
<!-- Sunrise/Sunset panel styles -->
|
||||
<Style x:Key="SunIconStyle" TargetType="FontIcon">
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorTertiaryBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- ===== Bands ===== -->
|
||||
<Style x:Key="DarkBandStyle" TargetType="Border">
|
||||
<!--<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Opacity="0.6" Color="{ThemeResource SystemAccentColorDark1}"/>
|
||||
</Setter.Value>
|
||||
</Setter>-->
|
||||
<Setter Property="Background" Value="{ThemeResource AccentFillColorTertiaryBrush}" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="Padding" Value="4" />
|
||||
<Setter Property="ToolTipService.ToolTip" Value="Dark mode" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LightBandStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="ToolTipService.ToolTip" Value="Light mode" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DarkBandIconStyle" TargetType="FontIcon">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Glyph" Value="" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="LightBandIconStyle"
|
||||
BasedOn="{StaticResource DarkBandIconStyle}"
|
||||
TargetType="FontIcon">
|
||||
<Setter Property="Glyph" Value="" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeLightSwitch"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:OOBEPageControl x:Uid="Oobe_LightSwitch" HeroImage="ms-appx:///Assets/Settings/Modules/OOBE/LightSwitch.png">
|
||||
<controls:OOBEPageControl.PageContent>
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock x:Uid="Oobe_HowToUse" Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<tkcontrols:MarkdownTextBlock x:Uid="Oobe_LightSwitch_HowToUse" />
|
||||
|
||||
<TextBlock x:Uid="Oobe_TipsAndTricks" Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<tkcontrols:MarkdownTextBlock x:Uid="Oobe_LightSwitch_TipsAndTricks" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button x:Uid="OOBE_Settings" Click="SettingsLaunchButton_Click" />
|
||||
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_LightSwitch" Style="{StaticResource TextButtonStyle}">
|
||||
<TextBlock x:Uid="LearnMore_LightSwitch" TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:OOBEPageControl.PageContent>
|
||||
</controls:OOBEPageControl>
|
||||
</Page>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
public sealed partial class OobeLightSwitch : Page
|
||||
{
|
||||
public OobePowerToysModule ViewModel { get; set; }
|
||||
|
||||
public OobeLightSwitch()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.LightSwitch]);
|
||||
}
|
||||
|
||||
private void SettingsLaunchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (OobeShellPage.OpenMainWindowCallback != null)
|
||||
{
|
||||
OobeShellPage.OpenMainWindowCallback(typeof(LightSwitchPage));
|
||||
}
|
||||
|
||||
ViewModel.LogOpeningSettingsEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||