Compare commits

..

51 Commits

Author SHA1 Message Date
Niels Laute
d403d5a7fb Adding (temp) icon 2025-08-29 07:38:06 +02:00
Niels Laute
289f47c1ea UX tweaks 2025-08-29 07:33:33 +02:00
Jaylyn Barbee
6bbfdd9a93 all things merged and well 2025-08-28 17:23:02 -04:00
Jaylyn Barbee
47102583af Merge branch 'jay/DarkModeModule' of https://github.com/microsoft/PowerToys into jay/DarkModeModule 2025-08-28 17:00:42 -04:00
Jaylyn Barbee
52ff8bea34 merging renaming 2025-08-28 17:00:39 -04:00
Niels Laute
a3441eecc6 Rename pages 2025-08-28 23:00:14 +02:00
Jaylyn Barbee
88cf1ecaec Changed name from dark mode to light swith 2025-08-28 16:32:22 -04:00
Niels Laute
a1b09180d8 Moving location stuff to the dialog 2025-08-28 22:24:45 +02:00
Niels Laute
6907f26243 Small tweaks + adding chart 2025-08-28 19:41:11 +02:00
Jaylyn Barbee
911a4e1009 some clean up around forcing modeS 2025-08-27 15:41:18 -04:00
Jaylyn Barbee
44be38e9b6 shortcuts working 2025-08-27 15:25:54 -04:00
Jaylyn Barbee
00d15ba780 3rd mode added, user selected city location 2025-08-27 14:15:19 -04:00
Jaylyn Barbee
b1b1791489 offset setting added 2025-08-27 13:02:01 -04:00
Jaylyn Barbee
05ae7129aa moved some files around 2025-08-26 11:59:39 -04:00
Jaylyn Barbee
4b015605a1 working on mode switching, some bugs right now 2025-08-21 14:15:41 -04:00
Jaylyn Barbee
fa29bebec3 using location services to calculate sun rise and sunset times is working 2025-08-21 13:53:26 -04:00
Jaylyn Barbee
9816d6fc05 Small comment 2025-08-21 11:47:18 -04:00
Jaylyn Barbee
323ddfdf55 stale catchup from sleep/lock at next minute + wrap around scale rather than forcing light mode to be first 2025-08-21 11:35:01 -04:00
Jaylyn Barbee
ae6187101f stale catchup for if you were shutdown/not running the service 2025-08-21 11:15:53 -04:00
Jaylyn Barbee
be105b5e27 Fixed flashing issue, legacy code got stuck when removing old logic 2025-08-21 10:56:30 -04:00
Jaylyn Barbee
8aedc4a61d Force mode buttons working using custom actions 2025-08-21 10:43:35 -04:00
Jaylyn Barbee
9c82281bb1 gpo fixes, tested and works! 2025-08-20 14:10:17 -04:00
Jaylyn Barbee
df74f6e3c7 added GPO 2025-08-20 10:20:34 -04:00
Jaylyn Barbee
887e552d43 merged main + ui changes + clean up in logic 2025-08-19 12:09:18 -04:00
Jaylyn Barbee
df61b2863e changed to date time pickers and separated the apps and system theme change 2025-08-19 11:37:31 -04:00
Jaylyn Barbee
9f581101a8 settings are persisting 2025-08-19 11:21:48 -04:00
Jaylyn Barbee
db41f61010 settings saving correctly 2025-08-19 11:03:10 -04:00
Niels Laute
e28e4582c9 Merge branch 'main' into jay/DarkModeModule 2025-08-19 16:43:51 +02:00
Niels Laute
abc7f3f3fb UI tweaks in Settings 2025-08-19 16:40:41 +02:00
Jaylyn Barbee
039991bc4a wiring in settings to load into service 2025-08-19 10:19:37 -04:00
Jaylyn Barbee
a857cc688b removing the ui 2025-08-19 09:23:51 -04:00
Jaylyn Barbee
1be5e5931a in nuget package trouble, settings are saving but not loading properly 2025-08-18 17:04:59 -04:00
Jaylyn Barbee
03fafa747f settings saving appropriately. 2025-08-14 17:07:57 -04:00
Jaylyn Barbee
62b4075349 service works, changes settings based on the time it has in the settings, but settings ui not connected to settings 2025-08-11 14:37:27 -04:00
Jaylyn Barbee
cc42876c01 service is enabling and disabling via ui, still one error at launch 2025-08-11 11:15:54 -04:00
Jaylyn Barbee
2d30fe2ec2 moved logic to service, incomplete right now though 2025-08-08 12:32:29 -04:00
Jaylyn Barbee
247cc47491 got logging figured out 2025-08-07 18:02:28 -04:00
Jaylyn Barbee
7de506010e trying to figured out build error 2025-08-07 15:53:48 -04:00
Jaylyn Barbee
736a04f65c small updates 2025-08-06 11:59:42 -04:00
Jaylyn Barbee
b66b44cc49 showing in runner now. settings still not connected, service still not running. 2025-08-06 10:47:16 -04:00
Jaylyn Barbee
f55f465c83 forward progress, trying to figure out runner 2025-08-05 14:20:12 -04:00
Jaylyn Barbee
30e6215003 repair work to the module interface 2025-08-05 13:55:29 -04:00
Jaylyn Barbee
bdafb0e38a trying to get it to show in runner but imports are busted 2025-08-05 12:21:14 -04:00
Jaylyn Barbee
50c5d577bc something broken with converters flipping to show settings 2025-08-04 16:41:35 -04:00
Jaylyn Barbee
a9e838ae1d Starting to fill in settings page 2025-08-04 15:26:32 -04:00
Jaylyn Barbee
c47ff9cd55 interface done? 2025-08-04 12:23:44 -04:00
Jaylyn Barbee
104d4fd6a0 Working on interface 2025-08-04 12:16:55 -04:00
Jaylyn Barbee
37242fbb4d commit base interface 2025-08-04 11:28:39 -04:00
Jaylyn Barbee
2c39113914 Fixing build errors 2025-08-04 11:23:10 -04:00
Jaylyn Barbee
bf07c11640 Linked to project 2025-08-04 11:03:15 -04:00
Jaylyn Barbee
2c6a8bac27 Dark mode code dump 2025-08-04 10:54:36 -04:00
343 changed files with 16992 additions and 9923 deletions

View File

@@ -25,14 +25,11 @@ ADMINS
adml
admx
advancedpaste
advancedpasteui
advancedpasteuishortcut
advfirewall
AFeature
affordances
AFX
AGGREGATABLE
AHK
AHybrid
akv
ALarger
@@ -43,7 +40,6 @@ ALLINPUT
Allman
Allmodule
ALLOWUNDO
allpc
ALLVIEW
ALPHATYPE
AModifier
@@ -168,7 +164,6 @@ callbackptr
calpwstr
Cangjie
CANRENAME
Canvascustomlayout
CAPTUREBLT
CAPTURECHANGED
CARETBLINKING
@@ -575,7 +570,6 @@ GPOCA
gpp
gpu
gradians
Gridcustomlayout
GSM
gtm
guiddata
@@ -635,7 +629,6 @@ HKCU
hkey
HKLM
HKM
hkmng
HKPD
HKU
HMD
@@ -653,11 +646,7 @@ Hostx
hotfixes
hotkeycontrol
HOTKEYF
hotkeylockmachine
hotkeyreconnect
hotkeys
hotkeyswitch
hotkeytoggleeasymouse
hotlight
hotspot
HPAINTBUFFER
@@ -670,7 +659,6 @@ HROW
hsb
HSCROLL
hsi
HSpeed
HTCLIENT
hthumbnail
HTOUCHINPUT
@@ -716,12 +704,9 @@ IMAGERESIZERCONTEXTMENU
IMAGERESIZEREXT
imageresizerinput
imageresizersettings
imagetotext
imagetotextshortcut
imagingdevices
ime
imgflip
inapp
inbox
INCONTACT
Indo
@@ -775,7 +760,6 @@ istep
ith
ITHUMBNAIL
IUI
IUWP
IWIC
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
@@ -805,7 +789,6 @@ keyvault
KILLFOCUS
killrunner
kmph
kvp
Kybd
lastcodeanalysissucceeded
LASTEXITCODE
@@ -844,7 +827,6 @@ localappdata
localpackage
LOCALSYSTEM
LOCATIONCHANGE
LOCKMACHINE
LOCKTYPE
LOGFONT
LOGFONTW
@@ -930,7 +912,6 @@ MDL
mdtext
mdtxt
mdwn
measuretool
meme
memicmp
MENUITEMINFO
@@ -980,7 +961,6 @@ MOUSEHWHEEL
MOUSEINPUT
mousejump
mousepointer
mousepointercrosshairs
mouseutils
MOVESIZEEND
MOVESIZESTART
@@ -1010,7 +990,6 @@ Mso
msrc
msstore
msvcp
MT
MTND
MULTIPLEUSE
multizone
@@ -1121,7 +1100,6 @@ NOTSRCCOPY
NOTSRCERASE
notwindows
NOTXORPEN
nowarn
NOZORDER
NPH
npmjs
@@ -1183,18 +1161,6 @@ PARENTRELATIVEFORADDRESSBAR
PARENTRELATIVEPARSING
parray
PARTIALCONFIRMATIONDIALOGTITLE
pasteashtmlfile
pasteashtmlfileshortcut
pasteasjson
pasteasjsonshortcut
pasteasmarkdown
pasteasmarkdownshortcut
pasteasplaintext
pasteasplaintextshortcut
pasteaspngfile
pasteaspngfileshortcut
pasteastxtfile
pasteastxtfileshortcut
PATCOPY
PATHMUSTEXIST
PATINVERT
@@ -1262,7 +1228,6 @@ Pomodoro
Popups
POPUPWINDOW
POSITIONITEM
powerocr
POWERRENAMECONTEXTMENU
powerrenameinput
POWERRENAMETEST
@@ -1403,7 +1368,6 @@ Removelnk
renamable
RENAMEONCOLLISION
reparented
reparenthotkey
reparenting
reportfileaccesses
requery
@@ -1653,7 +1617,6 @@ STYLECHANGED
STYLECHANGING
subkeys
sublang
Subdomain
SUBMODULEUPDATE
subresource
Superbar
@@ -1724,7 +1687,6 @@ THH
THICKFRAME
THISCOMPONENT
throughs
thumbnailhotkey
TILEDWINDOW
TILLSON
timedate
@@ -1739,7 +1701,6 @@ tlb
tlbimp
tlc
TNP
TOGGLEEASYMOUSE
Toolhelp
toolkitconverters
toolwindow
@@ -1753,7 +1714,6 @@ tracelogging
tracerpt
trackbar
trafficmanager
transcodetomp
transicc
TRAYMOUSEMESSAGE
triaging
@@ -1868,7 +1828,6 @@ VSINSTALLDIR
VSM
vso
vsonline
VSpeed
vstemplate
vstest
VSTHRD
@@ -2005,13 +1964,10 @@ XNamespace
Xoshiro
XPels
XPixel
XPos
XResource
xsi
XSpeed
XStr
xstyler
XTimer
XUP
XVIRTUALSCREEN
xxxxxx
@@ -2021,10 +1977,7 @@ YIncrement
yinle
yinyue
YPels
YPos
YResolution
YSpeed
YTimer
YStr
YVIRTUALSCREEN
ZEROINIT

View File

@@ -57,19 +57,12 @@ $totalList = $projFiles | ForEach-Object -Parallel {
$p = -split $p
$p = $p[1, 2]
$tempString = $p[0]
$tempString = $p[0] + " " + $p[1]
if([string]::IsNullOrWhiteSpace($tempString))
if(![string]::IsNullOrWhiteSpace($tempString))
{
Continue
echo "- $tempString";
}
if($tempString.StartsWith("Microsoft.") -Or $tempString.StartsWith("System."))
{
Continue
}
echo "- $tempString"
}
$csproj = $null;
}

View File

@@ -21,13 +21,4 @@ if (-not $?)
exit 1
}
# Ignore NU1503 on vcxproj files
dotnet restore $solution /nowarn:NU1503
if ($lastExitCode -ne 0)
{
$result = $lastExitCode
Write-Error "Error running dotnet restore, with the exit code $lastExitCode. Please verify logs on the nuget package versions."
exit $result
}
exit 0

View File

@@ -65,8 +65,7 @@
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="MSTest" Version="3.8.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NLog" Version="5.2.8" />
<PackageVersion Include="NLog" Version="5.0.4" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
<PackageVersion Include="OpenAI" Version="2.0.0" />
@@ -104,7 +103,7 @@
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
<PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="WinUIEx" Version="2.2.0" />
<PackageVersion Include="WPF-UI" Version="3.0.5" />
<PackageVersion Include="WyHash" Version="1.0.5" />
@@ -114,4 +113,4 @@
<PackageVersion Include="Microsoft.VariantAssignment.Client" Version="2.4.17140001" />
<PackageVersion Include="Microsoft.VariantAssignment.Contract" Version="3.0.16990001" />
</ItemGroup>
</Project>
</Project>

137
NOTICE.md
View File

@@ -1491,50 +1491,93 @@ SOFTWARE.
## NuGet Packages used by PowerToys
- AdaptiveCards.ObjectModel.WinUI3
- AdaptiveCards.Rendering.WinUI3
- AdaptiveCards.Templating
- Appium.WebDriver
- Azure.AI.OpenAI
- CoenM.ImageSharp.ImageHash
- CommunityToolkit.Common
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
- CommunityToolkit.Mvvm
- CommunityToolkit.WinUI.Animations
- CommunityToolkit.WinUI.Collections
- CommunityToolkit.WinUI.Controls.Primitives
- CommunityToolkit.WinUI.Controls.Segmented
- CommunityToolkit.WinUI.Controls.SettingsControls
- CommunityToolkit.WinUI.Controls.Sizers
- CommunityToolkit.WinUI.Converters
- CommunityToolkit.WinUI.Extensions
- CommunityToolkit.WinUI.UI.Controls.DataGrid
- CommunityToolkit.WinUI.UI.Controls.Markdown
- ControlzEx
- HelixToolkit
- HelixToolkit.Core.Wpf
- hyjiacan.pinyin4net
- Interop.Microsoft.Office.Interop.OneNote
- LazyCache
- Mages
- Markdig.Signed
- MessagePack
- ModernWpfUI
- Moq
- MSTest
- NLog
- NLog.Extensions.Logging
- NLog.Schema
- OpenAI
- ReverseMarkdown
- ScipBe.Common.Office.OneNote
- SharpCompress
- SkiaSharp.Views.WinUI
- StreamJsonRpc
- StyleCop.Analyzers
- UnicodeInformation
- UnitsNet
- UTF.Unknown
- WinUIEx
- WPF-UI
- WyHash
- AdaptiveCards.ObjectModel.WinUI3 2.0.0-beta
- AdaptiveCards.Rendering.WinUI3 2.1.0-beta
- AdaptiveCards.Templating 2.0.5
- Appium.WebDriver 4.4.5
- Azure.AI.OpenAI 1.0.0-beta.17
- CoenM.ImageSharp.ImageHash 1.3.6
- CommunityToolkit.Common 8.4.0
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock 0.1.250703-build.2173
- CommunityToolkit.Mvvm 8.4.0
- CommunityToolkit.WinUI.Animations 8.2.250402
- CommunityToolkit.WinUI.Collections 8.2.250402
- CommunityToolkit.WinUI.Controls.Primitives 8.2.250402
- CommunityToolkit.WinUI.Controls.Segmented 8.2.250402
- CommunityToolkit.WinUI.Controls.SettingsControls 8.2.250402
- CommunityToolkit.WinUI.Controls.Sizers 8.2.250402
- CommunityToolkit.WinUI.Converters 8.2.250402
- CommunityToolkit.WinUI.Extensions 8.2.250402
- CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2
- CommunityToolkit.WinUI.UI.Controls.Markdown 7.1.2
- ControlzEx 6.0.0
- HelixToolkit 2.24.0
- HelixToolkit.Core.Wpf 2.24.0
- hyjiacan.pinyin4net 4.1.1
- Interop.Microsoft.Office.Interop.OneNote 1.1.0.2
- LazyCache 2.4.0
- Mages 3.0.0
- Markdig.Signed 0.34.0
- MessagePack 3.1.3
- Microsoft.Bcl.AsyncInterfaces 9.0.8
- Microsoft.Bot.AdaptiveExpressions.Core 4.23.0
- Microsoft.CodeAnalysis.NetAnalyzers 9.0.0
- Microsoft.Data.Sqlite 9.0.8
- Microsoft.Diagnostics.Tracing.TraceEvent 3.1.16
- Microsoft.DotNet.ILCompiler (A)
- Microsoft.Extensions.DependencyInjection 9.0.8
- Microsoft.Extensions.Hosting 9.0.8
- Microsoft.Extensions.Hosting.WindowsServices 9.0.8
- Microsoft.Extensions.Logging 9.0.8
- Microsoft.Extensions.Logging.Abstractions 9.0.8
- Microsoft.NET.ILLink.Tasks (A)
- Microsoft.SemanticKernel 1.15.0
- Microsoft.Toolkit.Uwp.Notifications 7.1.2
- Microsoft.Web.WebView2 1.0.2903.40
- Microsoft.Win32.SystemEvents 9.0.8
- Microsoft.Windows.Compatibility 9.0.8
- Microsoft.Windows.CsWin32 0.3.183
- Microsoft.Windows.CsWinRT 2.2.0
- Microsoft.Windows.SDK.BuildTools 10.0.26100.4188
- Microsoft.WindowsAppSDK 1.7.250513003
- Microsoft.WindowsPackageManager.ComInterop 1.10.340
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
- Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4
- Moq 4.18.4
- MSTest 3.8.3
- NLog.Extensions.Logging 5.3.8
- NLog.Schema 5.2.8
- OpenAI 2.0.0
- ReverseMarkdown 4.1.0
- ScipBe.Common.Office.OneNote 3.0.1
- SharpCompress 0.37.2
- SkiaSharp.Views.WinUI 2.88.9
- StreamJsonRpc 2.21.69
- StyleCop.Analyzers 1.2.0-beta.556
- System.CodeDom 9.0.8
- System.CommandLine 2.0.0-beta4.22272.1
- System.ComponentModel.Composition 9.0.8
- System.Configuration.ConfigurationManager 9.0.8
- System.Data.OleDb 9.0.8
- System.Data.SqlClient 4.9.0
- System.Diagnostics.EventLog 9.0.8
- System.Diagnostics.PerformanceCounter 9.0.8
- System.Drawing.Common 9.0.8
- System.IO.Abstractions 22.0.13
- System.IO.Abstractions.TestingHelpers 22.0.13
- System.Management 9.0.8
- System.Net.Http 4.3.4
- System.Private.Uri 4.3.2
- System.Reactive 6.0.1
- System.Runtime.Caching 9.0.8
- System.ServiceProcess.ServiceController 9.0.8
- System.Text.Encoding.CodePages 9.0.8
- System.Text.Json 9.0.8
- System.Text.RegularExpressions 4.3.1
- UnicodeInformation 2.6.0
- UnitsNet 5.56.0
- UTF.Unknown 2.5.1
- WinUIEx 2.2.0
- WPF-UI 3.0.5
- WyHash 1.0.5

View File

@@ -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}
@@ -262,7 +264,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h
src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h
src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
src\common\utils\exec.h = src\common\utils\exec.h
src\common\utils\game_mode.h = src\common\utils\game_mode.h
src\common\utils\gpo.h = src\common\utils\gpo.h
@@ -789,13 +790,11 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LightSwitch", "LightSwitch", "{5B201255-53C8-490B-A34F-01F05D48A477}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "src\modules\LightSwitch\LightSwitchModuleInterface\LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSearch.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WebSearch.UnitTests\Microsoft.CmdPal.Ext.WebSearch.UnitTests.csproj", "{E816D7B2-4688-4ECB-97CC-3D8E798F3831}"
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}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightModeService", "src\modules\LightSwitch\LightSwitchService\LightSwitchService.vcxproj", "{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -2859,38 +2858,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
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.Build.0 = Debug|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|ARM64.ActiveCfg = Release|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|x64.ActiveCfg = Release|x64
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Release|x64.Build.0 = Release|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|x64.ActiveCfg = Debug|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Debug|x64.Build.0 = Debug|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|ARM64.ActiveCfg = Release|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|x64.ActiveCfg = Release|x64
{E816D7B3-4688-4ECB-97CC-3D8E798F3832}.Release|x64.Build.0 = Release|x64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|x64.ActiveCfg = Debug|x64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Debug|x64.Build.0 = Debug|x64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|ARM64.ActiveCfg = Release|ARM64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|x64.ActiveCfg = Release|x64
{E816D7B2-4688-4ECB-97CC-3D8E798F3831}.Release|x64.Build.0 = Release|x64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|ARM64.Build.0 = Debug|ARM64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|x64.ActiveCfg = Debug|x64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Debug|x64.Build.0 = Debug|x64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.ActiveCfg = Release|ARM64
{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
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3202,10 +3185,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}
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{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}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -71,41 +71,6 @@ When the user changes settings in the UI:
3. The runner calls the `set_config` function on the appropriate module
4. The module parses the JSON and applies the new settings
# Shortcut Conflict Detection
Steps to enable conflict detection for a hotkey:
### 1. Implement module interface for hotkeys
Ensure the module interface provides either `size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size)` or `std::optional<HotkeyEx> GetHotkeyEx()`.
- If not yet implemented, you need to add it so that it returns all hotkeys used by the module.
- **Important**: The order of the returned hotkeys matters. This order is used as an index to uniquely identify each hotkey for conflict detection and lookup.
- For reference, see: `src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp`
### 2. Implement IHotkeyConfig in the module settings (UI side)
Make sure the modules settings file inherits from `IHotkeyConfig` and implements `HotkeyAccessor[] GetAllHotkeyAccessors()`.
- This method should return all hotkeys used in the module.
- **Important**: The order of the returned hotkeys must be consistent with step 1 (`get_hotkeys()` or `GetHotkeyEx()`).
- For reference, see: `src/settings-ui/Settings.UI.Library/AdvancedPasteSettings.cs`
- **_Note:_** `HotkeyAccessor` is a wrapper around HotkeySettings.
It provides both `getter` and `setter` methods to read and update the corresponding hotkey.
Additionally, each `HotkeyAccessor` requires a resource string that describes the purpose of the hotkey.
This string is typically defined in: `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`
### 3. Update the modules ViewModel
The corresponding ViewModel should inherit from `PageViewModelBase` and implement `Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()`.
- This method should return all hotkeys, maintaining the same order as in steps 1 and 2.
- For reference, see: `src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs`
### 4. Ensure the modules Views call `OnPageLoaded()`
Once the modules view is loaded, make sure to invoke the ViewModels `OnPageLoaded()` method:
```cs
Loaded += (s, e) => ViewModel.OnPageLoaded();
```
- For reference, see: `src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml.cs`
## Debugging Settings
To debug settings issues:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -14,6 +14,21 @@
<DirectoryRef Id="FileLocksmithAssetsInstallFolder" FileSource="$(var.FileLocksmithAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--FileLocksmithAssetsFiles_Component_Def-->
<!-- !Warning! Make sure to change Component Guid if you update something here -->
<Component Id="Module_FileLocksmith" Guid="108D3EC1-E6E0-4E81-88EF-25966133CB41" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{84D68575-E186-46AD-B0CB-BAEB45EE29C0}">
<RegistryValue Type="string" Value="File Locksmith Shell Extension" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.FileLocksmithExt.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\AllFileSystemObjects\ShellEx\ContextMenuHandlers\FileLocksmithExt">
<RegistryValue Type="string" Value="{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"/>
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\Drive\ShellEx\ContextMenuHandlers\FileLocksmithExt">
<RegistryValue Type="string" Value="{84D68575-E186-46AD-B0CB-BAEB45EE29C0}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="FileLocksmithComponentGroup">
@@ -23,6 +38,7 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderFileLocksmithAssetsFolder" Directory="FileLocksmithAssetsInstallFolder" On="uninstall"/>
</Component>
<ComponentRef Id="Module_FileLocksmith" />
</ComponentGroup>
</Fragment>

View File

@@ -16,6 +16,71 @@
<!-- Generated by generateFileComponents.ps1 -->
<!--ImageResizerAssetsFiles_Component_Def-->
<Component Id="Module_ImageResizer_Registry" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32">
<RegistryValue Value="[WinUI3AppsInstallFolder]PowerToys.ImageResizerExt.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Apartment" Type="string" />
</RegistryKey>
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\Directory\ShellEx\DragDropHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<!-- Registry Keys for the context menu handler for each of the following image formats: bmp, dib, gif, jfif, jpe, jpeg, jpg, jxr, png, rle, tif, tiff, wdp -->
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.bmp\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.dib\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.gif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jfif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jpe\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jpeg\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jpg\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.jxr\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.png\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.rle\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.tif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.tiff\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="$(var.RegistryScope)"
Key="SOFTWARE\Classes\SystemFileAssociations\.wdp\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
</Component>
</DirectoryRef>
<ComponentGroup Id="ImageResizerComponentGroup">
@@ -25,6 +90,7 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderImageResizerAssetsFolder" Directory="ImageResizerAssetsFolder" On="uninstall"/>
</Component>
<ComponentRef Id="Module_ImageResizer_Registry" />
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -18,6 +18,19 @@
<DirectoryRef Id="NewPlusAssetsInstallFolder" FileSource="$(var.NewPlusAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--NewPlusAssetsFiles_Component_Def-->
<!-- NewPlus Shell Extension for Win10 registration -->
<Component Id="NewPlus_ShellExtension_win10" Guid="D5456D4A-6EEC-4B85-944D-6A6A4A74FFA6" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{FF90D477-E32A-4BE8-8CC5-A502A97F5401}">
<RegistryValue Type="string" Value="NewPlus Shell Extension Win10" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.NewPlus.ShellExtension.win10.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\Directory\background\ShellEx\ContextMenuHandlers\NewPlusShellExtensionWin10">
<RegistryValue Type="string" Value="{FF90D477-E32A-4BE8-8CC5-A502A97F5401}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="NewPlusComponentGroup">
@@ -27,7 +40,8 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderNewPlusAssetsFolder" Directory="NewPlusAssetsInstallFolder" On="uninstall"/>
</Component>
</ComponentGroup>
<ComponentRef Id="NewPlus_ShellExtension_win10" />
</ComponentGroup>
<!-- Example templates -->
@@ -67,7 +81,7 @@
</Component>
<ComponentRef Id="NewPlusTemplateFiles_Component" />
<ComponentRef Id="NewPlusTemplateSubFiles_Component" />
</ComponentGroup>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -14,6 +14,22 @@
<DirectoryRef Id="PowerRenameAssetsFolder" FileSource="$(var.PowerRenameAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--PowerRenameAssetsFiles_Component_Def-->
<!-- !Warning! Make sure to change Component Guid if you update something here -->
<Component Id="Module_PowerRename" Guid="40D43079-240E-402D-8CE8-571BFFA71175" Win64="yes">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}">
<RegistryValue Type="string" Value="PowerRename Shell Extension" />
<RegistryValue Type="string" Name="ContextMenuOptIn" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="[WinUI3AppsInstallFolder]PowerToys.PowerRenameExt.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\AllFileSystemObjects\ShellEx\ContextMenuHandlers\PowerRenameExt">
<RegistryValue Type="string" Value="{0440049F-D1DC-4E46-B27B-98393D79486B}"/>
</RegistryKey>
<RegistryKey Root="$(var.RegistryScope)" Key="SOFTWARE\Classes\Directory\background\ShellEx\ContextMenuHandlers\PowerRenameExt">
<RegistryValue Type="string" Value="{0440049F-D1DC-4E46-B27B-98393D79486B}"/>
</RegistryKey>
</Component>
</DirectoryRef>
<ComponentGroup Id="PowerRenameComponentGroup">
@@ -23,6 +39,7 @@
</RegistryKey>
<RemoveFolder Id="RemoveFolderPowerRenameAssetsFolder" Directory="PowerRenameAssetsFolder" On="uninstall"/>
</Component>
<ComponentRef Id="Module_PowerRename" />
</ComponentGroup>
</Fragment>

View File

@@ -176,18 +176,6 @@
<Custom Action="UnRegisterContextMenuPackages" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanImageResizerRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanFileLocksmithRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanPowerRenameRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="CleanNewPlusRuntimeRegistry" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
<Custom Action="UnRegisterCmdPalPackage" Before="RemoveFiles">
Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
@@ -449,35 +437,6 @@
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="UnRegisterContextMenuPackagesCA"
/>
<CustomAction Id="CleanImageResizerRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanImageResizerRuntimeRegistryCA"
/>
<CustomAction Id="CleanFileLocksmithRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanFileLocksmithRuntimeRegistryCA"
/>
<CustomAction Id="CleanPowerRenameRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanPowerRenameRuntimeRegistryCA"
/>
<CustomAction Id="CleanNewPlusRuntimeRegistry"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="CleanNewPlusRuntimeRegistryCA"
/>
<CustomAction Id="UnRegisterCmdPalPackage"

View File

@@ -1153,113 +1153,6 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
return WcaFinalize(er);
}
UINT __stdcall CleanImageResizerRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanImageResizerRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{51B4D7E5-7568-4234-B4BB-47FB3C016A69}";
const wchar_t* exts[] = { L".bmp", L".dib", L".gif", L".jfif", L".jpe", L".jpeg", L".jpg", L".jxr", L".png", L".rle", L".tif", L".tiff", L".wdp" };
auto deleteKeyRecursive = [](HKEY root, const std::wstring &path) {
RegDeleteTreeW(root, path.c_str());
};
// InprocServer32 chain root CLSID
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
// DragDrop handler
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\ShellEx\\DragDropHandlers\\ImageResizer");
// Extensions
for (auto ext : exts)
{
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\SystemFileAssociations\\" + std::wstring(ext) + L"\\ShellEx\\ContextMenuHandlers\\ImageResizer");
}
// Sentinel
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\ImageResizer");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanFileLocksmithRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanFileLocksmithRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt");
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\FileLocksmith");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanPowerRenameRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanPowerRenameRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{0440049F-D1DC-4E46-B27B-98393D79486B}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt");
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\PowerRenameExt");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\PowerRename");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall CleanNewPlusRuntimeRegistryCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
hr = WcaInitialize(hInstall, "CleanNewPlusRuntimeRegistryCA");
try
{
const wchar_t* CLSID_STR = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}";
auto deleteKeyRecursive = [](HKEY root, const std::wstring& path) {
RegDeleteTreeW(root, path.c_str());
};
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" + std::wstring(CLSID_STR));
deleteKeyRecursive(HKEY_CURRENT_USER, L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10");
RegDeleteTreeW(HKEY_CURRENT_USER, L"Software\\Microsoft\\PowerToys\\NewPlus");
}
catch (...)
{
er = ERROR_INSTALL_FAILURE;
}
er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er;
return WcaFinalize(er);
}
UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;

View File

@@ -28,7 +28,3 @@ EXPORTS
UninstallCommandNotFoundModuleCA
UpgradeCommandNotFoundModuleCA
UnsetAdvancedPasteAPIKeyCA
CleanImageResizerRuntimeRegistryCA
CleanFileLocksmithRuntimeRegistryCA
CleanPowerRenameRuntimeRegistryCA
CleanNewPlusRuntimeRegistryCA

View File

@@ -2,11 +2,11 @@
<configuration>
<packageSources>
<clear />
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies/nuget/v3/index.json" />
<add key="PowerToysPublicDependencies" value="https://pkgs.dev.azure.com/shine-oss/PowerToys/_packaging/PowerToysPublicDependencies%40Local/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="PowerToysPublicDependencies">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
</configuration>

View File

@@ -7,7 +7,7 @@ import { srtDefinition } from './customLanguages/srt.js';
export async function registerAdditionalLanguages(monaco){
await languageDefinitions();
registerAdditionalLanguage("cppExt", [".ino", ".pde"], "cpp", monaco);
registerAdditionalLanguage("xmlExt", [".wsdl", ".projitems", ".csproj", ".fsproj", ".shproj", ".vcxproj", ".vbproj", ".resx", ".resw"], "xml", monaco);
registerAdditionalLanguage("xmlExt", [".wsdl", ".csproj", ".vcxproj", ".vbproj", ".fsproj", ".resx", ".resw"], "xml", monaco);
registerAdditionalLanguage("txtExt", [".sln", ".log", ".vsconfig", ".env", ".ahk", ".ion"], "txt", monaco);
registerAdditionalLanguage("razorExt", [".razor"], "razor", monaco);
registerAdditionalLanguage("vbExt", [".vbs"], "vb", monaco);

File diff suppressed because one or more lines are too long

View File

@@ -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:

View File

@@ -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());

View File

@@ -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();

View File

@@ -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();

View File

@@ -12,6 +12,7 @@ namespace ManagedCommon
ColorPicker,
CmdPal,
CropAndLock,
LightSwitch,
EnvironmentVariables,
FancyZones,
FileLocksmith,

View File

@@ -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();

View File

@@ -257,7 +257,10 @@ 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;
// failing bc using "runas" with PowerToys.exe already running?
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

View File

@@ -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);

View File

@@ -3,6 +3,7 @@
#include <filesystem>
#include <common/version/version.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger_settings.h>
namespace LoggerHelpers
{

View File

@@ -1,266 +0,0 @@
// Shared runtime shell extension registration utility for PowerToys modules.
// Provides a generic EnsureRegistered function so individual modules only need
// to supply a specification (CLSID, sentinel, handler key paths, etc.).
#pragma once
#include <string>
#include <vector>
#include <windows.h>
#include <shlwapi.h>
#include "../logger/logger.h"
namespace runtime_shell_ext
{
struct Spec
{
// Mandatory
std::wstring clsid; // e.g. {GUID}
std::wstring sentinelKey; // e.g. Software\\Microsoft\\PowerToys\\ModuleName
std::wstring sentinelValue; // e.g. ContextMenuRegistered
std::vector<std::wstring> dllFileCandidates; // relative filenames (pick first existing)
std::vector<std::wstring> contextMenuHandlerKeyPaths; // full HKCU relative paths where default value = CLSID
// Optional
std::wstring friendlyName; // if non-empty written as default under CLSID root
bool writeOptInEmptyValue = true; // write ContextMenuOptIn="" under CLSID root (legacy pattern)
bool writeThreadingModel = true; // write Apartment threading model
std::vector<std::wstring> extraAssociationPaths; // additional key paths (DragDropHandlers etc.) default=CLSID
std::vector<std::wstring> systemFileAssocExtensions; // e.g. .png -> Software\\Classes\\SystemFileAssociations\\.png\\ShellEx\\ContextMenuHandlers\\<HandlerName>
std::wstring systemFileAssocHandlerName; // e.g. ImageResizer
std::wstring representativeSystemExt; // used to decide if associations need repair (.png)
bool logRepairs = true;
};
namespace detail
{
// Minimal RAII wrapper for HKEY
struct unique_hkey
{
HKEY h{ nullptr };
unique_hkey() = default;
explicit unique_hkey(HKEY handle) : h(handle) {}
~unique_hkey() { if (h) RegCloseKey(h); }
unique_hkey(const unique_hkey&) = delete;
unique_hkey& operator=(const unique_hkey&) = delete;
unique_hkey(unique_hkey&& other) noexcept : h(other.h) { other.h = nullptr; }
unique_hkey& operator=(unique_hkey&& other) noexcept { if (this != &other) { if (h) RegCloseKey(h); h = other.h; other.h = nullptr; } return *this; }
HKEY get() const { return h; }
HKEY* put() { if (h) { RegCloseKey(h); h = nullptr; } return &h; }
};
inline std::wstring base_dir_from_module(HMODULE h)
{
wchar_t buf[MAX_PATH];
if (GetModuleFileNameW(h, buf, MAX_PATH))
{
PathRemoveFileSpecW(buf);
return buf;
}
return L"";
}
inline std::wstring pick_existing_dll(const std::wstring& base, const std::vector<std::wstring>& candidates)
{
for (const auto& rel : candidates)
{
std::wstring full = base + L"\\" + rel;
if (GetFileAttributesW(full.c_str()) != INVALID_FILE_ATTRIBUTES)
{
return full;
}
}
if (!candidates.empty())
{
return base + L"\\" + candidates.front();
}
return L"";
}
inline bool sentinel_exists(const Spec& spec)
{
unique_hkey key;
if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS)
return false;
DWORD v = 0; DWORD sz = sizeof(v);
return RegQueryValueExW(key.get(), spec.sentinelValue.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&v), &sz) == ERROR_SUCCESS && v == 1;
}
inline void write_sentinel(const Spec& spec)
{
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
DWORD one = 1;
RegSetValueExW(key.get(), spec.sentinelValue.c_str(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&one), sizeof(one));
}
}
inline void write_inproc_server(const Spec& spec, const std::wstring& dllPath)
{
using namespace std::string_literals;
std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid;
std::wstring inprocKey = clsidRoot + L"\\InprocServer32";
{
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, clsidRoot.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
if (!spec.friendlyName.empty())
{
RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(spec.friendlyName.c_str()), static_cast<DWORD>((spec.friendlyName.size() + 1) * sizeof(wchar_t)));
}
if (spec.writeOptInEmptyValue)
{
const wchar_t* optIn = L"ContextMenuOptIn";
const wchar_t empty = L'\0';
RegSetValueExW(key.get(), optIn, 0, REG_SZ, reinterpret_cast<const BYTE*>(&empty), sizeof(empty));
}
}
}
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(dllPath.c_str()), static_cast<DWORD>((dllPath.size() + 1) * sizeof(wchar_t)));
if (spec.writeThreadingModel)
{
const wchar_t* tm = L"Apartment";
RegSetValueExW(key.get(), L"ThreadingModel", 0, REG_SZ, reinterpret_cast<const BYTE*>(tm), static_cast<DWORD>((wcslen(tm) + 1) * sizeof(wchar_t)));
}
}
}
inline std::wstring read_inproc_server(const Spec& spec)
{
using namespace std::string_literals;
std::wstring inprocKey = L"Software\\Classes\\CLSID\\"s + spec.clsid + L"\\InprocServer32";
unique_hkey key;
if (RegOpenKeyExW(HKEY_CURRENT_USER, inprocKey.c_str(), 0, KEY_READ, key.put()) != ERROR_SUCCESS)
return L"";
wchar_t buf[MAX_PATH]; DWORD sz = sizeof(buf);
if (RegQueryValueExW(key.get(), nullptr, nullptr, nullptr, reinterpret_cast<LPBYTE>(buf), &sz) == ERROR_SUCCESS)
return std::wstring(buf);
return L"";
}
inline void write_default_value_key(const std::wstring& keyPath, const std::wstring& value)
{
unique_hkey key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, key.put(), nullptr) == ERROR_SUCCESS)
{
RegSetValueExW(key.get(), nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(value.c_str()), static_cast<DWORD>((value.size() + 1) * sizeof(wchar_t)));
}
}
inline bool representative_association_exists(const Spec& spec)
{
using namespace std::string_literals;
if (spec.representativeSystemExt.empty() || spec.systemFileAssocHandlerName.empty())
return true;
std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + spec.representativeSystemExt + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName;
unique_hkey key;
return RegOpenKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, KEY_READ, key.put()) == ERROR_SUCCESS;
}
}
inline bool EnsureRegistered(const Spec& spec, HMODULE moduleInstance)
{
using namespace std::string_literals;
auto base = detail::base_dir_from_module(moduleInstance);
auto dllPath = detail::pick_existing_dll(base, spec.dllFileCandidates);
if (dllPath.empty())
{
Logger::error(L"Runtime registration: cannot locate dll path for CLSID {}", spec.clsid);
return false;
}
bool exists = detail::sentinel_exists(spec);
bool repaired = false;
if (exists)
{
auto current = detail::read_inproc_server(spec);
if (_wcsicmp(current.c_str(), dllPath.c_str()) != 0)
{
detail::write_inproc_server(spec, dllPath);
repaired = true;
}
if (!detail::representative_association_exists(spec))
{
repaired = true;
}
}
if (!exists)
{
detail::write_inproc_server(spec, dllPath);
}
if (!exists || repaired)
{
for (const auto& path : spec.contextMenuHandlerKeyPaths)
{
detail::write_default_value_key(path, spec.clsid);
}
for (const auto& path : spec.extraAssociationPaths)
{
detail::write_default_value_key(path, spec.clsid);
}
if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty())
{
for (const auto& ext : spec.systemFileAssocExtensions)
{
std::wstring path = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName;
detail::write_default_value_key(path, spec.clsid);
}
}
}
if (!exists)
{
detail::write_sentinel(spec);
Logger::info(L"Runtime registration completed for CLSID {}", spec.clsid);
}
else if (repaired && spec.logRepairs)
{
Logger::info(L"Runtime registration repaired for CLSID {}", spec.clsid);
}
return true;
}
inline bool Unregister(const Spec& spec)
{
using namespace std::string_literals;
// Remove handler key paths
for (const auto& path : spec.contextMenuHandlerKeyPaths)
{
RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str());
}
// Remove extra association paths (e.g., drag & drop handlers)
for (const auto& path : spec.extraAssociationPaths)
{
RegDeleteTreeW(HKEY_CURRENT_USER, path.c_str());
}
// Remove per-extension system file association handler keys
if (!spec.systemFileAssocExtensions.empty() && !spec.systemFileAssocHandlerName.empty())
{
for (const auto& ext : spec.systemFileAssocExtensions)
{
std::wstring keyPath = L"Software\\Classes\\SystemFileAssociations\\"s + ext + L"\\ShellEx\\ContextMenuHandlers\\" + spec.systemFileAssocHandlerName;
RegDeleteTreeW(HKEY_CURRENT_USER, keyPath.c_str());
}
}
// Remove CLSID branch
if (!spec.clsid.empty())
{
std::wstring clsidRoot = L"Software\\Classes\\CLSID\\"s + spec.clsid;
RegDeleteTreeW(HKEY_CURRENT_USER, clsidRoot.c_str());
}
// Remove sentinel value (not deleting entire key to avoid disturbing other values)
if (!spec.sentinelKey.empty() && !spec.sentinelValue.empty())
{
HKEY hKey{};
if (RegOpenKeyExW(HKEY_CURRENT_USER, spec.sentinelKey.c_str(), 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS)
{
RegDeleteValueW(hKey, spec.sentinelValue.c_str());
RegCloseKey(hKey);
}
}
Logger::info(L"Successfully unregistered CLSID {}", spec.clsid);
return true;
}
}

View File

@@ -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_90_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" />

View File

@@ -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>

View File

@@ -112,7 +112,7 @@ private:
return {};
}
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject, bool isShown = true)
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
{
try
{
@@ -122,7 +122,6 @@ private:
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
hotkey.isShown = isShown;
return hotkey;
}
catch (...)
@@ -232,10 +231,8 @@ private:
return false;
}
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue, bool actionsGroupIsShown = true)
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue)
{
bool actionIsShown = true;
if (actionValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return;
@@ -243,9 +240,9 @@ private:
const auto action = actionValue.GetObjectW();
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false) || !actionsGroupIsShown)
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
actionIsShown = false;
return;
}
if (action.HasKey(JSON_KEY_SHORTCUT))
@@ -253,7 +250,7 @@ private:
const AdditionalAction additionalAction
{
actionName.c_str(),
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT), actionIsShown)
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_additional_actions.push_back(additionalAction);
@@ -262,12 +259,12 @@ private:
{
for (const auto& [subActionName, subAction] : action)
{
process_additional_action(subActionName, subAction, actionIsShown);
process_additional_action(subActionName, subAction);
}
}
}
void read_settings(PowerToysSettings::PowerToyValues& settings)
void read_settings(PowerToysSettings::PowerToyValues& settings)
{
const auto settingsObject = settings.get_raw_json();
@@ -320,21 +317,9 @@ private:
{
const auto additionalActions = propertiesObject.GetNamedObject(JSON_KEY_ADDITIONAL_ACTIONS);
// Define the expected order to ensure consistent hotkey ID assignment
const std::vector<winrt::hstring> expectedOrder = {
L"image-to-text",
L"paste-as-file",
L"transcode"
};
// Process actions in the predefined order
for (auto& actionKey : expectedOrder)
for (const auto& [actionName, additionalAction] : additionalActions)
{
if (additionalActions.HasKey(actionKey))
{
const auto actionValue = additionalActions.GetNamedValue(actionKey);
process_additional_action(actionKey, actionValue);
}
process_additional_action(actionName, additionalAction);
}
}
@@ -346,14 +331,17 @@ private:
for (const auto& customAction : customActions)
{
const auto object = customAction.GetObjectW();
bool actionIsShown = object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false);
const CustomAction customActionData{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT), actionIsShown)
};
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
const CustomAction customActionData
{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_custom_actions.push_back(customActionData);
m_custom_actions.push_back(customActionData);
}
}
}
}

View File

@@ -73,7 +73,6 @@
<ClInclude Include="ClassFactory.h" />
<ClInclude Include="dllmain.h" />
<ClInclude Include="ExplorerCommand.h" />
<ClInclude Include="RuntimeRegistration.h" />
<ClInclude Include="pch.h" />
<None Include="packages.config" />
<None Include="resource.base.h" />

View File

@@ -27,9 +27,6 @@
<ClInclude Include="dllmain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RuntimeRegistration.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">

View File

@@ -12,7 +12,6 @@
#include "FileLocksmithLib/Constants.h"
#include "FileLocksmithLib/Settings.h"
#include "FileLocksmithLib/Trace.h"
#include "RuntimeRegistration.h"
#include "dllmain.h"
#include "Generated Files/resource.h"
@@ -83,17 +82,12 @@ public:
{
std::wstring path = get_module_folderpath(globals::instance);
std::wstring packageUri = path + L"\\FileLocksmithContextMenuPackage.msix";
if (!package::IsPackageRegisteredWithPowerToysVersion(constants::nonlocalizable::ContextMenuPackageName))
{
package::RegisterSparsePackage(path, packageUri);
}
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::EnsureRegistered();
#endif
}
m_enabled = true;
}
@@ -101,13 +95,6 @@ public:
virtual void disable() override
{
Logger::info(L"File Locksmith disabled");
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered (Win10)");
#endif
}
m_enabled = false;
}

View File

@@ -1,36 +0,0 @@
// Header-only runtime registration for FileLocksmith context menu extension.
#pragma once
#include <common/utils/shell_ext_registration.h>
namespace globals { extern HMODULE instance; }
namespace FileLocksmithRuntimeRegistration
{
namespace
{
inline runtime_shell_ext::Spec BuildSpec()
{
runtime_shell_ext::Spec spec;
spec.clsid = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}";
spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\FileLocksmith";
spec.sentinelValue = L"ContextMenuRegistered";
spec.dllFileCandidates = { L"PowerToys.FileLocksmithExt.dll" };
spec.contextMenuHandlerKeyPaths = {
L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt",
L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt" };
spec.friendlyName = L"File Locksmith Shell Extension";
return spec;
}
}
inline bool EnsureRegistered()
{
return runtime_shell_ext::EnsureRegistered(BuildSpec(), globals::instance);
}
inline void Unregister()
{
runtime_shell_ext::Unregister(BuildSpec());
}
}

View File

@@ -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

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36127.28 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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|x64.ActiveCfg = Release|x64
{38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FAF634A3-0D98-4A45-B082-D93B59782572}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,229 @@
<?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 Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
</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>
$(SolutionDir)src\;
$(SolutionDir)src\modules;
$(SolutionDir)src\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>

View File

@@ -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>

View File

@@ -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 == 0; // 0 = dark, 1 = light
}
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"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 0;
}

View File

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

View File

@@ -0,0 +1,669 @@
#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_FORCE_LIGHT_HOTKEY[] = L"force-light-mode-hotkey";
const wchar_t JSON_KEY_FORCE_DARK_HOTKEY[] = L"force-dark-mode-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,
SunsetToSunriseGeo,
SunsetToSunriseUser
// add more later
};
inline std::wstring ToString(ScheduleMode mode)
{
switch (mode)
{
case ScheduleMode::SunsetToSunriseGeo:
return L"SunsetToSunriseGeo";
case ScheduleMode::SunsetToSunriseUser:
return L"SunsetToSunriseUser";
case ScheduleMode::FixedHours:
default:
return L"FixedHours";
}
}
inline ScheduleMode FromString(const std::wstring& str)
{
if (str == L"SunsetToSunriseGeo")
return ScheduleMode::SunsetToSunriseGeo;
if (str == L"SunsetToSunriseUser")
return ScheduleMode::SunsetToSunriseUser;
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_offset = 0;
std::wstring m_latitude = L"0.0";
std::wstring m_longitude = L"0.0";
} g_settings;
// Implement the PowerToy Module Interface and all the required methods.
class LightSwitchInterface : public PowertoyModuleIface
{
private:
// The PowerToy state.
bool m_enabled = false;
HANDLE m_process{ nullptr };
HANDLE m_force_light_event_handle;
HANDLE m_force_dark_event_handle;
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
Hotkey m_force_light_mode_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'L' };
Hotkey m_force_dark_mode_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'D' };
// Load initial settings from the persisted values.
void init_settings();
public:
// Constructor
LightSwitchInterface()
{
LoggerHelpers::init_logger(L"LightSwitch", L"ModuleInterface", LogSettings::lightSwitchLoggerName);
m_force_light_event_handle = CreateDefaultEvent(L"POWEROYS_LIGHTSWITCH_FORCE_LIGHT");
m_force_dark_event_handle = CreateDefaultEvent(L"POWEROYS_LIGHTSWITCH_FORCE_DARK");
init_settings();
};
virtual const wchar_t* get_key() override
{
return L"LightSwitch"; // your unique key string
}
// 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 array of the names of all events that this powertoy listens for, with
// nullptr as the last element of the array. Nullptr can also be retured for empty
// list.
//virtual const wchar_t** get_events() override
//{
// static const wchar_t* events[] = { nullptr };
// // Available events:
// // - ll_keyboard
// // - win_hook_event
// //
// // static const wchar_t* events[] = { ll_keyboard,
// // win_hook_event,
// // nullptr };
// return events;
//}
// 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"SunsetToSunriseGeo", L"Use sunrise/sunset times (Geolocation)" },
{ L"SunsetToSunriseUser", L"Use sunrise/sunset times (User selected)" } });
// Integer spinners (for time in minutes since midnight)
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"offset",
L"Time to offset turning on your light/dark themes.",
g_settings.m_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"{}");
PowerToysSettings::HotkeyObject lm_hk = PowerToysSettings::HotkeyObject::from_settings(
m_force_light_mode_hotkey.win,
m_force_light_mode_hotkey.ctrl,
m_force_light_mode_hotkey.alt,
m_force_light_mode_hotkey.shift,
m_force_light_mode_hotkey.key);
settings.add_hotkey(
L"force-light-hotkey",
L"Shortcut to force light theme immediately",
lm_hk);
PowerToysSettings::HotkeyObject dm_hk = PowerToysSettings::HotkeyObject::from_settings(
m_force_dark_mode_hotkey.win,
m_force_dark_mode_hotkey.ctrl,
m_force_dark_mode_hotkey.alt,
m_force_dark_mode_hotkey.shift,
m_force_dark_mode_hotkey.key);
settings.add_hotkey(
L"force-dark-hotkey",
L"Shortcut to force dark 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"offset"))
{
g_settings.m_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";
// Resolve the executable path
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: '{}'", exe_name);
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(), // lpApplicationName
command_line.data(), // lpCommandLine (must be mutable)
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)
{
// Try waiting briefly to allow graceful exit, if needed
constexpr DWORD timeout_ms = 1500;
DWORD result = WaitForSingleObject(m_process, timeout_ms);
if (result == WAIT_TIMEOUT)
{
// Force kill if it didn<64>t exit in time
Logger::warn("Light Switch: Process didn't exit in time. Forcing termination.");
TerminateProcess(m_process, 0);
}
// Always clean up the handle
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_force_light;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_FORCE_LIGHT_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_force_light.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_force_light.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_force_light.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_force_light.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_force_light.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_force_light_mode_hotkey = _temp_force_light;
}
catch (...)
{
Logger::error("Failed to initialize Light Switch force light mode shortcut from settings. Value will keep unchanged.");
}
try
{
Hotkey _temp_force_dark;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_FORCE_DARK_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_force_dark.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_force_dark.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_force_dark.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_force_dark.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_force_dark.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_force_dark_mode_hotkey = _temp_force_dark;
}
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 >= 2)
{
hotkeys[0] = m_force_light_mode_hotkey;
hotkeys[1] = m_force_dark_mode_hotkey;
}
return 2;
}
virtual bool on_hotkey(size_t hotkeyId) override
{
if (m_enabled)
{
Logger::trace(L"Light Switch hotkey pressed");
if (!is_process_running())
{
enable();
}
if (hotkeyId == 0)
{
Logger::info(L"[Light Switch] Hotkey triggered: Force Light");
SetSystemTheme(true);
SetAppsTheme(true);
}
else if (hotkeyId == 1)
{
Logger::info(L"[Light Switch] Hotkey triggered: Force Dark");
SetSystemTheme(false);
SetAppsTheme(false);
}
return true;
}
return false;
}
bool is_process_running()
{
return WaitForSingleObject(m_process, 0) == WAIT_TIMEOUT;
}
// Handle incoming event, data is event-specific
//virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override
//{
// if (wcscmp(name, ll_keyboard) == 0)
// {
// auto& event = *(reinterpret_cast<LowlevelKeyboardEvent*>(data));
// // Return 1 if the keypress is to be suppressed (not forwarded to Windows),
// // otherwise return 0.
// return 0;
// }
// else if (wcscmp(name, win_hook_event) == 0)
// {
// auto& event = *(reinterpret_cast<WinHookEvent*>(data));
// // Return value is ignored
// return 0;
// }
// return 0;
//}
//// This methods are part of an experimental features not fully supported yet
//virtual void register_system_menu_helper(PowertoySystemMenuIface* helper) override
//{
//}
//virtual void signal_system_menu_action(const wchar_t* name) override
//{
//}
};
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"offset"))
g_settings.m_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");
}
}
// This method of saving the module settings is only required if you need to do any
// custom processing of the settings before saving them to disk.
//void $projectname$::save_settings() {
// try {
// // Create a PowerToyValues object for this PowerToy
// PowerToysSettings::PowerToyValues values(get_name());
//
// // Save a bool property.
// //values.add_property(
// // L"bool_toggle_1", // property name
// // g_settings.bool_prop // property value
// // g_settings.bool_prop // property value
// //);
//
// // Save an int property.
// //values.add_property(
// // L"int_spinner_1", // property name
// // g_settings.int_prop // property value
// //);
//
// // Save a string property.
// //values.add_property(
// // L"string_text_1", // property name
// // g_settings.string_prop // property value
// );
//
// // Save a color property.
// //values.add_property(
// // L"color_picker_1", // property name
// // g_settings.color_prop // property value
// //);
//
// // Save the PowerToyValues JSON to the power toy settings file.
// values.save_to_settings_file();
// }
// catch (std::exception ex) {
// // Couldn't save the settings.
// }
//}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new LightSwitchInterface();
}

View File

@@ -0,0 +1,2 @@
#include "pch.h"
#pragma comment(lib, "windowsapp")

View 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>

View 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));
}

View 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();
};

View File

@@ -0,0 +1,258 @@
#include <windows.h>
#include <tchar.h>
#include "ThemeScheduler.h"
#include "ThemeHelper.h"
#include <stdio.h>
#include <string>
#include <LightSwitchSettings.h>
#include <common/utils/gpo.h>
// Global service variables
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
HANDLE g_ServiceStopEvent = nullptr;
// Forward declarations of service functions (we<77>ll define them later)
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[])
{
// Parse args
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]);
}
if (debug)
{
// Create a console window for debug output
AllocConsole();
FILE* f;
freopen_s(&f, "CONOUT$", "w", stdout);
freopen_s(&f, "CONOUT$", "w", stderr);
// Optional: set a title so you can find it easily
SetConsoleTitle(L"LightSwitchService Debug");
// Console mode (debug)
g_ServiceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
ServiceWorkerThread(reinterpret_cast<void*>(static_cast<ULONG_PTR>(parentPid)));
CloseHandle(g_ServiceStopEvent);
// Keep window open until a key is pressed (optional)
// system("pause");
FreeConsole();
return 0;
}
// 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;
}
}
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();
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);
}
if (isLightActive)
{
if (settings.changeSystem)
SetSystemTheme(true);
if (settings.changeApps)
SetAppsTheme(true);
}
else
{
if (settings.changeSystem)
SetSystemTheme(false);
if (settings.changeApps)
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.offset, settings.darkTime + settings.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();
// Debug print
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);
// Apply theme logic
applyTheme(nowMinutes, settings.lightTime + settings.offset, settings.darkTime + settings.offset, settings);
// Sleep until next minute, wake early if stop/parent dies
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 (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;
}

View File

@@ -0,0 +1,254 @@
<?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|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<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 Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<TargetName>PowerToys.LightSwitchService</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</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;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalIncludeDirectories>
./../;
$(SolutionDir)src\common\Telemetry;
$(SolutionDir)src\common;
$(SolutionDir)src\;
$(SolutionDir)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;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\..\common\SettingsAPI\FileWatcher.cpp" />
<ClCompile Include="..\..\..\common\SettingsAPI\settings_helpers.cpp" />
<ClCompile Include="..\..\..\common\SettingsAPI\settings_objects.cpp" />
<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>
<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>

View File

@@ -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>

View File

@@ -0,0 +1,148 @@
#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()
{
// Mirrors AlwaysOnTop: <module name>.json
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);
}
}
// 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 (...)
{
//Logger::error(L"[LightSwitchSettings] Failed to read settings file");
// Keeps defaults if load fails
}
}

View File

@@ -0,0 +1,87 @@
#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 offset = 0; // offset in minutes to apply to calculated times
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;
};

View File

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

View File

@@ -0,0 +1,12 @@
#pragma once
enum class SettingId
{
ScheduleMode = 0,
Latitude,
Longitude,
LightTime,
DarkTime,
ChangeSystem,
ChangeApps
};

View File

@@ -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;
};

View File

@@ -0,0 +1,80 @@
#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 == 0; // 0 = dark, 1 = light
}
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"SystemUsesLightTheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &size);
RegCloseKey(hKey);
}
return value == 0;
}

View File

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

View File

@@ -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;
}

View 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);

View File

@@ -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}");
});
}

View File

@@ -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();

View File

@@ -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>

View File

@@ -27,73 +27,6 @@ struct InclusiveCrosshairs
void SwitchActivationMode();
void ApplySettings(InclusiveCrosshairsSettings& settings, bool applyToRuntimeObjects);
public:
// Allow external callers to request a position update (thread-safe enqueue)
static void RequestUpdatePosition()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr)
{
instance->UpdateCrosshairsPosition();
}
});
}
}
static void EnsureOn()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr && !instance->m_drawing)
{
instance->StartDrawing();
}
});
}
}
static void EnsureOff()
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([]() {
if (instance != nullptr && instance->m_drawing)
{
instance->StopDrawing();
}
});
}
}
static void SetExternalControl(bool enabled)
{
if (instance != nullptr)
{
auto dispatcherQueue = instance->m_dispatcherQueueController.DispatcherQueue();
dispatcherQueue.TryEnqueue([enabled]() {
if (instance != nullptr)
{
instance->m_externalControl = enabled;
if (enabled && instance->m_mouseHook)
{
UnhookWindowsHookEx(instance->m_mouseHook);
instance->m_mouseHook = NULL;
}
else if (!enabled && instance->m_drawing && !instance->m_mouseHook)
{
instance->m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, instance->m_hinstance, 0);
}
}
});
}
}
private:
enum class MouseButton
{
@@ -136,7 +69,6 @@ private:
bool m_drawing = false;
bool m_destroyed = false;
bool m_hiddenCursor = false;
bool m_externalControl = false;
void SetAutoHideTimer() noexcept;
// Configurable Settings
@@ -332,12 +264,9 @@ LRESULT CALLBACK InclusiveCrosshairs::MouseHookProc(int nCode, WPARAM wParam, LP
if (nCode >= 0)
{
MSLLHOOKSTRUCT* hookData = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
if (instance && !instance->m_externalControl)
if (wParam == WM_MOUSEMOVE)
{
if (wParam == WM_MOUSEMOVE)
{
instance->UpdateCrosshairsPosition();
}
instance->UpdateCrosshairsPosition();
}
}
return CallNextHookEx(0, nCode, wParam, lParam);
@@ -598,26 +527,6 @@ bool InclusiveCrosshairsIsEnabled()
return (InclusiveCrosshairs::instance != nullptr);
}
void InclusiveCrosshairsRequestUpdatePosition()
{
InclusiveCrosshairs::RequestUpdatePosition();
}
void InclusiveCrosshairsEnsureOn()
{
InclusiveCrosshairs::EnsureOn();
}
void InclusiveCrosshairsEnsureOff()
{
InclusiveCrosshairs::EnsureOff();
}
void InclusiveCrosshairsSetExternalControl(bool enabled)
{
InclusiveCrosshairs::SetExternalControl(enabled);
}
int InclusiveCrosshairsMain(HINSTANCE hInstance, InclusiveCrosshairsSettings& settings)
{
Logger::info("Starting a crosshairs instance.");

View File

@@ -31,7 +31,3 @@ void InclusiveCrosshairsDisable();
bool InclusiveCrosshairsIsEnabled();
void InclusiveCrosshairsSwitch();
void InclusiveCrosshairsApplySettings(InclusiveCrosshairsSettings& settings);
void InclusiveCrosshairsRequestUpdatePosition();
void InclusiveCrosshairsEnsureOn();
void InclusiveCrosshairsEnsureOff();
void InclusiveCrosshairsSetExternalControl(bool enabled);

View File

@@ -4,15 +4,6 @@
#include "trace.h"
#include "InclusiveCrosshairs.h"
#include "common/utils/color.h"
#include <atomic>
#include <thread>
#include <chrono>
#include <memory>
extern void InclusiveCrosshairsRequestUpdatePosition();
extern void InclusiveCrosshairsEnsureOn();
extern void InclusiveCrosshairsEnsureOff();
extern void InclusiveCrosshairsSetExternalControl(bool enabled);
// Non-Localizable strings
namespace
@@ -20,7 +11,6 @@ namespace
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_VALUE[] = L"value";
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
const wchar_t JSON_KEY_GLIDING_ACTIVATION_SHORTCUT[] = L"gliding_cursor_activation_shortcut";
const wchar_t JSON_KEY_CROSSHAIRS_COLOR[] = L"crosshairs_color";
const wchar_t JSON_KEY_CROSSHAIRS_OPACITY[] = L"crosshairs_opacity";
const wchar_t JSON_KEY_CROSSHAIRS_RADIUS[] = L"crosshairs_radius";
@@ -31,15 +21,13 @@ namespace
const wchar_t JSON_KEY_CROSSHAIRS_IS_FIXED_LENGTH_ENABLED[] = L"crosshairs_is_fixed_length_enabled";
const wchar_t JSON_KEY_CROSSHAIRS_FIXED_LENGTH[] = L"crosshairs_fixed_length";
const wchar_t JSON_KEY_AUTO_ACTIVATE[] = L"auto_activate";
const wchar_t JSON_KEY_GLIDE_TRAVEL_SPEED[] = L"gliding_travel_speed";
const wchar_t JSON_KEY_GLIDE_DELAY_SPEED[] = L"gliding_delay_speed";
}
extern "C" IMAGE_DOS_HEADER __ImageBase;
HMODULE m_hModule;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
m_hModule = hModule;
switch (ul_reason_for_call)
@@ -69,46 +57,8 @@ private:
// The PowerToy state.
bool m_enabled = false;
// Additional hotkeys (legacy API) to support multiple shortcuts
Hotkey m_activationHotkey{}; // Crosshairs toggle
Hotkey m_glidingHotkey{}; // Gliding cursor state machine
// Shared state for worker threads (decoupled from this lifetime)
struct State
{
std::atomic<bool> stopX{ false };
std::atomic<bool> stopY{ false };
// positions and speeds
int currentXPos{ 0 };
int currentYPos{ 0 };
int currentXSpeed{ 0 }; // pixels per base window
int currentYSpeed{ 0 }; // pixels per base window
int xPosSnapshot{ 0 }; // xPos captured at end of horizontal scan
// Fractional accumulators to spread movement across 10ms ticks
double xFraction{ 0.0 };
double yFraction{ 0.0 };
// Speeds represent pixels per 200ms (min 5, max 60 enforced by UI/settings)
int fastHSpeed{ 30 }; // pixels per base window
int slowHSpeed{ 5 }; // pixels per base window
int fastVSpeed{ 30 }; // pixels per base window
int slowVSpeed{ 5 }; // pixels per base window
};
std::shared_ptr<State> m_state;
// Worker threads
std::thread m_xThread;
std::thread m_yThread;
// Gliding cursor state machine
std::atomic<int> m_glideState{ 0 }; // 0..4 like the AHK script
// Timer configuration: 10ms tick, speeds are defined per 200ms base window
static constexpr int kTimerTickMs = 10;
static constexpr int kBaseSpeedTickMs = 200; // mapping period for configured pixel counts
// Hotkey to invoke the module
HotkeyEx m_hotkey;
// Mouse Pointer Crosshairs specific settings
InclusiveCrosshairsSettings m_inclusiveCrosshairsSettings;
@@ -118,17 +68,12 @@ public:
MousePointerCrosshairs()
{
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mousePointerCrosshairsLoggerName);
m_state = std::make_shared<State>();
init_settings();
};
// Destroy the powertoy and free memory
virtual void destroy() override
{
StopXTimer();
StopYTimer();
// Release shared state so worker threads (if any) exit when weak_ptr lock fails
m_state.reset();
delete this;
}
@@ -162,7 +107,9 @@ public:
// Signal from the Settings editor to call a custom action.
// This can be used to spawn more complex editors.
virtual void call_custom_action(const wchar_t* /*action*/) override {}
virtual void call_custom_action(const wchar_t* action) override
{
}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override
@@ -196,9 +143,6 @@ public:
{
m_enabled = false;
Trace::EnableMousePointerCrosshairs(false);
StopXTimer();
StopYTimer();
m_glideState = 0;
InclusiveCrosshairsDisable();
}
@@ -214,249 +158,15 @@ public:
return false;
}
// Legacy multi-hotkey support (like CropAndLock)
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
virtual std::optional<HotkeyEx> GetHotkeyEx() override
{
if (buffer && buffer_size >= 2)
{
buffer[0] = m_activationHotkey; // Crosshairs toggle
buffer[1] = m_glidingHotkey; // Gliding cursor toggle
}
return 2;
return m_hotkey;
}
virtual bool on_hotkey(size_t hotkeyId) override
virtual void OnHotkeyEx() override
{
if (!m_enabled)
{
return false;
}
if (hotkeyId == 0)
{
InclusiveCrosshairsSwitch();
return true;
}
if (hotkeyId == 1)
{
HandleGlidingHotkey();
return true;
}
return false;
InclusiveCrosshairsSwitch();
}
private:
static void LeftClick()
{
INPUT inputs[2]{};
inputs[0].type = INPUT_MOUSE;
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
inputs[1].type = INPUT_MOUSE;
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
SendInput(2, inputs, sizeof(INPUT));
}
// Stateless helpers operating on shared State
static void PositionCursorX(const std::shared_ptr<State>& s)
{
int screenW = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
s->currentYPos = screenH / 2;
// Distribute movement over 10ms ticks to match pixels-per-base-window speeds
const double perTick = (static_cast<double>(s->currentXSpeed) * kTimerTickMs) / static_cast<double>(kBaseSpeedTickMs);
s->xFraction += perTick;
int step = static_cast<int>(s->xFraction);
if (step > 0)
{
s->xFraction -= step;
s->currentXPos += step;
}
s->xPosSnapshot = s->currentXPos;
if (s->currentXPos >= screenW)
{
s->currentXPos = 0;
s->currentXSpeed = s->fastHSpeed;
s->xPosSnapshot = 0;
s->xFraction = 0.0; // reset fractional remainder on wrap
}
SetCursorPos(s->currentXPos, s->currentYPos);
// Ensure overlay crosshairs follow immediately
InclusiveCrosshairsRequestUpdatePosition();
}
static void PositionCursorY(const std::shared_ptr<State>& s)
{
int screenH = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Keep X at snapshot
// Use s->xPosSnapshot captured during X pass
// Distribute movement over 10ms ticks to match pixels-per-base-window speeds
const double perTick = (static_cast<double>(s->currentYSpeed) * kTimerTickMs) / static_cast<double>(kBaseSpeedTickMs);
s->yFraction += perTick;
int step = static_cast<int>(s->yFraction);
if (step > 0)
{
s->yFraction -= step;
s->currentYPos += step;
}
if (s->currentYPos >= screenH)
{
s->currentYPos = 0;
s->currentYSpeed = s->fastVSpeed;
s->yFraction = 0.0; // reset fractional remainder on wrap
}
SetCursorPos(s->xPosSnapshot, s->currentYPos);
// Ensure overlay crosshairs follow immediately
InclusiveCrosshairsRequestUpdatePosition();
}
void StartXTimer()
{
auto s = m_state;
if (!s)
{
return;
}
s->stopX = false;
std::weak_ptr<State> wp = s;
m_xThread = std::thread([wp]() {
while (true)
{
auto sp = wp.lock();
if (!sp || sp->stopX.load())
{
break;
}
PositionCursorX(sp);
std::this_thread::sleep_for(std::chrono::milliseconds(kTimerTickMs));
}
});
}
void StopXTimer()
{
auto s = m_state;
if (s)
{
s->stopX = true;
}
if (m_xThread.joinable())
{
m_xThread.join();
}
}
void StartYTimer()
{
auto s = m_state;
if (!s)
{
return;
}
s->stopY = false;
std::weak_ptr<State> wp = s;
m_yThread = std::thread([wp]() {
while (true)
{
auto sp = wp.lock();
if (!sp || sp->stopY.load())
{
break;
}
PositionCursorY(sp);
std::this_thread::sleep_for(std::chrono::milliseconds(kTimerTickMs));
}
});
}
void StopYTimer()
{
auto s = m_state;
if (s)
{
s->stopY = true;
}
if (m_yThread.joinable())
{
m_yThread.join();
}
}
void HandleGlidingHotkey()
{
auto s = m_state;
if (!s)
{
return;
}
// Simulate the AHK state machine
int state = m_glideState.load();
switch (state)
{
case 0:
{
// Ensure crosshairs on (do not toggle off if already on)
InclusiveCrosshairsEnsureOn();
// Disable internal mouse hook so we control position updates explicitly
InclusiveCrosshairsSetExternalControl(true);
s->currentXPos = 0;
s->currentXSpeed = s->fastHSpeed;
s->xFraction = 0.0;
s->yFraction = 0.0;
int y = GetSystemMetrics(SM_CYVIRTUALSCREEN) / 2;
SetCursorPos(0, y);
InclusiveCrosshairsRequestUpdatePosition();
m_glideState = 1;
StartXTimer();
break;
}
case 1:
{
// Slow horizontal
s->currentXSpeed = s->slowHSpeed;
m_glideState = 2;
break;
}
case 2:
{
// Stop horizontal, start vertical (fast)
StopXTimer();
s->currentYSpeed = s->fastVSpeed;
s->currentYPos = 0;
s->yFraction = 0.0;
SetCursorPos(s->xPosSnapshot, s->currentYPos);
InclusiveCrosshairsRequestUpdatePosition();
m_glideState = 3;
StartYTimer();
break;
}
case 3:
{
// Slow vertical
s->currentYSpeed = s->slowVSpeed;
m_glideState = 4;
break;
}
case 4:
default:
{
// Stop vertical, click, turn crosshairs off, re-enable internal tracking, reset state
StopYTimer();
m_glideState = 0;
LeftClick();
InclusiveCrosshairsEnsureOff();
InclusiveCrosshairsSetExternalControl(false);
s->xFraction = 0.0;
s->yFraction = 0.0;
break;
}
}
}
// Load the settings file.
void init_settings()
{
@@ -482,44 +192,37 @@ private:
{
try
{
// Parse primary activation HotKey (for centralized hook)
// Parse HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_hotkey = HotkeyEx();
if (hotkey.win_pressed())
{
m_hotkey.modifiersMask |= MOD_WIN;
}
// Map to legacy Hotkey for multi-hotkey API
m_activationHotkey.win = hotkey.win_pressed();
m_activationHotkey.ctrl = hotkey.ctrl_pressed();
m_activationHotkey.shift = hotkey.shift_pressed();
m_activationHotkey.alt = hotkey.alt_pressed();
m_activationHotkey.key = static_cast<unsigned char>(hotkey.get_code());
if (hotkey.ctrl_pressed())
{
m_hotkey.modifiersMask |= MOD_CONTROL;
}
if (hotkey.shift_pressed())
{
m_hotkey.modifiersMask |= MOD_SHIFT;
}
if (hotkey.alt_pressed())
{
m_hotkey.modifiersMask |= MOD_ALT;
}
m_hotkey.vkCode = hotkey.get_code();
}
catch (...)
{
Logger::warn("Failed to initialize Mouse Pointer Crosshairs activation shortcut");
}
try
{
// Parse Gliding Cursor HotKey
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDING_ACTIVATION_SHORTCUT);
auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
m_glidingHotkey.win = hotkey.win_pressed();
m_glidingHotkey.ctrl = hotkey.ctrl_pressed();
m_glidingHotkey.shift = hotkey.shift_pressed();
m_glidingHotkey.alt = hotkey.alt_pressed();
m_glidingHotkey.key = static_cast<unsigned char>(hotkey.get_code());
}
catch (...)
{
// note that this is also defined in src\settings-ui\Settings.UI.Library\MousePointerCrosshairsProperties.cs, DefaultGlidingCursorActivationShortcut
// both need to be kept in sync!
Logger::warn("Failed to initialize Gliding Cursor activation shortcut. Using default Win+Alt+.");
m_glidingHotkey.win = true;
m_glidingHotkey.alt = true;
m_glidingHotkey.ctrl = false;
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
}
try
{
// Parse Opacity
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_OPACITY);
@@ -569,6 +272,7 @@ private:
{
throw std::runtime_error("Invalid Radius value");
}
}
catch (...)
{
@@ -587,6 +291,7 @@ private:
{
throw std::runtime_error("Invalid Thickness value");
}
}
catch (...)
{
@@ -615,7 +320,7 @@ private:
{
// Parse border size
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_CROSSHAIRS_BORDER_SIZE);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
int value = static_cast <int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 0)
{
inclusiveCrosshairsSettings.crosshairsBorderSize = value;
@@ -678,86 +383,20 @@ private:
{
Logger::warn("Failed to initialize auto activate from settings. Will use default value");
}
try
{
// Parse Travel speed (fast speed mapping)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDE_TRAVEL_SPEED);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 5 && value <= 60)
{
m_state->fastHSpeed = value;
m_state->fastVSpeed = value;
}
else if (value < 5)
{
m_state->fastHSpeed = 5; m_state->fastVSpeed = 5;
}
else
{
m_state->fastHSpeed = 60; m_state->fastVSpeed = 60;
}
}
catch (...)
{
Logger::warn("Failed to initialize gliding travel speed from settings. Using default 25.");
if (m_state)
{
m_state->fastHSpeed = 25;
m_state->fastVSpeed = 25;
}
}
try
{
// Parse Delay speed (slow speed mapping)
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_GLIDE_DELAY_SPEED);
int value = static_cast<int>(jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE));
if (value >= 5 && value <= 60)
{
m_state->slowHSpeed = value;
m_state->slowVSpeed = value;
}
else if (value < 5)
{
m_state->slowHSpeed = 5; m_state->slowVSpeed = 5;
}
else
{
m_state->slowHSpeed = 60; m_state->slowVSpeed = 60;
}
}
catch (...)
{
Logger::warn("Failed to initialize gliding delay speed from settings. Using default 5.");
if (m_state)
{
m_state->slowHSpeed = 5;
m_state->slowVSpeed = 5;
}
}
}
else
{
Logger::info("Mouse Pointer Crosshairs settings are empty");
}
if (m_activationHotkey.key == 0)
if (!m_hotkey.modifiersMask)
{
m_activationHotkey.win = true;
m_activationHotkey.alt = true;
m_activationHotkey.ctrl = false;
m_activationHotkey.shift = false;
m_activationHotkey.key = 'P';
}
if (m_glidingHotkey.key == 0)
{
m_glidingHotkey.win = true;
m_glidingHotkey.alt = true;
m_glidingHotkey.ctrl = false;
m_glidingHotkey.shift = false;
m_glidingHotkey.key = VK_OEM_PERIOD;
Logger::info("Mouse Pointer Crosshairs is going to use default shortcut");
m_hotkey.modifiersMask = MOD_WIN | MOD_ALT;
m_hotkey.vkCode = 0x50; // P key
}
m_inclusiveCrosshairsSettings = inclusiveCrosshairsSettings;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

View File

@@ -556,61 +556,6 @@ public:
return m_enabled;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
constexpr size_t num_hotkeys = 4; // We have 4 hotkeys
if (hotkeys && buffer_size >= num_hotkeys)
{
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(MODULE_NAME);
// Cache the raw JSON object to avoid multiple parsing
json::JsonObject root_json = values.get_raw_json();
json::JsonObject properties_json = root_json.GetNamedObject(L"properties", json::JsonObject{});
size_t hotkey_index = 0;
// Helper lambda to extract hotkey from JSON properties
auto extract_hotkey = [&](const wchar_t* property_name) -> Hotkey {
if (properties_json.HasKey(property_name))
{
try
{
json::JsonObject hotkey_json = properties_json.GetNamedObject(property_name);
// Extract hotkey properties directly from JSON
bool win = hotkey_json.GetNamedBoolean(L"win", false);
bool ctrl = hotkey_json.GetNamedBoolean(L"ctrl", false);
bool alt = hotkey_json.GetNamedBoolean(L"alt", false);
bool shift = hotkey_json.GetNamedBoolean(L"shift", false);
unsigned char key = static_cast<unsigned char>(
hotkey_json.GetNamedNumber(L"code", 0));
return { win, ctrl, shift, alt, key };
}
catch (...)
{
// If parsing individual hotkey fails, use defaults
return { false, false, false, false, 0 };
}
}
else
{
// Property doesn't exist, use defaults
return { false, false, false, false, 0 };
}
};
// Extract all hotkeys using the optimized helper
hotkeys[hotkey_index++] = extract_hotkey(L"ToggleEasyMouseShortcut"); // [0] Toggle Easy Mouse
hotkeys[hotkey_index++] = extract_hotkey(L"LockMachineShortcut"); // [1] Lock Machine
hotkeys[hotkey_index++] = extract_hotkey(L"Switch2AllPCShortcut"); // [2] Switch to All PCs
hotkeys[hotkey_index++] = extract_hotkey(L"ReconnectShortcut"); // [3] Reconnect
}
return num_hotkeys;
}
void launch_add_firewall_process()
{
Logger::trace(L"Starting Process to add firewall rule");

View File

@@ -123,7 +123,6 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv</Command>
<ClInclude Include="settings.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="new_utilities.h" />
<ClInclude Include="RuntimeRegistration.h" />
<ClInclude Include="resource.base.h" />
<ClInclude Include="template_folder.h" />
<ClInclude Include="pch.h" />

View File

@@ -84,9 +84,6 @@
<ClInclude Include="helpers_variables.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RuntimeRegistration.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -1,36 +0,0 @@
// Header-only runtime registration for New+ Win10 context menu.
#pragma once
#include <windows.h>
#include <string>
#include <common/utils/shell_ext_registration.h>
// Provided by dll_main.cpp
extern HMODULE module_instance_handle;
namespace NewPlusRuntimeRegistration
{
namespace {
inline runtime_shell_ext::Spec BuildSpec()
{
runtime_shell_ext::Spec spec;
spec.clsid = L"{FF90D477-E32A-4BE8-8CC5-A502A97F5401}";
spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\NewPlus";
spec.sentinelValue = L"ContextMenuRegisteredWin10";
spec.dllFileCandidates = { L"PowerToys.NewPlus.ShellExtension.win10.dll" };
spec.contextMenuHandlerKeyPaths = { L"Software\\Classes\\Directory\\background\\ShellEx\\ContextMenuHandlers\\NewPlusShellExtensionWin10" };
spec.friendlyName = L"NewPlus Shell Extension Win10";
return spec;
}
}
inline bool EnsureRegisteredWin10()
{
return runtime_shell_ext::EnsureRegistered(BuildSpec(), module_instance_handle);
}
inline void Unregister()
{
runtime_shell_ext::Unregister(BuildSpec());
}
}

View File

@@ -16,7 +16,6 @@
#include "trace.h"
#include "new_utilities.h"
#include "Generated Files/resource.h"
#include "RuntimeRegistration.h"
// Note: Settings are managed via Settings and UI Settings
class NewModule : public PowertoyModuleIface
@@ -94,16 +93,8 @@ public:
// Log telemetry
Trace::EventToggleOnOff(true);
if (package::IsWin11OrGreater())
{
newplus::utilities::register_msix_package();
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
#endif
}
newplus::utilities::register_msix_package();
powertoy_new_enabled = true;
}
@@ -150,13 +141,6 @@ private:
{
Trace::EventToggleOnOff(false);
}
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::Unregister();
Logger::info(L"New+ context menu unregistered (Win10)");
#endif
}
powertoy_new_enabled = false;
}

View File

@@ -189,7 +189,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
else
{
return new SeparatorViewModel() as IContextItemViewModel;
return new SeparatorContextItemViewModel() as IContextItemViewModel;
}
})
.ToList();
@@ -350,7 +350,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
}
else
{
return new SeparatorViewModel() as IContextItemViewModel;
return new SeparatorContextItemViewModel() as IContextItemViewModel;
}
})
.ToList();

View File

@@ -29,15 +29,6 @@ public partial class CommandViewModel : ExtensionObjectViewModel
public IconInfoViewModel Icon { get; private set; }
// UNDER NO CIRCUMSTANCES MAY SOMEONE WRITE TO THIS DICTIONARY.
// This is our copy of the data from the extension.
// Adding values to it does not add to the extension.
// Modifying it will not modify the extension
// (except it might, if the dictionary was passed by ref)
private Dictionary<string, ExtensionObject<object>>? _properties;
public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly();
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
: base(pageContext)
{
@@ -89,11 +80,6 @@ public partial class CommandViewModel : ExtensionObjectViewModel
UpdateProperty(nameof(Icon));
}
if (model is IExtendedAttributesProvider command2)
{
UpdatePropertiesFromExtension(command2);
}
model.PropChanged += Model_PropChanged;
}
@@ -144,26 +130,4 @@ public partial class CommandViewModel : ExtensionObjectViewModel
model.PropChanged -= Model_PropChanged;
}
}
private void UpdatePropertiesFromExtension(IExtendedAttributesProvider? model)
{
var propertiesFromExtension = model?.GetProperties();
if (propertiesFromExtension == null)
{
_properties = null;
return;
}
_properties = [];
// COPY the properties into us.
// The IDictionary that was passed to us may be marshalled by-ref or by-value, we _don't know_.
//
// If it's by-ref, the values are arbitrary objects that are out-of-proc.
// If it's bu-value, then everything is in-proc, and we can't mutate the data.
foreach (var property in propertiesFromExtension)
{
_properties.Add(property.Key, new(property.Value));
}
}
}

View File

@@ -119,7 +119,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
}
else
{
return new SeparatorViewModel();
return new SeparatorContextItemViewModel();
}
})
.ToList();
@@ -178,7 +178,7 @@ public abstract partial class ContentPageViewModel : PageViewModel, ICommandBarC
}
else
{
return new SeparatorViewModel();
return new SeparatorContextItemViewModel();
}
})
.ToList();

View File

@@ -1,57 +0,0 @@
// 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.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class FilterItemViewModel : ExtensionObjectViewModel, IFilterItemViewModel
{
private ExtensionObject<IFilter> _model;
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public IconInfoViewModel Icon { get; set; } = new(null);
internal InitializedState Initialized { get; private set; } = InitializedState.Uninitialized;
protected bool IsInitialized => IsInErrorState || Initialized.HasFlag(InitializedState.Initialized);
public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error);
public FilterItemViewModel(IFilter filter, WeakReference<IPageContext> context)
: base(context)
{
_model = new(filter);
}
public override void InitializeProperties()
{
if (IsInitialized)
{
return;
}
var filter = _model.Unsafe;
if (filter == null)
{
return; // throw?
}
Id = filter.Id;
Name = filter.Name;
Icon = new(filter.Icon);
if (Icon is not null)
{
Icon.InitializeProperties();
}
UpdateProperty(nameof(Id));
UpdateProperty(nameof(Name));
UpdateProperty(nameof(Icon));
}
}

View File

@@ -1,81 +0,0 @@
// 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 CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class FiltersViewModel : ExtensionObjectViewModel
{
private readonly ExtensionObject<IFilters> _filtersModel = new(null);
[ObservableProperty]
public partial string CurrentFilterId { get; set; } = string.Empty;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShouldShowFilters))]
public partial IFilterItemViewModel[] Filters { get; set; } = [];
public bool ShouldShowFilters => Filters.Length > 0;
public FiltersViewModel(ExtensionObject<IFilters> filters, WeakReference<IPageContext> context)
: base(context)
{
_filtersModel = filters;
}
public override void InitializeProperties()
{
try
{
if (_filtersModel.Unsafe is not null)
{
var filters = _filtersModel.Unsafe.GetFilters();
Filters = filters.Select<IFilterItem, IFilterItemViewModel>(filter =>
{
var filterItem = filter as IFilter;
if (filterItem != null)
{
var filterVM = new FilterItemViewModel(filterItem!, PageContext);
filterVM.InitializeProperties();
return filterVM;
}
else
{
return new SeparatorViewModel();
}
}).ToArray();
CurrentFilterId = _filtersModel.Unsafe.CurrentFilterId;
return;
}
}
catch (Exception ex)
{
ShowException(ex, _filtersModel.Unsafe?.GetType().Name);
}
Filters = [];
CurrentFilterId = string.Empty;
}
public override void SafeCleanup()
{
base.SafeCleanup();
foreach (var filter in Filters)
{
if (filter is FilterItemViewModel filterVM)
{
filterVM.SafeCleanup();
}
}
Filters = [];
}
}

View File

@@ -2,7 +2,12 @@
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Core.ViewModels;

View File

@@ -1,9 +0,0 @@
// 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.
namespace Microsoft.CmdPal.Core.ViewModels;
public interface IFilterItemViewModel
{
}

View File

@@ -27,8 +27,6 @@ public partial class IconDataViewModel : ObservableObject, IIconData
IRandomAccessStreamReference? IIconData.Data => Data.Unsafe;
public string? FontFamily { get; private set; }
public IconDataViewModel(IIconData? icon)
{
_model = new(icon);
@@ -45,22 +43,5 @@ public partial class IconDataViewModel : ObservableObject, IIconData
Icon = model.Icon;
Data = new(model.Data);
if (model is IExtendedAttributesProvider icon2)
{
var props = icon2.GetProperties();
// From Raymond Chen:
// Make sure you don't try do do something like
// icon2.GetProperties().TryGetValue("awesomeKey", out var awesomeValue);
// icon2.GetProperties().TryGetValue("slackerKey", out var slackerValue);
// because each call to GetProperties() is a cross process hop, and if you
// marshal-by-value the property set, then you don't want to throw it away and
// re-marshal it for every property. MAKE SURE YOU CACHE IT.
if (props?.TryGetValue("FontFamily", out var family) ?? false)
{
FontFamily = family as string;
}
}
}
}

View File

@@ -26,8 +26,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
[ObservableProperty]
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
public FiltersViewModel? Filters { get; set; }
private ObservableCollection<ListItemViewModel> Items { get; set; } = [];
private readonly ExtensionObject<IListPage> _model;
@@ -88,7 +86,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
// TODO: Does this need to hop to a _different_ thread, so that we don't block the extension while we're fetching?
private void Model_ItemsChanged(object sender, IItemsChangedEventArgs args) => FetchItems();
protected override void OnSearchTextBoxUpdated(string searchTextBox)
protected override void OnFilterUpdated(string filter)
{
//// TODO: Just temp testing, need to think about where we want to filter, as AdvancedCollectionView in View could be done, but then grouping need CollectionViewSource, maybe we do grouping in view
//// and manage filtering below, but we should be smarter about this and understand caching and other requirements...
@@ -106,7 +104,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
if (_model.Unsafe is IDynamicListPage dynamic)
{
dynamic.SearchText = searchTextBox;
dynamic.SearchText = filter;
}
}
catch (Exception ex)
@@ -129,26 +127,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
public void UpdateCurrentFilter(string currentFilterId)
{
// We're getting called on the UI thread.
// Hop off to a BG thread to update the extension.
_ = Task.Run(() =>
{
try
{
if (_model.Unsafe is IListPage listPage)
{
listPage.Filters?.CurrentFilterId = currentFilterId;
}
}
catch (Exception ex)
{
ShowException(ex, _model?.Unsafe?.Name);
}
});
}
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
private void FetchItems()
{
@@ -163,9 +141,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
// see 9806fe5d8 for the last commit that had this with sections
_isFetching = true;
// Collect all the items into new viewmodels
Collection<ListItemViewModel> newViewModels = [];
try
{
// Check for cancellation before starting expensive operations
@@ -176,6 +151,9 @@ public partial class ListViewModel : PageViewModel, IDisposable
// Check for cancellation after getting items from extension
cancellationToken.ThrowIfCancellationRequested();
// Collect all the items into new viewmodels
Collection<ListItemViewModel> newViewModels = [];
// TODO we can probably further optimize this by also keeping a
// HashSet of every ExtensionObject we currently have, and only
// building new viewmodels for the ones we haven't already built.
@@ -209,22 +187,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
// Check for cancellation before updating the list
cancellationToken.ThrowIfCancellationRequested();
List<ListItemViewModel> removedItems = [];
lock (_listLock)
{
// Now that we have new ViewModels for everything from the
// extension, smartly update our list of VMs
ListHelpers.InPlaceUpdateList(Items, newViewModels, out removedItems);
// DO NOT ThrowIfCancellationRequested AFTER THIS! If you do,
// you'll clean up list items that we've now transferred into
// .Items
}
// If we removed items, we need to clean them up, to remove our event handlers
foreach (var removedItem in removedItems)
{
removedItem.SafeCleanup();
ListHelpers.InPlaceUpdateList(Items, newViewModels);
}
// TODO: Iterate over everything in Items, and prune items from the
@@ -233,15 +200,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
catch (OperationCanceledException)
{
// Cancellation is expected, don't treat as error
// However, if we were cancelled, we didn't actually add these items to
// our Items list. Before we release them to the GC, make sure we clean
// them up
foreach (var vm in newViewModels)
{
vm.SafeCleanup();
}
return;
}
catch (Exception ex)
@@ -327,7 +285,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
/// Apply our current filter text to the list of items, and update
/// FilteredItems to match the results.
/// </summary>
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, SearchTextBox));
private void ApplyFilterUnderLock() => ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(Items, Filter));
/// <summary>
/// Helper to generate a weighting for a given list item, based on title,
@@ -529,10 +487,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent.SlowInitializeProperties();
Filters = new(new(model.Filters), PageContext);
Filters.InitializeProperties();
UpdateProperty(nameof(Filters));
FetchItems();
model.ItemsChanged += Model_ItemsChanged;
}
@@ -604,10 +558,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent.SlowInitializeProperties();
break;
case nameof(Filters):
Filters = new(new(model.Filters), PageContext);
Filters.InitializeProperties();
break;
case nameof(IsLoading):
UpdateEmptyContent();
break;
@@ -671,8 +621,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
FilteredItems.Clear();
}
Filters?.SafeCleanup();
var model = _model.Unsafe;
if (model is not null)
{

View File

@@ -32,7 +32,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
// This is set from the SearchBar
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowSuggestion))]
public partial string SearchTextBox { get; set; } = string.Empty;
public partial string Filter { get; set; } = string.Empty;
[ObservableProperty]
public virtual partial string PlaceholderText { get; private set; } = "Type here to search...";
@@ -41,7 +41,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
[NotifyPropertyChangedFor(nameof(ShowSuggestion))]
public virtual partial string TextToSuggest { get; protected set; } = string.Empty;
public bool ShowSuggestion => !string.IsNullOrEmpty(TextToSuggest) && TextToSuggest != SearchTextBox;
public bool ShowSuggestion => !string.IsNullOrEmpty(TextToSuggest) && TextToSuggest != Filter;
[ObservableProperty]
public partial AppExtensionHost ExtensionHost { get; private set; }
@@ -167,9 +167,9 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
}
}
partial void OnSearchTextBoxChanged(string oldValue, string newValue) => OnSearchTextBoxUpdated(newValue);
partial void OnFilterChanged(string oldValue, string newValue) => OnFilterUpdated(newValue);
protected virtual void OnSearchTextBoxUpdated(string searchTextBox)
protected virtual void OnFilterUpdated(string filter)
{
// The base page has no notion of data, so we do nothing here...
// subclasses should override.

View File

@@ -9,10 +9,6 @@ using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class SeparatorViewModel() :
IContextItemViewModel,
IFilterItemViewModel,
ISeparatorContextItem,
ISeparatorFilterItem
public partial class SeparatorContextItemViewModel() : IContextItemViewModel, ISeparatorContextItem
{
}

View File

@@ -97,27 +97,14 @@ public partial class AliasManager : ObservableObject
}
}
// Look for the old alias, and remove it
List<CommandAlias> toRemove = [];
foreach (var kv in _aliases)
{
// Look for the old aliases for the command, and remove it
if (kv.Value.CommandId == commandId)
{
toRemove.Add(kv.Value);
}
// Look for the alias belonging to another command, and remove it
if (newAlias is not null && kv.Value.Alias == newAlias.Alias)
{
toRemove.Add(kv.Value);
// Remove alias from other TopLevelViewModels it may be assigned to
var topLevelCommand = _topLevelCommandManager.LookupCommand(kv.Value.CommandId);
if (topLevelCommand is not null)
{
topLevelCommand.AliasText = string.Empty;
}
}
}
foreach (var alias in toRemove)

View File

@@ -153,11 +153,6 @@ public sealed class CommandProviderWrapper
// On a BG thread here
fallbacks = model.FallbackCommands();
if (model is ICommandProvider2 two)
{
UnsafePreCacheApiAdditions(two);
}
Id = model.Id;
DisplayName = model.DisplayName;
Icon = new(model.Icon);
@@ -208,19 +203,6 @@ public sealed class CommandProviderWrapper
}
}
private void UnsafePreCacheApiAdditions(ICommandProvider2 provider)
{
var apiExtensions = provider.GetApiExtensionStubs();
Logger.LogDebug($"Provider supports {apiExtensions.Length} extensions");
foreach (var a in apiExtensions)
{
if (a is IExtendedAttributesProvider command2)
{
Logger.LogDebug($"{ProviderId}: Found an IExtendedAttributesProvider");
}
}
}
public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid;
public override int GetHashCode() => _commandProvider.GetHashCode();

View File

@@ -2,7 +2,6 @@
// 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.Diagnostics.CodeAnalysis;
using System.Text.Json;
using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Templating;
@@ -97,40 +96,109 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
UpdateProperty(nameof(Card));
}
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AdaptiveOpenUrlAction))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AdaptiveSubmitAction))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AdaptiveExecuteAction))]
public void HandleSubmit(IAdaptiveActionElement action, JsonObject inputs)
{
if (action is AdaptiveOpenUrlAction openUrlAction)
{
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(openUrlAction.Url));
return;
}
// BODGY circa GH #40979
// Usually, you're supposed to try to cast the action to a specific
// type, and use those objects to get the data you need.
// However, there's something weird with AdaptiveCards and the way it
// works when we consume it when built in Release, with AOT (and
// trimming) enabled. Any sort of `action.As<IAdaptiveSubmitAction>()`
// or similar will throw a System.InvalidCastException.
//
// Instead we have this horror show.
//
// The `action.ToJson()` blob ACTUALLY CONTAINS THE `type` field, which
// we can use to determine what kind of action it is. Then we can parse
// the JSON manually based on the type.
var actionJson = action.ToJson();
if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
if (actionJson.TryGetValue("type", out var actionTypeValue))
{
// Get the data and inputs
var dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
var inputString = inputs.Stringify();
var actionTypeString = actionTypeValue.GetString();
Logger.LogTrace($"atString={actionTypeString}");
_ = Task.Run(() =>
var actionType = actionTypeString switch
{
try
{
var model = _formModel.Unsafe!;
if (model != null)
"Action.Submit" => ActionType.Submit,
"Action.Execute" => ActionType.Execute,
"Action.OpenUrl" => ActionType.OpenUrl,
_ => ActionType.Unsupported,
};
Logger.LogDebug($"{actionTypeString}->{actionType}");
switch (actionType)
{
case ActionType.OpenUrl:
{
var result = model.SubmitForm(inputString, dataString);
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
HandleOpenUrlAction(action, actionJson);
}
}
catch (Exception ex)
{
ShowException(ex);
}
});
break;
case ActionType.Submit:
case ActionType.Execute:
{
HandleSubmitAction(action, actionJson, inputs);
}
break;
default:
Logger.LogError($"{actionType} was an unexpected action `type`");
break;
}
}
else
{
Logger.LogError($"actionJson.TryGetValue(type) failed");
}
}
private void HandleOpenUrlAction(IAdaptiveActionElement action, JsonObject actionJson)
{
if (actionJson.TryGetValue("url", out var actionUrlValue))
{
var actionUrl = actionUrlValue.GetString() ?? string.Empty;
if (Uri.TryCreate(actionUrl, default(UriCreationOptions), out var uri))
{
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(uri));
}
else
{
Logger.LogError($"Failed to produce URI for {actionUrlValue}");
}
}
}
private void HandleSubmitAction(
IAdaptiveActionElement action,
JsonObject actionJson,
JsonObject inputs)
{
var dataString = string.Empty;
if (actionJson.TryGetValue("data", out var actionDataValue))
{
dataString = actionDataValue.Stringify() ?? string.Empty;
}
var inputString = inputs.Stringify();
_ = Task.Run(() =>
{
try
{
var model = _formModel.Unsafe!;
if (model != null)
{
var result = model.SubmitForm(inputString, dataString);
Logger.LogDebug($"SubmitForm() returned {result}");
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
}
}
catch (Exception ex)
{
ShowException(ex);
}
});
}
private static readonly string ErrorCardJson = """

View File

@@ -110,8 +110,6 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
get => Alias?.Alias ?? string.Empty;
set
{
var previousAlias = Alias?.Alias ?? string.Empty;
if (string.IsNullOrEmpty(value))
{
Alias = null;
@@ -128,13 +126,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
}
}
// Only call HandleChangeAlias if there was an actual change.
if (previousAlias != Alias?.Alias)
{
HandleChangeAlias();
OnPropertyChanged(nameof(AliasText));
OnPropertyChanged(nameof(IsDirectAlias));
}
HandleChangeAlias();
OnPropertyChanged(nameof(AliasText));
OnPropertyChanged(nameof(IsDirectAlias));
}
}

View File

@@ -41,18 +41,14 @@
<Style.Setters>
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="{ThemeResource DesktopAcrylicTransparentBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource DividerStrokeColorDefaultBrush}" />
</Style.Setters>
</Style>
<!-- Backdrop requires ShouldConstrainToRootBounds="False" -->
<Flyout
x:Name="ContextMenuFlyout"
FlyoutPresenterStyle="{StaticResource ContextMenuFlyoutStyle}"
Opened="ContextMenuFlyout_Opened"
ShouldConstrainToRootBounds="False"
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
ShouldConstrainToRootBounds="False">
<cpcontrols:ContextMenu x:Name="ContextControl" />
</Flyout>
@@ -165,7 +161,6 @@
x:Name="PrimaryButton"
Padding="6,4,4,4"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
AutomationProperties.AutomationId="PrimaryCommandButton"
AutomationProperties.Name="{x:Bind ViewModel.PrimaryCommand.Name, Mode=OneWay}"
Background="Transparent"
Click="PrimaryButton_Clicked"
@@ -185,7 +180,6 @@
x:Name="SecondaryButton"
Padding="6,4,4,4"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
AutomationProperties.AutomationId="SecondaryCommandButton"
AutomationProperties.Name="{x:Bind ViewModel.SecondaryCommand.Name, Mode=OneWay}"
Click="SecondaryButton_Clicked"
Style="{StaticResource SubtleButtonStyle}"
@@ -209,7 +203,6 @@
x:Name="MoreCommandsButton"
x:Uid="MoreCommandsButton"
Padding="6,4,4,4"
AutomationProperties.AutomationId="MoreContextMenuButton"
Click="MoreCommandsButton_Clicked"
Style="{StaticResource SubtleButtonStyle}"
ToolTipService.ToolTip="Ctrl+K"

View File

@@ -108,7 +108,7 @@
</DataTemplate>
<!-- Template for context item separators -->
<DataTemplate x:Key="SeparatorContextMenuViewModelTemplate" x:DataType="coreViewModels:SeparatorViewModel">
<DataTemplate x:Key="SeparatorContextMenuViewModelTemplate" x:DataType="coreViewModels:SeparatorContextItemViewModel">
<Rectangle
Height="1"
Margin="-16,-12,-12,-12"

View File

@@ -270,7 +270,7 @@ public sealed partial class ContextMenu : UserControl,
private bool IsSeparator(object item)
{
return item is SeparatorViewModel;
return item is SeparatorContextItemViewModel;
}
private void UpdateUiForStackChange()

View File

@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.CmdPal.UI.Controls.FiltersDropDown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="Transparent"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<cmdpalUI:FilterTemplateSelector
x:Key="FilterTemplateSelector"
Default="{StaticResource FilterItemViewModelTemplate}"
Separator="{StaticResource SeparatorViewModelTemplate}" />
<Style
x:Name="ComboBoxStyle"
BasedOn="{StaticResource DefaultComboBoxStyle}"
TargetType="ComboBox">
<Style.Setters>
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Margin" Value="0,0,12,0" />
<Setter Property="Padding" Value="16,4" />
</Style.Setters>
</Style>
<!-- Template for the filter items -->
<DataTemplate x:Key="FilterItemViewModelTemplate" x:DataType="coreViewModels:FilterItemViewModel">
<Grid AutomationProperties.Name="{x:Bind Name, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<cpcontrols:IconBox
Width="16"
Margin="4,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
SourceKey="{x:Bind Icon}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
<!-- Template for separators -->
<DataTemplate x:Key="SeparatorViewModelTemplate" x:DataType="coreViewModels:SeparatorViewModel">
<Rectangle
Height="1"
Margin="-16,-12,-12,-12"
Fill="{ThemeResource MenuFlyoutSeparatorThemeBrush}" />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<ComboBox
Name="FiltersComboBox"
x:Uid="FiltersComboBox"
VerticalAlignment="Center"
ItemTemplateSelector="{StaticResource FilterTemplateSelector}"
ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}"
PlaceholderText="Filters"
PreviewKeyDown="FiltersComboBox_PreviewKeyDown"
SelectionChanged="FiltersComboBox_SelectionChanged"
Style="{StaticResource ComboBoxStyle}"
Visibility="{x:Bind ViewModel.ShouldShowFilters, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<ComboBox.ItemContainerStyle>
<Style BasedOn="{StaticResource DefaultComboBoxItemStyle}" TargetType="ComboBoxItem">
<Setter Property="MinHeight" Value="0" />
<Setter Property="Padding" Value="12,8" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemContainerTransitions>
<TransitionCollection />
</ComboBox.ItemContainerTransitions>
</ComboBox>
</UserControl>

View File

@@ -1,189 +0,0 @@
// 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.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.UI.Views;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Windows.System;
namespace Microsoft.CmdPal.UI.Controls;
public sealed partial class FiltersDropDown : UserControl,
ICurrentPageAware
{
public PageViewModel? CurrentPageViewModel
{
get => (PageViewModel?)GetValue(CurrentPageViewModelProperty);
set => SetValue(CurrentPageViewModelProperty, value);
}
public static readonly DependencyProperty CurrentPageViewModelProperty =
DependencyProperty.Register(nameof(CurrentPageViewModel), typeof(PageViewModel), typeof(FiltersDropDown), new PropertyMetadata(null, OnCurrentPageViewModelChanged));
private static void OnCurrentPageViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var @this = (FiltersDropDown)d;
if (@this != null
&& e.OldValue is PageViewModel old)
{
old.PropertyChanged -= @this.Page_PropertyChanged;
}
// If this new page does not implement ListViewModel or if
// it doesn't contain Filters, we need to clear any filters
// that may have been set.
if (@this != null)
{
if (e.NewValue is ListViewModel listViewModel)
{
@this.ViewModel = listViewModel.Filters;
}
else
{
@this.ViewModel = null;
}
}
if (@this != null
&& e.NewValue is PageViewModel page)
{
page.PropertyChanged += @this.Page_PropertyChanged;
}
}
public FiltersViewModel? ViewModel
{
get => (FiltersViewModel?)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(nameof(ViewModel), typeof(FiltersViewModel), typeof(FiltersDropDown), new PropertyMetadata(null));
public FiltersDropDown()
{
this.InitializeComponent();
}
// Used to handle the case when a ListPage's `Filters` may have changed
private void Page_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var property = e.PropertyName;
if (CurrentPageViewModel is ListViewModel list)
{
if (property == nameof(ListViewModel.Filters))
{
ViewModel = list.Filters;
}
}
}
private void FiltersComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (CurrentPageViewModel is ListViewModel listViewModel &&
FiltersComboBox.SelectedItem is FilterItemViewModel filterItem)
{
listViewModel.UpdateCurrentFilter(filterItem.Id);
}
}
private void FiltersComboBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Up)
{
NavigateUp();
e.Handled = true;
}
else if (e.Key == VirtualKey.Down)
{
NavigateDown();
e.Handled = true;
}
}
private void NavigateUp()
{
var newIndex = FiltersComboBox.SelectedIndex;
if (FiltersComboBox.SelectedIndex > 0)
{
newIndex--;
while (
newIndex >= 0 &&
IsSeparator(FiltersComboBox.Items[newIndex]) &&
newIndex != FiltersComboBox.SelectedIndex)
{
newIndex--;
}
if (newIndex < 0)
{
newIndex = FiltersComboBox.Items.Count - 1;
while (
newIndex >= 0 &&
IsSeparator(FiltersComboBox.Items[newIndex]) &&
newIndex != FiltersComboBox.SelectedIndex)
{
newIndex--;
}
}
}
else
{
newIndex = FiltersComboBox.Items.Count - 1;
}
FiltersComboBox.SelectedIndex = newIndex;
}
private void NavigateDown()
{
var newIndex = FiltersComboBox.SelectedIndex;
if (FiltersComboBox.SelectedIndex == FiltersComboBox.Items.Count - 1)
{
newIndex = 0;
}
else
{
newIndex++;
while (
newIndex < FiltersComboBox.Items.Count &&
IsSeparator(FiltersComboBox.Items[newIndex]) &&
newIndex != FiltersComboBox.SelectedIndex)
{
newIndex++;
}
if (newIndex >= FiltersComboBox.Items.Count)
{
newIndex = 0;
while (
newIndex < FiltersComboBox.Items.Count &&
IsSeparator(FiltersComboBox.Items[newIndex]) &&
newIndex != FiltersComboBox.SelectedIndex)
{
newIndex++;
}
}
}
FiltersComboBox.SelectedIndex = newIndex;
}
private bool IsSeparator(object item)
{
return item is SeparatorViewModel;
}
}

View File

@@ -22,7 +22,6 @@
MinHeight="32"
VerticalAlignment="Stretch"
VerticalContentAlignment="Stretch"
AutomationProperties.AutomationId="MainSearchBox"
KeyDown="FilterBox_KeyDown"
PlaceholderText="{x:Bind CurrentPageViewModel.PlaceholderText, Converter={StaticResource PlaceholderTextConverter}, Mode=OneWay}"
PreviewKeyDown="FilterBox_PreviewKeyDown"

View File

@@ -62,7 +62,7 @@ public sealed partial class SearchBar : UserControl,
{
// TODO: In some cases we probably want commands to clear a filter
// somewhere in the process, so we need to figure out when that is.
@this.FilterBox.Text = page.SearchTextBox;
@this.FilterBox.Text = page.Filter;
@this.FilterBox.Select(@this.FilterBox.Text.Length, 0);
page.PropertyChanged += @this.Page_PropertyChanged;
@@ -87,7 +87,7 @@ public sealed partial class SearchBar : UserControl,
if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.SearchTextBox = string.Empty;
CurrentPageViewModel.Filter = string.Empty;
}
}));
}
@@ -145,7 +145,7 @@ public sealed partial class SearchBar : UserControl,
// hack TODO GH #245
if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
CurrentPageViewModel.Filter = FilterBox.Text;
}
}
@@ -156,7 +156,7 @@ public sealed partial class SearchBar : UserControl,
// hack TODO GH #245
if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
CurrentPageViewModel.Filter = FilterBox.Text;
}
}
}
@@ -320,7 +320,7 @@ public sealed partial class SearchBar : UserControl,
// Actually plumb Filtering to the view model
if (CurrentPageViewModel is not null)
{
CurrentPageViewModel.SearchTextBox = FilterBox.Text;
CurrentPageViewModel.Filter = FilterBox.Text;
}
}

View File

@@ -28,7 +28,7 @@ internal sealed partial class ContextItemTemplateSelector : DataTemplateSelector
{
li.IsEnabled = true;
if (item is SeparatorViewModel)
if (item is SeparatorContextItemViewModel)
{
li.IsEnabled = false;
li.AllowFocusWhenDisabled = false;

View File

@@ -1,36 +0,0 @@
// 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.CmdPal.Core.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI;
internal sealed partial class FilterTemplateSelector : DataTemplateSelector
{
public DataTemplate? Default { get; set; }
public DataTemplate? Separator { get; set; }
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{
DataTemplate? dataTemplate = Default;
if (dependencyObject is ComboBoxItem comboBoxItem)
{
comboBoxItem.IsEnabled = true;
if (item is SeparatorViewModel)
{
comboBoxItem.IsEnabled = false;
comboBoxItem.AllowFocusWhenDisabled = false;
comboBoxItem.AllowFocusOnInteraction = false;
dataTemplate = Separator;
}
}
return dataTemplate;
}
}

View File

@@ -25,7 +25,7 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
{
if (!string.IsNullOrEmpty(icon.Icon))
{
var source = IconPathConverter.IconSourceMUX(icon.Icon, false, icon.FontFamily);
var source = IconPathConverter.IconSourceMUX(icon.Icon, false);
return source;
}
else if (icon.Data is not null)

View File

@@ -92,7 +92,7 @@ internal sealed partial class TrayIconService
{
_popupMenu = PInvoke.CreatePopupMenu_SafeHandle();
PInvoke.InsertMenu(_popupMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, PInvoke.WM_USER + 1, RS_.GetString("TrayMenu_Settings"));
PInvoke.InsertMenu(_popupMenu, 1, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, PInvoke.WM_USER + 2, RS_.GetString("TrayMenu_Close"));
PInvoke.InsertMenu(_popupMenu, 1, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, PInvoke.WM_USER + 2, RS_.GetString("TrayMenu_Exit"));
}
}
else

View File

@@ -176,7 +176,6 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Back button -->
@@ -321,18 +320,6 @@
</TransitionCollection>
</Grid.Transitions>
</Grid>
<!-- Filter: wrapped in a grid to enable RepositionThemeTransitions -->
<Grid Grid.Column="2" HorizontalAlignment="Right">
<cpcontrols:FiltersDropDown
x:Name="FiltersDropDown"
HorizontalAlignment="Right"
CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
<Grid.Transitions>
<TransitionCollection>
<RepositionThemeTransition />
</TransitionCollection>
</Grid.Transitions>
</Grid>
</Grid>
<ProgressBar

View File

@@ -419,9 +419,8 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="TrayMenu_Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="TrayMenu_Close" xml:space="preserve">
<value>Close</value>
<comment>Close as a verb, as in Close the application</comment>
<data name="TrayMenu_Exit" xml:space="preserve">
<value>Exit</value>
</data>
<data name="Settings_ExtensionPage_Alias_ToggleSwitch.OnContent" xml:space="preserve">
<value>Direct</value>

View File

@@ -158,7 +158,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
// Return Value:
// - An IconElement with its IconSource set, if possible.
template<typename TIconSource>
TIconSource _getIconSource(const winrt::hstring& iconPath, bool monochrome, const winrt::hstring& fontFamily, const int targetSize)
TIconSource _getIconSource(const winrt::hstring& iconPath, bool monochrome, const int targetSize)
{
TIconSource iconSource{ nullptr };
@@ -187,11 +187,6 @@ namespace winrt::Microsoft::Terminal::UI::implementation
{
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
}
else if (!fontFamily.empty())
{
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ fontFamily });
}
else
{
// Note: you _do_ need to manually set the font here.
@@ -230,9 +225,9 @@ namespace winrt::Microsoft::Terminal::UI::implementation
// return _getIconSource<Windows::UI::Xaml::Controls::IconSource>(path, false);
// }
static Microsoft::UI::Xaml::Controls::IconSource _IconSourceMUX(const hstring& path, bool monochrome, const winrt::hstring& fontFamily, const int targetSize)
static Microsoft::UI::Xaml::Controls::IconSource _IconSourceMUX(const hstring& path, bool monochrome, const int targetSize)
{
return _getIconSource<Microsoft::UI::Xaml::Controls::IconSource>(path, monochrome, fontFamily, targetSize);
return _getIconSource<Microsoft::UI::Xaml::Controls::IconSource>(path, monochrome, targetSize);
}
static SoftwareBitmap _convertToSoftwareBitmap(HICON hicon,
@@ -348,14 +343,13 @@ namespace winrt::Microsoft::Terminal::UI::implementation
MUX::Controls::IconSource IconPathConverter::IconSourceMUX(const winrt::hstring& iconPath,
const bool monochrome,
const winrt::hstring& fontFamily,
const int targetSize)
{
std::wstring_view iconPathWithoutIndex;
const auto indexOpt = _getIconIndex(iconPath, iconPathWithoutIndex);
if (!indexOpt.has_value())
{
return _IconSourceMUX(iconPath, monochrome, fontFamily, targetSize);
return _IconSourceMUX(iconPath, monochrome, targetSize);
}
const auto bitmapSource = _getImageIconSourceForBinary(iconPathWithoutIndex, indexOpt.value(), targetSize);
@@ -375,7 +369,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
const auto indexOpt = _getIconIndex(iconPath, iconPathWithoutIndex);
if (!indexOpt.has_value())
{
auto source = IconSourceMUX(iconPath, false, L"", targetSize);
auto source = IconSourceMUX(iconPath, false, targetSize);
Microsoft::UI::Xaml::Controls::IconSourceElement icon;
icon.IconSource(source);
return icon;

View File

@@ -10,7 +10,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
//static Windows::UI::Xaml::Controls::IconElement IconWUX(const winrt::hstring& iconPath);
//static Windows::UI::Xaml::Controls::IconSource IconSourceWUX(const winrt::hstring& iconPath);
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const winrt::hstring& fontFamily, const int targetSize=24);
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const int targetSize=24);
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath);
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath, const int targetSize);

Some files were not shown because too many files have changed in this diff Show More