diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index f9389b8d91..1a85de1e06 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -7,6 +7,13 @@ body:
- type: markdown
attributes:
value: Please make sure to [search for existing issues](https://github.com/microsoft/PowerToys/issues) before filing a new one!
+- type: markdown
+ attributes:
+ value: |
+ We are aware of the following high-volume issues and are actively working on them. Please check if your issue is one of these before filing a new bug report:
+ * **PowerToys Run crash related to "Desktop composition is disabled"**: This may appear as `COMException: 0x80263001`. For more details, see issue [#31226](https://github.com/microsoft/PowerToys/issues/31226).
+ * **PowerToys Run crash with `COMException (0xD0000701)`**: For more details, see issue [#30769](https://github.com/microsoft/PowerToys/issues/30769).
+ * **PowerToys Run crash with a "Cyclic reference" error**: This `System.InvalidOperationException` is detailed in issue [#36451](https://github.com/microsoft/PowerToys/issues/36451).
- id: version
type: input
attributes:
diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt
index 756c450534..c655bb1b55 100644
--- a/.github/actions/spell-check/allow/code.txt
+++ b/.github/actions/spell-check/allow/code.txt
@@ -95,6 +95,7 @@ OTP
Yubi
Yubico
Perplexity
+Groq
svgl
# KEYS
@@ -321,3 +322,16 @@ REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST
+
+# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
+DDDD
+FFF
+HHH
+riday
+YYY
+
+# GitHub issue/PR commands
+azp
+feedbackhub
+needinfo
+reportbug
diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt
index c6f1225788..551c248923 100644
--- a/.github/actions/spell-check/excludes.txt
+++ b/.github/actions/spell-check/excludes.txt
@@ -105,6 +105,7 @@
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
^src/common/sysinternals/Eula/
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
+^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index cb1ddb8cc4..d4be728886 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -2,8 +2,8 @@ AAAAs
abcdefghjkmnpqrstuvxyz
abgr
ABlocked
-ABOUTBOX
ABORTIFHUNG
+ABOUTBOX
Abug
Acceleratorkeys
ACCEPTFILES
@@ -22,6 +22,7 @@ ADate
ADDSTRING
ADDUNDORECORD
ADifferent
+adjacents
ADMINS
adml
admx
@@ -34,6 +35,7 @@ AFX
AGGREGATABLE
AHK
AHybrid
+AIUI
akv
ALarger
ALIGNRIGHT
@@ -54,6 +56,7 @@ ANull
AOC
aocfnapldcnfbofgmbbllojgocaelgdd
AOklab
+aot
APARTMENTTHREADED
APeriod
apicontract
@@ -63,6 +66,7 @@ APIIs
Apm
APPBARDATA
APPEXECLINK
+appext
APPLICATIONFRAMEHOST
appmanifest
APPMODEL
@@ -94,10 +98,10 @@ ASSOCSTR
ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
-ATX
ATRIOX
+ATX
aumid
-Authenticode
+authenticode
AUTOBUDDY
AUTOCHECKBOX
AUTOHIDE
@@ -111,10 +115,13 @@ AValid
AWAYMODE
azcliversion
azman
+azureaiinference
+azureinference
+azureopenai
+backticks
bbwe
BCIE
bck
-backticks
BESTEFFORT
bezelled
bhid
@@ -135,7 +142,7 @@ bla
BLACKFRAME
BLENDFUNCTION
Blockquotes
-Blt
+blt
BLURBEHIND
BLURREGION
bmi
@@ -143,6 +150,7 @@ BNumber
BODGY
BOklab
BOOTSTRAPPERINSTALLFOLDER
+Bootstrappers
BOTTOMALIGN
boxmodel
BPBF
@@ -176,7 +184,10 @@ Canvascustomlayout
CAPTUREBLT
CAPTURECHANGED
CARETBLINKING
+Carlseibert
CAtl
+caub
+CBN
cch
CCHDEVICENAME
CCHFORMNAME
@@ -195,7 +206,6 @@ changecursor
CHILDACTIVATE
CHILDWINDOW
CHOOSEFONT
-CIBUILD
cidl
CIELCh
cim
@@ -211,6 +221,7 @@ clientside
CLIPBOARDUPDATE
CLIPCHILDREN
CLIPSIBLINGS
+CLITo
closesocket
clp
CLSCTX
@@ -231,6 +242,7 @@ CODENAME
codereview
Codespaces
Coen
+cognitiveservices
COINIT
colid
colorconv
@@ -245,12 +257,12 @@ cominterop
commandnotfound
commandpalette
compmgmt
+COMPOSITIONDISABLED
COMPOSITIONFULL
CONFIGW
CONFLICTINGMODIFIERKEY
CONFLICTINGMODIFIERSHORTCUT
CONOUT
-coreclr
constexpr
contentdialog
contentfiles
@@ -262,6 +274,7 @@ copiedcolorrepresentation
coppied
copyable
COPYPEN
+coreclr
COREWINDOW
Corpor
cotaskmem
@@ -270,9 +283,9 @@ countof
covrun
cpcontrols
cph
-cppcoreguidelines
cplusplus
CPower
+cppcoreguidelines
cpptools
cppvsdbg
cppwinrt
@@ -281,6 +294,7 @@ CREATEPROCESS
CREATESCHEDULEDTASK
CREATESTRUCT
CREATEWINDOWFAILED
+creativecommons
CRECT
CRH
critsec
@@ -300,6 +314,7 @@ CURRENTDIR
CURSORINFO
cursorpos
CURSORSHOWING
+CURSORWRAP
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
@@ -315,7 +330,6 @@ CYSCREEN
CYSMICON
CYVIRTUALSCREEN
Czechia
-cziplib
Dac
dacl
DAffine
@@ -340,7 +354,6 @@ debugbreak
decryptor
Dedup
Deduplicator
-Deeplink
DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR
DEFAULTFLAGS
@@ -387,7 +400,6 @@ DISPLAYFREQUENCY
displayname
DISPLAYORIENTATION
divyan
-djwsxzxb
Dlg
DLGFRAME
DLGMODALFRAME
@@ -398,6 +410,8 @@ DNLEN
DONOTROUND
DONTVALIDATEPATH
dotnet
+downsampled
+downsampling
downscale
DPICHANGED
DPIs
@@ -413,7 +427,7 @@ DROPFILES
DSTINVERT
DString
DSVG
-DTo
+dto
DUMMYUNIONNAME
dutil
DVASPECT
@@ -447,7 +461,6 @@ EDITKEYBOARD
EDITSHORTCUTS
EDITTEXT
EFile
-ekus
eku
emojis
ENABLEDELAYEDEXPANSION
@@ -516,6 +529,7 @@ FARPROC
fdx
fesf
FFFF
+Figma
FILEEXPLORER
fileexploreraddons
fileexplorerpreview
@@ -543,6 +557,7 @@ flac
flyouts
FMask
fmtid
+FNumber
FOF
FOFX
FOLDERID
@@ -553,6 +568,7 @@ FORCEMINIMIZE
FORMATDLGORD
formatetc
FORPARSING
+foundrylocal
FRAMECHANGED
frm
FROMTOUCH
@@ -571,15 +587,16 @@ gdi
gdiplus
GDIPVER
GDISCALED
+geolocator
GETCLIENTAREAANIMATION
GETCURSEL
GETDESKWALLPAPER
GETDLGCODE
GETDPISCALEDSIZE
getfilesiginforedist
-geolocator
GETHOTKEY
GETICON
+GETLBTEXT
GETMINMAXINFO
GETNONCLIENTMETRICS
GETPROPERTYSTOREFLAGS
@@ -587,16 +604,19 @@ GETSCREENSAVERRUNNING
GETSECKEY
GETSTICKYKEYS
GETTEXTLENGTH
-gitmodules
GHND
+gitmodules
GMEM
GNumber
+googleai
+googlegemini
gpedit
gpo
GPOCA
gpp
gpu
gradians
+grctlext
Gridcustomlayout
GSM
gtm
@@ -694,6 +714,7 @@ HTCLIENT
hthumbnail
HTOUCHINPUT
HTTRANSPARENT
+hutchinsoniana
HVal
HValue
Hvci
@@ -708,9 +729,9 @@ HWNDPARENT
HWNDPREV
hyjiacan
IAI
-icf
ICONERROR
ICONLOCATION
+icf
IDCANCEL
IDD
idk
@@ -724,9 +745,11 @@ ietf
IEXPLORE
IFACEMETHOD
IFACEMETHODIMP
+ifd
IGNOREUNKNOWN
IGo
iid
+IIM
Iindex
Ijwhost
ILD
@@ -750,7 +773,8 @@ INITDIALOG
INITGUID
INITTOLOGFONTSTRUCT
INLINEPREFIX
-Inlines
+inlines
+Inno
INPC
inproc
INPUTHARDWARE
@@ -767,7 +791,6 @@ INSTALLFOLDERTOPREVIOUSINSTALLFOLDER
INSTALLLOCATION
INSTALLMESSAGE
INSTALLPROPERTY
-installscopeperuser
INSTALLSTARTMENUSHORTCUT
INSTALLSTATE
Inste
@@ -780,6 +803,7 @@ invokecommand
ipcmanager
IPREVIEW
ipreviewhandlervisualssetfont
+IPTC
irow
irprops
isbi
@@ -823,15 +847,14 @@ keyvault
KILLFOCUS
killrunner
kmph
-ksa
kvp
Kybd
LARGEICON
lastcodeanalysissucceeded
LASTEXITCODE
LAYOUTRTL
-LCh
lbl
+LCh
lcid
LCIDTo
lcl
@@ -847,10 +870,10 @@ LExit
lhwnd
LIBFUZZER
LIBID
+lightswitch
LIMITSIZE
LIMITTEXT
lindex
-lightswitch
linkid
LINKOVERLAY
LINQTo
@@ -861,6 +884,7 @@ LLKH
llkhf
LMEM
LMENU
+lng
LOADFROMFILE
LOBYTE
localappdata
@@ -870,16 +894,14 @@ LOCATIONCHANGE
LOCKTYPE
LOGFONT
LOGFONTW
-logon
LOGMSG
+logon
LOGPIXELSX
LOGPIXELSY
-lng
lon
longdate
LONGNAMES
lowlevel
-lquadrant
LOWORD
lparam
LPBITMAPINFOHEADER
@@ -913,6 +935,7 @@ lpv
LPW
lpwcx
lpwndpl
+lquadrant
LReader
LRESULT
LSTATUS
@@ -939,6 +962,7 @@ MAKELONG
MAKELPARAM
makepri
MAKEWPARAM
+Malware
manifestdependency
MAPPEDTOSAMEKEY
MAPTOSAMESHORTCUT
@@ -961,6 +985,7 @@ MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
+metadatamatters
Metadatas
metafile
mfc
@@ -1007,9 +1032,6 @@ mousepointer
mouseutils
MOVESIZEEND
MOVESIZESTART
-muxx
-muxxc
-muxxh
MRM
MRT
mru
@@ -1029,6 +1051,7 @@ msiexec
MSIFASTINSTALL
MSIHANDLE
MSIRESTARTMANAGERCONTROL
+MSIs
msixbundle
MSIXCA
MSLLHOOKSTRUCT
@@ -1037,16 +1060,21 @@ msrc
msstore
msvcp
MT
+mstsc
MTND
MULTIPLEUSE
multizone
muxc
+muxx
+muxxc
+muxxh
MVPs
mvvm
MVVMTK
MWBEx
MYICON
NAMECHANGE
+Notavailable
namespaceanddescendants
nao
NCACTIVATE
@@ -1151,6 +1179,7 @@ nowarn
NOZORDER
NPH
npmjs
+NPU
NResize
NTAPI
ntdll
@@ -1158,8 +1187,8 @@ ntfs
NTSTATUS
NTSYSAPI
NULLCURSOR
-nullref
nullonfailure
+nullref
numberbox
nwc
ocr
@@ -1175,13 +1204,18 @@ oldpath
oldtheme
oleaut
OLECHAR
+ollama
onebranch
+onnx
OOBEUI
openas
opencode
OPENFILENAME
+openrdp
opensource
openxmlformats
+ollama
+onnx
OPTIMIZEFORINVOKE
ORPHANEDDIALOGTITLE
ORSCANS
@@ -1254,6 +1288,7 @@ pguid
phbm
phbmp
phicon
+Photoshop
phwnd
pici
pidl
@@ -1372,6 +1407,7 @@ QDC
qit
QITAB
QITABENT
+QNN
qoi
Quarternary
QUERYENDSESSION
@@ -1381,8 +1417,8 @@ quickaccent
QUNS
RAII
RAlt
-RAquadrant
randi
+RAquadrant
rasterization
Rasterize
RAWINPUTDEVICE
@@ -1392,6 +1428,8 @@ RAWPATH
rbhid
rclsid
RCZOOMIT
+remotedesktop
+rdp
RDW
READMODE
READOBJECTS
@@ -1409,9 +1447,7 @@ regfile
REGISTERCLASSFAILED
REGISTRYHEADER
REGISTRYPREVIEWEXT
-registryroot
regkey
-regroot
regsvr
REINSTALLMODE
releaseblog
@@ -1464,7 +1500,6 @@ rstringalpha
rstringdigit
rtb
RTLREADING
-rtm
runas
rundll
rungameid
@@ -1478,6 +1513,7 @@ sacl
safeprojectname
SAMEKEYPREVIOUSLYMAPPED
SAMESHORTCUTPREVIOUSLYMAPPED
+samsung
sancov
SAVEFAILED
scanled
@@ -1520,8 +1556,8 @@ SETRULES
SETSCREENSAVEACTIVE
SETSTICKYKEYS
SETTEXT
-settingscard
SETTINGCHANGE
+settingscard
SETTINGSCHANGED
settingsheader
settingshotkeycontrol
@@ -1596,6 +1632,7 @@ SKIPOWNPROCESS
sku
SLGP
sln
+slnx
SMALLICON
smartphone
smileys
@@ -1666,6 +1703,7 @@ stringtable
stringval
Strm
strret
+STRSAFE
stscanf
sttngs
Stubless
@@ -1677,7 +1715,6 @@ sublang
SUBMODULEUPDATE
subresource
Superbar
-suntimes
sut
svchost
SVGIn
@@ -1711,7 +1748,6 @@ SYSTEMMODAL
SYSTEMTIME
TARG
TARGETAPPHEADER
-TARGETDIR
targetentrypoint
TARGETHEADER
targetver
@@ -1741,10 +1777,10 @@ textextractor
TEXTINCLUDE
tfopen
tgz
+THEMECHANGED
themeresources
THH
THICKFRAME
-THEMECHANGED
THISCOMPONENT
throughs
TILEDWINDOW
@@ -1816,6 +1852,7 @@ UNCPRIORITY
UNDNAME
UNICODETEXT
unins
+Uninstaller
uninstalls
Uniquifies
unitconverter
@@ -1832,6 +1869,7 @@ UPDATENOW
UPDATEREGISTRY
updown
UPGRADINGPRODUCTCODE
+upscaling
Uptool
urld
Usb
@@ -1913,6 +1951,7 @@ Wca
WCE
wcex
WClass
+WCRAPI
wcsicmp
wcsncpy
wcsnicmp
@@ -1930,6 +1969,7 @@ wgpocpl
WHEREID
wic
wifi
+wikimedia
wikipedia
WIL
winapi
@@ -1943,6 +1983,7 @@ WINDOWPLACEMENT
WINDOWPOSCHANGED
WINDOWPOSCHANGING
WINDOWSBUILDNUMBER
+windowsml
windowssearch
windowssettings
WINDOWSTYLES
@@ -1958,6 +1999,7 @@ Winhook
WINL
winlogon
winmd
+winml
WINNT
winres
winrt
@@ -2020,18 +2062,21 @@ WTSAT
Wubi
WUX
Wwanpp
+xap
XAxis
XButton
xclip
xcopy
XDeployment
xdf
+XDimension
XDocument
XElement
xfd
XFile
XIncrement
XLoc
+xmp
XNamespace
Xoshiro
XPels
@@ -2048,6 +2093,7 @@ XVIRTUALSCREEN
xxxxxx
YAxis
ycombinator
+YDimension
YIncrement
yinle
yinyue
@@ -2055,8 +2101,8 @@ YPels
YPos
YResolution
YSpeed
-YTimer
YStr
+YTimer
YVIRTUALSCREEN
ZEROINIT
zonability
diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt
index cb303a10ad..181d728e84 100644
--- a/.github/actions/spell-check/patterns.txt
+++ b/.github/actions/spell-check/patterns.txt
@@ -253,7 +253,7 @@ _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
# hit-count: 1 file-count: 1
# Amazon
-\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)[^"'\s]+
+\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
# hit-count: 3 file-count: 3
# imgur
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 04f9cfaaeb..560b44b5a4 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -5,6 +5,7 @@
## PR Checklist
- [ ] Closes: #xxx
+
- [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 0db3dc6595..be07e7facc 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -21,6 +21,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
\ No newline at end of file
diff --git a/.github/workflows/manual-batch-issue-deduplication.yml b/.github/workflows/manual-batch-issue-deduplication.yml
index d02dc2e282..616e2244f0 100644
--- a/.github/workflows/manual-batch-issue-deduplication.yml
+++ b/.github/workflows/manual-batch-issue-deduplication.yml
@@ -27,7 +27,7 @@ jobs:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
- name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0
diff --git a/.github/workflows/msstore-submissions.yml b/.github/workflows/msstore-submissions.yml
index a44dafb199..36dfc4d785 100644
--- a/.github/workflows/msstore-submissions.yml
+++ b/.github/workflows/msstore-submissions.yml
@@ -40,7 +40,7 @@ jobs:
echo powerToysInstallerArm64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') >> $GITHUB_OUTPUT
- name: Setup .NET 9.0
- uses: actions/setup-dotnet@v4
+ uses: actions/setup-dotnet@v5
with:
dotnet-version: '9.0.x'
diff --git a/.gitignore b/.gitignore
index ed3f80a4ec..1318abc22c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -349,10 +349,7 @@ src/common/Telemetry/*.etl
/src/modules/powerrename/ui/RCb24464
# Generated installer file for Monaco source files.
-/installer/PowerToysSetup/MonacoSRC.wxs
-/installer/PowerToysSetup/DscResources.wxs
/installer/PowerToysSetupVNext/MonacoSRC.wxs
-/installer/PowerToysSetupVNext/DscResources.wxs
# MSBuildCache
/MSBuildCacheLogs/
diff --git a/.gitmodules b/.gitmodules
index 1601291341..f878c1a9e3 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,6 +4,3 @@
[submodule "deps/expected-lite"]
path = deps/expected-lite
url = https://github.com/martinmoene/expected-lite.git
-[submodule "deps/cziplib"]
- path = deps/cziplib
- url = https://github.com/kuba--/zip.git
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index d99fabf0eb..83289fa102 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -5,7 +5,6 @@
{
"MatchedPath": [
"*.resources.dll",
-
"WinUI3Apps\\Assets\\Settings\\Scripts\\*.ps1",
"PowerToys.ActionRunner.exe",
@@ -27,6 +26,7 @@
"PowerToys.GPOWrapper.dll",
"PowerToys.GPOWrapperProjection.dll",
"PowerToys.AllExperiments.dll",
+ "LanguageModelProvider.dll",
"Common.Search.dll",
@@ -181,6 +181,7 @@
"PowerToys.MousePointerCrosshairs.dll",
"PowerToys.MouseJumpUI.dll",
"PowerToys.MouseJumpUI.exe",
+ "PowerToys.CursorWrap.dll",
"PowerToys.MouseWithoutBorders.dll",
"PowerToys.MouseWithoutBorders.exe",
@@ -290,6 +291,7 @@
"Mono.Cecil.Rocks.dll",
"Newtonsoft.Json.dll",
"CommunityToolkit.WinUI.Controls.TitleBar.dll",
+ "CommunityToolkit.WinUI.Controls.OpacityMaskView.dll",
"NLog.dll",
"HtmlAgilityPack.dll",
@@ -346,6 +348,8 @@
"Testably.Abstractions.FileSystem.Interface.dll",
"WinUI3Apps\\Testably.Abstractions.FileSystem.Interface.dll",
"ColorCode.Core.dll",
+ "Microsoft.SemanticKernel.Connectors.Ollama.dll",
+ "OllamaSharp.dll",
"UnitsNet.dll",
"UtfUnknown.dll",
diff --git a/.pipelines/ESRPSigning_installer.json b/.pipelines/ESRPSigning_installer.json
deleted file mode 100644
index c9e505d3a2..0000000000
--- a/.pipelines/ESRPSigning_installer.json
+++ /dev/null
@@ -1,53 +0,0 @@
-{
- "Version": "1.0.0",
- "UseMinimatch": false,
- "SignBatches": [
- {
- "MatchedPath": [
- "PowerToysSetupCustomActionsVNext.dll",
- "SilentFilesInUseBAFunction.dll",
- "PowerToys*Setup-*.exe",
- "PowerToys*Setup-*.msi"
- ],
- "SigningInfo": {
- "Operations": [
- {
- "KeyCode": "CP-230012",
- "OperationSetCode": "SigntoolSign",
- "Parameters": [
- {
- "parameterName": "OpusName",
- "parameterValue": "Microsoft"
- },
- {
- "parameterName": "OpusInfo",
- "parameterValue": "http://www.microsoft.com"
- },
- {
- "parameterName": "FileDigest",
- "parameterValue": "/fd \"SHA256\""
- },
- {
- "parameterName": "PageHash",
- "parameterValue": "/NPH"
- },
- {
- "parameterName": "TimeStamp",
- "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
- }
- ],
- "ToolName": "sign",
- "ToolVersion": "1.0"
- },
- {
- "KeyCode": "CP-230012",
- "OperationSetCode": "SigntoolVerify",
- "Parameters": [],
- "ToolName": "sign",
- "ToolVersion": "1.0"
- }
- ]
- }
- }
- ]
-}
diff --git a/.pipelines/generateDscManifests.ps1 b/.pipelines/generateDscManifests.ps1
index 109610e62e..e0a2f463af 100644
--- a/.pipelines/generateDscManifests.ps1
+++ b/.pipelines/generateDscManifests.ps1
@@ -65,21 +65,28 @@ if (-not (Test-Path $outputDir)) {
New-Item -Path $outputDir -ItemType Directory -Force | Out-Null
}
-Write-Host "DSC manifests will be generated to: '$outputDir'"
+# DSC v3 manifests go to DSCModules subfolder
+$dscOutputDir = Join-Path $outputDir 'DSCModules'
+if (-not (Test-Path $dscOutputDir)) {
+ Write-Host "Creating DSCModules subfolder at '$dscOutputDir'."
+ New-Item -Path $dscOutputDir -ItemType Directory -Force | Out-Null
+}
-Write-Host "Cleaning previously generated DSC manifest files from '$outputDir'."
-Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
+Write-Host "DSC manifests will be generated to: '$dscOutputDir'"
-$arguments = @('manifest', '--resource', 'settings', '--outputDir', $outputDir)
+Write-Host "Cleaning previously generated DSC manifest files from '$dscOutputDir'."
+Get-ChildItem -Path $dscOutputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction SilentlyContinue | Remove-Item -Force
+
+$arguments = @('manifest', '--resource', 'settings', '--outputDir', $dscOutputDir)
Write-Host "Invoking DSC manifest generator: '$exePath' $($arguments -join ' ')"
& $exePath @arguments
if ($LASTEXITCODE -ne 0) {
throw "PowerToys.DSC.exe exited with code $LASTEXITCODE"
}
-$generatedFiles = Get-ChildItem -Path $outputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
+$generatedFiles = Get-ChildItem -Path $dscOutputDir -Filter 'microsoft.powertoys.*.settings.dsc.resource.json' -ErrorAction Stop
if ($generatedFiles.Count -eq 0) {
- throw "No DSC manifest files were generated in '$outputDir'."
+ throw "No DSC manifest files were generated in '$dscOutputDir'."
}
Write-Host "Generated $($generatedFiles.Count) DSC manifest file(s):"
diff --git a/.pipelines/packages.config b/.pipelines/packages.config
index c4bca409f9..1e9b92d3b7 100644
--- a/.pipelines/packages.config
+++ b/.pipelines/packages.config
@@ -1,4 +1,4 @@
-
+
diff --git a/.pipelines/v2/ci.yml b/.pipelines/v2/ci.yml
index 6b0105a38a..297c268757 100644
--- a/.pipelines/v2/ci.yml
+++ b/.pipelines/v2/ci.yml
@@ -32,7 +32,7 @@ parameters:
- name: enableMsBuildCaching
type: boolean
displayName: "Enable MSBuild Caching"
- default: true
+ default: false
- name: runTests
type: boolean
displayName: "Run Tests"
diff --git a/.pipelines/v2/release.yml b/.pipelines/v2/release.yml
index e8cc7d5ed8..71f80f574b 100644
--- a/.pipelines/v2/release.yml
+++ b/.pipelines/v2/release.yml
@@ -52,8 +52,6 @@ extends:
name: SHINE-INT-S
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS17-Preview
- ${{ else }}:
- image: SHINE-VS17-Latest
os: windows
sdl:
tsa:
@@ -75,7 +73,6 @@ extends:
name: SHINE-INT-L
demands:
# Our INT agents have a large disk mounted at P:\
- - WorkFolder -equals P:\_work
- ${{ if eq(parameters.useVSPreview, true) }}:
- ImageOverride -equals SHINE-VS17-Preview
os: windows
@@ -126,7 +123,6 @@ extends:
parameters:
pool:
name: SHINE-INT-L
- image: SHINE-VS17-Latest
os: windows
official: true
codeSign: true
diff --git a/.pipelines/v2/templates/job-build-project.yml b/.pipelines/v2/templates/job-build-project.yml
index 2ffd0c7f62..4ce0c0e7c0 100644
--- a/.pipelines/v2/templates/job-build-project.yml
+++ b/.pipelines/v2/templates/job-build-project.yml
@@ -111,6 +111,7 @@ jobs:
${{ else }}:
OutputBuildPlatform: ${{ platform }}
variables:
+ NUGET_PACKAGES: 'C:\NuGetPackages' # Some of our build steps cache these here... and it was apparently part of the global environment
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\MakeAppx.exe'
# Azure DevOps abhors a vacuum
# If these are blank, expansion will fail later on... which will result in direct substitution of the variable *names*
@@ -139,6 +140,10 @@ jobs:
- output: pipelineArtifact
artifactName: $(JobOutputArtifactName)
targetPath: $(Build.ArtifactStagingDirectory)
+ - output: pipelineArtifact
+ artifactName: $(JobOutputArtifactName)-failure-$(System.JobAttempt)
+ targetPath: $(LogOutputDirectory)
+ condition: or(failed(), canceled())
steps:
- checkout: self
clean: true
@@ -187,14 +192,14 @@ jobs:
displayName: Verify XAML formatting
- pwsh: |-
- & '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln'
- displayName: Verify Nuget package versions for PowerToys.sln
+ & '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
+ displayName: Verify Nuget package versions for PowerToys.slnx
- pwsh: |-
- & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.sln'
+ & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\BugReportTool\BugReportTool.sln'
& '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\tools\StylesReportTool\StylesReportTool.sln'
- & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.sln'
+ & '.pipelines/verifyArm64Configuration.ps1' -solution '$(build.sourcesdirectory)\installer\PowerToysSetup.slnx'
displayName: Verify ARM64 configurations
- ${{ if eq(parameters.enablePackageCaching, true) }}:
@@ -247,7 +252,7 @@ jobs:
${{ else }}:
displayName: Build PowerToys main project
inputs:
- solution: 'PowerToys.sln'
+ solution: 'PowerToys.slnx'
vsVersion: 17.0
msbuildArgs: >-
-restore -graph
@@ -266,6 +271,26 @@ jobs:
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ - task: VSBuild@1
+ displayName: Generate DSC artifacts for ARM64
+ condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
+ inputs:
+ solution: PowerToys.slnx
+ vsVersion: 17.0
+ msbuildArgs: >-
+ -restore
+ /p:Configuration=$(BuildConfiguration)
+ /p:Platform=x64
+ /t:DSC\PowerToys_Settings_DSC_Schema_Generator
+ /bl:$(LogOutputDirectory)\build-dsc-generator.binlog
+ ${{ parameters.additionalBuildOptions }}
+ $(MSBuildCacheParameters)
+ $(RestoreAdditionalProjectSourcesArg)
+ platform: x64
+ configuration: $(BuildConfiguration)
+ msbuildArchitecture: x64
+ maximumCpuCount: true
+
# Build PowerToys.DSC.exe for ARM64 (x64 uses existing binary from previous build)
- task: VSBuild@1
displayName: Build PowerToys.DSC.exe (x64 for generating manifests)
@@ -375,7 +400,7 @@ jobs:
### HACK: On ARM64 builds, building an app with Windows App SDK copies the x64 WebView2 dll instead of the ARM64 one. This task makes sure the right dll is used.
- task: CopyFiles@2
displayName: HACK Copy core WebView2 ARM64 dll to output directory
- condition: eq(variables['BuildPlatform'],'arm64')
+ condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
inputs:
contents: packages/Microsoft.Web.WebView2.1.0.2903.40/runtimes/win-ARM64/native_uap/Microsoft.Web.WebView2.Core.dll
targetFolder: $(Build.SourcesDirectory)/ARM64/Release/WinUI3Apps/
@@ -414,11 +439,11 @@ jobs:
inputs:
testResultsFormat: VSTest
testResultsFiles: '**/*.trx'
- condition: ne(variables['BuildPlatform'],'arm64')
+ condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
# Native dlls
- task: VSTest@2
- condition: ne(variables['BuildPlatform'],'arm64') # No arm64 agents to run the tests.
+ condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64')) # No arm64 agents to run the tests.
displayName: 'Native Tests'
inputs:
platform: '$(BuildPlatform)'
@@ -512,14 +537,6 @@ jobs:
versionNumber: ${{ parameters.versionNumber }}
additionalBuildOptions: ${{ parameters.additionalBuildOptions }}
- - template: steps-build-installer-vnext.yml
- parameters:
- codeSign: ${{ parameters.codeSign }}
- signingIdentity: ${{ parameters.signingIdentity }}
- versionNumber: ${{ parameters.versionNumber }}
- additionalBuildOptions: ${{ parameters.additionalBuildOptions }}
- buildUserInstaller: true # NOTE: This is the distinction between the above and below rules
-
# This saves ~1GiB per architecture. We won't need these later.
# Removes:
# - All .pdb files from any static libs .libs (which were only used during linking)
diff --git a/.pipelines/v2/templates/job-build-ui-tests.yml b/.pipelines/v2/templates/job-build-ui-tests.yml
index b9fad16d44..342750d51c 100644
--- a/.pipelines/v2/templates/job-build-ui-tests.yml
+++ b/.pipelines/v2/templates/job-build-ui-tests.yml
@@ -74,7 +74,7 @@ jobs:
command: restore
feedsToUse: config
configPath: nuget.config
- restoreSolution: PowerToys.sln
+ restoreSolution: PowerToys.slnx
restoreDirectory: '$(Build.SourcesDirectory)\packages'
# Build all UI test projects if no specific modules are specified
@@ -129,4 +129,4 @@ jobs:
- publish: $(JobOutputDirectory)
artifact: $(JobOutputArtifactName)
displayName: Publish UI Test artifacts
- condition: always()
\ No newline at end of file
+ condition: always()
diff --git a/.pipelines/v2/templates/steps-build-installer-vnext.yml b/.pipelines/v2/templates/steps-build-installer-vnext.yml
index 882df8696a..bf467ed5d9 100644
--- a/.pipelines/v2/templates/steps-build-installer-vnext.yml
+++ b/.pipelines/v2/templates/steps-build-installer-vnext.yml
@@ -2,9 +2,6 @@ parameters:
- name: versionNumber
type: string
default: "0.0.1"
- - name: buildUserInstaller
- type: boolean
- default: false
- name: codeSign
type: boolean
default: false
@@ -25,43 +22,26 @@ steps:
arguments: 'install --global wix --version 5.0.2'
- pwsh: |-
- & git clean -xfd -e *exe -- .\installer\
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Clean installer to reduce cross-contamination
-
- - pwsh: |-
- # Determine whether this is a per-user build
- $IsPerUser = $${{ parameters.buildUserInstaller }}
-
- # Build slug used to locate the artifacts
- $InstallerBuildSlug = if ($IsPerUser) { 'UserSetup' } else { 'MachineSetup' }
-
- # VNext bundle folder; base name intentionally omits the VNext suffix
- $InstallerFolder = 'PowerToysSetupVNext'
- if ($IsPerUser) {
- $InstallerBasename = "PowerToysUserSetup-${{ parameters.versionNumber }}-$(BuildPlatform)"
- }
- else {
- $InstallerBasename = "PowerToysSetup-${{ parameters.versionNumber }}-$(BuildPlatform)"
- }
-
- # Export variables for downstream steps
- Write-Host "##vso[task.setvariable variable=InstallerBuildSlug]$InstallerBuildSlug"
- Write-Host "##vso[task.setvariable variable=InstallerRelativePath]$(BuildPlatform)\$(BuildConfiguration)\$InstallerBuildSlug"
- Write-Host "##vso[task.setvariable variable=InstallerBasename]$InstallerBasename"
- Write-Host "##vso[task.setvariable variable=InstallerFolder]$InstallerFolder"
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Prepare Installer variables
+ Write-Host "##vso[task.setvariable variable=InstallerMachineRoot]installer\PowerToysSetupVNext\$(BuildPlatform)\$(BuildConfiguration)\MachineSetup"
+ Write-Host "##vso[task.setvariable variable=InstallerUserRoot]installer\PowerToysSetupVNext\$(BuildPlatform)\$(BuildConfiguration)\UserSetup"
+ Write-Host "##vso[task.setvariable variable=InstallerMachineBasename]PowerToysSetup-${{ parameters.versionNumber }}-$(BuildPlatform)"
+ Write-Host "##vso[task.setvariable variable=InstallerUserBasename]PowerToysUserSetup-${{ parameters.versionNumber }}-$(BuildPlatform)"
+ displayName: Prepare Installer variables
# This dll needs to be built and signed before building the MSI.
+ # The Custom Actions project contains a pre-build event that prepares the .wxs files
+ # by filling them out with all our components. We pass RunBuildEvents=true to force
+ # that logic to run.
- task: VSBuild@1
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build PowerToysSetupCustomActionsVNext
+ displayName: Build Shared Support DLLs
inputs:
- solution: "**/installer/PowerToysSetup.sln"
+ solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
- /t:PowerToysSetupCustomActionsVNext
- /p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true
+ /t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction
+ /p:RunBuildEvents=true;RestorePackagesConfig=true;CIBuild=true
-restore -graph
- /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-actions.binlog
+ /bl:$(LogOutputDirectory)\installer-actions.binlog
${{ parameters.additionalBuildOptions }}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
@@ -70,28 +50,53 @@ steps:
maximumCpuCount: true
- ${{ if eq(parameters.codeSign, true) }}:
- - template: steps-esrp-signing.yml
+ - template: steps-esrp-sign-files-authenticode.yml
parameters:
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign PowerToysSetupCustomActionsVNext
+ displayName: Sign Shared Support DLLs
signingIdentity: ${{ parameters.signingIdentity }}
- inputs:
- FolderPath: 'installer/PowerToysSetupCustomActionsVNext/$(InstallerRelativePath)'
- signType: batchSigning
- batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json'
- ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
+ folder: 'installer'
+ pattern: |-
+ **/PowerToysSetupCustomActionsVNext.dll
+ **/SilentFilesInUseBAFunction.dll
## INSTALLER START
#### MSI BUILDING AND SIGNING
+ #
+ # The MSI build contains code that reverts the .wxs files to their in-tree versions.
+ # This is only supposed to happen during local builds. Since this build system is
+ # supposed to run side by side--machine and then user--we do NOT want to destroy
+ # the .wxs files. Therefore, we pass RunBuildEvents=false to suppress all of that
+ # logic.
+ #
+ # We pass BuildProjectReferences=false so that it does not recompile the DLLs we just built.
+ # We only pass -restore on the first one because the second run should already have all
+ # of the dependencies.
- task: VSBuild@1
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build VNext MSI
+ displayName: 💻 Build VNext MSI
inputs:
- solution: "**/installer/PowerToysSetup.sln"
+ solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
-restore
/t:PowerToysInstallerVNext
- /p:RunBuildEvents=false;PerUser=${{parameters.buildUserInstaller}};BuildProjectReferences=false;CIBuild=true
- /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-msi.binlog
+ /p:RunBuildEvents=false;PerUser=false;BuildProjectReferences=false;CIBuild=true
+ /bl:$(LogOutputDirectory)\installer-machine-msi.binlog
+ ${{ parameters.additionalBuildOptions }}
+ platform: $(BuildPlatform)
+ configuration: $(BuildConfiguration)
+ clean: false # don't undo our hard work above by deleting the CustomActions dll
+ msbuildArchitecture: x64
+ maximumCpuCount: true
+
+ - task: VSBuild@1
+ displayName: 👤 Build VNext MSI
+ inputs:
+ solution: "**/installer/PowerToysSetup.slnx"
+ vsVersion: 17.0
+ msbuildArgs: >-
+ /t:PowerToysInstallerVNext
+ /p:RunBuildEvents=false;PerUser=true;BuildProjectReferences=false;CIBuild=true
+ /bl:$(LogOutputDirectory)\installer-user-msi.binlog
${{ parameters.additionalBuildOptions }}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
@@ -100,77 +105,66 @@ steps:
maximumCpuCount: true
- script: |-
- wix msi decompile installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).msi -x $(build.sourcesdirectory)\extractedMsi
- dir $(build.sourcesdirectory)\extractedMsi
- displayName: "${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} WiX5: Extract and verify MSI"
+ wix msi decompile $(InstallerMachineRoot)\$(InstallerMachineBasename).msi -x $(build.sourcesdirectory)\extractedMachineMsi
+ wix msi decompile $(InstallerUserRoot)\$(InstallerUserBasename).msi -x $(build.sourcesdirectory)\extractedUserMsi
+ dir $(build.sourcesdirectory)\extractedMachineMsi
+ dir $(build.sourcesdirectory)\extractedUserMsi
+ displayName: "WiX5: Extract and verify MSIs"
# Check if deps.json files don't reference different dll versions.
- pwsh: |-
- & '.pipelines/verifyDepsJsonLibraryVersions.ps1' -targetDir '$(build.sourcesdirectory)\extractedMsi\File'
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Audit deps.json in MSI extracted files
+ & '.pipelines/verifyDepsJsonLibraryVersions.ps1' -targetDir '$(build.sourcesdirectory)\extractedMachineMsi\File'
+ & '.pipelines/verifyDepsJsonLibraryVersions.ps1' -targetDir '$(build.sourcesdirectory)\extractedUserMsi\File'
+ displayName: Audit deps.json in MSI extracted files
- ${{ if eq(parameters.codeSign, true) }}:
- pwsh: |-
- & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedMsi\File'
- & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedMsi\Binary'
- git clean -xfd ./extractedMsi
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Verify all binaries are signed and versioned
+ & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedMachineMsi\File'
+ & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedMachineMsi\Binary'
+ & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedUserMsi\File'
+ & .pipelines/versionAndSignCheck.ps1 -targetDir '$(build.sourcesdirectory)\extractedUserMsi\Binary'
+ git clean -xfd ./extractedMachineMsi ./extractedUserMsi
+ displayName: Verify all binaries are signed and versioned
- - template: steps-esrp-signing.yml
+ - template: steps-esrp-sign-files-authenticode.yml
parameters:
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign VNext MSI
+ displayName: Sign VNext MSIs
signingIdentity: ${{ parameters.signingIdentity }}
- inputs:
- FolderPath: 'installer/$(InstallerFolder)/$(InstallerRelativePath)'
- signType: batchSigning
- batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json'
- ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
+ folder: 'installer'
+ pattern: '**/PowerToys*Setup-*.msi'
#### END MSI
- #### BUILDING AND SIGNING SilentFilesInUseBAFunction DLL
- - task: VSBuild@1
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build SilentFilesInUseBAFunction
- inputs:
- solution: "**/installer/PowerToysSetup.sln"
- vsVersion: 17.0
- msbuildArgs: >-
- /t:SilentFilesInUseBAFunction
- /p:RunBuildEvents=true;PerUser=${{parameters.buildUserInstaller}};RestorePackagesConfig=true;CIBuild=true
- -restore -graph
- /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-SilentFilesInUseBAFunction.binlog
- ${{ parameters.additionalBuildOptions }}
- platform: $(BuildPlatform)
- configuration: $(BuildConfiguration)
- clean: false # don't undo our hard work above by deleting the msi
- msbuildArchitecture: x64
- maximumCpuCount: true
-
- - ${{ if eq(parameters.codeSign, true) }}:
- - template: steps-esrp-signing.yml
- parameters:
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign SilentFilesInUseBAFunction
- signingIdentity: ${{ parameters.signingIdentity }}
- inputs:
- FolderPath: 'installer/$(BuildPlatform)/$(BuildConfiguration)'
- signType: batchSigning
- batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json'
- ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
-
- #### END BUILDING AND SIGNING SilentFilesInUseBAFunction DLL
-
#### BOOTSTRAP BUILDING AND SIGNING
+ # We pass BuildProjectReferences=false so that it does not recompile the DLLs we just built.
+ # We only pass -restore on the first one because the second run should already have all
+ # of the dependencies.
- task: VSBuild@1
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Build VNext Bootstrapper
+ displayName: 💻 Build VNext Bootstrapper
inputs:
- solution: "**/installer/PowerToysSetup.sln"
+ solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 17.0
msbuildArgs: >-
-restore
/t:PowerToysBootstrapperVNext
- /p:PerUser=${{parameters.buildUserInstaller}};CIBuild=true
- /bl:$(LogOutputDirectory)\installer-$(InstallerBuildSlug)-bootstrapper.binlog
- -restore -graph
+ /p:PerUser=false;BuildProjectReferences=false;CIBuild=true
+ /bl:$(LogOutputDirectory)\installer-machine-bootstrapper.binlog
+ ${{ parameters.additionalBuildOptions }}
+ platform: $(BuildPlatform)
+ configuration: $(BuildConfiguration)
+ clean: false # don't undo our hard work above by deleting the MSI nor SilentFilesInUseBAFunction
+ msbuildArchitecture: x64
+ maximumCpuCount: true
+
+ - task: VSBuild@1
+ displayName: 👤 Build VNext Bootstrapper
+ inputs:
+ solution: "**/installer/PowerToysSetup.slnx"
+ vsVersion: 17.0
+ msbuildArgs: >-
+ /t:PowerToysBootstrapperVNext
+ /p:PerUser=true;BuildProjectReferences=false;CIBuild=true
+ /bl:$(LogOutputDirectory)\installer-user-bootstrapper.binlog
${{ parameters.additionalBuildOptions }}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
@@ -181,54 +175,41 @@ steps:
# The entirety of bundle unpacking/re-packing is unnecessary if we are not code signing it.
- ${{ if eq(parameters.codeSign, true) }}:
- script: |-
- wix burn detach installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).exe -engine installer\engine.exe
- displayName: "${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} WiX5: Extract Engine from Bundle"
+ wix burn detach $(InstallerMachineRoot)\$(InstallerMachineBasename).exe -engine installer\machine-engine.exe
+ wix burn detach $(InstallerUserRoot)\$(InstallerUserBasename).exe -engine installer\user-engine.exe
+ displayName: "WiX5: Extract Engines from Bundles"
- - template: steps-esrp-signing.yml
+ - template: steps-esrp-sign-files-authenticode.yml
parameters:
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign WiX Engine
+ displayName: Sign WiX Engines
signingIdentity: ${{ parameters.signingIdentity }}
- inputs:
- FolderPath: "installer"
- Pattern: engine.exe
- signConfigType: inlineSignParams
- inlineOperation: |
- [
- {
- "KeyCode": "CP-230012",
- "OperationCode": "SigntoolSign",
- "Parameters": {
- "OpusName": "Microsoft",
- "OpusInfo": "http://www.microsoft.com",
- "FileDigest": "/fd \"SHA256\"",
- "PageHash": "/NPH",
- "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
- },
- "ToolName": "sign",
- "ToolVersion": "1.0"
- },
- {
- "KeyCode": "CP-230012",
- "OperationCode": "SigntoolVerify",
- "Parameters": {},
- "ToolName": "sign",
- "ToolVersion": "1.0"
- }
- ]
+ folder: "installer"
+ pattern: '*-engine.exe'
- script: |-
- wix burn reattach installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).exe -engine installer\engine.exe -o installer\$(InstallerFolder)\$(InstallerRelativePath)\$(InstallerBasename).exe
- displayName: "${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} WiX5: Reattach Engine to Bundle"
+ wix burn reattach $(InstallerMachineRoot)\$(InstallerMachineBasename).exe -engine installer\machine-engine.exe -o $(InstallerMachineRoot)\$(InstallerMachineBasename).exe
+ wix burn reattach $(InstallerUserRoot)\$(InstallerUserBasename).exe -engine installer\user-engine.exe -o $(InstallerUserRoot)\$(InstallerUserBasename).exe
+ displayName: "WiX5: Reattach Engines to Bundles"
- - template: steps-esrp-signing.yml
+ - pwsh: |-
+ & wix burn extract -oba installer\ba\m "$(InstallerMachineRoot)\$(InstallerMachineBasename).exe"
+ & wix burn extract -oba installer\ba\u "$(InstallerUserRoot)\$(InstallerUserBasename).exe"
+ Get-ChildItem installer\ba -Recurse -Include *.exe,*.dll | Get-AuthenticodeSignature | ForEach-Object {
+ If ($_.Status -Ne "Valid") {
+ Write-Error $_.StatusMessage
+ } Else {
+ Write-Host $_.StatusMessage
+ }
+ }
+ & git clean -fdx installer\ba
+ displayName: "WiX5: Verify Bootstrapper content is signed"
+
+ - template: steps-esrp-sign-files-authenticode.yml
parameters:
- displayName: ${{replace(replace(parameters.buildUserInstaller,'True','👤'),'False','💻')}} Sign Final Bootstrapper
+ displayName: Sign Final Bootstrappers
signingIdentity: ${{ parameters.signingIdentity }}
- inputs:
- FolderPath: 'installer/$(InstallerFolder)/$(InstallerRelativePath)'
- signType: batchSigning
- batchSignPolicyFile: '$(build.sourcesdirectory)\.pipelines\ESRPSigning_installer.json'
- ciPolicyFile: '$(build.sourcesdirectory)\.pipelines\CIPolicy.xml'
+ folder: 'installer'
+ pattern: '**/PowerToys*Setup-*.exe'
#### END BOOTSTRAP
## END INSTALLER
diff --git a/.pipelines/v2/templates/steps-esrp-sign-files-authenticode.yml b/.pipelines/v2/templates/steps-esrp-sign-files-authenticode.yml
new file mode 100644
index 0000000000..5b9bbd2fce
--- /dev/null
+++ b/.pipelines/v2/templates/steps-esrp-sign-files-authenticode.yml
@@ -0,0 +1,45 @@
+parameters:
+ - name: displayName
+ type: string
+ default: Sign Specific Files
+ - name: folder
+ type: string
+ - name: pattern
+ type: string
+ - name: signingIdentity
+ type: object
+ default: {}
+
+steps:
+ - template: steps-esrp-signing.yml
+ parameters:
+ displayName: ${{ parameters.displayName }}
+ signingIdentity: ${{ parameters.signingIdentity }}
+ inputs:
+ FolderPath: ${{ parameters.folder }}
+ Pattern: ${{ parameters.pattern }}
+ UseMinimatch: true
+ signConfigType: inlineSignParams
+ inlineOperation: |-
+ [
+ {
+ "KeyCode": "CP-230012",
+ "OperationCode": "SigntoolSign",
+ "Parameters": {
+ "OpusName": "Microsoft",
+ "OpusInfo": "http://www.microsoft.com",
+ "FileDigest": "/fd \"SHA256\"",
+ "PageHash": "/NPH",
+ "TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
+ },
+ "ToolName": "sign",
+ "ToolVersion": "1.0"
+ },
+ {
+ "KeyCode": "CP-230012",
+ "OperationCode": "SigntoolVerify",
+ "Parameters": {},
+ "ToolName": "sign",
+ "ToolVersion": "1.0"
+ }
+ ]
diff --git a/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml b/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml
index 9c59312844..cdb28b572c 100644
--- a/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml
+++ b/.pipelines/v2/templates/steps-update-winappsdk-and-restore-nuget.yml
@@ -54,4 +54,13 @@ steps:
feedsToUse: 'config'
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
restoreSolution: '$(build.sourcesdirectory)\**\*.sln'
- includeNuGetOrg: false
\ No newline at end of file
+ includeNuGetOrg: false
+
+- task: NuGetCommand@2
+ displayName: 'Restore NuGet packages (slnx)'
+ inputs:
+ command: 'restore'
+ feedsToUse: 'config'
+ nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
+ restoreSolution: '$(build.sourcesdirectory)\**\*.slnx'
+ includeNuGetOrg: false
diff --git a/.vscode/launch.json b/.vscode/launch.json
index b2d2bca9ac..940ff302de 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -38,6 +38,17 @@
"env": {},
"console": "internalConsole",
"stopAtEntry": false
- }
+ },
+ {
+ "name": "Run AdvancedPaste (managed, no build, ARCH configurable)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceFolder}\\${input:arch}\\Debug\\WinUI3Apps\\PowerToys.AdvancedPaste.exe",
+ "args": [],
+ "cwd": "${workspaceFolder}",
+ "env": {},
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
]
}
\ No newline at end of file
diff --git a/Cpp.Build.props b/Cpp.Build.props
index 5a4538f940..f146a4d770 100644
--- a/Cpp.Build.props
+++ b/Cpp.Build.props
@@ -26,6 +26,7 @@
true
$(MsbuildThisFileDirectory)\CppRuleSet.ruleset
+ $(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(CAExcludePath)
@@ -34,7 +35,7 @@
arm64
false
true
- $(MSBuildThisFileFullPath)\..\deps\;$(MSBuildThisFileFullPath)\..\packages\;$(ExternalIncludePath)
+ $(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath)
Guard
diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md
index 56a2eb9eee..8aba94f12f 100644
--- a/DATA_AND_PRIVACY.md
+++ b/DATA_AND_PRIVACY.md
@@ -147,6 +147,18 @@ _If you want to find diagnostic data events in the source code, these two links
Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent
Triggered when Advanced Paste leverages the Semantic Kernel.
+
+ Microsoft.PowerToys.AdvancedPasteSemanticKernelErrorEvent
+ Occurs when the Semantic Kernel workflow encounters an error.
+
+
+ Microsoft.PowerToys.AdvancedPasteEndpointUsageEvent
+ Logs the AI provider, model, and processing duration for each endpoint call.
+
+
+ Microsoft.PowerToys.AdvancedPasteCustomActionErrorEvent
+ Records provider, model, and status details when a custom action fails.
+
### Always on Top
@@ -231,6 +243,10 @@ _If you want to find diagnostic data events in the source code, these two links
Event Name
Description
+
+ Microsoft.PowerToys.CmdNotFound_EnableCmdNotFound
+ Triggered when Command Not Found is enabled or disabled.
+
Microsoft.PowerToys.CmdNotFoundInstallEvent
Triggered when a Command Not Found is installed.
@@ -245,6 +261,62 @@ _If you want to find diagnostic data events in the source code, these two links
+### Command Palette
+
+
+ Event Name
+ Description
+
+
+ Microsoft.PowerToys.CmdPal_BeginInvoke
+ Triggered when the Command Palette is launched by the user.
+
+
+ Microsoft.PowerToys.CmdPal_ColdLaunch
+ Occurs when Command Palette starts for the first time (cold start).
+
+
+ Microsoft.PowerToys.CmdPal_OpenPage
+ Triggered when a page is opened within the Command Palette, tracking navigation depth.
+
+
+ Microsoft.PowerToys.CmdPal_OpenUri
+ Occurs when a URI is opened through the Command Palette, including whether it's a web URL.
+
+
+ Microsoft.PowerToys.CmdPal_ReactivateInstance
+ Triggered when an existing Command Palette instance is reactivated.
+
+
+ Microsoft.PowerToys.CmdPal_RunCommand
+ Logs when a command is executed through the Command Palette, including admin elevation status.
+
+
+ Microsoft.PowerToys.CmdPal_RunQuery
+ Triggered when a search query is performed, including result count and duration.
+
+
+ Microsoft.PowerToys.CmdPalDismissedOnEsc
+ Occurs when the Command Palette is dismissed by pressing the Escape key.
+
+
+ Microsoft.PowerToys.CmdPalDismissedOnLostFocus
+ Triggered when the Command Palette is dismissed due to losing focus.
+
+
+ Microsoft.PowerToys.CmdPalHotkeySummoned
+ Logs when the Command Palette is summoned via hotkey, distinguishing between global and context-specific hotkeys.
+
+
+ Microsoft.PowerToys.CmdPalInvokeResult
+ Records the result type of a Command Palette invocation.
+
+
+ Microsoft.PowerToys.CmdPalProcessStarted
+ Triggered when the Command Palette process is started.
+
+
+
### Crop And Lock
@@ -723,6 +795,10 @@ _If you want to find diagnostic data events in the source code, these two links
Event Name
Description
+
+ Microsoft.PowerToys.NewPlus_ChangedTemplateLocation
+ Triggered when the template folder location is changed.
+
Microsoft.PowerToys.NewPlus_EventCopyTemplate
Triggered when an item from New+ is created (copied to the current directory).
@@ -731,6 +807,10 @@ _If you want to find diagnostic data events in the source code, these two links
Microsoft.PowerToys.NewPlus_EventCopyTemplateResult
Logs the success of item creation (copying).
+
+ Microsoft.PowerToys.NewPlus_EventOpenTemplates
+ Triggered when the templates folder is opened.
+
Microsoft.PowerToys.NewPlus_EventShowTemplateItems
Triggered when the New+ context menu flyout is displayed.
@@ -916,12 +996,8 @@ _If you want to find diagnostic data events in the source code, these two links
Description
- Microsoft.PowerToys.ShortcutGuide_EnableGuide
- Triggered when Shortcut Guide is enabled.
-
-
- Microsoft.PowerToys.ShortcutGuide_HideGuide
- Occurs when Shortcut Guide is hidden from view.
+ Microsoft.PowerToys.ShortcutGuide_GuideSession
+ Logs a Shortcut Guide session including duration and how it was closed.
Microsoft.PowerToys.ShortcutGuide_Settings
diff --git a/Directory.Packages.props b/Directory.Packages.props
index e19421ff79..89d2284b34 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,4 +1,4 @@
-
+
true
true
@@ -7,9 +7,9 @@
+
-
@@ -35,23 +35,33 @@
-
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
@@ -74,7 +84,7 @@
-
+
@@ -84,28 +94,29 @@
-
+
-
-
-
+
+
+
-
+
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
@@ -128,4 +139,4 @@
-
+
\ No newline at end of file
diff --git a/NOTICE.md b/NOTICE.md
index 51cb2cb79b..23efb64864 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -1526,10 +1526,10 @@ SOFTWARE.
- AdaptiveCards.Rendering.WinUI3
- AdaptiveCards.Templating
- Appium.WebDriver
-- Azure.AI.OpenAI
- CoenM.ImageSharp.ImageHash
- CommunityToolkit.Common
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
+- CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView
- CommunityToolkit.Mvvm
- CommunityToolkit.WinUI.Animations
- CommunityToolkit.WinUI.Collections
diff --git a/PowerToys.sln b/PowerToys.sln
deleted file mode 100644
index f4f2e1bd0f..0000000000
--- a/PowerToys.sln
+++ /dev/null
@@ -1,3352 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.32014.148
-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}
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A} = {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {655C9AF2-18D3-4DA6-80E4-85504A7722BA}
- {69E1EE8D-143A-4060-9129-4658ACF14AAF} = {69E1EE8D-143A-4060-9129-4658ACF14AAF}
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9} = {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}
- {AF2349B8-E5B6-4004-9502-687C1C7730B1} = {AF2349B8-E5B6-4004-9502-687C1C7730B1}
- {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670}
- {BA58206B-1493-4C75-BFEA-A85768A1E156} = {BA58206B-1493-4C75-BFEA-A85768A1E156}
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}
- {D940E07F-532C-4FF3-883F-790DA014F19A} = {D940E07F-532C-4FF3-883F-790DA014F19A}
- {DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A}
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D} = {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}
- {E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B}
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{4574FDD0-F61D-4376-98BF-E5A1262C11EC}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interface", "interface", "{3BB8493E-D18E-4485-A320-CB40F90F55AE}"
- ProjectSection(SolutionItems) = preProject
- src\modules\interface\powertoy_module_interface.h = src\modules\interface\powertoy_module_interface.h
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fancyzones", "fancyzones", "{D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesLib", "src\modules\fancyzones\FancyZonesLib\FancyZonesLib.vcxproj", "{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones.UnitTests", "src\modules\fancyzones\FancyZonesTests\UnitTests\UnitTests.vcxproj", "{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}"
- ProjectSection(ProjectDependencies) = postProject
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{1AFB6476-670D-4E80-A464-657E01DFF482}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Lib.UnitTests", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PackageIdentity", "src\PackageIdentity\PackageIdentity.vcxproj", "{E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameExt", "src\modules\powerrename\dll\PowerRenameExt.vcxproj", "{B25AC7A5-FB9F-4789-B392-D5C85E948670}"
- ProjectSection(ProjectDependencies) = postProject
- {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameLib", "src\modules\powerrename\lib\PowerRenameLib.vcxproj", "{51920F1F-C28C-4ADF-8660-4238766796C2}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameTest", "src\modules\powerrename\testapp\PowerRenameTest.vcxproj", "{A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.UnitTests", "src\modules\powerrename\unittests\PowerRenameLibUnitTests.vcxproj", "{2151F984-E006-4A9F-92EF-C6DDE3DC8413}"
- ProjectSection(ProjectDependencies) = postProject
- {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
- {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ModuleTemplateCompileTest", "tools\project_template\ModuleTemplate\ModuleTemplateCompileTest.vcxproj", "{64A80062-4D8B-4229-8A38-DFA1D7497749}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager", "src\modules\keyboardmanager\dll\KeyboardManager.vcxproj", "{89F34AF7-1C34-4A72-AA6E-534BCF972BD9}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "imageresizer", "imageresizer", "{6C7F47CC-2151-44A3-A546-41C70025132C}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageResizerUI", "src\modules\imageresizer\ui\ImageResizerUI.csproj", "{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerExt", "src\modules\imageresizer\dll\ImageResizerExt.vcxproj", "{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageResizer.UnitTests", "src\modules\imageresizer\tests\ImageResizer.UnitTests.csproj", "{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.ActionRunner", "src\ActionRunner\ActionRunner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}"
- ProjectSection(ProjectDependencies) = postProject
- {17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ApplicationUpdate", "src\common\updating\updating.vcxproj", "{17DA04DF-E393-4397-9CF0-84DABE11032E}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "keyboardmanager", "keyboardmanager", "{38BDB927-829B-4C65-9CD9-93FB05D66D65}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerCommon", "src\modules\keyboardmanager\common\KeyboardManagerCommon.vcxproj", "{8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "launcher", "launcher", "{C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wox.Infrastructure", "src\modules\launcher\Wox.Infrastructure\Wox.Infrastructure.csproj", "{4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wox.Plugin", "src\modules\launcher\Wox.Plugin\Wox.Plugin.csproj", "{8451ECDD-2EA4-4966-BB0A-7BBC40138E80}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wox.Test", "src\modules\launcher\Wox.Test\Wox.Test.csproj", "{FF742965-9A80-41A5-B042-D6C7D3A21708}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{4AFC9975-2456-4C70-94A4-84073C1CED93}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Calculator", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.csproj", "{59BD9891-3837-438A-958D-ADC7F91F6F7E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.VSCodeWorkspaces", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.VSCodeWorkspaces\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.csproj", "{4D971245-7A70-41D5-BAA0-DDB5684CAF51}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.WindowWalker", "src\modules\launcher\Plugins\Microsoft.Plugin.WindowWalker\Microsoft.Plugin.WindowWalker.csproj", "{74F1B9ED-F59C-4FE7-B473-7B453E30837E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Program", "src\modules\launcher\Plugins\Microsoft.Plugin.Program\Microsoft.Plugin.Program.csproj", "{FDB3555B-58EF-4AE6-B5F1-904719637AB4}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Shell", "src\modules\launcher\Plugins\Microsoft.Plugin.Shell\Microsoft.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Indexer", "src\modules\launcher\Plugins\Microsoft.Plugin.Indexer\Microsoft.Plugin.Indexer.csproj", "{F8B870EB-D5F5-45BA-9CF7-A5C459818820}"
- ProjectSection(ProjectDependencies) = postProject
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80} = {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Launcher", "src\modules\launcher\Microsoft.Launcher\Microsoft.Launcher.vcxproj", "{E364F67B-BB12-4E91-B639-355866EBCD8B}"
- ProjectSection(ProjectDependencies) = postProject
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2} = {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\modules\launcher\PowerLauncher\PowerLauncher.csproj", "{F97E5003-F263-4D4A-A964-0F1F3C82DEF2}"
- ProjectSection(ProjectDependencies) = postProject
- {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {03276A39-D4E9-417C-8FFD-200B0EE5E871}
- {0351ADA4-0C32-4652-9BA0-41F7B602372B} = {0351ADA4-0C32-4652-9BA0-41F7B602372B}
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4BABF3FE-3451-42FD-873F-3C332E18DCEF}
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51} = {4D971245-7A70-41D5-BAA0-DDB5684CAF51}
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D} = {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}
- {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {5043CECE-E6A7-4867-9CBE-02D27D83747A}
- {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045} = {5A1DB2F0-0715-4B3B-98E6-79BC41540045}
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF} = {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E} = {74F1B9ED-F59C-4FE7-B473-7B453E30837E}
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}
- {9F94B303-5E21-4364-9362-64426F8DB932} = {9F94B303-5E21-4364-9362-64426F8DB932}
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
- {D095BE44-1F2E-463E-A494-121892A75EA2} = {D095BE44-1F2E-463E-A494-121892A75EA2}
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820}
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "previewpane", "previewpane", "{2F305555-C296-497E-AC20-5FA1B237996A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PreviewHandlerCommon", "src\modules\previewpane\Common\PreviewHandlerCommon.csproj", "{AF2349B8-E5B6-4004-9502-687C1C7730B1}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkdownPreviewHandler", "src\modules\previewpane\MarkdownPreviewHandler\MarkdownPreviewHandler.csproj", "{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.MarkdownPreviewHandler.UnitTests", "src\modules\previewpane\UnitTests-MarkdownPreviewHandler\Preview.MarkdownPreviewHandler.UnitTests.csproj", "{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SvgPreviewHandler", "src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj", "{DA425894-6E13-404F-8DCB-78584EC0557A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.SvgPreviewHandler.UnitTests", "src\modules\previewpane\UnitTests-SvgPreviewHandler\Preview.SvgPreviewHandler.UnitTests.csproj", "{060D75DA-2D1C-48E6-A4A1-6F0718B64661}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.PreviewHandlerCommon.UnitTests", "src\modules\previewpane\UnitTests-PreviewHandlerCommon\Preview.PreviewHandlerCommon.UnitTests.csproj", "{748417CA-F17E-487F-9411-CAFB6D3F4877}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "powerpreview", "src\modules\previewpane\powerpreview\powerpreview.vcxproj", "{217DF501-135C-4E38-BFC8-99D4821032EA}"
- ProjectSection(ProjectDependencies) = postProject
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF} = {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "settings-ui", "settings-ui", "{C3081D9A-1586-441A-B5F4-ED815B3719C1}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4981CCD1-4CD9-4A49-B240-00AA46493FF8}"
- ProjectSection(SolutionItems) = preProject
- src\.editorconfig = src\.editorconfig
- .vsconfig = .vsconfig
- src\Common.Dotnet.AotCompatibility.props = src\Common.Dotnet.AotCompatibility.props
- src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props
- src\Common.SelfContained.props = src\Common.SelfContained.props
- Cpp.Build.props = Cpp.Build.props
- Directory.Build.props = Directory.Build.props
- Directory.Build.targets = Directory.Build.targets
- Directory.Packages.props = Directory.Packages.props
- src\Monaco.props = src\Monaco.props
- src\Solution.props = src\Solution.props
- src\Version.props = src\Version.props
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.Library", "src\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Folder", "src\modules\launcher\Plugins\Microsoft.Plugin.Folder\Microsoft.Plugin.Folder.csproj", "{787B8AA6-CA93-4C84-96FE-DF31110AD1C4}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher.Telemetry", "src\modules\launcher\PowerLauncher.Telemetry\PowerLauncher.Telemetry.csproj", "{08C8C05F-0362-41BC-818C-724572DF8B06}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedTelemetry", "src\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj", "{5D00D290-4016-4CFE-9E41-1E7C724509BA}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedCommon", "src\common\ManagedCommon\ManagedCommon.csproj", "{4AED67B6-55FD-486F-B917-E543DEE2CB3C}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Program.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Program.UnitTests\Microsoft.Plugin.Program.UnitTests.csproj", "{42851751-CBC8-45A6-97F5-7A0753F7B4D1}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.SvgThumbnailProvider.UnitTests", "src\modules\previewpane\UnitTests-SvgThumbnailProvider\Preview.SvgThumbnailProvider.UnitTests.csproj", "{1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SvgThumbnailProvider", "src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj", "{8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ColorPicker", "src\modules\colorPicker\ColorPicker\ColorPicker.vcxproj", "{655C9AF2-18D3-4DA6-80E4-85504A7722BA}"
- ProjectSection(ProjectDependencies) = postProject
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD} = {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorPickerUI", "src\modules\colorPicker\ColorPickerUI\ColorPickerUI.csproj", "{BA58206B-1493-4C75-BFEA-A85768A1E156}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "colorpicker", "colorpicker", "{1D78B84B-CA39-406C-98F4-71F7EC266CC0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri", "src\modules\launcher\Plugins\Microsoft.Plugin.Uri\Microsoft.Plugin.Uri.csproj", "{03276A39-D4E9-417C-8FFD-200B0EE5E871}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Uri.UnitTests\Microsoft.Plugin.Uri.UnitTests.csproj", "{B81FB7B6-D30E-428F-908A-41422EFC1172}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.UnitTests", "src\settings-ui\Settings.UI.UnitTests\Settings.UI.UnitTests.csproj", "{0F85E674-34AE-443D-954C-8321EB8B93B1}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.csproj", "{632BBE62-5421-49EA-835A-7FFA4F499BD6}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.System", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.System\Microsoft.PowerToys.Run.Plugin.System.csproj", "{FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.System.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.System.UnitTests\Microsoft.PowerToys.Run.Plugin.System.UnitTests.csproj", "{DA5A6FE9-0040-40CC-83CC-764AE5306590}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Service", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Service\Microsoft.PowerToys.Run.Plugin.Service.csproj", "{0351ADA4-0C32-4652-9BA0-41F7B602372B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}"
- ProjectSection(ProjectDependencies) = postProject
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F} = {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SettingsAPI", "src\common\SettingsAPI\SettingsAPI.vcxproj", "{6955446D-23F7-4023-9BB3-8657F904AF99}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Interop.UnitTests", "src\common\interop\interop-tests\Common.Interop.UnitTests.csproj", "{58736667-1027-4AD7-BFDF-7A3A6474103A}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "notifications", "notifications", "{D92131D6-7610-4D60-A7DB-1C169783F83B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Notifications", "src\common\notifications\notifications.vcxproj", "{1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BackgroundActivatorDLL", "src\common\notifications\BackgroundActivatorDLL\BackgroundActivatorDLL.vcxproj", "{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}"
- ProjectSection(ProjectDependencies) = postProject
- {0B593A6C-4143-4337-860E-DB5710FB87DB} = {0B593A6C-4143-4337-860E-DB5710FB87DB}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BackgroundActivator", "src\common\notifications\BackgroundActivator\BackgroundActivator.vcxproj", "{0B593A6C-4143-4337-860E-DB5710FB87DB}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interop", "interop", "{5A7818A8-109C-4E1C-850D-1A654E234B0E}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "log", "log", "{E4E03FE0-94FD-47C7-88C5-F17D0AA549D3}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "COMUtils", "src\common\COMUtils\COMUtils.vcxproj", "{7319089E-46D6-4400-BC65-E39BDF1416EE}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Display", "src\common\Display\Display.vcxproj", "{CABA8DFB-823B-4BF2-93AC-3F31984150D9}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Themes", "src\common\Themes\Themes.vcxproj", "{98537082-0FDB-40DE-ABD8-0DC5A4269BAB}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643-4663-475E-B329-03F0C9918D48}"
- ProjectSection(SolutionItems) = preProject
- src\common\utils\appMutex.h = src\common\utils\appMutex.h
- src\common\utils\color.h = src\common\utils\color.h
- src\common\utils\com_object_factory.h = src\common\utils\com_object_factory.h
- src\common\utils\elevation.h = src\common\utils\elevation.h
- 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\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
- src\common\utils\HDropIterator.h = src\common\utils\HDropIterator.h
- src\common\utils\HttpClient.h = src\common\utils\HttpClient.h
- src\common\utils\json.h = src\common\utils\json.h
- src\common\utils\language_helper.h = src\common\utils\language_helper.h
- src\common\utils\logger_helper.h = src\common\utils\logger_helper.h
- src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h
- src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h
- src\common\utils\MsWindowsSettings.h = src\common\utils\MsWindowsSettings.h
- src\common\utils\OnThreadExecutor.h = src\common\utils\OnThreadExecutor.h
- src\common\utils\os-detect.h = src\common\utils\os-detect.h
- src\common\utils\package.h = src\common\utils\package.h
- src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h
- src\common\utils\process_path.h = src\common\utils\process_path.h
- src\common\utils\registry.h = src\common\utils\registry.h
- src\common\utils\resources.h = src\common\utils\resources.h
- src\common\utils\serialized.h = src\common\utils\serialized.h
- src\common\utils\shell_ext_registration.h = src\common\utils\shell_ext_registration.h
- src\common\utils\string_utils.h = src\common\utils\string_utils.h
- src\common\utils\timeutil.h = src\common\utils\timeutil.h
- src\common\utils\UnhandledExceptionHandler.h = src\common\utils\UnhandledExceptionHandler.h
- src\common\utils\winapi_error.h = src\common\utils\winapi_error.h
- src\common\utils\window.h = src\common\utils\window.h
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Telemetry", "Telemetry", "{8F62026A-294B-41C6-8839-87463613F216}"
- ProjectSection(SolutionItems) = preProject
- src\common\Telemetry\ProjectTelemetry.h = src\common\Telemetry\ProjectTelemetry.h
- src\common\Telemetry\TelemetryBase.cs = src\common\Telemetry\TelemetryBase.cs
- src\common\Telemetry\TraceBase.h = src\common\Telemetry\TraceBase.h
- src\common\Telemetry\TraceLoggingDefines.h = src\common\Telemetry\TraceLoggingDefines.h
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.UI", "src\common\Common.UI\Common.UI.csproj", "{C3A17DCA-217B-462C-BB0C-BE086AF80081}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfPreviewHandler", "src\modules\previewpane\PdfPreviewHandler\PdfPreviewHandler.csproj", "{69E1EE8D-143A-4060-9129-4658ACF14AAF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.PdfPreviewHandler.UnitTests", "src\modules\previewpane\UnitTests-PdfPreviewHandler\Preview.PdfPreviewHandler.UnitTests.csproj", "{ECC20689-002A-4354-95A6-B58DF089C6FF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Registry", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\Microsoft.PowerToys.Run.Plugin.Registry.csproj", "{4BABF3FE-3451-42FD-873F-3C332E18DCEF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Registry.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry.UnitTest\Microsoft.PowerToys.Run.Plugin.Registry.UnitTests.csproj", "{0648DF05-5DDA-4BE1-B5F2-584926EBDB65}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEngine", "src\modules\keyboardmanager\KeyboardManagerEngine\KeyboardManagerEngine.vcxproj", "{BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEngineLibrary", "src\modules\keyboardmanager\KeyboardManagerEngineLibrary\KeyboardManagerEngineLibrary.vcxproj", "{E496B7FC-1E99-4BAB-849B-0E8367040B02}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager.Engine.UnitTests", "src\modules\keyboardmanager\KeyboardManagerEngineTest\KeyboardManagerEngineTest.vcxproj", "{7F4B3A60-BC27-45A7-8000-68B0B6EA7466}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditor", "src\modules\keyboardmanager\KeyboardManagerEditor\KeyboardManagerEditor.vcxproj", "{8DF78B53-200E-451F-9328-01EB907193AE}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrary", "src\modules\keyboardmanager\KeyboardManagerEditorLibrary\KeyboardManagerEditorLibrary.vcxproj", "{23D2070D-E4AD-4ADD-85A7-083D9C76AD49}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager.Editor.UnitTests", "src\modules\keyboardmanager\KeyboardManagerEditorTest\KeyboardManagerEditorTest.vcxproj", "{62173D9A-6724-4C00-A1C8-FB646480A9EC}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "awake", "awake", "{127F38E0-40AA-4594-B955-5616BF206882}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AwakeModuleInterface", "src\modules\awake\AwakeModuleInterface\AwakeModuleInterface.vcxproj", "{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Awake", "src\modules\awake\Awake\Awake.csproj", "{D940E07F-532C-4FF3-883F-790DA014F19A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.csproj", "{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter.UnitTest", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj", "{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shortcutguide", "shortcutguide", "{106CBECA-0701-4FC3-838C-9DF816A19AE2}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuideModuleInterface", "src\modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.vcxproj", "{2D604C07-51FC-46BB-9EB7-75AECC7F5E81}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide", "src\modules\ShortcutGuide\ShortcutGuide\ShortcutGuide.vcxproj", "{2EDB3EB4-FA92-4BFF-B2D8-566584837231}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesModuleInterface", "src\modules\fancyzones\FancyZonesModuleInterface\FancyZonesModuleInterface.vcxproj", "{48804216-2A0E-4168-A6D8-9CD068D14227}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones", "src\modules\fancyzones\FancyZones\FancyZones.vcxproj", "{FF1D7936-842A-4BBB-8BEA-E9FE796DE700}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Update", "src\Update\PowerToys.Update.vcxproj", "{44CE9AE1-4390-42C5-BACC-0FD6B40AA203}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsSettings", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsSettings\Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj", "{5043CECE-E6A7-4867-9CBE-02D27D83747A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfThumbnailProvider", "src\modules\previewpane\PdfThumbnailProvider\PdfThumbnailProvider.csproj", "{11491FD8-F921-48BF-880C-7FEA185B80A1}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.PdfThumbnailProvider.UnitTests", "src\modules\previewpane\UnitTests-PdfThumbnailProvider\Preview.PdfThumbnailProvider.UnitTests.csproj", "{F40C3397-1834-4530-B2D9-8F8B8456BCDF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsTerminal", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.csproj", "{A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.WindowsTerminal.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests\Microsoft.Plugin.WindowsTerminal.UnitTests.csproj", "{4ED320BC-BA04-4D42-8D15-CBE62151F08B}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseUtils", "MouseUtils", "{322566EF-20DC-43A6-B9F8-616AF942579A}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FindMyMouse", "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj", "{E94FD11C-0591-456F-899F-EFC0CA548336}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseHighlighter", "src\modules\MouseUtils\MouseHighlighter\MouseHighlighter.vcxproj", "{782A61BE-9D85-4081-B35C-1CCC9DCC1E88}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GcodeThumbnailProvider", "src\modules\previewpane\GcodeThumbnailProvider\GcodeThumbnailProvider.csproj", "{809AA252-E17A-4FA2-B0A1-0450976B763F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.GcodeThumbnailProvider.UnitTests", "src\modules\previewpane\UnitTests-GcodeThumbnailProvider\Preview.GcodeThumbnailProvider.UnitTests.csproj", "{133281D8-1BCE-4D07-B31E-796612A9609E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GcodePreviewHandler", "src\modules\previewpane\GcodePreviewHandler\GcodePreviewHandler.csproj", "{805306FF-A562-4415-8DEF-E493BDC45918}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.GcodePreviewHandler.UnitTests", "src\modules\previewpane\UnitTests-GcodePreviewHandler\Preview.GcodePreviewHandler.UnitTests.csproj", "{FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AlwaysOnTop", "AlwaysOnTop", "{60CD2D4F-C3B9-4897-9821-FCA5098B41CE}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlwaysOnTop", "src\modules\alwaysontop\AlwaysOnTop\AlwaysOnTop.vcxproj", "{1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlwaysOnTopModuleInterface", "src\modules\alwaysontop\AlwaysOnTopModuleInterface\AlwaysOnTopModuleInterface.vcxproj", "{48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.WebSearch", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.WebSearch\Community.PowerToys.Run.Plugin.WebSearch.csproj", "{9F94B303-5E21-4364-9362-64426F8DB932}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MousePointerCrosshairs", "src\modules\MouseUtils\MousePointerCrosshairs\MousePointerCrosshairs.vcxproj", "{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StlThumbnailProvider", "src\modules\previewpane\StlThumbnailProvider\StlThumbnailProvider.csproj", "{F7C8C0F1-5431-4347-89D0-8E5354F93CF2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.StlThumbnailProvider.UnitTests", "src\modules\previewpane\UnitTests-StlThumbnailProvider\Preview.StlThumbnailProvider.UnitTests.csproj", "{F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonacoPreviewHandler", "src\modules\previewpane\MonacoPreviewHandler\MonacoPreviewHandler.csproj", "{04B193D7-3E21-46B8-A958-89B63A8A69DE}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.TimeDate", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.TimeDate\Microsoft.PowerToys.Run.Plugin.TimeDate.csproj", "{5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests\Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.csproj", "{FD464B4C-2F68-4D06-91E7-4208146C41F5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.WindowWalker.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.WindowWalker.UnitTests\Microsoft.Plugin.WindowWalker.UnitTests.csproj", "{8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings", "src\settings-ui\Settings.UI\PowerToys.Settings.csproj", "{020A7474-3601-4160-A159-D7B70B77B15F}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameUI", "src\modules\powerrename\PowerRenameUILib\PowerRenameUI.vcxproj", "{27718999-C175-450A-861C-89F911E16A88}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameContextMenu", "src\modules\powerrename\PowerRenameContextMenu\PowerRenameContextMenu.vcxproj", "{1DBBB112-4BB1-444B-8EBB-E66555C76BA6}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.OneNote", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.OneNote\Microsoft.PowerToys.Run.Plugin.OneNote.csproj", "{5A1DB2F0-0715-4B3B-98E6-79BC41540045}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerContextMenu", "src\modules\imageresizer\ImageResizerContextMenu\ImageResizerContextMenu.vcxproj", "{93B72A06-C8BD-484F-A6F7-C9F280B150BF}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerLib", "src\modules\imageresizer\ImageResizerLib\ImageResizerLib.vcxproj", "{18B3DB45-4FFE-4D01-97D6-5223FEEE1853}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerAccent", "PowerAccent", "{0F14491C-6369-4C45-AAA8-135814E66E6B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerAccentModuleInterface", "src\modules\poweraccent\PowerAccentModuleInterface\PowerAccentModuleInterface.vcxproj", "{34A354C5-23C7-4343-916C-C52DAF4FC39D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerAccent.Core", "src\modules\poweraccent\PowerAccent.Core\PowerAccent.Core.csproj", "{3264DF53-C805-4B0C-867C-FCEAF7AEF762}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerAccent.UI", "src\modules\poweraccent\PowerAccent.UI\PowerAccent.UI.csproj", "{31CAD28E-778A-441C-85BC-40AB3EAA2A10}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerOCR", "PowerOCR", "{A50C70A6-2DA0-4027-B90E-B1A40755A8A5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerOCR", "src\modules\PowerOCR\PowerOCR\PowerOCR.csproj", "{25C91A4E-BA4E-467A-85CD-8B62545BF674}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerOCRModuleInterface", "src\modules\PowerOCR\PowerOCRModuleInterface\PowerOCRModuleInterface.vcxproj", "{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B1234567-1234-1234-1234-123456789ABC}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.History", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.History\Microsoft.PowerToys.Run.Plugin.History.csproj", "{212AD910-8488-4036-BE20-326931B75FB2}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MeasureTool", "MeasureTool", "{7AC943C9-52E8-44CF-9083-744D8049667B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.MeasureToolCore", "src\modules\MeasureTool\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj", "{54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}"
- ProjectSection(ProjectDependencies) = postProject
- {6955446D-23F7-4023-9BB3-8657F904AF99} = {6955446D-23F7-4023-9BB3-8657F904AF99}
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9} = {CABA8DFB-823B-4BF2-93AC-3F31984150D9}
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF} = {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MeasureToolModuleInterface", "src\modules\MeasureTool\MeasureToolModuleInterface\MeasureToolModuleInterface.vcxproj", "{92C39820-9F84-4529-BC7D-22AAE514D63B}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeasureToolUI", "src\modules\MeasureTool\MeasureToolUI\MeasureToolUI.csproj", "{515554D1-D004-4F7F-A107-2211FC0F6B2C}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerAccentKeyboardService", "src\modules\poweraccent\PowerAccentKeyboardService\PowerAccentKeyboardService.vcxproj", "{C97D9A5D-206C-454E-997E-009E227D7F02}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostsUILib", "src\modules\Hosts\HostsUILib\HostsUILib.csproj", "{31D1C81D-765F-4446-AA62-E743F6325049}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostsEditor.UnitTests", "src\modules\Hosts\Hosts.Tests\HostsEditor.UnitTests.csproj", "{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HostsModuleInterface", "src\modules\Hosts\HostsModuleInterface\HostsModuleInterface.vcxproj", "{B41B888C-7DB8-4747-B262-4062E05A230D}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileLocksmith", "FileLocksmith", "{AB82E5DD-C32D-4F28-9746-2C780846188E}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithExt", "src\modules\FileLocksmith\FileLocksmithExt\FileLocksmithExt.vcxproj", "{57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLocksmithUI", "src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj", "{E69B044A-2F8A-45AA-AD0B-256C59421807}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.FileLocksmithLib.Interop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GPOWrapperProjection", "src\common\GPOWrapperProjection\GPOWrapperProjection.csproj", "{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Peek", "Peek", "{17B4FA70-001E-4D33-BBBB-0D142DBC2E20}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Peek", "src\modules\peek\peek\peek.vcxproj", "{A1425B53-3D61-4679-8623-E64A0D3D0A48}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.UI", "src\modules\peek\Peek.UI\Peek.UI.csproj", "{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.Common", "src\modules\peek\Peek.Common\Peek.Common.csproj", "{17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.FilePreviewer", "src\modules\peek\Peek.FilePreviewer\Peek.FilePreviewer.csproj", "{AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peek.UITests", "src\modules\peek\Peek.UITests\Peek.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MarkdownPreviewHandlerCpp", "src\modules\previewpane\MarkdownPreviewHandlerCpp\MarkdownPreviewHandlerCpp.vcxproj", "{ED9A1AC6-AEB0-4569-A6E9-E1696182B545}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodePreviewHandlerCpp", "src\modules\previewpane\GcodePreviewHandlerCpp\GcodePreviewHandlerCpp.vcxproj", "{5A5DD09D-723A-44D3-8F2B-293584C3D731}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MonacoPreviewHandlerCpp", "src\modules\previewpane\MonacoPreviewHandlerCpp\MonacoPreviewHandlerCpp.vcxproj", "{B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PdfPreviewHandlerCpp", "src\modules\previewpane\PdfPreviewHandlerCpp\PdfPreviewHandlerCpp.vcxproj", "{54F7C616-FD41-4E62-BFF9-015686914F4D}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SvgPreviewHandlerCpp", "src\modules\previewpane\SvgPreviewHandlerCpp\SvgPreviewHandlerCpp.vcxproj", "{143F13E3-D2E3-4D83-B035-356612D99956}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GcodeThumbnailProviderCpp", "src\modules\previewpane\GcodeThumbnailProviderCpp\GcodeThumbnailProviderCpp.vcxproj", "{56CC2F10-6E41-453D-BE16-C593A5E58482}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PdfThumbnailProviderCpp", "src\modules\previewpane\PdfThumbnailProviderCpp\PdfThumbnailProviderCpp.vcxproj", "{CA5518ED-0458-4B09-8F53-4122B9888655}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StlThumbnailProviderCpp", "src\modules\previewpane\StlThumbnailProviderCpp\StlThumbnailProviderCpp.vcxproj", "{D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SvgThumbnailProviderCpp", "src\modules\previewpane\SvgThumbnailProviderCpp\SvgThumbnailProviderCpp.vcxproj", "{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseWithoutBorders", "MouseWithoutBorders", "{B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseWithoutBordersModuleInterface", "src\modules\MouseWithoutBorders\ModuleInterface\MouseWithoutBordersModuleInterface.vcxproj", "{2833C9C6-AB32-4048-A5C7-A70898337B57}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders", "src\modules\MouseWithoutBorders\App\MouseWithoutBorders.csproj", "{50B82783-242F-42D2-BC03-B3430BF01354}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBordersService", "src\modules\MouseWithoutBorders\App\Service\MouseWithoutBordersService.csproj", "{B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBordersHelper", "src\modules\MouseWithoutBorders\App\Helper\MouseWithoutBordersHelper.csproj", "{A663E672-B26D-4EC0-BEAB-FE2E424AC46F}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseJump", "src\modules\MouseUtils\MouseJump\MouseJump.vcxproj", "{8A08D663-4995-40E3-B42C-3F910625F284}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJump.Common", "src\modules\MouseUtils\MouseJump.Common\MouseJump.Common.csproj", "{923DF87C-CA99-4D1C-B1D2-959174E95BFA}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJump.Common.UnitTests", "src\modules\MouseUtils\MouseJump.Common.UnitTests\MouseJump.Common.UnitTests.csproj", "{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI", "src\modules\MouseUtils\MouseJumpUI\MouseJumpUI.csproj", "{D962A009-834F-4EEC-AABB-430DF8F98E39}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdvancedPaste", "AdvancedPaste", "{9873BA05-4C41-4819-9283-CF45D795431B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AdvancedPasteModuleInterface", "src\modules\AdvancedPaste\AdvancedPasteModuleInterface\AdvancedPasteModuleInterface.vcxproj", "{FC373B24-3293-453C-AAF5-CF2909DCEE6A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllExperiments", "src\common\AllExperiments\AllExperiments.csproj", "{9CE59ED5-7087-4353-88EB-788038A73CEC}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryPreviewUILib", "src\modules\registrypreview\RegistryPreviewUILib\RegistryPreviewUILib.csproj", "{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RegistryPreviewExt", "src\modules\registrypreview\RegistryPreviewExt\RegistryPreviewExt.vcxproj", "{697C6AF9-0A48-49A9-866C-67DA12384015}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RegistryPreview", "RegistryPreview", "{929C1324-22E8-4412-A9A8-80E85F3985A5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilePreviewCommon", "src\common\FilePreviewCommon\FilePreviewCommon.csproj", "{9EBAA524-0EDA-470B-95D4-39383285CBB2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.PowerToys", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.csproj", "{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.csproj", "{D095BE44-1F2E-463E-A494-121892A75EA2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj", "{90F9FA90-2C20-4004-96E6-F3B78151F5A5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CropAndLock", "CropAndLock", "{3B227528-4BA6-4CAF-B44A-A10C78A64849}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLock", "src\modules\CropAndLock\CropAndLock\CropAndLock.vcxproj", "{F5E1146E-B7B3-4E11-85FD-270A500BD78C}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLockModuleInterface", "src\modules\CropAndLock\CropAndLockModuleInterface\CropAndLockModuleInterface.vcxproj", "{3157FA75-86CF-4EE2-8F62-C43F776493C6}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cmdNotFound", "cmdNotFound", "{4C0D0746-BE5B-49EE-BD5D-A7811628AE8B}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvironmentVariables", "EnvironmentVariables", "{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariablesUILib", "src\modules\EnvironmentVariables\EnvironmentVariablesUILib\EnvironmentVariablesUILib.csproj", "{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnvironmentVariablesModuleInterface", "src\modules\EnvironmentVariables\EnvironmentVariablesModuleInterface\EnvironmentVariablesModuleInterface.vcxproj", "{B9420661-B0E4-4241-ABD4-4A27A1F64250}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QoiThumbnailProviderCpp", "src\modules\previewpane\QoiThumbnailProviderCpp\QoiThumbnailProviderCpp.vcxproj", "{CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QoiThumbnailProvider", "src\modules\previewpane\QoiThumbnailProvider\QoiThumbnailProvider.csproj", "{D949EC7D-48A9-4279-95D5-078E7FD1F048}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QoiPreviewHandlerCpp", "src\modules\previewpane\QoiPreviewHandlerCpp\QoiPreviewHandlerCpp.vcxproj", "{3BAF9C81-A194-4925-A035-5E24A5D1E542}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QoiPreviewHandler", "src\modules\previewpane\QoiPreviewHandler\QoiPreviewHandler.csproj", "{6B04803D-B418-4833-A67E-B0FC966636A5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.QoiPreviewHandler.UnitTests", "src\modules\previewpane\UnitTests-QoiPreviewHandler\Preview.QoiPreviewHandler.UnitTests.csproj", "{3940AD4D-F748-4BE4-9083-85769CD553EF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Preview.QoiThumbnailProvider.UnitTests", "src\modules\previewpane\UnitTests-QoiThumbnailProvider\Preview.QoiThumbnailProvider.UnitTests.csproj", "{F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdNotFoundModuleInterface", "src\modules\cmdNotFound\CmdNotFoundModuleInterface\CmdNotFoundModuleInterface.vcxproj", "{0014D652-901F-4456-8D65-06FC5F997FB0}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithContextMenu", "src\modules\FileLocksmith\FileLocksmithContextMenu\FileLocksmithContextMenu.vcxproj", "{799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLib", "src\modules\FileLocksmith\FileLocksmithLib\FileLocksmithLib.vcxproj", "{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedPaste", "src\modules\AdvancedPaste\AdvancedPaste\AdvancedPaste.csproj", "{C32D254F-7597-4CBE-BF74-D922D81CDF29}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts", "src\modules\Hosts\Hosts\Hosts.csproj", "{02DD46D3-F761-47D9-8894-2D6DA0124650}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryPreview", "src\modules\registrypreview\RegistryPreview\RegistryPreview.csproj", "{8E23E173-7127-4A5F-9F93-3049F2B68047}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariables", "src\modules\EnvironmentVariables\EnvironmentVariables\EnvironmentVariables.csproj", "{DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditorCommon", "src\modules\fancyzones\FancyZonesEditorCommon\FancyZonesEditorCommon.csproj", "{C0974915-8A1D-4BF0-977B-9587D3807AB7}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DSC", "DSC", "{557C4636-D7E1-4838-A504-7D19B725EE95}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Schema.Generator", "src\dsc\PowerToys.Settings.DSC.Schema.Generator\PowerToys.Settings.DSC.Schema.Generator.csproj", "{1D6893CB-BC0C-46A8-A76C-9728706CA51A}"
- ProjectSection(ProjectDependencies) = postProject
- {020A7474-3601-4160-A159-D7B70B77B15F} = {020A7474-3601-4160-A159-D7B70B77B15F}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension", "src\modules\NewPlus\NewShellExtensionContextMenu\NewShellExtensionContextMenu.vcxproj", "{8ACB33D9-C95B-47D4-8363-9731EE0930A0}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "New+", "New+", "{CA716AE6-FE5C-40AC-BB8F-2C87912687AC}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "src\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workspaces", "Workspaces", "{A2221D7E-55E7-4BEA-90D1-4F162D670BBF}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workspaces-common", "workspaces-common", "{BE126CBB-AE12-406A-9837-A05ACFCA57A7}"
- ProjectSection(SolutionItems) = preProject
- src\modules\Workspaces\workspaces-common\GuidUtils.h = src\modules\Workspaces\workspaces-common\GuidUtils.h
- src\modules\Workspaces\workspaces-common\InvokePoint.h = src\modules\Workspaces\workspaces-common\InvokePoint.h
- src\modules\Workspaces\workspaces-common\MonitorEnumerator.h = src\modules\Workspaces\workspaces-common\MonitorEnumerator.h
- src\modules\Workspaces\workspaces-common\MonitorUtils.h = src\modules\Workspaces\workspaces-common\MonitorUtils.h
- src\modules\Workspaces\workspaces-common\VirtualDesktop.h = src\modules\Workspaces\workspaces-common\VirtualDesktop.h
- src\modules\Workspaces\workspaces-common\WindowEnumerator.h = src\modules\Workspaces\workspaces-common\WindowEnumerator.h
- src\modules\Workspaces\workspaces-common\WindowFilter.h = src\modules\Workspaces\workspaces-common\WindowFilter.h
- src\modules\Workspaces\workspaces-common\WindowUtils.h = src\modules\Workspaces\workspaces-common\WindowUtils.h
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowProperties", "WindowProperties", "{14CB58B7-D280-4A7A-95DE-4B2DF14EA000}"
- ProjectSection(SolutionItems) = preProject
- src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h = src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLib", "src\modules\Workspaces\WorkspacesLib\WorkspacesLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Workspaces.Lib.UnitTests", "src\modules\Workspaces\WorkspacesLib.UnitTests\WorkspacesLibUnitTests.vcxproj", "{A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesLauncherUI", "src\modules\Workspaces\WorkspacesLauncherUI\WorkspacesLauncherUI.csproj", "{9C53CC25-0623-4569-95BC-B05410675EE3}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesModuleInterface", "src\modules\Workspaces\WorkspacesModuleInterface\WorkspacesModuleInterface.vcxproj", "{45285DF2-9742-4ECA-9AC9-58951FC26489}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesSnapshotTool", "src\modules\Workspaces\WorkspacesSnapshotTool\WorkspacesSnapshotTool.vcxproj", "{3D63307B-9D27-44FD-B033-B26F39245B85}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesEditor", "src\modules\Workspaces\WorkspacesEditor\WorkspacesEditor.csproj", "{367D7543-7DBA-4381-99F1-BF6142A996C4}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\modules\Workspaces\WorkspacesLauncher\WorkspacesLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesWindowArranger", "src\modules\Workspaces\WorkspacesWindowArranger\WorkspacesWindowArranger.vcxproj", "{37D07516-4185-43A4-924F-3C7A5D95ECF6}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseWithoutBorders.UnitTests", "src\modules\MouseWithoutBorders\MouseWithoutBorders.UnitTests\MouseWithoutBorders.UnitTests.csproj", "{66614C26-314C-4B91-9071-76133422CFEF}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CommandPalette", "CommandPalette", "{3846508C-77EB-4034-A702-F8BB263C4F79}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Built-in Extensions", "Built-in Extensions", "{ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.Apps", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.Apps\Microsoft.CmdPal.Ext.Apps.csproj", "{6CE438DF-C245-4997-A360-0A0939E4BA34}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.Bookmarks", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.Bookmark\Microsoft.CmdPal.Ext.Bookmarks.csproj", "{E09AA983-C755-474F-83D6-A5CDF528C070}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.Calc", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.Calc\Microsoft.CmdPal.Ext.Calc.csproj", "{6D56B64D-FF1F-488F-AFED-9B9854A5D399}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.Registry", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.Registry\Microsoft.CmdPal.Ext.Registry.csproj", "{92EC89E4-9972-453A-8A1A-3A9E230C146A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.WindowsServices", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.WindowsServices\Microsoft.CmdPal.Ext.WindowsServices.csproj", "{51939B4F-1F62-4BFF-A6A2-C08646E5BE95}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.WindowsSettings", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.WindowsSettings\Microsoft.CmdPal.Ext.WindowsSettings.csproj", "{D1160404-D3D1-497A-883A-4059C07C2273}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.WindowsTerminal", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.WindowsTerminal\Microsoft.CmdPal.Ext.WindowsTerminal.csproj", "{40F6D69D-E321-400F-A767-5628C7AE453D}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extension SDK", "Extension SDK", "{F3D09629-59A2-4924-A4B9-D6BFAA2C1B49}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.CommandPalette.Extensions", "src\modules\cmdpal\extensionsdk\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.vcxproj", "{305DD37E-C85D-4B08-AAFE-7381FA890463}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CommandPalette.Extensions.Toolkit", "src\modules\cmdpal\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj", "{CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Core.Common", "src\modules\cmdpal\Core\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj", "{14E62033-58D0-4A7D-8990-52F50A08BBBD}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.UI", "src\modules\cmdpal\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj", "{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sample Extensions", "Sample Extensions", "{071E18A4-A530-46B8-AB7D-B862EE55E24E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProcessMonitorExtension", "src\modules\cmdpal\ext\ProcessMonitorExtension\ProcessMonitorExtension.csproj", "{C846F7A7-792A-47D9-B0CB-417C900EE03D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SamplePagesExtension", "src\modules\cmdpal\ext\SamplePagesExtension\SamplePagesExtension.csproj", "{C831231F-891C-4572-9694-45062534B42A}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{7520A2FE-00A2-49B8-83ED-DB216E874C04}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.UI", "src\modules\cmdpal\Microsoft.CmdPal.UI\Microsoft.CmdPal.UI.csproj", "{8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.UI.ViewModels", "src\modules\cmdpal\Microsoft.CmdPal.UI.ViewModels\Microsoft.CmdPal.UI.ViewModels.csproj", "{C66020D1-CB10-4CF7-8715-84C97FD5E5E2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.ClipboardHistory", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj", "{79775343-7A3D-445D-9104-3DD5B2893DF9}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalModuleInterface", "src\modules\cmdpal\CmdPalModuleInterface\CmdPalModuleInterface.vcxproj", "{0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkspacesCsharpLibrary", "src\modules\Workspaces\WorkspacesCsharpLibrary\WorkspacesCsharpLibrary.csproj", "{89D0E199-B17A-418C-B2F8-7375B6708357}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension.win10", "src\modules\NewPlus\NewShellExtensionContextMenu.win10\NewPlus.ShellExtension.win10.vcxproj", "{0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Indexer", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj", "{453CBB73-A3CB-4D0B-8D24-6940B86FE21D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.Shell", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.Shell\Microsoft.CmdPal.Ext.Shell.csproj", "{C0CE3B5E-16D3-495D-B335-CA791B660162}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Ext.WindowWalker", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.WindowWalker\Microsoft.CmdPal.Ext.WindowWalker.csproj", "{3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSearch", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.WebSearch\Microsoft.CmdPal.Ext.WebSearch.csproj", "{605E914B-7232-4789-AF46-BF5D3DDFC14E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WinGet", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.WinGet\Microsoft.CmdPal.Ext.WinGet.csproj", "{E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ZoomIt", "ZoomIt", "{DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomIt", "src\modules\ZoomIt\ZoomIt\ZoomIt.vcxproj", "{0A84F764-3A88-44CD-AA96-41BDBD48627B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomItModuleInterface", "src\modules\ZoomIt\ZoomItModuleInterface\ZoomItModuleInterface.vcxproj", "{E4585179-2AC1-4D5F-A3FF-CFC5392F694C}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZoomItSettingsInterop", "src\modules\ZoomIt\ZoomItSettingsInterop\ZoomItSettingsInterop.vcxproj", "{CA7D8106-30B9-4AEC-9D05-B69B31B8C461}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDate", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.TimeDate\Microsoft.CmdPal.Ext.TimeDate.csproj", "{DCC6BD67-17BB-47AA-B507-FB0FE43A7449}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITestAutomation", "src\common\UITestAutomation\UITestAutomation.csproj", "{A558C25D-2007-498E-8B6F-43405AFAE9E2}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeyboardManagerEditorUI", "src\modules\keyboardmanager\KeyboardManagerEditorUI\KeyboardManagerEditorUI.csproj", "{08F9155D-B6DC-46E5-9C83-AF60B655898B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibraryWrapper", "src\modules\keyboardmanager\KeyboardManagerEditorLibraryWrapper\KeyboardManagerEditorLibraryWrapper.vcxproj", "{4382A954-179A-4078-92AF-715187DFFF50}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostsEditor.FuzzTests", "src\modules\Hosts\Hosts.FuzzTests\HostsEditor.FuzzTests.csproj", "{EBED240C-8702-452D-B764-6DB9DA9179AF}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostsEditor.UITests", "src\modules\Hosts\Hosts.UITests\HostsEditor.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RegistryPreview.FuzzTests", "src\modules\registrypreview\RegistryPreview.FuzzTests\RegistryPreview.FuzzTests.csproj", "{5702B3CC-8575-48D5-83D8-15BB42269CD3}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.System", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj", "{64B88F02-CD88-4ED8-9624-989A800230F9}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "src\modules\cmdpal\CmdPalKeyboardService\CmdPalKeyboardService.vcxproj", "{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzTests", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodePreviewHandler", "src\modules\previewpane\BgcodePreviewHandler\BgcodePreviewHandler.csproj", "{9E0CBC06-F29A-4810-B93C-97D53863B95E}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodePreviewHandlerCpp", "src\modules\previewpane\BgcodePreviewHandlerCpp\BgcodePreviewHandlerCpp.vcxproj", "{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodeThumbnailProviderCpp", "src\modules\previewpane\BgcodeThumbnailProviderCpp\BgcodeThumbnailProviderCpp.vcxproj", "{47B0678C-806B-4FE1-9F50-46BA88989532}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodeThumbnailProvider", "src\modules\previewpane\BgcodeThumbnailProvider\BgcodeThumbnailProvider.csproj", "{9BC1C986-1E97-4D07-A7B1-CE226C239EFA}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Preview.BgcodePreviewHandler.UnitTests", "src\modules\previewpane\UnitTests-BgcodePreviewHandler\Preview.BgcodePreviewHandler.UnitTests.csproj", "{99CA1509-FB73-456E-AFAF-AB89C017BD72}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Preview.BgcodeThumbnailProvider.UnitTests", "src\modules\previewpane\UnitTests-BgcodeThumbnailProvider\Preview.BgcodeThumbnailProvider.UnitTests.csproj", "{61CBF221-9452-4934-B685-146285E080D7}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Search", "src\common\Common.Search\Common.Search.csproj", "{38F187B2-6638-5A40-072F-DBE5E54070A0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Settings.UI.XamlIndexBuilder", "src\settings-ui\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj", "{DA0744BC-E822-680E-9CEB-D0FBA903A8EE}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MouseUtils.UITests", "src\modules\MouseUtils\MouseUtils.UITests\MouseUtils.UITests.csproj", "{4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workspaces.Editor.UITests", "src\modules\Workspaces\WorkspacesEditorUITest\Workspaces.Editor.UITests.csproj", "{43E779F3-D83C-48B1-BA8D-1912DBD76FC9}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CalculatorEngineCommon", "src\common\CalculatorEngineCommon\CalculatorEngineCommon.vcxproj", "{2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCsWin32", "src\common\ManagedCsWin32\ManagedCsWin32.csproj", "{14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerRename.UITests", "src\modules\powerrename\PowerRenameUITest\PowerRename.UITests.csproj", "{9D3F3793-EFE3-4525-8782-238015DABA62}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Core.ViewModels", "src\modules\cmdpal\Core\Microsoft.CmdPal.Core.ViewModels\Microsoft.CmdPal.Core.ViewModels.csproj", "{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{0E556541-6A45-42CB-AE49-EE5A9BE05E7C}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{27D9CB3A-46D1-402C-9273-F88CB8AC42F7}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B9617A31-0F0A-4397-851D-BF2FBEE32D7F}"
- ProjectSection(SolutionItems) = preProject
- src\modules\launcher\Plugins\Microsoft.Plugin.Folder.UnitTests\Microsoft.Plugin.Folder.UnitTests.csproj = src\modules\launcher\Plugins\Microsoft.Plugin.Folder.UnitTests\Microsoft.Plugin.Folder.UnitTests.csproj
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1C48CD47-D610-463A-A53C-AF82DD6C47E7}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D9BD324E-1D80-44AA-8E7B-73EB00944434}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8EF25507-2575-4ADE-BF7E-D23376903AB8}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerOCR.UITests", "src\modules\PowerOCR\PowerOCR-UITests\PowerOCR.UITests.csproj", "{070AC093-C9F2-20AD-0BCD-F318FC2761EA}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2C318EC3-BA86-4372-B1BC-DB0F33C208B2}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BFFB607F-7C78-434B-86B9-DA4C8196A1B5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{66E1534A-1587-42B2-912F-45C994D32904}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E885E71F-0B34-4A03-B13B-20F4E05E90BB}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{264B412F-DB8B-4CF8-A74B-96998B183045}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3527BF37-DFC5-4309-A032-29278CA21328}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{68328142-5B31-4715-BCBB-7B6345EE0971}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.FuzzTests", "src\modules\AdvancedPaste\AdvancedPaste.FuzzTests\AdvancedPaste.FuzzTests.csproj", "{4122388B-59E4-CDB0-0338-EA6881DF86F0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste.UnitTests", "src\modules\AdvancedPaste\AdvancedPaste.UnitTests\AdvancedPaste.UnitTests.csproj", "{988C9FAF-5AEC-EB15-578D-FED0DF52BF55}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UITests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UITests\Microsoft.CmdPal.UITests.csproj", "{6748A29D-DA6A-033A-825B-752295FF6AA0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZones.FuzzTests", "src\modules\fancyzones\FancyZones.FuzzTests\FancyZones.FuzzTests.csproj", "{6EABCF9A-6526-441F-932F-658B1DC3E403}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZones.UITests", "src\modules\fancyzones\FancyZones.UITests\FancyZones.UITests.csproj", "{69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor.UITests", "src\modules\fancyzones\FancyZonesEditor.UITests\FancyZonesEditor.UITests.csproj", "{9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FancyZonesEditor.UnitTests", "src\modules\fancyzones\FancyZonesEditor.UnitTests\FancyZonesEditor.UnitTests.csproj", "{806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPickerUI.UnitTests", "src\modules\colorPicker\ColorPickerUI.UnitTests\ColorPickerUI.UnitTests.csproj", "{F93C2817-C846-4259-84D8-B39A6B57C8DE}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{8131151D-B0E9-4E18-84A5-E5F946C4480A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Calc.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Calc.UnitTests\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj", "{E816D7AC-4688-4ECB-97CC-3D8E798F3825}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Registry.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Registry.UnitTests\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj", "{E816D7AD-4688-4ECB-97CC-3D8E798F3826}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.System.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.System.UnitTests\Microsoft.CmdPal.Ext.System.UnitTests.csproj", "{E816D7AE-4688-4ECB-97CC-3D8E798F3827}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDate.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.TimeDate.UnitTests\Microsoft.CmdPal.Ext.TimeDate.UnitTests.csproj", "{E816D7AF-4688-4ECB-97CC-3D8E798F3828}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LightSwitch", "LightSwitch", "{5B201255-53C8-490B-A34F-01F05D48A477}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchModuleInterface", "src\modules\LightSwitch\LightSwitchModuleInterface\LightSwitchModuleInterface.vcxproj", "{38177D56-6AD1-4ADF-88C9-2843A7932166}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LightSwitchService", "src\modules\LightSwitch\LightSwitchService\LightSwitchService.vcxproj", "{08E71C67-6A7E-4CA1-B04E-2FB336410BAC}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v3", "v3", "{9605B84E-FAC4-477B-B9EC-0753177EE6A8}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.DSC", "src\dsc\v3\PowerToys.DSC\PowerToys.DSC.csproj", "{94CDC147-6137-45E9-AEDE-17FF809607C0}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.DSC.UnitTests", "src\dsc\v3\PowerToys.DSC.UnitTests\PowerToys.DSC.UnitTests.csproj", "{A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}"
-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}"
-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}"
-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}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3DCCD936-D085-4869-A1DE-CA6A64152C94}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LightSwitch.UITests", "src\modules\LightSwitch\Tests\LightSwitch.UITests\LightSwitch.UITests.csproj", "{F5333ED7-06D8-4AB3-953A-36D63F08CB6F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj", "{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UI.ViewModels.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UI.ViewModels.UnitTests\Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj", "{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|ARM64 = Debug|ARM64
- Debug|x64 = Debug|x64
- Release|ARM64 = Release|ARM64
- Release|x64 = Release|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|ARM64.Build.0 = Debug|ARM64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.ActiveCfg = Debug|x64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.Build.0 = Debug|x64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|ARM64.ActiveCfg = Release|ARM64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|ARM64.Build.0 = Release|ARM64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.ActiveCfg = Release|x64
- {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.Build.0 = Release|x64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|ARM64.Build.0 = Debug|ARM64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.ActiveCfg = Debug|x64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.Build.0 = Debug|x64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|ARM64.ActiveCfg = Release|ARM64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|ARM64.Build.0 = Release|ARM64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.ActiveCfg = Release|x64
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.Build.0 = Release|x64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|ARM64.Build.0 = Debug|ARM64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.ActiveCfg = Debug|x64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.Build.0 = Debug|x64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|ARM64.ActiveCfg = Release|ARM64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|ARM64.Build.0 = Release|ARM64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.ActiveCfg = Release|x64
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.Build.0 = Release|x64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|ARM64.Build.0 = Debug|ARM64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x64.ActiveCfg = Debug|x64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Debug|x64.Build.0 = Debug|x64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|ARM64.ActiveCfg = Release|ARM64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|ARM64.Build.0 = Release|ARM64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.ActiveCfg = Release|x64
- {1A066C63-64B3-45F8-92FE-664E1CCE8077}.Release|x64.Build.0 = Release|x64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|ARM64.Build.0 = Debug|ARM64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.ActiveCfg = Debug|x64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Debug|x64.Build.0 = Debug|x64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.ActiveCfg = Release|ARM64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|ARM64.Build.0 = Release|ARM64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.ActiveCfg = Release|x64
- {E2A5A82E-1E5B-4C8D-9A4F-2B1A8F9E5C3D}.Release|x64.Build.0 = Release|x64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|ARM64.Build.0 = Debug|ARM64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.ActiveCfg = Debug|x64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Debug|x64.Build.0 = Debug|x64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|ARM64.ActiveCfg = Release|ARM64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|ARM64.Build.0 = Release|ARM64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|ARM64.Build.0 = Debug|ARM64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.ActiveCfg = Debug|x64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.Build.0 = Debug|x64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|ARM64.ActiveCfg = Release|ARM64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|ARM64.Build.0 = Release|ARM64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x64.ActiveCfg = Release|x64
- {B25AC7A5-FB9F-4789-B392-D5C85E948670}.Release|x64.Build.0 = Release|x64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|ARM64.Build.0 = Debug|ARM64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x64.ActiveCfg = Debug|x64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Debug|x64.Build.0 = Debug|x64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|ARM64.ActiveCfg = Release|ARM64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|ARM64.Build.0 = Release|ARM64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.ActiveCfg = Release|x64
- {51920F1F-C28C-4ADF-8660-4238766796C2}.Release|x64.Build.0 = Release|x64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|ARM64.Build.0 = Debug|ARM64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.ActiveCfg = Debug|x64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Debug|x64.Build.0 = Debug|x64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|ARM64.ActiveCfg = Release|ARM64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|ARM64.Build.0 = Release|ARM64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x64.ActiveCfg = Release|x64
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}.Release|x64.Build.0 = Release|x64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|ARM64.Build.0 = Debug|ARM64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x64.ActiveCfg = Debug|x64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Debug|x64.Build.0 = Debug|x64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|ARM64.ActiveCfg = Release|ARM64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|ARM64.Build.0 = Release|ARM64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x64.ActiveCfg = Release|x64
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413}.Release|x64.Build.0 = Release|x64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|ARM64.Build.0 = Debug|ARM64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|x64.ActiveCfg = Debug|x64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Debug|x64.Build.0 = Debug|x64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|ARM64.ActiveCfg = Release|ARM64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|ARM64.Build.0 = Release|ARM64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|x64.ActiveCfg = Release|x64
- {64A80062-4D8B-4229-8A38-DFA1D7497749}.Release|x64.Build.0 = Release|x64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|ARM64.Build.0 = Debug|ARM64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|x64.ActiveCfg = Debug|x64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Debug|x64.Build.0 = Debug|x64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|ARM64.ActiveCfg = Release|ARM64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|ARM64.Build.0 = Release|ARM64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|x64.ActiveCfg = Release|x64
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9}.Release|x64.Build.0 = Release|x64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|ARM64.Build.0 = Debug|ARM64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.ActiveCfg = Debug|x64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.Build.0 = Debug|x64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|ARM64.ActiveCfg = Release|ARM64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|ARM64.Build.0 = Release|ARM64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.ActiveCfg = Release|x64
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.Build.0 = Release|x64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|ARM64.Build.0 = Debug|ARM64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.ActiveCfg = Debug|x64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.Build.0 = Debug|x64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|ARM64.ActiveCfg = Release|ARM64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|ARM64.Build.0 = Release|ARM64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.ActiveCfg = Release|x64
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.Build.0 = Release|x64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|ARM64.Build.0 = Debug|ARM64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.ActiveCfg = Debug|x64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.Build.0 = Debug|x64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|ARM64.ActiveCfg = Release|ARM64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|ARM64.Build.0 = Release|ARM64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.ActiveCfg = Release|x64
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.Build.0 = Release|x64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|ARM64.Build.0 = Debug|ARM64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.ActiveCfg = Debug|x64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.Build.0 = Debug|x64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|ARM64.ActiveCfg = Release|ARM64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|ARM64.Build.0 = Release|ARM64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x64.ActiveCfg = Release|x64
- {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x64.Build.0 = Release|x64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|ARM64.Build.0 = Debug|ARM64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|x64.ActiveCfg = Debug|x64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Debug|x64.Build.0 = Debug|x64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|ARM64.ActiveCfg = Release|ARM64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|ARM64.Build.0 = Release|ARM64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|x64.ActiveCfg = Release|x64
- {17DA04DF-E393-4397-9CF0-84DABE11032E}.Release|x64.Build.0 = Release|x64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|ARM64.Build.0 = Debug|ARM64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|x64.ActiveCfg = Debug|x64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Debug|x64.Build.0 = Debug|x64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|ARM64.ActiveCfg = Release|ARM64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|ARM64.Build.0 = Release|ARM64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|x64.ActiveCfg = Release|x64
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}.Release|x64.Build.0 = Release|x64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|ARM64.Build.0 = Debug|ARM64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.ActiveCfg = Debug|x64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.Build.0 = Debug|x64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|ARM64.ActiveCfg = Release|ARM64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|ARM64.Build.0 = Release|ARM64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.ActiveCfg = Release|x64
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Release|x64.Build.0 = Release|x64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|ARM64.Build.0 = Debug|ARM64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|x64.ActiveCfg = Debug|x64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Debug|x64.Build.0 = Debug|x64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|ARM64.ActiveCfg = Release|ARM64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|ARM64.Build.0 = Release|ARM64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.ActiveCfg = Release|x64
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.Build.0 = Release|x64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|ARM64.Build.0 = Debug|ARM64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.ActiveCfg = Debug|x64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.Build.0 = Debug|x64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|ARM64.ActiveCfg = Release|ARM64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|ARM64.Build.0 = Release|ARM64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|x64.ActiveCfg = Release|x64
- {FF742965-9A80-41A5-B042-D6C7D3A21708}.Release|x64.Build.0 = Release|x64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|ARM64.Build.0 = Debug|ARM64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|x64.ActiveCfg = Debug|x64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|x64.Build.0 = Debug|x64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|ARM64.ActiveCfg = Release|ARM64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|ARM64.Build.0 = Release|ARM64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x64.ActiveCfg = Release|x64
- {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Release|x64.Build.0 = Release|x64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|ARM64.Build.0 = Debug|ARM64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|x64.ActiveCfg = Debug|x64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Debug|x64.Build.0 = Debug|x64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|ARM64.ActiveCfg = Release|ARM64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|ARM64.Build.0 = Release|ARM64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|x64.ActiveCfg = Release|x64
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51}.Release|x64.Build.0 = Release|x64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|ARM64.Build.0 = Debug|ARM64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|x64.ActiveCfg = Debug|x64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Debug|x64.Build.0 = Debug|x64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|ARM64.ActiveCfg = Release|ARM64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|ARM64.Build.0 = Release|ARM64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|x64.ActiveCfg = Release|x64
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E}.Release|x64.Build.0 = Release|x64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|ARM64.Build.0 = Debug|ARM64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|x64.ActiveCfg = Debug|x64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Debug|x64.Build.0 = Debug|x64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|ARM64.ActiveCfg = Release|ARM64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|ARM64.Build.0 = Release|ARM64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|x64.ActiveCfg = Release|x64
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4}.Release|x64.Build.0 = Release|x64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|ARM64.Build.0 = Debug|ARM64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.ActiveCfg = Debug|x64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Debug|x64.Build.0 = Debug|x64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|ARM64.ActiveCfg = Release|ARM64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|ARM64.Build.0 = Release|ARM64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x64.ActiveCfg = Release|x64
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x64.Build.0 = Release|x64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|ARM64.Build.0 = Debug|ARM64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|x64.ActiveCfg = Debug|x64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Debug|x64.Build.0 = Debug|x64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|ARM64.ActiveCfg = Release|ARM64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|ARM64.Build.0 = Release|ARM64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|x64.ActiveCfg = Release|x64
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820}.Release|x64.Build.0 = Release|x64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|ARM64.Build.0 = Debug|ARM64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|x64.ActiveCfg = Debug|x64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Debug|x64.Build.0 = Debug|x64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|ARM64.ActiveCfg = Release|ARM64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|ARM64.Build.0 = Release|ARM64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|x64.ActiveCfg = Release|x64
- {E364F67B-BB12-4E91-B639-355866EBCD8B}.Release|x64.Build.0 = Release|x64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|ARM64.Build.0 = Debug|ARM64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|x64.ActiveCfg = Debug|x64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Debug|x64.Build.0 = Debug|x64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|ARM64.ActiveCfg = Release|ARM64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|ARM64.Build.0 = Release|ARM64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|x64.ActiveCfg = Release|x64
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2}.Release|x64.Build.0 = Release|x64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|ARM64.Build.0 = Debug|ARM64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.ActiveCfg = Debug|x64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.Build.0 = Debug|x64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|ARM64.ActiveCfg = Release|ARM64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|ARM64.Build.0 = Release|ARM64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.ActiveCfg = Release|x64
- {AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.Build.0 = Release|x64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|ARM64.Build.0 = Debug|ARM64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.ActiveCfg = Debug|x64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.Build.0 = Debug|x64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|ARM64.ActiveCfg = Release|ARM64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|ARM64.Build.0 = Release|ARM64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.ActiveCfg = Release|x64
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.Build.0 = Release|x64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|ARM64.Build.0 = Debug|ARM64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.ActiveCfg = Debug|x64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.Build.0 = Debug|x64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|ARM64.ActiveCfg = Release|ARM64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|ARM64.Build.0 = Release|ARM64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.ActiveCfg = Release|x64
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.Build.0 = Release|x64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|ARM64.Build.0 = Debug|ARM64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x64.ActiveCfg = Debug|x64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x64.Build.0 = Debug|x64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|ARM64.ActiveCfg = Release|ARM64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|ARM64.Build.0 = Release|ARM64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x64.ActiveCfg = Release|x64
- {DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x64.Build.0 = Release|x64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|ARM64.Build.0 = Debug|ARM64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x64.ActiveCfg = Debug|x64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x64.Build.0 = Debug|x64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|ARM64.ActiveCfg = Release|ARM64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|ARM64.Build.0 = Release|ARM64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x64.ActiveCfg = Release|x64
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x64.Build.0 = Release|x64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|ARM64.Build.0 = Debug|ARM64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x64.ActiveCfg = Debug|x64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x64.Build.0 = Debug|x64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|ARM64.ActiveCfg = Release|ARM64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|ARM64.Build.0 = Release|ARM64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x64.ActiveCfg = Release|x64
- {748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x64.Build.0 = Release|x64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|ARM64.Build.0 = Debug|ARM64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x64.ActiveCfg = Debug|x64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x64.Build.0 = Debug|x64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|ARM64.ActiveCfg = Release|ARM64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|ARM64.Build.0 = Release|ARM64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.ActiveCfg = Release|x64
- {217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.Build.0 = Release|x64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|ARM64.Build.0 = Debug|ARM64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|x64.ActiveCfg = Debug|x64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Debug|x64.Build.0 = Debug|x64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|ARM64.ActiveCfg = Release|ARM64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|ARM64.Build.0 = Release|ARM64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|x64.ActiveCfg = Release|x64
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}.Release|x64.Build.0 = Release|x64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|ARM64.Build.0 = Debug|ARM64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x64.ActiveCfg = Debug|x64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Debug|x64.Build.0 = Debug|x64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|ARM64.ActiveCfg = Release|ARM64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|ARM64.Build.0 = Release|ARM64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x64.ActiveCfg = Release|x64
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}.Release|x64.Build.0 = Release|x64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|ARM64.Build.0 = Debug|ARM64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|x64.ActiveCfg = Debug|x64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Debug|x64.Build.0 = Debug|x64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|ARM64.ActiveCfg = Release|ARM64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|ARM64.Build.0 = Release|ARM64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|x64.ActiveCfg = Release|x64
- {08C8C05F-0362-41BC-818C-724572DF8B06}.Release|x64.Build.0 = Release|x64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|ARM64.Build.0 = Debug|ARM64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|x64.ActiveCfg = Debug|x64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Debug|x64.Build.0 = Debug|x64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|ARM64.ActiveCfg = Release|ARM64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|ARM64.Build.0 = Release|ARM64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|x64.ActiveCfg = Release|x64
- {5D00D290-4016-4CFE-9E41-1E7C724509BA}.Release|x64.Build.0 = Release|x64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|ARM64.Build.0 = Debug|ARM64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x64.ActiveCfg = Debug|x64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Debug|x64.Build.0 = Debug|x64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|ARM64.ActiveCfg = Release|ARM64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|ARM64.Build.0 = Release|ARM64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.ActiveCfg = Release|x64
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C}.Release|x64.Build.0 = Release|x64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|ARM64.Build.0 = Debug|ARM64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.ActiveCfg = Debug|x64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Debug|x64.Build.0 = Debug|x64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|ARM64.ActiveCfg = Release|ARM64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|ARM64.Build.0 = Release|ARM64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.ActiveCfg = Release|x64
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1}.Release|x64.Build.0 = Release|x64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|ARM64.Build.0 = Debug|ARM64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|x64.ActiveCfg = Debug|x64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Debug|x64.Build.0 = Debug|x64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|ARM64.ActiveCfg = Release|ARM64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|ARM64.Build.0 = Release|ARM64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|x64.ActiveCfg = Release|x64
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E}.Release|x64.Build.0 = Release|x64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|ARM64.Build.0 = Debug|ARM64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|x64.ActiveCfg = Debug|x64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Debug|x64.Build.0 = Debug|x64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|ARM64.ActiveCfg = Release|ARM64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|ARM64.Build.0 = Release|ARM64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|x64.ActiveCfg = Release|x64
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD}.Release|x64.Build.0 = Release|x64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|ARM64.Build.0 = Debug|ARM64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x64.ActiveCfg = Debug|x64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Debug|x64.Build.0 = Debug|x64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|ARM64.ActiveCfg = Release|ARM64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|ARM64.Build.0 = Release|ARM64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x64.ActiveCfg = Release|x64
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA}.Release|x64.Build.0 = Release|x64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|ARM64.Build.0 = Debug|ARM64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.ActiveCfg = Debug|x64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.Build.0 = Debug|x64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|ARM64.ActiveCfg = Release|ARM64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|ARM64.Build.0 = Release|ARM64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.ActiveCfg = Release|x64
- {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.Build.0 = Release|x64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|ARM64.Build.0 = Debug|ARM64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.ActiveCfg = Debug|x64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.Build.0 = Debug|x64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|ARM64.ActiveCfg = Release|ARM64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|ARM64.Build.0 = Release|ARM64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.ActiveCfg = Release|x64
- {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.Build.0 = Release|x64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|ARM64.Build.0 = Debug|ARM64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.ActiveCfg = Debug|x64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.Build.0 = Debug|x64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|ARM64.ActiveCfg = Release|ARM64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|ARM64.Build.0 = Release|ARM64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.ActiveCfg = Release|x64
- {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.Build.0 = Release|x64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|ARM64.Build.0 = Debug|ARM64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|x64.ActiveCfg = Debug|x64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Debug|x64.Build.0 = Debug|x64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|ARM64.ActiveCfg = Release|ARM64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|ARM64.Build.0 = Release|ARM64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x64.ActiveCfg = Release|x64
- {0F85E674-34AE-443D-954C-8321EB8B93B1}.Release|x64.Build.0 = Release|x64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|ARM64.Build.0 = Debug|ARM64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.ActiveCfg = Debug|x64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.Build.0 = Debug|x64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|ARM64.ActiveCfg = Release|ARM64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|ARM64.Build.0 = Release|ARM64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.ActiveCfg = Release|x64
- {632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.Build.0 = Release|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.Build.0 = Debug|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.ActiveCfg = Release|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.Build.0 = Release|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|ARM64.Build.0 = Debug|ARM64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|x64.ActiveCfg = Debug|x64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Debug|x64.Build.0 = Debug|x64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|ARM64.ActiveCfg = Release|ARM64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|ARM64.Build.0 = Release|ARM64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|x64.ActiveCfg = Release|x64
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}.Release|x64.Build.0 = Release|x64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|ARM64.Build.0 = Debug|ARM64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|x64.ActiveCfg = Debug|x64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Debug|x64.Build.0 = Debug|x64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|ARM64.ActiveCfg = Release|ARM64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|ARM64.Build.0 = Release|ARM64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|x64.ActiveCfg = Release|x64
- {DA5A6FE9-0040-40CC-83CC-764AE5306590}.Release|x64.Build.0 = Release|x64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|ARM64.Build.0 = Debug|ARM64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|x64.ActiveCfg = Debug|x64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Debug|x64.Build.0 = Debug|x64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|ARM64.ActiveCfg = Release|ARM64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|ARM64.Build.0 = Release|ARM64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|x64.ActiveCfg = Release|x64
- {0351ADA4-0C32-4652-9BA0-41F7B602372B}.Release|x64.Build.0 = Release|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|ARM64.Build.0 = Debug|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.ActiveCfg = Release|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.Build.0 = Release|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|ARM64.Build.0 = Debug|ARM64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|x64.ActiveCfg = Debug|x64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Debug|x64.Build.0 = Debug|x64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|ARM64.ActiveCfg = Release|ARM64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|ARM64.Build.0 = Release|ARM64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|x64.ActiveCfg = Release|x64
- {6955446D-23F7-4023-9BB3-8657F904AF99}.Release|x64.Build.0 = Release|x64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|ARM64.Build.0 = Debug|ARM64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|x64.ActiveCfg = Debug|x64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Debug|x64.Build.0 = Debug|x64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|ARM64.ActiveCfg = Release|ARM64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|ARM64.Build.0 = Release|ARM64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|x64.ActiveCfg = Release|x64
- {58736667-1027-4AD7-BFDF-7A3A6474103A}.Release|x64.Build.0 = Release|x64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|ARM64.Build.0 = Debug|ARM64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|x64.ActiveCfg = Debug|x64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Debug|x64.Build.0 = Debug|x64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|ARM64.ActiveCfg = Release|ARM64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|ARM64.Build.0 = Release|ARM64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|x64.ActiveCfg = Release|x64
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}.Release|x64.Build.0 = Release|x64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|ARM64.Build.0 = Debug|ARM64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x64.ActiveCfg = Debug|x64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Debug|x64.Build.0 = Debug|x64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|ARM64.ActiveCfg = Release|ARM64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|ARM64.Build.0 = Release|ARM64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x64.ActiveCfg = Release|x64
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}.Release|x64.Build.0 = Release|x64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|ARM64.Build.0 = Debug|ARM64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.ActiveCfg = Debug|x64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.Build.0 = Debug|x64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|ARM64.ActiveCfg = Release|ARM64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|ARM64.Build.0 = Release|ARM64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.ActiveCfg = Release|x64
- {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.Build.0 = Release|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.Build.0 = Debug|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.Build.0 = Debug|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.ActiveCfg = Release|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.Build.0 = Release|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.ActiveCfg = Release|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.Build.0 = Release|x64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|ARM64.Build.0 = Debug|ARM64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|x64.ActiveCfg = Debug|x64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Debug|x64.Build.0 = Debug|x64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|ARM64.ActiveCfg = Release|ARM64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|ARM64.Build.0 = Release|ARM64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|x64.ActiveCfg = Release|x64
- {7319089E-46D6-4400-BC65-E39BDF1416EE}.Release|x64.Build.0 = Release|x64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|ARM64.Build.0 = Debug|ARM64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|x64.ActiveCfg = Debug|x64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Debug|x64.Build.0 = Debug|x64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|ARM64.ActiveCfg = Release|ARM64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|ARM64.Build.0 = Release|ARM64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|x64.ActiveCfg = Release|x64
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9}.Release|x64.Build.0 = Release|x64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|ARM64.Build.0 = Debug|ARM64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|x64.ActiveCfg = Debug|x64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Debug|x64.Build.0 = Debug|x64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|ARM64.ActiveCfg = Release|ARM64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|ARM64.Build.0 = Release|ARM64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|x64.ActiveCfg = Release|x64
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB}.Release|x64.Build.0 = Release|x64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|ARM64.Build.0 = Debug|ARM64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|x64.ActiveCfg = Debug|x64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Debug|x64.Build.0 = Debug|x64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|ARM64.ActiveCfg = Release|ARM64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|ARM64.Build.0 = Release|ARM64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.ActiveCfg = Release|x64
- {C3A17DCA-217B-462C-BB0C-BE086AF80081}.Release|x64.Build.0 = Release|x64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|ARM64.Build.0 = Debug|ARM64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|x64.ActiveCfg = Debug|x64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Debug|x64.Build.0 = Debug|x64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|ARM64.ActiveCfg = Release|ARM64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|ARM64.Build.0 = Release|ARM64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|x64.ActiveCfg = Release|x64
- {69E1EE8D-143A-4060-9129-4658ACF14AAF}.Release|x64.Build.0 = Release|x64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|ARM64.Build.0 = Debug|ARM64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|x64.ActiveCfg = Debug|x64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Debug|x64.Build.0 = Debug|x64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|ARM64.ActiveCfg = Release|ARM64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|ARM64.Build.0 = Release|ARM64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|x64.ActiveCfg = Release|x64
- {ECC20689-002A-4354-95A6-B58DF089C6FF}.Release|x64.Build.0 = Release|x64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|ARM64.Build.0 = Debug|ARM64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.ActiveCfg = Debug|x64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Debug|x64.Build.0 = Debug|x64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|ARM64.ActiveCfg = Release|ARM64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|ARM64.Build.0 = Release|ARM64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x64.ActiveCfg = Release|x64
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF}.Release|x64.Build.0 = Release|x64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|ARM64.Build.0 = Debug|ARM64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x64.ActiveCfg = Debug|x64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Debug|x64.Build.0 = Debug|x64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|ARM64.ActiveCfg = Release|ARM64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|ARM64.Build.0 = Release|ARM64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x64.ActiveCfg = Release|x64
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65}.Release|x64.Build.0 = Release|x64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|ARM64.Build.0 = Debug|ARM64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|x64.ActiveCfg = Debug|x64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Debug|x64.Build.0 = Debug|x64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|ARM64.ActiveCfg = Release|ARM64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|ARM64.Build.0 = Release|ARM64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|x64.ActiveCfg = Release|x64
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD}.Release|x64.Build.0 = Release|x64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|ARM64.Build.0 = Debug|ARM64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|x64.ActiveCfg = Debug|x64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Debug|x64.Build.0 = Debug|x64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|ARM64.ActiveCfg = Release|ARM64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|ARM64.Build.0 = Release|ARM64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|x64.ActiveCfg = Release|x64
- {E496B7FC-1E99-4BAB-849B-0E8367040B02}.Release|x64.Build.0 = Release|x64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|ARM64.Build.0 = Debug|ARM64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|x64.ActiveCfg = Debug|x64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Debug|x64.Build.0 = Debug|x64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|ARM64.ActiveCfg = Release|ARM64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|ARM64.Build.0 = Release|ARM64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|x64.ActiveCfg = Release|x64
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466}.Release|x64.Build.0 = Release|x64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|ARM64.Build.0 = Debug|ARM64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|x64.ActiveCfg = Debug|x64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Debug|x64.Build.0 = Debug|x64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Release|ARM64.ActiveCfg = Release|ARM64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Release|ARM64.Build.0 = Release|ARM64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Release|x64.ActiveCfg = Release|x64
- {8DF78B53-200E-451F-9328-01EB907193AE}.Release|x64.Build.0 = Release|x64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|ARM64.Build.0 = Debug|ARM64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|x64.ActiveCfg = Debug|x64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Debug|x64.Build.0 = Debug|x64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|ARM64.ActiveCfg = Release|ARM64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|ARM64.Build.0 = Release|ARM64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|x64.ActiveCfg = Release|x64
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49}.Release|x64.Build.0 = Release|x64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|ARM64.Build.0 = Debug|ARM64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x64.ActiveCfg = Debug|x64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x64.Build.0 = Debug|x64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|ARM64.ActiveCfg = Release|ARM64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|ARM64.Build.0 = Release|ARM64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.ActiveCfg = Release|x64
- {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.Build.0 = Release|x64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|ARM64.Build.0 = Debug|ARM64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x64.ActiveCfg = Debug|x64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x64.Build.0 = Debug|x64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|ARM64.ActiveCfg = Release|ARM64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|ARM64.Build.0 = Release|ARM64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.ActiveCfg = Release|x64
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.Build.0 = Release|x64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|ARM64.Build.0 = Debug|ARM64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.ActiveCfg = Debug|x64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.Build.0 = Debug|x64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|ARM64.ActiveCfg = Release|ARM64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|ARM64.Build.0 = Release|ARM64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.ActiveCfg = Release|x64
- {D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.Build.0 = Release|x64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|ARM64.Build.0 = Debug|ARM64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.ActiveCfg = Debug|x64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.Build.0 = Debug|x64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|ARM64.ActiveCfg = Release|ARM64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|ARM64.Build.0 = Release|ARM64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.ActiveCfg = Release|x64
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.Build.0 = Release|x64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|ARM64.Build.0 = Debug|ARM64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.ActiveCfg = Debug|x64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.Build.0 = Debug|x64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|ARM64.ActiveCfg = Release|ARM64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|ARM64.Build.0 = Release|ARM64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.ActiveCfg = Release|x64
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.Build.0 = Release|x64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|ARM64.Build.0 = Debug|ARM64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.ActiveCfg = Debug|x64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.Build.0 = Debug|x64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|ARM64.ActiveCfg = Release|ARM64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|ARM64.Build.0 = Release|ARM64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.ActiveCfg = Release|x64
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.Build.0 = Release|x64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|ARM64.Build.0 = Debug|ARM64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.ActiveCfg = Debug|x64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.Build.0 = Debug|x64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|ARM64.ActiveCfg = Release|ARM64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|ARM64.Build.0 = Release|ARM64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.ActiveCfg = Release|x64
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.Build.0 = Release|x64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|ARM64.Build.0 = Debug|ARM64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.ActiveCfg = Debug|x64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.Build.0 = Debug|x64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|ARM64.ActiveCfg = Release|ARM64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|ARM64.Build.0 = Release|ARM64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.ActiveCfg = Release|x64
- {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.Build.0 = Release|x64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|ARM64.Build.0 = Debug|ARM64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|x64.ActiveCfg = Debug|x64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Debug|x64.Build.0 = Debug|x64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|ARM64.ActiveCfg = Release|ARM64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|ARM64.Build.0 = Release|ARM64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|x64.ActiveCfg = Release|x64
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700}.Release|x64.Build.0 = Release|x64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|ARM64.Build.0 = Debug|ARM64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x64.ActiveCfg = Debug|x64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x64.Build.0 = Debug|x64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|ARM64.ActiveCfg = Release|ARM64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|ARM64.Build.0 = Release|ARM64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|x64.ActiveCfg = Release|x64
- {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|x64.Build.0 = Release|x64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|ARM64.Build.0 = Debug|ARM64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|x64.ActiveCfg = Debug|x64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Debug|x64.Build.0 = Debug|x64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|ARM64.ActiveCfg = Release|ARM64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|ARM64.Build.0 = Release|ARM64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.ActiveCfg = Release|x64
- {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.Build.0 = Release|x64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Debug|ARM64.Build.0 = Debug|ARM64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Debug|x64.ActiveCfg = Debug|x64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Debug|x64.Build.0 = Debug|x64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Release|ARM64.ActiveCfg = Release|ARM64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Release|ARM64.Build.0 = Release|ARM64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Release|x64.ActiveCfg = Release|x64
- {11491FD8-F921-48BF-880C-7FEA185B80A1}.Release|x64.Build.0 = Release|x64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Debug|ARM64.Build.0 = Debug|ARM64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Debug|x64.ActiveCfg = Debug|x64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Debug|x64.Build.0 = Debug|x64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|ARM64.ActiveCfg = Release|ARM64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|ARM64.Build.0 = Release|ARM64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|x64.ActiveCfg = Release|x64
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|x64.Build.0 = Release|x64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|ARM64.Build.0 = Debug|ARM64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|x64.ActiveCfg = Debug|x64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|x64.Build.0 = Debug|x64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|ARM64.ActiveCfg = Release|ARM64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|ARM64.Build.0 = Release|ARM64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|x64.ActiveCfg = Release|x64
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|x64.Build.0 = Release|x64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|ARM64.Build.0 = Debug|ARM64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|x64.ActiveCfg = Debug|x64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|x64.Build.0 = Debug|x64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|ARM64.ActiveCfg = Release|ARM64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|ARM64.Build.0 = Release|ARM64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.ActiveCfg = Release|x64
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.Build.0 = Release|x64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|ARM64.Build.0 = Debug|ARM64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.ActiveCfg = Debug|x64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Debug|x64.Build.0 = Debug|x64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|ARM64.ActiveCfg = Release|ARM64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|ARM64.Build.0 = Release|ARM64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.ActiveCfg = Release|x64
- {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.Build.0 = Release|x64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|ARM64.Build.0 = Debug|ARM64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x64.ActiveCfg = Debug|x64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x64.Build.0 = Debug|x64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|ARM64.ActiveCfg = Release|ARM64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|ARM64.Build.0 = Release|ARM64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x64.ActiveCfg = Release|x64
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x64.Build.0 = Release|x64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Debug|ARM64.Build.0 = Debug|ARM64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Debug|x64.ActiveCfg = Debug|x64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Debug|x64.Build.0 = Debug|x64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Release|ARM64.ActiveCfg = Release|ARM64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Release|ARM64.Build.0 = Release|ARM64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Release|x64.ActiveCfg = Release|x64
- {809AA252-E17A-4FA2-B0A1-0450976B763F}.Release|x64.Build.0 = Release|x64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Debug|ARM64.Build.0 = Debug|ARM64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Debug|x64.ActiveCfg = Debug|x64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Debug|x64.Build.0 = Debug|x64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Release|ARM64.ActiveCfg = Release|ARM64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Release|ARM64.Build.0 = Release|ARM64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Release|x64.ActiveCfg = Release|x64
- {133281D8-1BCE-4D07-B31E-796612A9609E}.Release|x64.Build.0 = Release|x64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Debug|ARM64.Build.0 = Debug|ARM64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Debug|x64.ActiveCfg = Debug|x64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Debug|x64.Build.0 = Debug|x64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Release|ARM64.ActiveCfg = Release|ARM64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Release|ARM64.Build.0 = Release|ARM64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Release|x64.ActiveCfg = Release|x64
- {805306FF-A562-4415-8DEF-E493BDC45918}.Release|x64.Build.0 = Release|x64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Debug|ARM64.Build.0 = Debug|ARM64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Debug|x64.ActiveCfg = Debug|x64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Debug|x64.Build.0 = Debug|x64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|ARM64.ActiveCfg = Release|ARM64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|ARM64.Build.0 = Release|ARM64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|x64.ActiveCfg = Release|x64
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|x64.Build.0 = Release|x64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Debug|ARM64.Build.0 = Debug|ARM64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Debug|x64.ActiveCfg = Debug|x64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Debug|x64.Build.0 = Debug|x64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|ARM64.ActiveCfg = Release|ARM64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|ARM64.Build.0 = Release|ARM64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|x64.ActiveCfg = Release|x64
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}.Release|x64.Build.0 = Release|x64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|ARM64.Build.0 = Debug|ARM64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|x64.ActiveCfg = Debug|x64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Debug|x64.Build.0 = Debug|x64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|ARM64.ActiveCfg = Release|ARM64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|ARM64.Build.0 = Release|ARM64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|x64.ActiveCfg = Release|x64
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9}.Release|x64.Build.0 = Release|x64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|ARM64.Build.0 = Debug|ARM64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|x64.ActiveCfg = Debug|x64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Debug|x64.Build.0 = Debug|x64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Release|ARM64.ActiveCfg = Release|ARM64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Release|ARM64.Build.0 = Release|ARM64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Release|x64.ActiveCfg = Release|x64
- {9F94B303-5E21-4364-9362-64426F8DB932}.Release|x64.Build.0 = Release|x64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|ARM64.Build.0 = Debug|ARM64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|x64.ActiveCfg = Debug|x64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Debug|x64.Build.0 = Debug|x64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|ARM64.ActiveCfg = Release|ARM64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|ARM64.Build.0 = Release|ARM64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x64.ActiveCfg = Release|x64
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x64.Build.0 = Release|x64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|ARM64.Build.0 = Debug|ARM64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|x64.ActiveCfg = Debug|x64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|x64.Build.0 = Debug|x64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|ARM64.ActiveCfg = Release|ARM64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|ARM64.Build.0 = Release|ARM64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|x64.ActiveCfg = Release|x64
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|x64.Build.0 = Release|x64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|ARM64.Build.0 = Debug|ARM64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|x64.ActiveCfg = Debug|x64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|x64.Build.0 = Debug|x64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|ARM64.ActiveCfg = Release|ARM64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|ARM64.Build.0 = Release|ARM64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|x64.ActiveCfg = Release|x64
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|x64.Build.0 = Release|x64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Debug|ARM64.Build.0 = Debug|ARM64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Debug|x64.ActiveCfg = Debug|x64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Debug|x64.Build.0 = Debug|x64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|ARM64.ActiveCfg = Release|ARM64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|ARM64.Build.0 = Release|ARM64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x64.ActiveCfg = Release|x64
- {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x64.Build.0 = Release|x64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|ARM64.Build.0 = Debug|ARM64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|x64.ActiveCfg = Debug|x64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Debug|x64.Build.0 = Debug|x64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|ARM64.ActiveCfg = Release|ARM64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|ARM64.Build.0 = Release|ARM64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|x64.ActiveCfg = Release|x64
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF}.Release|x64.Build.0 = Release|x64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|ARM64.Build.0 = Debug|ARM64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|x64.ActiveCfg = Debug|x64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Debug|x64.Build.0 = Debug|x64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|ARM64.ActiveCfg = Release|ARM64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|ARM64.Build.0 = Release|ARM64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|x64.ActiveCfg = Release|x64
- {FD464B4C-2F68-4D06-91E7-4208146C41F5}.Release|x64.Build.0 = Release|x64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|ARM64.Build.0 = Debug|ARM64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x64.ActiveCfg = Debug|x64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Debug|x64.Build.0 = Debug|x64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|ARM64.ActiveCfg = Release|ARM64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|ARM64.Build.0 = Release|ARM64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|x64.ActiveCfg = Release|x64
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1}.Release|x64.Build.0 = Release|x64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Debug|ARM64.Build.0 = Debug|ARM64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Debug|x64.ActiveCfg = Debug|x64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Debug|x64.Build.0 = Debug|x64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Release|ARM64.ActiveCfg = Release|ARM64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Release|ARM64.Build.0 = Release|ARM64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Release|x64.ActiveCfg = Release|x64
- {020A7474-3601-4160-A159-D7B70B77B15F}.Release|x64.Build.0 = Release|x64
- {27718999-C175-450A-861C-89F911E16A88}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {27718999-C175-450A-861C-89F911E16A88}.Debug|ARM64.Build.0 = Debug|ARM64
- {27718999-C175-450A-861C-89F911E16A88}.Debug|x64.ActiveCfg = Debug|x64
- {27718999-C175-450A-861C-89F911E16A88}.Debug|x64.Build.0 = Debug|x64
- {27718999-C175-450A-861C-89F911E16A88}.Release|ARM64.ActiveCfg = Release|ARM64
- {27718999-C175-450A-861C-89F911E16A88}.Release|ARM64.Build.0 = Release|ARM64
- {27718999-C175-450A-861C-89F911E16A88}.Release|x64.ActiveCfg = Release|x64
- {27718999-C175-450A-861C-89F911E16A88}.Release|x64.Build.0 = Release|x64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Debug|ARM64.Build.0 = Debug|ARM64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Debug|x64.ActiveCfg = Debug|x64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Debug|x64.Build.0 = Debug|x64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Release|ARM64.ActiveCfg = Release|ARM64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Release|ARM64.Build.0 = Release|ARM64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Release|x64.ActiveCfg = Release|x64
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6}.Release|x64.Build.0 = Release|x64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Debug|ARM64.Build.0 = Debug|ARM64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Debug|x64.ActiveCfg = Debug|x64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Debug|x64.Build.0 = Debug|x64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Release|ARM64.ActiveCfg = Release|ARM64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Release|ARM64.Build.0 = Release|ARM64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Release|x64.ActiveCfg = Release|x64
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045}.Release|x64.Build.0 = Release|x64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Debug|ARM64.Build.0 = Debug|ARM64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Debug|x64.ActiveCfg = Debug|x64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Debug|x64.Build.0 = Debug|x64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Release|ARM64.ActiveCfg = Release|ARM64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Release|ARM64.Build.0 = Release|ARM64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Release|x64.ActiveCfg = Release|x64
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF}.Release|x64.Build.0 = Release|x64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Debug|ARM64.Build.0 = Debug|ARM64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Debug|x64.ActiveCfg = Debug|x64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Debug|x64.Build.0 = Debug|x64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|ARM64.ActiveCfg = Release|ARM64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|ARM64.Build.0 = Release|ARM64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|x64.ActiveCfg = Release|x64
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|x64.Build.0 = Release|x64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Debug|ARM64.Build.0 = Debug|ARM64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Debug|x64.ActiveCfg = Debug|x64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Debug|x64.Build.0 = Debug|x64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Release|ARM64.ActiveCfg = Release|ARM64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Release|ARM64.Build.0 = Release|ARM64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Release|x64.ActiveCfg = Release|x64
- {34A354C5-23C7-4343-916C-C52DAF4FC39D}.Release|x64.Build.0 = Release|x64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Debug|ARM64.Build.0 = Debug|ARM64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Debug|x64.ActiveCfg = Debug|x64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Debug|x64.Build.0 = Debug|x64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Release|ARM64.ActiveCfg = Release|ARM64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Release|ARM64.Build.0 = Release|ARM64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Release|x64.ActiveCfg = Release|x64
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762}.Release|x64.Build.0 = Release|x64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Debug|ARM64.Build.0 = Debug|ARM64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Debug|x64.ActiveCfg = Debug|x64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Debug|x64.Build.0 = Debug|x64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Release|ARM64.ActiveCfg = Release|ARM64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Release|ARM64.Build.0 = Release|ARM64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Release|x64.ActiveCfg = Release|x64
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10}.Release|x64.Build.0 = Release|x64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|ARM64.Build.0 = Debug|ARM64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|x64.ActiveCfg = Debug|x64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|x64.Build.0 = Debug|x64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|ARM64.ActiveCfg = Release|ARM64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|ARM64.Build.0 = Release|ARM64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|x64.ActiveCfg = Release|x64
- {25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|x64.Build.0 = Release|x64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|ARM64.Build.0 = Debug|ARM64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|x64.ActiveCfg = Debug|x64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|x64.Build.0 = Debug|x64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|ARM64.ActiveCfg = Release|ARM64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|ARM64.Build.0 = Release|ARM64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|x64.ActiveCfg = Release|x64
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|x64.Build.0 = Release|x64
- {212AD910-8488-4036-BE20-326931B75FB2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {212AD910-8488-4036-BE20-326931B75FB2}.Debug|ARM64.Build.0 = Debug|ARM64
- {212AD910-8488-4036-BE20-326931B75FB2}.Debug|x64.ActiveCfg = Debug|x64
- {212AD910-8488-4036-BE20-326931B75FB2}.Debug|x64.Build.0 = Debug|x64
- {212AD910-8488-4036-BE20-326931B75FB2}.Release|ARM64.ActiveCfg = Release|ARM64
- {212AD910-8488-4036-BE20-326931B75FB2}.Release|ARM64.Build.0 = Release|ARM64
- {212AD910-8488-4036-BE20-326931B75FB2}.Release|x64.ActiveCfg = Release|x64
- {212AD910-8488-4036-BE20-326931B75FB2}.Release|x64.Build.0 = Release|x64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Debug|ARM64.Build.0 = Debug|ARM64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Debug|x64.ActiveCfg = Debug|x64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Debug|x64.Build.0 = Debug|x64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Release|ARM64.ActiveCfg = Release|ARM64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Release|ARM64.Build.0 = Release|ARM64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Release|x64.ActiveCfg = Release|x64
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A}.Release|x64.Build.0 = Release|x64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Debug|ARM64.Build.0 = Debug|ARM64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Debug|x64.ActiveCfg = Debug|x64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Debug|x64.Build.0 = Debug|x64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Release|ARM64.ActiveCfg = Release|ARM64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Release|ARM64.Build.0 = Release|ARM64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Release|x64.ActiveCfg = Release|x64
- {92C39820-9F84-4529-BC7D-22AAE514D63B}.Release|x64.Build.0 = Release|x64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Debug|ARM64.Build.0 = Debug|ARM64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Debug|x64.ActiveCfg = Debug|x64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Debug|x64.Build.0 = Debug|x64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|ARM64.ActiveCfg = Release|ARM64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|ARM64.Build.0 = Release|ARM64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x64.ActiveCfg = Release|x64
- {515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x64.Build.0 = Release|x64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|ARM64.Build.0 = Debug|ARM64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x64.ActiveCfg = Debug|x64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x64.Build.0 = Debug|x64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|ARM64.ActiveCfg = Release|ARM64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|ARM64.Build.0 = Release|ARM64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.ActiveCfg = Release|x64
- {C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.Build.0 = Release|x64
- {31D1C81D-765F-4446-AA62-E743F6325049}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Debug|ARM64.Build.0 = Debug|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Debug|x64.ActiveCfg = Debug|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Debug|x64.Build.0 = Debug|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Release|ARM64.ActiveCfg = Release|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Release|ARM64.Build.0 = Release|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Release|x64.ActiveCfg = Release|Any CPU
- {31D1C81D-765F-4446-AA62-E743F6325049}.Release|x64.Build.0 = Release|Any CPU
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|ARM64.Build.0 = Debug|ARM64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|x64.ActiveCfg = Debug|x64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|x64.Build.0 = Debug|x64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|ARM64.ActiveCfg = Release|ARM64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|ARM64.Build.0 = Release|ARM64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x64.ActiveCfg = Release|x64
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x64.Build.0 = Release|x64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|ARM64.Build.0 = Debug|ARM64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x64.ActiveCfg = Debug|x64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x64.Build.0 = Debug|x64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|ARM64.ActiveCfg = Release|ARM64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|ARM64.Build.0 = Release|ARM64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.ActiveCfg = Release|x64
- {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.Build.0 = Release|x64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|ARM64.Build.0 = Debug|ARM64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|x64.ActiveCfg = Debug|x64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|x64.Build.0 = Debug|x64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|ARM64.ActiveCfg = Release|ARM64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|ARM64.Build.0 = Release|ARM64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|x64.ActiveCfg = Release|x64
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|x64.Build.0 = Release|x64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|ARM64.Build.0 = Debug|ARM64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|x64.ActiveCfg = Debug|x64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|x64.Build.0 = Debug|x64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|ARM64.ActiveCfg = Release|ARM64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|ARM64.Build.0 = Release|ARM64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|x64.ActiveCfg = Release|x64
- {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|x64.Build.0 = Release|x64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|ARM64.Build.0 = Debug|ARM64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|x64.ActiveCfg = Debug|x64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|x64.Build.0 = Debug|x64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|ARM64.ActiveCfg = Release|ARM64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|ARM64.Build.0 = Release|ARM64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|x64.ActiveCfg = Release|x64
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|x64.Build.0 = Release|x64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|ARM64.Build.0 = Debug|ARM64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|x64.ActiveCfg = Debug|x64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|x64.Build.0 = Debug|x64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Release|ARM64.ActiveCfg = Release|ARM64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Release|ARM64.Build.0 = Release|ARM64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Release|x64.ActiveCfg = Release|x64
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Release|x64.Build.0 = Release|x64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Debug|ARM64.Build.0 = Debug|ARM64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Debug|x64.ActiveCfg = Debug|x64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Debug|x64.Build.0 = Debug|x64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|ARM64.ActiveCfg = Release|ARM64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|ARM64.Build.0 = Release|ARM64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|x64.ActiveCfg = Release|x64
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}.Release|x64.Build.0 = Release|x64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|ARM64.Build.0 = Debug|ARM64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|x64.ActiveCfg = Debug|x64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Debug|x64.Build.0 = Debug|x64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|ARM64.ActiveCfg = Release|ARM64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|ARM64.Build.0 = Release|ARM64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|x64.ActiveCfg = Release|x64
- {A1425B53-3D61-4679-8623-E64A0D3D0A48}.Release|x64.Build.0 = Release|x64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|ARM64.Build.0 = Debug|ARM64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x64.ActiveCfg = Debug|x64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x64.Build.0 = Debug|x64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Debug|x64.Deploy.0 = Debug|x64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|ARM64.ActiveCfg = Release|ARM64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|ARM64.Build.0 = Release|ARM64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|ARM64.Deploy.0 = Release|ARM64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x64.ActiveCfg = Release|x64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x64.Build.0 = Release|x64
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}.Release|x64.Deploy.0 = Release|x64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|ARM64.Build.0 = Debug|ARM64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|x64.ActiveCfg = Debug|x64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Debug|x64.Build.0 = Debug|x64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|ARM64.ActiveCfg = Release|ARM64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|ARM64.Build.0 = Release|ARM64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|x64.ActiveCfg = Release|x64
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB}.Release|x64.Build.0 = Release|x64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|ARM64.Build.0 = Debug|ARM64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|x64.ActiveCfg = Debug|x64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Debug|x64.Build.0 = Debug|x64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.ActiveCfg = Release|ARM64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|ARM64.Build.0 = Release|ARM64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.ActiveCfg = Release|x64
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC}.Release|x64.Build.0 = Release|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|ARM64.Build.0 = Debug|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.ActiveCfg = Debug|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Debug|x64.Build.0 = Debug|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.ActiveCfg = Release|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|ARM64.Build.0 = Release|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.ActiveCfg = Release|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5}.Release|x64.Build.0 = Release|x64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|ARM64.Build.0 = Debug|ARM64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|x64.ActiveCfg = Debug|x64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Debug|x64.Build.0 = Debug|x64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Release|ARM64.ActiveCfg = Release|ARM64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Release|ARM64.Build.0 = Release|ARM64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Release|x64.ActiveCfg = Release|x64
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545}.Release|x64.Build.0 = Release|x64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Debug|ARM64.Build.0 = Debug|ARM64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Debug|x64.ActiveCfg = Debug|x64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Debug|x64.Build.0 = Debug|x64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Release|ARM64.ActiveCfg = Release|ARM64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Release|ARM64.Build.0 = Release|ARM64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Release|x64.ActiveCfg = Release|x64
- {5A5DD09D-723A-44D3-8F2B-293584C3D731}.Release|x64.Build.0 = Release|x64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Debug|ARM64.Build.0 = Debug|ARM64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Debug|x64.ActiveCfg = Debug|x64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Debug|x64.Build.0 = Debug|x64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Release|ARM64.ActiveCfg = Release|ARM64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Release|ARM64.Build.0 = Release|ARM64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Release|x64.ActiveCfg = Release|x64
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9}.Release|x64.Build.0 = Release|x64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Debug|ARM64.Build.0 = Debug|ARM64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Debug|x64.ActiveCfg = Debug|x64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Debug|x64.Build.0 = Debug|x64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Release|ARM64.ActiveCfg = Release|ARM64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Release|ARM64.Build.0 = Release|ARM64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Release|x64.ActiveCfg = Release|x64
- {54F7C616-FD41-4E62-BFF9-015686914F4D}.Release|x64.Build.0 = Release|x64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Debug|ARM64.Build.0 = Debug|ARM64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Debug|x64.ActiveCfg = Debug|x64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Debug|x64.Build.0 = Debug|x64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Release|ARM64.ActiveCfg = Release|ARM64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Release|ARM64.Build.0 = Release|ARM64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Release|x64.ActiveCfg = Release|x64
- {143F13E3-D2E3-4D83-B035-356612D99956}.Release|x64.Build.0 = Release|x64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Debug|ARM64.Build.0 = Debug|ARM64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Debug|x64.ActiveCfg = Debug|x64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Debug|x64.Build.0 = Debug|x64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Release|ARM64.ActiveCfg = Release|ARM64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Release|ARM64.Build.0 = Release|ARM64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Release|x64.ActiveCfg = Release|x64
- {56CC2F10-6E41-453D-BE16-C593A5E58482}.Release|x64.Build.0 = Release|x64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Debug|ARM64.Build.0 = Debug|ARM64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Debug|x64.ActiveCfg = Debug|x64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Debug|x64.Build.0 = Debug|x64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Release|ARM64.ActiveCfg = Release|ARM64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Release|ARM64.Build.0 = Release|ARM64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Release|x64.ActiveCfg = Release|x64
- {CA5518ED-0458-4B09-8F53-4122B9888655}.Release|x64.Build.0 = Release|x64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Debug|ARM64.Build.0 = Debug|ARM64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Debug|x64.ActiveCfg = Debug|x64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Debug|x64.Build.0 = Debug|x64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Release|ARM64.ActiveCfg = Release|ARM64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Release|ARM64.Build.0 = Release|ARM64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Release|x64.ActiveCfg = Release|x64
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D}.Release|x64.Build.0 = Release|x64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Debug|ARM64.Build.0 = Debug|ARM64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Debug|x64.ActiveCfg = Debug|x64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Debug|x64.Build.0 = Debug|x64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|ARM64.ActiveCfg = Release|ARM64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|ARM64.Build.0 = Release|ARM64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x64.ActiveCfg = Release|x64
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x64.Build.0 = Release|x64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Debug|ARM64.Build.0 = Debug|ARM64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Debug|x64.ActiveCfg = Debug|x64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Debug|x64.Build.0 = Debug|x64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Release|ARM64.ActiveCfg = Release|ARM64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Release|ARM64.Build.0 = Release|ARM64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Release|x64.ActiveCfg = Release|x64
- {2833C9C6-AB32-4048-A5C7-A70898337B57}.Release|x64.Build.0 = Release|x64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Debug|ARM64.Build.0 = Debug|ARM64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Debug|x64.ActiveCfg = Debug|x64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Debug|x64.Build.0 = Debug|x64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Release|ARM64.ActiveCfg = Release|ARM64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Release|ARM64.Build.0 = Release|ARM64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Release|x64.ActiveCfg = Release|x64
- {50B82783-242F-42D2-BC03-B3430BF01354}.Release|x64.Build.0 = Release|x64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Debug|ARM64.Build.0 = Debug|ARM64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Debug|x64.ActiveCfg = Debug|x64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Debug|x64.Build.0 = Debug|x64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Release|ARM64.ActiveCfg = Release|ARM64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Release|ARM64.Build.0 = Release|ARM64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Release|x64.ActiveCfg = Release|x64
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2}.Release|x64.Build.0 = Release|x64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Debug|ARM64.Build.0 = Debug|ARM64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Debug|x64.ActiveCfg = Debug|x64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Debug|x64.Build.0 = Debug|x64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Release|ARM64.ActiveCfg = Release|ARM64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Release|ARM64.Build.0 = Release|ARM64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Release|x64.ActiveCfg = Release|x64
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F}.Release|x64.Build.0 = Release|x64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Debug|ARM64.Build.0 = Debug|ARM64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Debug|x64.ActiveCfg = Debug|x64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Debug|x64.Build.0 = Debug|x64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Release|ARM64.ActiveCfg = Release|ARM64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Release|ARM64.Build.0 = Release|ARM64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Release|x64.ActiveCfg = Release|x64
- {8A08D663-4995-40E3-B42C-3F910625F284}.Release|x64.Build.0 = Release|x64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|ARM64.Build.0 = Debug|ARM64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.ActiveCfg = Debug|x64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Debug|x64.Build.0 = Debug|x64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.ActiveCfg = Release|ARM64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|ARM64.Build.0 = Release|ARM64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.ActiveCfg = Release|x64
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA}.Release|x64.Build.0 = Release|x64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|ARM64.Build.0 = Debug|ARM64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.ActiveCfg = Debug|x64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Debug|x64.Build.0 = Debug|x64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.ActiveCfg = Release|ARM64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|ARM64.Build.0 = Release|ARM64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.ActiveCfg = Release|x64
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77}.Release|x64.Build.0 = Release|x64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|ARM64.Build.0 = Debug|ARM64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|x64.ActiveCfg = Debug|x64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Debug|x64.Build.0 = Debug|x64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|ARM64.ActiveCfg = Release|ARM64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|ARM64.Build.0 = Release|ARM64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x64.ActiveCfg = Release|x64
- {D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x64.Build.0 = Release|x64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|ARM64.Build.0 = Debug|ARM64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x64.ActiveCfg = Debug|x64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x64.Build.0 = Debug|x64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|ARM64.ActiveCfg = Release|ARM64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|ARM64.Build.0 = Release|ARM64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|x64.ActiveCfg = Release|x64
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|x64.Build.0 = Release|x64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|ARM64.Build.0 = Debug|ARM64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x64.ActiveCfg = Debug|x64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x64.Build.0 = Debug|x64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|ARM64.ActiveCfg = Release|ARM64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|ARM64.Build.0 = Release|ARM64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x64.ActiveCfg = Release|x64
- {9CE59ED5-7087-4353-88EB-788038A73CEC}.Release|x64.Build.0 = Release|x64
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|ARM64.Build.0 = Debug|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|x64.ActiveCfg = Debug|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Debug|x64.Build.0 = Debug|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|ARM64.ActiveCfg = Release|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|ARM64.Build.0 = Release|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|x64.ActiveCfg = Release|Any CPU
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F}.Release|x64.Build.0 = Release|Any CPU
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|ARM64.Build.0 = Debug|ARM64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|x64.ActiveCfg = Debug|x64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Debug|x64.Build.0 = Debug|x64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Release|ARM64.ActiveCfg = Release|ARM64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Release|ARM64.Build.0 = Release|ARM64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Release|x64.ActiveCfg = Release|x64
- {697C6AF9-0A48-49A9-866C-67DA12384015}.Release|x64.Build.0 = Release|x64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Debug|ARM64.Build.0 = Debug|ARM64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Debug|x64.ActiveCfg = Debug|x64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Debug|x64.Build.0 = Debug|x64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Release|ARM64.ActiveCfg = Release|ARM64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Release|ARM64.Build.0 = Release|ARM64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Release|x64.ActiveCfg = Release|x64
- {9EBAA524-0EDA-470B-95D4-39383285CBB2}.Release|x64.Build.0 = Release|x64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Debug|ARM64.Build.0 = Debug|ARM64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Debug|x64.ActiveCfg = Debug|x64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Debug|x64.Build.0 = Debug|x64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|ARM64.ActiveCfg = Release|ARM64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|ARM64.Build.0 = Release|ARM64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x64.ActiveCfg = Release|x64
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D}.Release|x64.Build.0 = Release|x64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|ARM64.Build.0 = Debug|ARM64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x64.ActiveCfg = Debug|x64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Debug|x64.Build.0 = Debug|x64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|ARM64.ActiveCfg = Release|ARM64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|ARM64.Build.0 = Release|ARM64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x64.ActiveCfg = Release|x64
- {D095BE44-1F2E-463E-A494-121892A75EA2}.Release|x64.Build.0 = Release|x64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|ARM64.Build.0 = Debug|ARM64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x64.ActiveCfg = Debug|x64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Debug|x64.Build.0 = Debug|x64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|ARM64.ActiveCfg = Release|ARM64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|ARM64.Build.0 = Release|ARM64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.ActiveCfg = Release|x64
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.Build.0 = Release|x64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|ARM64.Build.0 = Debug|ARM64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|x64.ActiveCfg = Debug|x64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|x64.Build.0 = Debug|x64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|ARM64.ActiveCfg = Release|ARM64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|ARM64.Build.0 = Release|ARM64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|x64.ActiveCfg = Release|x64
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|x64.Build.0 = Release|x64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|ARM64.Build.0 = Debug|ARM64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|x64.ActiveCfg = Debug|x64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|x64.Build.0 = Debug|x64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|ARM64.ActiveCfg = Release|ARM64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|ARM64.Build.0 = Release|ARM64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x64.ActiveCfg = Release|x64
- {3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x64.Build.0 = Release|x64
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|ARM64.Build.0 = Debug|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|x64.ActiveCfg = Debug|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|x64.Build.0 = Debug|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|ARM64.ActiveCfg = Release|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|ARM64.Build.0 = Release|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|x64.ActiveCfg = Release|Any CPU
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|x64.Build.0 = Release|Any CPU
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|ARM64.Build.0 = Debug|ARM64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|x64.ActiveCfg = Debug|x64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|x64.Build.0 = Debug|x64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|ARM64.ActiveCfg = Release|ARM64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|ARM64.Build.0 = Release|ARM64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x64.ActiveCfg = Release|x64
- {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x64.Build.0 = Release|x64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|ARM64.Build.0 = Debug|ARM64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x64.ActiveCfg = Debug|x64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x64.Build.0 = Debug|x64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|ARM64.ActiveCfg = Release|ARM64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|ARM64.Build.0 = Release|ARM64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x64.ActiveCfg = Release|x64
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x64.Build.0 = Release|x64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|ARM64.Build.0 = Debug|ARM64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x64.ActiveCfg = Debug|x64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x64.Build.0 = Debug|x64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|ARM64.ActiveCfg = Release|ARM64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|ARM64.Build.0 = Release|ARM64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x64.ActiveCfg = Release|x64
- {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x64.Build.0 = Release|x64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|ARM64.Build.0 = Debug|ARM64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x64.ActiveCfg = Debug|x64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x64.Build.0 = Debug|x64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|ARM64.ActiveCfg = Release|ARM64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|ARM64.Build.0 = Release|ARM64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x64.ActiveCfg = Release|x64
- {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x64.Build.0 = Release|x64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|ARM64.Build.0 = Debug|ARM64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x64.ActiveCfg = Debug|x64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x64.Build.0 = Debug|x64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|ARM64.ActiveCfg = Release|ARM64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|ARM64.Build.0 = Release|ARM64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x64.ActiveCfg = Release|x64
- {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x64.Build.0 = Release|x64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|ARM64.Build.0 = Debug|ARM64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x64.ActiveCfg = Debug|x64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x64.Build.0 = Debug|x64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|ARM64.ActiveCfg = Release|ARM64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|ARM64.Build.0 = Release|ARM64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x64.ActiveCfg = Release|x64
- {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x64.Build.0 = Release|x64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|ARM64.Build.0 = Debug|ARM64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x64.ActiveCfg = Debug|x64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x64.Build.0 = Debug|x64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|ARM64.ActiveCfg = Release|ARM64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|ARM64.Build.0 = Release|ARM64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.ActiveCfg = Release|x64
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.Build.0 = Release|x64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|ARM64.Build.0 = Debug|ARM64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|x64.ActiveCfg = Debug|x64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Debug|x64.Build.0 = Debug|x64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|ARM64.ActiveCfg = Release|ARM64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|ARM64.Build.0 = Release|ARM64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x64.ActiveCfg = Release|x64
- {0014D652-901F-4456-8D65-06FC5F997FB0}.Release|x64.Build.0 = Release|x64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|ARM64.Build.0 = Debug|ARM64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|x64.ActiveCfg = Debug|x64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Debug|x64.Build.0 = Debug|x64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Release|ARM64.ActiveCfg = Release|ARM64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Release|ARM64.Build.0 = Release|ARM64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Release|x64.ActiveCfg = Release|x64
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA}.Release|x64.Build.0 = Release|x64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Debug|ARM64.Build.0 = Debug|ARM64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Debug|x64.ActiveCfg = Debug|x64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Debug|x64.Build.0 = Debug|x64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|ARM64.ActiveCfg = Release|ARM64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|ARM64.Build.0 = Release|ARM64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x64.ActiveCfg = Release|x64
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x64.Build.0 = Release|x64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.Build.0 = Debug|ARM64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x64.ActiveCfg = Debug|x64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x64.Build.0 = Debug|x64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x64.Deploy.0 = Debug|x64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|ARM64.ActiveCfg = Release|ARM64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|ARM64.Build.0 = Release|ARM64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|ARM64.Deploy.0 = Release|ARM64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x64.ActiveCfg = Release|x64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x64.Build.0 = Release|x64
- {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x64.Deploy.0 = Release|x64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|ARM64.Build.0 = Debug|ARM64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|x64.ActiveCfg = Debug|x64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|x64.Build.0 = Debug|x64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Release|ARM64.ActiveCfg = Release|ARM64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Release|ARM64.Build.0 = Release|ARM64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Release|x64.ActiveCfg = Release|x64
- {02DD46D3-F761-47D9-8894-2D6DA0124650}.Release|x64.Build.0 = Release|x64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Debug|ARM64.Build.0 = Debug|ARM64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Debug|x64.ActiveCfg = Debug|x64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Debug|x64.Build.0 = Debug|x64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|ARM64.ActiveCfg = Release|ARM64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|ARM64.Build.0 = Release|ARM64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|x64.ActiveCfg = Release|x64
- {8E23E173-7127-4A5F-9F93-3049F2B68047}.Release|x64.Build.0 = Release|x64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|ARM64.Build.0 = Debug|ARM64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|x64.ActiveCfg = Debug|x64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Debug|x64.Build.0 = Debug|x64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Release|ARM64.ActiveCfg = Release|ARM64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Release|ARM64.Build.0 = Release|ARM64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Release|x64.ActiveCfg = Release|x64
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8}.Release|x64.Build.0 = Release|x64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Debug|ARM64.Build.0 = Debug|ARM64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Debug|x64.ActiveCfg = Debug|x64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Debug|x64.Build.0 = Debug|x64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|ARM64.ActiveCfg = Release|ARM64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|ARM64.Build.0 = Release|ARM64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|x64.ActiveCfg = Release|x64
- {C0974915-8A1D-4BF0-977B-9587D3807AB7}.Release|x64.Build.0 = Release|x64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|ARM64.Build.0 = Debug|ARM64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|x64.ActiveCfg = Debug|x64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Debug|x64.Build.0 = Debug|x64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|ARM64.ActiveCfg = Release|ARM64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|ARM64.Build.0 = Release|ARM64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x64.ActiveCfg = Release|x64
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A}.Release|x64.Build.0 = Release|x64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|ARM64.Build.0 = Debug|ARM64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|x64.ActiveCfg = Debug|x64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Debug|x64.Build.0 = Debug|x64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|ARM64.ActiveCfg = Release|ARM64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|ARM64.Build.0 = Release|ARM64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|x64.ActiveCfg = Release|x64
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0}.Release|x64.Build.0 = Release|x64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|ARM64.Build.0 = Debug|ARM64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.ActiveCfg = Debug|x64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Debug|x64.Build.0 = Debug|x64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Release|ARM64.ActiveCfg = Release|ARM64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Release|ARM64.Build.0 = Release|ARM64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.ActiveCfg = Release|x64
- {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.Build.0 = Debug|x64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.ActiveCfg = Release|ARM64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.Build.0 = Release|ARM64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.ActiveCfg = Release|x64
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.Build.0 = Release|x64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|ARM64.Build.0 = Debug|ARM64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|x64.ActiveCfg = Debug|x64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Debug|x64.Build.0 = Debug|x64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|ARM64.ActiveCfg = Release|ARM64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|ARM64.Build.0 = Release|ARM64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|x64.ActiveCfg = Release|x64
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}.Release|x64.Build.0 = Release|x64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.Build.0 = Debug|ARM64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.ActiveCfg = Debug|x64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.Build.0 = Debug|x64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.ActiveCfg = Release|ARM64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.Build.0 = Release|ARM64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.ActiveCfg = Release|x64
- {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.Build.0 = Release|x64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.Build.0 = Debug|ARM64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.ActiveCfg = Debug|x64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.Build.0 = Debug|x64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.ActiveCfg = Release|ARM64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.Build.0 = Release|ARM64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.ActiveCfg = Release|x64
- {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.Build.0 = Release|x64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.Build.0 = Debug|ARM64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.ActiveCfg = Debug|x64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.Build.0 = Debug|x64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.ActiveCfg = Release|ARM64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.Build.0 = Release|ARM64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.ActiveCfg = Release|x64
- {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.Build.0 = Release|x64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.Build.0 = Debug|ARM64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.ActiveCfg = Debug|x64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.Build.0 = Debug|x64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.ActiveCfg = Release|ARM64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.Build.0 = Release|ARM64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.ActiveCfg = Release|x64
- {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.Build.0 = Release|x64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.Build.0 = Debug|ARM64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.ActiveCfg = Debug|x64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.Build.0 = Debug|x64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.ActiveCfg = Release|ARM64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.Build.0 = Release|ARM64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.ActiveCfg = Release|x64
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|ARM64.Build.0 = Debug|ARM64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|x64.ActiveCfg = Debug|x64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|x64.Build.0 = Debug|x64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|ARM64.ActiveCfg = Release|ARM64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|ARM64.Build.0 = Release|ARM64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x64.ActiveCfg = Release|x64
- {37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x64.Build.0 = Release|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.Build.0 = Debug|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.ActiveCfg = Debug|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.Build.0 = Debug|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.ActiveCfg = Release|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.Build.0 = Release|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.ActiveCfg = Release|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.Build.0 = Release|x64
- {66614C26-314C-4B91-9071-76133422CFEF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {66614C26-314C-4B91-9071-76133422CFEF}.Debug|ARM64.Build.0 = Debug|ARM64
- {66614C26-314C-4B91-9071-76133422CFEF}.Debug|x64.ActiveCfg = Debug|x64
- {66614C26-314C-4B91-9071-76133422CFEF}.Debug|x64.Build.0 = Debug|x64
- {66614C26-314C-4B91-9071-76133422CFEF}.Release|ARM64.ActiveCfg = Release|ARM64
- {66614C26-314C-4B91-9071-76133422CFEF}.Release|ARM64.Build.0 = Release|ARM64
- {66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.ActiveCfg = Release|x64
- {66614C26-314C-4B91-9071-76133422CFEF}.Release|x64.Build.0 = Release|x64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Debug|ARM64.Build.0 = Debug|ARM64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Debug|x64.ActiveCfg = Debug|x64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Debug|x64.Build.0 = Debug|x64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Release|ARM64.ActiveCfg = Release|ARM64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Release|ARM64.Build.0 = Release|ARM64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Release|x64.ActiveCfg = Release|x64
- {6CE438DF-C245-4997-A360-0A0939E4BA34}.Release|x64.Build.0 = Release|x64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Debug|ARM64.Build.0 = Debug|ARM64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Debug|x64.ActiveCfg = Debug|x64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Debug|x64.Build.0 = Debug|x64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Release|ARM64.ActiveCfg = Release|ARM64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Release|ARM64.Build.0 = Release|ARM64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Release|x64.ActiveCfg = Release|x64
- {E09AA983-C755-474F-83D6-A5CDF528C070}.Release|x64.Build.0 = Release|x64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Debug|ARM64.Build.0 = Debug|ARM64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Debug|x64.ActiveCfg = Debug|x64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Debug|x64.Build.0 = Debug|x64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Release|ARM64.ActiveCfg = Release|ARM64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Release|ARM64.Build.0 = Release|ARM64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Release|x64.ActiveCfg = Release|x64
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399}.Release|x64.Build.0 = Release|x64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Debug|ARM64.Build.0 = Debug|ARM64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Debug|x64.ActiveCfg = Debug|x64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Debug|x64.Build.0 = Debug|x64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Release|ARM64.ActiveCfg = Release|ARM64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Release|ARM64.Build.0 = Release|ARM64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Release|x64.ActiveCfg = Release|x64
- {92EC89E4-9972-453A-8A1A-3A9E230C146A}.Release|x64.Build.0 = Release|x64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Debug|ARM64.Build.0 = Debug|ARM64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Debug|x64.ActiveCfg = Debug|x64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Debug|x64.Build.0 = Debug|x64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Release|ARM64.ActiveCfg = Release|ARM64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Release|ARM64.Build.0 = Release|ARM64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Release|x64.ActiveCfg = Release|x64
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95}.Release|x64.Build.0 = Release|x64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Debug|ARM64.Build.0 = Debug|ARM64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Debug|x64.ActiveCfg = Debug|x64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Debug|x64.Build.0 = Debug|x64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Release|ARM64.ActiveCfg = Release|ARM64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Release|ARM64.Build.0 = Release|ARM64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Release|x64.ActiveCfg = Release|x64
- {D1160404-D3D1-497A-883A-4059C07C2273}.Release|x64.Build.0 = Release|x64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Debug|ARM64.Build.0 = Debug|ARM64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Debug|x64.ActiveCfg = Debug|x64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Debug|x64.Build.0 = Debug|x64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Release|ARM64.ActiveCfg = Release|ARM64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Release|ARM64.Build.0 = Release|ARM64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Release|x64.ActiveCfg = Release|x64
- {40F6D69D-E321-400F-A767-5628C7AE453D}.Release|x64.Build.0 = Release|x64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|ARM64.Build.0 = Debug|ARM64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|x64.ActiveCfg = Debug|x64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|x64.Build.0 = Debug|x64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|ARM64.ActiveCfg = Release|ARM64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|ARM64.Build.0 = Release|ARM64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|x64.ActiveCfg = Release|x64
- {305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|x64.Build.0 = Release|x64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Debug|ARM64.Build.0 = Debug|ARM64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Debug|x64.ActiveCfg = Debug|x64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Debug|x64.Build.0 = Debug|x64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Release|ARM64.ActiveCfg = Release|ARM64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Release|ARM64.Build.0 = Release|ARM64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Release|x64.ActiveCfg = Release|x64
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24}.Release|x64.Build.0 = Release|x64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Debug|ARM64.Build.0 = Debug|ARM64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Debug|x64.ActiveCfg = Debug|x64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Debug|x64.Build.0 = Debug|x64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Release|ARM64.ActiveCfg = Release|ARM64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Release|ARM64.Build.0 = Release|ARM64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Release|x64.ActiveCfg = Release|x64
- {14E62033-58D0-4A7D-8990-52F50A08BBBD}.Release|x64.Build.0 = Release|x64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|ARM64.Build.0 = Debug|ARM64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x64.ActiveCfg = Debug|x64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x64.Build.0 = Debug|x64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|ARM64.ActiveCfg = Release|ARM64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|ARM64.Build.0 = Release|ARM64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x64.ActiveCfg = Release|x64
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x64.Build.0 = Release|x64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Debug|ARM64.Build.0 = Debug|ARM64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Debug|x64.ActiveCfg = Debug|x64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Debug|x64.Build.0 = Debug|x64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Debug|x64.Deploy.0 = Debug|x64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Release|ARM64.ActiveCfg = Release|ARM64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Release|ARM64.Build.0 = Release|ARM64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Release|ARM64.Deploy.0 = Release|ARM64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Release|x64.ActiveCfg = Release|x64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Release|x64.Build.0 = Release|x64
- {C846F7A7-792A-47D9-B0CB-417C900EE03D}.Release|x64.Deploy.0 = Release|x64
- {C831231F-891C-4572-9694-45062534B42A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C831231F-891C-4572-9694-45062534B42A}.Debug|ARM64.Build.0 = Debug|ARM64
- {C831231F-891C-4572-9694-45062534B42A}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {C831231F-891C-4572-9694-45062534B42A}.Debug|x64.ActiveCfg = Debug|x64
- {C831231F-891C-4572-9694-45062534B42A}.Debug|x64.Build.0 = Debug|x64
- {C831231F-891C-4572-9694-45062534B42A}.Debug|x64.Deploy.0 = Debug|x64
- {C831231F-891C-4572-9694-45062534B42A}.Release|ARM64.ActiveCfg = Release|ARM64
- {C831231F-891C-4572-9694-45062534B42A}.Release|ARM64.Build.0 = Release|ARM64
- {C831231F-891C-4572-9694-45062534B42A}.Release|ARM64.Deploy.0 = Release|ARM64
- {C831231F-891C-4572-9694-45062534B42A}.Release|x64.ActiveCfg = Release|x64
- {C831231F-891C-4572-9694-45062534B42A}.Release|x64.Build.0 = Release|x64
- {C831231F-891C-4572-9694-45062534B42A}.Release|x64.Deploy.0 = Release|x64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Debug|ARM64.Build.0 = Debug|ARM64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Debug|x64.ActiveCfg = Debug|x64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Debug|x64.Build.0 = Debug|x64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Debug|x64.Deploy.0 = Debug|x64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Release|ARM64.ActiveCfg = Release|ARM64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Release|ARM64.Build.0 = Release|ARM64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Release|ARM64.Deploy.0 = Release|ARM64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Release|x64.ActiveCfg = Release|x64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Release|x64.Build.0 = Release|x64
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90}.Release|x64.Deploy.0 = Release|x64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Debug|ARM64.Build.0 = Debug|ARM64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Debug|x64.ActiveCfg = Debug|x64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Debug|x64.Build.0 = Debug|x64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Release|ARM64.ActiveCfg = Release|ARM64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Release|ARM64.Build.0 = Release|ARM64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Release|x64.ActiveCfg = Release|x64
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2}.Release|x64.Build.0 = Release|x64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Debug|ARM64.Build.0 = Debug|ARM64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Debug|x64.ActiveCfg = Debug|x64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Debug|x64.Build.0 = Debug|x64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Release|ARM64.ActiveCfg = Release|ARM64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Release|ARM64.Build.0 = Release|ARM64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Release|x64.ActiveCfg = Release|x64
- {79775343-7A3D-445D-9104-3DD5B2893DF9}.Release|x64.Build.0 = Release|x64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Debug|ARM64.Build.0 = Debug|ARM64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Debug|x64.ActiveCfg = Debug|x64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Debug|x64.Build.0 = Debug|x64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Release|ARM64.ActiveCfg = Release|ARM64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Release|ARM64.Build.0 = Release|ARM64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Release|x64.ActiveCfg = Release|x64
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8}.Release|x64.Build.0 = Release|x64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|ARM64.Build.0 = Debug|ARM64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x64.ActiveCfg = Debug|x64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Debug|x64.Build.0 = Debug|x64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|ARM64.ActiveCfg = Release|ARM64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|ARM64.Build.0 = Release|ARM64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.ActiveCfg = Release|x64
- {89D0E199-B17A-418C-B2F8-7375B6708357}.Release|x64.Build.0 = Release|x64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|ARM64.Build.0 = Debug|ARM64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x64.ActiveCfg = Debug|x64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Debug|x64.Build.0 = Debug|x64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|ARM64.ActiveCfg = Release|ARM64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|ARM64.Build.0 = Release|ARM64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.ActiveCfg = Release|x64
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E}.Release|x64.Build.0 = Release|x64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Debug|ARM64.Build.0 = Debug|ARM64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Debug|x64.ActiveCfg = Debug|x64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Debug|x64.Build.0 = Debug|x64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Debug|x64.Deploy.0 = Debug|x64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Release|ARM64.ActiveCfg = Release|ARM64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Release|ARM64.Build.0 = Release|ARM64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Release|ARM64.Deploy.0 = Release|ARM64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Release|x64.ActiveCfg = Release|x64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Release|x64.Build.0 = Release|x64
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D}.Release|x64.Deploy.0 = Release|x64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Debug|ARM64.Build.0 = Debug|ARM64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Debug|x64.ActiveCfg = Debug|x64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Debug|x64.Build.0 = Debug|x64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Release|ARM64.ActiveCfg = Release|ARM64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Release|ARM64.Build.0 = Release|ARM64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Release|x64.ActiveCfg = Release|x64
- {C0CE3B5E-16D3-495D-B335-CA791B660162}.Release|x64.Build.0 = Release|x64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|ARM64.Build.0 = Debug|ARM64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|x64.ActiveCfg = Debug|x64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Debug|x64.Build.0 = Debug|x64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|ARM64.ActiveCfg = Release|ARM64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|ARM64.Build.0 = Release|ARM64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|x64.ActiveCfg = Release|x64
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C}.Release|x64.Build.0 = Release|x64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Debug|ARM64.Build.0 = Debug|ARM64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Debug|x64.ActiveCfg = Debug|x64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Debug|x64.Build.0 = Debug|x64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Release|ARM64.ActiveCfg = Release|ARM64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Release|ARM64.Build.0 = Release|ARM64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Release|x64.ActiveCfg = Release|x64
- {605E914B-7232-4789-AF46-BF5D3DDFC14E}.Release|x64.Build.0 = Release|x64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Debug|ARM64.Build.0 = Debug|ARM64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Debug|x64.ActiveCfg = Debug|x64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Debug|x64.Build.0 = Debug|x64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Debug|x64.Deploy.0 = Debug|x64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Release|ARM64.ActiveCfg = Release|ARM64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Release|ARM64.Build.0 = Release|ARM64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Release|ARM64.Deploy.0 = Release|ARM64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Release|x64.ActiveCfg = Release|x64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Release|x64.Build.0 = Release|x64
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9}.Release|x64.Deploy.0 = Release|x64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|ARM64.Build.0 = Debug|ARM64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x64.ActiveCfg = Debug|x64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Debug|x64.Build.0 = Debug|x64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|ARM64.ActiveCfg = Release|ARM64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|ARM64.Build.0 = Release|ARM64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x64.ActiveCfg = Release|x64
- {0A84F764-3A88-44CD-AA96-41BDBD48627B}.Release|x64.Build.0 = Release|x64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|ARM64.Build.0 = Debug|ARM64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|x64.ActiveCfg = Debug|x64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Debug|x64.Build.0 = Debug|x64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|ARM64.ActiveCfg = Release|ARM64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|ARM64.Build.0 = Release|ARM64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|x64.ActiveCfg = Release|x64
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C}.Release|x64.Build.0 = Release|x64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|ARM64.Build.0 = Debug|ARM64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|x64.ActiveCfg = Debug|x64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Debug|x64.Build.0 = Debug|x64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.ActiveCfg = Release|ARM64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|ARM64.Build.0 = Release|ARM64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.ActiveCfg = Release|x64
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461}.Release|x64.Build.0 = Release|x64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Debug|ARM64.Build.0 = Debug|ARM64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Debug|x64.ActiveCfg = Debug|x64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Debug|x64.Build.0 = Debug|x64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Debug|x64.Deploy.0 = Debug|x64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Release|ARM64.ActiveCfg = Release|ARM64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Release|ARM64.Build.0 = Release|ARM64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Release|ARM64.Deploy.0 = Release|ARM64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Release|x64.ActiveCfg = Release|x64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Release|x64.Build.0 = Release|x64
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449}.Release|x64.Deploy.0 = Release|x64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|ARM64.Build.0 = Debug|ARM64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.ActiveCfg = Debug|x64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Debug|x64.Build.0 = Debug|x64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.ActiveCfg = Release|ARM64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|ARM64.Build.0 = Release|ARM64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.ActiveCfg = Release|x64
- {A558C25D-2007-498E-8B6F-43405AFAE9E2}.Release|x64.Build.0 = Release|x64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|ARM64.Build.0 = Debug|ARM64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|x64.ActiveCfg = Debug|x64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Debug|x64.Build.0 = Debug|x64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Release|ARM64.ActiveCfg = Release|ARM64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Release|ARM64.Build.0 = Release|ARM64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Release|x64.ActiveCfg = Release|x64
- {08F9155D-B6DC-46E5-9C83-AF60B655898B}.Release|x64.Build.0 = Release|x64
- {4382A954-179A-4078-92AF-715187DFFF50}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4382A954-179A-4078-92AF-715187DFFF50}.Debug|ARM64.Build.0 = Debug|ARM64
- {4382A954-179A-4078-92AF-715187DFFF50}.Debug|x64.ActiveCfg = Debug|x64
- {4382A954-179A-4078-92AF-715187DFFF50}.Debug|x64.Build.0 = Debug|x64
- {4382A954-179A-4078-92AF-715187DFFF50}.Release|ARM64.ActiveCfg = Release|ARM64
- {4382A954-179A-4078-92AF-715187DFFF50}.Release|ARM64.Build.0 = Release|ARM64
- {4382A954-179A-4078-92AF-715187DFFF50}.Release|x64.ActiveCfg = Release|x64
- {4382A954-179A-4078-92AF-715187DFFF50}.Release|x64.Build.0 = Release|x64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Debug|ARM64.Build.0 = Debug|ARM64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Debug|x64.ActiveCfg = Debug|x64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Debug|x64.Build.0 = Debug|x64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|ARM64.ActiveCfg = Release|ARM64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|ARM64.Build.0 = Release|ARM64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.ActiveCfg = Release|x64
- {EBED240C-8702-452D-B764-6DB9DA9179AF}.Release|x64.Build.0 = Release|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|ARM64.Build.0 = Debug|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|x64.ActiveCfg = Debug|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Debug|x64.Build.0 = Debug|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.ActiveCfg = Release|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|ARM64.Build.0 = Release|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.ActiveCfg = Release|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0}.Release|x64.Build.0 = Release|x64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|ARM64.Build.0 = Debug|ARM64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|x64.ActiveCfg = Debug|x64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Debug|x64.Build.0 = Debug|x64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|ARM64.ActiveCfg = Release|ARM64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|ARM64.Build.0 = Release|ARM64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|x64.ActiveCfg = Release|x64
- {5702B3CC-8575-48D5-83D8-15BB42269CD3}.Release|x64.Build.0 = Release|x64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Debug|ARM64.Build.0 = Debug|ARM64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Debug|x64.ActiveCfg = Debug|x64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Debug|x64.Build.0 = Debug|x64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|ARM64.ActiveCfg = Release|ARM64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|ARM64.Build.0 = Release|ARM64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.ActiveCfg = Release|x64
- {64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.Build.0 = Release|x64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|ARM64.Build.0 = Debug|ARM64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|x64.ActiveCfg = Debug|x64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Debug|x64.Build.0 = Debug|x64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Release|ARM64.ActiveCfg = Release|ARM64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Release|ARM64.Build.0 = Release|ARM64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Release|x64.ActiveCfg = Release|x64
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}.Release|x64.Build.0 = Release|x64
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Debug|x64.ActiveCfg = Debug|x64
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Debug|x64.Build.0 = Debug|x64
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|ARM64.ActiveCfg = Release|ARM64
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.ActiveCfg = Release|x64
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}.Release|x64.Build.0 = Release|x64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|ARM64.Build.0 = Debug|ARM64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|x64.ActiveCfg = Debug|x64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Debug|x64.Build.0 = Debug|x64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|ARM64.ActiveCfg = Release|ARM64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|ARM64.Build.0 = Release|ARM64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|x64.ActiveCfg = Release|x64
- {9E0CBC06-F29A-4810-B93C-97D53863B95E}.Release|x64.Build.0 = Release|x64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|ARM64.Build.0 = Debug|ARM64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|x64.ActiveCfg = Debug|x64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Debug|x64.Build.0 = Debug|x64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|ARM64.ActiveCfg = Release|ARM64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|ARM64.Build.0 = Release|ARM64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|x64.ActiveCfg = Release|x64
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}.Release|x64.Build.0 = Release|x64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|ARM64.Build.0 = Debug|ARM64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|x64.ActiveCfg = Debug|x64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Debug|x64.Build.0 = Debug|x64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|ARM64.ActiveCfg = Release|ARM64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|ARM64.Build.0 = Release|ARM64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|x64.ActiveCfg = Release|x64
- {47B0678C-806B-4FE1-9F50-46BA88989532}.Release|x64.Build.0 = Release|x64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|ARM64.Build.0 = Debug|ARM64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|x64.ActiveCfg = Debug|x64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Debug|x64.Build.0 = Debug|x64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|ARM64.ActiveCfg = Release|ARM64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|ARM64.Build.0 = Release|ARM64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|x64.ActiveCfg = Release|x64
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA}.Release|x64.Build.0 = Release|x64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|ARM64.Build.0 = Debug|ARM64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|x64.ActiveCfg = Debug|x64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Debug|x64.Build.0 = Debug|x64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|ARM64.ActiveCfg = Release|ARM64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|ARM64.Build.0 = Release|ARM64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|x64.ActiveCfg = Release|x64
- {99CA1509-FB73-456E-AFAF-AB89C017BD72}.Release|x64.Build.0 = Release|x64
- {61CBF221-9452-4934-B685-146285E080D7}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {61CBF221-9452-4934-B685-146285E080D7}.Debug|ARM64.Build.0 = Debug|ARM64
- {61CBF221-9452-4934-B685-146285E080D7}.Debug|x64.ActiveCfg = Debug|x64
- {61CBF221-9452-4934-B685-146285E080D7}.Debug|x64.Build.0 = Debug|x64
- {61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.ActiveCfg = Release|ARM64
- {61CBF221-9452-4934-B685-146285E080D7}.Release|ARM64.Build.0 = Release|ARM64
- {61CBF221-9452-4934-B685-146285E080D7}.Release|x64.ActiveCfg = Release|x64
- {61CBF221-9452-4934-B685-146285E080D7}.Release|x64.Build.0 = Release|x64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|ARM64.Build.0 = Debug|ARM64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.ActiveCfg = Debug|x64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Debug|x64.Build.0 = Debug|x64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.ActiveCfg = Release|ARM64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|ARM64.Build.0 = Release|ARM64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.ActiveCfg = Release|x64
- {38F187B2-6638-5A40-072F-DBE5E54070A0}.Release|x64.Build.0 = Release|x64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|ARM64.Build.0 = Debug|ARM64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.ActiveCfg = Debug|x64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Debug|x64.Build.0 = Debug|x64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.ActiveCfg = Release|ARM64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|ARM64.Build.0 = Release|ARM64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.ActiveCfg = Release|x64
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE}.Release|x64.Build.0 = Release|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|ARM64.Build.0 = Debug|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.ActiveCfg = Debug|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Debug|x64.Build.0 = Debug|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.ActiveCfg = Release|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|ARM64.Build.0 = Release|ARM64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.ActiveCfg = Release|x64
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1}.Release|x64.Build.0 = Release|x64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|ARM64.Build.0 = Debug|ARM64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.ActiveCfg = Debug|x64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Debug|x64.Build.0 = Debug|x64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.ActiveCfg = Release|ARM64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|ARM64.Build.0 = Release|ARM64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.ActiveCfg = Release|x64
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9}.Release|x64.Build.0 = Release|x64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|ARM64.Build.0 = Debug|ARM64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.ActiveCfg = Debug|x64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Debug|x64.Build.0 = Debug|x64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|ARM64.ActiveCfg = Release|ARM64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|ARM64.Build.0 = Release|ARM64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|x64.ActiveCfg = Release|x64
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6}.Release|x64.Build.0 = Release|x64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|ARM64.Build.0 = Debug|ARM64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|x64.ActiveCfg = Debug|x64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Debug|x64.Build.0 = Debug|x64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|ARM64.ActiveCfg = Release|ARM64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|ARM64.Build.0 = Release|ARM64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.ActiveCfg = Release|x64
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A}.Release|x64.Build.0 = Release|x64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|ARM64.Build.0 = Debug|ARM64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|x64.ActiveCfg = Debug|x64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Debug|x64.Build.0 = Debug|x64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Release|ARM64.ActiveCfg = Release|ARM64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Release|ARM64.Build.0 = Release|ARM64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Release|x64.ActiveCfg = Release|x64
- {9D3F3793-EFE3-4525-8782-238015DABA62}.Release|x64.Build.0 = Release|x64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|ARM64.Build.0 = Debug|ARM64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.ActiveCfg = Debug|x64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Debug|x64.Build.0 = Debug|x64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.ActiveCfg = Release|ARM64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|ARM64.Build.0 = Release|ARM64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.ActiveCfg = Release|x64
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027}.Release|x64.Build.0 = Release|x64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Debug|ARM64.Build.0 = Debug|ARM64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Debug|x64.ActiveCfg = Debug|x64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Debug|x64.Build.0 = Debug|x64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Release|ARM64.ActiveCfg = Release|ARM64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Release|ARM64.Build.0 = Release|ARM64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Release|x64.ActiveCfg = Release|x64
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA}.Release|x64.Build.0 = Release|x64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Debug|ARM64.Build.0 = Debug|ARM64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Debug|x64.ActiveCfg = Debug|x64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Debug|x64.Build.0 = Debug|x64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Release|ARM64.ActiveCfg = Release|ARM64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Release|ARM64.Build.0 = Release|ARM64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Release|x64.ActiveCfg = Release|x64
- {4122388B-59E4-CDB0-0338-EA6881DF86F0}.Release|x64.Build.0 = Release|x64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Debug|ARM64.Build.0 = Debug|ARM64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Debug|x64.ActiveCfg = Debug|x64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Debug|x64.Build.0 = Debug|x64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Release|ARM64.ActiveCfg = Release|ARM64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Release|ARM64.Build.0 = Release|ARM64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Release|x64.ActiveCfg = Release|x64
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55}.Release|x64.Build.0 = Release|x64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Debug|ARM64.Build.0 = Debug|ARM64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Debug|x64.ActiveCfg = Debug|x64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Debug|x64.Build.0 = Debug|x64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Release|ARM64.ActiveCfg = Release|ARM64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Release|ARM64.Build.0 = Release|ARM64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Release|x64.ActiveCfg = Release|x64
- {6748A29D-DA6A-033A-825B-752295FF6AA0}.Release|x64.Build.0 = Release|x64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Debug|ARM64.Build.0 = Debug|ARM64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Debug|x64.ActiveCfg = Debug|x64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Debug|x64.Build.0 = Debug|x64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Release|ARM64.ActiveCfg = Release|ARM64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Release|ARM64.Build.0 = Release|ARM64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Release|x64.ActiveCfg = Release|x64
- {6EABCF9A-6526-441F-932F-658B1DC3E403}.Release|x64.Build.0 = Release|x64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Debug|ARM64.Build.0 = Debug|ARM64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Debug|x64.ActiveCfg = Debug|x64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Debug|x64.Build.0 = Debug|x64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Release|ARM64.ActiveCfg = Release|ARM64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Release|ARM64.Build.0 = Release|ARM64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Release|x64.ActiveCfg = Release|x64
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15}.Release|x64.Build.0 = Release|x64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Debug|ARM64.Build.0 = Debug|ARM64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Debug|x64.ActiveCfg = Debug|x64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Debug|x64.Build.0 = Debug|x64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Release|ARM64.ActiveCfg = Release|ARM64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Release|ARM64.Build.0 = Release|ARM64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Release|x64.ActiveCfg = Release|x64
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83}.Release|x64.Build.0 = Release|x64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Debug|ARM64.Build.0 = Debug|ARM64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Debug|x64.ActiveCfg = Debug|x64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Debug|x64.Build.0 = Debug|x64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Release|ARM64.ActiveCfg = Release|ARM64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Release|ARM64.Build.0 = Release|ARM64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Release|x64.ActiveCfg = Release|x64
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9}.Release|x64.Build.0 = Release|x64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Debug|ARM64.Build.0 = Debug|ARM64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Debug|x64.ActiveCfg = Debug|x64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Debug|x64.Build.0 = Debug|x64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Release|ARM64.ActiveCfg = Release|ARM64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Release|ARM64.Build.0 = Release|ARM64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Release|x64.ActiveCfg = Release|x64
- {F93C2817-C846-4259-84D8-B39A6B57C8DE}.Release|x64.Build.0 = Release|x64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|ARM64.Build.0 = Debug|ARM64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|x64.ActiveCfg = Debug|x64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|x64.Build.0 = Debug|x64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|ARM64.ActiveCfg = Release|ARM64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|ARM64.Build.0 = Release|ARM64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|x64.ActiveCfg = Release|x64
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|x64.Build.0 = Release|x64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|ARM64.Build.0 = Debug|ARM64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|x64.ActiveCfg = Debug|x64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Debug|x64.Build.0 = Debug|x64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|ARM64.ActiveCfg = Release|ARM64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|ARM64.Build.0 = Release|ARM64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|x64.ActiveCfg = Release|x64
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826}.Release|x64.Build.0 = Release|x64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|ARM64.Build.0 = Debug|ARM64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|x64.ActiveCfg = Debug|x64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Debug|x64.Build.0 = Debug|x64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|ARM64.ActiveCfg = Release|ARM64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|ARM64.Build.0 = Release|ARM64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|x64.ActiveCfg = Release|x64
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827}.Release|x64.Build.0 = Release|x64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|ARM64.Build.0 = Debug|ARM64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|x64.ActiveCfg = Debug|x64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Debug|x64.Build.0 = Debug|x64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|ARM64.ActiveCfg = Release|ARM64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|ARM64.Build.0 = Release|ARM64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|x64.ActiveCfg = Release|x64
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828}.Release|x64.Build.0 = Release|x64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|ARM64.Build.0 = Debug|ARM64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|x64.ActiveCfg = Debug|x64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Debug|x64.Build.0 = Debug|x64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.ActiveCfg = Release|ARM64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.Build.0 = Debug|ARM64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.ActiveCfg = Debug|x64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.Build.0 = Debug|x64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.ActiveCfg = Release|ARM64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
- {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|ARM64.Build.0 = Debug|ARM64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.ActiveCfg = Debug|x64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Debug|x64.Build.0 = Debug|x64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.ActiveCfg = Release|ARM64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|ARM64.Build.0 = Release|ARM64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.ActiveCfg = Release|x64
- {38177D56-6AD1-4ADF-88C9-2843A7932166}.Release|x64.Build.0 = Release|x64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|ARM64.Build.0 = Debug|ARM64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.ActiveCfg = Debug|x64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Debug|x64.Build.0 = Debug|x64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.ActiveCfg = Release|ARM64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|ARM64.Build.0 = Release|ARM64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.ActiveCfg = Release|x64
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC}.Release|x64.Build.0 = Release|x64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.Build.0 = Debug|x64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.ActiveCfg = Release|ARM64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
- {66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|ARM64.Build.0 = Debug|ARM64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|x64.ActiveCfg = Debug|x64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Debug|x64.Build.0 = Debug|x64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|ARM64.ActiveCfg = Release|ARM64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|ARM64.Build.0 = Release|ARM64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|x64.ActiveCfg = Release|x64
- {94CDC147-6137-45E9-AEDE-17FF809607C0}.Release|x64.Build.0 = Release|x64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|ARM64.Build.0 = Debug|ARM64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|x64.ActiveCfg = Debug|x64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Debug|x64.Build.0 = Debug|x64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|ARM64.ActiveCfg = Release|ARM64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|ARM64.Build.0 = Release|ARM64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.Release|x64.ActiveCfg = Release|x64
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78}.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
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|ARM64.Build.0 = Debug|ARM64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|ARM64.Deploy.0 = Debug|ARM64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|x64.ActiveCfg = Debug|x64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|x64.Build.0 = Debug|x64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Debug|x64.Deploy.0 = Debug|x64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|ARM64.ActiveCfg = Release|ARM64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|ARM64.Build.0 = Release|ARM64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|ARM64.Deploy.0 = Release|ARM64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|x64.ActiveCfg = Release|x64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|x64.Build.0 = Release|x64
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F}.Release|x64.Deploy.0 = Release|x64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|ARM64.Build.0 = Debug|ARM64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|x64.ActiveCfg = Debug|x64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|x64.Build.0 = Debug|x64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|ARM64.ActiveCfg = Release|ARM64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|ARM64.Build.0 = Release|ARM64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|x64.ActiveCfg = Release|x64
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|x64.Build.0 = Release|x64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Debug|ARM64.Build.0 = Debug|ARM64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Debug|x64.ActiveCfg = Debug|x64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Debug|x64.Build.0 = Debug|x64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|ARM64.ActiveCfg = Release|ARM64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|ARM64.Build.0 = Release|ARM64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.ActiveCfg = Release|x64
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {3BB8493E-D18E-4485-A320-CB40F90F55AE} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
- {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
- {1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
- {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
- {51920F1F-C28C-4ADF-8660-4238766796C2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
- {A3935CF4-46C5-4A88-84D3-6B12E16E6BA2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
- {2151F984-E006-4A9F-92EF-C6DDE3DC8413} = {66E1534A-1587-42B2-912F-45C994D32904}
- {89F34AF7-1C34-4A72-AA6E-534BCF972BD9} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {6C7F47CC-2151-44A3-A546-41C70025132C} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {6C7F47CC-2151-44A3-A546-41C70025132C}
- {0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {6C7F47CC-2151-44A3-A546-41C70025132C}
- {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8} = {0E556541-6A45-42CB-AE49-EE5A9BE05E7C}
- {17DA04DF-E393-4397-9CF0-84DABE11032E} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {38BDB927-829B-4C65-9CD9-93FB05D66D65} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {8AFFA899-0B73-49EC-8C50-0FADDA57B2FC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {8451ECDD-2EA4-4966-BB0A-7BBC40138E80} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {FF742965-9A80-41A5-B042-D6C7D3A21708} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {4AFC9975-2456-4C70-94A4-84073C1CED93} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {4D971245-7A70-41D5-BAA0-DDB5684CAF51} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {74F1B9ED-F59C-4FE7-B473-7B453E30837E} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {E364F67B-BB12-4E91-B639-355866EBCD8B} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {F97E5003-F263-4D4A-A964-0F1F3C82DEF2} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {2F305555-C296-497E-AC20-5FA1B237996A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {AF2349B8-E5B6-4004-9502-687C1C7730B1} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {DA425894-6E13-404F-8DCB-78584EC0557A} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {060D75DA-2D1C-48E6-A4A1-6F0718B64661} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {748417CA-F17E-487F-9411-CAFB6D3F4877} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {217DF501-135C-4E38-BFC8-99D4821032EA} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
- {787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {08C8C05F-0362-41BC-818C-724572DF8B06} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {5D00D290-4016-4CFE-9E41-1E7C724509BA} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {4AED67B6-55FD-486F-B917-E543DEE2CB3C} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {42851751-CBC8-45A6-97F5-7A0753F7B4D1} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {1EF1EEF0-10F0-4F2E-8550-39B6D8044D3E} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {8FFE09DA-FA4F-4EE1-B3A2-AD5497FBD1AD} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
- {BA58206B-1493-4C75-BFEA-A85768A1E156} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
- {1D78B84B-CA39-406C-98F4-71F7EC266CC0} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {B81FB7B6-D30E-428F-908A-41422EFC1172} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {0F85E674-34AE-443D-954C-8321EB8B93B1} = {E885E71F-0B34-4A03-B13B-20F4E05E90BB}
- {632BBE62-5421-49EA-835A-7FFA4F499BD6} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F} = {E4E03FE0-94FD-47C7-88C5-F17D0AA549D3}
- {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {DA5A6FE9-0040-40CC-83CC-764AE5306590} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {0351ADA4-0C32-4652-9BA0-41F7B602372B} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD} = {E4E03FE0-94FD-47C7-88C5-F17D0AA549D3}
- {6955446D-23F7-4023-9BB3-8657F904AF99} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {58736667-1027-4AD7-BFDF-7A3A6474103A} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
- {D92131D6-7610-4D60-A7DB-1C169783F83B} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525} = {D92131D6-7610-4D60-A7DB-1C169783F83B}
- {031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {D92131D6-7610-4D60-A7DB-1C169783F83B}
- {0B593A6C-4143-4337-860E-DB5710FB87DB} = {D92131D6-7610-4D60-A7DB-1C169783F83B}
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {5A7818A8-109C-4E1C-850D-1A654E234B0E} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {E4E03FE0-94FD-47C7-88C5-F17D0AA549D3} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {7319089E-46D6-4400-BC65-E39BDF1416EE} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {CABA8DFB-823B-4BF2-93AC-3F31984150D9} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {98537082-0FDB-40DE-ABD8-0DC5A4269BAB} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {B39DC643-4663-475E-B329-03F0C9918D48} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {8F62026A-294B-41C6-8839-87463613F216} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {C3A17DCA-217B-462C-BB0C-BE086AF80081} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {69E1EE8D-143A-4060-9129-4658ACF14AAF} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {ECC20689-002A-4354-95A6-B58DF089C6FF} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {0648DF05-5DDA-4BE1-B5F2-584926EBDB65} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {BA661F5B-1D5A-4FFC-9BF1-FC39DF280BDD} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {E496B7FC-1E99-4BAB-849B-0E8367040B02} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {7F4B3A60-BC27-45A7-8000-68B0B6EA7466} = {D9BD324E-1D80-44AA-8E7B-73EB00944434}
- {8DF78B53-200E-451F-9328-01EB907193AE} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {23D2070D-E4AD-4ADD-85A7-083D9C76AD49} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {62173D9A-6724-4C00-A1C8-FB646480A9EC} = {D9BD324E-1D80-44AA-8E7B-73EB00944434}
- {127F38E0-40AA-4594-B955-5616BF206882} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {5E7360A8-D048-4ED3-8F09-0BFD64C5529A} = {127F38E0-40AA-4594-B955-5616BF206882}
- {D940E07F-532C-4FF3-883F-790DA014F19A} = {127F38E0-40AA-4594-B955-5616BF206882}
- {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {3E424AD2-19E5-4AE6-B833-F53963EB5FC1} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {106CBECA-0701-4FC3-838C-9DF816A19AE2} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {2D604C07-51FC-46BB-9EB7-75AECC7F5E81} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
- {2EDB3EB4-FA92-4BFF-B2D8-566584837231} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}
- {48804216-2A0E-4168-A6D8-9CD068D14227} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
- {FF1D7936-842A-4BBB-8BEA-E9FE796DE700} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
- {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {11491FD8-F921-48BF-880C-7FEA185B80A1} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {F40C3397-1834-4530-B2D9-8F8B8456BCDF} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {4ED320BC-BA04-4D42-8D15-CBE62151F08B} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {322566EF-20DC-43A6-B9F8-616AF942579A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {E94FD11C-0591-456F-899F-EFC0CA548336} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {782A61BE-9D85-4081-B35C-1CCC9DCC1E88} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {809AA252-E17A-4FA2-B0A1-0450976B763F} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {133281D8-1BCE-4D07-B31E-796612A9609E} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {805306FF-A562-4415-8DEF-E493BDC45918} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {FCF3E52D-B80A-4FC3-98FD-6391354F0EE3} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {60CD2D4F-C3B9-4897-9821-FCA5098B41CE} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {1DC3BE92-CE89-43FB-8110-9C043A2FE7A2} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE}
- {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE}
- {9F94B303-5E21-4364-9362-64426F8DB932} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {F7C8C0F1-5431-4347-89D0-8E5354F93CF2} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {04B193D7-3E21-46B8-A958-89B63A8A69DE} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {5BDBD6C9-A31F-4CEB-871A-5E9E709197EF} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {FD464B4C-2F68-4D06-91E7-4208146C41F5} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {8FE5A5EE-1B59-401C-9FB3-B04ECD3E29C1} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {020A7474-3601-4160-A159-D7B70B77B15F} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
- {27718999-C175-450A-861C-89F911E16A88} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
- {1DBBB112-4BB1-444B-8EBB-E66555C76BA6} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
- {5A1DB2F0-0715-4B3B-98E6-79BC41540045} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {93B72A06-C8BD-484F-A6F7-C9F280B150BF} = {6C7F47CC-2151-44A3-A546-41C70025132C}
- {18B3DB45-4FFE-4D01-97D6-5223FEEE1853} = {6C7F47CC-2151-44A3-A546-41C70025132C}
- {0F14491C-6369-4C45-AAA8-135814E66E6B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {34A354C5-23C7-4343-916C-C52DAF4FC39D} = {0F14491C-6369-4C45-AAA8-135814E66E6B}
- {3264DF53-C805-4B0C-867C-FCEAF7AEF762} = {0F14491C-6369-4C45-AAA8-135814E66E6B}
- {31CAD28E-778A-441C-85BC-40AB3EAA2A10} = {0F14491C-6369-4C45-AAA8-135814E66E6B}
- {A50C70A6-2DA0-4027-B90E-B1A40755A8A5} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {25C91A4E-BA4E-467A-85CD-8B62545BF674} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
- {6AB6A2D6-F859-4A82-9184-0BD29C9F07D1} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
- {B1234567-1234-1234-1234-123456789ABC} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
- {212AD910-8488-4036-BE20-326931B75FB2} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {7AC943C9-52E8-44CF-9083-744D8049667B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {54A93AF7-60C7-4F6C-99D2-FBB1F75F853A} = {7AC943C9-52E8-44CF-9083-744D8049667B}
- {92C39820-9F84-4529-BC7D-22AAE514D63B} = {7AC943C9-52E8-44CF-9083-744D8049667B}
- {515554D1-D004-4F7F-A107-2211FC0F6B2C} = {7AC943C9-52E8-44CF-9083-744D8049667B}
- {C97D9A5D-206C-454E-997E-009E227D7F02} = {0F14491C-6369-4C45-AAA8-135814E66E6B}
- {31D1C81D-765F-4446-AA62-E743F6325049} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
- {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A} = {1C48CD47-D610-463A-A53C-AF82DD6C47E7}
- {B41B888C-7DB8-4747-B262-4062E05A230D} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
- {AB82E5DD-C32D-4F28-9746-2C780846188E} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
- {E69B044A-2F8A-45AA-AD0B-256C59421807} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
- {C604B37E-9D0E-4484-8778-E8B31B0E1B3A} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
- {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {17B4FA70-001E-4D33-BBBB-0D142DBC2E20} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {A1425B53-3D61-4679-8623-E64A0D3D0A48} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
- {9D7A6DE0-7D27-424D-ABAE-41B2161F9A03} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
- {17A99C7C-0BFF-45BB-A9FD-63A0DDC105BB} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
- {AA9F0AF8-7924-4D59-BAA1-E36F1304E0DC} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A5} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
- {ED9A1AC6-AEB0-4569-A6E9-E1696182B545} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {5A5DD09D-723A-44D3-8F2B-293584C3D731} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {B3E869C4-8210-4EBD-A621-FF4C4AFCBFA9} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {54F7C616-FD41-4E62-BFF9-015686914F4D} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {143F13E3-D2E3-4D83-B035-356612D99956} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {56CC2F10-6E41-453D-BE16-C593A5E58482} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {CA5518ED-0458-4B09-8F53-4122B9888655} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {2833C9C6-AB32-4048-A5C7-A70898337B57} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
- {50B82783-242F-42D2-BC03-B3430BF01354} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
- {B5EB9FE9-37EF-47C3-B8B8-81AD3C2972C2} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
- {A663E672-B26D-4EC0-BEAB-FE2E424AC46F} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
- {8A08D663-4995-40E3-B42C-3F910625F284} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {923DF87C-CA99-4D1C-B1D2-959174E95BFA} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
- {D962A009-834F-4EEC-AABB-430DF8F98E39} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B}
- {9CE59ED5-7087-4353-88EB-788038A73CEC} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {FD86C06A-FB54-4D5E-9831-1CDADF60D45F} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
- {697C6AF9-0A48-49A9-866C-67DA12384015} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
- {929C1324-22E8-4412-A9A8-80E85F3985A5} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {9EBAA524-0EDA-470B-95D4-39383285CBB2} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {D095BE44-1F2E-463E-A494-121892A75EA2} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
- {90F9FA90-2C20-4004-96E6-F3B78151F5A5} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F}
- {3B227528-4BA6-4CAF-B44A-A10C78A64849} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {F5E1146E-B7B3-4E11-85FD-270A500BD78C} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
- {3157FA75-86CF-4EE2-8F62-C43F776493C6} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
- {4C0D0746-BE5B-49EE-BD5D-A7811628AE8B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}
- {B9420661-B0E4-4241-ABD4-4A27A1F64250} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}
- {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {D949EC7D-48A9-4279-95D5-078E7FD1F048} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {3BAF9C81-A194-4925-A035-5E24A5D1E542} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {6B04803D-B418-4833-A67E-B0FC966636A5} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {3940AD4D-F748-4BE4-9083-85769CD553EF} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {0014D652-901F-4456-8D65-06FC5F997FB0} = {4C0D0746-BE5B-49EE-BD5D-A7811628AE8B}
- {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
- {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F} = {AB82E5DD-C32D-4F28-9746-2C780846188E}
- {C32D254F-7597-4CBE-BF74-D922D81CDF29} = {9873BA05-4C41-4819-9283-CF45D795431B}
- {02DD46D3-F761-47D9-8894-2D6DA0124650} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
- {8E23E173-7127-4A5F-9F93-3049F2B68047} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
- {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}
- {C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
- {1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95}
- {8ACB33D9-C95B-47D4-8363-9731EE0930A0} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
- {CA716AE6-FE5C-40AC-BB8F-2C87912687AC} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E}
- {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C} = {68328142-5B31-4715-BCBB-7B6345EE0971}
- {9C53CC25-0623-4569-95BC-B05410675EE3} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {45285DF2-9742-4ECA-9AC9-58951FC26489} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {8F021B46-362B-485C-BFBA-CCF83E820CBD} = {8F62026A-294B-41C6-8839-87463613F216}
- {66614C26-314C-4B91-9071-76133422CFEF} = {BFFB607F-7C78-434B-86B9-DA4C8196A1B5}
- {3846508C-77EB-4034-A702-F8BB263C4F79} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {6CE438DF-C245-4997-A360-0A0939E4BA34} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {E09AA983-C755-474F-83D6-A5CDF528C070} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {6D56B64D-FF1F-488F-AFED-9B9854A5D399} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {92EC89E4-9972-453A-8A1A-3A9E230C146A} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {51939B4F-1F62-4BFF-A6A2-C08646E5BE95} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {D1160404-D3D1-497A-883A-4059C07C2273} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {40F6D69D-E321-400F-A767-5628C7AE453D} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {F3D09629-59A2-4924-A4B9-D6BFAA2C1B49} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {305DD37E-C85D-4B08-AAFE-7381FA890463} = {F3D09629-59A2-4924-A4B9-D6BFAA2C1B49}
- {CA4D810F-C8F4-4B61-9DA9-71807E0B9F24} = {F3D09629-59A2-4924-A4B9-D6BFAA2C1B49}
- {14E62033-58D0-4A7D-8990-52F50A08BBBD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
- {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
- {071E18A4-A530-46B8-AB7D-B862EE55E24E} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {C846F7A7-792A-47D9-B0CB-417C900EE03D} = {071E18A4-A530-46B8-AB7D-B862EE55E24E}
- {C831231F-891C-4572-9694-45062534B42A} = {071E18A4-A530-46B8-AB7D-B862EE55E24E}
- {7520A2FE-00A2-49B8-83ED-DB216E874C04} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {8FBDABA4-40EE-4C0E-9BC8-2F6444A6EF90} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
- {C66020D1-CB10-4CF7-8715-84C97FD5E5E2} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
- {79775343-7A3D-445D-9104-3DD5B2893DF9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {0ADEB797-C8C7-4FFA-ACD5-2AF6CAD7ECD8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {89D0E199-B17A-418C-B2F8-7375B6708357} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {0DB0F63A-D2F8-4DA3-A650-2D0B8724218E} = {CA716AE6-FE5C-40AC-BB8F-2C87912687AC}
- {453CBB73-A3CB-4D0B-8D24-6940B86FE21D} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {C0CE3B5E-16D3-495D-B335-CA791B660162} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {3A9A7297-92C4-4F16-B6F9-8D4AB652C86C} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {605E914B-7232-4789-AF46-BF5D3DDFC14E} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {E81A7D20-9862-ABDB-0AAE-9BC5B517A9F9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {0A84F764-3A88-44CD-AA96-41BDBD48627B} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}
- {E4585179-2AC1-4D5F-A3FF-CFC5392F694C} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}
- {CA7D8106-30B9-4AEC-9D05-B69B31B8C461} = {DD6E12FE-5509-4ABC-ACC2-3D6DC98A238C}
- {DCC6BD67-17BB-47AA-B507-FB0FE43A7449} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {A558C25D-2007-498E-8B6F-43405AFAE9E2} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {08F9155D-B6DC-46E5-9C83-AF60B655898B} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {4382A954-179A-4078-92AF-715187DFFF50} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {EBED240C-8702-452D-B764-6DB9DA9179AF} = {1C48CD47-D610-463A-A53C-AF82DD6C47E7}
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {1C48CD47-D610-463A-A53C-AF82DD6C47E7}
- {5702B3CC-8575-48D5-83D8-15BB42269CD3} = {8131151D-B0E9-4E18-84A5-E5F946C4480A}
- {64B88F02-CD88-4ED8-9624-989A800230F9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
- {5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {66E1534A-1587-42B2-912F-45C994D32904}
- {9E0CBC06-F29A-4810-B93C-97D53863B95E} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {F6088A11-1C9E-4420-AA90-CF7E78DD7F1C} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {47B0678C-806B-4FE1-9F50-46BA88989532} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {9BC1C986-1E97-4D07-A7B1-CE226C239EFA} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {99CA1509-FB73-456E-AFAF-AB89C017BD72} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {61CBF221-9452-4934-B685-146285E080D7} = {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704}
- {38F187B2-6638-5A40-072F-DBE5E54070A0} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {DA0744BC-E822-680E-9CEB-D0FBA903A8EE} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
- {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
- {43E779F3-D83C-48B1-BA8D-1912DBD76FC9} = {68328142-5B31-4715-BCBB-7B6345EE0971}
- {2CF78CF7-8FEB-4BE1-9591-55FA25B48FC6} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {14AFD976-B4D2-49D0-9E6C-AA93CC061B8A} = {1AFB6476-670D-4E80-A464-657E01DFF482}
- {9D3F3793-EFE3-4525-8782-238015DABA62} = {66E1534A-1587-42B2-912F-45C994D32904}
- {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {24133F7F-C1D1-DE04-EFA8-F5D5467FE027} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
- {0E556541-6A45-42CB-AE49-EE5A9BE05E7C} = {6C7F47CC-2151-44A3-A546-41C70025132C}
- {27D9CB3A-46D1-402C-9273-F88CB8AC42F7} = {9873BA05-4C41-4819-9283-CF45D795431B}
- {B9617A31-0F0A-4397-851D-BF2FBEE32D7F} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
- {1C48CD47-D610-463A-A53C-AF82DD6C47E7} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
- {D9BD324E-1D80-44AA-8E7B-73EB00944434} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
- {8EF25507-2575-4ADE-BF7E-D23376903AB8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
- {070AC093-C9F2-20AD-0BCD-F318FC2761EA} = {B1234567-1234-1234-1234-123456789ABC}
- {2C318EC3-BA86-4372-B1BC-DB0F33C208B2} = {322566EF-20DC-43A6-B9F8-616AF942579A}
- {BFFB607F-7C78-434B-86B9-DA4C8196A1B5} = {B6C42F16-73EB-477E-8B0D-4E6CF6C20AAC}
- {66E1534A-1587-42B2-912F-45C994D32904} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
- {E885E71F-0B34-4A03-B13B-20F4E05E90BB} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
- {264B412F-DB8B-4CF8-A74B-96998B183045} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
- {3527BF37-DFC5-4309-A032-29278CA21328} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0}
- {6B01F1CF-F4DB-48B5-BFE7-0BF576C1D704} = {2F305555-C296-497E-AC20-5FA1B237996A}
- {68328142-5B31-4715-BCBB-7B6345EE0971} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
- {4122388B-59E4-CDB0-0338-EA6881DF86F0} = {27D9CB3A-46D1-402C-9273-F88CB8AC42F7}
- {988C9FAF-5AEC-EB15-578D-FED0DF52BF55} = {27D9CB3A-46D1-402C-9273-F88CB8AC42F7}
- {6748A29D-DA6A-033A-825B-752295FF6AA0} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {6EABCF9A-6526-441F-932F-658B1DC3E403} = {264B412F-DB8B-4CF8-A74B-96998B183045}
- {69D76A76-6EF6-4846-94CD-EAAF0CAC9F15} = {264B412F-DB8B-4CF8-A74B-96998B183045}
- {9BAFFC28-E1EF-4C14-A101-EEBFC0A50D83} = {264B412F-DB8B-4CF8-A74B-96998B183045}
- {806BF185-8B89-5BE1-9AA1-DA5BC9487DB9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
- {F93C2817-C846-4259-84D8-B39A6B57C8DE} = {3527BF37-DFC5-4309-A032-29278CA21328}
- {8131151D-B0E9-4E18-84A5-E5F946C4480A} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
- {E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {E816D7AD-4688-4ECB-97CC-3D8E798F3826} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {5B201255-53C8-490B-A34F-01F05D48A477} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {38177D56-6AD1-4ADF-88C9-2843A7932166} = {5B201255-53C8-490B-A34F-01F05D48A477}
- {08E71C67-6A7E-4CA1-B04E-2FB336410BAC} = {5B201255-53C8-490B-A34F-01F05D48A477}
- {E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
- {66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
- {9605B84E-FAC4-477B-B9EC-0753177EE6A8} = {557C4636-D7E1-4838-A504-7D19B725EE95}
- {94CDC147-6137-45E9-AEDE-17FF809607C0} = {9605B84E-FAC4-477B-B9EC-0753177EE6A8}
- {A24BF1AF-79AA-4896-BAE3-CCBBE0380A78} = {9605B84E-FAC4-477B-B9EC-0753177EE6A8}
- {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}
- {3DCCD936-D085-4869-A1DE-CA6A64152C94} = {5B201255-53C8-490B-A34F-01F05D48A477}
- {F5333ED7-06D8-4AB3-953A-36D63F08CB6F} = {3DCCD936-D085-4869-A1DE-CA6A64152C94}
- {4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
- EndGlobalSection
-EndGlobal
diff --git a/PowerToys.slnx b/PowerToys.slnx
new file mode 100644
index 0000000000..c946514fb5
--- /dev/null
+++ b/PowerToys.slnx
@@ -0,0 +1,1041 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 85fad26e1f..dd3abefe1c 100644
--- a/README.md
+++ b/README.md
@@ -7,19 +7,23 @@
Microsoft PowerToys
-
+
+ Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
+
-Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
-
+
+## 🔨 Utilities
+
+PowerToys includes over 25 utilities to help you customize and optimize your Windows experience:
| | | |
|---|---|---|
@@ -37,41 +41,35 @@ Microsoft PowerToys is a collection of utilities that help you customize Windows
## 📋 Installation
-For detailed installation instructions, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
-
-Before you begin, make sure your device meets the system requirements:
-
-> [!NOTE]
-> - Windows 11 or Windows 10 version 2004 (20H1 / build 19041) or newer
-> - 64-bit processor: x64 or ARM64
-> - Latest stable version of [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) is installed via the bootstrapper during setup
-
-Choose one of the installation methods below:
-
-
-Download .exe from GitHub
+For detailed installation instructions and system requirements, visit the [installation docs](https://learn.microsoft.com/windows/powertoys/install).
+But to get started quickly, choose one of the installation methods below:
+
+
+Download .exe from GitHub
+
Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
-[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22
-[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22
-[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysUserSetup-0.95.0-x64.exe
-[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysUserSetup-0.95.0-arm64.exe
-[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysSetup-0.95.0-x64.exe
-[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.0/PowerToysSetup-0.95.0-arm64.exe
+[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
+[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22
+[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysUserSetup-0.96.1-x64.exe
+[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysUserSetup-0.96.1-arm64.exe
+[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysSetup-0.96.1-x64.exe
+[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.1/PowerToysSetup-0.96.1-arm64.exe
| Description | Filename |
|----------------|----------|
-| Per user - x64 | [PowerToysUserSetup-0.95.0-x64.exe][ptUserX64] |
-| Per user - ARM64 | [PowerToysUserSetup-0.95.0-arm64.exe][ptUserArm64] |
-| Machine wide - x64 | [PowerToysSetup-0.95.0-x64.exe][ptMachineX64] |
-| Machine wide - ARM64 | [PowerToysSetup-0.95.0-arm64.exe][ptMachineArm64] |
+| Per user - x64 | [PowerToysUserSetup-0.96.1-x64.exe][ptUserX64] |
+| Per user - ARM64 | [PowerToysUserSetup-0.96.1-arm64.exe][ptUserArm64] |
+| Machine wide - x64 | [PowerToysSetup-0.96.1-x64.exe][ptMachineX64] |
+| Machine wide - ARM64 | [PowerToysSetup-0.96.1-arm64.exe][ptMachineArm64] |
-Microsoft Store
+Microsoft Store
+
You can easily install PowerToys from the Microsoft Store:
@@ -82,10 +80,9 @@ You can easily install PowerToys from the Microsoft Store:
-
-WinGet
-
+WinGet
+
Download PowerToys from [WinGet][winget-link]. Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
*User scope installer [default]*
@@ -100,162 +97,137 @@ winget install --scope machine Microsoft.PowerToys -s winget
-Other methods
-
+Other methods
+
There are [community driven install methods](./doc/unofficialInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
## ✨ What's new
-**Version 0.95 (October 2025)**
+**Version 0.96 (November 2025)**
For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
**✨ Highlights**
- - **NEW:** The **Light Switch** utility in PowerToys allows you to automatically switch between light and dark themes in Windows based on the time of day.
- - Command Palette delivered major search performance gains (new fuzzy matcher and smarter fallbacks) improving relevance and speed.
- - Peek can now be activated using just the Spacebar!
- - Find My Mouse added transparent spotlight with independent backdrop opacity, boosting focus and accessibility.
- - Settings now lets you delete shortcuts entirely and ignore conflicts.
- - Mouse Pointer Crosshairs gained orientation options (vertical / horizontal / both) for customizable accessibility. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
- - PowerRename fixed enumeration counter skipping ensuring reliable batch renames. Thanks [@daverayment](https://github.com/daverayment)!
- - ZoomIt restored legacy draw and snipping behaviors, and fixed recording issues, improving reliability. Thanks [@chakrik73](https://github.com/chakrik73)!
+ - Advanced Paste now supports multiple online and on-device AI model providers: Azure OpenAI, OpenAI, Google Gemini, Mistral, Foundry Local and Ollama.
+ - Command Palette received extensive improvements including file search filters, better clipboard history metadata, context-menu styling, and dozens of bug fixes and enhancements.
+ - PowerRename can now extract and use photo metadata (EXIF, XMP) in renaming patterns like `%Camera`, `%Lens`, and `%ExposureTime`.
+
+### Advanced Paste
+ - Advanced Paste now lets you connect to multiple AI providers instead of being limited to a single OpenAI provider. See [Advanced Paste documentation](https://learn.microsoft.com/windows/powertoys/advanced-paste) for usage.
+
+### Awake
+ - The Awake countdown timer now stays accurate over long periods. Thanks [@daverayment](https://github.com/daverayment)!
+ - Fixed Awake context menu positioning. The fix removed the conversion of the mouse cursor from screen to client-window coordinates, instead using the raw screen coordinates returned by GetCursorPos; the context menu now appears at the correct screen position. Thanks [@lzandman](https://github.com/lzandman)!
### Command Palette
- - Applied conditional margin for icon-only tags to tighten layout. Thanks [@samrueby](https://github.com/samrueby)
- - Improved the reliability of accessing Command Palette settings through PowerToys Settings and executing other x-cmdpal:// protocol commands. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Enabled AOT by default for improved performance while simplifying publish configs.
- - Replaced service state color dots with play/pause/stop icons for enhanced accessibility. Thanks [@samrueby](https://github.com/samrueby)
- - Fixed filter dropdown sync and crash by binding SelectedValue and raising UI-thread notifications. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Ensured long links wrap correctly in details view.
- - Removed animation and enforced minimum width on filter dropdown for clarity. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Restored focus to More button after ESC closes context menu, improving keyboard flow. Thanks [@chatasweetie](https://github.com/chatasweetie)
- - Marked main and toast windows as tool windows to keep them out of Alt+Tab while preserving style. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Fixed AOT template and theming issues for filter separators. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Introduced grid layouts (small, medium, gallery) for richer page presentation.
- - Materialized result lists to avoid rescoring overhead.
- - Disabled problematic selection TextToSuggest behind environment flag.
- - Major search performance improvements (new fuzzy matcher, smarter fallbacks, fewer exceptions).
- - Added context menu "Show Details" command when details pane is hidden.
- - Reduced window flicker by avoiding unnecessary cloaking. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Restored EmptyContent rendering for blank states. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)
- - Saved new state even if prior app state file was corrupt (better resilience). Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Migrated settings window to WinUI TitleBar control. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Prevented crash on duplicate keybindings and simplified matching. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Hotkeys now always respect the “Ignore shortcut in fullscreen†setting. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Hid search box on content pages, improving focus and accessibility, and added Home title. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Blocked Ctrl+I from inserting stray tabs in search box.
- - Logged HRESULT codes in error logs for deeper diagnostics. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Advanced font and emoji icon classification and alignment improvements. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Ensured that fallback command icons are visible on the extension settings page. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Fixed breadcrumb margin misalignment (visual polish). Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Truncated overly long command labels with ellipsis to prevent overflow.
- - Added a setting to configure the page transition animation.
- - Collection of small improvements and nits for Run Commands.
- - Improved bookmarks performance and experience. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Added Ctrl+O shortcut in Clipboard History to open links directly.
- - Resolved conflict with external software that blocked Command Palette from hiding.
- - Updated context menu items to reflect name and icon changes, and ensured application icons are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Added Alt+Home shortcut to return immediately to the Command Palette home page. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Fixed a crash when displaying code blocks in markdown on detail or content pages. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Fixed an issue where the search bar icon and title were not updated when rapidly switching pages. Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Improved the appearance of the search box in the context menu.
-
+ - The search field in context menus now matches the look of the Command Palette, with a smoke backdrop and improved padding.
+ - Fallback items such as math calculations or the Run command now appear in results more quickly. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Ensured the command bar updates correctly after navigating to another page and commands are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - The Command Palette settings page has been reorganized. Activation-key options are grouped under an expander and extension settings are framed for improved readability.
+ - When you modify a command, its alias, hotkey, and tags now update in the top-level list, keeping the displayed information in sync. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Press `Ctrl + ,` to open Command Palette settings from anywhere. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - You can use `Page Up` and `Page Down` to navigate the list while focus is in the search box. Thanks [@samrueby](https://github.com/samrueby)!
+ - Fixed an issue where the search box could disappear when navigating pages. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Ensured search text is selected when *Go home when activated* and *Highlight search on activate* are both enabled. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Fixed an issue where Command Palette window occasionally appeared on the taskbar under certain Windows settings. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Ensured that labels and icons of list items and menu items update when they change. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Fixed visibility of list filters when navigating to a content page. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)!
+ - Added search to the extension list and a link to extensions on the Microsoft Store. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Added options to open the Command Palette window at its last position or re-center it.
+ - The Command Palette now remembers its window size after restarting.
+ - Added a global error handler that logs fatal errors and provides feedback when unexpected failures force Command Palette to close. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Fixed forms and extension settings not showing on some machines due to a missing VC++ runtime.
+ - Restored ranking of fallback commands for built-in extensions (Sleep, Shutdown, Windows settings, Web search, etc.). Thanks [@jiripolasek](https://github.com/jiripolasek).
+ - Improved and unified labels and texts across the application!
+ - Maintainance: Resolved numerous build warnings in Command Palette projects; no user-visible impact. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Maintainance: Fixed a logging issue so exception messages are properly recorded instead of placeholder text, improving troubleshooting. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Command Palette Extensions
- - Replaced localized WebSearch setting keys with stable literals and numeric history count. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Enabled advanced markdown tables and emphasis extensions. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Added setting to choose Clipboard History primary action (Paste vs Copy). Thanks [@jiripolasek](https://github.com/jiripolasek)
- - Added actionable empty-state hints for File Search (search PC / open indexing settings). Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Ensured all WinGet extension assets copy reliably to output. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Improved Run command line parsing for paths with spaces; sped up related tests.
- - Updated WebSearch extension icon set for enhanced clarity and contrast. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Added Terminal profile sort order setting including MRU tracking. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Added Uninstall Application command (UWP direct, Win32 via Settings). Thanks [@mKpwnz](https://github.com/mKpwnz)!
- - Deferred WinGet details loading and added timing logs.
- - Removed LINQ from All Apps extension for performance.
- - Added standardized key chord system + shortcuts to File Search commands. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Added Terminal channel filter & remembered selection option. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Enabled loading local/data/app images in markdown with sizing hints. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Added external extension reload via x-cmdpal://reload (configurable). Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Instant WebSearch history updates with in-memory store & events. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- - Added keep-after-paste option and safe delete with confirmation for Clipboard History. Thanks [@jiripolasek](https://github.com/jiripolasek)!
-
-### Environment Variables
- - Replaced custom window chrome with WinUI TitleBar for cleaner, maintainable Environment Variables UI.
-
-### File Locksmith
- - Adopted WinUI TitleBar to simplify window chrome while preserving appearance.
+ - Bookmarks: Added hints about bookmark placeholders to the Add/Edit Bookmark form. — Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Bookmarks: Improved migration of bookmarks from older versions and fixed an issue where aliases or keyboard shortcuts could be lost after restart. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Clipboard history: Items shown in Command Palette’s clipboard history now include helpful metadata. For example, image items show dimensions, text files show names and sizes, web links include page titles, and text entries display word counts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - File search: Added filter buttons to show *all items*, *files only*, or *folders only*. Selecting a filter adds `kind:folders` or `kind:not folders` to narrow results.
+ - System commands: Replaced the `:red_circle:` placeholder with an actual red-circle emoji so the correct icon appears in the UI. Thanks [@samrueby](https://github.com/samrueby)!
+ - WinGet: Search performance feels more responsive because typed input is now processed via a task queue rather than complex cancellation tokens!
+ - Window Walker: UWP apps no longer show a "not responding" label when suspended. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Window Walker: Now displays the actual icon of each window rather than using the process icon, improving recognition of PWAs and Python GUIs. Thanks [@Lee-WonJun](https://github.com/Lee-WonJun)!
+- Windows Terminal profiles: Fixed a rare crash in the Windows Terminal extension when the `LOCALAPPDATA` environment variable was missing. The path is now retrieved via a reliable API. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Find My Mouse
- - Added transparent spotlight support with separate backdrop opacity; migrated to Windows App SDK composition APIs.
+ - Activating Find My Mouse no longer makes the cursor change to the busy (hourglass) icon or steals focus from your active application.
### Hosts File Editor
- - Migrated to native WinUI TitleBar for cleaner, maintainable window chrome.
+ - Added customizable backup settings allowing users to configure backup frequency, location, and auto-deletion policies. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
+
+### Image Resizer
+ - Fixed settings consistency during batch resize operations by capturing settings once before processing. Thanks [@daverayment](https://github.com/daverayment)!
### Light Switch
- - Introduced as a brand-new PowerToy module.
- - Automatically switches between light and dark themes.
- - Supports time-based scheduling or location-based sunrise/sunset switching.
- - Supports using a keyboard shortcut to force a change.
- - Supports filtering changes for Apps and/or System Theme.
+- Introduced new UI to allow users to manually enter their latitude and longitude in Sunrise to Sunset mode.
+- Refactored service with cleaner state management for stability.
+- Removed logs from every tick, only logging key events to largely reduce log size.
### Mouse Pointer Crosshairs
- - Added Esc key to cancel active gliding cursor sequence. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
- - Added orientation option (vertical / horizontal / both) for crosshairs customization. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
+ - Enabled switching between Mouse Pointer Crosshairs and Gliding Cursor modes. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
### Mouse Without Borders
- - Continued Common class refactor (part 5/7) by extracting clipboard and init/cleanup logic into focused classes. Thanks [@mikeclayton](https://github.com/mikeclayton)!
-
- - Fix connection failures caused by conflicting MachineId across machines. Thanks [@noraa-junker](https://github.com/noraa-junker) for troubleshooting!
+ - Added horizontal scrolling support. Thanks [@MasonBergstrom](https://github.com/MasonBergstrom)!
### Peek
- - Added the option to activate Peek with just the Spacebar.
+- Fixed media files remaining locked after preview window closes. Thanks [@daverayment](https://github.com/daverayment)!
+- Added a command-line interface for file previewing. See the [Peek documentation](https://learn.microsoft.com/windows/powertoys/peek) for usage. Thanks [@prochan2](https://github.com/prochan2)!
### PowerRename
- - Fixed enumeration counter skipping when regex replacement equals original filename (counters now advance reliably). Thanks [@daverayment](https://github.com/daverayment)!
+- PowerRename no longer crashes due to a missing resources file.
+- Added photo metadata extraction support using EXIF and XMP for pattern-based renaming with camera info, GPS coordinates, and date taken. See [PowerRename Documentation](https://learn.microsoft.com/en-us/windows/powertoys/powerrename).
-### Quick Accent
- - Expanded Welsh layout with acute, grave, and dieresis variants for vowels (consistent ordering). Thanks [@PesBandi](https://github.com/PesBandi)!
+### PowerToys Run
+ - Added retry logic with exponential backoff to handle DWM composition errors during theme changes. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+ - Updated OneNote icons to reflect new Microsoft 365 design. Thanks [@trevorNgo](https://github.com/trevorNgo)!
-### Registry Preview
- - Migrated to native TitleBar and AppWindow APIs for cleaner window chrome.
+ ### Quick Accent
+ - Added diameter symbol (⌀) for Shift+O in Special Characters mode, thanks to [@anselumjuju](https://github.com/anselumjuju)!
-### Screen Ruler
- - Fixed ARM64 crash by aligning cursor position structure to 8-byte boundary.
+### Zoomit
+ - Smoothed out zoom-animation in ZoomIt by coalescing mouse-move and timer events, thanks to [@foxmsft](https://github.com/foxmsft)!
+ - Enabled GIF support for ZoomIt, thanks to [@MarioHewardt](https://github.com/MarioHewardt)!
+ - Fixed spelling mistakes, and refactored some literal strings to string constants, thanks to [@lzandman](https://github.com/lzandman)!
+ - Fixed inaccurate "actual size" screenshots in ZoomIt and resolves a GDI handle leak, improving capture fidelity and long-session stability. thanks to [@daverayment](https://github.com/daverayment)!
### Settings
- - Added ability to ignore specific hotkey conflicts to reduce noise.
- - Stopped creating backup directory during dry-run status checks (cleaner first-run).
- - Standardized casing and localization for ZoomIt and modules header.
- - Improved search results page accessibility and conditional module grouping.
+- Fixed title bar overlapping issue at smaller window sizes.
+- Refined shortcut control visual design with improved consistency and spacing.
+- Added dashboard utilities sorting by name or status.
+- Made update notification InfoBar in flyout clickable for direct navigation to update page.
+- Expanded installation instructions by default in README.
+- Improved accessibility for shortcut conflict button with static resource-based automation properties.
+- Added ScrollViewer to Command Palette page in PowerToys Settings. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+- Fixed module list glitches and Sort Status checkmark issue. Thanks [@daverayment](https://github.com/daverayment)!
-### ZoomIt
- - Updated resource file to reflect standalone v9.01 and current copyright year. Thanks [@foxmsft](https://github.com/foxmsft)!
- - Restored legacy draw/snipping behaviors and fixed recording race conditions. Thanks [@chakrik73](https://github.com/chakrik73)!
- - Added smooth image option for improved zoom quality using GDI+ for static zoom and Magnifier API for live zoom. Thanks [@markrussinovich](https://github.com/markrussinovich)!
-
- ### Documentation
- - New Microsoft Learn documentation for the Light Switch module.
- - New dev docs for the Light Switch module.
-
-### Development (Area-Build & Area-Tests)
-- Allowed debug launches to continue when modules fail to load, speeding developer iteration.
-- Fixed spell checker dictionary entry (advapi) to eliminate false error.
-- Added VS Code development guide and launch configs to streamline cross-editor workflows.
-- Upgraded Windows App SDK and related dependencies to 1.8 for newer platform features.
-- Rewrote YAML comment to resolve new spell checker forbidden pattern. Thanks [@jiripolasek](https://github.com/jiripolasek)!
-- Corrected solution structure by returning misplaced Common project, reducing build confusion.
-- Modernized build scripts with shared helpers and VS environment autodetection for simpler CLI builds.
-- Standardized build scripts and platform detection to improve reliability and reuse.
-- Added missing Command Palette version bump to align module release cadence.
-- Added EXECUTEDEFAULT term to dictionary to prevent regression build failures. Thanks [@jiripolasek](https://github.com/jiripolasek)!
-- Introduced nightly pre-warm pipeline and configurable MSBuild cache mode to improve CI performance.
-- Resolved CI forbidden pattern spelling complaint to keep pipelines green.
-- Added AI contributor instruction set to clarify code area expectations.
-- Added accessibility IDs to settings and FancyZones toggles, stabilizing UI tests.
-- Added automatic log collection on UI test failures to speed root cause analysis.
-- Stabilized Mouse Utils tests by switching to AccessibilityId selectors.
-- Added Screen Ruler UI test coverage to validate core measurement workflows.
+### Development
+- Fixed accessibility by associating controls with labels for screen readers.
+- Added accessible name to Shortcut Conflicts button for screen readers.
+- Excluded TitleBars from tab navigation across multiple utilities. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+- Migrated build infrastructure from Windows Server 2019 to Server 2022 with improved failure logging and predictable NuGet package paths.
+- Configured build agents to use larger P: drive for release builds to address disk space constraints.
+- Enhanced DSC v3 support by organizing resource manifests in a dedicated subfolder with PATH configuration.
+- Reduced installer bundle size by 6-7MB through centralized Hybrid CRT configuration across all C++ projects.
+- Updated .NET packages to version 9.0.10 for security fixes. Thanks [@snickler](https://github.com/snickler)!
+- Fixed spell check dictionary entries for consistency.
+- Restored accidentally deleted NuGet configuration file for Command Palette extensions.
+- Fixed package identity build by updating AppxManifest entry points to use PowerShell Core.
+- Optimized CI pipeline by replacing file copy operations with hard links and moves, reducing build time and disk usage by 10-15GB.
+- Updated Copilot guidance and PR prompt workflow.
+- Included high-volume bugs in issue template header. Thanks [@daverayment](https://github.com/daverayment)!
+- Fixed incorrect HRESULT logging for inner exceptions. Thanks [@jiripolasek](https://github.com/jiripolasek)!
+- Introduced shared sparse package identity for PowerToys Win32 components to enable access to Windows platform APIs.
+- Consolidated installer builds to produce both machine and user installers simultaneously, reducing build time and complexity.
+- Migrated exclusively to WiX v5 installer infrastructure, removing legacy WiX v3 support.
+- Temporarily removed PowerToys installer path from PATH environment variable to prevent application crashes.
+- Added complete OCR UI test coverage with automated tests for activation, settings, language selection, and text extraction.
+- Fixed test input for drive path normalization in bookmark resolver unit tests.
+- Fixed Peek UI tests by restoring Ctrl+Space activation shortcut for test scenarios.
+- Hided apps in PowerToys.SpareApps package from Start Menu. Thanks [@jiripolasek](https://github.com/jiripolasek)!
## ðŸ›£ï¸ Roadmap
We are planning some nice new features and improvements for the next releases – a revamped Keyboard Manager UI, custom endpoint and local model support for Advanced Paste, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.96][github-next-release-work]!
@@ -281,4 +253,4 @@ The application logs basic diagnostic data (telemetry). For more privacy informa
[roadmap]: https://github.com/microsoft/PowerToys/wiki/Roadmap
[privacy-link]: http://go.microsoft.com/fwlink/?LinkId=521839
[loc-bug]: https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=translation_issue.md&title=
-[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs
\ No newline at end of file
+[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs
diff --git a/deps/cziplib b/deps/cziplib
deleted file mode 160000
index 81314fff0a..0000000000
--- a/deps/cziplib
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 81314fff0a882b72a9ad321e7a3311660125b56e
diff --git a/doc/devdocs/commands.md b/doc/devdocs/commands.md
new file mode 100644
index 0000000000..811625284e
--- /dev/null
+++ b/doc/devdocs/commands.md
@@ -0,0 +1,34 @@
+# Issue/PR commands
+
+The PowerToys repository uses some special keywords to help manage issues and pull requests. Here is a list of the most important commands you can use in issue and PR descriptions or comments.
+
+| Command | Description |
+|---------|-------------|
+| `/azp run` | Triggers the Azure Pipelines CI build for the current PR. Useful if you want to re-run the build without creating a new commit. |
+| `/bugreport` / `/reportbug` | Adds a comment with a manual for the Bug Report Tool, which helps users collect logs and system information for debugging purposes. It requests to upload this file and adds the `Needs-Author-Feedback` label. |
+| `/feedbackhub` | Adds a comment with a link to the Feedback Hub app on Windows, where users can submit feedback about PowerToys. Closes the issue and adds the `Resolution-Please File on Feedback Hub` label. |
+| `/dup #...` / `/duplicate #...` / `/dup https://...` / `/duplicate https://...` | Marks the current issue as a duplicate of another issue. It closes the current issue and applies the `Resolution-Duplicate` label. Replace `#...` with the issue number or a link to the issue. |
+| `/needinfo` | Adds the `Needs-Author-Feedback` label to the issue or PR, indicating that more information is needed from the author. |
+| `/helped` | Closes the issue and adds the `Resolution-Helped User` label. Furthermore a comment is added with a link to the PowerToys user documentation. |
+| `/loc` | Adds a comment informing the user that the issue was forwarded to the localization team and will soon be fixed. It adds the `Loc-Sent To Team` label. |
+
+## Defining new commands
+
+Most of these commands are using the [Microsoft GitHub Policy Service](https://github.com/apps/microsoft-github-policy-service) bot. Its commands are defined in the [PowerToys policy configuration file](/.github/policies/resourceManagement.yml).
+
+## Other automated tasks
+
+### Automatic labeling
+
+The bot can automatically apply the correct `product-...` label for any opened issue.
+
+> [!NOTE]
+> This feature is currently only available for the Workspaces module as a test.
+
+### The `Needs-Author-Feedback` label
+
+If an issue has this label and had no activity for 5 days, the bot will post a comment reminding the author to provide the needed information. It also adds the `Status-No recent activity` label. If no further activity occurs for another 5 days, the bot will close the issue.
+
+### Filtering users that want to contribute
+
+If a user utters their intention to contribute (e.g., by using the phrase "I want to contribute" in an issue or PR), the bot will add a comment with a link to the ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769).
diff --git a/doc/devdocs/core/installer.md b/doc/devdocs/core/installer.md
index 5bcbb0f87c..90af7668c2 100644
--- a/doc/devdocs/core/installer.md
+++ b/doc/devdocs/core/installer.md
@@ -134,7 +134,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
#### Locally compiling the installer
-1. Open `installer\PowerToysSetup.sln`
+1. Open `installer\PowerToysSetup.slnx`
1. In Visual Studio, in the `Solutions Configuration` drop-down menu select `Release`
1. From the `Build` menu choose `Build Solution`.
@@ -144,9 +144,9 @@ To build the installer from the command line, run `Developer Command Prompt for
```
git clean -xfd -e *exe -- .\installer\
-MSBuild -t:restore .\installer\PowerToysSetup.sln -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
-MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
-MSBuild -t:Restore -m .\installer\PowerToysSetup.sln /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
+MSBuild -t:restore .\installer\PowerToysSetup.slnx -p:RestorePackagesConfig=true /p:Platform="x64" /p:Configuration=Release
+MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysInstallerVNext /p:Configuration=Release /p:Platform="x64"
+MSBuild -t:Restore -m .\installer\PowerToysSetup.slnx /t:PowerToysBootstrapperVNext /p:Configuration=Release /p:Platform="x64"
```
### Supported arguments for the .EXE Bootstrapper installer
diff --git a/doc/devdocs/development/debugging.md b/doc/devdocs/development/debugging.md
index c3e0fdac89..3756dd9396 100644
--- a/doc/devdocs/development/debugging.md
+++ b/doc/devdocs/development/debugging.md
@@ -19,7 +19,7 @@ You can build the entire solution from the command line, which is sometimes fast
2. Navigate to the repository root directory
3. Run the following command(don't forget to set the correct platform):
```pwsh
- msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true"
+ msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx /tl /p:NuGetInteractive="true"
```
4. This process should complete in approximately 13-14 minutes for a full build
diff --git a/doc/devdocs/development/dev-with-vscode.md b/doc/devdocs/development/dev-with-vscode.md
index 4b1b7def24..bd2f894f79 100644
--- a/doc/devdocs/development/dev-with-vscode.md
+++ b/doc/devdocs/development/dev-with-vscode.md
@@ -42,10 +42,10 @@ Or reach out to "tools\build\BUILD-GUIDELINES.md"
### Sample plain msbuild command
```powershell
# Restore:
-msbuild powertoys.sln -t:restore -p:configuration=debug -p:platform=x64 -m
+msbuild powertoys.slnx -t:restore -p:configuration=debug -p:platform=x64 -m
-# Build powertoys sln
-msbuild powertoys.sln -p:configuration=debug -p:platform=x64 -m
+# Build powertoys slnx
+msbuild powertoys.slnx -p:configuration=debug -p:platform=x64 -m
# dotnet project
msbuild src\settings-ui\Settings.UI\PowerToys.Settings.csproj -p:Platform=x64 -p:Configuration=Debug -m
@@ -122,7 +122,7 @@ Similar for attach to managed code.
| Task | Command / Action | Notes |
|------|------------------|-------|
-| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.sln` | Deep clean removes packages & build outputs |
+| Clean | `git clean -xdf` (careful) or `msbuild /t:Clean PowerToys.slnx` | Deep clean removes packages & build outputs |
| Rebuild single project | `msbuild path\to\proj.vcxproj /t:Rebuild -p:Platform=x64 -p:Configuration=Debug` | Faster than whole solution |
| Generate installer (rare in inner loop) | See `tools\build\build-installer.ps1` | Usually not needed for local debug |
-| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
\ No newline at end of file
+| Resource conversion errors | Re-run restore + build | Triggers custom PowerShell targets |
diff --git a/doc/devdocs/style.md b/doc/devdocs/development/style.md
similarity index 100%
rename from doc/devdocs/style.md
rename to doc/devdocs/development/style.md
diff --git a/doc/devdocs/development/ui-tests.md b/doc/devdocs/development/ui-tests.md
index 63bddb0591..941f9dacd4 100644
--- a/doc/devdocs/development/ui-tests.md
+++ b/doc/devdocs/development/ui-tests.md
@@ -12,7 +12,7 @@
- Exit PowerToys if it's running.
-- Open `PowerToys.sln` in Visual Studio and build the solution.
+- Open `PowerToys.slnx` in Visual Studio and build the solution.
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
diff --git a/doc/devdocs/localization.md b/doc/devdocs/localization.md
deleted file mode 100644
index f9e7e50a67..0000000000
--- a/doc/devdocs/localization.md
+++ /dev/null
@@ -1,165 +0,0 @@
-# Localization
-
-> **NOTE**: THIS DOCUMENT IS OUTDATED.
-> Follow [issue 15243](https://github.com/microsoft/PowerToys/issues/15243) for updates.
-
-## Table of Contents
-1. [Localization on the pipeline (CDPX)](#localization-on-the-pipeline-cdpx)
- 1. [UWP Special case](#uwp-special-case)
-2. [Enabling localization on a new project](#enabling-localization-on-a-new-project)
- 1. [C++](#c)
- 2. [C#](#c-1)
- 3. [UWP](#uwp)
-3. [Lcl Files](#lcl-files)
-4. [Possible Issues in localization PRs (LEGO)](#possible-issues-in-localization-prs-lego)
-5. [Enabling localized MSI for a new project](#enabling-localized-msi-for-a-new-project)
-
-## Localization on the pipeline (CDPX)
-[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/main/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package.
-
-The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [this](https://github.com/microsoft/PowerToys/tree/main/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail in the [Lcl files section](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules.
-
-Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/main/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this.
-
-The process and variables that can be tweaked on the pipeline are described in more detail on [onebranch (account required) under Localization](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/290/Localization).
-
-The localized resource dlls for C# projects are added to the MSI only for build on the pipeline. This is done by checking if the [`IsPipeline` variable is defined](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L804-L805), which gets defined before [building the installer on the pipeline](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/build-installer.cmd#L4). This is done because the localized resx files are only present on the pipeline, and not having this check would result in the installer project failing to build locally.
-
-## Enabling localization on a new project
-To enable localization on a new project, the first step is to create a file `LocProject.json` in the project root.
-
-For example, for a project in the folder `src\path` where the resx file is present in `resources\Resources.resx`, the LocProject.json file will contain the following:
-```
-{
- "Projects": [
- {
- "LanguageSet": "Azure_Languages",
- "LocItems": [
- {
- "SourceFile": "src\\path\\resources\\Resources.resx",
- "CopyOption": "LangIDOnName",
- "OutputPath": "src\\path\\resources"
- }
- ]
- }
- ]
-}
-```
-The rest of the steps depend on the project type and are covered in the sections below. The steps to add the localized files to the MSI can be found in [Enabling localized MSI for a new project](#Enabling-localized-MSI-for-a-new-project).
-
-### C++
-C++ projects do not support `resx` files, and instead use `rc` files along with `resource.h` files. The CDPX pipeline however doesn't support localizing `rc` files and the other alternative they support is directly translating the resources from the binary which makes it harder to maintain resources. To avoid this, a custom script has been added which expects a resx file and converts the entries to an rc file with a string table and adds resource declarations to a resource.h file so that the resources can be compiled with the C++ project.
-
-If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files.
-
-After generating the resx file, rename the existing rc and h files to ProjName.base.rc and resource.base.h. In the rc file remove the string table which is to be localized and in the .h file remove all `#define`s corresponding to localized resources. In the vcxproj of the C++ project, add the following build event:
-```
-
-
-
-```
-
-This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script is [convert-resx-to-rc.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format:
-```
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-
-STRINGTABLE
-BEGIN
- strings
-END
-
-#endif
-```
-Since there is no API to identify the `AFX_TARG_*`, `LANG_*` or `SUBLANG_*` values from each langId from the pipeline, these are hardcoded in the script (for each language) as done in [lines 50-77 of `convert-resx-to-rc.ps1`](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/tools/build/convert-resx-to-rc.ps1#L50-L77). **If any other languages are added in the future, this script will have to be updated.** In order to determine what are the language codes, you can open the rc file in Resource View, right click the string table and press `Insert Copy` and choose the corresponding language. This autogenerates the required code and can be used to figure out the language codes. The files also add the resource declarations to a resource.h file, starting from 101 by default(this can be changed by an optional argument). Since the output files will be generated in `Generated Files`, any includes in these two files will require an additional `..\` and wherever resource.h is used, it will have to be included as `Generated Files\resource.h`. While adding `resource.base.h` and `ProjName.base.rc` to the vcxproj, these should be modified to not participate in the build to avoid build errors:
-```
-
-```
-
-Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/Directory.Build.targets) for an example.
-
-Check [this PR](https://github.com/microsoft/PowerToys/pull/6104) for an example for making these changes for a C++ project.
-
-### C#
-Since C# projects natively support `resx` files, the only step required here is to include all the resx files in the build. For .NET Core projects this is done automatically and the .csproj does not need to be modified. For other projects, the following line needs to be added:
-```
-
-```
-
-**Note:** Building with localized resources may cause a build warning `Referenced assembly 'mscorlib.dll' targets a different processor` which is a VS bug. More details can be found in [PowerToys issue #7269](https://github.com/microsoft/PowerToys/issues/7269).
-
-**Note:** If a project needs to be migrated from XAML resources to resx, the easiest way to convert the resources would be to change to format to `=` separates resources by either manually (by Ctrl+H on a text editor), or by a script, and then running [`resgen`](https://learn.microsoft.com/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) on `Developer Command Prompt for VS` to convert it to resx format.
-```
-Calculator
-Allows to do mathematical calculations.(Try 5*3-2 in Wox)
-Not a number (NaN)
-```
-to
-```
-wox_plugin_calculator_plugin_name=Calculator
-wox_plugin_calculator_plugin_description=Allows to do mathematical calculations.(Try 5*3-2 in Wox)
-wox_plugin_calculator_not_a_number=Not a number (NaN)
-```
-After adding the resx file to the project along with the resource generator, references to the strings will have to be replaced with `Properties.Resources.resName` rather than the custom APIs. Check [this PR](https://github.com/microsoft/PowerToys/pull/6165) for an example of the changes required.
-
-### UWP
-UWP projects expect `resw` files rather than `resx` (the format is almost the same). Unlike other C# projects, the files are expected in the format `fullLangId\Resources.resw`. To include these files in the build, replace the following line in the csproj:
-```
-
-```
-to
-```
-
-```
-
-## Lcl Files
-Lcl files contain all the resources that are present in the English resx file, along with a translation if it has been added.
-
-For example, an entry for a resource in the lcl file looks like this:
-```
--
-
-
-
-
-
-
-
-
-```
-The `` element would not be present in the initial commits of the lcl files, as only the English version of the string would be present.
-
-**Note:** The CDPX Localization system has a fail-safe check on the lcl files, where if the English string value which is present inside ` ` does not match the value present in the English Resources.resx file then the translated value will not be copied to the localized resx file. This is present so that obsolete translations would not be loaded when the English resource has changed, and the English string will be used rather than the obsolete translation.
-
-## Possible Issues in localization PRs (LEGO)
-Since the LEGO PRs update some of the strings in LCL files at a time, there can be multiple PRs which modify the same files, leading to merge conflicts. In most cases this would show up on GitHub as a merge conflict, but sometimes a bad git merge may occur, and the file could end up with incorrect formatting, such as two `` elements for a single resource. These can be fixed by ensuring the elements follow the format described in [this section](#lcl-files). To catch such errors, the build farm should be run for every LEGO PR and if any error occurs in the localization step, we should check the corresponding resx/lcl files for conflicts.
-
-## Enabling localized MSI for a new project
-For C++ and UWP projects no additional files are generated with localization that need to be added to the MSI. For C++ projects all the resources are added to the dll/exe, while for UWP projects they are added to the `resources.pri` file (which is present even for an unlocalized project). To verify if the localized resources are added to the `resources.pri` file the following steps can be done:
-- Open `Developer Command Prompt for VS`
-- After navigating to the folder containing the pri file, run the following command:
-
- makepri.exe dump /if .\resources.pri
-- Check the contents of the `resources.pri.xml` file that is generated from the command. The last section of the file will contain the resources with the strings in all the languages:
-```
-
-
- Running as administrator
-
-
- Running as administrator
-
-
-```
-
-For C# projects, satellite dlls are generated when the project is built. For a project named `ProjName`, files are created in the format `langId\ProjName.resources.dll` where `langId` is in the same format as the lcl files. The satellite dlls need to be included with the MSI, but they must be added only if the solution is built from the build farm, as the localized resx files will not be present on local machines (and that could cause local builds of the installer to fail).
-This can be done by adding the directory name of the project to [Product.wxs near line 806](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L806) and a resource component for the project can be created in [Product.wxs near lines 845-847](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/installer/PowerToysSetup/Product.wxs#L845-L847) in this format:
-```
-
-
-
-```
-
-We should also ensure the new dlls are signed by the pipeline. Currently all dlls of the form [`*.resources.dll` are signed](https://github.com/microsoft/PowerToys/blob/f92bd6ffd38014c228544bb8d68d0937ce4c2b6d/.pipelines/pipeline.user.windows.yml#L68).
-
-**Note:** The resource dlls should be added to the MSI project only after the initial commit with the lcl files has been done by the Localization team. Otherwise, the pipeline will fail as there wouldn't be any resx files to generate the dlls.
diff --git a/doc/devdocs/modules/alwaysontop.md b/doc/devdocs/modules/alwaysontop.md
index 2162245d83..d8948697f1 100644
--- a/doc/devdocs/modules/alwaysontop.md
+++ b/doc/devdocs/modules/alwaysontop.md
@@ -86,7 +86,7 @@ The module provides a user interface for configuring settings in the PowerToys S
### Building and Testing
1. Clone the repository: `git clone https://github.com/microsoft/PowerToys.git`
-2. Open PowerToys.sln in Visual Studio
+2. Open PowerToys.slnx in Visual Studio
3. Select the Release configuration and build the solution
4. Run PowerToys.exe from the output directory to test the module
diff --git a/doc/devdocs/modules/fancyzones.md b/doc/devdocs/modules/fancyzones.md
index 07ffe491f5..a4571eacd7 100644
--- a/doc/devdocs/modules/fancyzones.md
+++ b/doc/devdocs/modules/fancyzones.md
@@ -161,7 +161,7 @@ FancyZones is divided into several projects:
```
git clone https://github.com/microsoft/PowerToys.git
```
-2. Open `PowerToys.sln` in Visual Studio
+2. Open `PowerToys.slnx` in Visual Studio
3. Select the Release configuration and build the solution
4. If you encounter build errors, try deleting the x64 output folder and rebuild
@@ -244,7 +244,7 @@ UI tests are implemented using [Windows Application Driver](https://github.com/m
- Exit PowerToys if it's running
- Run WinAppDriver.exe from the installation directory. Skip this step if installed in the default directory (`C:\Program Files (x86)\Windows Application Driver`); in this case, it'll be launched automatically during tests.
- - Open `PowerToys.sln` in Visual Studio and build the solution.
+ - Open `PowerToys.slnx` in Visual Studio and build the solution.
- Run tests in the Test Explorer (`Test > Test Explorer` or `Ctrl+E, T`).
>Note: notifications or other application windows, that are shown above the window under test, can disrupt the testing process.
diff --git a/doc/devdocs/modules/keyboardmanager/debug.md b/doc/devdocs/modules/keyboardmanager/debug.md
index 0efb3b0b3a..60b975da0f 100644
--- a/doc/devdocs/modules/keyboardmanager/debug.md
+++ b/doc/devdocs/modules/keyboardmanager/debug.md
@@ -11,7 +11,7 @@ Keyboard Manager consists of two main components:
## Development Environment Setup
1. Clone the PowerToys repository
-2. Open `PowerToys.sln` in Visual Studio
+2. Open `PowerToys.slnx` in Visual Studio
3. Ensure all NuGet packages are restored
4. Build the entire solution in Debug configuration
@@ -91,4 +91,4 @@ If you encounter issues with multiple instances, check the mutex logic in `Keybo
To debug both the Editor and Engine:
1. Launch the Engine first in debug mode
-2. Attach the debugger to the Editor process when it starts
\ No newline at end of file
+2. Attach the debugger to the Editor process when it starts
diff --git a/doc/devdocs/modules/lightswitch.md b/doc/devdocs/modules/lightswitch.md
index 1e251dfff1..eb5e07aea6 100644
--- a/doc/devdocs/modules/lightswitch.md
+++ b/doc/devdocs/modules/lightswitch.md
@@ -33,9 +33,12 @@ The **Light Switch** module lets users automatically transition between light an
> **Note:** Using the shortcut overrides the current schedule until the next transition event.
-* **LightSwitchService**
- Reads settings and applies theming. Runs a check every minute to ensure the state is correct.
-
+* **LightSwitchService.cpp**
+ is the heart beat of the module. Controls ticking every minute and depending on user actions (manual override, settings changing, etc) triggers the state manager to perform the corresponding operation.
+
+* **LightSwitchStateManager.cpp**
+ handles updating the state based on the signals sent by LightSwitchService.
+
* **SettingsXAML/LightSwitch**
Provides the settings UI for configuring schedules, syncing location, and customizing shortcuts.
@@ -89,7 +92,7 @@ The module’s settings are exposed in the PowerToys Settings UI. Options includ
3. Build the solution:
```sh
- msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
+ msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.slnx
```
> Note: This may take some time.
diff --git a/doc/devdocs/modules/screenruler.md b/doc/devdocs/modules/screenruler.md
index 08bdda764a..d43d1639ea 100644
--- a/doc/devdocs/modules/screenruler.md
+++ b/doc/devdocs/modules/screenruler.md
@@ -53,7 +53,7 @@ The Screen Ruler module consists of several components:
### Building
-1. Open PowerToys.sln in Visual Studio
+1. Open PowerToys.slnx in Visual Studio
2. In the Solutions Configuration drop-down menu, select Release or Debug
3. From the Build menu, choose Build Solution
4. The executable app for Screen Ruler is named PowerToys.MeasureToolUI.exe
diff --git a/doc/devdocs/modules/shortcut_guide.md b/doc/devdocs/modules/shortcut_guide.md
index c8cefbc3f3..f150a4456c 100644
--- a/doc/devdocs/modules/shortcut_guide.md
+++ b/doc/devdocs/modules/shortcut_guide.md
@@ -19,7 +19,7 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor
## Build and Debug Instructions
### Build
-1. Open PowerToys.sln in Visual Studio
+1. Open PowerToys.slnx in Visual Studio
2. Select Release or Debug in the Solutions Configuration drop-down menu
3. From the Build menu, choose Build Solution
4. The executable is named PowerToys.ShortcutGuide.exe
diff --git a/doc/devdocs/readme.md b/doc/devdocs/readme.md
index 75e03c0629..38df894d1a 100644
--- a/doc/devdocs/readme.md
+++ b/doc/devdocs/readme.md
@@ -38,6 +38,11 @@ Welcome to the PowerToys developer documentation. This documentation provides in
- [Update Process](processes/update-process.md) - How PowerToys updates work
- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details
+## Other Resources
+
+- [aka.ms links](akaLinks.md) - List of short links
+- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests
+
## Fork, Clone, Branch and Create your PR
Once you've discussed your proposed feature/fix/etc. with a team member, and an approach or a spec has been written and approved, it's time to start development:
@@ -80,7 +85,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
### Install Visual Studio dependencies
-1. Open the `PowerToys.sln` file.
+1. Open the `PowerToys.slnx` file.
1. If you see a dialog that says `install extra components` in the solution explorer pane, click `install`
### Get Submodules to compile
@@ -93,7 +98,7 @@ We have submodules that need to be initialized before you can compile most parts
### Compiling Source Code
-- Open `PowerToys.sln` in Visual Studio.
+- Open `PowerToys.slnx` in Visual Studio.
- In the `Solutions Configuration` drop-down menu select `Release` or `Debug`.
- From the `Build` menu choose `Build Solution`, or press Control +Shift +b on your keyboard.
- The build process may take several minutes depending on your computer's performance. Once it completes, the PowerToys binaries will be in your repo under `x64\Release\`.
@@ -107,10 +112,10 @@ Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains
The installer can only be compiled in `Release` mode; steps 1 and 2 must be performed before the MSI can be compiled.
-1. Compile `PowerToys.sln`. Instructions are listed above.
+1. Compile `PowerToys.slnx`. Instructions are listed above.
1. Compile `BugReportTool.sln` tool. Path from root: `tools\BugReportTool\BugReportTool.sln` (details listed below)
1. Compile `StylesReportTool.sln` tool. Path from root: `tools\StylesReportTool\StylesReportTool.sln` (details listed below)
-1. Compile `PowerToysSetup.sln` Path from root: `installer\PowerToysSetup.sln` (details listed below)
+1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below)
See [Installer](core/installer.md) for more details on building and debugging the installer.
diff --git a/doc/images/icons/CursorWrap.png b/doc/images/icons/CursorWrap.png
new file mode 100644
index 0000000000..20db84fc9a
Binary files /dev/null and b/doc/images/icons/CursorWrap.png differ
diff --git a/doc/images/icons/Find My Mouse.png b/doc/images/icons/Find My Mouse.png
index 71dd994569..82fbe59800 100644
Binary files a/doc/images/icons/Find My Mouse.png and b/doc/images/icons/Find My Mouse.png differ
diff --git a/doc/images/icons/Mouse Highlighter.png b/doc/images/icons/Mouse Highlighter.png
index b06843d941..0feb5cc15a 100644
Binary files a/doc/images/icons/Mouse Highlighter.png and b/doc/images/icons/Mouse Highlighter.png differ
diff --git a/doc/images/icons/MouseJump.png b/doc/images/icons/MouseJump.png
new file mode 100644
index 0000000000..2fbe450ac2
Binary files /dev/null and b/doc/images/icons/MouseJump.png differ
diff --git a/doc/images/icons/MouseWithoutBorders.png b/doc/images/icons/MouseWithoutBorders.png
index a29adf7d11..ee66893cbd 100644
Binary files a/doc/images/icons/MouseWithoutBorders.png and b/doc/images/icons/MouseWithoutBorders.png differ
diff --git a/doc/thirdPartyRunPlugins.md b/doc/thirdPartyRunPlugins.md
index a15cb542a8..9cfdc505ff 100644
--- a/doc/thirdPartyRunPlugins.md
+++ b/doc/thirdPartyRunPlugins.md
@@ -51,6 +51,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
| [CheatSheets](https://github.com/ruslanlap/PowerToysRun-CheatSheets) | [ruslanlap](https://github.com/ruslanlap) | 📚 Find cheat sheets and command examples instantly from tldr pages, cheat.sh, and devhints.io. Features include favorites system, categories, offline mode, and smart caching. |
+| [QuickAI](https://github.com/ruslanlap/PowerToysRun-QuickAi) | [ruslanlap](https://github.com/ruslanlap) | AI-powered assistance with instant, smart responses from multiple providers (Groq, Together, Fireworks, OpenRouter, Cohere) |
## Extending software plugins
diff --git a/installer/PowerToysSetup.sln b/installer/PowerToysSetup.sln
deleted file mode 100644
index 77d38c94ab..0000000000
--- a/installer/PowerToysSetup.sln
+++ /dev/null
@@ -1,96 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.1.32414.318
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "..\src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "..\src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EtwTrace", "..\src\common\Telemetry\EtwTrace\EtwTrace.vcxproj", "{8F021B46-362B-485C-BFBA-CCF83E820CBD}"
-EndProject
-Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysInstallerVNext", "PowerToysSetupVNext\PowerToysInstallerVNext.wixproj", "{B6E94700-DF38-41F6-A3FD-18B69674AB1E}"
-EndProject
-Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysBootstrapperVNext", "PowerToysSetupVNext\PowerToysBootstrapperVNext.wixproj", "{DA4E9744-80BE-424C-B0F5-AFD8757DB575}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActionsVNext", "PowerToysSetupCustomActionsVNext\PowerToysSetupCustomActionsVNext.vcxproj", "{B3A354B0-1E54-4B55-A962-FB5AF9330C19}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SilentFilesInUseBAFunction", "PowerToysSetupVNext\SilentFilesInUseBA\SilentFilesInUseBAFunction.vcxproj", "{F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|ARM64 = Debug|ARM64
- Debug|x64 = Debug|x64
- Release|ARM64 = Release|ARM64
- Release|x64 = Release|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.ActiveCfg = Release|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|ARM64.Build.0 = Release|ARM64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
- {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.ActiveCfg = Release|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|ARM64.Build.0 = Release|ARM64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64
- {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|ARM64.Build.0 = Debug|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.ActiveCfg = Debug|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Debug|x64.Build.0 = Debug|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.ActiveCfg = Release|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|ARM64.Build.0 = Release|ARM64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.ActiveCfg = Release|x64
- {CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}.Release|x64.Build.0 = Release|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|ARM64.Build.0 = Debug|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.ActiveCfg = Debug|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Debug|x64.Build.0 = Debug|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.ActiveCfg = Release|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|ARM64.Build.0 = Release|ARM64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.ActiveCfg = Release|x64
- {8F021B46-362B-485C-BFBA-CCF83E820CBD}.Release|x64.Build.0 = Release|x64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|ARM64.Build.0 = Debug|ARM64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.ActiveCfg = Debug|x64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Debug|x64.Build.0 = Debug|x64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.ActiveCfg = Release|ARM64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|ARM64.Build.0 = Release|ARM64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.ActiveCfg = Release|x64
- {B6E94700-DF38-41F6-A3FD-18B69674AB1E}.Release|x64.Build.0 = Release|x64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|ARM64.Build.0 = Debug|ARM64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.ActiveCfg = Debug|x64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Debug|x64.Build.0 = Debug|x64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.ActiveCfg = Release|ARM64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|ARM64.Build.0 = Release|ARM64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.ActiveCfg = Release|x64
- {DA4E9744-80BE-424C-B0F5-AFD8757DB575}.Release|x64.Build.0 = Release|x64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.ActiveCfg = Debug|x64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Debug|x64.Build.0 = Debug|x64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.ActiveCfg = Release|ARM64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|ARM64.Build.0 = Release|ARM64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.ActiveCfg = Release|x64
- {B3A354B0-1E54-4B55-A962-FB5AF9330C19}.Release|x64.Build.0 = Release|x64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.ActiveCfg = Debug|x64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Debug|x64.Build.0 = Debug|x64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.ActiveCfg = Release|ARM64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|ARM64.Build.0 = Release|ARM64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.ActiveCfg = Release|x64
- {F8B9F842-F5C3-4A2D-8C85-7F8B9E2B4F1D}.Release|x64.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {B7A3DA30-D443-40FF-AC51-988AD41E3962}
- EndGlobalSection
-EndGlobal
\ No newline at end of file
diff --git a/installer/PowerToysSetup.slnx b/installer/PowerToysSetup.slnx
new file mode 100644
index 0000000000..658310f9f6
--- /dev/null
+++ b/installer/PowerToysSetup.slnx
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/PowerToysSetup/generateDscResourcesWxs.ps1 b/installer/PowerToysSetup/generateDscResourcesWxs.ps1
deleted file mode 100644
index f76fd71953..0000000000
--- a/installer/PowerToysSetup/generateDscResourcesWxs.ps1
+++ /dev/null
@@ -1,87 +0,0 @@
-[CmdletBinding()]
-Param(
- [Parameter(Mandatory = $True)]
- [string]$dscWxsFile,
- [Parameter(Mandatory = $True)]
- [string]$Platform,
- [Parameter(Mandatory = $True)]
- [string]$Configuration
-)
-
-$ErrorActionPreference = 'Stop'
-$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
-$buildOutputDir = Join-Path $scriptDir "..\..\$Platform\$Configuration"
-
-if (-not (Test-Path $buildOutputDir)) {
- Write-Error "Build output directory not found: '$buildOutputDir'"
- exit 1
-}
-
-$dscFiles = Get-ChildItem -Path $buildOutputDir -Filter "microsoft.powertoys.*.settings.dsc.resource.json" -File
-
-if (-not $dscFiles) {
- Write-Warning "No DSC manifest files found in '$buildOutputDir'"
- $wxsContent = @"
-
-
-
-
-
-
-
-"@
- Set-Content -Path $dscWxsFile -Value $wxsContent
- exit 0
-}
-
-Write-Host "Found $($dscFiles.Count) DSC manifest file(s)"
-
-$wxsContent = @"
-
-
-
-
-"@
-
-$componentRefs = @()
-foreach ($file in $dscFiles) {
- $componentId = "DscResource_" + ($file.BaseName -replace '[^A-Za-z0-9_]', '_')
- $fileId = $componentId + "_File"
- $guid = [System.Guid]::NewGuid().ToString().ToUpper()
- $componentRefs += $componentId
-
- $wxsContent += @"
-
-
-
-
-
-
-
-"@
-}
-
-$wxsContent += @"
-
-
-
-
-
-"@
-
-foreach ($componentId in $componentRefs) {
- $wxsContent += @"
-
-
-"@
-}
-
-$wxsContent += @"
-
-
-
-
-"@
-
-Set-Content -Path $dscWxsFile -Value $wxsContent
-Write-Host "Generated DSC resources WiX fragment: '$dscWxsFile'"
diff --git a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp
index 0cfc3b1765..968fcc2530 100644
--- a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp
+++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include "../../src/common/logger/logger.h"
#include "../../src/common/utils/gpo.h"
@@ -856,14 +857,69 @@ UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall)
try
{
- winrt::Windows::Security::Credentials::PasswordVault vault;
- winrt::Windows::Security::Credentials::PasswordCredential cred;
-
hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey");
ExitOnFailure(hr, "Failed to initialize");
- cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey");
- vault.Remove(cred);
+ winrt::Windows::Security::Credentials::PasswordVault vault;
+
+ auto hasPrefix = [](std::wstring_view value, wchar_t const* prefix) {
+ std::wstring_view prefixView{ prefix };
+ return value.compare(0, prefixView.size(), prefixView) == 0;
+ };
+
+ const wchar_t* resourcePrefixes[] = {
+ L"https://platform.openai.com/api-keys",
+ L"https://azure.microsoft.com/products/ai-services/openai-service",
+ L"https://azure.microsoft.com/products/ai-services/ai-inference",
+ L"https://console.mistral.ai/account/api-keys",
+ L"https://ai.google.dev/",
+ };
+
+ const wchar_t* usernamePrefixes[] = {
+ L"PowerToys_AdvancedPaste_",
+ };
+
+ auto credentials = vault.RetrieveAll();
+ for (auto const& credential : credentials)
+ {
+ bool shouldRemove = false;
+
+ std::wstring resource{ credential.Resource() };
+ for (auto const prefix : resourcePrefixes)
+ {
+ if (hasPrefix(resource, prefix))
+ {
+ shouldRemove = true;
+ break;
+ }
+ }
+
+ if (!shouldRemove)
+ {
+ std::wstring username{ credential.UserName() };
+ for (auto const prefix : usernamePrefixes)
+ {
+ if (hasPrefix(username, prefix))
+ {
+ shouldRemove = true;
+ break;
+ }
+ }
+ }
+
+ if (!shouldRemove)
+ {
+ continue;
+ }
+
+ try
+ {
+ vault.Remove(credential);
+ }
+ catch (...)
+ {
+ }
+ }
}
catch (...)
{
diff --git a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj
index 7cd49be6ea..ae50cdcedb 100644
--- a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj
+++ b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj
@@ -34,13 +34,8 @@
- $(Platform)\$(Configuration)\MachineSetup\
- $(Platform)\$(Configuration)\UserSetup\
- $(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\MachineSetup\obj\
- $(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\UserSetup\obj\
-
- false
- true
+ $(Platform)\$(Configuration)\SetupShared\
+ $(SolutionDir)$(ProjectName)\$(Platform)\$(Configuration)\SetupShared\obj\
true
@@ -59,6 +54,7 @@
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\CmdPal.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\CmdPal.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\ColorPicker.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Core.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Core.wxs.bk""""
+ call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\DscResources.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\DscResources.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\EnvironmentVariables.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\EnvironmentVariables.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\FileExplorerPreview.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\FileExplorerPreview.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\FileLocksmith.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\FileLocksmith.wxs.bk""""
@@ -80,8 +76,7 @@
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\WinAppSDK.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\WinAppSDK.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\WinUI3Applications.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\WinUI3Applications.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetupVNext\Workspaces.wxs"" ""$(ProjectDir)..\PowerToysSetupVNext\Workspaces.wxs.bk""""
- if not "$(NormalizedPerUserValue)" == "true" call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetupVNext\generateAllFileComponents.ps1 -platform $(Platform)
- if "$(NormalizedPerUserValue)" == "true" call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetupVNext\generateAllFileComponents.ps1 -platform $(Platform) -installscopeperuser $(NormalizedPerUserValue)
+ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File ..\PowerToysSetupVNext\generateAllFileComponents.ps1 -platform $(Platform)
Backing up original files and populating .NET and WPF Runtime dependencies for WiX3 based installer
@@ -181,4 +176,4 @@
-
\ No newline at end of file
+
diff --git a/installer/PowerToysSetupVNext/CmdPal.wxs b/installer/PowerToysSetupVNext/CmdPal.wxs
index 8304551c12..f05a1f2f35 100644
--- a/installer/PowerToysSetupVNext/CmdPal.wxs
+++ b/installer/PowerToysSetupVNext/CmdPal.wxs
@@ -4,6 +4,13 @@
+
+
+
+
+
+
+
@@ -18,14 +25,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/PowerToysSetupVNext/Core.wxs b/installer/PowerToysSetupVNext/Core.wxs
index f7da6162f9..a9cf083512 100644
--- a/installer/PowerToysSetupVNext/Core.wxs
+++ b/installer/PowerToysSetupVNext/Core.wxs
@@ -15,8 +15,8 @@
-
-
+
+
@@ -24,8 +24,8 @@
-
-
+
+
@@ -63,16 +63,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -120,7 +110,6 @@
-
@@ -128,16 +117,15 @@
-
-
-
-
-
-
+
+
+
+
+
diff --git a/installer/PowerToysSetupVNext/DscResources.wxs b/installer/PowerToysSetupVNext/DscResources.wxs
new file mode 100644
index 0000000000..2c08253229
--- /dev/null
+++ b/installer/PowerToysSetupVNext/DscResources.wxs
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj b/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj
index afce3d396d..18d6232140 100644
--- a/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj
+++ b/installer/PowerToysSetupVNext/PowerToysInstallerVNext.wixproj
@@ -14,7 +14,6 @@ SET PTRoot=$(SolutionDir)\..
call "..\..\..\publish.cmd" x64
)
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2"
-call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
@@ -25,7 +24,6 @@ SET PTRoot=$(SolutionDir)\..
call "..\..\..\publish.cmd" arm64
)
call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateMonacoWxs.ps1 -monacoWxsFile "$(MSBuildThisFileDirectory)\MonacoSRC.wxs" -Platform "$(Platform)" -nugetHeatPath "$(NUGET_PACKAGES)\wixtoolset.heat\5.0.2"
-call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuildThisFileDirectory)\generateDscResourcesWxs.ps1 -dscWxsFile "$(MSBuildThisFileDirectory)\DscResources.wxs" -Platform "$(Platform)" -Configuration "$(Configuration)"
@@ -37,6 +35,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
call move /Y ..\..\..\CmdPal.wxs.bk ..\..\..\CmdPal.wxs
call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs
call move /Y ..\..\..\Core.wxs.bk ..\..\..\Core.wxs
+ call move /Y ..\..\..\DscResources.wxs.bk ..\..\..\DscResources.wxs
call move /Y ..\..\..\EnvironmentVariables.wxs.bk ..\..\..\EnvironmentVariables.wxs
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs
@@ -60,6 +59,12 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
call move /Y ..\..\..\Workspaces.wxs.bk ..\..\..\Workspaces.wxs
+
+
+
+ false
+ false
+
$(DefineConstants);PerUser=true
diff --git a/installer/PowerToysSetupVNext/Product.wxs b/installer/PowerToysSetupVNext/Product.wxs
index 556fddc7f4..3e812beb2e 100644
--- a/installer/PowerToysSetupVNext/Product.wxs
+++ b/installer/PowerToysSetupVNext/Product.wxs
@@ -120,8 +120,8 @@
-
-
+
+
diff --git a/installer/PowerToysSetupVNext/Settings.wxs b/installer/PowerToysSetupVNext/Settings.wxs
index f9e5312ea7..cf7cf7f727 100644
--- a/installer/PowerToysSetupVNext/Settings.wxs
+++ b/installer/PowerToysSetupVNext/Settings.wxs
@@ -14,11 +14,16 @@
+
+
+
-
+
+
+
@@ -45,6 +50,11 @@
+
+
+
+
+
@@ -67,6 +77,7 @@
+
diff --git a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1
index b6f2f88dd0..6724d95170 100644
--- a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1
+++ b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1
@@ -1,9 +1,7 @@
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, Position = 1)]
- [string]$platform,
- [Parameter(Mandatory = $False, Position = 2)]
- [string]$installscopeperuser = "false"
+ [string]$platform
)
Function Generate-FileList() {
@@ -77,9 +75,7 @@ Function Generate-FileComponents() {
[Parameter(Mandatory = $True, Position = 1)]
[string]$fileListName,
[Parameter(Mandatory = $True, Position = 2)]
- [string]$wxsFilePath,
- [Parameter(Mandatory = $True, Position = 3)]
- [string]$regroot
+ [string]$wxsFilePath
)
$wxsFile = Get-Content $wxsFilePath;
@@ -100,7 +96,7 @@ Function Generate-FileComponents() {
$componentDefs +=
@"
-
+
`r`n
"@
@@ -134,194 +130,194 @@ if ($platform -ceq "arm64") {
$platform = "ARM64"
}
-if ($installscopeperuser -eq "true") {
- $registryroot = "HKCU"
-} else {
- $registryroot = "HKLM"
-}
-
#BaseApplications
Generate-FileList -fileDepsJson "" -fileListName BaseApplicationsFiles -wxsFilePath $PSScriptRoot\BaseApplications.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release"
-Generate-FileComponents -fileListName "BaseApplicationsFiles" -wxsFilePath $PSScriptRoot\BaseApplications.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "BaseApplicationsFiles" -wxsFilePath $PSScriptRoot\BaseApplications.wxs
#WinUI3Applications
Generate-FileList -fileDepsJson "" -fileListName WinUI3ApplicationsFiles -wxsFilePath $PSScriptRoot\WinUI3Applications.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps"
-Generate-FileComponents -fileListName "WinUI3ApplicationsFiles" -wxsFilePath $PSScriptRoot\WinUI3Applications.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "WinUI3ApplicationsFiles" -wxsFilePath $PSScriptRoot\WinUI3Applications.wxs
#AdvancedPaste
Generate-FileList -fileDepsJson "" -fileListName AdvancedPasteAssetsFiles -wxsFilePath $PSScriptRoot\AdvancedPaste.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\AdvancedPaste"
-Generate-FileComponents -fileListName "AdvancedPasteAssetsFiles" -wxsFilePath $PSScriptRoot\AdvancedPaste.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "AdvancedPasteAssetsFiles" -wxsFilePath $PSScriptRoot\AdvancedPaste.wxs
#AwakeFiles
Generate-FileList -fileDepsJson "" -fileListName AwakeImagesFiles -wxsFilePath $PSScriptRoot\Awake.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Awake"
-Generate-FileComponents -fileListName "AwakeImagesFiles" -wxsFilePath $PSScriptRoot\Awake.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "AwakeImagesFiles" -wxsFilePath $PSScriptRoot\Awake.wxs
#ColorPicker
Generate-FileList -fileDepsJson "" -fileListName ColorPickerAssetsFiles -wxsFilePath $PSScriptRoot\ColorPicker.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ColorPicker"
-Generate-FileComponents -fileListName "ColorPickerAssetsFiles" -wxsFilePath $PSScriptRoot\ColorPicker.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ColorPickerAssetsFiles" -wxsFilePath $PSScriptRoot\ColorPicker.wxs
#Environment Variables
Generate-FileList -fileDepsJson "" -fileListName EnvironmentVariablesAssetsFiles -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\EnvironmentVariables"
-Generate-FileComponents -fileListName "EnvironmentVariablesAssetsFiles" -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "EnvironmentVariablesAssetsFiles" -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs
#FileExplorerAdd-ons
Generate-FileList -fileDepsJson "" -fileListName MonacoPreviewHandlerMonacoAssetsFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco"
Generate-FileList -fileDepsJson "" -fileListName MonacoPreviewHandlerCustomLanguagesFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco\customLanguages"
-Generate-FileComponents -fileListName "MonacoPreviewHandlerMonacoAssetsFiles" -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "MonacoPreviewHandlerCustomLanguagesFiles" -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "MonacoPreviewHandlerMonacoAssetsFiles" -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs
+Generate-FileComponents -fileListName "MonacoPreviewHandlerCustomLanguagesFiles" -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs
#FileLocksmith
Generate-FileList -fileDepsJson "" -fileListName FileLocksmithAssetsFiles -wxsFilePath $PSScriptRoot\FileLocksmith.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\FileLocksmith"
-Generate-FileComponents -fileListName "FileLocksmithAssetsFiles" -wxsFilePath $PSScriptRoot\FileLocksmith.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "FileLocksmithAssetsFiles" -wxsFilePath $PSScriptRoot\FileLocksmith.wxs
#Hosts
Generate-FileList -fileDepsJson "" -fileListName HostsAssetsFiles -wxsFilePath $PSScriptRoot\Hosts.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Hosts"
-Generate-FileComponents -fileListName "HostsAssetsFiles" -wxsFilePath $PSScriptRoot\Hosts.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "HostsAssetsFiles" -wxsFilePath $PSScriptRoot\Hosts.wxs
#ImageResizer
Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFilePath $PSScriptRoot\ImageResizer.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ImageResizer"
-Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs
# Light Switch Service
Generate-FileList -fileDepsJson "" -fileListName LightSwitchFiles -wxsFilePath $PSScriptRoot\LightSwitch.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\LightSwitchService"
-Generate-FileComponents -fileListName "LightSwitchFiles" -wxsFilePath $PSScriptRoot\LightSwitch.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "LightSwitchFiles" -wxsFilePath $PSScriptRoot\LightSwitch.wxs
#New+
Generate-FileList -fileDepsJson "" -fileListName NewPlusAssetsFiles -wxsFilePath $PSScriptRoot\NewPlus.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\NewPlus"
-Generate-FileComponents -fileListName "NewPlusAssetsFiles" -wxsFilePath $PSScriptRoot\NewPlus.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "NewPlusAssetsFiles" -wxsFilePath $PSScriptRoot\NewPlus.wxs
#Peek
Generate-FileList -fileDepsJson "" -fileListName PeekAssetsFiles -wxsFilePath $PSScriptRoot\Peek.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Peek\"
-Generate-FileComponents -fileListName "PeekAssetsFiles" -wxsFilePath $PSScriptRoot\Peek.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "PeekAssetsFiles" -wxsFilePath $PSScriptRoot\Peek.wxs
#PowerRename
Generate-FileList -fileDepsJson "" -fileListName PowerRenameAssetsFiles -wxsFilePath $PSScriptRoot\PowerRename.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\PowerRename\"
-Generate-FileComponents -fileListName "PowerRenameAssetsFiles" -wxsFilePath $PSScriptRoot\PowerRename.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "PowerRenameAssetsFiles" -wxsFilePath $PSScriptRoot\PowerRename.wxs
#RegistryPreview
Generate-FileList -fileDepsJson "" -fileListName RegistryPreviewAssetsFiles -wxsFilePath $PSScriptRoot\RegistryPreview.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\RegistryPreview\"
-Generate-FileComponents -fileListName "RegistryPreviewAssetsFiles" -wxsFilePath $PSScriptRoot\RegistryPreview.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "RegistryPreviewAssetsFiles" -wxsFilePath $PSScriptRoot\RegistryPreview.wxs
#Run
Generate-FileList -fileDepsJson "" -fileListName launcherImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\PowerLauncher"
-Generate-FileComponents -fileListName "launcherImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "launcherImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
## Plugins
###Calculator
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.deps.json" -fileListName calcComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName calcImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Calculator\Images"
-Generate-FileComponents -fileListName "calcComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "calcImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "calcComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "calcImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Folder
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Folder\Microsoft.Plugin.Folder.deps.json" -fileListName FolderComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName FolderImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Folder\Images"
-Generate-FileComponents -fileListName "FolderComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "FolderImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "FolderComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "FolderImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Program
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Program\Microsoft.Plugin.Program.deps.json" -fileListName ProgramComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName ProgramImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Program\Images"
-Generate-FileComponents -fileListName "ProgramComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "ProgramImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ProgramComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "ProgramImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Shell
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Shell\Microsoft.Plugin.Shell.deps.json" -fileListName ShellComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName ShellImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Shell\Images"
-Generate-FileComponents -fileListName "ShellComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "ShellImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ShellComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "ShellImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Indexer
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Indexer\Microsoft.Plugin.Indexer.deps.json" -fileListName IndexerComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName IndexerImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Indexer\Images"
-Generate-FileComponents -fileListName "IndexerComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "IndexerImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "IndexerComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "IndexerImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###UnitConverter
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.deps.json" -fileListName UnitConvCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName UnitConvImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\UnitConverter\Images"
-Generate-FileComponents -fileListName "UnitConvCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "UnitConvImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "UnitConvCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "UnitConvImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###WebSearch
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WebSearch\Community.PowerToys.Run.Plugin.WebSearch.deps.json" -fileListName WebSrchCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName WebSrchImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WebSearch\Images"
-Generate-FileComponents -fileListName "WebSrchCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "WebSrchImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "WebSrchCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "WebSrchImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###History
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\History\Microsoft.PowerToys.Run.Plugin.History.deps.json" -fileListName HistoryPluginComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName HistoryPluginImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\History\Images"
-Generate-FileComponents -fileListName "HistoryPluginComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "HistoryPluginImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "HistoryPluginComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "HistoryPluginImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Uri
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Uri\Microsoft.Plugin.Uri.deps.json" -fileListName UriComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName UriImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Uri\Images"
-Generate-FileComponents -fileListName "UriComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "UriImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "UriComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "UriImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###VSCodeWorkspaces
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\VSCodeWorkspaces\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.deps.json" -fileListName VSCWrkCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName VSCWrkImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\VSCodeWorkspaces\Images"
-Generate-FileComponents -fileListName "VSCWrkCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "VSCWrkImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "VSCWrkCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "VSCWrkImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###WindowWalker
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowWalker\Microsoft.Plugin.WindowWalker.deps.json" -fileListName WindowWlkrCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName WindowWlkrImagesCompFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowWalker\Images"
-Generate-FileComponents -fileListName "WindowWlkrCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "WindowWlkrImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "WindowWlkrCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "WindowWlkrImagesCompFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###OneNote
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\OneNote\Microsoft.PowerToys.Run.Plugin.OneNote.deps.json" -fileListName OneNoteComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName OneNoteImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\OneNote\Images"
-Generate-FileComponents -fileListName "OneNoteComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "OneNoteImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "OneNoteComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "OneNoteImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Registry
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Registry\Microsoft.PowerToys.Run.Plugin.Registry.deps.json" -fileListName RegistryComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName RegistryImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Registry\Images"
-Generate-FileComponents -fileListName "RegistryComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "RegistryImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "RegistryComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "RegistryImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###Service
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Service\Microsoft.PowerToys.Run.Plugin.Service.deps.json" -fileListName ServiceComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName ServiceImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\Service\Images"
-Generate-FileComponents -fileListName "ServiceComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "ServiceImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ServiceComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "ServiceImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###System
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\System\Microsoft.PowerToys.Run.Plugin.System.deps.json" -fileListName SystemComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName SystemImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\System\Images"
-Generate-FileComponents -fileListName "SystemComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "SystemImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "SystemComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "SystemImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###TimeDate
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\TimeDate\Microsoft.PowerToys.Run.Plugin.TimeDate.deps.json" -fileListName TimeDateComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName TimeDateImagesComponentFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\TimeDate\Images"
-Generate-FileComponents -fileListName "TimeDateComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "TimeDateImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "TimeDateComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "TimeDateImagesComponentFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###WindowsSettings
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsSettings\Microsoft.PowerToys.Run.Plugin.WindowsSettings.deps.json" -fileListName WinSetCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName WinSetImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsSettings\Images"
-Generate-FileComponents -fileListName "WinSetCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "WinSetImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "WinSetCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "WinSetImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###WindowsTerminal
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.deps.json" -fileListName WinTermCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName WinTermImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\WindowsTerminal\Images"
-Generate-FileComponents -fileListName "WinTermCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "WinTermImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "WinTermCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "WinTermImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###PowerToys
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\PowerToys\Microsoft.PowerToys.Run.Plugin.PowerToys.deps.json" -fileListName PowerToysCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName PowerToysImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\PowerToys\Images"
-Generate-FileComponents -fileListName "PowerToysCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "PowerToysImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "PowerToysCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "PowerToysImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
###ValueGenerator
Generate-FileList -fileDepsJson "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\ValueGenerator\Community.PowerToys.Run.Plugin.ValueGenerator.deps.json" -fileListName ValueGeneratorCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -isLauncherPlugin 1
Generate-FileList -fileDepsJson "" -fileListName ValueGeneratorImagesCmpFiles -wxsFilePath $PSScriptRoot\Run.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\RunPlugins\ValueGenerator\Images"
-Generate-FileComponents -fileListName "ValueGeneratorCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ValueGeneratorCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
+Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePath $PSScriptRoot\Run.wxs
## Plugins
#ShortcutGuide
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideSvgFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ShortcutGuide\"
-Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
#Settings
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsModulesFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Modules\OOBE\"
Generate-FileList -fileDepsJson "" -fileListName SettingsV2OOBEAssetsFluentIconsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\"
-Generate-FileComponents -fileListName "SettingsV2AssetsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "SettingsV2AssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "SettingsV2OOBEAssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot
-Generate-FileComponents -fileListName "SettingsV2OOBEAssetsFluentIconsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs -regroot $registryroot
+Generate-FileList -fileDepsJson "" -fileListName SettingsV2IconsModelsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\Icons\Models\"
+Generate-FileComponents -fileListName "SettingsV2AssetsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
+Generate-FileComponents -fileListName "SettingsV2AssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
+Generate-FileComponents -fileListName "SettingsV2OOBEAssetsModulesFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
+Generate-FileComponents -fileListName "SettingsV2OOBEAssetsFluentIconsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
+Generate-FileComponents -fileListName "SettingsV2IconsModelsFiles" -wxsFilePath $PSScriptRoot\Settings.wxs
#Workspaces
Generate-FileList -fileDepsJson "" -fileListName WorkspacesImagesComponentFiles -wxsFilePath $PSScriptRoot\Workspaces.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\Workspaces\"
-Generate-FileComponents -fileListName "WorkspacesImagesComponentFiles" -wxsFilePath $PSScriptRoot\Workspaces.wxs -regroot $registryroot
+Generate-FileComponents -fileListName "WorkspacesImagesComponentFiles" -wxsFilePath $PSScriptRoot\Workspaces.wxs
+
+#DSC Resources - JSON manifest files in DSCModules subfolder
+Generate-FileList -fileDepsJson "" -fileListName DscJsonFiles -wxsFilePath $PSScriptRoot\DscResources.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\DSCModules\"
+Generate-FileComponents -fileListName "DscJsonFiles" -wxsFilePath $PSScriptRoot\DscResources.wxs
diff --git a/installer/PowerToysSetupVNext/generateDscResourcesWxs.ps1 b/installer/PowerToysSetupVNext/generateDscResourcesWxs.ps1
deleted file mode 100644
index 14172db0bc..0000000000
--- a/installer/PowerToysSetupVNext/generateDscResourcesWxs.ps1
+++ /dev/null
@@ -1,102 +0,0 @@
-[CmdletBinding()]
-Param(
- [Parameter(Mandatory = $True)]
- [string]$dscWxsFile,
- [Parameter(Mandatory = $True)]
- [string]$Platform,
- [Parameter(Mandatory = $True)]
- [string]$Configuration
-)
-
-$ErrorActionPreference = 'Stop'
-
-$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
-
-# Find build output directory
-$buildOutputDir = Join-Path $scriptDir "..\..\$Platform\$Configuration"
-
-if (-not (Test-Path $buildOutputDir)) {
- Write-Error "Build output directory not found: '$buildOutputDir'"
- exit 1
-}
-
-# Find all DSC manifest JSON files
-$dscFiles = Get-ChildItem -Path $buildOutputDir -Filter "microsoft.powertoys.*.settings.dsc.resource.json" -File
-
-if (-not $dscFiles) {
- Write-Warning "No DSC manifest files found in '$buildOutputDir'"
- # Create empty component group
- $wxsContent = @"
-
-
-
-
-
-
-
-
-
-"@
- Set-Content -Path $dscWxsFile -Value $wxsContent
- exit 0
-}
-
-Write-Host "Found $($dscFiles.Count) DSC manifest file(s)"
-
-# Generate WiX fragment
-$wxsContent = @"
-
-
-
-
-
-
-"@
-
-$componentRefs = @()
-
-foreach ($file in $dscFiles) {
- $componentId = "DscResource_" + ($file.BaseName -replace '[^A-Za-z0-9_]', '_')
- $fileId = $componentId + "_File"
- $guid = [System.Guid]::NewGuid().ToString().ToUpper()
-
- $componentRefs += $componentId
-
- $wxsContent += @"
-
-
-
-
-
-
-
-"@
-}
-
-$wxsContent += @"
-
-
-
-
-
-
-"@
-
-foreach ($componentId in $componentRefs) {
- $wxsContent += @"
-
-
-"@
-}
-
-$wxsContent += @"
-
-
-
-
-"@
-
-# Write the WiX file
-Set-Content -Path $dscWxsFile -Value $wxsContent
-
-Write-Host "Generated DSC resources WiX fragment: '$dscWxsFile'"
\ No newline at end of file
diff --git a/src/PackageIdentity/AppxManifest.xml b/src/PackageIdentity/AppxManifest.xml
index af637229ca..822daae8bc 100644
--- a/src/PackageIdentity/AppxManifest.xml
+++ b/src/PackageIdentity/AppxManifest.xml
@@ -36,31 +36,34 @@
-
+
+ Square44x44Logo="Images\Square44x44Logo.png"
+ AppListEntry="none">
-
+
+ Square44x44Logo="Images\Square44x44Logo.png"
+ AppListEntry="none">
-
+
+ Square44x44Logo="Images\Square44x44Logo.png"
+ AppListEntry="none">
diff --git a/src/PackageIdentity/PackageIdentity.vcxproj b/src/PackageIdentity/PackageIdentity.vcxproj
index b8bd2dc1f2..f8d34f5650 100644
--- a/src/PackageIdentity/PackageIdentity.vcxproj
+++ b/src/PackageIdentity/PackageIdentity.vcxproj
@@ -17,7 +17,7 @@
-
diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp
index 361255f66f..2b256cd926 100644
--- a/src/common/GPOWrapper/GPOWrapper.cpp
+++ b/src/common/GPOWrapper/GPOWrapper.cpp
@@ -112,6 +112,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast(powertoys_gpo::getConfiguredMousePointerCrosshairsEnabledValue());
}
+ GpoRuleConfigured GPOWrapper::GetConfiguredCursorWrapEnabledValue()
+ {
+ return static_cast(powertoys_gpo::getConfiguredCursorWrapEnabledValue());
+ }
GpoRuleConfigured GPOWrapper::GetConfiguredPowerRenameEnabledValue()
{
return static_cast(powertoys_gpo::getConfiguredPowerRenameEnabledValue());
@@ -192,6 +196,34 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
}
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOpenAIValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteOpenAIValue());
+ }
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteAzureOpenAIValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteAzureOpenAIValue());
+ }
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteAzureAIInferenceValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteAzureAIInferenceValue());
+ }
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteMistralValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteMistralValue());
+ }
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteGoogleValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteGoogleValue());
+ }
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOllamaValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteOllamaValue());
+ }
+ GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteFoundryLocalValue()
+ {
+ return static_cast(powertoys_gpo::getAllowedAdvancedPasteFoundryLocalValue());
+ }
GpoRuleConfigured GPOWrapper::GetConfiguredNewPlusEnabledValue()
{
return static_cast(powertoys_gpo::getConfiguredNewPlusEnabledValue());
diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h
index c0fff9f542..e57cccccd9 100644
--- a/src/common/GPOWrapper/GPOWrapper.h
+++ b/src/common/GPOWrapper/GPOWrapper.h
@@ -35,6 +35,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredMouseHighlighterEnabledValue();
static GpoRuleConfigured GetConfiguredMouseJumpEnabledValue();
static GpoRuleConfigured GetConfiguredMousePointerCrosshairsEnabledValue();
+ static GpoRuleConfigured GetConfiguredCursorWrapEnabledValue();
static GpoRuleConfigured GetConfiguredPowerRenameEnabledValue();
static GpoRuleConfigured GetConfiguredPowerLauncherEnabledValue();
static GpoRuleConfigured GetConfiguredQuickAccentEnabledValue();
@@ -54,6 +55,13 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteOpenAIValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteAzureOpenAIValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteAzureAIInferenceValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteMistralValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteGoogleValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl
index 630beab9c9..06d035aa35 100644
--- a/src/common/GPOWrapper/GPOWrapper.idl
+++ b/src/common/GPOWrapper/GPOWrapper.idl
@@ -38,6 +38,7 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredMouseHighlighterEnabledValue();
static GpoRuleConfigured GetConfiguredMouseJumpEnabledValue();
static GpoRuleConfigured GetConfiguredMousePointerCrosshairsEnabledValue();
+ static GpoRuleConfigured GetConfiguredCursorWrapEnabledValue();
static GpoRuleConfigured GetConfiguredMouseWithoutBordersEnabledValue();
static GpoRuleConfigured GetConfiguredPowerRenameEnabledValue();
static GpoRuleConfigured GetConfiguredPowerLauncherEnabledValue();
@@ -58,6 +59,13 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteOpenAIValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteAzureOpenAIValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteAzureAIInferenceValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteMistralValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteGoogleValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteOllamaValue();
+ static GpoRuleConfigured GetAllowedAdvancedPasteFoundryLocalValue();
static GpoRuleConfigured GetConfiguredNewPlusEnabledValue();
static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue();
static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue();
diff --git a/src/common/LanguageModelProvider/FoundryLocal/FoundryCachedModel.cs b/src/common/LanguageModelProvider/FoundryLocal/FoundryCachedModel.cs
new file mode 100644
index 0000000000..489a779179
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/FoundryCachedModel.cs
@@ -0,0 +1,7 @@
+// 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 LanguageModelProvider.FoundryLocal;
+
+internal sealed record FoundryCachedModel(string Name, string? Id);
diff --git a/src/common/LanguageModelProvider/FoundryLocal/FoundryCatalogModel.cs b/src/common/LanguageModelProvider/FoundryLocal/FoundryCatalogModel.cs
new file mode 100644
index 0000000000..413bb47316
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/FoundryCatalogModel.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json.Serialization;
+
+namespace LanguageModelProvider.FoundryLocal;
+
+internal sealed record FoundryCatalogModel
+{
+ [JsonPropertyName("name")]
+ public string Name { get; init; } = string.Empty;
+
+ [JsonPropertyName("displayName")]
+ public string DisplayName { get; init; } = string.Empty;
+
+ [JsonPropertyName("providerType")]
+ public string ProviderType { get; init; } = string.Empty;
+
+ [JsonPropertyName("uri")]
+ public string Uri { get; init; } = string.Empty;
+
+ [JsonPropertyName("version")]
+ public string Version { get; init; } = string.Empty;
+
+ [JsonPropertyName("modelType")]
+ public string ModelType { get; init; } = string.Empty;
+
+ [JsonPropertyName("promptTemplate")]
+ public PromptTemplate PromptTemplate { get; init; } = default!;
+
+ [JsonPropertyName("publisher")]
+ public string Publisher { get; init; } = string.Empty;
+
+ [JsonPropertyName("task")]
+ public string Task { get; init; } = string.Empty;
+
+ [JsonPropertyName("runtime")]
+ public Runtime Runtime { get; init; } = default!;
+
+ [JsonPropertyName("fileSizeMb")]
+ public long FileSizeMb { get; init; }
+
+ [JsonPropertyName("modelSettings")]
+ public ModelSettings ModelSettings { get; init; } = default!;
+
+ [JsonPropertyName("alias")]
+ public string Alias { get; init; } = string.Empty;
+
+ [JsonPropertyName("supportsToolCalling")]
+ public bool SupportsToolCalling { get; init; }
+
+ [JsonPropertyName("license")]
+ public string License { get; init; } = string.Empty;
+
+ [JsonPropertyName("licenseDescription")]
+ public string LicenseDescription { get; init; } = string.Empty;
+
+ [JsonPropertyName("parentModelUri")]
+ public string ParentModelUri { get; init; } = string.Empty;
+}
diff --git a/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs b/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs
new file mode 100644
index 0000000000..a279f7389a
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs
@@ -0,0 +1,279 @@
+// 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 ManagedCommon;
+using Microsoft.AI.Foundry.Local;
+
+namespace LanguageModelProvider.FoundryLocal;
+
+internal sealed class FoundryClient
+{
+ public static async Task CreateAsync()
+ {
+ // First attempt with current environment
+ var client = await TryCreateClientAsync().ConfigureAwait(false);
+ if (client != null)
+ {
+ return client;
+ }
+
+ // If failed, refresh PATH from registry and retry once
+ // This handles cases where PowerToys was launched by MSI installer.
+ Logger.LogInfo("[FoundryClient] First attempt failed, refreshing PATH and retrying");
+ RefreshEnvironmentPath();
+
+ return await TryCreateClientAsync().ConfigureAwait(false);
+ }
+
+ private static async Task TryCreateClientAsync()
+ {
+ try
+ {
+ Logger.LogInfo("[FoundryClient] Creating Foundry Local client");
+
+ var manager = new FoundryLocalManager();
+
+ // Check if service is already running
+ if (manager.IsServiceRunning)
+ {
+ Logger.LogInfo("[FoundryClient] Foundry service is already running");
+ return new FoundryClient(manager);
+ }
+
+ // Start the service using SDK's method
+ Logger.LogInfo("[FoundryClient] Starting Foundry service using manager.StartServiceAsync()");
+ await manager.StartServiceAsync().ConfigureAwait(false);
+
+ Logger.LogInfo("[FoundryClient] Foundry service started successfully");
+ return new FoundryClient(manager);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"[FoundryClient] Error creating client: {ex.Message}");
+ if (ex.InnerException != null)
+ {
+ Logger.LogError($"[FoundryClient] Inner exception: {ex.InnerException.Message}");
+ }
+
+ return null;
+ }
+ }
+
+ private readonly FoundryLocalManager _foundryManager;
+ private readonly List _catalogModels = [];
+
+ private FoundryClient(FoundryLocalManager foundryManager)
+ {
+ _foundryManager = foundryManager;
+ }
+
+ public Task GetServiceUrl()
+ {
+ try
+ {
+ return Task.FromResult(_foundryManager.Endpoint?.ToString());
+ }
+ catch
+ {
+ return Task.FromResult(null);
+ }
+ }
+
+ public Uri? GetServiceUri()
+ {
+ try
+ {
+ return _foundryManager.ServiceUri;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ public async Task> ListCatalogModels()
+ {
+ if (_catalogModels.Count > 0)
+ {
+ return _catalogModels;
+ }
+
+ try
+ {
+ Logger.LogInfo("[FoundryClient] Listing catalog models");
+ var models = await _foundryManager.ListCatalogModelsAsync().ConfigureAwait(false);
+
+ if (models != null)
+ {
+ foreach (var model in models)
+ {
+ _catalogModels.Add(new FoundryCatalogModel
+ {
+ Name = model.ModelId ?? string.Empty,
+ DisplayName = model.DisplayName ?? string.Empty,
+ ProviderType = model.ProviderType ?? string.Empty,
+ Uri = model.Uri ?? string.Empty,
+ Version = model.Version ?? string.Empty,
+ ModelType = model.ModelType ?? string.Empty,
+ Publisher = model.Publisher ?? string.Empty,
+ Task = model.Task ?? string.Empty,
+ FileSizeMb = model.FileSizeMb,
+ Alias = model.Alias ?? string.Empty,
+ License = model.License ?? string.Empty,
+ LicenseDescription = model.LicenseDescription ?? string.Empty,
+ ParentModelUri = model.ParentModelUri ?? string.Empty,
+ SupportsToolCalling = model.SupportsToolCalling,
+ });
+ }
+
+ Logger.LogInfo($"[FoundryClient] Found {_catalogModels.Count} catalog models");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"[FoundryClient] Error listing catalog models: {ex.Message}");
+
+ // Surfacing errors here prevents listing other providers; swallow and return cached list instead.
+ }
+
+ return _catalogModels;
+ }
+
+ public async Task> ListCachedModels()
+ {
+ try
+ {
+ Logger.LogInfo("[FoundryClient] Listing cached models");
+ var cachedModels = await _foundryManager.ListCachedModelsAsync().ConfigureAwait(false);
+ var catalogModels = await ListCatalogModels().ConfigureAwait(false);
+
+ List models = [];
+
+ foreach (var model in cachedModels)
+ {
+ var catalogModel = catalogModels.FirstOrDefault(m => m.Name == model.ModelId);
+ var alias = catalogModel?.Alias ?? model.Alias;
+ models.Add(new FoundryCachedModel(model.ModelId ?? string.Empty, alias));
+ }
+
+ Logger.LogInfo($"[FoundryClient] Found {models.Count} cached models");
+ return models;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"[FoundryClient] Error listing cached models: {ex.Message}");
+ return [];
+ }
+ }
+
+ public async Task IsModelLoaded(string modelId)
+ {
+ try
+ {
+ var loadedModels = await _foundryManager.ListLoadedModelsAsync().ConfigureAwait(false);
+ var isLoaded = loadedModels.Any(m => m.ModelId == modelId);
+ Logger.LogInfo($"[FoundryClient] IsModelLoaded({modelId}): {isLoaded}");
+ Logger.LogInfo($"[FoundryClient] Loaded models: {string.Join(", ", loadedModels.Select(m => m.ModelId))}");
+ return isLoaded;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"[FoundryClient] IsModelLoaded exception: {ex.Message}");
+ return false;
+ }
+ }
+
+ public async Task EnsureModelLoaded(string modelId)
+ {
+ Logger.LogInfo($"[FoundryClient] EnsureModelLoaded called with: {modelId}");
+
+ // Check if already loaded
+ if (await IsModelLoaded(modelId).ConfigureAwait(false))
+ {
+ Logger.LogInfo($"[FoundryClient] Model already loaded: {modelId}");
+ return true;
+ }
+
+ // Load the model
+ Logger.LogInfo($"[FoundryClient] Loading model: {modelId}");
+ await _foundryManager.LoadModelAsync(modelId).ConfigureAwait(false);
+
+ // Verify it's loaded
+ var loaded = await IsModelLoaded(modelId).ConfigureAwait(false);
+ Logger.LogInfo($"[FoundryClient] Model load result: {loaded}");
+ return loaded;
+ }
+
+ public async Task EnsureRunning()
+ {
+ if (!_foundryManager.IsServiceRunning)
+ {
+ await _foundryManager.StartServiceAsync();
+ }
+ }
+
+ ///
+ /// Refreshes the PATH environment variable from the system registry.
+ /// This is necessary when tools are installed while PowerToys is running,
+ /// as the installer updates the system PATH but running processes don't see the change.
+ ///
+ private static void RefreshEnvironmentPath()
+ {
+ try
+ {
+ Logger.LogInfo("[FoundryClient] Refreshing PATH environment variable from system");
+
+ var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) ?? string.Empty;
+ var machinePath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? string.Empty;
+ var userPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty;
+
+ var pathsToAdd = new List();
+
+ if (!string.IsNullOrWhiteSpace(currentPath))
+ {
+ pathsToAdd.AddRange(currentPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries));
+ }
+
+ if (!string.IsNullOrWhiteSpace(userPath))
+ {
+ var userPaths = userPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var path in userPaths)
+ {
+ if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase))
+ {
+ pathsToAdd.Add(path);
+ }
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(machinePath))
+ {
+ var machinePaths = machinePath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var path in machinePaths)
+ {
+ if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase))
+ {
+ pathsToAdd.Add(path);
+ }
+ }
+ }
+
+ var newPath = string.Join(Path.PathSeparator.ToString(), pathsToAdd);
+
+ if (currentPath != newPath)
+ {
+ Logger.LogInfo("[FoundryClient] Updating process PATH with latest system values");
+ Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.Process);
+ }
+ else
+ {
+ Logger.LogInfo("[FoundryClient] PATH is already up to date");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"[FoundryClient] Failed to refresh PATH: {ex.Message}");
+ }
+ }
+}
diff --git a/src/common/LanguageModelProvider/FoundryLocal/FoundryJsonContext.cs b/src/common/LanguageModelProvider/FoundryLocal/FoundryJsonContext.cs
new file mode 100644
index 0000000000..5dcb4076ed
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/FoundryJsonContext.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace LanguageModelProvider.FoundryLocal;
+
+[JsonSourceGenerationOptions(
+ PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
+ WriteIndented = false)]
+[JsonSerializable(typeof(FoundryCatalogModel))]
+[JsonSerializable(typeof(List))]
+internal sealed partial class FoundryJsonContext : JsonSerializerContext
+{
+}
diff --git a/src/common/LanguageModelProvider/FoundryLocal/ModelSettings.cs b/src/common/LanguageModelProvider/FoundryLocal/ModelSettings.cs
new file mode 100644
index 0000000000..fda91217eb
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/ModelSettings.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace LanguageModelProvider.FoundryLocal;
+
+internal sealed record ModelSettings
+{
+ // The sample shows an empty array; keep it open-ended.
+ [JsonPropertyName("parameters")]
+ public List Parameters { get; init; } = [];
+}
diff --git a/src/common/LanguageModelProvider/FoundryLocal/PromptTemplate.cs b/src/common/LanguageModelProvider/FoundryLocal/PromptTemplate.cs
new file mode 100644
index 0000000000..a2cbb9fe45
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/PromptTemplate.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json.Serialization;
+
+namespace LanguageModelProvider.FoundryLocal;
+
+internal sealed record PromptTemplate
+{
+ [JsonPropertyName("assistant")]
+ public string Assistant { get; init; } = string.Empty;
+
+ [JsonPropertyName("prompt")]
+ public string Prompt { get; init; } = string.Empty;
+}
diff --git a/src/common/LanguageModelProvider/FoundryLocal/Runtime.cs b/src/common/LanguageModelProvider/FoundryLocal/Runtime.cs
new file mode 100644
index 0000000000..e2019c8f87
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocal/Runtime.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json.Serialization;
+
+namespace LanguageModelProvider.FoundryLocal;
+
+internal sealed record Runtime
+{
+ [JsonPropertyName("deviceType")]
+ public string DeviceType { get; init; } = string.Empty;
+
+ [JsonPropertyName("executionProvider")]
+ public string ExecutionProvider { get; init; } = string.Empty;
+}
diff --git a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs
new file mode 100644
index 0000000000..5158e4334e
--- /dev/null
+++ b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs
@@ -0,0 +1,156 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ClientModel;
+using LanguageModelProvider.FoundryLocal;
+using ManagedCommon;
+using Microsoft.Extensions.AI;
+using OpenAI;
+
+namespace LanguageModelProvider;
+
+public sealed class FoundryLocalModelProvider : ILanguageModelProvider
+{
+ private FoundryClient? _foundryClient;
+ private IEnumerable? _catalogModels;
+ private string? _serviceUrl;
+
+ public static FoundryLocalModelProvider Instance { get; } = new();
+
+ public string Name => "FoundryLocal";
+
+ public string ProviderDescription => "The model will run locally via Foundry Local";
+
+ public IChatClient? GetIChatClient(string modelId)
+ {
+ Logger.LogInfo($"[FoundryLocal] GetIChatClient called with url: {modelId}");
+ InitializeAsync().GetAwaiter().GetResult();
+
+ if (string.IsNullOrWhiteSpace(modelId))
+ {
+ Logger.LogError("[FoundryLocal] Model ID is empty after extraction");
+ return null;
+ }
+
+ // Check if model is in catalog
+ var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false;
+ if (!isInCatalog)
+ {
+ var errorMessage = $"{modelId} is not supported in Foundry Local. Please configure supported models in Settings.";
+ Logger.LogError($"[FoundryLocal] {errorMessage}");
+ throw new InvalidOperationException(errorMessage);
+ }
+
+ // Ensure the model is loaded before returning chat client
+ var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult();
+ if (!isLoaded)
+ {
+ Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}");
+ throw new InvalidOperationException($"Failed to load the model '{modelId}'.");
+ }
+
+ // Use ServiceUri instead of Endpoint since Endpoint already includes /v1
+ var baseUri = _foundryClient.GetServiceUri();
+ if (baseUri == null)
+ {
+ const string message = "Foundry Local service URL is not available. Please make sure Foundry Local is installed and running.";
+ Logger.LogError($"[FoundryLocal] {message}");
+ throw new InvalidOperationException(message);
+ }
+
+ var endpointUri = new Uri($"{baseUri.ToString().TrimEnd('/')}/v1");
+ Logger.LogInfo($"[FoundryLocal] Creating OpenAI client with endpoint: {endpointUri}");
+
+ return new OpenAIClient(
+ new ApiKeyCredential("none"),
+ new OpenAIClientOptions { Endpoint = endpointUri, NetworkTimeout = TimeSpan.FromMinutes(5) })
+ .GetChatClient(modelId)
+ .AsIChatClient();
+ }
+
+ public string GetIChatClientString(string url)
+ {
+ try
+ {
+ InitializeAsync().GetAwaiter().GetResult();
+ }
+ catch
+ {
+ return string.Empty;
+ }
+
+ var modelId = url.Split('/').LastOrDefault();
+
+ if (string.IsNullOrWhiteSpace(_serviceUrl) || string.IsNullOrWhiteSpace(modelId))
+ {
+ return string.Empty;
+ }
+
+ return $"new OpenAIClient(new ApiKeyCredential(\"none\"), new OpenAIClientOptions{{ Endpoint = new Uri(\"{_serviceUrl}/v1\") }}).GetChatClient(\"{modelId}\").AsIChatClient()";
+ }
+
+ public async Task> GetModelsAsync(CancellationToken cancelationToken = default)
+ {
+ await InitializeAsync(cancelationToken);
+
+ if (_foundryClient == null)
+ {
+ return Array.Empty();
+ }
+
+ var cachedModels = await _foundryClient.ListCachedModels();
+ List downloadedModels = [];
+
+ foreach (var model in cachedModels)
+ {
+ Logger.LogInfo($"[FoundryLocal] Adding unmatched cached model: {model.Name}");
+ downloadedModels.Add(new ModelDetails
+ {
+ Id = $"fl-{model.Name}",
+ Name = model.Name,
+ Url = $"fl://{model.Name}",
+ Description = $"{model.Name} running locally with Foundry Local",
+ HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL],
+ ProviderModelDetails = model,
+ });
+ }
+
+ return downloadedModels;
+ }
+
+ private async Task InitializeAsync(CancellationToken cancelationToken = default)
+ {
+ if (_foundryClient != null && _catalogModels != null && _catalogModels.Any())
+ {
+ await _foundryClient.EnsureRunning().ConfigureAwait(false);
+ return;
+ }
+
+ Logger.LogInfo("[FoundryLocal] Initializing provider");
+ _foundryClient ??= await FoundryClient.CreateAsync();
+
+ if (_foundryClient == null)
+ {
+ const string message = "Foundry Local client could not be created. Please make sure Foundry Local is installed and running.";
+ Logger.LogError($"[FoundryLocal] {message}");
+ throw new InvalidOperationException(message);
+ }
+
+ _serviceUrl ??= await _foundryClient.GetServiceUrl();
+ Logger.LogInfo($"[FoundryLocal] Service URL: {_serviceUrl}");
+
+ var catalogModels = await _foundryClient.ListCatalogModels();
+ Logger.LogInfo($"[FoundryLocal] Found {catalogModels.Count} catalog models");
+ _catalogModels = catalogModels;
+ }
+
+ public async Task IsAvailable()
+ {
+ Logger.LogInfo("[FoundryLocal] Checking availability");
+ await InitializeAsync();
+ var available = _foundryClient != null;
+ Logger.LogInfo($"[FoundryLocal] Available: {available}");
+ return available;
+ }
+}
diff --git a/src/common/LanguageModelProvider/HardwareAccelerator.cs b/src/common/LanguageModelProvider/HardwareAccelerator.cs
new file mode 100644
index 0000000000..d2c94b8155
--- /dev/null
+++ b/src/common/LanguageModelProvider/HardwareAccelerator.cs
@@ -0,0 +1,22 @@
+// 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 LanguageModelProvider;
+
+public enum HardwareAccelerator
+{
+ CPU,
+ DML,
+ QNN,
+ WCRAPI,
+ OLLAMA,
+ OPENAI,
+ FOUNDRYLOCAL,
+ LEMONADE,
+ NPU,
+ GPU,
+ VitisAI,
+ OpenVINO,
+ NvTensorRT,
+}
diff --git a/src/common/LanguageModelProvider/ILanguageModelProvider.cs b/src/common/LanguageModelProvider/ILanguageModelProvider.cs
new file mode 100644
index 0000000000..9d203adaf6
--- /dev/null
+++ b/src/common/LanguageModelProvider/ILanguageModelProvider.cs
@@ -0,0 +1,20 @@
+// 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.Extensions.AI;
+
+namespace LanguageModelProvider;
+
+public interface ILanguageModelProvider
+{
+ string Name { get; }
+
+ string ProviderDescription { get; }
+
+ Task> GetModelsAsync(CancellationToken cancelationToken = default);
+
+ IChatClient? GetIChatClient(string modelId);
+
+ string GetIChatClientString(string url);
+}
diff --git a/src/common/LanguageModelProvider/LanguageModelProvider.csproj b/src/common/LanguageModelProvider/LanguageModelProvider.csproj
new file mode 100644
index 0000000000..4dba9247a3
--- /dev/null
+++ b/src/common/LanguageModelProvider/LanguageModelProvider.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/common/LanguageModelProvider/ModelDetails.cs b/src/common/LanguageModelProvider/ModelDetails.cs
new file mode 100644
index 0000000000..e383aa7d27
--- /dev/null
+++ b/src/common/LanguageModelProvider/ModelDetails.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace LanguageModelProvider;
+
+public class ModelDetails
+{
+ public string Id { get; set; } = string.Empty;
+
+ public string Name { get; set; } = string.Empty;
+
+ public string Url { get; set; } = string.Empty;
+
+ public string Description { get; set; } = string.Empty;
+
+ public long Size { get; set; }
+
+ public bool IsUserAdded { get; set; }
+
+ public string Icon { get; set; } = string.Empty;
+
+ public List HardwareAccelerators { get; set; } = [];
+
+ public string License { get; set; } = string.Empty;
+
+ public object? ProviderModelDetails { get; set; }
+}
diff --git a/src/common/ManagedCommon/Logger.cs b/src/common/ManagedCommon/Logger.cs
index 11115b1846..7f72cdd78b 100644
--- a/src/common/ManagedCommon/Logger.cs
+++ b/src/common/ManagedCommon/Logger.cs
@@ -26,6 +26,21 @@ namespace ManagedCommon
private static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute()?.Version ?? "Unknown";
+ ///
+ /// Gets the path to the log directory for the current version of the app.
+ ///
+ public static string CurrentVersionLogDirectoryPath { get; private set; }
+
+ ///
+ /// Gets the path to the current log file.
+ ///
+ public static string CurrentLogFile { get; private set; }
+
+ ///
+ /// Gets the path to the log directory for the app.
+ ///
+ public static string AppLogDirectoryPath { get; private set; }
+
///
/// Initializes the logger and sets the path for logging.
///
@@ -42,7 +57,12 @@ namespace ManagedCommon
Directory.CreateDirectory(versionedPath);
}
- var logFilePath = Path.Combine(versionedPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
+ AppLogDirectoryPath = basePath;
+ CurrentVersionLogDirectoryPath = versionedPath;
+
+ var logFile = "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log";
+ var logFilePath = Path.Combine(versionedPath, logFile);
+ CurrentLogFile = logFilePath;
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
@@ -130,7 +150,7 @@ namespace ManagedCommon
{
exMessage +=
"Inner exception: " + Environment.NewLine +
- ex.InnerException.GetType() + " (" + ex.HResult + "): " + ex.InnerException.Message + Environment.NewLine;
+ ex.InnerException.GetType() + " (" + ex.InnerException.HResult + "): " + ex.InnerException.Message + Environment.NewLine;
}
exMessage +=
diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs
index aa741e2f3a..d7ae386191 100644
--- a/src/common/ManagedCommon/ModuleType.cs
+++ b/src/common/ManagedCommon/ModuleType.cs
@@ -12,6 +12,7 @@ namespace ManagedCommon
ColorPicker,
CmdPal,
CropAndLock,
+ CursorWrap,
EnvironmentVariables,
FancyZones,
FileLocksmith,
diff --git a/src/common/UITestAutomation/SettingsConfigHelper.cs b/src/common/UITestAutomation/SettingsConfigHelper.cs
new file mode 100644
index 0000000000..833ec4f19d
--- /dev/null
+++ b/src/common/UITestAutomation/SettingsConfigHelper.cs
@@ -0,0 +1,175 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+using Microsoft.PowerToys.Settings.UI.Library.Utilities;
+
+namespace Microsoft.PowerToys.UITest
+{
+ ///
+ /// Helper class for configuring PowerToys settings for UI tests.
+ ///
+ public class SettingsConfigHelper
+ {
+ private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
+ private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
+
+ ///
+ /// Configures global PowerToys settings to enable only specified modules and disable all others.
+ ///
+ /// Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled.
+ /// Thrown when modulesToEnable is null.
+ /// Thrown when settings file operations fail.
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
+ [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
+ public static void ConfigureGlobalModuleSettings(params string[] modulesToEnable)
+ {
+ ArgumentNullException.ThrowIfNull(modulesToEnable);
+
+ try
+ {
+ GeneralSettings settings;
+ try
+ {
+ settings = SettingsUtils.GetSettingsOrDefault();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Failed to load settings, creating defaults: {ex.Message}");
+ settings = new GeneralSettings();
+ }
+
+ string settingsJson = settings.ToJsonString();
+ using (JsonDocument doc = JsonDocument.Parse(settingsJson))
+ {
+ var options = new JsonSerializerOptions { WriteIndented = true };
+ var root = doc.RootElement.Clone();
+
+ if (root.TryGetProperty("enabled", out var enabledElement))
+ {
+ var enabledModules = new Dictionary();
+
+ foreach (var property in enabledElement.EnumerateObject())
+ {
+ string moduleName = property.Name;
+
+ bool shouldEnable = Array.Exists(modulesToEnable, m => string.Equals(m, moduleName, StringComparison.Ordinal));
+ enabledModules[moduleName] = shouldEnable;
+ }
+
+ var settingsDict = JsonSerializer.Deserialize>(settingsJson);
+ if (settingsDict != null)
+ {
+ settingsDict["enabled"] = enabledModules;
+ settingsJson = JsonSerializer.Serialize(settingsDict, IndentedJsonOptions);
+ }
+ }
+ }
+
+ SettingsUtils.SaveSettings(settingsJson);
+
+ string enabledList = modulesToEnable.Length > 0 ? string.Join(", ", modulesToEnable) : "none";
+ Debug.WriteLine($"Successfully updated global settings");
+ Debug.WriteLine($"Enabled modules: {enabledList}");
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"ERROR in ConfigureGlobalModuleSettings: {ex.Message}");
+ throw new InvalidOperationException($"Failed to configure global module settings: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// Updates a module's settings file. If the file doesn't exist, creates it with default content.
+ /// If the file exists, reads it and applies the provided update function to modify the settings.
+ ///
+ /// The name of the module (e.g., "Peek", "FancyZones").
+ /// The default JSON content to use if the settings file doesn't exist.
+ ///
+ /// A callback function that modifies the settings dictionary. The function receives the deserialized settings
+ /// and should modify it in-place. The function should accept a Dictionary<string, object> and not return a value.
+ /// Example: (settings) => { ((Dictionary<string, object>)settings["properties"])["SomeSetting"] = newValue; }
+ ///
+ /// Thrown when moduleName or updateSettingsAction is null.
+ /// Thrown when settings file operations fail.
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
+ [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
+ public static void UpdateModuleSettings(
+ string moduleName,
+ string defaultSettingsContent,
+ Action> updateSettingsAction)
+ {
+ ArgumentNullException.ThrowIfNull(moduleName);
+ ArgumentNullException.ThrowIfNull(updateSettingsAction);
+
+ try
+ {
+ // Build the path to the module settings file
+ string powerToysSettingsDirectory = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ "Microsoft",
+ "PowerToys");
+
+ string moduleDirectory = Path.Combine(powerToysSettingsDirectory, moduleName);
+ string settingsPath = Path.Combine(moduleDirectory, "settings.json");
+
+ // Ensure directory exists
+ Directory.CreateDirectory(moduleDirectory);
+
+ // Read existing settings or use default
+ string existingJson = string.Empty;
+ if (File.Exists(settingsPath))
+ {
+ existingJson = File.ReadAllText(settingsPath);
+ }
+
+ Dictionary? settings;
+
+ // If file doesn't exist or is empty, create from defaults
+ if (string.IsNullOrWhiteSpace(existingJson))
+ {
+ if (string.IsNullOrWhiteSpace(defaultSettingsContent))
+ {
+ throw new ArgumentException("Default settings content must be provided when file doesn't exist.", nameof(defaultSettingsContent));
+ }
+
+ settings = JsonSerializer.Deserialize>(defaultSettingsContent)
+ ?? throw new InvalidOperationException($"Failed to deserialize default settings for {moduleName}");
+
+ Debug.WriteLine($"Created default settings for {moduleName} at {settingsPath}");
+ }
+ else
+ {
+ // Parse existing settings
+ settings = JsonSerializer.Deserialize>(existingJson)
+ ?? throw new InvalidOperationException($"Failed to deserialize existing settings for {moduleName}");
+
+ Debug.WriteLine($"Loaded existing settings for {moduleName} from {settingsPath}");
+ }
+
+ // Apply the update action to modify settings
+ updateSettingsAction(settings);
+
+ // Serialize and save the updated settings using SettingsUtils
+ string updatedJson = JsonSerializer.Serialize(settings, IndentedJsonOptions);
+ SettingsUtils.SaveSettings(updatedJson, moduleName);
+
+ Debug.WriteLine($"Successfully updated settings for {moduleName}");
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"ERROR in UpdateModuleSettings for {moduleName}: {ex.Message}");
+ throw new InvalidOperationException($"Failed to update settings for {moduleName}: {ex.Message}", ex);
+ }
+ }
+ }
+}
diff --git a/src/common/UITestAutomation/UITestAutomation.csproj b/src/common/UITestAutomation/UITestAutomation.csproj
index add7acfeb9..549b8a430b 100644
--- a/src/common/UITestAutomation/UITestAutomation.csproj
+++ b/src/common/UITestAutomation/UITestAutomation.csproj
@@ -8,7 +8,7 @@
enable
true
true
- net9.0-windows10.0.22621.0
+ net9.0-windows10.0.26100.0
true
false
@@ -21,4 +21,8 @@
+
+
+
+
diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h
index b2e05fadfe..881633e05e 100644
--- a/src/common/logger/logger_settings.h
+++ b/src/common/logger/logger_settings.h
@@ -59,6 +59,7 @@ struct LogSettings
inline const static std::string mouseHighlighterLoggerName = "mouse-highlighter";
inline const static std::string mouseJumpLoggerName = "mouse-jump";
inline const static std::string mousePointerCrosshairsLoggerName = "mouse-pointer-crosshairs";
+ inline const static std::string cursorWrapLoggerName = "cursor-wrap";
inline const static std::string imageResizerLoggerName = "imageresizer";
inline const static std::string powerRenameLoggerName = "powerrename";
inline const static std::string alwaysOnTopLoggerName = "always-on-top";
diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h
index 471cefe480..ab71d09d0b 100644
--- a/src/common/utils/gpo.h
+++ b/src/common/utils/gpo.h
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
namespace powertoys_gpo
{
@@ -51,6 +52,7 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_HIGHLIGHTER = L"ConfigureEnabledUtilityMouseHighlighter";
const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_JUMP = L"ConfigureEnabledUtilityMouseJump";
const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_POINTER_CROSSHAIRS = L"ConfigureEnabledUtilityMousePointerCrosshairs";
+ const std::wstring POLICY_CONFIGURE_ENABLED_CURSOR_WRAP = L"ConfigureEnabledUtilityCursorWrap";
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_RENAME = L"ConfigureEnabledUtilityPowerRename";
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER = L"ConfigureEnabledUtilityPowerLauncher";
const std::wstring POLICY_CONFIGURE_ENABLED_QUICK_ACCENT = L"ConfigureEnabledUtilityQuickAccent";
@@ -82,6 +84,13 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_RUN_AT_STARTUP = L"ConfigureRunAtStartup";
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS = L"PowerLauncherAllPluginsEnabledState";
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS = L"AllowPowerToysAdvancedPasteOnlineAIModels";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_OPENAI = L"AllowAdvancedPasteOpenAI";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_AZURE_OPENAI = L"AllowAdvancedPasteAzureOpenAI";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_AZURE_AI_INFERENCE = L"AllowAdvancedPasteAzureAIInference";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_MISTRAL = L"AllowAdvancedPasteMistral";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_GOOGLE = L"AllowAdvancedPasteGoogle";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_OLLAMA = L"AllowAdvancedPasteOllama";
+ const std::wstring POLICY_ALLOW_ADVANCED_PASTE_FOUNDRY_LOCAL = L"AllowAdvancedPasteFoundryLocal";
const std::wstring POLICY_MWB_CLIPBOARD_SHARING_ENABLED = L"MwbClipboardSharingEnabled";
const std::wstring POLICY_MWB_FILE_TRANSFER_ENABLED = L"MwbFileTransferEnabled";
const std::wstring POLICY_MWB_USE_ORIGINAL_USER_INTERFACE = L"MwbUseOriginalUserInterface";
@@ -401,6 +410,11 @@ namespace powertoys_gpo
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_MOUSE_POINTER_CROSSHAIRS);
}
+ inline gpo_rule_configured_t getConfiguredCursorWrapEnabledValue()
+ {
+ return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_CURSOR_WRAP);
+ }
+
inline gpo_rule_configured_t getConfiguredPowerRenameEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_POWER_RENAME);
@@ -575,6 +589,41 @@ namespace powertoys_gpo
return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS);
}
+ inline gpo_rule_configured_t getAllowedAdvancedPasteOpenAIValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_OPENAI);
+ }
+
+ inline gpo_rule_configured_t getAllowedAdvancedPasteAzureOpenAIValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_AZURE_OPENAI);
+ }
+
+ inline gpo_rule_configured_t getAllowedAdvancedPasteAzureAIInferenceValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_AZURE_AI_INFERENCE);
+ }
+
+ inline gpo_rule_configured_t getAllowedAdvancedPasteMistralValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_MISTRAL);
+ }
+
+ inline gpo_rule_configured_t getAllowedAdvancedPasteGoogleValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_GOOGLE);
+ }
+
+ inline gpo_rule_configured_t getAllowedAdvancedPasteOllamaValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_OLLAMA);
+ }
+
+ inline gpo_rule_configured_t getAllowedAdvancedPasteFoundryLocalValue()
+ {
+ return getConfiguredValue(POLICY_ALLOW_ADVANCED_PASTE_FOUNDRY_LOCAL);
+ }
+
inline gpo_rule_configured_t getConfiguredMwbClipboardSharingEnabledValue()
{
return getConfiguredValue(POLICY_MWB_CLIPBOARD_SHARING_ENABLED);
diff --git a/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj b/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj
index 3186a01d43..b36e602d25 100644
--- a/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj
+++ b/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj
@@ -33,9 +33,4 @@
-
-
-
-
-
diff --git a/src/dsc/v3/PowerToys.DSC.UnitTests/SettingsResourceTests/SettingsResourceAdvancedPasteModuleTest.cs b/src/dsc/v3/PowerToys.DSC.UnitTests/SettingsResourceTests/SettingsResourceAdvancedPasteModuleTest.cs
index deae2eb832..45fd36d10e 100644
--- a/src/dsc/v3/PowerToys.DSC.UnitTests/SettingsResourceTests/SettingsResourceAdvancedPasteModuleTest.cs
+++ b/src/dsc/v3/PowerToys.DSC.UnitTests/SettingsResourceTests/SettingsResourceAdvancedPasteModuleTest.cs
@@ -23,7 +23,8 @@ public sealed class SettingsResourceAdvancedPasteModuleTest : SettingsResourceMo
{
s.Properties.ShowCustomPreview = !s.Properties.ShowCustomPreview;
s.Properties.CloseAfterLosingFocus = !s.Properties.CloseAfterLosingFocus;
- s.Properties.IsAdvancedAIEnabled = !s.Properties.IsAdvancedAIEnabled;
+
+ // s.Properties.IsAdvancedAIEnabled = !s.Properties.IsAdvancedAIEnabled;
s.Properties.AdvancedPasteUIShortcut = new HotkeySettings
{
Key = "mock",
diff --git a/src/dsc/v3/PowerToys.DSC/Models/DscManifest.cs b/src/dsc/v3/PowerToys.DSC/Models/DscManifest.cs
index dcb6abf4a1..5eb91acec3 100644
--- a/src/dsc/v3/PowerToys.DSC/Models/DscManifest.cs
+++ b/src/dsc/v3/PowerToys.DSC/Models/DscManifest.cs
@@ -13,7 +13,7 @@ namespace PowerToys.DSC.Models;
public sealed class DscManifest
{
private const string Schema = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json";
- private const string Executable = @"PowerToys.DSC.exe";
+ private const string Executable = @"..\PowerToys.DSC.exe";
private readonly string _type;
private readonly string _version;
diff --git a/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj b/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj
index 230cd4556b..9dc11a0a8a 100644
--- a/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj
+++ b/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj
@@ -40,9 +40,11 @@
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx
index 07d4f44bde..4b77a6783f 100644
--- a/src/gpo/assets/PowerToys.admx
+++ b/src/gpo/assets/PowerToys.admx
@@ -1,11 +1,11 @@
-
+
-
+
@@ -26,6 +26,7 @@
+
@@ -614,6 +615,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml
index 2703358bb0..1bfa55866d 100644
--- a/src/gpo/assets/en-US/PowerToys.adml
+++ b/src/gpo/assets/en-US/PowerToys.adml
@@ -1,7 +1,7 @@
-
+
PowerToys
PowerToys
@@ -33,6 +33,7 @@
PowerToys version 0.88.0 or later
PowerToys version 0.89.0 or later
PowerToys version 0.90.0 or later
+ PowerToys version 0.96.0 or later
From PowerToys version 0.64.0 until PowerToys version 0.87.1
This policy configures the enabled state for all PowerToys utilities.
@@ -291,6 +292,54 @@ If you don't configure this policy, the user will be able to control the setting
QOI file preview: Configure enabled state
QOI file thumbnail: Configure enabled state
Allow using online AI models
+ Advanced Paste: Allow OpenAI endpoint
+ This policy controls whether users can use the OpenAI endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use OpenAI as their AI provider.
+
+If you disable this policy, users will not be able to select or use OpenAI endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Azure OpenAI endpoint
+ This policy controls whether users can use the Azure OpenAI endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Azure OpenAI as their AI provider.
+
+If you disable this policy, users will not be able to select or use Azure OpenAI endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Azure AI Inference endpoint
+ This policy controls whether users can use the Azure AI Inference endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Azure AI Inference as their AI provider.
+
+If you disable this policy, users will not be able to select or use Azure AI Inference endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Mistral endpoint
+ This policy controls whether users can use the Mistral AI endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Mistral as their AI provider.
+
+If you disable this policy, users will not be able to select or use Mistral endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Google endpoint
+ This policy controls whether users can use the Google (Gemini) endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Google as their AI provider.
+
+If you disable this policy, users will not be able to select or use Google endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Anthropic endpoint
+ This policy controls whether users can use the Anthropic (Claude) endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Anthropic as their AI provider.
+
+If you disable this policy, users will not be able to select or use Anthropic endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Ollama endpoint
+ This policy controls whether users can use the Ollama local model endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Ollama as their AI provider.
+
+If you disable this policy, users will not be able to select or use Ollama endpoint in Advanced Paste settings.
+ Advanced Paste: Allow Foundry Local endpoint
+ This policy controls whether users can use the Foundry Local model endpoint in Advanced Paste.
+
+If you enable or don't configure this policy, users can configure and use Foundry Local as their AI provider.
+
+If you disable this policy, users will not be able to select or use Foundry Local endpoint in Advanced Paste settings.
Clipboard sharing enabled
File transfer enabled
Original user interface is available
diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ConvertersTests/HexColorToColorConverterTests.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ConvertersTests/HexColorToColorConverterTests.cs
new file mode 100644
index 0000000000..b8915f278e
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ConvertersTests/HexColorToColorConverterTests.cs
@@ -0,0 +1,56 @@
+// 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 AdvancedPaste.Converters;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Windows.UI;
+
+namespace AdvancedPaste.UnitTests.ConvertersTests;
+
+[TestClass]
+public sealed class HexColorToColorConverterTests
+{
+ [TestMethod]
+ public void TestConvert_ValidSixDigitHex_ReturnsColor()
+ {
+ Color? result = HexColorConverterHelper.ConvertHexColorToRgb("#FFBFAB");
+ Assert.IsNotNull(result);
+
+ var color = (Windows.UI.Color)result;
+ Assert.AreEqual(255, color.R);
+ Assert.AreEqual(191, color.G);
+ Assert.AreEqual(171, color.B);
+ Assert.AreEqual(255, color.A);
+ }
+
+ [TestMethod]
+ public void TestConvert_ValidThreeDigitHex_ReturnsColor()
+ {
+ Color? result = HexColorConverterHelper.ConvertHexColorToRgb("#abc");
+ Assert.IsNotNull(result);
+
+ var color = (Windows.UI.Color)result;
+
+ // #abc should expand to #aabbcc
+ Assert.AreEqual(170, color.R); // 0xaa
+ Assert.AreEqual(187, color.G); // 0xbb
+ Assert.AreEqual(204, color.B); // 0xcc
+ Assert.AreEqual(255, color.A);
+ }
+
+ [TestMethod]
+ public void TestConvert_NullOrEmpty_ReturnsNull()
+ {
+ Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb(null));
+ Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb(string.Empty));
+ Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb(" "));
+ }
+
+ [TestMethod]
+ public void TestConvert_InvalidHex_ReturnsNull()
+ {
+ Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb("#GGGGGG"));
+ Assert.IsNull(HexColorConverterHelper.ConvertHexColorToRgb("#12345"));
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/HelpersTests/ClipboardItemHelperTests.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/HelpersTests/ClipboardItemHelperTests.cs
new file mode 100644
index 0000000000..2b2a2c7595
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/HelpersTests/ClipboardItemHelperTests.cs
@@ -0,0 +1,36 @@
+// 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 AdvancedPaste.Helpers;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace AdvancedPaste.UnitTests.HelpersTests;
+
+[TestClass]
+public sealed class ClipboardItemHelperTests
+{
+ [TestMethod]
+ [DataRow("#FFBFAB", true)]
+ [DataRow("#000000", true)]
+ [DataRow("#FFFFFF", true)]
+ [DataRow("#fff", true)]
+ [DataRow("#abc", true)]
+ [DataRow("#123456", true)]
+ [DataRow("#AbCdEf", true)]
+ [DataRow("FFBFAB", false)] // Missing #
+ [DataRow("#GGGGGG", false)] // Invalid hex characters
+ [DataRow("#12345", false)] // Wrong length
+ [DataRow("#1234567", false)] // Too long
+ [DataRow("", false)]
+ [DataRow(null, false)]
+ [DataRow(" #FFF ", true)] // Whitespace should be trimmed
+ [DataRow("Not a color", false)]
+ [DataRow("#", false)]
+ [DataRow("##FFFFFF", false)]
+ public void TestIsRgbHexColor(string input, bool expected)
+ {
+ bool result = ClipboardItemHelper.IsRgbHexColor(input);
+ Assert.AreEqual(expected, result, $"IsRgbHexColor(\"{input}\") should return {expected}");
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs
new file mode 100644
index 0000000000..4446e24dde
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using AdvancedPaste.Models;
+using AdvancedPaste.Settings;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace AdvancedPaste.UnitTests.Mocks;
+
+///
+/// Minimal implementation used by integration tests that
+/// need to construct the runtime Advanced Paste services.
+///
+internal sealed class IntegrationTestUserSettings : IUserSettings
+{
+ private readonly PasteAIConfiguration _configuration;
+ private readonly IReadOnlyList _customActions;
+ private readonly IReadOnlyList _additionalActions;
+
+ public IntegrationTestUserSettings()
+ {
+ var provider = new PasteAIProviderDefinition
+ {
+ Id = "integration-openai",
+ EnableAdvancedAI = true,
+ ServiceTypeKind = AIServiceType.OpenAI,
+ ModelName = "gpt-4o",
+ ModerationEnabled = true,
+ };
+
+ _configuration = new PasteAIConfiguration
+ {
+ ActiveProviderId = provider.Id,
+ Providers = new ObservableCollection { provider },
+ };
+
+ _customActions = Array.Empty();
+ _additionalActions = Array.Empty();
+ }
+
+ public bool IsAIEnabled => true;
+
+ public bool ShowCustomPreview => false;
+
+ public bool CloseAfterLosingFocus => false;
+
+ public bool EnableClipboardPreview => true;
+
+ public IReadOnlyList CustomActions => _customActions;
+
+ public IReadOnlyList AdditionalActions => _additionalActions;
+
+ public PasteAIConfiguration PasteAIConfiguration => _configuration;
+
+ public event EventHandler Changed;
+
+ public Task SetActiveAIProviderAsync(string providerId)
+ {
+ _configuration.ActiveProviderId = providerId ?? string.Empty;
+ Changed?.Invoke(this, EventArgs.Empty);
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs
index 3782b057f1..17b8139bad 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs
@@ -13,6 +13,8 @@ using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
+using AdvancedPaste.Services;
+using AdvancedPaste.Services.CustomActions;
using AdvancedPaste.Services.OpenAI;
using AdvancedPaste.UnitTests.Mocks;
using ManagedCommon;
@@ -79,7 +81,9 @@ public sealed class AIServiceBatchIntegrationTests
Assert.IsTrue(results.Count <= inputs.Count);
CollectionAssert.AreEqual(results.Select(result => result.ToInput()).ToList(), inputs.Take(results.Count).ToList());
+ #pragma warning disable IL2026, IL3050 // The tests rely on runtime JSON serialization for ad-hoc data files.
async Task WriteResultsAsync() => await File.WriteAllTextAsync(resultsFile, JsonSerializer.Serialize(results, SerializerOptions));
+ #pragma warning restore IL2026, IL3050
Logger.LogInfo($"Starting {nameof(TestGenerateBatchResults)}; Count={inputs.Count}, InCache={results.Count}");
@@ -101,8 +105,12 @@ public sealed class AIServiceBatchIntegrationTests
await WriteResultsAsync();
}
- private static async Task> GetDataListAsync(string filePath) =>
- File.Exists(filePath) ? JsonSerializer.Deserialize>(await File.ReadAllTextAsync(filePath)) : [];
+ private static async Task> GetDataListAsync(string filePath)
+ {
+ #pragma warning disable IL2026, IL3050 // Tests only run locally and can depend on runtime JSON serialization.
+ return File.Exists(filePath) ? JsonSerializer.Deserialize>(await File.ReadAllTextAsync(filePath)) : [];
+ #pragma warning restore IL2026, IL3050
+ }
private static async Task GetTextOutputAsync(BatchTestInput input, PasteFormats format)
{
@@ -130,23 +138,35 @@ public sealed class AIServiceBatchIntegrationTests
private static async Task GetOutputDataPackageAsync(BatchTestInput batchTestInput, PasteFormats format)
{
- VaultCredentialsProvider credentialsProvider = new();
- PromptModerationService promptModerationService = new(credentialsProvider);
+ var services = CreateServices();
NoOpProgress progress = new();
- CustomTextTransformService customTextTransformService = new(credentialsProvider, promptModerationService);
switch (format)
{
case PasteFormats.CustomTextTransformation:
- return DataPackageHelpers.CreateFromText(await customTextTransformService.TransformTextAsync(batchTestInput.Prompt, batchTestInput.Clipboard, CancellationToken.None, progress));
+ var transformResult = await services.CustomActionTransformService.TransformTextAsync(batchTestInput.Prompt, batchTestInput.Clipboard, CancellationToken.None, progress);
+ return DataPackageHelpers.CreateFromText(transformResult.Content ?? string.Empty);
case PasteFormats.KernelQuery:
var clipboardData = DataPackageHelpers.CreateFromText(batchTestInput.Clipboard).GetView();
- KernelService kernelService = new(new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, customTextTransformService);
- return await kernelService.TransformClipboardAsync(batchTestInput.Prompt, clipboardData, isSavedQuery: false, CancellationToken.None, progress);
+ return await services.KernelService.TransformClipboardAsync(batchTestInput.Prompt, clipboardData, isSavedQuery: false, CancellationToken.None, progress);
default:
throw new InvalidOperationException($"Unexpected format {format}");
}
}
+
+ private static IntegrationTestServices CreateServices()
+ {
+ IntegrationTestUserSettings userSettings = new();
+ EnhancedVaultCredentialsProvider credentialsProvider = new(userSettings);
+ PromptModerationService promptModerationService = new(credentialsProvider);
+ PasteAIProviderFactory providerFactory = new();
+ ICustomActionTransformService customActionTransformService = new CustomActionTransformService(promptModerationService, providerFactory, credentialsProvider, userSettings);
+ IKernelService kernelService = new AdvancedAIKernelService(credentialsProvider, new NoOpKernelQueryCacheService(), promptModerationService, userSettings, customActionTransformService);
+
+ return new IntegrationTestServices(customActionTransformService, kernelService);
+ }
+
+ private readonly record struct IntegrationTestServices(ICustomActionTransformService CustomActionTransformService, IKernelService KernelService);
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/KernelServiceIntegrationTests.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/KernelServiceIntegrationTests.cs
index 998534cf5e..7c16089cd5 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/KernelServiceIntegrationTests.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/KernelServiceIntegrationTests.cs
@@ -11,6 +11,8 @@ using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
+using AdvancedPaste.Services;
+using AdvancedPaste.Services.CustomActions;
using AdvancedPaste.Services.OpenAI;
using AdvancedPaste.Telemetry;
using AdvancedPaste.UnitTests.Mocks;
@@ -27,16 +29,19 @@ namespace AdvancedPaste.UnitTests.ServicesTests;
public sealed class KernelServiceIntegrationTests : IDisposable
{
private const string StandardImageFile = "image_with_text_example.png";
- private KernelService _kernelService;
+ private IKernelService _kernelService;
private AdvancedPasteEventListener _eventListener;
[TestInitialize]
public void TestInitialize()
{
- VaultCredentialsProvider credentialsProvider = new();
+ IntegrationTestUserSettings userSettings = new();
+ EnhancedVaultCredentialsProvider credentialsProvider = new(userSettings);
PromptModerationService promptModerationService = new(credentialsProvider);
+ PasteAIProviderFactory providerFactory = new();
+ CustomActionTransformService customActionTransformService = new(promptModerationService, providerFactory, credentialsProvider, userSettings);
- _kernelService = new KernelService(new NoOpKernelQueryCacheService(), credentialsProvider, promptModerationService, new CustomTextTransformService(credentialsProvider, promptModerationService));
+ _kernelService = new AdvancedAIKernelService(credentialsProvider, new NoOpKernelQueryCacheService(), promptModerationService, userSettings, customActionTransformService);
_eventListener = new();
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj
index fba18de07c..1c80479c2d 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj
@@ -33,11 +33,14 @@
+
+
+
@@ -49,7 +52,6 @@
-
@@ -57,10 +59,15 @@
+
+
+
+
+
@@ -102,6 +109,7 @@
+
@@ -114,9 +122,38 @@
true
+
+
+ MSBuild:Compile
+
+
MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
+
+
+ Assets\Settings\Icons\Models\%(Filename)%(Extension)
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml
index da79c36a11..df6ed811ac 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml
@@ -9,6 +9,7 @@
+
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs
index 3ac3baa9d0..3fa940952e 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs
@@ -10,10 +10,10 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
-
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
using AdvancedPaste.Services;
+using AdvancedPaste.Services.CustomActions;
using AdvancedPaste.Settings;
using AdvancedPaste.ViewModels;
using ManagedCommon;
@@ -77,11 +77,12 @@ namespace AdvancedPaste
{
services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
}).Build();
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml
index f03a579821..a250fdffdc 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml
new file mode 100644
index 0000000000..996f0c5b4f
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml.cs
new file mode 100644
index 0000000000..765ba0e076
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using AdvancedPaste.Helpers;
+using AdvancedPaste.Models;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+
+namespace AdvancedPaste.Controls
+{
+ public sealed partial class ClipboardHistoryItemPreviewControl : UserControl
+ {
+ public static readonly DependencyProperty ClipboardItemProperty = DependencyProperty.Register(
+ nameof(ClipboardItem),
+ typeof(ClipboardItem),
+ typeof(ClipboardHistoryItemPreviewControl),
+ new PropertyMetadata(defaultValue: null, OnClipboardItemChanged));
+
+ public ClipboardItem ClipboardItem
+ {
+ get => (ClipboardItem)GetValue(ClipboardItemProperty);
+ set => SetValue(ClipboardItemProperty, value);
+ }
+
+ // Computed properties for display
+ public string Header => ClipboardItem != null ? GetHeaderFromFormat(ClipboardItem.Format) : string.Empty;
+
+ public string IconGlyph => ClipboardItem != null ? GetGlyphFromFormat(ClipboardItem.Format) : string.Empty;
+
+ public string ContentText => ClipboardItem?.Content ?? string.Empty;
+
+ public ImageSource ContentImage => ClipboardItem?.Image;
+
+ public DateTimeOffset? Timestamp => ClipboardItem?.Timestamp ?? ClipboardItem?.Item?.Timestamp;
+
+ public bool HasImage => ContentImage is not null;
+
+ public bool HasText => !string.IsNullOrEmpty(ContentText) && !HasImage && !HasColor;
+
+ public bool HasGlyph => !HasImage && !HasText && !HasColor && !string.IsNullOrEmpty(IconGlyph);
+
+ public bool HasColor => ClipboardItemHelper.IsRgbHexColor(ContentText);
+
+ public ClipboardHistoryItemPreviewControl()
+ {
+ InitializeComponent();
+ }
+
+ private static void OnClipboardItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ClipboardHistoryItemPreviewControl control)
+ {
+ // Notify bindings that all computed properties may have changed
+ control.Bindings.Update();
+ }
+ }
+
+ private static string GetHeaderFromFormat(ClipboardFormat format)
+ {
+ // Check flags in priority order (most specific first)
+ if (format.HasFlag(ClipboardFormat.Image))
+ {
+ return GetStringOrFallback("ClipboardPreviewCategoryImage", "Image");
+ }
+
+ if (format.HasFlag(ClipboardFormat.Video))
+ {
+ return GetStringOrFallback("ClipboardPreviewCategoryVideo", "Video");
+ }
+
+ if (format.HasFlag(ClipboardFormat.Audio))
+ {
+ return GetStringOrFallback("ClipboardPreviewCategoryAudio", "Audio");
+ }
+
+ if (format.HasFlag(ClipboardFormat.File))
+ {
+ return GetStringOrFallback("ClipboardPreviewCategoryFile", "File");
+ }
+
+ if (format.HasFlag(ClipboardFormat.Text) || format.HasFlag(ClipboardFormat.Html))
+ {
+ return GetStringOrFallback("ClipboardPreviewCategoryText", "Text");
+ }
+
+ return GetStringOrFallback("ClipboardPreviewCategoryUnknown", "Clipboard");
+ }
+
+ private static string GetGlyphFromFormat(ClipboardFormat format)
+ {
+ // Check flags in priority order (most specific first)
+ if (format.HasFlag(ClipboardFormat.Image))
+ {
+ return "\uEB9F"; // Image icon
+ }
+
+ if (format.HasFlag(ClipboardFormat.Video))
+ {
+ return "\uE714"; // Video icon
+ }
+
+ if (format.HasFlag(ClipboardFormat.Audio))
+ {
+ return "\uE189"; // Audio icon
+ }
+
+ if (format.HasFlag(ClipboardFormat.File))
+ {
+ return "\uE8A5"; // File icon
+ }
+
+ if (format.HasFlag(ClipboardFormat.Text) || format.HasFlag(ClipboardFormat.Html))
+ {
+ return "\uE8D2"; // Text icon
+ }
+
+ return "\uE77B"; // Generic clipboard icon
+ }
+
+ private static string GetStringOrFallback(string resourceKey, string fallback)
+ {
+ var value = ResourceLoaderInstance.ResourceLoader.GetString(resourceKey);
+ return string.IsNullOrEmpty(value) ? fallback : value;
+ }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml
index dd09c717b0..6303564d9b 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml
+++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml
@@ -7,34 +7,21 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:AdvancedPaste.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:settings="using:Microsoft.PowerToys.Settings.UI.Library"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
+ x:Name="PromptBoxControl"
mc:Ignorable="d">
-
-
- #65C8F2
-
-
-
-
-
-
-
- #005FB8
-
-
-
-
-
-
-
- #48B1E9
-
-
-
+
+
+
+
+
+
+ 44
+
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png
index 73621edfc0..78a9a18606 100644
Binary files a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png differ
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceFormatEvent.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceFormatEvent.cs
index 1ab58bf269..b74192213b 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceFormatEvent.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceFormatEvent.cs
@@ -18,6 +18,7 @@ namespace AdvancedPaste.Helpers
PromptTokens = semanticKernelFormatEvent.PromptTokens;
CompletionTokens = semanticKernelFormatEvent.CompletionTokens;
ModelName = semanticKernelFormatEvent.ModelName;
+ ProviderType = semanticKernelFormatEvent.ProviderType;
ActionChain = semanticKernelFormatEvent.ActionChain;
}
@@ -38,6 +39,8 @@ namespace AdvancedPaste.Helpers
public string ModelName { get; set; }
+ public string ProviderType { get; set; }
+
public string ActionChain { get; set; }
public string ToJsonString() => JsonSerializer.Serialize(this, SourceGenerationContext.Default.AIServiceFormatEvent);
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceUsageHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceUsageHelper.cs
new file mode 100644
index 0000000000..ba7d33f4fb
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AIServiceUsageHelper.cs
@@ -0,0 +1,54 @@
+// 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 AdvancedPaste.Models;
+using Microsoft.SemanticKernel;
+
+namespace AdvancedPaste.Helpers;
+
+///
+/// Helper class for extracting AI service usage information from chat messages.
+///
+public static class AIServiceUsageHelper
+{
+ ///
+ /// Extracts AI service usage information from OpenAI chat message metadata.
+ ///
+ /// The chat message containing usage metadata.
+ /// AI service usage information or AIServiceUsage.None if extraction fails.
+ public static AIServiceUsage GetOpenAIServiceUsage(ChatMessageContent chatMessage)
+ {
+ // Try to get usage information from metadata
+ if (chatMessage.Metadata?.TryGetValue("Usage", out var usageObj) == true)
+ {
+ // Handle different possible usage types through reflection to be version-agnostic
+ var usageType = usageObj.GetType();
+
+ try
+ {
+ // Try common property names for prompt tokens
+ var promptTokensProp = usageType.GetProperty("PromptTokens") ??
+ usageType.GetProperty("InputTokens") ??
+ usageType.GetProperty("InputTokenCount");
+
+ var completionTokensProp = usageType.GetProperty("CompletionTokens") ??
+ usageType.GetProperty("OutputTokens") ??
+ usageType.GetProperty("OutputTokenCount");
+
+ if (promptTokensProp != null && completionTokensProp != null)
+ {
+ var promptTokens = (int)(promptTokensProp.GetValue(usageObj) ?? 0);
+ var completionTokens = (int)(completionTokensProp.GetValue(usageObj) ?? 0);
+ return new AIServiceUsage(promptTokens, completionTokens);
+ }
+ }
+ catch
+ {
+ // If reflection fails, fall back to no usage
+ }
+ }
+
+ return AIServiceUsage.None;
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/ClipboardItemHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/ClipboardItemHelper.cs
new file mode 100644
index 0000000000..e4e18338c9
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/ClipboardItemHelper.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using AdvancedPaste.Models;
+using Microsoft.UI.Xaml.Media.Imaging;
+using Windows.ApplicationModel.DataTransfer;
+
+namespace AdvancedPaste.Helpers
+{
+ internal static partial class ClipboardItemHelper
+ {
+ // Compiled regex for better performance when checking multiple clipboard items
+ private static readonly Regex HexColorRegex = HexColorCompiledRegex();
+
+ ///
+ /// Creates a ClipboardItem from current clipboard data.
+ ///
+ public static async Task CreateFromCurrentClipboardAsync(
+ DataPackageView clipboardData,
+ ClipboardFormat availableFormats,
+ DateTimeOffset? timestamp = null,
+ BitmapImage existingImage = null)
+ {
+ if (clipboardData == null || availableFormats == ClipboardFormat.None)
+ {
+ return null;
+ }
+
+ var clipboardItem = new ClipboardItem
+ {
+ Format = availableFormats,
+ Timestamp = timestamp,
+ };
+
+ // Text or HTML content
+ if (availableFormats.HasFlag(ClipboardFormat.Text) || availableFormats.HasFlag(ClipboardFormat.Html))
+ {
+ clipboardItem.Content = await clipboardData.GetTextOrEmptyAsync();
+ }
+
+ // Image content
+ else if (availableFormats.HasFlag(ClipboardFormat.Image))
+ {
+ // Reuse existing image if provided
+ if (existingImage != null)
+ {
+ clipboardItem.Image = existingImage;
+ }
+ else
+ {
+ clipboardItem.Image = await TryCreateBitmapImageAsync(clipboardData);
+ }
+ }
+
+ return clipboardItem;
+ }
+
+ ///
+ /// Checks if text is a valid RGB hex color (e.g., #FFBFAB or #fff).
+ ///
+ public static bool IsRgbHexColor(string text)
+ {
+ if (text == null)
+ {
+ return false;
+ }
+
+ string trimmedText = text.Trim();
+ if (trimmedText.Length > 7)
+ {
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(trimmedText))
+ {
+ return false;
+ }
+
+ // Match #RGB or #RRGGBB format (case-insensitive)
+ return HexColorRegex.IsMatch(trimmedText);
+ }
+
+ ///
+ /// Creates a BitmapImage from clipboard data.
+ ///
+ private static async Task TryCreateBitmapImageAsync(DataPackageView clipboardData)
+ {
+ try
+ {
+ var imageReference = await clipboardData.GetBitmapAsync();
+ if (imageReference != null)
+ {
+ using (var imageStream = await imageReference.OpenReadAsync())
+ {
+ var bitmapImage = new BitmapImage();
+ await bitmapImage.SetSourceAsync(imageStream);
+ return bitmapImage;
+ }
+ }
+ }
+ catch
+ {
+ // Silently fail - caller can check for null
+ }
+
+ return null;
+ }
+
+ [GeneratedRegex(@"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$")]
+ private static partial Regex HexColorCompiledRegex();
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs
index 529773f9a6..2cd7554a50 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs
@@ -6,11 +6,13 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
-
using AdvancedPaste.Models;
using ManagedCommon;
+using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.Win32;
using Windows.ApplicationModel.DataTransfer;
using Windows.Data.Html;
@@ -180,6 +182,46 @@ internal static class DataPackageHelpers
}
}
+ internal static async Task GetClipboardTextOrThrowAsync(this DataPackageView dataPackageView, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(dataPackageView);
+
+ try
+ {
+ if (dataPackageView.Contains(StandardDataFormats.Text))
+ {
+ return await dataPackageView.GetTextAsync();
+ }
+
+ if (dataPackageView.Contains(StandardDataFormats.Html))
+ {
+ var html = await dataPackageView.GetHtmlFormatAsync();
+ return HtmlUtilities.ConvertToText(html);
+ }
+
+ if (dataPackageView.Contains(StandardDataFormats.Bitmap))
+ {
+ var bitmap = await dataPackageView.GetImageContentAsync();
+ if (bitmap != null)
+ {
+ return await OcrHelpers.ExtractTextAsync(bitmap, cancellationToken);
+ }
+ }
+ }
+ catch (Exception ex) when (ex is COMException or InvalidOperationException)
+ {
+ throw CreateClipboardTextMissingException(ex);
+ }
+
+ throw CreateClipboardTextMissingException();
+ }
+
+ private static PasteActionException CreateClipboardTextMissingException(Exception innerException = null)
+ {
+ var message = ResourceLoaderInstance.ResourceLoader.GetString("ClipboardEmptyWarning");
+ return new PasteActionException(message, innerException ?? new InvalidOperationException("Clipboard does not contain text content."));
+ }
+
internal static async Task GetHtmlContentAsync(this DataPackageView dataPackageView) =>
dataPackageView.Contains(StandardDataFormats.Html) ? await dataPackageView.GetHtmlFormatAsync() : string.Empty;
@@ -195,6 +237,22 @@ internal static class DataPackageHelpers
return null;
}
+ internal static async Task GetPreviewBitmapAsync(this DataPackageView dataPackageView)
+ {
+ var stream = await dataPackageView.GetImageStreamAsync();
+ if (stream == null)
+ {
+ return null;
+ }
+
+ using (stream)
+ {
+ var bitmapImage = new BitmapImage();
+ bitmapImage.SetSource(stream);
+ return bitmapImage;
+ }
+ }
+
private static async Task GetImageStreamAsync(this DataPackageView dataPackageView)
{
if (dataPackageView.Contains(StandardDataFormats.StorageItems))
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs
index 105fe2c0d8..d692263dc1 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using AdvancedPaste.Models;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -12,16 +13,22 @@ namespace AdvancedPaste.Settings
{
public interface IUserSettings
{
- public bool IsAdvancedAIEnabled { get; }
+ public bool IsAIEnabled { get; }
public bool ShowCustomPreview { get; }
public bool CloseAfterLosingFocus { get; }
+ public bool EnableClipboardPreview { get; }
+
public IReadOnlyList CustomActions { get; }
public IReadOnlyList AdditionalActions { get; }
+ public PasteAIConfiguration PasteAIConfiguration { get; }
+
public event EventHandler Changed;
+
+ Task SetActiveAIProviderAsync(string providerId);
}
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
index 8a25b70f07..59f31f0e99 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
@@ -13,6 +13,7 @@ using AdvancedPaste.Models;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
+using Windows.Security.Credentials;
namespace AdvancedPaste.Settings
{
@@ -33,23 +34,29 @@ namespace AdvancedPaste.Settings
public event EventHandler Changed;
- public bool IsAdvancedAIEnabled { get; private set; }
+ public bool IsAIEnabled { get; private set; }
public bool ShowCustomPreview { get; private set; }
public bool CloseAfterLosingFocus { get; private set; }
+ public bool EnableClipboardPreview { get; private set; }
+
public IReadOnlyList AdditionalActions => _additionalActions;
public IReadOnlyList CustomActions => _customActions;
+ public PasteAIConfiguration PasteAIConfiguration { get; private set; }
+
public UserSettings(IFileSystem fileSystem)
{
_settingsUtils = new SettingsUtils(fileSystem);
- IsAdvancedAIEnabled = false;
+ IsAIEnabled = false;
ShowCustomPreview = true;
CloseAfterLosingFocus = false;
+ EnableClipboardPreview = true;
+ PasteAIConfiguration = new PasteAIConfiguration();
_additionalActions = [];
_customActions = [];
_taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
@@ -94,13 +101,17 @@ namespace AdvancedPaste.Settings
var settings = _settingsUtils.GetSettingsOrDefault(AdvancedPasteModuleName);
if (settings != null)
{
+ bool migratedLegacyEnablement = TryMigrateLegacyAIEnablement(settings);
+
void UpdateSettings()
{
var properties = settings.Properties;
- IsAdvancedAIEnabled = properties.IsAdvancedAIEnabled;
+ IsAIEnabled = properties.IsAIEnabled;
ShowCustomPreview = properties.ShowCustomPreview;
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
+ EnableClipboardPreview = properties.EnableClipboardPreview;
+ PasteAIConfiguration = properties.PasteAIConfiguration ?? new PasteAIConfiguration();
var sourceAdditionalActions = properties.AdditionalActions;
(PasteFormats Format, IAdvancedPasteAction[] Actions)[] additionalActionFormats =
@@ -126,6 +137,11 @@ namespace AdvancedPaste.Settings
Task.Factory
.StartNew(UpdateSettings, CancellationToken.None, TaskCreationOptions.None, _taskScheduler)
.Wait();
+
+ if (migratedLegacyEnablement)
+ {
+ settings.Save(_settingsUtils);
+ }
}
retry = false;
@@ -144,6 +160,220 @@ namespace AdvancedPaste.Settings
}
}
+ private static bool TryMigrateLegacyAIEnablement(AdvancedPasteSettings settings)
+ {
+ if (settings?.Properties is null)
+ {
+ return false;
+ }
+
+ var properties = settings.Properties;
+ bool legacyAdvancedAIConsumed = properties.TryConsumeLegacyAdvancedAIEnabled(out var advancedFlag);
+ bool legacyAdvancedAIEnabled = legacyAdvancedAIConsumed && advancedFlag;
+ PasswordCredential legacyCredential = TryGetLegacyOpenAICredential();
+
+ if (legacyCredential is null)
+ {
+ return legacyAdvancedAIConsumed;
+ }
+
+ var configuration = properties.PasteAIConfiguration;
+
+ if (configuration is null)
+ {
+ configuration = new PasteAIConfiguration();
+ properties.PasteAIConfiguration = configuration;
+ }
+
+ bool configurationUpdated = false;
+
+ var ensureResult = AdvancedPasteMigrationHelper.EnsureOpenAIProvider(configuration);
+ PasteAIProviderDefinition openAIProvider = ensureResult.Provider;
+ configurationUpdated |= ensureResult.Updated;
+
+ if (legacyAdvancedAIConsumed && openAIProvider is not null && openAIProvider.EnableAdvancedAI != legacyAdvancedAIEnabled)
+ {
+ openAIProvider.EnableAdvancedAI = legacyAdvancedAIEnabled;
+ configurationUpdated = true;
+ }
+
+ if (openAIProvider is not null)
+ {
+ StoreMigratedOpenAICredential(openAIProvider.Id, openAIProvider.ServiceType, legacyCredential.Password);
+ RemoveLegacyOpenAICredential();
+ }
+
+ const bool shouldEnableAI = true;
+ bool enabledUpdated = false;
+ if (properties.IsAIEnabled != shouldEnableAI)
+ {
+ properties.IsAIEnabled = shouldEnableAI;
+ enabledUpdated = true;
+ }
+
+ return configurationUpdated || enabledUpdated || legacyAdvancedAIConsumed;
+ }
+
+ private static PasswordCredential TryGetLegacyOpenAICredential()
+ {
+ try
+ {
+ PasswordVault vault = new();
+ var credential = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
+ credential?.RetrievePassword();
+ return credential;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+ private static void RemoveLegacyOpenAICredential()
+ {
+ try
+ {
+ PasswordVault vault = new();
+ TryRemoveCredential(vault, "https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ private static void StoreMigratedOpenAICredential(string providerId, string serviceType, string password)
+ {
+ if (string.IsNullOrWhiteSpace(password))
+ {
+ return;
+ }
+
+ try
+ {
+ var serviceKind = serviceType.ToAIServiceType();
+ if (serviceKind != AIServiceType.OpenAI)
+ {
+ return;
+ }
+
+ string resource = "https://platform.openai.com/api-keys";
+ string username = $"PowerToys_AdvancedPaste_PasteAI_openai_{NormalizeProviderIdentifier(providerId)}";
+
+ PasswordVault vault = new();
+ TryRemoveCredential(vault, resource, username);
+
+ PasswordCredential credential = new(resource, username, password);
+ vault.Add(credential);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to migrate legacy OpenAI credential", ex);
+ }
+ }
+
+ private static void TryRemoveCredential(PasswordVault vault, string credentialResource, string credentialUserName)
+ {
+ try
+ {
+ PasswordCredential existingCred = vault.Retrieve(credentialResource, credentialUserName);
+ vault.Remove(existingCred);
+ }
+ catch (Exception)
+ {
+ // Credential doesn't exist, which is fine
+ }
+ }
+
+ private static string NormalizeProviderIdentifier(string providerId)
+ {
+ if (string.IsNullOrWhiteSpace(providerId))
+ {
+ return "default";
+ }
+
+ var filtered = new string(providerId.Where(char.IsLetterOrDigit).ToArray());
+ return string.IsNullOrWhiteSpace(filtered) ? "default" : filtered.ToLowerInvariant();
+ }
+
+ public async Task SetActiveAIProviderAsync(string providerId)
+ {
+ if (string.IsNullOrWhiteSpace(providerId))
+ {
+ return;
+ }
+
+ await Task.Run(() =>
+ {
+ lock (_loadingSettingsLock)
+ {
+ var settings = _settingsUtils.GetSettingsOrDefault(AdvancedPasteModuleName);
+ var configuration = settings?.Properties?.PasteAIConfiguration;
+ var providers = configuration?.Providers;
+
+ if (configuration == null || providers == null || providers.Count == 0)
+ {
+ return;
+ }
+
+ var target = providers.FirstOrDefault(provider => string.Equals(provider.Id, providerId, StringComparison.OrdinalIgnoreCase));
+ if (target == null)
+ {
+ return;
+ }
+
+ if (string.Equals(configuration.ActiveProvider?.Id, providerId, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ configuration.ActiveProviderId = providerId;
+
+ foreach (var provider in providers)
+ {
+ provider.IsActive = string.Equals(provider.Id, providerId, StringComparison.OrdinalIgnoreCase);
+ }
+
+ try
+ {
+ settings.Save(_settingsUtils);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to set active AI provider", ex);
+ return;
+ }
+
+ try
+ {
+ Task.Factory
+ .StartNew(
+ () =>
+ {
+ PasteAIConfiguration.ActiveProviderId = providerId;
+
+ if (PasteAIConfiguration.Providers is not null)
+ {
+ foreach (var provider in PasteAIConfiguration.Providers)
+ {
+ provider.IsActive = string.Equals(provider.Id, providerId, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ Changed?.Invoke(this, EventArgs.Empty);
+ },
+ CancellationToken.None,
+ TaskCreationOptions.None,
+ _taskScheduler)
+ .Wait();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to dispatch active AI provider change", ex);
+ }
+ }
+ });
+ }
+
public void Dispose()
{
Dispose(true);
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Models/ClipboardItem.cs b/src/modules/AdvancedPaste/AdvancedPaste/Models/ClipboardItem.cs
index 1013108bc9..16814e7001 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Models/ClipboardItem.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Models/ClipboardItem.cs
@@ -2,6 +2,7 @@
// 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 AdvancedPaste.Helpers;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.ApplicationModel.DataTransfer;
@@ -12,10 +13,15 @@ public class ClipboardItem
{
public string Content { get; set; }
- public ClipboardHistoryItem Item { get; set; }
-
public BitmapImage Image { get; set; }
+ public ClipboardFormat Format { get; set; }
+
+ public DateTimeOffset? Timestamp { get; set; }
+
+ // Only used for clipboard history items that have a ClipboardHistoryItem
+ public ClipboardHistoryItem Item { get; set; }
+
public string Description => !string.IsNullOrEmpty(Content) ? Content :
Image is not null ? ResourceLoaderInstance.ResourceLoader.GetString("ClipboardHistoryImage") :
string.Empty;
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs
new file mode 100644
index 0000000000..c886bcef43
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs
@@ -0,0 +1,220 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using AdvancedPaste.Helpers;
+using AdvancedPaste.Models;
+using AdvancedPaste.Services.CustomActions;
+using AdvancedPaste.Settings;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+
+namespace AdvancedPaste.Services;
+
+public sealed class AdvancedAIKernelService : KernelServiceBase
+{
+ private sealed record RuntimeConfiguration(
+ AIServiceType ServiceType,
+ string ModelName,
+ string Endpoint,
+ string DeploymentName,
+ string ModelPath,
+ string SystemPrompt,
+ bool ModerationEnabled) : IKernelRuntimeConfiguration;
+
+ private readonly IAICredentialsProvider credentialsProvider;
+
+ public AdvancedAIKernelService(
+ IAICredentialsProvider credentialsProvider,
+ IKernelQueryCacheService queryCacheService,
+ IPromptModerationService promptModerationService,
+ IUserSettings userSettings,
+ ICustomActionTransformService customActionTransformService)
+ : base(queryCacheService, promptModerationService, userSettings, customActionTransformService)
+ {
+ ArgumentNullException.ThrowIfNull(credentialsProvider);
+
+ this.credentialsProvider = credentialsProvider;
+ }
+
+ protected override string AdvancedAIModelName => GetRuntimeConfiguration().ModelName;
+
+ protected override PromptExecutionSettings PromptExecutionSettings => CreatePromptExecutionSettings();
+
+ protected override void AddChatCompletionService(IKernelBuilder kernelBuilder)
+ {
+ ArgumentNullException.ThrowIfNull(kernelBuilder);
+
+ var runtimeConfig = GetRuntimeConfiguration();
+ var serviceType = runtimeConfig.ServiceType;
+ var modelName = runtimeConfig.ModelName;
+ var requiresApiKey = RequiresApiKey(serviceType);
+ var apiKey = string.Empty;
+ if (requiresApiKey)
+ {
+ this.credentialsProvider.Refresh();
+ apiKey = (this.credentialsProvider.GetKey() ?? string.Empty).Trim();
+ if (string.IsNullOrWhiteSpace(apiKey))
+ {
+ throw new InvalidOperationException($"An API key is required for {serviceType} but none was found in the credential vault.");
+ }
+ }
+
+ var endpoint = string.IsNullOrWhiteSpace(runtimeConfig.Endpoint) ? null : runtimeConfig.Endpoint.Trim();
+ var deployment = string.IsNullOrWhiteSpace(runtimeConfig.DeploymentName) ? modelName : runtimeConfig.DeploymentName;
+
+ switch (serviceType)
+ {
+ case AIServiceType.OpenAI:
+ kernelBuilder.AddOpenAIChatCompletion(modelName, apiKey, serviceId: modelName);
+ break;
+ case AIServiceType.AzureOpenAI:
+ kernelBuilder.AddAzureOpenAIChatCompletion(deployment, RequireEndpoint(endpoint, serviceType), apiKey, serviceId: modelName);
+ break;
+ default:
+ throw new NotSupportedException($"Service type '{runtimeConfig.ServiceType}' is not supported");
+ }
+ }
+
+ protected override AIServiceUsage GetAIServiceUsage(ChatMessageContent chatMessage)
+ {
+ return AIServiceUsageHelper.GetOpenAIServiceUsage(chatMessage);
+ }
+
+ protected override bool ShouldModerateAdvancedAI()
+ {
+ if (!TryGetRuntimeConfiguration(out var runtimeConfig))
+ {
+ return false;
+ }
+
+ return runtimeConfig.ModerationEnabled && (runtimeConfig.ServiceType == AIServiceType.OpenAI || runtimeConfig.ServiceType == AIServiceType.AzureOpenAI);
+ }
+
+ private static string GetModelName(PasteAIProviderDefinition config)
+ {
+ if (!string.IsNullOrWhiteSpace(config?.ModelName))
+ {
+ return config.ModelName;
+ }
+
+ return "gpt-4o";
+ }
+
+ protected override IKernelRuntimeConfiguration GetRuntimeConfiguration()
+ {
+ if (TryGetRuntimeConfiguration(out var runtimeConfig))
+ {
+ return runtimeConfig;
+ }
+
+ throw new InvalidOperationException("No Advanced AI provider is configured.");
+ }
+
+ private bool TryGetRuntimeConfiguration(out IKernelRuntimeConfiguration runtimeConfig)
+ {
+ runtimeConfig = null;
+
+ if (!TryResolveAdvancedProvider(out var provider))
+ {
+ return false;
+ }
+
+ var serviceType = NormalizeServiceType(provider.ServiceTypeKind);
+ if (!IsServiceTypeSupported(serviceType))
+ {
+ return false;
+ }
+
+ runtimeConfig = new RuntimeConfiguration(
+ serviceType,
+ GetModelName(provider),
+ provider.EndpointUrl,
+ provider.DeploymentName,
+ provider.ModelPath,
+ provider.SystemPrompt,
+ provider.ModerationEnabled);
+ return true;
+ }
+
+ private bool TryResolveAdvancedProvider(out PasteAIProviderDefinition provider)
+ {
+ provider = null;
+
+ var configuration = this.UserSettings?.PasteAIConfiguration;
+ if (configuration is null)
+ {
+ return false;
+ }
+
+ var activeProvider = configuration.ActiveProvider;
+ if (IsAdvancedProvider(activeProvider))
+ {
+ provider = activeProvider;
+ return true;
+ }
+
+ if (activeProvider is not null)
+ {
+ return false;
+ }
+
+ var fallback = configuration.Providers?.FirstOrDefault(IsAdvancedProvider);
+ if (fallback is not null)
+ {
+ provider = fallback;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsAdvancedProvider(PasteAIProviderDefinition provider)
+ {
+ if (provider is null || !provider.EnableAdvancedAI)
+ {
+ return false;
+ }
+
+ var serviceType = NormalizeServiceType(provider.ServiceTypeKind);
+ return IsServiceTypeSupported(serviceType);
+ }
+
+ private static bool IsServiceTypeSupported(AIServiceType serviceType)
+ {
+ return serviceType is AIServiceType.OpenAI or AIServiceType.AzureOpenAI;
+ }
+
+ private static AIServiceType NormalizeServiceType(AIServiceType serviceType)
+ {
+ return serviceType == AIServiceType.Unknown ? AIServiceType.OpenAI : serviceType;
+ }
+
+ private static bool RequiresApiKey(AIServiceType serviceType)
+ {
+ return true;
+ }
+
+ private static string RequireEndpoint(string endpoint, AIServiceType serviceType)
+ {
+ if (!string.IsNullOrWhiteSpace(endpoint))
+ {
+ return endpoint;
+ }
+
+ throw new InvalidOperationException($"Endpoint is required for {serviceType} configuration but was not provided.");
+ }
+
+ private PromptExecutionSettings CreatePromptExecutionSettings()
+ {
+ var serviceType = GetRuntimeConfiguration().ServiceType;
+ return new OpenAIPromptExecutionSettings
+ {
+ FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
+ };
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformResult.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformResult.cs
new file mode 100644
index 0000000000..562ea3976c
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformResult.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using AdvancedPaste.Models;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class CustomActionTransformResult
+ {
+ public CustomActionTransformResult(string content, AIServiceUsage usage)
+ {
+ Content = content;
+ Usage = usage;
+ }
+
+ public string Content { get; }
+
+ public AIServiceUsage Usage { get; }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs
new file mode 100644
index 0000000000..57d55492a4
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs
@@ -0,0 +1,205 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using AdvancedPaste.Helpers;
+using AdvancedPaste.Models;
+using AdvancedPaste.Settings;
+using AdvancedPaste.Telemetry;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class CustomActionTransformService : ICustomActionTransformService
+ {
+ private const string DefaultSystemPrompt = """
+ You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it.
+ Do not output anything else besides the reformatted clipboard content.
+ """;
+
+ private readonly IPromptModerationService promptModerationService;
+ private readonly IPasteAIProviderFactory providerFactory;
+ private readonly IAICredentialsProvider credentialsProvider;
+ private readonly IUserSettings userSettings;
+
+ public CustomActionTransformService(IPromptModerationService promptModerationService, IPasteAIProviderFactory providerFactory, IAICredentialsProvider credentialsProvider, IUserSettings userSettings)
+ {
+ this.promptModerationService = promptModerationService;
+ this.providerFactory = providerFactory;
+ this.credentialsProvider = credentialsProvider;
+ this.userSettings = userSettings;
+ }
+
+ public async Task TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress progress)
+ {
+ var pasteConfig = userSettings?.PasteAIConfiguration;
+ var providerConfig = BuildProviderConfig(pasteConfig);
+
+ return await TransformAsync(prompt, inputText, providerConfig, cancellationToken, progress);
+ }
+
+ private async Task TransformAsync(string prompt, string inputText, PasteAIConfig providerConfig, CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(providerConfig);
+
+ if (string.IsNullOrWhiteSpace(prompt))
+ {
+ return new CustomActionTransformResult(string.Empty, AIServiceUsage.None);
+ }
+
+ if (string.IsNullOrWhiteSpace(inputText))
+ {
+ Logger.LogWarning("Clipboard has no usable text data");
+ return new CustomActionTransformResult(string.Empty, AIServiceUsage.None);
+ }
+
+ var systemPrompt = providerConfig.SystemPrompt ?? DefaultSystemPrompt;
+
+ var fullPrompt = (systemPrompt ?? string.Empty) + "\n\n" + (inputText ?? string.Empty);
+
+ if (ShouldModerate(providerConfig))
+ {
+ await promptModerationService.ValidateAsync(fullPrompt, cancellationToken);
+ }
+
+ try
+ {
+ var provider = providerFactory.CreateProvider(providerConfig);
+
+ var request = new PasteAIRequest
+ {
+ Prompt = prompt,
+ InputText = inputText,
+ SystemPrompt = systemPrompt,
+ };
+
+ var operationStart = DateTime.UtcNow;
+
+ var providerContent = await provider.ProcessPasteAsync(
+ request,
+ cancellationToken,
+ progress);
+
+ var durationMs = (int)Math.Round((DateTime.UtcNow - operationStart).TotalMilliseconds);
+
+ var usage = request.Usage;
+ var content = providerContent ?? string.Empty;
+
+ // Log endpoint usage (custom action pipeline is not the advanced SK flow)
+ var endpointEvent = new AdvancedPasteEndpointUsageEvent(providerConfig.ProviderType, providerConfig.Model ?? string.Empty, isAdvanced: false, durationMs: durationMs);
+ PowerToysTelemetry.Log.WriteEvent(endpointEvent);
+
+ Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}, DurationMs={durationMs}");
+
+ return new CustomActionTransformResult(content, usage);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} failed", ex);
+ var statusCode = ExtractStatusCode(ex);
+ var modelName = providerConfig.Model ?? string.Empty;
+ AdvancedPasteCustomActionErrorEvent errorEvent = new(providerConfig.ProviderType, modelName, statusCode, ex is PasteActionModeratedException ? PasteActionModeratedException.ErrorDescription : ex.Message);
+ PowerToysTelemetry.Log.WriteEvent(errorEvent);
+
+ if (ex is PasteActionException or OperationCanceledException)
+ {
+ throw;
+ }
+
+ var failureMessage = providerConfig.ProviderType switch
+ {
+ AIServiceType.OpenAI or AIServiceType.AzureOpenAI => ErrorHelpers.TranslateErrorText(statusCode),
+ _ => ResourceLoaderInstance.ResourceLoader.GetString("PasteError"),
+ };
+
+ throw new PasteActionException(failureMessage, ex);
+ }
+ }
+
+ private static int ExtractStatusCode(Exception exception)
+ {
+ if (exception is HttpOperationException httpOperationException)
+ {
+ return (int?)httpOperationException.StatusCode ?? -1;
+ }
+
+ if (exception is HttpRequestException httpRequestException && httpRequestException.StatusCode is HttpStatusCode statusCode)
+ {
+ return (int)statusCode;
+ }
+
+ return -1;
+ }
+
+ private static AIServiceType NormalizeServiceType(AIServiceType serviceType)
+ {
+ return serviceType == AIServiceType.Unknown ? AIServiceType.OpenAI : serviceType;
+ }
+
+ private PasteAIConfig BuildProviderConfig(PasteAIConfiguration config)
+ {
+ config ??= new PasteAIConfiguration();
+ var provider = config.ActiveProvider ?? config.Providers?.FirstOrDefault() ?? new PasteAIProviderDefinition();
+ var serviceType = NormalizeServiceType(provider.ServiceTypeKind);
+ var systemPrompt = string.IsNullOrWhiteSpace(provider.SystemPrompt) ? DefaultSystemPrompt : provider.SystemPrompt;
+ var apiKey = AcquireApiKey(serviceType);
+ var modelName = provider.ModelName;
+
+ var providerConfig = new PasteAIConfig
+ {
+ ProviderType = serviceType,
+ ApiKey = apiKey,
+ Model = modelName,
+ Endpoint = provider.EndpointUrl,
+ DeploymentName = provider.DeploymentName,
+ LocalModelPath = provider.ModelPath,
+ ModelPath = provider.ModelPath,
+ SystemPrompt = systemPrompt,
+ ModerationEnabled = provider.ModerationEnabled,
+ };
+
+ return providerConfig;
+ }
+
+ private string AcquireApiKey(AIServiceType serviceType)
+ {
+ if (!RequiresApiKey(serviceType))
+ {
+ return string.Empty;
+ }
+
+ credentialsProvider.Refresh();
+ return credentialsProvider.GetKey() ?? string.Empty;
+ }
+
+ private static bool RequiresApiKey(AIServiceType serviceType)
+ {
+ return serviceType switch
+ {
+ AIServiceType.Onnx => false,
+ AIServiceType.Ollama => false,
+ _ => true,
+ };
+ }
+
+ private static bool ShouldModerate(PasteAIConfig providerConfig)
+ {
+ if (providerConfig is null || !providerConfig.ModerationEnabled)
+ {
+ return false;
+ }
+
+ return providerConfig.ProviderType == AIServiceType.OpenAI || providerConfig.ProviderType == AIServiceType.AzureOpenAI;
+ }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs
new file mode 100644
index 0000000000..8b57baae74
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs
@@ -0,0 +1,196 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AdvancedPaste.Helpers;
+using AdvancedPaste.Models;
+using LanguageModelProvider;
+using Microsoft.Extensions.AI;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace AdvancedPaste.Services.CustomActions;
+
+public sealed class FoundryLocalPasteProvider : IPasteAIProvider
+{
+ private static readonly IReadOnlyCollection SupportedTypes = new[]
+ {
+ AIServiceType.FoundryLocal,
+ };
+
+ public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new FoundryLocalPasteProvider(config));
+
+ private static readonly FoundryLocalModelProvider _modelProvider = FoundryLocalModelProvider.Instance;
+
+ private readonly PasteAIConfig _config;
+
+ public FoundryLocalPasteProvider(PasteAIConfig config)
+ {
+ ArgumentNullException.ThrowIfNull(config);
+ _config = config;
+ }
+
+ public async Task IsAvailableAsync(CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return await FoundryLocalModelProvider.Instance.IsAvailable().ConfigureAwait(false);
+ }
+
+ public async Task ProcessPasteAsync(PasteAIRequest request, CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+
+ try
+ {
+ var systemPrompt = request.SystemPrompt;
+ if (string.IsNullOrWhiteSpace(systemPrompt))
+ {
+ throw new PasteActionException(
+ "System prompt is required for Foundry Local",
+ new ArgumentException("System prompt must be provided", nameof(request)));
+ }
+
+ var prompt = request.Prompt;
+ var inputText = request.InputText;
+ if (string.IsNullOrWhiteSpace(prompt) || string.IsNullOrWhiteSpace(inputText))
+ {
+ throw new PasteActionException(
+ "Prompt and input text are required",
+ new ArgumentException("Prompt and input text must be provided", nameof(request)));
+ }
+
+ var modelReference = _config?.Model;
+ if (string.IsNullOrWhiteSpace(modelReference))
+ {
+ throw new PasteActionException(
+ "No Foundry Local model selected",
+ new InvalidOperationException("Model identifier is required"),
+ aiServiceMessage: "Please select a model in the AI provider settings.");
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ IChatClient chatClient;
+ try
+ {
+ chatClient = _modelProvider.GetIChatClient(modelReference);
+ }
+ catch (InvalidOperationException ex)
+ {
+ // GetIChatClient throws InvalidOperationException for user-facing errors
+ var errorMessage = string.Format(System.Globalization.CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("FoundryLocal_UnableToLoadModel"), modelReference);
+ throw new PasteActionException(
+ errorMessage,
+ ex,
+ aiServiceMessage: ex.Message);
+ }
+
+ var userMessageContent = $"""
+ User instructions:
+ {prompt}
+
+ Text:
+ {inputText}
+
+ Output:
+ """;
+
+ var chatMessages = new List
+ {
+ new(ChatRole.System, systemPrompt),
+ new(ChatRole.User, userMessageContent),
+ };
+
+ var chatOptions = CreateChatOptions(_config?.SystemPrompt, modelReference);
+
+ progress?.Report(0.1);
+
+ var response = await chatClient.GetResponseAsync(chatMessages, chatOptions, cancellationToken).ConfigureAwait(false);
+
+ progress?.Report(0.8);
+
+ var responseText = GetResponseText(response);
+ request.Usage = ToUsage(response.Usage);
+
+ progress?.Report(1.0);
+
+ return responseText ?? string.Empty;
+ }
+ catch (OperationCanceledException)
+ {
+ // Let cancellation exceptions pass through unchanged
+ throw;
+ }
+ catch (PasteActionException)
+ {
+ // Let our custom exceptions pass through unchanged
+ throw;
+ }
+ catch (Exception ex)
+ {
+ // Wrap any other exceptions with context
+ var modelInfo = !string.IsNullOrWhiteSpace(_config?.Model) ? $" (Model: {_config.Model})" : string.Empty;
+ throw new PasteActionException(
+ $"Failed to generate response using Foundry Local{modelInfo}",
+ ex,
+ aiServiceMessage: $"Error details: {ex.Message}");
+ }
+ }
+
+ private static ChatOptions CreateChatOptions(string systemPrompt, string modelReference)
+ {
+ var options = new ChatOptions
+ {
+ ModelId = modelReference,
+ MaxOutputTokens = 2048,
+ };
+
+ if (!string.IsNullOrWhiteSpace(systemPrompt))
+ {
+ options.Instructions = systemPrompt;
+ }
+
+ return options;
+ }
+
+ private static string GetResponseText(ChatResponse response)
+ {
+ if (!string.IsNullOrWhiteSpace(response.Text))
+ {
+ return response.Text;
+ }
+
+ if (response.Messages is { Count: > 0 })
+ {
+ var lastMessage = response.Messages.LastOrDefault(m => !string.IsNullOrWhiteSpace(m.Text));
+ if (!string.IsNullOrWhiteSpace(lastMessage?.Text))
+ {
+ return lastMessage.Text;
+ }
+ }
+
+ return string.Empty;
+ }
+
+ private static AIServiceUsage ToUsage(UsageDetails usageDetails)
+ {
+ if (usageDetails is null)
+ {
+ return AIServiceUsage.None;
+ }
+
+ int promptTokens = (int)(usageDetails.InputTokenCount ?? 0);
+ int completionTokens = (int)(usageDetails.OutputTokenCount ?? 0);
+
+ if (promptTokens == 0 && completionTokens == 0)
+ {
+ return AIServiceUsage.None;
+ }
+
+ return new AIServiceUsage(promptTokens, completionTokens);
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs
new file mode 100644
index 0000000000..1c3ecb980c
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using AdvancedPaste.Settings;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public interface ICustomActionTransformService
+ {
+ Task TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress progress);
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/IPasteAIProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/IPasteAIProvider.cs
new file mode 100644
index 0000000000..764d99f942
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/IPasteAIProvider.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public interface IPasteAIProvider
+ {
+ Task IsAvailableAsync(CancellationToken cancellationToken);
+
+ Task ProcessPasteAsync(PasteAIRequest request, CancellationToken cancellationToken, IProgress progress);
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/IPasteAIProviderFactory.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/IPasteAIProviderFactory.cs
new file mode 100644
index 0000000000..aacc61bec9
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/IPasteAIProviderFactory.cs
@@ -0,0 +1,11 @@
+// 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 AdvancedPaste.Services.CustomActions
+{
+ public interface IPasteAIProviderFactory
+ {
+ IPasteAIProvider CreateProvider(PasteAIConfig config);
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/LocalModelPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/LocalModelPasteProvider.cs
new file mode 100644
index 0000000000..f4d45ccd74
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/LocalModelPasteProvider.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using AdvancedPaste.Models;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class LocalModelPasteProvider : IPasteAIProvider
+ {
+ private static readonly IReadOnlyCollection SupportedTypes = new[]
+ {
+ AIServiceType.Onnx,
+ AIServiceType.ML,
+ };
+
+ public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new LocalModelPasteProvider(config));
+
+ private readonly PasteAIConfig _config;
+
+ public LocalModelPasteProvider(PasteAIConfig config)
+ {
+ _config = config ?? throw new ArgumentNullException(nameof(config));
+ }
+
+ public Task IsAvailableAsync(CancellationToken cancellationToken) => Task.FromResult(true);
+
+ public Task ProcessPasteAsync(PasteAIRequest request, CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+
+ // TODO: Implement local model inference logic using _config.LocalModelPath/_config.ModelPath
+ var content = request.InputText ?? string.Empty;
+ request.Usage = AIServiceUsage.None;
+ return Task.FromResult(content);
+ }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIConfig.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIConfig.cs
new file mode 100644
index 0000000000..1d8a60f041
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIConfig.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using AdvancedPaste.Models;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.SemanticKernel.ChatCompletion;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public class PasteAIConfig
+ {
+ public AIServiceType ProviderType { get; set; }
+
+ public string Model { get; set; }
+
+ public string ApiKey { get; set; }
+
+ public string Endpoint { get; set; }
+
+ public string DeploymentName { get; set; }
+
+ public string LocalModelPath { get; set; }
+
+ public string ModelPath { get; set; }
+
+ public string SystemPrompt { get; set; }
+
+ public bool ModerationEnabled { get; set; }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIProviderFactory.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIProviderFactory.cs
new file mode 100644
index 0000000000..7339b4e4e3
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIProviderFactory.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class PasteAIProviderFactory : IPasteAIProviderFactory
+ {
+ private static readonly IReadOnlyList ProviderRegistrations = new[]
+ {
+ SemanticKernelPasteProvider.Registration,
+ LocalModelPasteProvider.Registration,
+ FoundryLocalPasteProvider.Registration,
+ };
+
+ private static readonly IReadOnlyDictionary> ProviderFactories = CreateProviderFactories();
+
+ public IPasteAIProvider CreateProvider(PasteAIConfig config)
+ {
+ ArgumentNullException.ThrowIfNull(config);
+
+ var serviceType = config.ProviderType;
+ if (serviceType == AIServiceType.Unknown)
+ {
+ serviceType = AIServiceType.OpenAI;
+ config.ProviderType = serviceType;
+ }
+
+ if (!ProviderFactories.TryGetValue(serviceType, out var factory))
+ {
+ throw new NotSupportedException($"Provider {config.ProviderType} not supported");
+ }
+
+ return factory(config);
+ }
+
+ private static IReadOnlyDictionary> CreateProviderFactories()
+ {
+ var map = new Dictionary>();
+
+ foreach (var registration in ProviderRegistrations)
+ {
+ Register(map, registration.SupportedTypes, registration.Factory);
+ }
+
+ return map;
+ }
+
+ private static void Register(Dictionary> map, IReadOnlyCollection types, Func factory)
+ {
+ foreach (var type in types)
+ {
+ map[type] = factory;
+ }
+ }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIProviderRegistration.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIProviderRegistration.cs
new file mode 100644
index 0000000000..6bd78450e8
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIProviderRegistration.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class PasteAIProviderRegistration
+ {
+ public PasteAIProviderRegistration(IReadOnlyCollection supportedTypes, Func factory)
+ {
+ SupportedTypes = supportedTypes ?? throw new ArgumentNullException(nameof(supportedTypes));
+ Factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ }
+
+ public IReadOnlyCollection SupportedTypes { get; }
+
+ public Func Factory { get; }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs
new file mode 100644
index 0000000000..0e15c93e05
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs
@@ -0,0 +1,19 @@
+// 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 AdvancedPaste.Models;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class PasteAIRequest
+ {
+ public string Prompt { get; init; }
+
+ public string InputText { get; init; }
+
+ public string SystemPrompt { get; init; }
+
+ public AIServiceUsage Usage { get; set; } = AIServiceUsage.None;
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs
new file mode 100644
index 0000000000..eb2f56e01f
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs
@@ -0,0 +1,185 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using AdvancedPaste.Helpers;
+using AdvancedPaste.Models;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.ChatCompletion;
+using Microsoft.SemanticKernel.Connectors.AzureAIInference;
+using Microsoft.SemanticKernel.Connectors.Google;
+using Microsoft.SemanticKernel.Connectors.MistralAI;
+using Microsoft.SemanticKernel.Connectors.Ollama;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+
+namespace AdvancedPaste.Services.CustomActions
+{
+ public sealed class SemanticKernelPasteProvider : IPasteAIProvider
+ {
+ private static readonly IReadOnlyCollection SupportedTypes = new[]
+ {
+ AIServiceType.OpenAI,
+ AIServiceType.AzureOpenAI,
+ AIServiceType.Mistral,
+ AIServiceType.Google,
+ AIServiceType.AzureAIInference,
+ AIServiceType.Ollama,
+ };
+
+ public static PasteAIProviderRegistration Registration { get; } = new(SupportedTypes, config => new SemanticKernelPasteProvider(config));
+
+ private readonly PasteAIConfig _config;
+ private readonly AIServiceType _serviceType;
+
+ public SemanticKernelPasteProvider(PasteAIConfig config)
+ {
+ ArgumentNullException.ThrowIfNull(config);
+ _config = config;
+ _serviceType = config.ProviderType;
+ if (_serviceType == AIServiceType.Unknown)
+ {
+ _serviceType = AIServiceType.OpenAI;
+ _config.ProviderType = _serviceType;
+ }
+ }
+
+ public IReadOnlyCollection SupportedServiceTypes => SupportedTypes;
+
+ public Task IsAvailableAsync(CancellationToken cancellationToken) => Task.FromResult(true);
+
+ public async Task ProcessPasteAsync(PasteAIRequest request, CancellationToken cancellationToken, IProgress progress)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+
+ var systemPrompt = request.SystemPrompt;
+ if (string.IsNullOrWhiteSpace(systemPrompt))
+ {
+ throw new ArgumentException("System prompt must be provided", nameof(request));
+ }
+
+ var prompt = request.Prompt;
+ var inputText = request.InputText;
+ if (string.IsNullOrWhiteSpace(prompt) || string.IsNullOrWhiteSpace(inputText))
+ {
+ throw new ArgumentException("Prompt and input text must be provided", nameof(request));
+ }
+
+ var userMessageContent = $"""
+ User instructions:
+ {prompt}
+
+ Clipboard Content:
+ {inputText}
+
+ Output:
+ """;
+
+ var executionSettings = CreateExecutionSettings();
+ var kernel = CreateKernel();
+ var modelId = _config.Model;
+
+ IChatCompletionService chatService;
+ if (!string.IsNullOrWhiteSpace(modelId))
+ {
+ try
+ {
+ chatService = kernel.GetRequiredService(modelId);
+ }
+ catch (Exception)
+ {
+ chatService = kernel.GetRequiredService();
+ }
+ }
+ else
+ {
+ chatService = kernel.GetRequiredService();
+ }
+
+ var chatHistory = new ChatHistory();
+ chatHistory.AddSystemMessage(systemPrompt);
+ chatHistory.AddUserMessage(userMessageContent);
+
+ var response = await chatService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel, cancellationToken);
+ chatHistory.Add(response);
+
+ request.Usage = AIServiceUsageHelper.GetOpenAIServiceUsage(response);
+ return response.Content;
+ }
+
+ private Kernel CreateKernel()
+ {
+ var kernelBuilder = Kernel.CreateBuilder();
+ var endpoint = string.IsNullOrWhiteSpace(_config.Endpoint) ? null : _config.Endpoint.Trim();
+ var apiKey = _config.ApiKey?.Trim() ?? string.Empty;
+
+ if (RequiresApiKey(_serviceType) && string.IsNullOrWhiteSpace(apiKey))
+ {
+ throw new InvalidOperationException($"API key is required for {_serviceType} but was not provided.");
+ }
+
+ switch (_serviceType)
+ {
+ case AIServiceType.OpenAI:
+ kernelBuilder.AddOpenAIChatCompletion(_config.Model, apiKey, serviceId: _config.Model);
+ break;
+ case AIServiceType.AzureOpenAI:
+ var deploymentName = string.IsNullOrWhiteSpace(_config.DeploymentName) ? _config.Model : _config.DeploymentName;
+ kernelBuilder.AddAzureOpenAIChatCompletion(deploymentName, RequireEndpoint(endpoint, _serviceType), apiKey, serviceId: _config.Model);
+ break;
+ case AIServiceType.Mistral:
+ kernelBuilder.AddMistralChatCompletion(_config.Model, apiKey: apiKey);
+ break;
+ case AIServiceType.Google:
+ kernelBuilder.AddGoogleAIGeminiChatCompletion(_config.Model, apiKey: apiKey);
+ break;
+ case AIServiceType.AzureAIInference:
+ kernelBuilder.AddAzureAIInferenceChatCompletion(_config.Model, apiKey: apiKey, endpoint: new Uri(endpoint));
+ break;
+ case AIServiceType.Ollama:
+ kernelBuilder.AddOllamaChatCompletion(_config.Model, endpoint: new Uri(endpoint));
+ break;
+
+ default:
+ throw new NotSupportedException($"Provider '{_config.ProviderType}' is not supported by {nameof(SemanticKernelPasteProvider)}");
+ }
+
+ return kernelBuilder.Build();
+ }
+
+ private PromptExecutionSettings CreateExecutionSettings()
+ {
+ return _serviceType switch
+ {
+ AIServiceType.OpenAI or AIServiceType.AzureOpenAI => new OpenAIPromptExecutionSettings
+ {
+ FunctionChoiceBehavior = null,
+ },
+ _ => new PromptExecutionSettings(),
+ };
+ }
+
+ private static bool RequiresApiKey(AIServiceType serviceType)
+ {
+ return serviceType switch
+ {
+ AIServiceType.Ollama => false,
+ _ => true,
+ };
+ }
+
+ private static string RequireEndpoint(string endpoint, AIServiceType serviceType)
+ {
+ if (!string.IsNullOrWhiteSpace(endpoint))
+ {
+ return endpoint;
+ }
+
+ throw new InvalidOperationException($"Endpoint is required for {serviceType} but was not provided.");
+ }
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs
new file mode 100644
index 0000000000..648881fba0
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/EnhancedVaultCredentialsProvider.cs
@@ -0,0 +1,182 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using System.Threading;
+using AdvancedPaste.Settings;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Windows.Security.Credentials;
+
+namespace AdvancedPaste.Services;
+
+///
+/// Enhanced credentials provider that supports different AI service types
+/// Keys are stored in Windows Credential Vault with service-specific identifiers
+///
+public sealed class EnhancedVaultCredentialsProvider : IAICredentialsProvider
+{
+ private sealed class CredentialSlot
+ {
+ public AIServiceType ServiceType { get; set; } = AIServiceType.Unknown;
+
+ public string ProviderId { get; set; } = string.Empty;
+
+ public (string Resource, string Username)? Entry { get; set; }
+
+ public string Key { get; set; } = string.Empty;
+ }
+
+ private readonly IUserSettings _userSettings;
+ private readonly CredentialSlot _slot;
+ private readonly Lock _syncRoot = new();
+
+ public EnhancedVaultCredentialsProvider(IUserSettings userSettings)
+ {
+ _userSettings = userSettings ?? throw new ArgumentNullException(nameof(userSettings));
+
+ _slot = new CredentialSlot();
+
+ Refresh();
+ }
+
+ public string GetKey()
+ {
+ using (_syncRoot.EnterScope())
+ {
+ UpdateSlot(forceRefresh: false);
+ return _slot.Key;
+ }
+ }
+
+ public bool IsConfigured()
+ {
+ return !string.IsNullOrEmpty(GetKey());
+ }
+
+ public bool Refresh()
+ {
+ using (_syncRoot.EnterScope())
+ {
+ return UpdateSlot(forceRefresh: true);
+ }
+ }
+
+ private bool UpdateSlot(bool forceRefresh)
+ {
+ var (serviceType, providerId) = ResolveCredentialTarget();
+ var desiredServiceType = NormalizeServiceType(serviceType);
+ providerId ??= string.Empty;
+
+ var hasChanged = false;
+
+ if (_slot.ServiceType != desiredServiceType || !string.Equals(_slot.ProviderId, providerId, StringComparison.Ordinal))
+ {
+ _slot.ServiceType = desiredServiceType;
+ _slot.ProviderId = providerId;
+ _slot.Entry = BuildCredentialEntry(desiredServiceType, providerId);
+ forceRefresh = true;
+ hasChanged = true;
+ }
+
+ if (!forceRefresh)
+ {
+ return hasChanged;
+ }
+
+ var newKey = LoadKey(_slot.Entry);
+ if (!string.Equals(_slot.Key, newKey, StringComparison.Ordinal))
+ {
+ _slot.Key = newKey;
+ hasChanged = true;
+ }
+
+ return hasChanged;
+ }
+
+ private (AIServiceType ServiceType, string ProviderId) ResolveCredentialTarget()
+ {
+ var provider = _userSettings.PasteAIConfiguration?.ActiveProvider;
+ if (provider is null)
+ {
+ return (AIServiceType.OpenAI, string.Empty);
+ }
+
+ return (provider.ServiceTypeKind, provider.Id ?? string.Empty);
+ }
+
+ private static AIServiceType NormalizeServiceType(AIServiceType serviceType)
+ {
+ return serviceType == AIServiceType.Unknown ? AIServiceType.OpenAI : serviceType;
+ }
+
+ private static string LoadKey((string Resource, string Username)? entry)
+ {
+ if (entry is null)
+ {
+ return string.Empty;
+ }
+
+ try
+ {
+ var credential = new PasswordVault().Retrieve(entry.Value.Resource, entry.Value.Username);
+ return credential?.Password ?? string.Empty;
+ }
+ catch (Exception)
+ {
+ return string.Empty;
+ }
+ }
+
+ private static (string Resource, string Username)? BuildCredentialEntry(AIServiceType serviceType, string providerId)
+ {
+ string resource;
+ string serviceKey;
+
+ switch (serviceType)
+ {
+ case AIServiceType.OpenAI:
+ resource = "https://platform.openai.com/api-keys";
+ serviceKey = "openai";
+ break;
+ case AIServiceType.AzureOpenAI:
+ resource = "https://azure.microsoft.com/products/ai-services/openai-service";
+ serviceKey = "azureopenai";
+ break;
+ case AIServiceType.AzureAIInference:
+ resource = "https://azure.microsoft.com/products/ai-services/ai-inference";
+ serviceKey = "azureaiinference";
+ break;
+ case AIServiceType.Mistral:
+ resource = "https://console.mistral.ai/account/api-keys";
+ serviceKey = "mistral";
+ break;
+ case AIServiceType.Google:
+ resource = "https://ai.google.dev/";
+ serviceKey = "google";
+ break;
+ case AIServiceType.FoundryLocal:
+ case AIServiceType.ML:
+ case AIServiceType.Onnx:
+ case AIServiceType.Ollama:
+ return null;
+ default:
+ return null;
+ }
+
+ string username = $"PowerToys_AdvancedPaste_PasteAI_{serviceKey}_{NormalizeProviderIdentifier(providerId)}";
+ return (resource, username);
+ }
+
+ private static string NormalizeProviderIdentifier(string providerId)
+ {
+ if (string.IsNullOrWhiteSpace(providerId))
+ {
+ return "default";
+ }
+
+ var filtered = new string(providerId.Where(char.IsLetterOrDigit).ToArray());
+ return string.IsNullOrWhiteSpace(filtered) ? "default" : filtered.ToLowerInvariant();
+ }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/IAICredentialsProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/IAICredentialsProvider.cs
index 54759b7dc8..7aa6f63b19 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/IAICredentialsProvider.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/IAICredentialsProvider.cs
@@ -4,11 +4,26 @@
namespace AdvancedPaste.Services;
+///
+/// Provides access to AI credentials stored for Advanced Paste scenarios.
+///
public interface IAICredentialsProvider
{
- bool IsConfigured { get; }
+ ///
+ /// Gets a value indicating whether any credential is configured.
+ ///
+ /// when a non-empty credential exists for the active AI provider.
+ bool IsConfigured();
- string Key { get; }
+ ///
+ /// Retrieves the credential for the active AI provider.
+ ///
+ /// Credential string or when missing.
+ string GetKey();
+ ///
+ /// Refreshes the cached credential for the active AI provider.
+ ///
+ /// when the credential changed.
bool Refresh();
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/ICustomTextTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/ICustomTextTransformService.cs
deleted file mode 100644
index 75f1df259e..0000000000
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/ICustomTextTransformService.cs
+++ /dev/null
@@ -1,14 +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 System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace AdvancedPaste.Services;
-
-public interface ICustomTextTransformService
-{
- Task TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress progress);
-}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/IKernelRuntimeConfiguration.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/IKernelRuntimeConfiguration.cs
new file mode 100644
index 0000000000..d634c13e30
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/IKernelRuntimeConfiguration.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace AdvancedPaste.Services;
+
+///
+/// Represents runtime information required to configure an AI kernel service.
+///
+public interface IKernelRuntimeConfiguration
+{
+ AIServiceType ServiceType { get; }
+
+ string ModelName { get; }
+
+ string Endpoint { get; }
+
+ string DeploymentName { get; }
+
+ string ModelPath { get; }
+
+ string SystemPrompt { get; }
+
+ bool ModerationEnabled { get; }
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs
index e921b21e54..47e208eb49 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs
@@ -5,15 +5,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
using AdvancedPaste.Models.KernelQueryCache;
+using AdvancedPaste.Services.CustomActions;
+using AdvancedPaste.Settings;
using AdvancedPaste.Telemetry;
using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
@@ -21,15 +22,21 @@ using Windows.ApplicationModel.DataTransfer;
namespace AdvancedPaste.Services;
-public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheService, IPromptModerationService promptModerationService, ICustomTextTransformService customTextTransformService) : IKernelService
+public abstract class KernelServiceBase(
+ IKernelQueryCacheService queryCacheService,
+ IPromptModerationService promptModerationService,
+ IUserSettings userSettings,
+ ICustomActionTransformService customActionTransformService) : IKernelService
{
private const string PromptParameterName = "prompt";
+ private const string DefaultSystemPrompt = "You are an agent who is tasked with helping users paste their clipboard data. You have functions available to help you with this task. Call function when necessary to help user finish the transformation task. You never need to ask permission, always try to do as the user asks. The user will only input one message and will not be available for further questions, so try your best. The user will put in a request to format their clipboard data and you will fulfill it. Do not output anything else besides the reformatted clipboard content.";
private readonly IKernelQueryCacheService _queryCacheService = queryCacheService;
private readonly IPromptModerationService _promptModerationService = promptModerationService;
- private readonly ICustomTextTransformService _customTextTransformService = customTextTransformService;
+ private readonly IUserSettings _userSettings = userSettings;
+ private readonly ICustomActionTransformService _customActionTransformService = customActionTransformService;
- protected abstract string ModelName { get; }
+ protected abstract string AdvancedAIModelName { get; }
protected abstract PromptExecutionSettings PromptExecutionSettings { get; }
@@ -37,6 +44,8 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
protected abstract AIServiceUsage GetAIServiceUsage(ChatMessageContent chatMessage);
+ protected abstract IKernelRuntimeConfiguration GetRuntimeConfiguration();
+
public async Task TransformClipboardAsync(string prompt, DataPackageView clipboardData, bool isSavedQuery, CancellationToken cancellationToken, IProgress progress)
{
Logger.LogTrace();
@@ -132,21 +141,21 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
private async Task<(ChatHistory ChatHistory, AIServiceUsage Usage)> ExecuteAICompletion(Kernel kernel, string prompt, CancellationToken cancellationToken)
{
+ var runtimeConfig = GetRuntimeConfiguration();
+
ChatHistory chatHistory = [];
- chatHistory.AddSystemMessage("""
- You are an agent who is tasked with helping users paste their clipboard data. You have functions available to help you with this task.
- You never need to ask permission, always try to do as the user asks. The user will only input one message and will not be available for further questions, so try your best.
- The user will put in a request to format their clipboard data and you will fulfill it.
- You will not directly see the output clipboard content, and do not need to provide it in the chat. You just need to do the transform operations as needed.
- If you are unable to fulfill the request, end with an error message in the language of the user's request.
- """);
+ var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt;
+ chatHistory.AddSystemMessage(systemPrompt);
chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}");
chatHistory.AddUserMessage(prompt);
- await _promptModerationService.ValidateAsync(GetFullPrompt(chatHistory), cancellationToken);
+ if (ShouldModerateAdvancedAI())
+ {
+ await _promptModerationService.ValidateAsync(GetFullPrompt(chatHistory), cancellationToken);
+ }
- var chatResult = await kernel.GetRequiredService()
+ var chatResult = await kernel.GetRequiredService(AdvancedAIModelName)
.GetChatMessageContentAsync(chatHistory, PromptExecutionSettings, kernel, cancellationToken);
chatHistory.Add(chatResult);
@@ -175,10 +184,26 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
return ([], AIServiceUsage.None);
}
+ protected IUserSettings UserSettings => _userSettings;
+
private void LogResult(bool cacheUsed, bool isSavedQuery, IEnumerable actionChain, AIServiceUsage usage)
{
- AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(cacheUsed, isSavedQuery, usage.PromptTokens, usage.CompletionTokens, ModelName, AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
+ var runtimeConfig = GetRuntimeConfiguration();
+
+ AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(
+ cacheUsed,
+ isSavedQuery,
+ usage.PromptTokens,
+ usage.CompletionTokens,
+ AdvancedAIModelName,
+ runtimeConfig.ServiceType.ToString(),
+ AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
+
+ // Log endpoint usage
+ var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType, AdvancedAIModelName, isAdvanced: true);
+ PowerToysTelemetry.Log.WriteEvent(endpointEvent);
+
var logEvent = new AIServiceFormatEvent(telemetryEvent);
Logger.LogDebug($"{nameof(TransformClipboardAsync)} complete; {logEvent.ToJsonString()}");
}
@@ -191,20 +216,96 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
return kernelBuilder.Build();
}
- private IEnumerable GetKernelFunctions() =>
- from format in Enum.GetValues()
- let metadata = PasteFormat.MetadataDict[format]
- let coreDescription = metadata.KernelFunctionDescription
- where !string.IsNullOrEmpty(coreDescription)
- let requiresPrompt = metadata.RequiresPrompt
- orderby requiresPrompt descending
- select KernelFunctionFactory.CreateFromMethod(
- method: requiresPrompt ? async (Kernel kernel, string prompt) => await ExecutePromptTransformAsync(kernel, format, prompt)
- : async (Kernel kernel) => await ExecuteStandardTransformAsync(kernel, format),
- functionName: format.ToString(),
- description: requiresPrompt ? coreDescription : $"{coreDescription} Puts the result back on the clipboard.",
- parameters: requiresPrompt ? [new(PromptParameterName) { Description = "Input instructions to AI", ParameterType = typeof(string) }] : null,
- returnParameter: new() { Description = "Array of available clipboard formats after operation" });
+ private IEnumerable GetKernelFunctions()
+ {
+ // Get standard format functions
+ var standardFunctions =
+ from format in Enum.GetValues()
+ let metadata = PasteFormat.MetadataDict[format]
+ let coreDescription = metadata.KernelFunctionDescription
+ where !string.IsNullOrEmpty(coreDescription)
+ let requiresPrompt = metadata.RequiresPrompt
+ orderby requiresPrompt descending
+ select KernelFunctionFactory.CreateFromMethod(
+ method: requiresPrompt ? async (Kernel kernel, string prompt) => await ExecutePromptTransformAsync(kernel, format, prompt)
+ : async (Kernel kernel) => await ExecuteStandardTransformAsync(kernel, format),
+ functionName: format.ToString(),
+ description: requiresPrompt ? coreDescription : $"{coreDescription} Puts the result back on the clipboard.",
+ parameters: requiresPrompt ? [new(PromptParameterName) { Description = "Input instructions to AI", ParameterType = typeof(string) }] : null,
+ returnParameter: new() { Description = "Array of available clipboard formats after operation" });
+
+ HashSet usedFunctionNames = new(Enum.GetNames(), StringComparer.OrdinalIgnoreCase);
+
+ // Get custom action functions
+ var customActionFunctions = _userSettings.CustomActions
+ .Where(customAction => !string.IsNullOrWhiteSpace(customAction.Name) && !string.IsNullOrWhiteSpace(customAction.Prompt))
+ .Select(customAction =>
+ {
+ var sanitizedBaseName = SanitizeFunctionName(customAction.Name);
+ var functionName = GetUniqueFunctionName(sanitizedBaseName, usedFunctionNames, customAction.Id);
+ var description = string.IsNullOrWhiteSpace(customAction.Description)
+ ? $"Runs the \"{customAction.Name}\" custom action."
+ : customAction.Description;
+ return KernelFunctionFactory.CreateFromMethod(
+ method: async (Kernel kernel) => await ExecuteCustomActionAsync(kernel, customAction.Prompt),
+ functionName: functionName,
+ description: description,
+ parameters: null,
+ returnParameter: new() { Description = "Array of available clipboard formats after operation" });
+ });
+
+ return standardFunctions.Concat(customActionFunctions);
+ }
+
+ private static string GetUniqueFunctionName(string baseName, HashSet usedFunctionNames, int customActionId)
+ {
+ ArgumentNullException.ThrowIfNull(usedFunctionNames);
+
+ var candidate = string.IsNullOrEmpty(baseName) ? "_CustomAction" : baseName;
+
+ if (usedFunctionNames.Add(candidate))
+ {
+ return candidate;
+ }
+
+ int suffix = 1;
+ while (true)
+ {
+ var nextCandidate = $"{candidate}_{customActionId}_{suffix}";
+ if (usedFunctionNames.Add(nextCandidate))
+ {
+ return nextCandidate;
+ }
+
+ suffix++;
+ }
+ }
+
+ private static string SanitizeFunctionName(string name)
+ {
+ // Remove invalid characters and ensure the function name is valid for kernel
+ var sanitized = new string(name.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray());
+
+ // Ensure it starts with a letter or underscore
+ if (sanitized.Length > 0 && !char.IsLetter(sanitized[0]) && sanitized[0] != '_')
+ {
+ sanitized = "_" + sanitized;
+ }
+
+ // Ensure it's not empty
+ return string.IsNullOrEmpty(sanitized) ? "_CustomAction" : sanitized;
+ }
+
+ private Task ExecuteCustomActionAsync(Kernel kernel, string fixedPrompt) =>
+ ExecuteTransformAsync(
+ kernel,
+ new ActionChainItem(PasteFormats.CustomTextTransformation, Arguments: new() { { PromptParameterName, fixedPrompt } }),
+ async dataPackageView =>
+ {
+ var input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken());
+ var result = await _customActionTransformService.TransformTextAsync(fixedPrompt, input, kernel.GetCancellationToken(), kernel.GetProgress());
+ return DataPackageHelpers.CreateFromText(result?.Content ?? string.Empty);
+ });
private Task ExecutePromptTransformAsync(Kernel kernel, PasteFormats format, string prompt) =>
ExecuteTransformAsync(
@@ -212,7 +313,7 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
new ActionChainItem(format, Arguments: new() { { PromptParameterName, prompt } }),
async dataPackageView =>
{
- var input = await dataPackageView.GetTextAsync();
+ var input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken());
string output = await GetPromptBasedOutput(format, prompt, input, kernel.GetCancellationToken(), kernel.GetProgress());
return DataPackageHelpers.CreateFromText(output);
});
@@ -220,7 +321,7 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
private async Task GetPromptBasedOutput(PasteFormats format, string prompt, string input, CancellationToken cancellationToken, IProgress progress) =>
format switch
{
- PasteFormats.CustomTextTransformation => await _customTextTransformService.TransformTextAsync(prompt, input, cancellationToken, progress),
+ PasteFormats.CustomTextTransformation => (await _customActionTransformService.TransformTextAsync(prompt, input, cancellationToken, progress))?.Content ?? string.Empty,
_ => throw new ArgumentException($"Unsupported format {format} for prompt transform", nameof(format)),
};
@@ -281,4 +382,9 @@ public abstract class KernelServiceBase(IKernelQueryCacheService queryCacheServi
var usageString = usage.HasUsage ? $" [{usage}]" : string.Empty;
return $"-> {role}: {redactedContent}{usageString}";
}
+
+ protected virtual bool ShouldModerateAdvancedAI()
+ {
+ return false;
+ }
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/CustomTextTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/CustomTextTransformService.cs
deleted file mode 100644
index b6aa156b9d..0000000000
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/CustomTextTransformService.cs
+++ /dev/null
@@ -1,113 +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 System;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-
-using AdvancedPaste.Helpers;
-using AdvancedPaste.Models;
-using AdvancedPaste.Telemetry;
-using Azure;
-using Azure.AI.OpenAI;
-using ManagedCommon;
-using Microsoft.PowerToys.Telemetry;
-
-namespace AdvancedPaste.Services.OpenAI;
-
-public sealed class CustomTextTransformService(IAICredentialsProvider aiCredentialsProvider, IPromptModerationService promptModerationService) : ICustomTextTransformService
-{
- private const string ModelName = "gpt-3.5-turbo-instruct";
-
- private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
- private readonly IPromptModerationService _promptModerationService = promptModerationService;
-
- private async Task GetAICompletionAsync(string systemInstructions, string userMessage, CancellationToken cancellationToken)
- {
- var fullPrompt = systemInstructions + "\n\n" + userMessage;
-
- await _promptModerationService.ValidateAsync(fullPrompt, cancellationToken);
-
- OpenAIClient azureAIClient = new(_aiCredentialsProvider.Key);
-
- var response = await azureAIClient.GetCompletionsAsync(
- new()
- {
- DeploymentName = ModelName,
- Prompts =
- {
- fullPrompt,
- },
- Temperature = 0.01F,
- MaxTokens = 2000,
- },
- cancellationToken);
-
- if (response.Value.Choices[0].FinishReason == "length")
- {
- Logger.LogDebug("Cut off due to length constraints");
- }
-
- return response;
- }
-
- public async Task TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress progress)
- {
- if (string.IsNullOrWhiteSpace(prompt))
- {
- return string.Empty;
- }
-
- if (string.IsNullOrWhiteSpace(inputText))
- {
- Logger.LogWarning("Clipboard has no usable text data");
- return string.Empty;
- }
-
- string systemInstructions =
-$@"You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it.
-Do not output anything else besides the reformatted clipboard content.";
-
- string userMessage =
-$@"User instructions:
-{prompt}
-
-Clipboard Content:
-{inputText}
-
-Output:
-";
-
- try
- {
- var response = await GetAICompletionAsync(systemInstructions, userMessage, cancellationToken);
-
- var usage = response.Usage;
- AdvancedPasteGenerateCustomFormatEvent telemetryEvent = new(usage.PromptTokens, usage.CompletionTokens, ModelName);
- PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
- var logEvent = new AIServiceFormatEvent(telemetryEvent);
-
- Logger.LogDebug($"{nameof(TransformTextAsync)} complete; {logEvent.ToJsonString()}");
-
- return response.Choices[0].Text;
- }
- catch (Exception ex)
- {
- Logger.LogError($"{nameof(TransformTextAsync)} failed", ex);
-
- AdvancedPasteGenerateCustomErrorEvent errorEvent = new(ex is PasteActionModeratedException ? PasteActionModeratedException.ErrorDescription : ex.Message);
- PowerToysTelemetry.Log.WriteEvent(errorEvent);
-
- if (ex is PasteActionException or OperationCanceledException)
- {
- throw;
- }
- else
- {
- throw new PasteActionException(ErrorHelpers.TranslateErrorText((ex as RequestFailedException)?.Status ?? -1), ex);
- }
- }
- }
-}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/KernelService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/KernelService.cs
deleted file mode 100644
index b19a6d51cb..0000000000
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/KernelService.cs
+++ /dev/null
@@ -1,34 +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 System.Collections.Generic;
-
-using AdvancedPaste.Models;
-using Azure.AI.OpenAI;
-using Microsoft.SemanticKernel;
-using Microsoft.SemanticKernel.Connectors.OpenAI;
-
-namespace AdvancedPaste.Services.OpenAI;
-
-public sealed class KernelService(IKernelQueryCacheService queryCacheService, IAICredentialsProvider aiCredentialsProvider, IPromptModerationService promptModerationService, ICustomTextTransformService customTextTransformService) :
- KernelServiceBase(queryCacheService, promptModerationService, customTextTransformService)
-{
- private readonly IAICredentialsProvider _aiCredentialsProvider = aiCredentialsProvider;
-
- protected override string ModelName => "gpt-4o";
-
- protected override PromptExecutionSettings PromptExecutionSettings =>
- new OpenAIPromptExecutionSettings()
- {
- ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
- Temperature = 0.01,
- };
-
- protected override void AddChatCompletionService(IKernelBuilder kernelBuilder) => kernelBuilder.AddOpenAIChatCompletion(ModelName, _aiCredentialsProvider.Key);
-
- protected override AIServiceUsage GetAIServiceUsage(ChatMessageContent chatMessage) =>
- chatMessage.Metadata?.GetValueOrDefault("Usage") is CompletionsUsage completionsUsage
- ? new(PromptTokens: completionsUsage.PromptTokens, CompletionTokens: completionsUsage.CompletionTokens)
- : AIServiceUsage.None;
-}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/PromptModerationService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/PromptModerationService.cs
index 0ca15e4161..2668300526 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/PromptModerationService.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/PromptModerationService.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
+using AdvancedPaste.Services;
using ManagedCommon;
using OpenAI.Moderations;
@@ -23,7 +24,16 @@ public sealed class PromptModerationService(IAICredentialsProvider aiCredentials
{
try
{
- ModerationClient moderationClient = new(ModelName, _aiCredentialsProvider.Key);
+ _aiCredentialsProvider.Refresh();
+ var apiKey = _aiCredentialsProvider.GetKey()?.Trim() ?? string.Empty;
+
+ if (string.IsNullOrEmpty(apiKey))
+ {
+ Logger.LogWarning("Skipping OpenAI moderation because no credential is configured.");
+ return;
+ }
+
+ ModerationClient moderationClient = new(ModelName, apiKey);
var moderationClientResult = await moderationClient.ClassifyTextAsync(fullPrompt, cancellationToken);
var moderationResult = moderationClientResult.Value;
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/VaultCredentialsProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/VaultCredentialsProvider.cs
deleted file mode 100644
index 169c1c2422..0000000000
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/OpenAI/VaultCredentialsProvider.cs
+++ /dev/null
@@ -1,37 +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 System;
-
-using Windows.Security.Credentials;
-
-namespace AdvancedPaste.Services.OpenAI;
-
-public sealed class VaultCredentialsProvider : IAICredentialsProvider
-{
- public VaultCredentialsProvider() => Refresh();
-
- public string Key { get; private set; }
-
- public bool IsConfigured => !string.IsNullOrEmpty(Key);
-
- public bool Refresh()
- {
- var oldKey = Key;
- Key = LoadKey();
- return oldKey != Key;
- }
-
- private static string LoadKey()
- {
- try
- {
- return new PasswordVault().Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey")?.Password ?? string.Empty;
- }
- catch (Exception)
- {
- return string.Empty;
- }
- }
-}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs
index 5d6740977b..aef9e39bb9 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs
@@ -8,15 +8,16 @@ using System.Threading.Tasks;
using AdvancedPaste.Helpers;
using AdvancedPaste.Models;
+using AdvancedPaste.Services.CustomActions;
using Microsoft.PowerToys.Telemetry;
using Windows.ApplicationModel.DataTransfer;
namespace AdvancedPaste.Services;
-public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomTextTransformService customTextTransformService) : IPasteFormatExecutor
+public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomActionTransformService customActionTransformService) : IPasteFormatExecutor
{
private readonly IKernelService _kernelService = kernelService;
- private readonly ICustomTextTransformService _customTextTransformService = customTextTransformService;
+ private readonly ICustomActionTransformService _customActionTransformService = customActionTransformService;
public async Task ExecutePasteFormatAsync(PasteFormat pasteFormat, PasteActionSource source, CancellationToken cancellationToken, IProgress progress)
{
@@ -36,7 +37,7 @@ public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomTex
pasteFormat.Format switch
{
PasteFormats.KernelQuery => await _kernelService.TransformClipboardAsync(pasteFormat.Prompt, clipboardData, pasteFormat.IsSavedQuery, cancellationToken, progress),
- PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText(await _customTextTransformService.TransformTextAsync(pasteFormat.Prompt, await clipboardData.GetTextAsync(), cancellationToken, progress)),
+ PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText((await _customActionTransformService.TransformTextAsync(pasteFormat.Prompt, await clipboardData.GetClipboardTextOrThrowAsync(cancellationToken), cancellationToken, progress))?.Content ?? string.Empty),
_ => await TransformHelpers.TransformAsync(format, clipboardData, cancellationToken, progress),
});
}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw b/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw
index 604cbf403b..f365778321 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw
@@ -144,16 +144,67 @@
The paste operation was moderated due to sensitive content. Please try another query.
-
+
Clipboard history
Clipboard history
+
+ AI provider selector
+
+
+ Select an AI provider
+
+
+ Active provider: {0}
+
+
+ Configured models
+
+
+ No models configured
+
+
+ Configure models in Settings
+
Image data
Label used to represent an image in the clipboard history
+
+ Text
+
+
+ Image
+
+
+ Audio
+
+
+ Video
+
+
+ File
+
+
+ Clipboard
+
+
+ Copied just now
+
+
+ Copied {0} sec ago
+
+
+ Copied {0} min ago
+
+
+ Copied {0} hr ago
+
+
+ Copied {0} day ago
+
More options
@@ -196,7 +247,7 @@
Transcode to .mp3
Option to transcode audio files to MP3 format
-
+
Transcode to .mp4 (H.264/AAC)
Option to transcode video files to MP4 format with H.264 video codec and AAC audio codec
@@ -272,11 +323,11 @@
Next result
-
- OpenAI Privacy
+
+ Privacy Policy
-
- OpenAI Terms
+
+ Terms
To custom with AI is disabled by your organization
@@ -287,4 +338,38 @@
PowerToys_Paste_
+
+ Just now
+
+
+ 1 minute ago
+
+
+ {0} minutes ago
+
+
+ Today, {0}
+
+
+ Yesterday, {0}
+
+
+ {0}, {1}
+ (e.g., “Wednesday, 17:05â€)
+
+
+ {0}, {1}
+ (e.g., "10/20/2025, 17:05" in the user's locale)
+
+
+ You are using a custom endpoint. Verify all answers.
+
+
+ Local
+ Badge label displayed next to local AI model providers (e.g., Ollama, Foundry Local) to indicate the model runs locally
+
+
+ Unable to load Foundry Local model: {0}
+ {0} is the model identifier. Do not translate {0}.
+
\ No newline at end of file
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteCustomActionErrorEvent.cs b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteCustomActionErrorEvent.cs
new file mode 100644
index 0000000000..06f45a98ae
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteCustomActionErrorEvent.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace AdvancedPaste.Telemetry;
+
+[EventData]
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
+public sealed class AdvancedPasteCustomActionErrorEvent : EventBase, IEvent
+{
+ public AdvancedPasteCustomActionErrorEvent(AIServiceType providerType, string modelName, int statusCode, string error)
+ {
+ ProviderType = providerType.ToString();
+ ModelName = modelName;
+ StatusCode = statusCode;
+ Error = error;
+ }
+
+ public string ProviderType { get; set; }
+
+ public string ModelName { get; set; }
+
+ public int StatusCode { get; set; }
+
+ public string Error { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs
new file mode 100644
index 0000000000..671f6a7b9c
--- /dev/null
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteEndpointUsageEvent.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace AdvancedPaste.Telemetry;
+
+[EventData]
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
+public class AdvancedPasteEndpointUsageEvent : EventBase, IEvent
+{
+ ///
+ /// Gets or sets the AI provider type (e.g., OpenAI, AzureOpenAI, Google).
+ ///
+ public string ProviderType { get; set; }
+
+ ///
+ /// Gets or sets the configured model name.
+ ///
+ public string ModelName { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the advanced AI pipeline was used.
+ ///
+ public bool IsAdvanced { get; set; }
+
+ ///
+ /// Gets or sets the total duration in milliseconds, or -1 if unavailable.
+ ///
+ public int DurationMs { get; set; }
+
+ public AdvancedPasteEndpointUsageEvent(AIServiceType providerType, string modelName, bool isAdvanced, int durationMs = -1)
+ {
+ ProviderType = providerType.ToString();
+ ModelName = modelName;
+ IsAdvanced = isAdvanced;
+ DurationMs = durationMs;
+ }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+}
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteSemanticKernelFormatEvent.cs b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteSemanticKernelFormatEvent.cs
index 70542da6c8..53b4008782 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteSemanticKernelFormatEvent.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/Telemetry/AdvancedPasteSemanticKernelFormatEvent.cs
@@ -14,7 +14,7 @@ namespace AdvancedPaste.Telemetry;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
-public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string actionChain) : EventBase, IEvent
+public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string providerType, string actionChain) : EventBase, IEvent
{
public static string FormatActionChain(IEnumerable actionChain) => FormatActionChain(actionChain.Select(item => item.Format));
@@ -30,6 +30,8 @@ public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSaved
public string ModelName { get; set; } = modelName;
+ public string ProviderType { get; set; } = providerType;
+
///
/// Gets or sets a comma-separated list of paste formats used - in the same order they were executed.
/// Conceptually an array but formatted this way to work around https://github.com/dotnet/runtime/issues/10428
diff --git a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
index 688c3047e2..8edd9b76ad 100644
--- a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
+++ b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
+using System.Globalization;
using System.IO.Abstractions;
using System.Linq;
using System.Runtime.InteropServices;
@@ -22,6 +23,8 @@ using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.Win32;
using Windows.ApplicationModel.DataTransfer;
using Windows.System;
@@ -37,12 +40,20 @@ namespace AdvancedPaste.ViewModels
private readonly DispatcherTimer _clipboardTimer;
private readonly IUserSettings _userSettings;
private readonly IPasteFormatExecutor _pasteFormatExecutor;
- private readonly IAICredentialsProvider _aiCredentialsProvider;
+ private readonly IAICredentialsProvider _credentialsProvider;
private CancellationTokenSource _pasteActionCancellationTokenSource;
+ private string _currentClipboardHistoryId;
+ private DateTimeOffset? _currentClipboardTimestamp;
+ private ClipboardFormat _lastClipboardFormats = ClipboardFormat.None;
+ private bool _clipboardHistoryUnavailableLogged;
+
public DataPackageView ClipboardData { get; set; }
+ [ObservableProperty]
+ private ClipboardItem _currentClipboardItem;
+
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsCustomAIAvailable))]
[NotifyPropertyChangedFor(nameof(ClipboardHasData))]
@@ -52,12 +63,21 @@ namespace AdvancedPaste.ViewModels
private ClipboardFormat _availableClipboardFormats;
[ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(ShowClipboardHistoryButton))]
private bool _clipboardHistoryEnabled;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CustomAIUnavailableErrorText))]
[NotifyPropertyChangedFor(nameof(IsCustomAIServiceEnabled))]
[NotifyPropertyChangedFor(nameof(IsCustomAIAvailable))]
+ [NotifyPropertyChangedFor(nameof(AllowedAIProviders))]
+ [NotifyPropertyChangedFor(nameof(ActiveAIProvider))]
+ [NotifyPropertyChangedFor(nameof(ActiveAIProviderTooltip))]
+ [NotifyPropertyChangedFor(nameof(TermsLinkUri))]
+ [NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
+ [NotifyPropertyChangedFor(nameof(HasTermsLink))]
+ [NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
+ [NotifyPropertyChangedFor(nameof(HasLegalLinks))]
private bool _isAllowedByGPO;
[ObservableProperty]
@@ -79,19 +99,146 @@ namespace AdvancedPaste.ViewModels
public ObservableCollection CustomActionPasteFormats { get; } = [];
- public bool IsCustomAIServiceEnabled => IsAllowedByGPO && _aiCredentialsProvider.IsConfigured;
+ public bool IsCustomAIServiceEnabled
+ {
+ get
+ {
+ if (!IsAllowedByGPO || !_userSettings.IsAIEnabled)
+ {
+ return false;
+ }
+
+ // Check if there are any allowed providers
+ if (!AllowedAIProviders.Any())
+ {
+ return false;
+ }
+
+ // We should handle the IsAIEnabled logic in settings, don't check again here.
+ // If setting says yes, and here should pass check, and if error happens, it happens.
+ return true;
+ }
+ }
public bool IsCustomAIAvailable => IsCustomAIServiceEnabled && ClipboardHasDataForCustomAI;
- public bool IsAdvancedAIEnabled => IsCustomAIServiceEnabled && _userSettings.IsAdvancedAIEnabled;
+ public bool IsAdvancedAIEnabled
+ {
+ get
+ {
+ if (!IsAllowedByGPO || !_userSettings.IsAIEnabled)
+ {
+ return false;
+ }
+
+ if (!TryResolveAdvancedAIProvider(out _))
+ {
+ return false;
+ }
+
+ return _credentialsProvider.IsConfigured();
+ }
+ }
+
+ public ObservableCollection AIProviders => _userSettings?.PasteAIConfiguration?.Providers ?? new ObservableCollection();
+
+ public IEnumerable AllowedAIProviders
+ {
+ get
+ {
+ var providers = AIProviders;
+ if (providers is null || providers.Count == 0)
+ {
+ return Enumerable.Empty();
+ }
+
+ return providers.Where(IsProviderAllowedByGPO);
+ }
+ }
+
+ public PasteAIProviderDefinition ActiveAIProvider
+ {
+ get
+ {
+ var provider = _userSettings?.PasteAIConfiguration?.ActiveProvider;
+ if (provider is null || !IsProviderAllowedByGPO(provider))
+ {
+ return null;
+ }
+
+ return provider;
+ }
+ }
+
+ public string ActiveAIProviderTooltip
+ {
+ get
+ {
+ var resourceLoader = ResourceLoaderInstance.ResourceLoader;
+ var provider = ActiveAIProvider;
+
+ if (provider is null)
+ {
+ return resourceLoader.GetString("AIProviderButtonTooltipEmpty");
+ }
+
+ var format = resourceLoader.GetString("AIProviderButtonTooltipFormat");
+ var displayName = provider.DisplayName;
+
+ if (!string.IsNullOrEmpty(format))
+ {
+ return string.Format(CultureInfo.CurrentCulture, format, displayName);
+ }
+
+ return displayName;
+ }
+ }
+
+ private AIServiceTypeMetadata GetActiveProviderMetadata()
+ {
+ var provider = ActiveAIProvider ?? AllowedAIProviders.FirstOrDefault();
+ var serviceType = provider?.ServiceTypeKind ?? AIServiceType.OpenAI;
+ return AIServiceTypeRegistry.GetMetadata(serviceType);
+ }
+
+ public Uri TermsLinkUri
+ {
+ get
+ {
+ var metadata = GetActiveProviderMetadata();
+ return metadata.HasTermsLink ? metadata.TermsUri : null;
+ }
+ }
+
+ public Uri PrivacyLinkUri
+ {
+ get
+ {
+ var metadata = GetActiveProviderMetadata();
+ return metadata.HasPrivacyLink ? metadata.PrivacyUri : null;
+ }
+ }
+
+ public bool HasTermsLink => GetActiveProviderMetadata().HasTermsLink;
+
+ public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
+
+ public bool HasLegalLinks => HasTermsLink || HasPrivacyLink;
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
+ public bool ShowClipboardPreview => _userSettings.EnableClipboardPreview;
+
+ public bool ShowClipboardHistoryButton => ClipboardHistoryEnabled;
+
public bool HasIndeterminateTransformProgress => double.IsNaN(TransformProgress);
- private PasteFormats CustomAIFormat => _userSettings.IsAdvancedAIEnabled ? PasteFormats.KernelQuery : PasteFormats.CustomTextTransformation;
+ private PasteFormats CustomAIFormat =>
+ _userSettings.IsAIEnabled && TryResolveAdvancedAIProvider(out _)
+ ? PasteFormats.KernelQuery
+ : PasteFormats.CustomTextTransformation;
private bool Visible
{
@@ -110,9 +257,9 @@ namespace AdvancedPaste.ViewModels
public event EventHandler PreviewRequested;
- public OptionsViewModel(IFileSystem fileSystem, IAICredentialsProvider aiCredentialsProvider, IUserSettings userSettings, IPasteFormatExecutor pasteFormatExecutor)
+ public OptionsViewModel(IFileSystem fileSystem, IAICredentialsProvider credentialsProvider, IUserSettings userSettings, IPasteFormatExecutor pasteFormatExecutor)
{
- _aiCredentialsProvider = aiCredentialsProvider;
+ _credentialsProvider = credentialsProvider;
_userSettings = userSettings;
_pasteFormatExecutor = pasteFormatExecutor;
@@ -130,6 +277,7 @@ namespace AdvancedPaste.ViewModels
_clipboardTimer.Start();
RefreshPasteFormats();
+ UpdateAIProviderActiveFlags();
_userSettings.Changed += UserSettings_Changed;
PropertyChanged += (_, e) =>
{
@@ -158,15 +306,21 @@ namespace AdvancedPaste.ViewModels
if (Visible)
{
await ReadClipboardAsync();
- UpdateAllowedByGPO();
}
}
private void UserSettings_Changed(object sender, EventArgs e)
{
+ UpdateAIProviderActiveFlags();
+ OnPropertyChanged(nameof(IsCustomAIServiceEnabled));
OnPropertyChanged(nameof(ClipboardHasDataForCustomAI));
OnPropertyChanged(nameof(IsCustomAIAvailable));
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
+ OnPropertyChanged(nameof(AIProviders));
+ OnPropertyChanged(nameof(AllowedAIProviders));
+ OnPropertyChanged(nameof(ShowClipboardPreview));
+
+ NotifyActiveProviderChanged();
EnqueueRefreshPasteFormats();
}
@@ -192,6 +346,33 @@ namespace AdvancedPaste.ViewModels
private PasteFormat CreateCustomAIPasteFormat(string name, string prompt, bool isSavedQuery) =>
PasteFormat.CreateCustomAIFormat(CustomAIFormat, name, prompt, isSavedQuery, AvailableClipboardFormats, IsCustomAIServiceEnabled);
+ private void UpdateAIProviderActiveFlags()
+ {
+ var providers = _userSettings?.PasteAIConfiguration?.Providers;
+ if (providers is not null)
+ {
+ var activeId = ActiveAIProvider?.Id;
+
+ foreach (var provider in providers)
+ {
+ provider.IsActive = !string.IsNullOrEmpty(activeId) && string.Equals(provider.Id, activeId, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ NotifyActiveProviderChanged();
+ }
+
+ private void NotifyActiveProviderChanged()
+ {
+ OnPropertyChanged(nameof(ActiveAIProvider));
+ OnPropertyChanged(nameof(ActiveAIProviderTooltip));
+ OnPropertyChanged(nameof(TermsLinkUri));
+ OnPropertyChanged(nameof(PrivacyLinkUri));
+ OnPropertyChanged(nameof(HasTermsLink));
+ OnPropertyChanged(nameof(HasPrivacyLink));
+ OnPropertyChanged(nameof(HasLegalLinks));
+ }
+
private void RefreshPasteFormats()
{
var ctrlString = ResourceLoaderInstance.ResourceLoader.GetString("CtrlKey");
@@ -253,8 +434,96 @@ namespace AdvancedPaste.ViewModels
return;
}
- ClipboardData = Clipboard.GetContent();
- AvailableClipboardFormats = await ClipboardData.GetAvailableFormatsAsync();
+ try
+ {
+ ClipboardData = Clipboard.GetContent();
+ AvailableClipboardFormats = ClipboardData != null ? await ClipboardData.GetAvailableFormatsAsync() : ClipboardFormat.None;
+ }
+ catch (Exception ex) when (ex is COMException or InvalidOperationException)
+ {
+ // Logger.LogDebug("Failed to read clipboard content", ex);
+ ClipboardData = null;
+ AvailableClipboardFormats = ClipboardFormat.None;
+ }
+
+ await UpdateClipboardPreviewAsync();
+ }
+
+ private async Task UpdateClipboardPreviewAsync()
+ {
+ if (ClipboardData is null || !ClipboardHasData)
+ {
+ ResetClipboardPreview();
+ _currentClipboardHistoryId = null;
+ _currentClipboardTimestamp = null;
+ _lastClipboardFormats = ClipboardFormat.None;
+ return;
+ }
+
+ var formatsChanged = AvailableClipboardFormats != _lastClipboardFormats;
+ _lastClipboardFormats = AvailableClipboardFormats;
+
+ var clipboardChanged = await UpdateClipboardTimestampAsync(formatsChanged);
+
+ // Create ClipboardItem directly from current clipboard data using helper
+ CurrentClipboardItem = await ClipboardItemHelper.CreateFromCurrentClipboardAsync(
+ ClipboardData,
+ AvailableClipboardFormats,
+ _currentClipboardTimestamp,
+ clipboardChanged ? null : CurrentClipboardItem?.Image);
+ }
+
+ private async Task UpdateClipboardTimestampAsync(bool formatsChanged)
+ {
+ bool clipboardChanged = formatsChanged;
+
+ if (Clipboard.IsHistoryEnabled())
+ {
+ try
+ {
+ var historyItems = await Clipboard.GetHistoryItemsAsync();
+ if (historyItems.Status == ClipboardHistoryItemsResultStatus.Success && historyItems.Items.Count > 0)
+ {
+ var latest = historyItems.Items[0];
+ if (_currentClipboardHistoryId != latest.Id)
+ {
+ clipboardChanged = true;
+ _currentClipboardHistoryId = latest.Id;
+ }
+
+ _currentClipboardTimestamp = latest.Timestamp;
+ _clipboardHistoryUnavailableLogged = false;
+ return clipboardChanged;
+ }
+ }
+ catch (Exception ex)
+ {
+ if (!_clipboardHistoryUnavailableLogged)
+ {
+ Logger.LogDebug("Failed to access clipboard history timestamp", ex.Message);
+ _clipboardHistoryUnavailableLogged = true;
+ }
+ }
+ }
+
+ if (!_currentClipboardTimestamp.HasValue || clipboardChanged)
+ {
+ _currentClipboardTimestamp = DateTimeOffset.Now;
+ clipboardChanged = true;
+ }
+
+ return clipboardChanged;
+ }
+
+ private void ResetClipboardPreview()
+ {
+ // Clear to avoid leaks due to Garbage Collection not clearing the bitmap from memory
+ if (CurrentClipboardItem?.Image is not null)
+ {
+ CurrentClipboardItem.Image.ClearValue(BitmapImage.UriSourceProperty);
+ }
+
+ CurrentClipboardItem = null;
}
public async Task OnShowAsync()
@@ -270,7 +539,7 @@ namespace AdvancedPaste.ViewModels
_dispatcherQueue.TryEnqueue(() =>
{
- GetMainWindow()?.FinishLoading(_aiCredentialsProvider.IsConfigured);
+ GetMainWindow()?.FinishLoading(IsCustomAIServiceEnabled);
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
OnPropertyChanged(nameof(CustomAIUnavailableErrorText));
OnPropertyChanged(nameof(IsCustomAIServiceEnabled));
@@ -319,7 +588,7 @@ namespace AdvancedPaste.ViewModels
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
}
- if (!_aiCredentialsProvider.IsConfigured)
+ if (!IsCustomAIServiceEnabled)
{
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
}
@@ -515,11 +784,113 @@ namespace AdvancedPaste.ViewModels
IsAllowedByGPO = PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled;
}
+ private bool IsProviderAllowedByGPO(PasteAIProviderDefinition provider)
+ {
+ if (provider is null)
+ {
+ return false;
+ }
+
+ var serviceType = provider.ServiceType.ToAIServiceType();
+ var metadata = AIServiceTypeRegistry.GetMetadata(serviceType);
+
+ // Check global online AI GPO for online services
+ if (metadata.IsOnlineService && !IsAllowedByGPO)
+ {
+ return false;
+ }
+
+ // Check individual endpoint GPO
+ return serviceType switch
+ {
+ AIServiceType.OpenAI => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOpenAIValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ AIServiceType.AzureOpenAI => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteAzureOpenAIValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ AIServiceType.AzureAIInference => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteAzureAIInferenceValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ AIServiceType.Mistral => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteMistralValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ AIServiceType.Google => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteGoogleValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ AIServiceType.Ollama => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOllamaValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ AIServiceType.FoundryLocal => PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteFoundryLocalValue() != PowerToys.GPOWrapper.GpoRuleConfigured.Disabled,
+ _ => true, // Allow unknown types by default
+ };
+ }
+
+ private bool TryResolveAdvancedAIProvider(out PasteAIProviderDefinition provider)
+ {
+ provider = null;
+
+ var configuration = _userSettings?.PasteAIConfiguration;
+ if (configuration is null)
+ {
+ return false;
+ }
+
+ var activeProvider = configuration.ActiveProvider;
+ if (IsAdvancedAIProvider(activeProvider))
+ {
+ provider = activeProvider;
+ return true;
+ }
+
+ if (activeProvider is not null)
+ {
+ return false;
+ }
+
+ var fallback = configuration.Providers?.FirstOrDefault(IsAdvancedAIProvider);
+ if (fallback is not null)
+ {
+ provider = fallback;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool IsAdvancedAIProvider(PasteAIProviderDefinition provider)
+ {
+ return provider is not null && provider.EnableAdvancedAI && SupportsAdvancedAI(provider.ServiceTypeKind);
+ }
+
+ private static bool SupportsAdvancedAI(AIServiceType serviceType)
+ {
+ return serviceType is AIServiceType.OpenAI
+ or AIServiceType.AzureOpenAI;
+ }
+
private bool UpdateOpenAIKey()
{
UpdateAllowedByGPO();
- return IsAllowedByGPO && _aiCredentialsProvider.Refresh();
+ return _credentialsProvider.Refresh();
+ }
+
+ [RelayCommand]
+ private async Task SetActiveProviderAsync(PasteAIProviderDefinition provider)
+ {
+ if (provider is null || string.IsNullOrEmpty(provider.Id))
+ {
+ return;
+ }
+
+ if (string.Equals(ActiveAIProvider?.Id, provider.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ try
+ {
+ await _userSettings.SetActiveAIProviderAsync(provider.Id);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Failed to activate AI provider", ex);
+ return;
+ }
+
+ UpdateAIProviderActiveFlags();
+ OnPropertyChanged(nameof(AIProviders));
+ NotifyActiveProviderChanged();
+ EnqueueRefreshPasteFormats();
}
public async Task CancelPasteActionAsync()
diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj
index 083aa868d3..2cf2920673 100644
--- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj
+++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj
@@ -2,7 +2,7 @@
-
+
15.0
diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp
index 6af0d636ac..c7d22d474f 100644
--- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp
+++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp
@@ -16,7 +16,8 @@
#include
#include
-#include
+#include
+#include
#include
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
@@ -54,12 +55,14 @@ namespace
const wchar_t JSON_KEY_ADVANCED_PASTE_UI_HOTKEY[] = L"advanced-paste-ui-hotkey";
const wchar_t JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY[] = L"paste-as-markdown-hotkey";
const wchar_t JSON_KEY_PASTE_AS_JSON_HOTKEY[] = L"paste-as-json-hotkey";
- const wchar_t JSON_KEY_IS_ADVANCED_AI_ENABLED[] = L"IsAdvancedAIEnabled";
+ const wchar_t JSON_KEY_IS_AI_ENABLED[] = L"IsAIEnabled";
+ const wchar_t JSON_KEY_IS_OPEN_AI_ENABLED[] = L"IsOpenAIEnabled";
const wchar_t JSON_KEY_SHOW_CUSTOM_PREVIEW[] = L"ShowCustomPreview";
+ const wchar_t JSON_KEY_PASTE_AI_CONFIGURATION[] = L"paste-ai-configuration";
+ const wchar_t JSON_KEY_PROVIDERS[] = L"providers";
+ const wchar_t JSON_KEY_SERVICE_TYPE[] = L"service-type";
+ const wchar_t JSON_KEY_ENABLE_ADVANCED_AI[] = L"enable-advanced-ai";
const wchar_t JSON_KEY_VALUE[] = L"value";
-
- const wchar_t OPENAI_VAULT_RESOURCE[] = L"https://platform.openai.com/api-keys";
- const wchar_t OPENAI_VAULT_USERNAME[] = L"PowerToys_AdvancedPaste_OpenAIKey";
}
class AdvancedPaste : public PowertoyModuleIface
@@ -94,6 +97,7 @@ private:
using CustomAction = ActionData;
std::vector m_custom_actions;
+ bool m_is_ai_enabled = false;
bool m_is_advanced_ai_enabled = false;
bool m_preview_custom_format_output = true;
@@ -145,32 +149,11 @@ private:
return jsonObject;
}
- static bool open_ai_key_exists()
- {
- try
- {
- winrt::Windows::Security::Credentials::PasswordVault().Retrieve(OPENAI_VAULT_RESOURCE, OPENAI_VAULT_USERNAME);
- return true;
- }
- catch (const winrt::hresult_error& ex)
- {
- // Looks like the only way to access the PasswordVault is through an API that throws an exception in case the resource doesn't exist.
- // If the debugger breaks here, just continue.
- // If you want to disable breaking here in a more permanent way, just add a condition in Visual Studio's Exception Settings to not break on win::hresult_error, but that might make you not hit other exceptions you might want to catch.
- if (ex.code() == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
- {
- return false; // Credential doesn't exist.
- }
- Logger::error("Unexpected error while retrieving OpenAI key from vault: {}", winrt::to_string(ex.message()));
- return false;
- }
- }
-
- bool is_open_ai_enabled()
+ bool is_ai_enabled()
{
return gpo_policy_enabled_configuration() != powertoys_gpo::gpo_rule_configured_disabled &&
powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue() != powertoys_gpo::gpo_rule_configured_disabled &&
- open_ai_key_exists();
+ m_is_ai_enabled;
}
static std::wstring kebab_to_pascal_case(const std::wstring& kebab_str)
@@ -201,6 +184,13 @@ private:
return result;
}
+ static std::wstring to_lower_case(const std::wstring& value)
+ {
+ std::wstring result = value;
+ std::transform(result.begin(), result.end(), result.begin(), [](wchar_t ch) { return std::towlower(ch); });
+ return result;
+ }
+
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
{
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
@@ -267,6 +257,61 @@ private:
}
}
+ bool has_advanced_ai_provider(const winrt::Windows::Data::Json::JsonObject& propertiesObject)
+ {
+ if (!propertiesObject.HasKey(JSON_KEY_PASTE_AI_CONFIGURATION))
+ {
+ return false;
+ }
+
+ const auto configValue = propertiesObject.GetNamedValue(JSON_KEY_PASTE_AI_CONFIGURATION);
+ if (configValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
+ {
+ return false;
+ }
+
+ const auto configObject = configValue.GetObjectW();
+ if (!configObject.HasKey(JSON_KEY_PROVIDERS))
+ {
+ return false;
+ }
+
+ const auto providersValue = configObject.GetNamedValue(JSON_KEY_PROVIDERS);
+ if (providersValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Array)
+ {
+ return false;
+ }
+
+ const auto providers = providersValue.GetArray();
+ for (const auto providerValue : providers)
+ {
+ if (providerValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
+ {
+ continue;
+ }
+
+ const auto providerObject = providerValue.GetObjectW();
+ if (!providerObject.GetNamedBoolean(JSON_KEY_ENABLE_ADVANCED_AI, false))
+ {
+ continue;
+ }
+
+ if (!providerObject.HasKey(JSON_KEY_SERVICE_TYPE))
+ {
+ continue;
+ }
+
+ const std::wstring serviceType = providerObject.GetNamedString(JSON_KEY_SERVICE_TYPE, L"").c_str();
+ const auto normalizedServiceType = to_lower_case(serviceType);
+ if (normalizedServiceType == L"openai" || normalizedServiceType == L"azureopenai")
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
void read_settings(PowerToysSettings::PowerToyValues& settings)
{
const auto settingsObject = settings.get_raw_json();
@@ -341,7 +386,7 @@ private:
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
{
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
- if (customActions.Size() > 0 && is_open_ai_enabled())
+ if (customActions.Size() > 0 && is_ai_enabled())
{
for (const auto& customAction : customActions)
{
@@ -365,9 +410,19 @@ private:
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
- if (propertiesObject.HasKey(JSON_KEY_IS_ADVANCED_AI_ENABLED))
+ m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject);
+
+ if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED))
{
- m_is_advanced_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_ADVANCED_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE);
+ m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
+ }
+ else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED))
+ {
+ m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
+ }
+ else
+ {
+ m_is_ai_enabled = false;
}
if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
diff --git a/src/modules/AdvancedPaste/UITest-AdvancedPaste/TestFiles/settings.json b/src/modules/AdvancedPaste/UITest-AdvancedPaste/TestFiles/settings.json
index 31ad05c701..bc0803796e 100644
--- a/src/modules/AdvancedPaste/UITest-AdvancedPaste/TestFiles/settings.json
+++ b/src/modules/AdvancedPaste/UITest-AdvancedPaste/TestFiles/settings.json
@@ -1 +1 @@
-{"properties":{"IsAdvancedAIEnabled":{"value":false},"ShowCustomPreview":{"value":true},"CloseAfterLosingFocus":{"value":false},"advanced-paste-ui-hotkey":{"win":true,"ctrl":false,"alt":false,"shift":true,"code":86,"key":""},"paste-as-plain-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":79,"key":""},"paste-as-markdown-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":77,"key":""},"paste-as-json-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":74,"key":""},"custom-actions":{"value":[]},"additional-actions":{"image-to-text":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-file":{"isShown":true,"paste-as-txt-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-png-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-html-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}},"transcode":{"isShown":true,"transcode-to-mp3":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"transcode-to-mp4":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}}}},"name":"AdvancedPaste","version":"1"}
\ No newline at end of file
+{"properties":{"IsAIEnabled":{"value":false},"ShowCustomPreview":{"value":true},"CloseAfterLosingFocus":{"value":false},"advanced-paste-ui-hotkey":{"win":true,"ctrl":false,"alt":false,"shift":true,"code":86,"key":""},"paste-as-plain-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":79,"key":""},"paste-as-markdown-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":77,"key":""},"paste-as-json-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":74,"key":""},"custom-actions":{"value":[]},"additional-actions":{"image-to-text":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-file":{"isShown":true,"paste-as-txt-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-png-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-html-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}},"transcode":{"isShown":true,"transcode-to-mp3":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"transcode-to-mp4":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}}},"paste-ai-configuration":{"active-provider-id":"","providers":[],"use-shared-credentials":true}},"name":"AdvancedPaste","version":"1"}
\ No newline at end of file
diff --git a/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/MainWindow.xaml b/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/MainWindow.xaml
index c48b7fbb25..ae77a78caa 100644
--- a/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/MainWindow.xaml
+++ b/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariablesXAML/MainWindow.xaml
@@ -20,7 +20,7 @@
-
+
-
+
_userSettings;
private static Mock _elevationHelper;
+ private static Mock _backupManager;
// Case1: Fuzzing method for ValidIPv4
public static void FuzzValidIPv4(ReadOnlySpan input)
@@ -73,9 +70,10 @@ namespace Hosts.FuzzTests
_userSettings = new Mock();
_elevationHelper = new Mock();
_elevationHelper.Setup(m => m.IsElevated).Returns(true);
+ _backupManager = new Mock();
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
string input = System.Text.Encoding.UTF8.GetString(data);
diff --git a/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj b/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj
index 667cfcc0ad..51dee7a40b 100644
--- a/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj
+++ b/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj
@@ -30,8 +30,11 @@
+
+
+
diff --git a/src/modules/Hosts/Hosts.Tests/BackupManagerTest.cs b/src/modules/Hosts/Hosts.Tests/BackupManagerTest.cs
new file mode 100644
index 0000000000..6aeb834029
--- /dev/null
+++ b/src/modules/Hosts/Hosts.Tests/BackupManagerTest.cs
@@ -0,0 +1,156 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO.Abstractions.TestingHelpers;
+using HostsUILib.Helpers;
+using HostsUILib.Settings;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+
+namespace Hosts.Tests
+{
+ [TestClass]
+ public class BackupManagerTest
+ {
+ private const string HostsPath = @"C:\Windows\System32\Drivers\etc\hosts";
+ private const string BackupPath = @"C:\Backup\hosts";
+ private const string BackupSearchPattern = $"*_PowerToysBackup_*";
+
+ [TestMethod]
+ public void Hosts_Backup_Not_Executed()
+ {
+ var fileSystem = new MockFileSystem();
+ SetupFiles(fileSystem, true);
+ var userSettings = new Mock();
+ userSettings.Setup(m => m.BackupHosts).Returns(false);
+ userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ var backupManager = new BackupManager(fileSystem, userSettings.Object);
+ backupManager.Create(HostsPath);
+
+ Assert.AreEqual(0, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
+ }
+
+ [TestMethod]
+ public void Hosts_Backup_Executed_Once()
+ {
+ var fileSystem = new MockFileSystem();
+ SetupFiles(fileSystem, true);
+ var userSettings = new Mock();
+ userSettings.Setup(m => m.BackupHosts).Returns(true);
+ userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ var backupManager = new BackupManager(fileSystem, userSettings.Object);
+ backupManager.Create(HostsPath);
+ backupManager.Create(HostsPath);
+
+ Assert.AreEqual(1, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
+ var hostsContent = fileSystem.File.ReadAllText(HostsPath);
+ var backupContent = fileSystem.File.ReadAllText(fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern)[0]);
+ Assert.AreEqual(hostsContent, backupContent);
+ }
+
+ [DataTestMethod]
+ [DataRow(-10, -10)]
+ [DataRow(-10, 0)]
+ [DataRow(-10, 10)]
+ [DataRow(0, -10)]
+ [DataRow(0, 0)]
+ [DataRow(0, 10)]
+ [DataRow(10, -10)]
+ [DataRow(10, 0)]
+ [DataRow(10, 10)]
+ public void Hosts_Backups_Delete_Never(int count, int days)
+ {
+ var fileSystem = new MockFileSystem();
+ SetupFiles(fileSystem, false);
+ var userSettings = new Mock();
+ userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ userSettings.Setup(m => m.DeleteBackupsMode).Returns(HostsDeleteBackupMode.Never);
+ var backupManager = new BackupManager(fileSystem, userSettings.Object);
+ backupManager.Delete();
+
+ Assert.AreEqual(30, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
+ Assert.AreEqual(31, fileSystem.Directory.GetFiles(BackupPath).Length);
+ }
+
+ [DataTestMethod]
+ [DataRow(-10, 30)]
+ [DataRow(0, 30)]
+ [DataRow(10, 10)]
+ public void Hosts_Backups_Delete_ByCount(int count, int expectedBackups)
+ {
+ var fileSystem = new MockFileSystem();
+ SetupFiles(fileSystem, false);
+ var userSettings = new Mock();
+ userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ userSettings.Setup(m => m.DeleteBackupsMode).Returns(HostsDeleteBackupMode.Count);
+ userSettings.Setup(m => m.DeleteBackupsCount).Returns(count);
+ var backupManager = new BackupManager(fileSystem, userSettings.Object);
+ backupManager.Delete();
+
+ Assert.AreEqual(expectedBackups, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
+ Assert.AreEqual(expectedBackups + 1, fileSystem.Directory.GetFiles(BackupPath).Length);
+ }
+
+ [DataTestMethod]
+ [DataRow(-10, -10, 30)]
+ [DataRow(-10, 0, 30)]
+ [DataRow(-10, 10, 5)]
+ [DataRow(0, -10, 30)]
+ [DataRow(0, 0, 30)]
+ [DataRow(0, 10, 5)]
+ [DataRow(10, -10, 30)]
+ [DataRow(10, 0, 30)]
+ [DataRow(5, 1, 5)]
+ [DataRow(1, 15, 10)]
+ [DataRow(2, 2, 2)]
+ public void Hosts_Backups_Delete_ByAge(int count, int days, int expectedBackups)
+ {
+ var fileSystem = new MockFileSystem();
+ SetupFiles(fileSystem, false);
+ var userSettings = new Mock();
+ userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ userSettings.Setup(m => m.DeleteBackupsMode).Returns(HostsDeleteBackupMode.Age);
+ userSettings.Setup(m => m.DeleteBackupsCount).Returns(count);
+ userSettings.Setup(m => m.DeleteBackupsDays).Returns(days);
+ var backupManager = new BackupManager(fileSystem, userSettings.Object);
+ backupManager.Delete();
+
+ Assert.AreEqual(expectedBackups, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
+ Assert.AreEqual(expectedBackups + 1, fileSystem.Directory.GetFiles(BackupPath).Length);
+ }
+
+ private void SetupFiles(MockFileSystem fileSystem, bool hostsOnly)
+ {
+ fileSystem.AddDirectory(BackupPath);
+ fileSystem.AddFile(HostsPath, new MockFileData("HOSTS FILE CONTENT"));
+
+ if (hostsOnly)
+ {
+ return;
+ }
+
+ var today = new DateTimeOffset(DateTime.Today);
+
+ var notBackupData = new MockFileData("NOT A BACKUP")
+ {
+ CreationTime = today.AddDays(-100),
+ };
+
+ fileSystem.AddFile(fileSystem.Path.Combine(BackupPath, "hosts_not_a_backup"), notBackupData);
+
+ // The first backup is from 5 days ago. There are 30 backups, one for each day.
+ var offset = 5;
+ for (var i = 0; i < 30; i++)
+ {
+ var backupData = new MockFileData("THIS IS A BACKUP")
+ {
+ CreationTime = today.AddDays(-i - offset),
+ };
+
+ fileSystem.AddFile(fileSystem.Path.Combine(BackupPath, $"hosts_PowerToysBackup_{i}"), backupData);
+ }
+ }
+ }
+}
diff --git a/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs b/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs
index 81052fd101..4c6ee77f8c 100644
--- a/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs
+++ b/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs
@@ -20,8 +20,10 @@ namespace Hosts.Tests
[TestClass]
public class HostsServiceTest
{
+ private const string BackupPath = @"C:\Backup\hosts";
private static Mock _userSettings;
private static Mock _elevationHelper;
+ private static Mock _backupManager;
[ClassInitialize]
public static void ClassInitialize(TestContext context)
@@ -29,27 +31,7 @@ namespace Hosts.Tests
_userSettings = new Mock();
_elevationHelper = new Mock();
_elevationHelper.Setup(m => m.IsElevated).Returns(true);
- }
-
- [TestMethod]
- public void Hosts_Exists()
- {
- var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
- fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty));
- var result = service.Exists();
-
- Assert.IsTrue(result);
- }
-
- [TestMethod]
- public void Hosts_Not_Exists()
- {
- var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
- var result = service.Exists();
-
- Assert.IsFalse(result);
+ _backupManager = new Mock();
}
[TestMethod]
@@ -67,7 +49,7 @@ namespace Hosts.Tests
";
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
var data = await service.ReadAsync();
@@ -92,7 +74,7 @@ namespace Hosts.Tests
";
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
var data = await service.ReadAsync();
@@ -118,7 +100,7 @@ namespace Hosts.Tests
";
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
var data = await service.ReadAsync();
@@ -137,7 +119,7 @@ namespace Hosts.Tests
public async Task Empty_Hosts()
{
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty));
await service.WriteAsync(string.Empty, Enumerable.Empty());
@@ -168,7 +150,7 @@ namespace Hosts.Tests
var fileSystem = new CustomMockFileSystem();
var userSettings = new Mock();
userSettings.Setup(m => m.AdditionalLinesPosition).Returns(HostsAdditionalLinesPosition.Top);
- var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
var data = await service.ReadAsync();
@@ -200,7 +182,7 @@ namespace Hosts.Tests
var fileSystem = new CustomMockFileSystem();
var userSettings = new Mock();
userSettings.Setup(m => m.AdditionalLinesPosition).Returns(HostsAdditionalLinesPosition.Bottom);
- var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
var data = await service.ReadAsync();
@@ -224,7 +206,7 @@ namespace Hosts.Tests
";
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
var data = await service.ReadAsync();
@@ -241,7 +223,7 @@ namespace Hosts.Tests
var elevationHelper = new Mock();
elevationHelper.Setup(m => m.IsElevated).Returns(false);
- var service = new HostsService(fileSystem, _userSettings.Object, elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, elevationHelper.Object, _backupManager.Object);
await Assert.ThrowsExceptionAsync(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty()));
}
@@ -249,7 +231,7 @@ namespace Hosts.Tests
public async Task Save_ReadOnlyHostsException()
{
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
var hostsFile = new MockFileData(string.Empty)
{
@@ -265,7 +247,7 @@ namespace Hosts.Tests
public void Remove_ReadOnly_Attribute()
{
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
var hostsFile = new MockFileData(string.Empty)
{
@@ -284,7 +266,7 @@ namespace Hosts.Tests
public async Task Save_Hidden_Hosts()
{
var fileSystem = new CustomMockFileSystem();
- var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
var hostsFile = new MockFileData(string.Empty)
{
@@ -316,7 +298,7 @@ namespace Hosts.Tests
var fs = new CustomMockFileSystem();
var settings = new Mock();
settings.Setup(s => s.NoLeadingSpaces).Returns(true);
- var svc = new HostsService(fs, settings.Object, _elevationHelper.Object);
+ var svc = new HostsService(fs, settings.Object, _elevationHelper.Object, _backupManager.Object);
fs.AddFile(svc.HostsFilePath, new MockFileData(content));
var data = await svc.ReadAsync();
@@ -327,5 +309,57 @@ namespace Hosts.Tests
var result = fs.GetFile(svc.HostsFilePath);
Assert.AreEqual(expected, result.TextContents);
}
+
+ [TestMethod]
+ public async Task Hosts_Backup_Not_Executed()
+ {
+ var content =
+@"10.1.1.1 host host.local # comment
+10.1.1.2 host2 host2.local # another comment
+";
+
+ var fileSystem = new CustomMockFileSystem();
+ fileSystem.AddDirectory(BackupPath);
+ _userSettings.Setup(m => m.BackupHosts).Returns(false);
+ _userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ var backupManager = new BackupManager(fileSystem, _userSettings.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, backupManager);
+
+ fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
+
+ var data = await service.ReadAsync();
+ var entries = data.Entries.ToList();
+ entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
+ await service.WriteAsync(data.AdditionalLines, data.Entries);
+
+ Assert.AreEqual(0, fileSystem.Directory.GetFiles(BackupPath).Length);
+ }
+
+ [TestMethod]
+ public async Task Hosts_Backup_Executed_Once()
+ {
+ var content =
+@"10.1.1.1 host host.local # comment
+10.1.1.2 host2 host2.local # another comment
+";
+
+ var fileSystem = new CustomMockFileSystem();
+ _userSettings.Setup(m => m.BackupHosts).Returns(true);
+ _userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
+ var backupManager = new BackupManager(fileSystem, _userSettings.Object);
+ var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, backupManager);
+
+ fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
+
+ var data = await service.ReadAsync();
+ var entries = data.Entries.ToList();
+ entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
+ await service.WriteAsync(data.AdditionalLines, data.Entries);
+ await service.WriteAsync(data.AdditionalLines, data.Entries);
+
+ Assert.AreEqual(1, fileSystem.Directory.GetFiles(BackupPath).Length);
+ var backupContent = fileSystem.File.ReadAllText(fileSystem.Directory.GetFiles(BackupPath)[0]);
+ Assert.AreEqual(content, backupContent);
+ }
}
}
diff --git a/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs b/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs
index fbe5d3662d..8dbec70de8 100644
--- a/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs
+++ b/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs
@@ -56,6 +56,7 @@ namespace Hosts
{
// Core Services
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
@@ -74,7 +75,7 @@ namespace Hosts
}).
Build();
- var cleanupBackupThread = new Thread(() =>
+ var deleteBackupThread = new Thread(() =>
{
// Delete old backups only if running elevated
if (!Host.GetService().IsElevated)
@@ -84,7 +85,7 @@ namespace Hosts
try
{
- Host.GetService().CleanupBackup();
+ Host.GetService().Delete();
}
catch (Exception ex)
{
@@ -92,8 +93,8 @@ namespace Hosts
}
});
- cleanupBackupThread.IsBackground = true;
- cleanupBackupThread.Start();
+ deleteBackupThread.IsBackground = true;
+ deleteBackupThread.Start();
UnhandledException += App_UnhandledException;
diff --git a/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml b/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml
index 92d1594556..001cbeb3ed 100644
--- a/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml
+++ b/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml
@@ -20,7 +20,7 @@
-
+
-
+
16.0
@@ -46,7 +46,7 @@
- $(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)
+ ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
diff --git a/src/modules/Hosts/HostsUILib/Helpers/BackupManager.cs b/src/modules/Hosts/HostsUILib/Helpers/BackupManager.cs
new file mode 100644
index 0000000000..5417408409
--- /dev/null
+++ b/src/modules/Hosts/HostsUILib/Helpers/BackupManager.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO.Abstractions;
+using System.Linq;
+using HostsUILib.Settings;
+
+namespace HostsUILib.Helpers
+{
+ public class BackupManager : IBackupManager
+ {
+ private const string BackupSuffix = "_PowerToysBackup_";
+ private readonly IFileSystem _fileSystem;
+ private readonly IUserSettings _userSettings;
+ private bool _backupDone;
+
+ public BackupManager(IFileSystem fileSystem, IUserSettings userSettings)
+ {
+ _fileSystem = fileSystem;
+ _userSettings = userSettings;
+ }
+
+ public void Create(string hostsFilePath)
+ {
+ if (_backupDone || !_userSettings.BackupHosts || !_fileSystem.File.Exists(hostsFilePath))
+ {
+ return;
+ }
+
+ try
+ {
+ if (!_fileSystem.Directory.Exists(_userSettings.BackupPath))
+ {
+ _fileSystem.Directory.CreateDirectory(_userSettings.BackupPath);
+ }
+
+ var backupPath = _fileSystem.Path.Combine(_userSettings.BackupPath, $"hosts{BackupSuffix}{DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)}");
+
+ _fileSystem.File.Copy(hostsFilePath, backupPath);
+ _backupDone = true;
+ }
+ catch (Exception ex)
+ {
+ LoggerInstance.Logger.LogError("Backup failed", ex);
+ }
+ }
+
+ public void Delete()
+ {
+ switch (_userSettings.DeleteBackupsMode)
+ {
+ case HostsDeleteBackupMode.Count:
+ DeleteByCount(_userSettings.DeleteBackupsCount);
+ break;
+ case HostsDeleteBackupMode.Age:
+ DeleteByAge(_userSettings.DeleteBackupsDays, _userSettings.DeleteBackupsCount);
+ break;
+ }
+ }
+
+ public void DeleteByCount(int count)
+ {
+ if (count < 1)
+ {
+ return;
+ }
+
+ var backups = GetAll().OrderByDescending(f => f.CreationTime).Skip(count).ToArray();
+ DeleteAll(backups);
+ }
+
+ public void DeleteByAge(int days, int count)
+ {
+ if (days < 1)
+ {
+ return;
+ }
+
+ var backupsEnumerable = GetAll();
+
+ if (count > 0)
+ {
+ backupsEnumerable = backupsEnumerable.OrderByDescending(f => f.CreationTime).Skip(count);
+ }
+
+ var backups = backupsEnumerable.Where(f => f.CreationTime < DateTime.Now.AddDays(-days)).ToArray();
+ DeleteAll(backups);
+ }
+
+ private IEnumerable GetAll()
+ {
+ if (!_fileSystem.Directory.Exists(_userSettings.BackupPath))
+ {
+ return [];
+ }
+
+ return _fileSystem.Directory.GetFiles(_userSettings.BackupPath, $"*{BackupSuffix}*").Select(_fileSystem.FileInfo.New);
+ }
+
+ private void DeleteAll(IFileInfo[] files)
+ {
+ foreach (var f in files)
+ {
+ _fileSystem.File.Delete(f.FullName);
+ }
+ }
+ }
+}
diff --git a/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs b/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs
index 83aa3544b1..9b16e04f20 100644
--- a/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs
+++ b/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
@@ -23,16 +22,15 @@ namespace HostsUILib.Helpers
{
public partial class HostsService : IHostsService, IDisposable
{
- private const string _backupSuffix = $"_PowerToysBackup_";
- private const int _defaultBufferSize = 4096; // From System.IO.File source code
+ private const int DefaultBufferSize = 4096; // From System.IO.File source code
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
private readonly IFileSystem _fileSystem;
private readonly IUserSettings _userSettings;
private readonly IElevationHelper _elevationHelper;
private readonly IFileSystemWatcher _fileSystemWatcher;
+ private readonly IBackupManager _backupManager;
private readonly string _hostsFilePath;
- private bool _backupDone;
private bool _disposed;
public string HostsFilePath => _hostsFilePath;
@@ -44,11 +42,13 @@ namespace HostsUILib.Helpers
public HostsService(
IFileSystem fileSystem,
IUserSettings userSettings,
- IElevationHelper elevationHelper)
+ IElevationHelper elevationHelper,
+ IBackupManager backupManager)
{
_fileSystem = fileSystem;
_userSettings = userSettings;
_elevationHelper = elevationHelper;
+ _backupManager = backupManager;
_hostsFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"System32\drivers\etc\hosts");
@@ -60,18 +60,13 @@ namespace HostsUILib.Helpers
_fileSystemWatcher.EnableRaisingEvents = true;
}
- public bool Exists()
- {
- return _fileSystem.File.Exists(HostsFilePath);
- }
-
public async Task ReadAsync()
{
var entries = new List();
var unparsedBuilder = new StringBuilder();
var splittedEntries = false;
- if (!Exists())
+ if (!_fileSystem.File.Exists(HostsFilePath))
{
return new HostsData(entries, unparsedBuilder.ToString(), false);
}
@@ -192,15 +187,10 @@ namespace HostsUILib.Helpers
{
await _asyncLock.WaitAsync();
_fileSystemWatcher.EnableRaisingEvents = false;
-
- if (!_backupDone && Exists())
- {
- _fileSystem.File.Copy(HostsFilePath, HostsFilePath + _backupSuffix + DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture));
- _backupDone = true;
- }
+ _backupManager.Create(HostsFilePath);
// FileMode.OpenOrCreate is necessary to prevent UnauthorizedAccessException when the hosts file is hidden
- using var stream = _fileSystem.FileStream.New(HostsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, _defaultBufferSize, FileOptions.Asynchronous);
+ using var stream = _fileSystem.FileStream.New(HostsFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, DefaultBufferSize, FileOptions.Asynchronous);
using var writer = new StreamWriter(stream, Encoding);
foreach (var line in lines)
{
@@ -231,15 +221,6 @@ namespace HostsUILib.Helpers
}
}
- public void CleanupBackup()
- {
- Directory.GetFiles(Path.GetDirectoryName(HostsFilePath), $"*{_backupSuffix}*")
- .Select(f => new FileInfo(f))
- .Where(f => f.CreationTime < DateTime.Now.AddDays(-15))
- .ToList()
- .ForEach(f => f.Delete());
- }
-
public void OpenHostsFile()
{
var notepadFallback = false;
diff --git a/src/modules/Hosts/HostsUILib/Helpers/IBackupManager.cs b/src/modules/Hosts/HostsUILib/Helpers/IBackupManager.cs
new file mode 100644
index 0000000000..9da9802a26
--- /dev/null
+++ b/src/modules/Hosts/HostsUILib/Helpers/IBackupManager.cs
@@ -0,0 +1,13 @@
+// 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 HostsUILib.Helpers
+{
+ public interface IBackupManager
+ {
+ void Create(string hostsFilePath);
+
+ void Delete();
+ }
+}
diff --git a/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs b/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs
index fe75946a12..c6f2678156 100644
--- a/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs
+++ b/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs
@@ -22,8 +22,6 @@ namespace HostsUILib.Helpers
Task PingAsync(string address);
- void CleanupBackup();
-
void OpenHostsFile();
void RemoveReadOnlyAttribute();
diff --git a/src/modules/Hosts/HostsUILib/Settings/HostsDeleteBackupMode.cs b/src/modules/Hosts/HostsUILib/Settings/HostsDeleteBackupMode.cs
new file mode 100644
index 0000000000..d1e1d79ded
--- /dev/null
+++ b/src/modules/Hosts/HostsUILib/Settings/HostsDeleteBackupMode.cs
@@ -0,0 +1,13 @@
+// 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 HostsUILib.Settings
+{
+ public enum HostsDeleteBackupMode
+ {
+ Never = 0,
+ Count = 1,
+ Age = 2,
+ }
+}
diff --git a/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs b/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs
index 46c7a7dab5..4f175398ad 100644
--- a/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs
+++ b/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs
@@ -16,6 +16,16 @@ namespace HostsUILib.Settings
public HostsEncoding Encoding { get; }
+ public bool BackupHosts { get; }
+
+ public string BackupPath { get; }
+
+ public HostsDeleteBackupMode DeleteBackupsMode { get; }
+
+ public int DeleteBackupsDays { get; }
+
+ public int DeleteBackupsCount { get; }
+
event EventHandler LoopbackDuplicatesChanged;
public delegate void OpenSettingsFunction();
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
index 7ebe4a67eb..845e24fa93 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
@@ -1,4 +1,4 @@
-#include
+#include
#include
#include "ThemeScheduler.h"
#include "ThemeHelper.h"
@@ -11,17 +11,17 @@
#include
#include
#include
+#include "LightSwitchStateManager.h"
+#include
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
HANDLE g_ServiceStopEvent = nullptr;
-static int g_lastUpdatedDay = -1;
-static ScheduleMode prevMode = ScheduleMode::Off;
-static std::wstring prevLat, prevLon;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
+void ApplyTheme(bool shouldBeLight);
// Entry point for the executable
int _tmain(int argc, TCHAR* argv[])
@@ -122,33 +122,67 @@ VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl)
}
}
-static void update_sun_times(auto& settings)
+void ApplyTheme(bool shouldBeLight)
{
- double latitude = std::stod(settings.latitude);
- double longitude = std::stod(settings.longitude);
+ const auto& s = LightSwitchSettings::settings();
+
+ if (s.changeSystem)
+ {
+ bool isSystemCurrentlyLight = GetCurrentSystemTheme();
+ if (shouldBeLight != isSystemCurrentlyLight)
+ {
+ SetSystemTheme(shouldBeLight);
+ Logger::info(L"[LightSwitchService] Changed system theme to {}.", shouldBeLight ? L"light" : L"dark");
+ }
+ }
+
+ if (s.changeApps)
+ {
+ bool isAppsCurrentlyLight = GetCurrentAppsTheme();
+ if (shouldBeLight != isAppsCurrentlyLight)
+ {
+ SetAppsTheme(shouldBeLight);
+ Logger::info(L"[LightSwitchService] Changed apps theme to {}.", shouldBeLight ? L"light" : L"dark");
+ }
+ }
+}
+
+static void DetectAndHandleExternalThemeChange(LightSwitchStateManager& stateManager)
+{
+ const auto& s = LightSwitchSettings::settings();
+ if (s.scheduleMode == ScheduleMode::Off)
+ return;
SYSTEMTIME st;
GetLocalTime(&st);
+ int nowMinutes = st.wHour * 60 + st.wMinute;
- SunTimes newTimes = CalculateSunriseSunset(latitude, longitude, st.wYear, st.wMonth, st.wDay);
+ // Compute effective boundaries (with offsets if needed)
+ int effectiveLight = s.lightTime;
+ int effectiveDark = s.darkTime;
- int newLightTime = newTimes.sunriseHour * 60 + newTimes.sunriseMinute;
- int newDarkTime = newTimes.sunsetHour * 60 + newTimes.sunsetMinute;
- try
+ if (s.scheduleMode == ScheduleMode::SunsetToSunrise)
{
- auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
- values.add_property(L"lightTime", newLightTime);
- values.add_property(L"darkTime", newDarkTime);
- values.save_to_settings_file();
+ effectiveLight = (s.lightTime + s.sunrise_offset) % 1440;
+ effectiveDark = (s.darkTime + s.sunset_offset) % 1440;
+ }
- Logger::info(L"[LightSwitchService] Updated sun times and saved to config.");
- }
- catch (const std::exception& e)
+ // Use shared helper (handles wraparound logic)
+ bool shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
+
+ // Compare current system/apps theme
+ bool currentSystemLight = GetCurrentSystemTheme();
+ bool currentAppsLight = GetCurrentAppsTheme();
+
+ bool systemMismatch = s.changeSystem && (currentSystemLight != shouldBeLight);
+ bool appsMismatch = s.changeApps && (currentAppsLight != shouldBeLight);
+
+ // Trigger manual override only if mismatch and not already active
+ if ((systemMismatch || appsMismatch) && !stateManager.GetState().isManualOverride)
{
- std::wstring wmsg(e.what(), e.what() + strlen(e.what()));
- Logger::error(L"[LightSwitchService] Exception during sun time update: {}", wmsg);
+ Logger::info(L"[LightSwitchService] External theme change detected (Windows Settings). Entering manual override mode.");
+ stateManager.OnManualOverride();
}
-
}
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
@@ -161,244 +195,101 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
Logger::info(L"[LightSwitchService] Worker thread starting...");
Logger::info(L"[LightSwitchService] Parent PID: {}", parentPid);
- // Initialize settings system
+ // ────────────────────────────────────────────────────────────────
+ // Initialization
+ // ────────────────────────────────────────────────────────────────
+ static LightSwitchStateManager stateManager;
+
LightSwitchSettings::instance().InitFileWatcher();
- // Open the manual override event created by the module interface
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
+ HANDLE hSettingsChanged = LightSwitchSettings::instance().GetSettingsChangedEvent();
- auto applyTheme = [](int nowMinutes, int lightMinutes, int darkMinutes, const auto& settings) {
- bool isLightActive = false;
-
- if (lightMinutes < darkMinutes)
- {
- // Normal case: sunrise < sunset
- isLightActive = (nowMinutes >= lightMinutes && nowMinutes < darkMinutes);
- }
- else
- {
- // Wraparound case: e.g. light at 21:00, dark at 06:00
- isLightActive = (nowMinutes >= lightMinutes || nowMinutes < darkMinutes);
- }
-
- bool isSystemCurrentlyLight = GetCurrentSystemTheme();
- bool isAppsCurrentlyLight = GetCurrentAppsTheme();
-
- if (isLightActive)
- {
- if (settings.changeSystem && !isSystemCurrentlyLight)
- {
- SetSystemTheme(true);
- Logger::info(L"[LightSwitchService] Changing system theme to light mode.");
- }
- if (settings.changeApps && !isAppsCurrentlyLight)
- {
- SetAppsTheme(true);
- Logger::info(L"[LightSwitchService] Changing apps theme to light mode.");
- }
- }
- else
- {
- if (settings.changeSystem && isSystemCurrentlyLight)
- {
- SetSystemTheme(false);
- Logger::info(L"[LightSwitchService] Changing system theme to dark mode.");
- }
- if (settings.changeApps && isAppsCurrentlyLight)
- {
- SetAppsTheme(false);
- Logger::info(L"[LightSwitchService] Changing apps theme to dark mode.");
- }
- }
- };
-
- // --- Initial settings load ---
LightSwitchSettings::instance().LoadSettings();
- auto& settings = LightSwitchSettings::instance().settings();
+ const auto& settings = LightSwitchSettings::instance().settings();
- // --- Initial theme application (if schedule enabled) ---
- if (settings.scheduleMode != ScheduleMode::Off)
- {
- SYSTEMTIME st;
- GetLocalTime(&st);
- int nowMinutes = st.wHour * 60 + st.wMinute;
- applyTheme(nowMinutes, settings.lightTime + settings.sunrise_offset, settings.darkTime + settings.sunset_offset, settings);
- }
- else
- {
- Logger::info(L"[LightSwitchService] Schedule mode is OFF - ticker suspended, waiting for manual action or mode change.");
- }
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+ int nowMinutes = st.wHour * 60 + st.wMinute;
- // --- Main loop ---
+ Logger::info(L"[LightSwitchService] Initialized at {:02d}:{:02d}.", st.wHour, st.wMinute);
+ stateManager.SyncInitialThemeState();
+ stateManager.OnTick(nowMinutes);
+
+ // ────────────────────────────────────────────────────────────────
+ // Worker Loop
+ // ────────────────────────────────────────────────────────────────
for (;;)
{
- HANDLE waits[2] = { g_ServiceStopEvent, hParent };
- DWORD count = hParent ? 2 : 1;
-
- LightSwitchSettings::instance().LoadSettings();
- const auto& settings = LightSwitchSettings::instance().settings();
-
- // Check for changes in schedule mode or coordinates
- bool modeChangedToSunset = (prevMode != settings.scheduleMode &&
- settings.scheduleMode == ScheduleMode::SunsetToSunrise);
- bool coordsChanged = (prevLat != settings.latitude || prevLon != settings.longitude);
-
- if ((modeChangedToSunset || coordsChanged) && settings.scheduleMode == ScheduleMode::SunsetToSunrise)
- {
- Logger::info(L"[LightSwitchService] Mode or coordinates changed, recalculating sun times.");
- update_sun_times(settings);
- SYSTEMTIME st;
- GetLocalTime(&st);
- g_lastUpdatedDay = st.wDay;
- prevMode = settings.scheduleMode;
- prevLat = settings.latitude;
- prevLon = settings.longitude;
- }
-
- // If schedule is off, idle but keep watching settings and manual override
- if (settings.scheduleMode == ScheduleMode::Off)
- {
- Logger::info(L"[LightSwitchService] Schedule mode OFF - suspending scheduler but keeping service alive.");
-
- if (!hManualOverride)
- {
- hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
- }
-
- HANDLE waits[4];
- DWORD count = 0;
- waits[count++] = g_ServiceStopEvent;
- if (hParent)
- waits[count++] = hParent;
- if (hManualOverride)
- waits[count++] = hManualOverride;
- waits[count++] = LightSwitchSettings::instance().GetSettingsChangedEvent();
-
- for (;;)
- {
- DWORD wait = WaitForMultipleObjects(count, waits, FALSE, INFINITE);
-
- // --- Handle exit signals ---
- if (wait == WAIT_OBJECT_0) // stop event
- {
- Logger::info(L"[LightSwitchService] Stop event triggered - exiting worker loop.");
- break;
- }
- if (hParent && wait == WAIT_OBJECT_0 + 1)
- {
- Logger::info(L"[LightSwitchService] Parent exited - stopping service.");
- break;
- }
-
- // --- Manual override triggered ---
- if (wait == WAIT_OBJECT_0 + (hParent ? 2 : 1))
- {
- Logger::info(L"[LightSwitchService] Manual override received while schedule OFF.");
- ResetEvent(hManualOverride);
- continue;
- }
-
- // --- Settings file changed ---
- if (wait == WAIT_OBJECT_0 + (hParent ? 3 : 2))
- {
- Logger::trace(L"[LightSwitchService] Settings change event triggered, reloading settings...");
-
- ResetEvent(LightSwitchSettings::instance().GetSettingsChangedEvent());
-
- LightSwitchSettings::instance().LoadSettings();
- const auto& newSettings = LightSwitchSettings::instance().settings();
-
- if (newSettings.scheduleMode != ScheduleMode::Off)
- {
- Logger::info(L"[LightSwitchService] Schedule re-enabled, resuming normal loop.");
- break;
- }
- }
- }
- }
-
-
- // --- When schedule is active, run once per minute ---
- SYSTEMTIME st;
- GetLocalTime(&st);
- int nowMinutes = st.wHour * 60 + st.wMinute;
-
- // Refresh suntimes at day boundary
- if ((g_lastUpdatedDay != st.wDay) && (settings.scheduleMode == ScheduleMode::SunsetToSunrise))
- {
- update_sun_times(settings);
- g_lastUpdatedDay = st.wDay;
- Logger::info(L"[LightSwitchService] Recalculated sun times at new day boundary.");
- }
-
- // Have to do this again in case settings got updated in the refresh suntimes chunk
- LightSwitchSettings::instance().LoadSettings();
- const auto& currentSettings = LightSwitchSettings::instance().settings();
-
- wchar_t msg[160];
- swprintf_s(msg,
- L"[LightSwitchService] now=%02d:%02d | light=%02d:%02d | dark=%02d:%02d | mode=%d",
- st.wHour,
- st.wMinute,
- currentSettings.lightTime / 60,
- currentSettings.lightTime % 60,
- currentSettings.darkTime / 60,
- currentSettings.darkTime % 60,
- static_cast(currentSettings.scheduleMode));
- Logger::info(msg);
-
- // --- Manual override check ---
- bool manualOverrideActive = false;
+ HANDLE waits[4];
+ DWORD count = 0;
+ waits[count++] = g_ServiceStopEvent;
+ if (hParent)
+ waits[count++] = hParent;
if (hManualOverride)
- {
- manualOverrideActive = (WaitForSingleObject(hManualOverride, 0) == WAIT_OBJECT_0);
- }
+ waits[count++] = hManualOverride;
+ waits[count++] = hSettingsChanged;
- if (manualOverrideActive)
- {
- if (nowMinutes == (currentSettings.lightTime + currentSettings.sunrise_offset) % 1440 ||
- nowMinutes == (currentSettings.darkTime + currentSettings.sunset_offset) % 1440)
- {
- ResetEvent(hManualOverride);
- Logger::info(L"[LightSwitchService] Manual override cleared at boundary");
- }
- else
- {
- Logger::info(L"[LightSwitchService] Skipping schedule due to manual override");
- goto sleep_until_next_minute;
- }
- }
-
- applyTheme(nowMinutes, currentSettings.lightTime + currentSettings.sunrise_offset, currentSettings.darkTime + currentSettings.sunset_offset, currentSettings);
-
- sleep_until_next_minute:
+ // Wait for one of these to trigger or for a new minute tick
+ SYSTEMTIME st;
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_TIMEOUT)
+ {
+ // regular minute tick
+ GetLocalTime(&st);
+ nowMinutes = st.wHour * 60 + st.wMinute;
+ DetectAndHandleExternalThemeChange(stateManager);
+ stateManager.OnTick(nowMinutes);
+ continue;
+ }
+
if (wait == WAIT_OBJECT_0)
{
- Logger::info(L"[LightSwitchService] Stop event triggered - exiting worker loop.");
+ Logger::info(L"[LightSwitchService] Stop event triggered — exiting.");
break;
}
+
if (hParent && wait == WAIT_OBJECT_0 + 1)
{
- Logger::info(L"[LightSwitchService] Parent process exited - stopping service.");
+ Logger::info(L"[LightSwitchService] Parent process exited — stopping service.");
break;
}
+
+ if (hManualOverride && wait == WAIT_OBJECT_0 + (hParent ? 2 : 1))
+ {
+ Logger::info(L"[LightSwitchService] Manual override event detected.");
+ stateManager.OnManualOverride();
+ ResetEvent(hManualOverride);
+ continue;
+ }
+
+ if (wait == WAIT_OBJECT_0 + (hParent ? (hManualOverride ? 3 : 2) : 2))
+ {
+ ResetEvent(hSettingsChanged);
+ LightSwitchSettings::instance().LoadSettings();
+ stateManager.OnSettingsChanged();
+ continue;
+ }
}
+ // ────────────────────────────────────────────────────────────────
+ // Cleanup
+ // ────────────────────────────────────────────────────────────────
if (hManualOverride)
CloseHandle(hManualOverride);
if (hParent)
CloseHandle(hParent);
+ Logger::info(L"[LightSwitchService] Worker thread exiting cleanly.");
return 0;
}
-
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
index b082250f61..a3a505f897 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
@@ -75,6 +75,7 @@
+
@@ -85,6 +86,8 @@
+
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
index f5aa05afc3..795df99aba 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
@@ -33,6 +33,9 @@
Source Files
+
+ Source Files
+
@@ -53,6 +56,12 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
index a7f44cca6d..5221a197fe 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
@@ -2,10 +2,8 @@
#include
#include
#include "SettingsObserver.h"
-
#include
#include
-#include
#include
using namespace std;
@@ -38,12 +36,79 @@ void LightSwitchSettings::InitFileWatcher()
m_settingsFileWatcher = std::make_unique(
GetSettingsFileName(),
[this]() {
- Logger::info(L"[LightSwitchSettings] Settings file changed, signaling event.");
- SetEvent(m_settingsChangedEvent);
+ using namespace std::chrono;
+
+ {
+ std::lock_guard lock(m_debounceMutex);
+ m_lastChangeTime = steady_clock::now();
+ if (m_debouncePending)
+ return;
+ m_debouncePending = true;
+ }
+
+ m_debounceThread = std::jthread([this](std::stop_token stop) {
+ using namespace std::chrono;
+ while (!stop.stop_requested())
+ {
+ std::this_thread::sleep_for(seconds(3));
+
+ auto elapsed = steady_clock::now() - m_lastChangeTime;
+ if (elapsed >= seconds(1))
+ break;
+ }
+
+ {
+ std::lock_guard lock(m_debounceMutex);
+ m_debouncePending = false;
+ }
+
+ Logger::info(L"[LightSwitchSettings] Settings file stabilized, reloading.");
+
+ try
+ {
+ LoadSettings();
+ SetEvent(m_settingsChangedEvent);
+ }
+ catch (const std::exception& e)
+ {
+ std::wstring wmsg;
+ wmsg.assign(e.what(), e.what() + strlen(e.what()));
+ Logger::error(L"[LightSwitchSettings] Exception during debounced reload: {}", wmsg);
+ }
+ });
});
}
}
+LightSwitchSettings::~LightSwitchSettings()
+{
+ Logger::info(L"[LightSwitchSettings] Cleaning up settings resources...");
+
+ // Stop and join the debounce thread (std::jthread auto-joins, but we can signal stop too)
+ if (m_debounceThread.joinable())
+ {
+ m_debounceThread.request_stop();
+ }
+
+ // Release the file watcher so it closes file handles and background threads
+ if (m_settingsFileWatcher)
+ {
+ m_settingsFileWatcher.reset();
+ Logger::info(L"[LightSwitchSettings] File watcher stopped.");
+ }
+
+ // Close the Windows event handle
+ if (m_settingsChangedEvent)
+ {
+ CloseHandle(m_settingsChangedEvent);
+ m_settingsChangedEvent = nullptr;
+ Logger::info(L"[LightSwitchSettings] Settings changed event closed.");
+ }
+
+ Logger::info(L"[LightSwitchSettings] Cleanup complete.");
+}
+
+
void LightSwitchSettings::AddObserver(SettingsObserver& observer)
{
m_observers.insert(&observer);
@@ -65,8 +130,14 @@ void LightSwitchSettings::NotifyObservers(SettingId id) const
}
}
+HANDLE LightSwitchSettings::GetSettingsChangedEvent() const
+{
+ return m_settingsChangedEvent;
+}
+
void LightSwitchSettings::LoadSettings()
{
+ std::lock_guard guard(m_settingsMutex);
try
{
PowerToysSettings::PowerToyValues values =
@@ -175,4 +246,4 @@ void LightSwitchSettings::LoadSettings()
{
// Keeps defaults if load fails
}
-}
\ No newline at end of file
+}
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h
index 32d011313f..d4029d072d 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.h
@@ -5,7 +5,10 @@
#include
#include
#include
-
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -79,11 +82,11 @@ public:
void LoadSettings();
- HANDLE GetSettingsChangedEvent() const { return m_settingsChangedEvent; }
+ HANDLE GetSettingsChangedEvent() const;
private:
LightSwitchSettings();
- ~LightSwitchSettings() = default;
+ ~LightSwitchSettings();
LightSwitchConfig m_settings;
std::unique_ptr m_settingsFileWatcher;
@@ -92,4 +95,11 @@ private:
void NotifyObservers(SettingId id) const;
HANDLE m_settingsChangedEvent = nullptr;
+ mutable std::mutex m_settingsMutex;
+
+ // Debounce state
+ std::atomic_bool m_debouncePending{ false };
+ std::mutex m_debounceMutex;
+ std::chrono::steady_clock::time_point m_lastChangeTime{};
+ std::jthread m_debounceThread;
};
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp
new file mode 100644
index 0000000000..4fba4ae9a6
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp
@@ -0,0 +1,232 @@
+#include "pch.h"
+#include "LightSwitchStateManager.h"
+#include
+#include
+#include "ThemeScheduler.h"
+#include
+
+void ApplyTheme(bool shouldBeLight);
+
+// Constructor
+LightSwitchStateManager::LightSwitchStateManager()
+{
+ Logger::info(L"[LightSwitchStateManager] Initialized");
+}
+
+// Called when settings.json changes
+void LightSwitchStateManager::OnSettingsChanged()
+{
+ std::lock_guard lock(_stateMutex);
+
+ // If manual override was active, clear it so new settings take effect
+ if (_state.isManualOverride)
+ {
+ _state.isManualOverride = false;
+ }
+
+ EvaluateAndApplyIfNeeded();
+}
+
+// Called once per minute
+void LightSwitchStateManager::OnTick(int currentMinutes)
+{
+ std::lock_guard lock(_stateMutex);
+ EvaluateAndApplyIfNeeded();
+}
+
+// Called when manual override is triggered
+void LightSwitchStateManager::OnManualOverride()
+{
+ std::lock_guard lock(_stateMutex);
+ Logger::info(L"[LightSwitchStateManager] Manual override triggered");
+ _state.isManualOverride = !_state.isManualOverride;
+
+ // When entering manual override, sync internal theme state to match the current system
+ if (_state.isManualOverride)
+ {
+ _state.isSystemLightActive = GetCurrentSystemTheme();
+
+ _state.isAppsLightActive = GetCurrentAppsTheme();
+
+ Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
+ (_state.isSystemLightActive ? L"light" : L"dark"),
+ (_state.isAppsLightActive ? L"light" : L"dark"));
+ }
+
+ EvaluateAndApplyIfNeeded();
+}
+
+// Helpers
+bool LightSwitchStateManager::CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon)
+{
+ try
+ {
+ double latVal = std::stod(lat);
+ double lonVal = std::stod(lon);
+ return !(latVal == 0 && lonVal == 0) && (latVal >= -90.0 && latVal <= 90.0) && (lonVal >= -180.0 && lonVal <= 180.0);
+ }
+ catch (...)
+ {
+ return false;
+ }
+}
+
+void LightSwitchStateManager::SyncInitialThemeState()
+{
+ std::lock_guard lock(_stateMutex);
+ _state.isSystemLightActive = GetCurrentSystemTheme();
+ _state.isAppsLightActive = GetCurrentAppsTheme();
+ Logger::debug(L"[LightSwitchStateManager] Synced initial state to current system theme ({})",
+ _state.isSystemLightActive ? L"light" : L"dark");
+ Logger::debug(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})",
+ _state.isAppsLightActive ? L"light" : L"dark");
+}
+
+static std::pair update_sun_times(auto& settings)
+{
+ double latitude = std::stod(settings.latitude);
+ double longitude = std::stod(settings.longitude);
+
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+
+ SunTimes newTimes = CalculateSunriseSunset(latitude, longitude, st.wYear, st.wMonth, st.wDay);
+
+ int newLightTime = newTimes.sunriseHour * 60 + newTimes.sunriseMinute;
+ int newDarkTime = newTimes.sunsetHour * 60 + newTimes.sunsetMinute;
+
+ try
+ {
+ auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(L"LightSwitch");
+ values.add_property(L"lightTime", newLightTime);
+ values.add_property(L"darkTime", newDarkTime);
+ values.save_to_settings_file();
+
+ Logger::info(L"[LightSwitchService] Updated sun times and saved to config.");
+ }
+ catch (const std::exception& e)
+ {
+ std::string msg = e.what();
+ std::wstring wmsg(msg.begin(), msg.end());
+ Logger::error(L"[LightSwitchService] Exception during sun time update: {}", wmsg);
+ }
+
+ return { newLightTime, newDarkTime };
+}
+
+// Internal: decide what should happen now
+void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
+{
+ LightSwitchSettings::instance().LoadSettings();
+ const auto& _currentSettings = LightSwitchSettings::settings();
+ auto now = GetNowMinutes();
+
+ // Early exit: OFF mode just pauses activity
+ if (_currentSettings.scheduleMode == ScheduleMode::Off)
+ {
+ _state.lastTickMinutes = now;
+ return;
+ }
+
+ bool coordsValid = CoordinatesAreValid(_currentSettings.latitude, _currentSettings.longitude);
+
+ // Handle Sun Mode recalculation
+ if (_currentSettings.scheduleMode == ScheduleMode::SunsetToSunrise && coordsValid)
+ {
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+ bool newDay = (_state.lastEvaluatedDay != st.wDay);
+ bool modeChangedToSun = (_state.lastAppliedMode != ScheduleMode::SunsetToSunrise &&
+ _currentSettings.scheduleMode == ScheduleMode::SunsetToSunrise);
+
+ if (newDay || modeChangedToSun)
+ {
+ auto [newLightTime, newDarkTime] = update_sun_times(_currentSettings);
+ _state.lastEvaluatedDay = st.wDay;
+ _state.effectiveLightMinutes = newLightTime + _currentSettings.sunrise_offset;
+ _state.effectiveDarkMinutes = newDarkTime + _currentSettings.sunset_offset;
+ }
+ else
+ {
+ _state.effectiveLightMinutes = _currentSettings.lightTime + _currentSettings.sunrise_offset;
+ _state.effectiveDarkMinutes = _currentSettings.darkTime + _currentSettings.sunset_offset;
+ }
+ }
+ else if (_currentSettings.scheduleMode == ScheduleMode::FixedHours)
+ {
+ _state.effectiveLightMinutes = _currentSettings.lightTime;
+ _state.effectiveDarkMinutes = _currentSettings.darkTime;
+ }
+
+ // Handle manual override logic
+ if (_state.isManualOverride)
+ {
+ bool crossedBoundary = false;
+ if (_state.lastTickMinutes != -1)
+ {
+ int prev = _state.lastTickMinutes;
+
+ // Handle midnight wraparound safely
+ if (now < prev)
+ {
+ crossedBoundary =
+ (prev <= _state.effectiveLightMinutes || now >= _state.effectiveLightMinutes) ||
+ (prev <= _state.effectiveDarkMinutes || now >= _state.effectiveDarkMinutes);
+ }
+ else
+ {
+ crossedBoundary =
+ (prev < _state.effectiveLightMinutes && now >= _state.effectiveLightMinutes) ||
+ (prev < _state.effectiveDarkMinutes && now >= _state.effectiveDarkMinutes);
+ }
+ }
+
+ if (crossedBoundary)
+ {
+ _state.isManualOverride = false;
+ }
+ else
+ {
+ _state.lastTickMinutes = now;
+ return;
+ }
+ }
+
+ _state.lastAppliedMode = _currentSettings.scheduleMode;
+
+ bool shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
+
+ bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
+ bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
+
+ /* Logger::debug(
+ L"[LightSwitchStateManager] now = {:02d}:{:02d}, light boundary = {:02d}:{:02d} ({}), dark boundary = {:02d}:{:02d} ({})",
+ now / 60,
+ now % 60,
+ _state.effectiveLightMinutes / 60,
+ _state.effectiveLightMinutes % 60,
+ _state.effectiveLightMinutes,
+ _state.effectiveDarkMinutes / 60,
+ _state.effectiveDarkMinutes % 60,
+ _state.effectiveDarkMinutes); */
+
+ /* Logger::debug("should be light = {}, apps needs change = {}, system needs change = {}",
+ shouldBeLight ? "true" : "false",
+ appsNeedsToChange ? "true" : "false",
+ systemNeedsToChange ? "true" : "false"); */
+
+ // Only apply theme if there's a change or no override active
+ if (!_state.isManualOverride && (appsNeedsToChange || systemNeedsToChange))
+ {
+ Logger::info(L"[LightSwitchStateManager] Applying {} theme", shouldBeLight ? L"light" : L"dark");
+ ApplyTheme(shouldBeLight);
+
+ _state.isSystemLightActive = GetCurrentSystemTheme();
+ _state.isAppsLightActive = GetCurrentAppsTheme();
+ }
+
+ _state.lastTickMinutes = now;
+}
+
+
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.h b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.h
new file mode 100644
index 0000000000..5c9bcc6e25
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.h
@@ -0,0 +1,47 @@
+#pragma once
+#include "LightSwitchSettings.h"
+#include
+
+// Represents runtime-only information (not saved in settings.json)
+struct LightSwitchState
+{
+ ScheduleMode lastAppliedMode = ScheduleMode::Off;
+ bool isManualOverride = false;
+ bool isSystemLightActive = false;
+ bool isAppsLightActive = false;
+ int lastEvaluatedDay = -1;
+ int lastTickMinutes = -1;
+
+ // Derived, runtime-resolved times
+ int effectiveLightMinutes = 0; // the boundary we actually act on
+ int effectiveDarkMinutes = 0; // includes offsets if needed
+};
+
+// The controller that reacts to settings changes, time ticks, and manual overrides.
+class LightSwitchStateManager
+{
+public:
+ LightSwitchStateManager();
+
+ // Called when settings.json changes or stabilizes.
+ void OnSettingsChanged();
+
+ // Called every minute (from service worker tick).
+ void OnTick(int currentMinutes);
+
+ // Called when manual override is toggled (via shortcut or system change).
+ void OnManualOverride();
+
+ // Initial sync at startup to align internal state with system theme
+ void SyncInitialThemeState();
+
+ // Accessor for current state (optional, for debugging or telemetry)
+ const LightSwitchState& GetState() const { return _state; }
+
+private:
+ LightSwitchState _state;
+ std::mutex _stateMutex;
+
+ void EvaluateAndApplyIfNeeded();
+ bool CoordinatesAreValid(const std::wstring& lat, const std::wstring& lon);
+};
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchUtils.h b/src/modules/LightSwitch/LightSwitchService/LightSwitchUtils.h
new file mode 100644
index 0000000000..0f4462bb65
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchUtils.h
@@ -0,0 +1,24 @@
+#pragma once
+#include
+
+constexpr bool ShouldBeLight(int nowMinutes, int lightTime, int darkTime)
+{
+ // Normalize values into [0, 1439]
+ int normalizedLightTime = (lightTime % 1440 + 1440) % 1440;
+ int normalizedDarkTime = (darkTime % 1440 + 1440) % 1440;
+ int normalizedNowMinutes = (nowMinutes % 1440 + 1440) % 1440;
+
+ // Case 1: Normal range, e.g. light mode comes before dark mode in the same day
+ if (normalizedLightTime < normalizedDarkTime)
+ return normalizedNowMinutes >= normalizedLightTime && normalizedNowMinutes < normalizedDarkTime;
+
+ // Case 2: Wrap-around range, e.g. light mode starts in the evening and dark mode starts in the morning
+ return normalizedNowMinutes >= normalizedLightTime || normalizedNowMinutes < normalizedDarkTime;
+}
+
+inline int GetNowMinutes()
+{
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+ return st.wHour * 60 + st.wMinute;
+}
diff --git a/src/modules/LightSwitch/LightSwitchService/SettingsObserver.h b/src/modules/LightSwitch/LightSwitchService/SettingsObserver.h
index 88d0194eef..b0ddde72ec 100644
--- a/src/modules/LightSwitch/LightSwitchService/SettingsObserver.h
+++ b/src/modules/LightSwitch/LightSwitchService/SettingsObserver.h
@@ -2,6 +2,7 @@
#include
#include "SettingsConstants.h"
+#include "LightSwitchSettings.h"
class LightSwitchSettings;
@@ -22,7 +23,7 @@ public:
// Override this in your class to respond to updates
virtual void SettingsUpdate(SettingId type) {}
- bool WantsToBeNotified(SettingId type) const noexcept
+ virtual bool WantsToBeNotified(SettingId type) const noexcept
{
return m_observedSettings.contains(type);
}
diff --git a/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs b/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
index 7b586301f6..37041b4b2d 100644
--- a/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
+++ b/src/modules/LightSwitch/Tests/LightSwitch.UITests/TestHelper.cs
@@ -152,16 +152,16 @@ namespace LightSwitch.UITests
var neededTabs = 6;
- if (modeCombobox.Text != "Manual")
+ if (modeCombobox.Text != "Fixed hours")
{
modeCombobox.Click();
var manualListItem = testBase.Session.Find(By.AccessibilityId("ManualCBItem_LightSwitch"), 5000);
- Assert.IsNotNull(manualListItem, "Manual combobox item not found.");
+ Assert.IsNotNull(manualListItem, "Fixed Hours combobox item not found.");
manualListItem.Click();
neededTabs = 1;
}
- Assert.AreEqual("Manual", modeCombobox.Text, "Mode combobox should be set to Manual.");
+ Assert.AreEqual("Fixed hours", modeCombobox.Text, "Mode combobox should be set to Fixed hours.");
var timeline = testBase.Session.Find(By.AccessibilityId("Timeline_LightSwitch"), 5000);
Assert.IsNotNull(timeline, "Timeline not found.");
@@ -198,7 +198,7 @@ namespace LightSwitch.UITests
}
///
- /// Perform a update geolocation test operation
+ /// Perform a update manual location test operation
///
public static void PerformUserSelectedLocationTest(UITestBase testBase)
{
@@ -216,19 +216,22 @@ namespace LightSwitch.UITests
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
+ // Click the select location button
var setLocationButton = testBase.Session.Find(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
Assert.IsNotNull(setLocationButton, "Set location button not found.");
- setLocationButton.Click();
+ setLocationButton.Click(msPostAction: 1000);
- var autoSuggestTextbox = testBase.Session.Find(By.AccessibilityId("CitySearchBox_LightSwitch"), 5000);
- Assert.IsNotNull(autoSuggestTextbox, "City search box not found.");
- autoSuggestTextbox.Click();
- autoSuggestTextbox.SendKeys("Seattle");
- autoSuggestTextbox.SendKeys(OpenQA.Selenium.Keys.Down);
- autoSuggestTextbox.SendKeys(OpenQA.Selenium.Keys.Enter);
+ var latitudeBox = testBase.Session.Find(By.AccessibilityId("LatitudeBox_LightSwitch"), 5000);
+ Assert.IsNotNull(latitudeBox, "Latitude text box not found.");
+ latitudeBox.Click();
- var latLong = testBase.Session.Find(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
- Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
+ testBase.Session.SendKeys(Key.Up);
+
+ var longitudeBox = testBase.Session.Find(By.AccessibilityId("LongitudeBox_LightSwitch"), 5000);
+ Assert.IsNotNull(longitudeBox, "Longitude text box not found.");
+ longitudeBox.Click();
+
+ testBase.Session.SendKeys(Key.Down);
var sunrise = testBase.Session.Find(By.AccessibilityId("SunriseText_LightSwitch"), 5000);
Assert.IsFalse(string.IsNullOrWhiteSpace(sunrise.Text));
@@ -256,13 +259,14 @@ namespace LightSwitch.UITests
Assert.AreEqual("Sunset to sunrise", modeCombobox.Text, "Mode combobox should be set to Sunset to sunrise.");
- // Click the select city button
+ // Click the select location button
var setLocationButton = testBase.Session.Find(By.AccessibilityId("SetLocationButton_LightSwitch"), 5000);
Assert.IsNotNull(setLocationButton, "Set location button not found.");
- setLocationButton.Click(msPostAction: 8000);
+ setLocationButton.Click(msPostAction: 1000);
- var latLong = testBase.Session.Find(By.AccessibilityId("LocationResultText_LightSwitch"), 5000);
- Assert.IsFalse(string.IsNullOrWhiteSpace(latLong.Text));
+ var syncLocationButton = testBase.Session.Find(By.AccessibilityId("SyncLocationButton_LightSwitch"), 5000);
+ Assert.IsNotNull(syncLocationButton, "Sync location button not found.");
+ syncLocationButton.Click(msPostAction: 8000);
var sunrise = testBase.Session.Find(By.AccessibilityId("SunriseText_LightSwitch"), 5000);
Assert.IsFalse(string.IsNullOrWhiteSpace(sunrise.Text));
@@ -363,6 +367,7 @@ namespace LightSwitch.UITests
var systemBeforeValue = GetSystemTheme();
var appsBeforeValue = GetAppsTheme();
+ Task.Delay(1000).Wait();
testBase.Session.SendKeys(activationKeys);
Task.Delay(5000).Wait();
@@ -389,6 +394,7 @@ namespace LightSwitch.UITests
var noneSystemBeforeValue = GetSystemTheme();
var noneAppsBeforeValue = GetAppsTheme();
+ Task.Delay(1000).Wait();
testBase.Session.SendKeys(activationKeys);
Task.Delay(5000).Wait();
diff --git a/src/modules/MouseUtils/CursorWrap/CursorWrap.rc b/src/modules/MouseUtils/CursorWrap/CursorWrap.rc
new file mode 100644
index 0000000000..37752edae0
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/CursorWrap.rc
@@ -0,0 +1,46 @@
+#include
+#include "resource.h"
+#include "../../../../common/version/version.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+#include "winres.h"
+#undef APSTUDIO_READONLY_SYMBOLS
+
+1 VERSIONINFO
+ FILEVERSION FILE_VERSION
+ PRODUCTVERSION PRODUCT_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", COMPANY_NAME
+ VALUE "FileDescription", "PowerToys CursorWrap"
+ VALUE "FileVersion", FILE_VERSION_STRING
+ VALUE "InternalName", "CursorWrap"
+ VALUE "LegalCopyright", COPYRIGHT_NOTE
+ VALUE "OriginalFilename", "PowerToys.CursorWrap.dll"
+ VALUE "ProductName", PRODUCT_NAME
+ VALUE "ProductVersion", PRODUCT_VERSION_STRING
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CURSORWRAP_NAME L"CursorWrap"
+ IDS_CURSORWRAP_DISABLE_WRAP_DURING_DRAG L"Disable wrapping during drag"
+END
\ No newline at end of file
diff --git a/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj b/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj
new file mode 100644
index 0000000000..59e2095ca7
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj
@@ -0,0 +1,130 @@
+
+
+
+
+ 15.0
+ {48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5}
+ Win32Proj
+ CursorWrap
+ CursorWrap
+
+
+
+
+ DynamicLibrary
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\..\..\$(Platform)\$(Configuration)\
+ PowerToys.CursorWrap
+
+
+ true
+
+
+ false
+
+
+
+ Level3
+ Disabled
+ true
+ _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ MultiThreadedDebug
+ stdcpplatest
+
+
+ Windows
+ true
+ $(OutDir)$(TargetName)$(TargetExt)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ stdcpplatest
+
+
+ Windows
+ true
+ true
+ true
+ $(OutDir)$(TargetName)$(TargetExt)
+
+
+
+
+ ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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}.
+
+
+
+
+
diff --git a/src/modules/MouseUtils/CursorWrap/CursorWrapTests.h b/src/modules/MouseUtils/CursorWrap/CursorWrapTests.h
new file mode 100644
index 0000000000..4274ad714f
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/CursorWrapTests.h
@@ -0,0 +1,213 @@
+#pragma once
+
+#include
+#include
+
+// Test case structure for comprehensive monitor layout testing
+struct MonitorTestCase
+{
+ std::string name;
+ std::string description;
+ int grid[3][3]; // 3x3 grid representing monitor layout (0 = no monitor, 1-9 = monitor ID)
+
+ // Test scenarios to validate
+ struct TestScenario
+ {
+ int sourceMonitor; // Which monitor to start cursor on (1-based)
+ int edgeDirection; // 0=top, 1=right, 2=bottom, 3=left
+ int expectedTargetMonitor; // Expected destination monitor (1-based, -1 = wrap within same monitor)
+ std::string description;
+ };
+
+ std::vector scenarios;
+};
+
+// Comprehensive test cases for all possible 3x3 monitor grid configurations
+class CursorWrapTestSuite
+{
+public:
+ static std::vector GetAllTestCases()
+ {
+ std::vector testCases;
+
+ // Test Case 1: Single monitor (center)
+ testCases.push_back({
+ "Single_Center",
+ "Single monitor in center position",
+ {
+ {0, 0, 0},
+ {0, 1, 0},
+ {0, 0, 0}
+ },
+ {
+ {1, 0, -1, "Top edge wraps to bottom of same monitor"},
+ {1, 1, -1, "Right edge wraps to left of same monitor"},
+ {1, 2, -1, "Bottom edge wraps to top of same monitor"},
+ {1, 3, -1, "Left edge wraps to right of same monitor"}
+ }
+ });
+
+ // Test Case 2: Two monitors horizontal (left + right)
+ testCases.push_back({
+ "Dual_Horizontal_Left_Right",
+ "Two monitors: left + right",
+ {
+ {0, 0, 0},
+ {1, 0, 2},
+ {0, 0, 0}
+ },
+ {
+ {1, 0, -1, "Monitor 1 top wraps to bottom of monitor 1"},
+ {1, 1, 2, "Monitor 1 right edge moves to monitor 2 left"},
+ {1, 2, -1, "Monitor 1 bottom wraps to top of monitor 1"},
+ {1, 3, -1, "Monitor 1 left edge wraps to right of monitor 1"},
+ {2, 0, -1, "Monitor 2 top wraps to bottom of monitor 2"},
+ {2, 1, -1, "Monitor 2 right edge wraps to left of monitor 2"},
+ {2, 2, -1, "Monitor 2 bottom wraps to top of monitor 2"},
+ {2, 3, 1, "Monitor 2 left edge moves to monitor 1 right"}
+ }
+ });
+
+ // Test Case 3: Two monitors vertical (Monitor 2 above Monitor 1) - CORRECTED FOR USER'S SETUP
+ testCases.push_back({
+ "Dual_Vertical_2_Above_1",
+ "Two monitors: Monitor 2 (top) above Monitor 1 (bottom/main)",
+ {
+ {0, 2, 0}, // Row 0: Monitor 2 (physically top monitor)
+ {0, 0, 0}, // Row 1: Empty
+ {0, 1, 0} // Row 2: Monitor 1 (physically bottom/main monitor)
+ },
+ {
+ // Monitor 1 (bottom/main monitor) tests
+ {1, 0, 2, "Monitor 1 (bottom) top edge should move to Monitor 2 (top) bottom"},
+ {1, 1, -1, "Monitor 1 right wraps to left of monitor 1"},
+ {1, 2, -1, "Monitor 1 bottom wraps to top of monitor 1"},
+ {1, 3, -1, "Monitor 1 left wraps to right of monitor 1"},
+
+ // Monitor 2 (top monitor) tests
+ {2, 0, -1, "Monitor 2 (top) top wraps to bottom of monitor 2"},
+ {2, 1, -1, "Monitor 2 right wraps to left of monitor 2"},
+ {2, 2, 1, "Monitor 2 (top) bottom edge should move to Monitor 1 (bottom) top"},
+ {2, 3, -1, "Monitor 2 left wraps to right of monitor 2"}
+ }
+ });
+
+ // Test Case 4: Three monitors L-shape (center + left + top)
+ testCases.push_back({
+ "Triple_L_Shape",
+ "Three monitors in L-shape: center + left + top",
+ {
+ {0, 3, 0},
+ {2, 1, 0},
+ {0, 0, 0}
+ },
+ {
+ {1, 0, 3, "Monitor 1 top moves to monitor 3 bottom"},
+ {1, 1, -1, "Monitor 1 right wraps to left of monitor 1"},
+ {1, 2, -1, "Monitor 1 bottom wraps to top of monitor 1"},
+ {1, 3, 2, "Monitor 1 left moves to monitor 2 right"},
+ {2, 0, -1, "Monitor 2 top wraps to bottom of monitor 2"},
+ {2, 1, 1, "Monitor 2 right moves to monitor 1 left"},
+ {2, 2, -1, "Monitor 2 bottom wraps to top of monitor 2"},
+ {2, 3, -1, "Monitor 2 left wraps to right of monitor 2"},
+ {3, 0, -1, "Monitor 3 top wraps to bottom of monitor 3"},
+ {3, 1, -1, "Monitor 3 right wraps to left of monitor 3"},
+ {3, 2, 1, "Monitor 3 bottom moves to monitor 1 top"},
+ {3, 3, -1, "Monitor 3 left wraps to right of monitor 3"}
+ }
+ });
+
+ // Test Case 5: Three monitors horizontal (left + center + right)
+ testCases.push_back({
+ "Triple_Horizontal",
+ "Three monitors horizontal: left + center + right",
+ {
+ {0, 0, 0},
+ {1, 2, 3},
+ {0, 0, 0}
+ },
+ {
+ {1, 0, -1, "Monitor 1 top wraps to bottom"},
+ {1, 1, 2, "Monitor 1 right moves to monitor 2"},
+ {1, 2, -1, "Monitor 1 bottom wraps to top"},
+ {1, 3, -1, "Monitor 1 left wraps to right"},
+ {2, 0, -1, "Monitor 2 top wraps to bottom"},
+ {2, 1, 3, "Monitor 2 right moves to monitor 3"},
+ {2, 2, -1, "Monitor 2 bottom wraps to top"},
+ {2, 3, 1, "Monitor 2 left moves to monitor 1"},
+ {3, 0, -1, "Monitor 3 top wraps to bottom"},
+ {3, 1, -1, "Monitor 3 right wraps to left"},
+ {3, 2, -1, "Monitor 3 bottom wraps to top"},
+ {3, 3, 2, "Monitor 3 left moves to monitor 2"}
+ }
+ });
+
+ // Test Case 6: Three monitors vertical (top + center + bottom)
+ testCases.push_back({
+ "Triple_Vertical",
+ "Three monitors vertical: top + center + bottom",
+ {
+ {0, 1, 0},
+ {0, 2, 0},
+ {0, 3, 0}
+ },
+ {
+ {1, 0, -1, "Monitor 1 top wraps to bottom"},
+ {1, 1, -1, "Monitor 1 right wraps to left"},
+ {1, 2, 2, "Monitor 1 bottom moves to monitor 2"},
+ {1, 3, -1, "Monitor 1 left wraps to right"},
+ {2, 0, 1, "Monitor 2 top moves to monitor 1"},
+ {2, 1, -1, "Monitor 2 right wraps to left"},
+ {2, 2, 3, "Monitor 2 bottom moves to monitor 3"},
+ {2, 3, -1, "Monitor 2 left wraps to right"},
+ {3, 0, 2, "Monitor 3 top moves to monitor 2"},
+ {3, 1, -1, "Monitor 3 right wraps to left"},
+ {3, 2, -1, "Monitor 3 bottom wraps to top"},
+ {3, 3, -1, "Monitor 3 left wraps to right"}
+ }
+ });
+
+ return testCases;
+ }
+
+ // Helper function to print test case in a readable format
+ static std::string FormatTestCase(const MonitorTestCase& testCase)
+ {
+ std::string result = "Test Case: " + testCase.name + "\n";
+ result += "Description: " + testCase.description + "\n";
+ result += "Layout:\n";
+
+ for (int row = 0; row < 3; row++)
+ {
+ result += " ";
+ for (int col = 0; col < 3; col++)
+ {
+ if (testCase.grid[row][col] == 0)
+ {
+ result += ". ";
+ }
+ else
+ {
+ result += std::to_string(testCase.grid[row][col]) + " ";
+ }
+ }
+ result += "\n";
+ }
+
+ result += "Test Scenarios:\n";
+ for (const auto& scenario : testCase.scenarios)
+ {
+ result += " - " + scenario.description + "\n";
+ }
+
+ return result;
+ }
+
+ // Helper function to validate a specific test case against actual behavior
+ static bool ValidateTestCase(const MonitorTestCase& testCase)
+ {
+ // This would be called with actual CursorWrap instance to validate behavior
+ // For now, just return true - this would need actual implementation
+ return true;
+ }
+};
\ No newline at end of file
diff --git a/src/modules/MouseUtils/CursorWrap/dllmain.cpp b/src/modules/MouseUtils/CursorWrap/dllmain.cpp
new file mode 100644
index 0000000000..74524ed9f9
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/dllmain.cpp
@@ -0,0 +1,1046 @@
+#include "pch.h"
+#include "../../../interface/powertoy_module_interface.h"
+#include "../../../common/SettingsAPI/settings_objects.h"
+#include "trace.h"
+#include "../../../common/utils/process_path.h"
+#include "../../../common/utils/resources.h"
+#include "../../../common/logger/logger.h"
+#include "../../../common/utils/logger_helper.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "resource.h"
+#include "CursorWrapTests.h"
+
+// Disable C26451 arithmetic overflow warning for this file since the operations are safe in this context
+#pragma warning(disable: 26451)
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+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;
+}
+
+// Non-Localizable strings
+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_AUTO_ACTIVATE[] = L"auto_activate";
+ const wchar_t JSON_KEY_DISABLE_WRAP_DURING_DRAG[] = L"disable_wrap_during_drag";
+}
+
+// The PowerToy name that will be shown in the settings.
+const static wchar_t* MODULE_NAME = L"CursorWrap";
+// Add a description that will we shown in the module settings page.
+const static wchar_t* MODULE_DESC = L"";
+
+// Mouse hook data structure
+struct MonitorInfo
+{
+ RECT rect;
+ bool isPrimary;
+ int monitorId; // Add monitor ID for easier debugging
+};
+
+// Add structure for logical monitor grid position
+struct LogicalPosition
+{
+ int row;
+ int col;
+ bool isValid;
+};
+
+// Add monitor topology helper
+struct MonitorTopology
+{
+ std::vector> grid; // 3x3 grid of monitors
+ std::map monitorToPosition;
+ std::map, HMONITOR> positionToMonitor;
+
+ void Initialize(const std::vector& monitors);
+ LogicalPosition GetPosition(HMONITOR monitor) const;
+ HMONITOR GetMonitorAt(int row, int col) const;
+ HMONITOR FindAdjacentMonitor(HMONITOR current, int deltaRow, int deltaCol) const;
+};
+
+// Forward declaration
+class CursorWrap;
+
+// Global instance pointer for the mouse hook
+static CursorWrap* g_cursorWrapInstance = nullptr;
+
+// Implement the PowerToy Module Interface and all the required methods.
+class CursorWrap : public PowertoyModuleIface
+{
+private:
+ // The PowerToy state.
+ bool m_enabled = false;
+ bool m_autoActivate = false;
+ bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag
+
+ // Mouse hook
+ HHOOK m_mouseHook = nullptr;
+ std::atomic m_hookActive{ false };
+
+ // Monitor information
+ std::vector m_monitors;
+ MonitorTopology m_topology;
+
+ // Hotkey
+ Hotkey m_activationHotkey{};
+
+public:
+ // Constructor
+ CursorWrap()
+ {
+ LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::cursorWrapLoggerName);
+ init_settings();
+ UpdateMonitorInfo();
+ g_cursorWrapInstance = this; // Set global instance pointer
+ };
+
+ // Destroy the powertoy and free memory
+ virtual void destroy() override
+ {
+ StopMouseHook();
+ g_cursorWrapInstance = nullptr; // Clear global instance pointer
+ delete this;
+ }
+
+ // Return the localized display name of the powertoy
+ virtual const wchar_t* get_name() override
+ {
+ return MODULE_NAME;
+ }
+
+ // Return the non localized key of the powertoy, this will be cached by the runner
+ virtual const wchar_t* get_key() 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::getConfiguredCursorWrapEnabledValue();
+ }
+
+ // Return JSON with the configuration options.
+ virtual bool get_config(wchar_t* buffer, int* buffer_size) override
+ {
+ HINSTANCE hinstance = reinterpret_cast(&__ImageBase);
+
+ PowerToysSettings::Settings settings(hinstance, get_name());
+
+ settings.set_description(IDS_CURSORWRAP_NAME);
+ settings.set_icon_key(L"pt-cursor-wrap");
+
+ // Create HotkeyObject from the Hotkey struct for the settings
+ auto hotkey_object = PowerToysSettings::HotkeyObject::from_settings(
+ m_activationHotkey.win,
+ m_activationHotkey.ctrl,
+ m_activationHotkey.alt,
+ m_activationHotkey.shift,
+ m_activationHotkey.key);
+
+ settings.add_hotkey(JSON_KEY_ACTIVATION_SHORTCUT, IDS_CURSORWRAP_NAME, hotkey_object);
+ settings.add_bool_toggle(JSON_KEY_AUTO_ACTIVATE, IDS_CURSORWRAP_NAME, m_autoActivate);
+ settings.add_bool_toggle(JSON_KEY_DISABLE_WRAP_DURING_DRAG, IDS_CURSORWRAP_NAME, m_disableWrapDuringDrag);
+
+ 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.
+ 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
+ {
+ try
+ {
+ // Parse the input JSON string.
+ PowerToysSettings::PowerToyValues values =
+ PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
+
+ parse_settings(values);
+ }
+ catch (std::exception&)
+ {
+ Logger::error("Invalid json when trying to parse CursorWrap settings json.");
+ }
+ }
+
+ // Enable the powertoy
+ virtual void enable()
+ {
+ m_enabled = true;
+ Trace::EnableCursorWrap(true);
+
+ // Always start the mouse hook when the module is enabled
+ // This ensures cursor wrapping is active immediately after enabling
+ StartMouseHook();
+ Logger::info("CursorWrap enabled - mouse hook started");
+ }
+
+ // Disable the powertoy
+ virtual void disable()
+ {
+ m_enabled = false;
+ Trace::EnableCursorWrap(false);
+ StopMouseHook();
+ Logger::info("CursorWrap disabled - mouse hook stopped");
+ }
+
+ // Returns if the powertoys is enabled
+ virtual bool is_enabled() override
+ {
+ return m_enabled;
+ }
+
+ // Returns whether the PowerToys should be enabled by default
+ virtual bool is_enabled_by_default() const override
+ {
+ return false;
+ }
+
+ // Legacy hotkey support
+ virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
+ {
+ if (buffer && buffer_size >= 1)
+ {
+ buffer[0] = m_activationHotkey;
+ }
+ return 1;
+ }
+
+ virtual bool on_hotkey(size_t hotkeyId) override
+ {
+ if (!m_enabled || hotkeyId != 0)
+ {
+ return false;
+ }
+
+ // Toggle cursor wrapping
+ if (m_hookActive)
+ {
+ StopMouseHook();
+ }
+ else
+ {
+ StartMouseHook();
+#ifdef _DEBUG
+ // Run comprehensive tests when hook is started in debug builds
+ RunComprehensiveTests();
+#endif
+ }
+
+ return true;
+ }
+
+private:
+ // Load the settings file.
+ void init_settings()
+ {
+ try
+ {
+ // Load and parse the settings file for this PowerToy.
+ PowerToysSettings::PowerToyValues settings =
+ PowerToysSettings::PowerToyValues::load_from_settings_file(CursorWrap::get_key());
+ parse_settings(settings);
+ }
+ catch (std::exception&)
+ {
+ Logger::error("Invalid json when trying to load the CursorWrap settings json from file.");
+ }
+ }
+
+ void parse_settings(PowerToysSettings::PowerToyValues& settings)
+ {
+ auto settingsObject = settings.get_raw_json();
+ if (settingsObject.GetView().Size())
+ {
+ try
+ {
+ // Parse activation HotKey
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
+ auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
+
+ 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(hotkey.get_code());
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize CursorWrap activation shortcut");
+ }
+
+ try
+ {
+ // Parse auto activate
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_AUTO_ACTIVATE);
+ m_autoActivate = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize CursorWrap auto activate from settings. Will use default value");
+ }
+
+ try
+ {
+ // Parse disable wrap during drag
+ auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
+ if (propertiesObject.HasKey(JSON_KEY_DISABLE_WRAP_DURING_DRAG))
+ {
+ auto disableDragObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_WRAP_DURING_DRAG);
+ m_disableWrapDuringDrag = disableDragObject.GetNamedBoolean(JSON_KEY_VALUE);
+ }
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize CursorWrap disable wrap during drag from settings. Will use default value (true)");
+ }
+ }
+ else
+ {
+ Logger::info("CursorWrap settings are empty");
+ }
+
+ // Set default hotkey if not configured
+ if (m_activationHotkey.key == 0)
+ {
+ m_activationHotkey.win = true;
+ m_activationHotkey.alt = true;
+ m_activationHotkey.ctrl = false;
+ m_activationHotkey.shift = false;
+ m_activationHotkey.key = 'U'; // Win+Alt+U
+ }
+ }
+
+ void UpdateMonitorInfo()
+ {
+ m_monitors.clear();
+
+ EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hMonitor, HDC, LPRECT, LPARAM lParam) -> BOOL {
+ auto* self = reinterpret_cast(lParam);
+
+ MONITORINFO mi{};
+ mi.cbSize = sizeof(MONITORINFO);
+ if (GetMonitorInfo(hMonitor, &mi))
+ {
+ MonitorInfo info{};
+ info.rect = mi.rcMonitor;
+ info.isPrimary = (mi.dwFlags & MONITORINFOF_PRIMARY) != 0;
+ info.monitorId = static_cast(self->m_monitors.size());
+ self->m_monitors.push_back(info);
+ }
+
+ return TRUE;
+ }, reinterpret_cast(this));
+
+ // Initialize monitor topology
+ m_topology.Initialize(m_monitors);
+ }
+
+ void StartMouseHook()
+ {
+ if (m_mouseHook || m_hookActive)
+ {
+ Logger::info("CursorWrap mouse hook already active");
+ return;
+ }
+
+ UpdateMonitorInfo();
+
+ m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, GetModuleHandle(nullptr), 0);
+ if (m_mouseHook)
+ {
+ m_hookActive = true;
+ Logger::info("CursorWrap mouse hook started successfully");
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Hook installed");
+#endif
+ }
+ else
+ {
+ DWORD error = GetLastError();
+ Logger::error(L"Failed to install CursorWrap mouse hook, error: {}", error);
+ }
+ }
+
+ void StopMouseHook()
+ {
+ if (m_mouseHook)
+ {
+ UnhookWindowsHookEx(m_mouseHook);
+ m_mouseHook = nullptr;
+ m_hookActive = false;
+ Logger::info("CursorWrap mouse hook stopped");
+#ifdef _DEBUG
+ Logger::info("CursorWrap DEBUG: Mouse hook stopped");
+#endif
+ }
+ }
+
+ static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam)
+ {
+ if (nCode >= 0 && wParam == WM_MOUSEMOVE)
+ {
+ auto* pMouseStruct = reinterpret_cast(lParam);
+ POINT currentPos = { pMouseStruct->pt.x, pMouseStruct->pt.y };
+
+ if (g_cursorWrapInstance && g_cursorWrapInstance->m_hookActive)
+ {
+ POINT newPos = g_cursorWrapInstance->HandleMouseMove(currentPos);
+ if (newPos.x != currentPos.x || newPos.y != currentPos.y)
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Wrapping cursor from ({}, {}) to ({}, {})",
+ currentPos.x, currentPos.y, newPos.x, newPos.y);
+#endif
+ SetCursorPos(newPos.x, newPos.y);
+ return 1; // Suppress the original message
+ }
+ }
+ }
+
+ return CallNextHookEx(nullptr, nCode, wParam, lParam);
+ }
+
+ // *** COMPLETELY REWRITTEN CURSOR WRAPPING LOGIC ***
+ // Implements vertical scrolling to bottom/top of vertical stack as requested
+ POINT HandleMouseMove(const POINT& currentPos)
+ {
+ POINT newPos = currentPos;
+
+ // Check if we should skip wrapping during drag if the setting is enabled
+ if (m_disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000))
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Left mouse button is down and disable_wrap_during_drag is enabled - skipping wrap");
+#endif
+ return currentPos; // Return unchanged position (no wrapping)
+ }
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: ======= HANDLE MOUSE MOVE START =======");
+ Logger::info(L"CursorWrap DEBUG: Input position ({}, {})", currentPos.x, currentPos.y);
+#endif
+
+ // Find which monitor the cursor is currently on
+ HMONITOR currentMonitor = MonitorFromPoint(currentPos, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO currentMonitorInfo{};
+ currentMonitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(currentMonitor, ¤tMonitorInfo);
+
+ LogicalPosition currentLogicalPos = m_topology.GetPosition(currentMonitor);
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Current monitor bounds: Left={}, Top={}, Right={}, Bottom={}",
+ currentMonitorInfo.rcMonitor.left, currentMonitorInfo.rcMonitor.top,
+ currentMonitorInfo.rcMonitor.right, currentMonitorInfo.rcMonitor.bottom);
+ Logger::info(L"CursorWrap DEBUG: Logical position: Row={}, Col={}, Valid={}",
+ currentLogicalPos.row, currentLogicalPos.col, currentLogicalPos.isValid);
+#endif
+
+ bool wrapped = false;
+
+ // *** VERTICAL WRAPPING LOGIC - CONFIRMED WORKING ***
+ // Move to bottom of vertical stack when hitting top edge
+ if (currentPos.y <= currentMonitorInfo.rcMonitor.top)
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: TOP EDGE DETECTED =======");
+#endif
+
+ // Find the bottom-most monitor in the vertical stack (same column)
+ HMONITOR bottomMonitor = nullptr;
+
+ if (currentLogicalPos.isValid) {
+ // Search down from current position to find the bottom-most monitor in same column
+ for (int row = 2; row >= 0; row--) { // Start from bottom and work up
+ HMONITOR candidateMonitor = m_topology.GetMonitorAt(row, currentLogicalPos.col);
+ if (candidateMonitor) {
+ bottomMonitor = candidateMonitor;
+ break; // Found the bottom-most monitor
+ }
+ }
+ }
+
+ if (bottomMonitor && bottomMonitor != currentMonitor) {
+ // *** MOVE TO BOTTOM OF VERTICAL STACK ***
+ MONITORINFO bottomInfo{};
+ bottomInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(bottomMonitor, &bottomInfo);
+
+ // Calculate relative X position to maintain cursor X alignment
+ double relativeX = static_cast(currentPos.x - currentMonitorInfo.rcMonitor.left) /
+ (currentMonitorInfo.rcMonitor.right - currentMonitorInfo.rcMonitor.left);
+
+ int targetWidth = bottomInfo.rcMonitor.right - bottomInfo.rcMonitor.left;
+ newPos.x = bottomInfo.rcMonitor.left + static_cast(relativeX * targetWidth);
+ newPos.y = bottomInfo.rcMonitor.bottom - 1; // Bottom edge of bottom monitor
+
+ // Clamp X to target monitor bounds
+ newPos.x = max(bottomInfo.rcMonitor.left, min(newPos.x, bottomInfo.rcMonitor.right - 1));
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP SUCCESS - Moved to bottom of vertical stack");
+ Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
+#endif
+ } else {
+ // *** NO OTHER MONITOR IN VERTICAL STACK - WRAP WITHIN CURRENT MONITOR ***
+ newPos.y = currentMonitorInfo.rcMonitor.bottom - 1;
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP - No other monitor in stack, wrapping within current monitor");
+#endif
+ }
+ }
+ else if (currentPos.y >= currentMonitorInfo.rcMonitor.bottom - 1)
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: ======= VERTICAL WRAP: BOTTOM EDGE DETECTED =======");
+#endif
+
+ // Find the top-most monitor in the vertical stack (same column)
+ HMONITOR topMonitor = nullptr;
+
+ if (currentLogicalPos.isValid) {
+ // Search up from current position to find the top-most monitor in same column
+ for (int row = 0; row <= 2; row++) { // Start from top and work down
+ HMONITOR candidateMonitor = m_topology.GetMonitorAt(row, currentLogicalPos.col);
+ if (candidateMonitor) {
+ topMonitor = candidateMonitor;
+ break; // Found the top-most monitor
+ }
+ }
+ }
+
+ if (topMonitor && topMonitor != currentMonitor) {
+ // *** MOVE TO TOP OF VERTICAL STACK ***
+ MONITORINFO topInfo{};
+ topInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(topMonitor, &topInfo);
+
+ // Calculate relative X position to maintain cursor X alignment
+ double relativeX = static_cast(currentPos.x - currentMonitorInfo.rcMonitor.left) /
+ (currentMonitorInfo.rcMonitor.right - currentMonitorInfo.rcMonitor.left);
+
+ int targetWidth = topInfo.rcMonitor.right - topInfo.rcMonitor.left;
+ newPos.x = topInfo.rcMonitor.left + static_cast(relativeX * targetWidth);
+ newPos.y = topInfo.rcMonitor.top; // Top edge of top monitor
+
+ // Clamp X to target monitor bounds
+ newPos.x = max(topInfo.rcMonitor.left, min(newPos.x, topInfo.rcMonitor.right - 1));
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP SUCCESS - Moved to top of vertical stack");
+ Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
+#endif
+ } else {
+ // *** NO OTHER MONITOR IN VERTICAL STACK - WRAP WITHIN CURRENT MONITOR ***
+ newPos.y = currentMonitorInfo.rcMonitor.top;
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: VERTICAL WRAP - No other monitor in stack, wrapping within current monitor");
+#endif
+ }
+ }
+
+ // *** FIXED HORIZONTAL WRAPPING LOGIC ***
+ // Move to opposite end of horizontal stack when hitting left/right edge
+ // Only handle horizontal wrapping if we haven't already wrapped vertically
+ if (!wrapped && currentPos.x <= currentMonitorInfo.rcMonitor.left)
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: LEFT EDGE DETECTED =======");
+#endif
+
+ // Find the right-most monitor in the horizontal stack (same row)
+ HMONITOR rightMonitor = nullptr;
+
+ if (currentLogicalPos.isValid) {
+ // Search right from current position to find the right-most monitor in same row
+ for (int col = 2; col >= 0; col--) { // Start from right and work left
+ HMONITOR candidateMonitor = m_topology.GetMonitorAt(currentLogicalPos.row, col);
+ if (candidateMonitor) {
+ rightMonitor = candidateMonitor;
+ break; // Found the right-most monitor
+ }
+ }
+ }
+
+ if (rightMonitor && rightMonitor != currentMonitor) {
+ // *** MOVE TO RIGHT END OF HORIZONTAL STACK ***
+ MONITORINFO rightInfo{};
+ rightInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(rightMonitor, &rightInfo);
+
+ // Calculate relative Y position to maintain cursor Y alignment
+ double relativeY = static_cast(currentPos.y - currentMonitorInfo.rcMonitor.top) /
+ (currentMonitorInfo.rcMonitor.bottom - currentMonitorInfo.rcMonitor.top);
+
+ int targetHeight = rightInfo.rcMonitor.bottom - rightInfo.rcMonitor.top;
+ newPos.y = rightInfo.rcMonitor.top + static_cast(relativeY * targetHeight);
+ newPos.x = rightInfo.rcMonitor.right - 1; // Right edge of right monitor
+
+ // Clamp Y to target monitor bounds
+ newPos.y = max(rightInfo.rcMonitor.top, min(newPos.y, rightInfo.rcMonitor.bottom - 1));
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP SUCCESS - Moved to right end of horizontal stack");
+ Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
+#endif
+ } else {
+ // *** NO OTHER MONITOR IN HORIZONTAL STACK - WRAP WITHIN CURRENT MONITOR ***
+ newPos.x = currentMonitorInfo.rcMonitor.right - 1;
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP - No other monitor in stack, wrapping within current monitor");
+#endif
+ }
+ }
+ else if (!wrapped && currentPos.x >= currentMonitorInfo.rcMonitor.right - 1)
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: ======= HORIZONTAL WRAP: RIGHT EDGE DETECTED =======");
+#endif
+
+ // Find the left-most monitor in the horizontal stack (same row)
+ HMONITOR leftMonitor = nullptr;
+
+ if (currentLogicalPos.isValid) {
+ // Search left from current position to find the left-most monitor in same row
+ for (int col = 0; col <= 2; col++) { // Start from left and work right
+ HMONITOR candidateMonitor = m_topology.GetMonitorAt(currentLogicalPos.row, col);
+ if (candidateMonitor) {
+ leftMonitor = candidateMonitor;
+ break; // Found the left-most monitor
+ }
+ }
+ }
+
+ if (leftMonitor && leftMonitor != currentMonitor) {
+ // *** MOVE TO LEFT END OF HORIZONTAL STACK ***
+ MONITORINFO leftInfo{};
+ leftInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(leftMonitor, &leftInfo);
+
+ // Calculate relative Y position to maintain cursor Y alignment
+ double relativeY = static_cast(currentPos.y - currentMonitorInfo.rcMonitor.top) /
+ (currentMonitorInfo.rcMonitor.bottom - currentMonitorInfo.rcMonitor.top);
+
+ int targetHeight = leftInfo.rcMonitor.bottom - leftInfo.rcMonitor.top;
+ newPos.y = leftInfo.rcMonitor.top + static_cast(relativeY * targetHeight);
+ newPos.x = leftInfo.rcMonitor.left; // Left edge of left monitor
+
+ // Clamp Y to target monitor bounds
+ newPos.y = max(leftInfo.rcMonitor.top, min(newPos.y, leftInfo.rcMonitor.bottom - 1));
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP SUCCESS - Moved to left end of horizontal stack");
+ Logger::info(L"CursorWrap DEBUG: New position: ({}, {})", newPos.x, newPos.y);
+#endif
+ } else {
+ // *** NO OTHER MONITOR IN HORIZONTAL STACK - WRAP WITHIN CURRENT MONITOR ***
+ newPos.x = currentMonitorInfo.rcMonitor.left;
+ wrapped = true;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: HORIZONTAL WRAP - No other monitor in stack, wrapping within current monitor");
+#endif
+ }
+ }
+
+#ifdef _DEBUG
+ if (wrapped)
+ {
+ Logger::info(L"CursorWrap DEBUG: ======= WRAP RESULT =======");
+ Logger::info(L"CursorWrap DEBUG: Original: ({}, {}) -> New: ({}, {})",
+ currentPos.x, currentPos.y, newPos.x, newPos.y);
+ }
+ else
+ {
+ Logger::info(L"CursorWrap DEBUG: No wrapping performed - cursor not at edge");
+ }
+ Logger::info(L"CursorWrap DEBUG: ======= HANDLE MOUSE MOVE END =======");
+#endif
+
+ return newPos;
+ }
+
+ // Add test method for monitor topology validation
+ void RunMonitorTopologyTests()
+ {
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap: Running monitor topology tests...");
+
+ // Test all 9 possible monitor positions in 3x3 grid
+ const char* gridNames[3][3] = {
+ {"TL", "TC", "TR"}, // Top-Left, Top-Center, Top-Right
+ {"ML", "MC", "MR"}, // Middle-Left, Middle-Center, Middle-Right
+ {"BL", "BC", "BR"} // Bottom-Left, Bottom-Center, Bottom-Right
+ };
+
+ for (int row = 0; row < 3; row++)
+ {
+ for (int col = 0; col < 3; col++)
+ {
+ HMONITOR monitor = m_topology.GetMonitorAt(row, col);
+ if (monitor)
+ {
+ std::string gridName(gridNames[row][col]);
+ std::wstring wGridName(gridName.begin(), gridName.end());
+ Logger::info(L"CursorWrap TEST: Monitor at [{}][{}] ({}) exists",
+ row, col, wGridName.c_str());
+
+ // Test adjacent monitor finding
+ HMONITOR up = m_topology.FindAdjacentMonitor(monitor, -1, 0);
+ HMONITOR down = m_topology.FindAdjacentMonitor(monitor, 1, 0);
+ HMONITOR left = m_topology.FindAdjacentMonitor(monitor, 0, -1);
+ HMONITOR right = m_topology.FindAdjacentMonitor(monitor, 0, 1);
+
+ Logger::info(L"CursorWrap TEST: Adjacent monitors - Up: {}, Down: {}, Left: {}, Right: {}",
+ up ? L"YES" : L"NO", down ? L"YES" : L"NO",
+ left ? L"YES" : L"NO", right ? L"YES" : L"NO");
+ }
+ }
+ }
+
+ Logger::info(L"CursorWrap: Monitor topology tests completed.");
+#endif
+ }
+
+ // Add method to trigger test suite (can be called via hotkey in debug builds)
+ void RunComprehensiveTests()
+ {
+#ifdef _DEBUG
+ RunMonitorTopologyTests();
+
+ // Test cursor wrapping scenarios
+ Logger::info(L"CursorWrap: Testing cursor wrapping scenarios...");
+
+ // Simulate cursor positions at each monitor edge and verify expected behavior
+ for (const auto& monitor : m_monitors)
+ {
+ HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
+ LogicalPosition pos = m_topology.GetPosition(hMonitor);
+
+ if (pos.isValid)
+ {
+ Logger::info(L"CursorWrap TEST: Testing monitor at position [{}][{}]", pos.row, pos.col);
+
+ // Test top edge
+ POINT topEdge = {(monitor.rect.left + monitor.rect.right) / 2, monitor.rect.top};
+ POINT newPos = HandleMouseMove(topEdge);
+ Logger::info(L"CursorWrap TEST: Top edge ({}, {}) -> ({}, {})",
+ topEdge.x, topEdge.y, newPos.x, newPos.y);
+
+ // Test bottom edge
+ POINT bottomEdge = {(monitor.rect.left + monitor.rect.right) / 2, monitor.rect.bottom - 1};
+ newPos = HandleMouseMove(bottomEdge);
+ Logger::info(L"CursorWrap TEST: Bottom edge ({}, {}) -> ({}, {})",
+ bottomEdge.x, bottomEdge.y, newPos.x, newPos.y);
+
+ // Test left edge
+ POINT leftEdge = {monitor.rect.left, (monitor.rect.top + monitor.rect.bottom) / 2};
+ newPos = HandleMouseMove(leftEdge);
+ Logger::info(L"CursorWrap TEST: Left edge ({}, {}) -> ({}, {})",
+ leftEdge.x, leftEdge.y, newPos.x, newPos.y);
+
+ // Test right edge
+ POINT rightEdge = {monitor.rect.right - 1, (monitor.rect.top + monitor.rect.bottom) / 2};
+ newPos = HandleMouseMove(rightEdge);
+ Logger::info(L"CursorWrap TEST: Right edge ({}, {}) -> ({}, {})",
+ rightEdge.x, rightEdge.y, newPos.x, newPos.y);
+ }
+ }
+
+ Logger::info(L"CursorWrap: Comprehensive tests completed.");
+#endif
+ }
+};
+
+// Implementation of MonitorTopology methods
+void MonitorTopology::Initialize(const std::vector& monitors)
+{
+ // Clear existing data
+ grid.assign(3, std::vector(3, nullptr));
+ monitorToPosition.clear();
+ positionToMonitor.clear();
+
+ if (monitors.empty()) return;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: ======= TOPOLOGY INITIALIZATION START =======");
+ Logger::info(L"CursorWrap DEBUG: Initializing topology for {} monitors", monitors.size());
+ for (const auto& monitor : monitors)
+ {
+ Logger::info(L"CursorWrap DEBUG: Monitor {}: bounds=({},{},{},{}), isPrimary={}",
+ monitor.monitorId, monitor.rect.left, monitor.rect.top,
+ monitor.rect.right, monitor.rect.bottom, monitor.isPrimary);
+ }
+#endif
+
+ // Special handling for 2 monitors - use physical position, not discovery order
+ if (monitors.size() == 2)
+ {
+ // Determine if arrangement is horizontal or vertical by comparing centers
+ POINT center0 = {(monitors[0].rect.left + monitors[0].rect.right) / 2,
+ (monitors[0].rect.top + monitors[0].rect.bottom) / 2};
+ POINT center1 = {(monitors[1].rect.left + monitors[1].rect.right) / 2,
+ (monitors[1].rect.top + monitors[1].rect.bottom) / 2};
+
+ int xDiff = abs(center0.x - center1.x);
+ int yDiff = abs(center0.y - center1.y);
+
+ bool isHorizontal = xDiff > yDiff;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Monitor centers: M0=({}, {}), M1=({}, {})",
+ center0.x, center0.y, center1.x, center1.y);
+ Logger::info(L"CursorWrap DEBUG: Differences: X={}, Y={}, IsHorizontal={}",
+ xDiff, yDiff, isHorizontal);
+#endif
+
+ if (isHorizontal)
+ {
+ // Horizontal arrangement - place in middle row [1,0] and [1,2]
+ for (const auto& monitor : monitors)
+ {
+ HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
+ POINT center = {(monitor.rect.left + monitor.rect.right) / 2,
+ (monitor.rect.top + monitor.rect.bottom) / 2};
+
+ int row = 1; // Middle row
+ int col = (center.x < (center0.x + center1.x) / 2) ? 0 : 2; // Left or right based on center
+
+ grid[row][col] = hMonitor;
+ monitorToPosition[hMonitor] = {row, col, true};
+ positionToMonitor[{row, col}] = hMonitor;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Monitor {} (horizontal) placed at grid[{}][{}]",
+ monitor.monitorId, row, col);
+#endif
+ }
+ }
+ else
+ {
+ // *** VERTICAL ARRANGEMENT - CRITICAL LOGIC ***
+ // Sort monitors by Y coordinate to determine vertical order
+ std::vector> sortedMonitors;
+ for (int i = 0; i < 2; i++) {
+ sortedMonitors.push_back({i, monitors[i]});
+ }
+
+ // Sort by Y coordinate (top to bottom)
+ std::sort(sortedMonitors.begin(), sortedMonitors.end(),
+ [](const std::pair& a, const std::pair& b) {
+ int centerA = (a.second.rect.top + a.second.rect.bottom) / 2;
+ int centerB = (b.second.rect.top + b.second.rect.bottom) / 2;
+ return centerA < centerB; // Top first
+ });
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: VERTICAL ARRANGEMENT DETECTED");
+ Logger::info(L"CursorWrap DEBUG: Top monitor: ID={}, Y-center={}",
+ sortedMonitors[0].second.monitorId,
+ (sortedMonitors[0].second.rect.top + sortedMonitors[0].second.rect.bottom) / 2);
+ Logger::info(L"CursorWrap DEBUG: Bottom monitor: ID={}, Y-center={}",
+ sortedMonitors[1].second.monitorId,
+ (sortedMonitors[1].second.rect.top + sortedMonitors[1].second.rect.bottom) / 2);
+#endif
+
+ // Place monitors in grid based on sorted order
+ for (int i = 0; i < 2; i++) {
+ const auto& monitorPair = sortedMonitors[i];
+ const auto& monitor = monitorPair.second;
+ HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
+
+ int col = 1; // Middle column for vertical arrangement
+ int row = (i == 0) ? 0 : 2; // Top monitor at row 0, bottom at row 2
+
+ grid[row][col] = hMonitor;
+ monitorToPosition[hMonitor] = {row, col, true};
+ positionToMonitor[{row, col}] = hMonitor;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Monitor {} (vertical) placed at grid[{}][{}] - {} position",
+ monitor.monitorId, row, col, (i == 0) ? L"TOP" : L"BOTTOM");
+#endif
+ }
+ }
+ }
+ else
+ {
+ // For more than 2 monitors, use the general algorithm
+ RECT totalBounds = monitors[0].rect;
+ for (const auto& monitor : monitors)
+ {
+ totalBounds.left = min(totalBounds.left, monitor.rect.left);
+ totalBounds.top = min(totalBounds.top, monitor.rect.top);
+ totalBounds.right = max(totalBounds.right, monitor.rect.right);
+ totalBounds.bottom = max(totalBounds.bottom, monitor.rect.bottom);
+ }
+
+ int totalWidth = totalBounds.right - totalBounds.left;
+ int totalHeight = totalBounds.bottom - totalBounds.top;
+ int gridWidth = max(1, totalWidth / 3);
+ int gridHeight = max(1, totalHeight / 3);
+
+ // Place monitors in the 3x3 grid based on their center points
+ for (const auto& monitor : monitors)
+ {
+ HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
+
+ // Calculate center point of monitor
+ int centerX = (monitor.rect.left + monitor.rect.right) / 2;
+ int centerY = (monitor.rect.top + monitor.rect.bottom) / 2;
+
+ // Map to grid position
+ int col = (centerX - totalBounds.left) / gridWidth;
+ int row = (centerY - totalBounds.top) / gridHeight;
+
+ // Ensure we stay within bounds
+ col = max(0, min(2, col));
+ row = max(0, min(2, row));
+
+ grid[row][col] = hMonitor;
+ monitorToPosition[hMonitor] = {row, col, true};
+ positionToMonitor[{row, col}] = hMonitor;
+
+#ifdef _DEBUG
+ Logger::info(L"CursorWrap DEBUG: Monitor {} placed at grid[{}][{}], center=({}, {})",
+ monitor.monitorId, row, col, centerX, centerY);
+#endif
+ }
+ }
+
+#ifdef _DEBUG
+ // *** CRITICAL: Print topology map using OutputDebugString for debug builds ***
+ Logger::info(L"CursorWrap DEBUG: ======= FINAL TOPOLOGY MAP =======");
+ OutputDebugStringA("CursorWrap TOPOLOGY MAP:\n");
+ for (int r = 0; r < 3; r++)
+ {
+ std::string rowStr = " ";
+ for (int c = 0; c < 3; c++)
+ {
+ if (grid[r][c])
+ {
+ // Find monitor ID for this handle
+ int monitorId = -1;
+ for (const auto& monitor : monitors)
+ {
+ HMONITOR handle = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
+ if (handle == grid[r][c])
+ {
+ monitorId = monitor.monitorId + 1; // Convert to 1-based for display
+ break;
+ }
+ }
+ rowStr += std::to_string(monitorId) + " ";
+ }
+ else
+ {
+ rowStr += ". ";
+ }
+ }
+ rowStr += "\n";
+ OutputDebugStringA(rowStr.c_str());
+
+ // Also log to PowerToys logger
+ std::wstring wRowStr(rowStr.begin(), rowStr.end());
+ Logger::info(wRowStr.c_str());
+ }
+ OutputDebugStringA("======= END TOPOLOGY MAP =======\n");
+
+ // Additional validation logging
+ Logger::info(L"CursorWrap DEBUG: ======= GRID POSITION VALIDATION =======");
+ for (const auto& monitor : monitors)
+ {
+ HMONITOR hMonitor = MonitorFromRect(&monitor.rect, MONITOR_DEFAULTTONEAREST);
+ LogicalPosition pos = GetPosition(hMonitor);
+ if (pos.isValid)
+ {
+ Logger::info(L"CursorWrap DEBUG: Monitor {} -> grid[{}][{}]", monitor.monitorId, pos.row, pos.col);
+ OutputDebugStringA(("Monitor " + std::to_string(monitor.monitorId) + " -> grid[" + std::to_string(pos.row) + "][" + std::to_string(pos.col) + "]\n").c_str());
+
+ // Test adjacent finding
+ HMONITOR up = FindAdjacentMonitor(hMonitor, -1, 0);
+ HMONITOR down = FindAdjacentMonitor(hMonitor, 1, 0);
+ HMONITOR left = FindAdjacentMonitor(hMonitor, 0, -1);
+ HMONITOR right = FindAdjacentMonitor(hMonitor, 0, 1);
+
+ Logger::info(L"CursorWrap DEBUG: Monitor {} adjacents - Up: {}, Down: {}, Left: {}, Right: {}",
+ monitor.monitorId, up ? L"YES" : L"NO", down ? L"YES" : L"NO",
+ left ? L"YES" : L"NO", right ? L"YES" : L"NO");
+ }
+ }
+ Logger::info(L"CursorWrap DEBUG: ======= TOPOLOGY INITIALIZATION COMPLETE =======");
+#endif
+}
+
+LogicalPosition MonitorTopology::GetPosition(HMONITOR monitor) const
+{
+ auto it = monitorToPosition.find(monitor);
+ if (it != monitorToPosition.end())
+ {
+ return it->second;
+ }
+ return {-1, -1, false};
+}
+
+HMONITOR MonitorTopology::GetMonitorAt(int row, int col) const
+{
+ if (row >= 0 && row < 3 && col >= 0 && col < 3)
+ {
+ return grid[row][col];
+ }
+ return nullptr;
+}
+
+HMONITOR MonitorTopology::FindAdjacentMonitor(HMONITOR current, int deltaRow, int deltaCol) const
+{
+ LogicalPosition currentPos = GetPosition(current);
+ if (!currentPos.isValid) return nullptr;
+
+ int newRow = currentPos.row + deltaRow;
+ int newCol = currentPos.col + deltaCol;
+
+ return GetMonitorAt(newRow, newCol);
+}
+
+extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
+{
+ return new CursorWrap();
+}
diff --git a/src/modules/MouseUtils/CursorWrap/packages.config b/src/modules/MouseUtils/CursorWrap/packages.config
new file mode 100644
index 0000000000..2c5d71ae86
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/MouseUtils/CursorWrap/pch.cpp b/src/modules/MouseUtils/CursorWrap/pch.cpp
new file mode 100644
index 0000000000..17305716aa
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
\ No newline at end of file
diff --git a/src/modules/MouseUtils/CursorWrap/pch.h b/src/modules/MouseUtils/CursorWrap/pch.h
new file mode 100644
index 0000000000..86f11c99ba
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/pch.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+#include
+
+#include
+#include
+#include
+
+// Note: Common includes moved to individual source files due to include path issues
+// #include
+// #include
+// #include
\ No newline at end of file
diff --git a/src/modules/MouseUtils/CursorWrap/resource.h b/src/modules/MouseUtils/CursorWrap/resource.h
new file mode 100644
index 0000000000..9b49c0e3cc
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/resource.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#define IDS_CURSORWRAP_NAME 101
+#define IDS_CURSORWRAP_DISABLE_WRAP_DURING_DRAG 102
diff --git a/src/modules/MouseUtils/CursorWrap/trace.cpp b/src/modules/MouseUtils/CursorWrap/trace.cpp
new file mode 100644
index 0000000000..ebfe32c23c
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/trace.cpp
@@ -0,0 +1,31 @@
+#include "pch.h"
+#include "trace.h"
+
+#include "../../../../common/Telemetry/TraceBase.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::EnableCursorWrap(const bool enabled) noexcept
+{
+ TraceLoggingWriteWrapper(
+ g_hProvider,
+ "CursorWrap_EnableCursorWrap",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingBoolean(enabled, "Enabled"));
+}
\ No newline at end of file
diff --git a/src/modules/MouseUtils/CursorWrap/trace.h b/src/modules/MouseUtils/CursorWrap/trace.h
new file mode 100644
index 0000000000..b2f6a9a8eb
--- /dev/null
+++ b/src/modules/MouseUtils/CursorWrap/trace.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include
+
+class Trace : public telemetry::TraceBase
+{
+public:
+ static void RegisterProvider();
+ static void UnregisterProvider();
+ static void EnableCursorWrap(const bool enabled) noexcept;
+};
\ No newline at end of file
diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp
index c94c79e178..f953af0fdd 100644
--- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp
+++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp
@@ -189,7 +189,7 @@ bool SuperSonar::Initialize(HINSTANCE hinst)
return false;
}
- DWORD exStyle = WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
+ DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle();
HWND created = CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this);
if (!created)
{
@@ -269,10 +269,6 @@ LRESULT SuperSonar::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) n
case WM_NCHITTEST:
return HTTRANSPARENT;
-
- case WM_SETCURSOR:
- SetCursor(LoadCursor(nullptr, IDC_ARROW));
- return TRUE;
}
if (message == WM_PRIV_SHORTCUT)
@@ -539,7 +535,7 @@ void SuperSonar::StartSonar()
Trace::MousePointerFocused();
// Cover the entire virtual screen.
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
- SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, SWP_NOACTIVATE);
+ SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, 0);
m_sonarPos = ptNowhere;
OnMouseTimer();
UpdateMouseSnooping();
diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp b/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp
index fd144e807b..b460e29643 100644
--- a/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp
+++ b/src/modules/MouseUtils/MousePointerCrosshairs/dllmain.cpp
@@ -14,6 +14,9 @@ extern void InclusiveCrosshairsRequestUpdatePosition();
extern void InclusiveCrosshairsEnsureOn();
extern void InclusiveCrosshairsEnsureOff();
extern void InclusiveCrosshairsSetExternalControl(bool enabled);
+extern void InclusiveCrosshairsSetOrientation(CrosshairsOrientation orientation);
+extern bool InclusiveCrosshairsIsEnabled();
+extern void InclusiveCrosshairsSwitch();
// Non-Localizable strings
namespace
@@ -244,12 +247,19 @@ public:
return false;
}
- if (hotkeyId == 0)
+ if (hotkeyId == 0) // Crosshairs activation
{
+ // If gliding cursor is active, cancel it and activate crosshairs
+ if (m_glideState.load() != 0)
+ {
+ CancelGliding(true /*activateCrosshairs*/);
+ return true;
+ }
+ // Otherwise, normal crosshairs toggle
InclusiveCrosshairsSwitch();
return true;
}
- if (hotkeyId == 1)
+ if (hotkeyId == 1) // Gliding cursor activation
{
HandleGlidingHotkey();
return true;
@@ -268,25 +278,44 @@ private:
SendInput(2, inputs, sizeof(INPUT));
}
- // Cancel gliding without performing the final click (Escape handling)
- void CancelGliding()
+ // Cancel gliding with option to activate crosshairs in user's preferred orientation
+ void CancelGliding(bool activateCrosshairs)
{
int state = m_glideState.load();
if (state == 0)
{
return; // nothing to cancel
}
+
+ // Stop all gliding operations
StopXTimer();
StopYTimer();
m_glideState = 0;
- InclusiveCrosshairsEnsureOff();
+ UninstallKeyboardHook();
+
+ // Reset crosshairs control and restore user settings
InclusiveCrosshairsSetExternalControl(false);
+ InclusiveCrosshairsSetOrientation(m_inclusiveCrosshairsSettings.crosshairsOrientation);
+
+ if (activateCrosshairs)
+ {
+ // User is switching to crosshairs mode - enable with their settings
+ InclusiveCrosshairsEnsureOn();
+ }
+ else
+ {
+ // User canceled (Escape) - turn off crosshairs completely
+ InclusiveCrosshairsEnsureOff();
+ }
+
+ // Reset gliding state
if (auto s = m_state)
{
s->xFraction = 0.0;
s->yFraction = 0.0;
}
- Logger::debug("Gliding cursor cancelled via Escape key");
+
+ Logger::debug("Gliding cursor cancelled (activateCrosshairs={})", activateCrosshairs ? 1 : 0);
}
// Stateless helpers operating on shared State
@@ -425,21 +454,22 @@ private:
{
return;
}
- // Simulate the AHK state machine
+
int state = m_glideState.load();
switch (state)
{
- case 0:
+ case 0: // Starting gliding
{
- // For detect for cancel key
+ // Install keyboard hook for Escape cancellation
InstallKeyboardHook();
- // Ensure crosshairs on (do not toggle off if already on)
- InclusiveCrosshairsEnsureOn();
- // Disable internal mouse hook so we control position updates explicitly
+
+ // Force crosshairs visible in BOTH orientation for gliding, regardless of user setting
+ // Set external control before enabling to prevent internal movement hook from attaching
InclusiveCrosshairsSetExternalControl(true);
- // Override crosshairs to show both for Gliding Cursor
InclusiveCrosshairsSetOrientation(CrosshairsOrientation::Both);
+ InclusiveCrosshairsEnsureOn(); // Always ensure they are visible
+ // Initialize gliding state
s->currentXPos = 0;
s->currentXSpeed = s->fastHSpeed;
s->xFraction = 0.0;
@@ -447,20 +477,17 @@ private:
int y = GetSystemMetrics(SM_CYVIRTUALSCREEN) / 2;
SetCursorPos(0, y);
InclusiveCrosshairsRequestUpdatePosition();
+
m_glideState = 1;
StartXTimer();
break;
}
- case 1:
- {
- // Slow horizontal
+ case 1: // Slow horizontal
s->currentXSpeed = s->slowHSpeed;
m_glideState = 2;
break;
- }
- case 2:
+ case 2: // Switch to vertical fast
{
- // Stop horizontal, start vertical (fast)
StopXTimer();
s->currentYSpeed = s->fastVSpeed;
s->currentYPos = 0;
@@ -471,33 +498,37 @@ private:
StartYTimer();
break;
}
- case 3:
- {
- // Slow vertical
+ case 3: // Slow vertical
s->currentYSpeed = s->slowVSpeed;
m_glideState = 4;
break;
- }
- case 4:
+ case 4: // Finalize (click and end)
default:
{
- UninstallKeyboardHook();
- // Stop vertical, click, turn crosshairs off, re-enable internal tracking, reset state
+ // Complete the gliding sequence
StopYTimer();
m_glideState = 0;
LeftClick();
- InclusiveCrosshairsEnsureOff();
+
+ // Restore normal crosshairs operation and turn them off
InclusiveCrosshairsSetExternalControl(false);
- // Restore original crosshairs orientation setting
InclusiveCrosshairsSetOrientation(m_inclusiveCrosshairsSettings.crosshairsOrientation);
- s->xFraction = 0.0;
- s->yFraction = 0.0;
+ InclusiveCrosshairsEnsureOff();
+
+ UninstallKeyboardHook();
+
+ // Reset state
+ if (auto sp = m_state)
+ {
+ sp->xFraction = 0.0;
+ sp->yFraction = 0.0;
+ }
break;
}
}
}
- // Low-level keyboard hook procedures
+ // Low-level keyboard hook for Escape cancellation
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
@@ -509,14 +540,11 @@ private:
{
if (inst->m_enabled && inst->m_glideState.load() != 0)
{
- inst->UninstallKeyboardHook();
- inst->CancelGliding();
+ inst->CancelGliding(false); // Escape cancels without activating crosshairs
}
}
}
}
-
- // Do not swallow Escape; pass it through
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.Encryption.cs b/src/modules/MouseWithoutBorders/App/Class/Common.Encryption.cs
deleted file mode 100644
index 1293a4ef39..0000000000
--- a/src/modules/MouseWithoutBorders/App/Class/Common.Encryption.cs
+++ /dev/null
@@ -1,248 +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.
-
-//
-// Encrypt/decrypt implementation.
-//
-//
-// 2008 created by Truong Do (ductdo).
-// 2009-... modified by Truong Do (TruongDo).
-// 2023- Included in PowerToys.
-//
-using System;
-using System.Collections.Concurrent;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Threading.Tasks;
-
-using MouseWithoutBorders.Core;
-
-namespace MouseWithoutBorders
-{
- internal partial class Common
- {
-#pragma warning disable SYSLIB0021
- private static AesCryptoServiceProvider symAl;
-#pragma warning restore SYSLIB0021
-#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
- internal static string myKey;
-#pragma warning restore SA1307
- private static uint magicNumber;
- private static Random ran = new(); // Used for non encryption related functionality.
- internal const int SymAlBlockSize = 16;
-
- ///
- /// This is used for the first encryption block, the following blocks will be combined with the cipher text of the previous block.
- /// Thus identical blocks in the socket stream would be encrypted to different cipher text blocks.
- /// The first block is a handshake one containing random data.
- /// Related Unit Test: TestEncryptDecrypt
- ///
- internal static readonly string InitialIV = ulong.MaxValue.ToString(CultureInfo.InvariantCulture);
-
- internal static Random Ran
- {
- get => Common.ran ??= new Random();
- set => Common.ran = value;
- }
-
- internal static uint MagicNumber
- {
- get => Common.magicNumber;
- set => Common.magicNumber = value;
- }
-
- internal static string MyKey
- {
- get => Common.myKey;
-
- set
- {
- if (Common.myKey != value)
- {
- Common.myKey = value;
- _ = Task.Factory.StartNew(
- () => Common.GenLegalKey(),
- System.Threading.CancellationToken.None,
- TaskCreationOptions.None,
- TaskScheduler.Default); // Cache the key to improve UX.
- }
- }
- }
-
- internal static string KeyDisplayedText(string key)
- {
- string displayedValue = string.Empty;
- int i = 0;
-
- do
- {
- int length = Math.Min(4, key.Length - i);
- displayedValue += string.Concat(key.AsSpan(i, length), " ");
- i += 4;
- }
- while (i < key.Length - 1);
-
- return displayedValue.Trim();
- }
-
- internal static bool GeneratedKey { get; set; }
-
- internal static bool KeyCorrupted { get; set; }
-
- internal static void InitEncryption()
- {
- try
- {
- if (symAl == null)
- {
-#pragma warning disable SYSLIB0021 // No proper replacement for now
- symAl = new AesCryptoServiceProvider();
-#pragma warning restore SYSLIB0021
- symAl.KeySize = 256;
- symAl.BlockSize = SymAlBlockSize * 8;
- symAl.Padding = PaddingMode.Zeros;
- symAl.Mode = CipherMode.CBC;
- symAl.GenerateIV();
- }
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
- }
-
- private static readonly ConcurrentDictionary LegalKeyDictionary = new(StringComparer.OrdinalIgnoreCase);
-
- internal static byte[] GenLegalKey()
- {
- byte[] rv;
- string myKey = Common.MyKey;
-
- if (!LegalKeyDictionary.TryGetValue(myKey, out byte[] value))
- {
- Rfc2898DeriveBytes key = new(
- myKey,
- Common.GetBytesU(InitialIV),
- 50000,
- HashAlgorithmName.SHA512);
- rv = key.GetBytes(32);
- _ = LegalKeyDictionary.AddOrUpdate(myKey, rv, (k, v) => rv);
- }
- else
- {
- rv = value;
- }
-
- return rv;
- }
-
- private static byte[] GenLegalIV()
- {
- string st = InitialIV;
- int ivLength = symAl.IV.Length;
- if (st.Length > ivLength)
- {
- st = st[..ivLength];
- }
- else if (st.Length < ivLength)
- {
- st = st.PadRight(ivLength, ' ');
- }
-
- return GetBytes(st);
- }
-
- internal static Stream GetEncryptedStream(Stream encryptedStream)
- {
- ICryptoTransform encryptor;
- encryptor = symAl.CreateEncryptor(GenLegalKey(), GenLegalIV());
- return new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write);
- }
-
- internal static Stream GetDecryptedStream(Stream encryptedStream)
- {
- ICryptoTransform decryptor;
- decryptor = symAl.CreateDecryptor(GenLegalKey(), GenLegalIV());
- return new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
- }
-
- internal static uint Get24BitHash(string st)
- {
- if (string.IsNullOrEmpty(st))
- {
- return 0;
- }
-
- byte[] bytes = new byte[PACKAGE_SIZE];
- for (int i = 0; i < PACKAGE_SIZE; i++)
- {
- if (i < st.Length)
- {
- bytes[i] = (byte)st[i];
- }
- }
-
- var hash = SHA512.Create();
- byte[] hashValue = hash.ComputeHash(bytes);
-
- for (int i = 0; i < 50000; i++)
- {
- hashValue = hash.ComputeHash(hashValue);
- }
-
- Logger.LogDebug(string.Format(CultureInfo.CurrentCulture, "magic: {0},{1},{2}", hashValue[0], hashValue[1], hashValue[^1]));
- hash.Clear();
- return (uint)((hashValue[0] << 23) + (hashValue[1] << 16) + (hashValue[^1] << 8) + hashValue[2]);
- }
-
- internal static string GetDebugInfo(string st)
- {
- return string.IsNullOrEmpty(st) ? st : ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
- }
-
- internal static string CreateDefaultKey()
- {
- return CreateRandomKey();
- }
-
- private const int PW_LENGTH = 16;
-
- public static string CreateRandomKey()
- {
- // Not including characters like "'`O0& since they are confusing to users.
- string[] chars = new[] { "abcdefghjkmnpqrstuvxyz", "ABCDEFGHJKMNPQRSTUVXYZ", "123456789", "~!@#$%^*()_-+=:;<,>.?/\\|[]" };
- char[][] charactersUsedForKey = chars.Select(charset => Enumerable.Range(0, charset.Length - 1).Select(i => charset[i]).ToArray()).ToArray();
- byte[] randomData = new byte[1];
- string key = string.Empty;
-
- do
- {
- foreach (string set in chars)
- {
- randomData = RandomNumberGenerator.GetBytes(1);
- key += set[randomData[0] % set.Length];
-
- if (key.Length >= PW_LENGTH)
- {
- break;
- }
- }
- }
- while (key.Length < PW_LENGTH);
-
- return key;
- }
-
- internal static bool IsKeyValid(string key, out string error)
- {
- error = string.IsNullOrEmpty(key) || key.Length < 16
- ? "Key must have at least 16 characters in length (spaces are discarded). Key must be auto generated in one of the machines."
- : null;
-
- return error == null;
- }
- }
-}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.Package.cs b/src/modules/MouseWithoutBorders/App/Class/Common.Package.cs
deleted file mode 100644
index baaf1c0544..0000000000
--- a/src/modules/MouseWithoutBorders/App/Class/Common.Package.cs
+++ /dev/null
@@ -1,262 +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.
-
-//
-// Package format/conversion.
-//
-//
-// 2008 created by Truong Do (ductdo).
-// 2009-... modified by Truong Do (TruongDo).
-// 2023- Included in PowerToys.
-//
-using System;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-
-// In X64, we are WOW
-[module: SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Scope = "type", Target = "MouseWithoutBorders.DATA", Justification = "Dotnet port with style preservation")]
-
-namespace MouseWithoutBorders
-{
- internal enum PackageType// : int
- {
- // Search for PACKAGE_TYPE_RELATED before changing these!
- Invalid = 0xFF,
-
- Error = 0xFE,
-
- Hi = 2,
- Hello = 3,
- ByeBye = 4,
-
- Heartbeat = 20,
- Awake = 21,
- HideMouse = 50,
- Heartbeat_ex = 51,
- Heartbeat_ex_l2 = 52,
- Heartbeat_ex_l3 = 53,
-
- Clipboard = 69,
- ClipboardDragDrop = 70,
- ClipboardDragDropEnd = 71,
- ExplorerDragDrop = 72,
- ClipboardCapture = 73,
- CaptureScreenCommand = 74,
- ClipboardDragDropOperation = 75,
- ClipboardDataEnd = 76,
- MachineSwitched = 77,
- ClipboardAsk = 78,
- ClipboardPush = 79,
-
- NextMachine = 121,
- Keyboard = 122,
- Mouse = 123,
- ClipboardText = 124,
- ClipboardImage = 125,
-
- Handshake = 126,
- HandshakeAck = 127,
-
- Matrix = 128,
- MatrixSwapFlag = 2,
- MatrixTwoRowFlag = 4,
- }
-
- internal struct PackageMonitor
- {
- internal ulong Keyboard;
- internal ulong Mouse;
- internal ulong Heartbeat;
- internal ulong ByeBye;
- internal ulong Hello;
- internal ulong Matrix;
- internal ulong ClipboardText;
- internal ulong ClipboardImage;
- internal ulong Clipboard;
- internal ulong ClipboardDragDrop;
- internal ulong ClipboardDragDropEnd;
- internal ulong ClipboardAsk;
- internal ulong ExplorerDragDrop;
- internal ulong Nil;
-
- internal PackageMonitor(ulong value)
- {
- ClipboardDragDrop = ClipboardDragDropEnd = ExplorerDragDrop =
- Keyboard = Mouse = Heartbeat = ByeBye = Hello = Clipboard =
- Matrix = ClipboardImage = ClipboardText = Nil = ClipboardAsk = value;
- }
- }
-
- internal enum ID : uint
- {
- NONE = 0,
- ALL = 255,
- }
-
- internal enum ClipboardPostAction : uint
- {
- Other = 0,
- Desktop = 1,
- Mspaint = 2,
- }
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct KEYBDDATA
- {
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
- internal int wVk;
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
- internal int dwFlags;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct MOUSEDATA
- {
- internal int X;
- internal int Y;
- internal int WheelDelta;
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
- internal int dwFlags;
- }
-
- // The beauty of "union" in C#
- [StructLayout(LayoutKind.Explicit)]
- internal class DATA
- {
- [FieldOffset(0)]
- internal PackageType Type; // 4 (first byte = package type, 1 = checksum, 2+3 = magic no.)
-
- [FieldOffset(sizeof(PackageType))]
- internal int Id; // 4
-
- [FieldOffset(sizeof(PackageType) + sizeof(uint))]
- internal ID Src; // 4
-
- [FieldOffset(sizeof(PackageType) + (2 * sizeof(uint)))]
- internal ID Des; // 4
-
- [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
- internal long DateTime;
-
- [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)) + sizeof(long))]
- internal KEYBDDATA Kd;
-
- [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
- internal MOUSEDATA Md;
-
- [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
- internal ID Machine1;
-
- [FieldOffset(sizeof(PackageType) + (4 * sizeof(uint)))]
- internal ID Machine2;
-
- [FieldOffset(sizeof(PackageType) + (5 * sizeof(uint)))]
- internal ID Machine3;
-
- [FieldOffset(sizeof(PackageType) + (6 * sizeof(uint)))]
- internal ID Machine4;
-
- [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
- internal ClipboardPostAction PostAction;
-
- [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)))]
- private long machineNameP1;
-
- [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + sizeof(long))]
- private long machineNameP2;
-
- [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (2 * sizeof(long)))]
- private long machineNameP3;
-
- [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (3 * sizeof(long)))]
- private long machineNameP4;
-
- internal string MachineName
- {
- get
- {
- string name = Common.GetString(BitConverter.GetBytes(machineNameP1))
- + Common.GetString(BitConverter.GetBytes(machineNameP2))
- + Common.GetString(BitConverter.GetBytes(machineNameP3))
- + Common.GetString(BitConverter.GetBytes(machineNameP4));
- return name.Trim();
- }
-
- set
- {
- byte[] machineName = Common.GetBytes(value.PadRight(32, ' '));
- machineNameP1 = BitConverter.ToInt64(machineName, 0);
- machineNameP2 = BitConverter.ToInt64(machineName, 8);
- machineNameP3 = BitConverter.ToInt64(machineName, 16);
- machineNameP4 = BitConverter.ToInt64(machineName, 24);
- }
- }
-
- public DATA()
- {
- }
-
- public DATA(byte[] initialData)
- {
- Bytes = initialData;
- }
-
- internal byte[] Bytes
- {
- get
- {
- byte[] buf = new byte[IsBigPackage ? Common.PACKAGE_SIZE_EX : Common.PACKAGE_SIZE];
- Array.Copy(StructToBytes(this), buf, IsBigPackage ? Common.PACKAGE_SIZE_EX : Common.PACKAGE_SIZE);
-
- return buf;
- }
-
- set
- {
- Debug.Assert(value.Length <= Common.PACKAGE_SIZE_EX, "Length > package size");
- byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
- Array.Copy(value, buf, value.Length);
- BytesToStruct(buf, this);
- }
- }
-
- internal bool IsBigPackage
- {
- get => Type == 0
- ? throw new InvalidOperationException("Package type not set.")
- : Type switch
- {
- PackageType.Hello or PackageType.Awake or PackageType.Heartbeat or PackageType.Heartbeat_ex or PackageType.Handshake or PackageType.HandshakeAck or PackageType.ClipboardPush or PackageType.Clipboard or PackageType.ClipboardAsk or PackageType.ClipboardImage or PackageType.ClipboardText or PackageType.ClipboardDataEnd => true,
- _ => (Type & PackageType.Matrix) == PackageType.Matrix,
- };
- }
-
- private byte[] StructToBytes(object structObject)
- {
- byte[] bytes = new byte[Common.PACKAGE_SIZE_EX];
- GCHandle bHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
- Marshal.StructureToPtr(structObject, Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0), false);
- bHandle.Free();
- return bytes;
- }
-
- private void BytesToStruct(byte[] value, object structObject)
- {
- GCHandle bHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
- Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(value, 0), structObject);
- bHandle.Free();
- }
- }
-
- internal partial class Common
- {
- internal const byte PACKAGE_SIZE = 32;
- internal const byte PACKAGE_SIZE_EX = 64;
- internal const byte WP_PACKAGE_SIZE = 6;
- internal static PackageMonitor PackageSent;
- internal static PackageMonitor PackageReceived;
- internal static int PackageID;
- }
-}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.ShutdownWithPowerToys.cs b/src/modules/MouseWithoutBorders/App/Class/Common.ShutdownWithPowerToys.cs
deleted file mode 100644
index 7c0dd4eb9b..0000000000
--- a/src/modules/MouseWithoutBorders/App/Class/Common.ShutdownWithPowerToys.cs
+++ /dev/null
@@ -1,33 +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 System;
-
-using ManagedCommon;
-using Microsoft.PowerToys.Telemetry;
-using MouseWithoutBorders.Class;
-
-using Logger = MouseWithoutBorders.Core.Logger;
-
-namespace MouseWithoutBorders
-{
- internal class ShutdownWithPowerToys
- {
- public static void WaitForPowerToysRunner(ETWTrace etwTrace)
- {
- try
- {
- RunnerHelper.WaitForPowerToysRunnerExitFallback(() =>
- {
- etwTrace?.Dispose();
- Common.MainForm.Quit(true, false);
- });
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
- }
- }
-}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.VK.cs b/src/modules/MouseWithoutBorders/App/Class/Common.VK.cs
deleted file mode 100644
index 3f54a0281d..0000000000
--- a/src/modules/MouseWithoutBorders/App/Class/Common.VK.cs
+++ /dev/null
@@ -1,131 +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.
-
-//
-// Virtual key constants.
-//
-//
-// 2008 created by Truong Do (ductdo).
-// 2009-... modified by Truong Do (TruongDo).
-// 2023- Included in PowerToys.
-//
-using System;
-
-namespace MouseWithoutBorders
-{
- internal enum VK : ushort
- {
- CAPITAL = 0x14,
- NUMLOCK = 0x90,
- SHIFT = 0x10,
- CONTROL = 0x11,
- MENU = 0x12,
- ESCAPE = 0x1B,
- BACK = 0x08,
- TAB = 0x09,
- RETURN = 0x0D,
- PRIOR = 0x21,
- NEXT = 0x22,
- END = 0x23,
- HOME = 0x24,
- LEFT = 0x25,
- UP = 0x26,
- RIGHT = 0x27,
- DOWN = 0x28,
- SELECT = 0x29,
- PRINT = 0x2A,
- EXECUTE = 0x2B,
- SNAPSHOT = 0x2C,
- INSERT = 0x2D,
- DELETE = 0x2E,
- HELP = 0x2F,
- NUMPAD0 = 0x60,
- NUMPAD1 = 0x61,
- NUMPAD2 = 0x62,
- NUMPAD3 = 0x63,
- NUMPAD4 = 0x64,
- NUMPAD5 = 0x65,
- NUMPAD6 = 0x66,
- NUMPAD7 = 0x67,
- NUMPAD8 = 0x68,
- NUMPAD9 = 0x69,
- MULTIPLY = 0x6A,
- ADD = 0x6B,
- SEPARATOR = 0x6C,
- SUBTRACT = 0x6D,
- DECIMAL = 0x6E,
- DIVIDE = 0x6F,
- F1 = 0x70,
- F2 = 0x71,
- F3 = 0x72,
- F4 = 0x73,
- F5 = 0x74,
- F6 = 0x75,
- F7 = 0x76,
- F8 = 0x77,
- F9 = 0x78,
- F10 = 0x79,
- F11 = 0x7A,
- F12 = 0x7B,
- OEM_1 = 0xBA,
- OEM_PLUS = 0xBB,
- OEM_COMMA = 0xBC,
- OEM_MINUS = 0xBD,
- OEM_PERIOD = 0xBE,
- OEM_2 = 0xBF,
- OEM_3 = 0xC0,
- MEDIA_NEXT_TRACK = 0xB0,
- MEDIA_PREV_TRACK = 0xB1,
- MEDIA_STOP = 0xB2,
- MEDIA_PLAY_PAUSE = 0xB3,
- LWIN = 0x5B,
- RWIN = 0x5C,
- LSHIFT = 0xA0,
- RSHIFT = 0xA1,
- LCONTROL = 0xA2,
- RCONTROL = 0xA3,
- LMENU = 0xA4,
- RMENU = 0xA5,
- }
-
- internal partial class Common
- {
- internal const ushort KEYEVENTF_KEYDOWN = 0x0001;
- internal const ushort KEYEVENTF_KEYUP = 0x0002;
-
- internal const int WH_MOUSE = 7;
- internal const int WH_KEYBOARD = 2;
- internal const int WH_MOUSE_LL = 14;
- internal const int WH_KEYBOARD_LL = 13;
-
- internal const int WM_MOUSEMOVE = 0x200;
- internal const int WM_LBUTTONDOWN = 0x201;
- internal const int WM_RBUTTONDOWN = 0x204;
- internal const int WM_MBUTTONDOWN = 0x207;
- internal const int WM_XBUTTONDOWN = 0x20B;
- internal const int WM_LBUTTONUP = 0x202;
- internal const int WM_RBUTTONUP = 0x205;
- internal const int WM_MBUTTONUP = 0x208;
- internal const int WM_XBUTTONUP = 0x20C;
- internal const int WM_LBUTTONDBLCLK = 0x203;
- internal const int WM_RBUTTONDBLCLK = 0x206;
- internal const int WM_MBUTTONDBLCLK = 0x209;
- internal const int WM_MOUSEWHEEL = 0x020A;
- internal const int WM_MOUSEHWHEEL = 0x020E;
-
- internal const int WM_KEYDOWN = 0x100;
- internal const int WM_KEYUP = 0x101;
- internal const int WM_SYSKEYDOWN = 0x104;
- internal const int WM_SYSKEYUP = 0x105;
-
- [Flags]
- internal enum LLKHF
- {
- EXTENDED = 0x01,
- INJECTED = 0x10,
- ALTDOWN = 0x20,
- UP = 0x80,
- }
- }
-}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs b/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs
deleted file mode 100644
index ee2d99398c..0000000000
--- a/src/modules/MouseWithoutBorders/App/Class/Common.WinAPI.cs
+++ /dev/null
@@ -1,363 +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 System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Drawing;
-using System.Globalization;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Windows.Forms;
-
-//
-// Screen/Desktop helper functions.
-//
-//
-// 2008 created by Truong Do (ductdo).
-// 2009-... modified by Truong Do (TruongDo).
-// 2023- Included in PowerToys.
-//
-using MouseWithoutBorders.Class;
-using MouseWithoutBorders.Core;
-
-using Thread = MouseWithoutBorders.Core.Thread;
-
-namespace MouseWithoutBorders
-{
- // Desktops, and GetScreenConfig routines
- internal partial class Common
- {
- private static MyRectangle newDesktopBounds;
- private static MyRectangle newPrimaryScreenBounds;
- private static string activeDesktop;
-
- internal static string ActiveDesktop => Common.activeDesktop;
-
- internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
- {
- GetScreenConfig();
- }
-
- internal static readonly List SensitivePoints = new();
-
- private static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeMethods.RECT lprcMonitor, IntPtr dwData)
- {
- // lprcMonitor is wrong!!! => using GetMonitorInfo(...)
- // Log(String.Format( CultureInfo.CurrentCulture,"MONITOR: l{0}, t{1}, r{2}, b{3}", lprcMonitor.Left, lprcMonitor.Top, lprcMonitor.Right, lprcMonitor.Bottom));
- NativeMethods.MonitorInfoEx mi = default;
- mi.cbSize = Marshal.SizeOf(mi);
- _ = NativeMethods.GetMonitorInfo(hMonitor, ref mi);
-
- try
- {
- // For logging only
- _ = NativeMethods.GetDpiForMonitor(hMonitor, 0, out uint dpiX, out uint dpiY);
- Logger.Log(string.Format(CultureInfo.CurrentCulture, "MONITOR: ({0}, {1}, {2}, {3}). DPI: ({4}, {5})", mi.rcMonitor.Left, mi.rcMonitor.Top, mi.rcMonitor.Right, mi.rcMonitor.Bottom, dpiX, dpiY));
- }
- catch (DllNotFoundException)
- {
- Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
- }
- catch (EntryPointNotFoundException)
- {
- Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
-
- if (mi.rcMonitor.Left == 0 && mi.rcMonitor.Top == 0 && mi.rcMonitor.Right != 0 && mi.rcMonitor.Bottom != 0)
- {
- // Primary screen
- _ = Interlocked.Exchange(ref screenWidth, mi.rcMonitor.Right - mi.rcMonitor.Left);
- _ = Interlocked.Exchange(ref screenHeight, mi.rcMonitor.Bottom - mi.rcMonitor.Top);
-
- newPrimaryScreenBounds.Left = mi.rcMonitor.Left;
- newPrimaryScreenBounds.Top = mi.rcMonitor.Top;
- newPrimaryScreenBounds.Right = mi.rcMonitor.Right;
- newPrimaryScreenBounds.Bottom = mi.rcMonitor.Bottom;
- }
- else
- {
- if (mi.rcMonitor.Left < newDesktopBounds.Left)
- {
- newDesktopBounds.Left = mi.rcMonitor.Left;
- }
-
- if (mi.rcMonitor.Top < newDesktopBounds.Top)
- {
- newDesktopBounds.Top = mi.rcMonitor.Top;
- }
-
- if (mi.rcMonitor.Right > newDesktopBounds.Right)
- {
- newDesktopBounds.Right = mi.rcMonitor.Right;
- }
-
- if (mi.rcMonitor.Bottom > newDesktopBounds.Bottom)
- {
- newDesktopBounds.Bottom = mi.rcMonitor.Bottom;
- }
- }
-
- lock (SensitivePoints)
- {
- SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Top));
- SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Top));
- SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Bottom));
- SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Bottom));
- }
-
- return true;
- }
-
- internal static void GetScreenConfig()
- {
- try
- {
- Logger.LogDebug("==================== GetScreenConfig started");
- newDesktopBounds = new MyRectangle();
- newPrimaryScreenBounds = new MyRectangle();
- newDesktopBounds.Left = newPrimaryScreenBounds.Left = Screen.PrimaryScreen.Bounds.Left;
- newDesktopBounds.Top = newPrimaryScreenBounds.Top = Screen.PrimaryScreen.Bounds.Top;
- newDesktopBounds.Right = newPrimaryScreenBounds.Right = Screen.PrimaryScreen.Bounds.Right;
- newDesktopBounds.Bottom = newPrimaryScreenBounds.Bottom = Screen.PrimaryScreen.Bounds.Bottom;
-
- Logger.Log(string.Format(
- CultureInfo.CurrentCulture,
- "logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
- Common.RunOnLogonDesktop,
- Common.newPrimaryScreenBounds.Left,
- Common.newPrimaryScreenBounds.Top,
- Common.newPrimaryScreenBounds.Right,
- Common.newPrimaryScreenBounds.Bottom,
- Common.newDesktopBounds.Left,
- Common.newDesktopBounds.Top,
- Common.newDesktopBounds.Right,
- Common.newDesktopBounds.Bottom));
-
-#if USE_MANAGED_ROUTINES
- // Managed routines do not work well when running on secure desktop:(
- screenWidth = Screen.PrimaryScreen.Bounds.Width;
- screenHeight = Screen.PrimaryScreen.Bounds.Height;
- screenCount = Screen.AllScreens.Length;
- for (int i = 0; i < Screen.AllScreens.Length; i++)
- {
- if (Screen.AllScreens[i].Bounds.Left < desktopBounds.Left) desktopBounds.Left = Screen.AllScreens[i].Bounds.Left;
- if (Screen.AllScreens[i].Bounds.Top < desktopBounds.Top) desktopBounds.Top = Screen.AllScreens[i].Bounds.Top;
- if (Screen.AllScreens[i].Bounds.Right > desktopBounds.Right) desktopBounds.Right = Screen.AllScreens[i].Bounds.Right;
- if (Screen.AllScreens[i].Bounds.Bottom > desktopBounds.Bottom) desktopBounds.Bottom = Screen.AllScreens[i].Bounds.Bottom;
- }
-#else
- lock (SensitivePoints)
- {
- SensitivePoints.Clear();
- }
-
- NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
-
- // 1000 calls to EnumDisplayMonitors cost a dozen of milliseconds
-#endif
- Interlocked.Exchange(ref MachineStuff.desktopBounds, newDesktopBounds);
- Interlocked.Exchange(ref MachineStuff.primaryScreenBounds, newPrimaryScreenBounds);
-
- Logger.Log(string.Format(
- CultureInfo.CurrentCulture,
- "logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
- Common.RunOnLogonDesktop,
- MachineStuff.PrimaryScreenBounds.Left,
- MachineStuff.PrimaryScreenBounds.Top,
- MachineStuff.PrimaryScreenBounds.Right,
- MachineStuff.PrimaryScreenBounds.Bottom,
- MachineStuff.DesktopBounds.Left,
- MachineStuff.DesktopBounds.Top,
- MachineStuff.DesktopBounds.Right,
- MachineStuff.DesktopBounds.Bottom));
-
- Logger.Log("==================== GetScreenConfig ended");
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
- }
-
-#if USING_SCREEN_SAVER_ROUTINES
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- private static extern int PostMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- private static extern IntPtr OpenDesktop(string hDesktop, int Flags, bool Inherit, UInt32 DesiredAccess);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- private static extern bool CloseDesktop(IntPtr hDesktop);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- private static extern bool EnumDesktopWindows( IntPtr hDesktop, EnumDesktopWindowsProc callback, IntPtr lParam);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- private static extern bool IsWindowVisible(IntPtr hWnd);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- private static extern bool SystemParametersInfo(int uAction, int uParam, ref int pvParam, int flags);
-
- private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam);
- private const int WM_CLOSE = 16;
- private const int SPI_GETSCREENSAVERRUNNING = 114;
-
- internal static bool IsScreenSaverRunning()
- {
- int isRunning = 0;
- SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0,ref isRunning, 0);
- return (isRunning != 0);
- }
-
- internal static void CloseScreenSaver()
- {
- IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
- if (hDesktop != IntPtr.Zero)
- {
- LogDebug("Closing screen saver...");
- EnumDesktopWindows(hDesktop, new EnumDesktopWindowsProc(CloseScreenSaverFunc), IntPtr.Zero);
- CloseDesktop(hDesktop);
- }
- }
-
- private static bool CloseScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
- {
- if (IsWindowVisible(hWnd))
- {
- LogDebug("Posting WM_CLOSE to " + hWnd.ToString(CultureInfo.InvariantCulture));
- PostMessage(hWnd, WM_CLOSE, 0, 0);
- }
- return true;
- }
-#endif
-
- internal static string GetMyDesktop()
- {
- byte[] arThreadDesktop = new byte[256];
- IntPtr hD = NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId());
- if (hD != IntPtr.Zero)
- {
- _ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arThreadDesktop, arThreadDesktop.Length, out _);
- return GetString(arThreadDesktop).Replace("\0", string.Empty);
- }
-
- return string.Empty;
- }
-
- internal static string GetInputDesktop()
- {
- byte[] arInputDesktop = new byte[256];
- IntPtr hD = NativeMethods.OpenInputDesktop(0, false, NativeMethods.DESKTOP_READOBJECTS);
- if (hD != IntPtr.Zero)
- {
- _ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arInputDesktop, arInputDesktop.Length, out _);
- return GetString(arInputDesktop).Replace("\0", string.Empty);
- }
-
- return string.Empty;
- }
-
- internal static void StartMMService(string desktopToRunMouseWithoutBordersOn)
- {
- if (!Common.RunWithNoAdminRight)
- {
- Logger.LogDebug("*** Starting on active Desktop: " + desktopToRunMouseWithoutBordersOn);
- Service.StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn);
- }
- }
-
- internal static void CheckForDesktopSwitchEvent(bool cleanupIfExit)
- {
- try
- {
- if (!IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
- {
- Helper.RunDDHelper(true);
- int waitCount = 20;
-
- while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0)
- {
- waitCount--;
- Logger.LogDebug("The session is detached/attached.");
- Thread.Sleep(500);
- }
-
- string myDesktop = GetMyDesktop();
- activeDesktop = GetInputDesktop();
-
- Logger.LogDebug("*** Active Desktop = " + activeDesktop);
- Logger.LogDebug("*** My Desktop = " + myDesktop);
-
- if (myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
- {
- Logger.LogDebug("*** Active Desktop == My Desktop (TS session)");
- }
-
- if (!activeDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
- !activeDesktop.Equals("default", StringComparison.OrdinalIgnoreCase) &&
- !activeDesktop.Equals("disconnect", StringComparison.OrdinalIgnoreCase))
- {
- try
- {
- StartMMService(activeDesktop);
- }
- catch (Exception e)
- {
- Logger.Log($"{nameof(CheckForDesktopSwitchEvent)}: {e}");
- }
- }
- else
- {
- if (!myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
- {
- Logger.Log("*** Active Desktop <> My Desktop");
- }
-
- uint sid = NativeMethods.WTSGetActiveConsoleSessionId();
-
- if (Process.GetProcessesByName(Common.BinaryName).Any(p => (uint)p.SessionId == sid))
- {
- Logger.Log("Found MouseWithoutBorders on the active session!");
- }
- else
- {
- Logger.Log("MouseWithoutBorders not found on the active session!");
- StartMMService(null);
- }
- }
-
- if (!myDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
- !myDesktop.Equals("default", StringComparison.OrdinalIgnoreCase))
- {
- Logger.LogDebug("*** Desktop inactive, exiting: " + myDesktop);
- Setting.Values.LastX = JUST_GOT_BACK_FROM_SCREEN_SAVER;
- if (cleanupIfExit)
- {
- InitAndCleanup.Cleanup();
- }
-
- Process.GetCurrentProcess().KillProcess();
- }
- }
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
- }
-
- private static Point p;
-
- internal static bool IsMyDesktopActive()
- {
- return NativeMethods.GetCursorPos(ref p);
- }
- }
-}
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.cs b/src/modules/MouseWithoutBorders/App/Class/Common.cs
index ba5a1655e0..9a34500b52 100644
--- a/src/modules/MouseWithoutBorders/App/Class/Common.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/Common.cs
@@ -315,7 +315,7 @@ namespace MouseWithoutBorders
if (!acquireMutex)
{
Process[] ps = Process.GetProcessesByName(Common.BinaryName);
- Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {GetMyDesktop()}/{GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning);
+ Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {WinAPI.IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {WinAPI.GetMyDesktop()}/{WinAPI.GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning);
}
Logger.LogDebug("SOCKET MUTEX ENDED.");
@@ -358,7 +358,7 @@ namespace MouseWithoutBorders
Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}, restarting the process.", SeverityLevel.Warning, true);
- string desktop = Common.GetMyDesktop();
+ string desktop = WinAPI.GetMyDesktop();
MachineStuff.oneInstanceCheck?.Close();
_ = Process.Start(Application.ExecutablePath, desktop);
Logger.LogDebug($"Started on desktop {desktop}");
@@ -514,7 +514,7 @@ namespace MouseWithoutBorders
internal static void SendHeartBeat(bool initial = false)
{
- SendPackage(ID.ALL, initial && Common.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat);
+ SendPackage(ID.ALL, initial && Encryption.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat);
}
private static long lastSendNextMachine;
@@ -550,7 +550,7 @@ namespace MouseWithoutBorders
internal static void SendAwakeBeat()
{
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() &&
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() &&
Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount)
{
SendPackage(ID.ALL, PackageType.Awake);
@@ -568,7 +568,7 @@ namespace MouseWithoutBorders
{
if (lastInputEventCount == Event.InputEventCount)
{
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive())
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive())
{
PokeMyself();
}
@@ -577,13 +577,13 @@ namespace MouseWithoutBorders
lastInputEventCount = Event.InputEventCount;
}
- private static void PokeMyself()
+ internal static void PokeMyself()
{
int x, y = 0;
for (int i = 0; i < 10; i++)
{
- x = Ran.Next(-9, 10);
+ x = Encryption.Ran.Next(-9, 10);
InputSimulation.MoveMouseRelative(x, y);
Thread.Sleep(50);
InputSimulation.MoveMouseRelative(-x, -y);
@@ -677,7 +677,7 @@ namespace MouseWithoutBorders
{
Common.MMSleep(0.2);
InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT });
- InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)Common.LLKHF.UP, wVk = (int)VK.SNAPSHOT });
+ InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)WM.LLKHF.UP, wVk = (int)VK.SNAPSHOT });
Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated.");
@@ -710,7 +710,7 @@ namespace MouseWithoutBorders
"\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
"\"",
"\"" + file + "\"",
- GetInputDesktop(),
+ WinAPI.GetInputDesktop(),
1);
// CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
@@ -919,7 +919,7 @@ namespace MouseWithoutBorders
try
{
- data.Id = Interlocked.Increment(ref PackageID);
+ data.Id = Interlocked.Increment(ref Package.PackageID);
bool updateClientSockets = false;
@@ -999,7 +999,7 @@ namespace MouseWithoutBorders
}
else
{
- PackageSent.Nil++;
+ Package.PackageSent.Nil++;
}
}
@@ -1379,7 +1379,7 @@ namespace MouseWithoutBorders
if (string.IsNullOrEmpty(machine_Name))
{
- machine_Name = "RANDOM" + Ran.Next().ToString(CultureInfo.CurrentCulture);
+ machine_Name = "RANDOM" + Encryption.Ran.Next().ToString(CultureInfo.CurrentCulture);
}
}
@@ -1533,13 +1533,13 @@ namespace MouseWithoutBorders
internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true)
{
- byte[] ranData = new byte[SymAlBlockSize];
+ byte[] ranData = new byte[Encryption.SymAlBlockSize];
try
{
if (send)
{
- ranData = RandomNumberGenerator.GetBytes(SymAlBlockSize);
+ ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize);
st.Write(ranData, 0, ranData.Length);
}
else
diff --git a/src/modules/MouseWithoutBorders/App/Class/InputHook.cs b/src/modules/MouseWithoutBorders/App/Class/InputHook.cs
index d68b1a1584..53e815c1a0 100644
--- a/src/modules/MouseWithoutBorders/App/Class/InputHook.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/InputHook.cs
@@ -109,7 +109,7 @@ namespace MouseWithoutBorders.Class
// Install Mouse Hook
mouseHookProcedure = new NativeMethods.HookProc(MouseHookProc);
hMouseHook = NativeMethods.SetWindowsHookEx(
- Common.WH_MOUSE_LL,
+ WM.WH_MOUSE_LL,
mouseHookProcedure,
Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[0]),
@@ -126,7 +126,7 @@ namespace MouseWithoutBorders.Class
// Install Keyboard Hook
keyboardHookProcedure = new NativeMethods.HookProc(KeyboardHookProc);
hKeyboardHook = NativeMethods.SetWindowsHookEx(
- Common.WH_KEYBOARD_LL,
+ WM.WH_KEYBOARD_LL,
keyboardHookProcedure,
Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[0]),
@@ -233,7 +233,7 @@ namespace MouseWithoutBorders.Class
if (nCode >= 0 && MouseEvent != null)
{
- if (wParam == Common.WM_LBUTTONUP && SkipMouseUpCount > 0)
+ if (wParam == WM.WM_LBUTTONUP && SkipMouseUpCount > 0)
{
Logger.LogDebug($"{nameof(SkipMouseUpCount)}: {SkipMouseUpCount}.");
SkipMouseUpCount--;
@@ -241,7 +241,7 @@ namespace MouseWithoutBorders.Class
return rv;
}
- if ((wParam == Common.WM_LBUTTONUP || wParam == Common.WM_LBUTTONDOWN) && SkipMouseUpDown)
+ if ((wParam == WM.WM_LBUTTONUP || wParam == WM.WM_LBUTTONDOWN) && SkipMouseUpDown)
{
rv = NativeMethods.CallNextHookEx(hMouseHook, nCode, wParam, lParam);
return rv;
@@ -370,7 +370,7 @@ namespace MouseWithoutBorders.Class
private bool ProcessKeyEx(int vkCode, int flags, KEYBDDATA hookCallbackKeybdData)
{
- if ((flags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
+ if ((flags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
{
EasyMouseKeyDown = false;
@@ -553,7 +553,7 @@ namespace MouseWithoutBorders.Class
KeyboardEvent(hookCallbackKeybdData);
}
- hookCallbackKeybdData.dwFlags |= (int)Common.LLKHF.UP;
+ hookCallbackKeybdData.dwFlags |= (int)WM.LLKHF.UP;
foreach (var code in codes)
{
diff --git a/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs b/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs
index e735db814c..156f69597d 100644
--- a/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/InputSimulation.cs
@@ -112,12 +112,12 @@ namespace MouseWithoutBorders.Class
uint scanCode = 0;
// http://msdn.microsoft.com/en-us/library/ms644967(VS.85).aspx
- if ((kd.dwFlags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
+ if ((kd.dwFlags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
{
dwFlags = NativeMethods.KEYEVENTF.KEYUP;
}
- if ((kd.dwFlags & (int)Common.LLKHF.EXTENDED) == (int)Common.LLKHF.EXTENDED)
+ if ((kd.dwFlags & (int)WM.LLKHF.EXTENDED) == (int)WM.LLKHF.EXTENDED)
{
dwFlags |= NativeMethods.KEYEVENTF.EXTENDEDKEY;
}
@@ -173,44 +173,44 @@ namespace MouseWithoutBorders.Class
mouse_input.mi.dy = (int)dy;
mouse_input.mi.mouseData = md.WheelDelta;
- if (md.dwFlags != Common.WM_MOUSEMOVE)
+ if (md.dwFlags != WM.WM_MOUSEMOVE)
{
Logger.LogDebug($"InputSimulation.SendMouse: x = {md.X}, y = {md.Y}, WheelDelta = {md.WheelDelta}, dwFlags = {md.dwFlags}.");
}
switch (md.dwFlags)
{
- case Common.WM_MOUSEMOVE:
+ case WM.WM_MOUSEMOVE:
mouse_input.mi.dwFlags |= (int)(NativeMethods.MOUSEEVENTF.MOVE | NativeMethods.MOUSEEVENTF.ABSOLUTE);
break;
- case Common.WM_LBUTTONDOWN:
+ case WM.WM_LBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTDOWN;
break;
- case Common.WM_LBUTTONUP:
+ case WM.WM_LBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.LEFTUP;
break;
- case Common.WM_RBUTTONDOWN:
+ case WM.WM_RBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTDOWN;
break;
- case Common.WM_RBUTTONUP:
+ case WM.WM_RBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.RIGHTUP;
break;
- case Common.WM_MBUTTONDOWN:
+ case WM.WM_MBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEDOWN;
break;
- case Common.WM_MBUTTONUP:
+ case WM.WM_MBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.MIDDLEUP;
break;
- case Common.WM_MOUSEWHEEL:
+ case WM.WM_MOUSEWHEEL:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.WHEEL;
break;
- case Common.WM_MOUSEHWHEEL:
+ case WM.WM_MOUSEHWHEEL:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.HWHEEL;
break;
- case Common.WM_XBUTTONUP:
+ case WM.WM_XBUTTONUP:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XUP;
break;
- case Common.WM_XBUTTONDOWN:
+ case WM.WM_XBUTTONDOWN:
mouse_input.mi.dwFlags |= (int)NativeMethods.MOUSEEVENTF.XDOWN;
break;
@@ -373,7 +373,7 @@ namespace MouseWithoutBorders.Class
{
eatKey = false;
- if ((flags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
+ if ((flags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
{
switch ((VK)vkCode)
{
diff --git a/src/modules/MouseWithoutBorders/App/Class/Program.cs b/src/modules/MouseWithoutBorders/App/Class/Program.cs
index c139da46e9..23513e1515 100644
--- a/src/modules/MouseWithoutBorders/App/Class/Program.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/Program.cs
@@ -143,7 +143,7 @@ namespace MouseWithoutBorders.Class
return;
}
- string myDesktop = Common.GetMyDesktop();
+ string myDesktop = WinAPI.GetMyDesktop();
if (firstArg.Equals("winlogon", StringComparison.OrdinalIgnoreCase))
{
@@ -305,8 +305,8 @@ namespace MouseWithoutBorders.Class
MachineStuff.ClearComputerMatrix();
Setting.Values.MyKey = securityKey;
- Common.MyKey = securityKey;
- Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
+ Encryption.MyKey = securityKey;
+ Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { pcName.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty };
string[] machines = MachineStuff.MachineMatrix;
@@ -328,8 +328,8 @@ namespace MouseWithoutBorders.Class
Setting.Values.EasyMouse = (int)EasyMouseOption.Enable;
MachineStuff.ClearComputerMatrix();
- Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
- Common.GeneratedKey = true;
+ Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
+ Encryption.GeneratedKey = true;
Setting.Values.PauseInstantSaving = false;
Setting.Values.SaveSettings();
diff --git a/src/modules/MouseWithoutBorders/App/Class/Setting.cs b/src/modules/MouseWithoutBorders/App/Class/Setting.cs
index 623571f6ce..c526c70976 100644
--- a/src/modules/MouseWithoutBorders/App/Class/Setting.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/Setting.cs
@@ -109,9 +109,9 @@ namespace MouseWithoutBorders.Class
var shouldReopenSockets = false;
- if (Common.MyKey != _properties.SecurityKey.Value)
+ if (Encryption.MyKey != _properties.SecurityKey.Value)
{
- Common.MyKey = _properties.SecurityKey.Value;
+ Encryption.MyKey = _properties.SecurityKey.Value;
shouldReopenSockets = true;
}
@@ -489,7 +489,7 @@ namespace MouseWithoutBorders.Class
}
else
{
- string randomKey = Common.CreateDefaultKey();
+ string randomKey = Encryption.CreateDefaultKey();
_properties.SecurityKey.Value = randomKey;
return randomKey;
@@ -1055,7 +1055,7 @@ namespace MouseWithoutBorders.Class
if (machineId == 0)
{
- var newMachineId = Common.Ran.Next();
+ var newMachineId = Encryption.Ran.Next();
_properties.MachineID.Value = newMachineId;
machineId = newMachineId;
if (!PauseInstantSaving)
diff --git a/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs b/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
index c5241beddf..575c9582df 100644
--- a/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/SocketStuff.cs
@@ -101,7 +101,7 @@ namespace MouseWithoutBorders.Class
{
if (encryptedStream == null && BackingSocket.Connected)
{
- encryptedStream = Common.GetEncryptedStream(new NetworkStream(BackingSocket));
+ encryptedStream = Encryption.GetEncryptedStream(new NetworkStream(BackingSocket));
Common.SendOrReceiveARandomDataBlockPerInitialIV(encryptedStream);
}
@@ -115,7 +115,7 @@ namespace MouseWithoutBorders.Class
{
if (decryptedStream == null && BackingSocket.Connected)
{
- decryptedStream = Common.GetDecryptedStream(new NetworkStream(BackingSocket));
+ decryptedStream = Encryption.GetDecryptedStream(new NetworkStream(BackingSocket));
Common.SendOrReceiveARandomDataBlockPerInitialIV(decryptedStream, false);
}
@@ -181,7 +181,7 @@ namespace MouseWithoutBorders.Class
Logger.LogDebug("SocketStuff started.");
bASE_PORT = port;
- Common.Ran = new Random();
+ Encryption.Ran = new Random();
Logger.LogDebug("Validating session...");
@@ -221,11 +221,11 @@ namespace MouseWithoutBorders.Class
if (Setting.Values.IsMyKeyRandom)
{
- Setting.Values.MyKey = Common.MyKey;
+ Setting.Values.MyKey = Encryption.MyKey;
}
- Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
- Common.PackageID = Setting.Values.PackageID;
+ Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
+ Package.PackageID = Setting.Values.PackageID;
TcpPort = bASE_PORT;
@@ -242,7 +242,7 @@ namespace MouseWithoutBorders.Class
Logger.TelemetryLogTrace($"{nameof(SocketStuff)}: {e.Message}", SeverityLevel.Warning);
}
- Common.GetScreenConfig();
+ WinAPI.GetScreenConfig();
if (firstRun && Common.RunOnScrSaverDesktop)
{
@@ -305,7 +305,7 @@ namespace MouseWithoutBorders.Class
sleepSecs = 10;
// It is reasonable to give a try on restarting MwB processes in other sessions.
- if (restartCount++ < 5 && Common.IsMyDesktopActive() && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
+ if (restartCount++ < 5 && WinAPI.IsMyDesktopActive() && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{
Logger.TelemetryLogTrace("Restarting the service dues to WSAEADDRINUSE.", SeverityLevel.Warning);
Program.StartService();
@@ -361,7 +361,7 @@ namespace MouseWithoutBorders.Class
{
Setting.Values.LastX = Common.LastX;
Setting.Values.LastY = Common.LastY;
- Setting.Values.PackageID = Common.PackageID;
+ Setting.Values.PackageID = Package.PackageID;
// Common.Log("Saving IP: " + Setting.Values.DesMachineID.ToString(CultureInfo.CurrentCulture));
Setting.Values.DesMachineID = (uint)Common.DesMachineID;
@@ -505,10 +505,10 @@ namespace MouseWithoutBorders.Class
throw new ExpectedSocketException(log);
}
- bytes[3] = (byte)((Common.MagicNumber >> 24) & 0xFF);
- bytes[2] = (byte)((Common.MagicNumber >> 16) & 0xFF);
+ bytes[3] = (byte)((Encryption.MagicNumber >> 24) & 0xFF);
+ bytes[2] = (byte)((Encryption.MagicNumber >> 16) & 0xFF);
bytes[1] = 0;
- for (int i = 2; i < Common.PACKAGE_SIZE; i++)
+ for (int i = 2; i < Package.PACKAGE_SIZE; i++)
{
bytes[1] = (byte)(bytes[1] + bytes[i]);
}
@@ -535,13 +535,13 @@ namespace MouseWithoutBorders.Class
magic = (buf[3] << 24) + (buf[2] << 16);
- if (magic != (Common.MagicNumber & 0xFFFF0000))
+ if (magic != (Encryption.MagicNumber & 0xFFFF0000))
{
Logger.Log("Magic number invalid!");
buf[0] = (byte)PackageType.Invalid;
}
- for (int i = 2; i < Common.PACKAGE_SIZE; i++)
+ for (int i = 2; i < Package.PACKAGE_SIZE; i++)
{
checksum = (byte)(checksum + buf[i]);
}
@@ -557,7 +557,7 @@ namespace MouseWithoutBorders.Class
internal static DATA TcpReceiveData(TcpSk tcp, out int bytesReceived)
{
- byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
+ byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
Stream decryptedStream = tcp.DecryptedStream;
if (tcp.BackingSocket == null || !tcp.BackingSocket.Connected || decryptedStream == null)
@@ -571,9 +571,9 @@ namespace MouseWithoutBorders.Class
try
{
- bytesReceived = decryptedStream.ReadEx(buf, 0, Common.PACKAGE_SIZE);
+ bytesReceived = decryptedStream.ReadEx(buf, 0, Package.PACKAGE_SIZE);
- if (bytesReceived != Common.PACKAGE_SIZE)
+ if (bytesReceived != Package.PACKAGE_SIZE)
{
buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
}
@@ -586,9 +586,9 @@ namespace MouseWithoutBorders.Class
if (package.IsBigPackage)
{
- bytesReceived = decryptedStream.ReadEx(buf, Common.PACKAGE_SIZE, Common.PACKAGE_SIZE);
+ bytesReceived = decryptedStream.ReadEx(buf, Package.PACKAGE_SIZE, Package.PACKAGE_SIZE);
- if (bytesReceived != Common.PACKAGE_SIZE)
+ if (bytesReceived != Package.PACKAGE_SIZE)
{
buf[0] = bytesReceived == 0 ? (byte)PackageType.Error : (byte)PackageType.Invalid;
}
@@ -614,28 +614,28 @@ namespace MouseWithoutBorders.Class
switch (type)
{
case PackageType.Keyboard:
- Common.PackageSent.Keyboard++;
+ Package.PackageSent.Keyboard++;
break;
case PackageType.Mouse:
- Common.PackageSent.Mouse++;
+ Package.PackageSent.Mouse++;
break;
case PackageType.Heartbeat:
case PackageType.Heartbeat_ex:
- Common.PackageSent.Heartbeat++;
+ Package.PackageSent.Heartbeat++;
break;
case PackageType.Hello:
- Common.PackageSent.Hello++;
+ Package.PackageSent.Hello++;
break;
case PackageType.ByeBye:
- Common.PackageSent.ByeBye++;
+ Package.PackageSent.ByeBye++;
break;
case PackageType.Matrix:
- Common.PackageSent.Matrix++;
+ Package.PackageSent.Matrix++;
break;
default:
@@ -643,11 +643,11 @@ namespace MouseWithoutBorders.Class
switch (subtype)
{
case (byte)PackageType.ClipboardText:
- Common.PackageSent.ClipboardText++;
+ Package.PackageSent.ClipboardText++;
break;
case (byte)PackageType.ClipboardImage:
- Common.PackageSent.ClipboardImage++;
+ Package.PackageSent.ClipboardImage++;
break;
default:
@@ -1266,7 +1266,7 @@ namespace MouseWithoutBorders.Class
string strIP = string.Empty;
ID remoteID = ID.NONE;
- byte[] buf = RandomNumberGenerator.GetBytes(Common.PACKAGE_SIZE_EX);
+ byte[] buf = RandomNumberGenerator.GetBytes(Package.PACKAGE_SIZE_EX);
d = new DATA(buf);
TcpSk currentTcp = tcp;
@@ -1280,8 +1280,8 @@ namespace MouseWithoutBorders.Class
try
{
- currentSocket.SendBufferSize = Common.PACKAGE_SIZE * 10000;
- currentSocket.ReceiveBufferSize = Common.PACKAGE_SIZE * 10000;
+ currentSocket.SendBufferSize = Package.PACKAGE_SIZE * 10000;
+ currentSocket.ReceiveBufferSize = Package.PACKAGE_SIZE * 10000;
currentSocket.NoDelay = true; // This is very interesting to know:(
currentSocket.SendTimeout = 500;
d.MachineName = Common.MachineName;
@@ -1829,7 +1829,7 @@ namespace MouseWithoutBorders.Class
}
while (rv > 0);
- if ((rv = Common.PACKAGE_SIZE - (sentCount % Common.PACKAGE_SIZE)) > 0)
+ if ((rv = Package.PACKAGE_SIZE - (sentCount % Package.PACKAGE_SIZE)) > 0)
{
Array.Clear(buf, 0, buf.Length);
ecStream.Write(buf, 0, rv);
@@ -1900,7 +1900,7 @@ namespace MouseWithoutBorders.Class
}
while (rv > 0);
- if ((rv = sentCount % Common.PACKAGE_SIZE) > 0)
+ if ((rv = sentCount % Package.PACKAGE_SIZE) > 0)
{
Array.Clear(buf, 0, buf.Length);
ecStream.Write(buf, 0, rv);
@@ -1984,7 +1984,7 @@ namespace MouseWithoutBorders.Class
if (tcp.MachineId == Setting.Values.MachineId)
{
tcp = null;
- Setting.Values.MachineId = Common.Ran.Next();
+ Setting.Values.MachineId = Encryption.Ran.Next();
InitAndCleanup.UpdateMachineTimeAndID();
InitAndCleanup.PleaseReopenSocket = InitAndCleanup.REOPEN_WHEN_HOTKEY;
diff --git a/src/modules/MouseWithoutBorders/App/Class/TcpServer.cs b/src/modules/MouseWithoutBorders/App/Class/TcpServer.cs
index f915ae94e0..cc98381483 100644
--- a/src/modules/MouseWithoutBorders/App/Class/TcpServer.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/TcpServer.cs
@@ -70,7 +70,7 @@ namespace MouseWithoutBorders.Class
continue;
}
- if (!Common.IsMyDesktopActive())
+ if (!WinAPI.IsMyDesktopActive())
{
// We can just throw the SocketException but to avoid a redundant log entry:
throw new ExpectedSocketException($"{nameof(StartServer)}: The desktop is no longer active.");
diff --git a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
index 5840325941..e557ff4a37 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
@@ -270,15 +270,15 @@ internal static class Clipboard
int index = 0;
int len;
DATA package = new();
- byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
- int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE;
+ byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
+ int dataStart = Package.PACKAGE_SIZE_EX - DATA_SIZE;
while (true)
{
if ((index + DATA_SIZE) > l)
{
len = l - index;
- Array.Clear(buf, 0, Common.PACKAGE_SIZE_EX);
+ Array.Clear(buf, 0, Package.PACKAGE_SIZE_EX);
}
else
{
@@ -315,7 +315,7 @@ internal static class Clipboard
}
MemoryStream m = new();
- int dataStart = Common.PACKAGE_SIZE_EX - DATA_SIZE;
+ int dataStart = Package.PACKAGE_SIZE_EX - DATA_SIZE;
m.Write(data.Bytes, dataStart, DATA_SIZE);
int unexpectedCount = 0;
@@ -809,27 +809,27 @@ internal static class Clipboard
MachineName = Common.MachineName,
};
- byte[] buf = new byte[Common.PACKAGE_SIZE_EX];
+ byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
NetworkStream ns = new(s);
- enStream = Common.GetEncryptedStream(ns);
+ enStream = Encryption.GetEncryptedStream(ns);
Common.SendOrReceiveARandomDataBlockPerInitialIV(enStream);
Logger.LogDebug($"{nameof(ShakeHand)}: Writing header package.");
- enStream.Write(package.Bytes, 0, Common.PACKAGE_SIZE_EX);
+ enStream.Write(package.Bytes, 0, Package.PACKAGE_SIZE_EX);
Logger.LogDebug($"{nameof(ShakeHand)}: Sent: clientPush={clientPushData}, postAction={postAction}.");
- deStream = Common.GetDecryptedStream(ns);
+ deStream = Encryption.GetDecryptedStream(ns);
Common.SendOrReceiveARandomDataBlockPerInitialIV(deStream, false);
Logger.LogDebug($"{nameof(ShakeHand)}: Reading header package.");
- int bytesReceived = deStream.ReadEx(buf, 0, Common.PACKAGE_SIZE_EX);
+ int bytesReceived = deStream.ReadEx(buf, 0, Package.PACKAGE_SIZE_EX);
package.Bytes = buf;
string name = "Unknown";
- if (bytesReceived == Common.PACKAGE_SIZE_EX)
+ if (bytesReceived == Package.PACKAGE_SIZE_EX)
{
if (package.Type is PackageType.Clipboard or PackageType.ClipboardPush)
{
diff --git a/src/modules/MouseWithoutBorders/App/Core/ClipboardPostAction.cs b/src/modules/MouseWithoutBorders/App/Core/ClipboardPostAction.cs
new file mode 100644
index 0000000000..c07a8bb91a
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/ClipboardPostAction.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal enum ClipboardPostAction : uint
+{
+ Other = 0,
+ Desktop = 1,
+ Mspaint = 2,
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/DATA.cs b/src/modules/MouseWithoutBorders/App/Core/DATA.cs
new file mode 100644
index 0000000000..4085483bd9
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/DATA.cs
@@ -0,0 +1,150 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+// In X64, we are WOW
+[module: SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", Scope = "type", Target = "MouseWithoutBorders.Core.DATA", Justification = "Dotnet port with style preservation")]
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+// The beauty of "union" in C#
+[StructLayout(LayoutKind.Explicit)]
+internal sealed class DATA
+{
+ [FieldOffset(0)]
+ internal PackageType Type; // 4 (first byte = package type, 1 = checksum, 2+3 = magic no.)
+
+ [FieldOffset(sizeof(PackageType))]
+ internal int Id; // 4
+
+ [FieldOffset(sizeof(PackageType) + sizeof(uint))]
+ internal ID Src; // 4
+
+ [FieldOffset(sizeof(PackageType) + (2 * sizeof(uint)))]
+ internal ID Des; // 4
+
+ [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
+ internal long DateTime;
+
+ [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)) + sizeof(long))]
+ internal KEYBDDATA Kd;
+
+ [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
+ internal MOUSEDATA Md;
+
+ [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
+ internal ID Machine1;
+
+ [FieldOffset(sizeof(PackageType) + (4 * sizeof(uint)))]
+ internal ID Machine2;
+
+ [FieldOffset(sizeof(PackageType) + (5 * sizeof(uint)))]
+ internal ID Machine3;
+
+ [FieldOffset(sizeof(PackageType) + (6 * sizeof(uint)))]
+ internal ID Machine4;
+
+ [FieldOffset(sizeof(PackageType) + (3 * sizeof(uint)))]
+ internal ClipboardPostAction PostAction;
+
+ [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)))]
+ private long machineNameP1;
+
+ [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + sizeof(long))]
+ private long machineNameP2;
+
+ [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (2 * sizeof(long)))]
+ private long machineNameP3;
+
+ [FieldOffset(sizeof(PackageType) + (7 * sizeof(uint)) + (3 * sizeof(long)))]
+ private long machineNameP4;
+
+ internal string MachineName
+ {
+ get
+ {
+ string name = Common.GetString(BitConverter.GetBytes(machineNameP1))
+ + Common.GetString(BitConverter.GetBytes(machineNameP2))
+ + Common.GetString(BitConverter.GetBytes(machineNameP3))
+ + Common.GetString(BitConverter.GetBytes(machineNameP4));
+ return name.Trim();
+ }
+
+ set
+ {
+ byte[] machineName = Common.GetBytes(value.PadRight(32, ' '));
+ machineNameP1 = BitConverter.ToInt64(machineName, 0);
+ machineNameP2 = BitConverter.ToInt64(machineName, 8);
+ machineNameP3 = BitConverter.ToInt64(machineName, 16);
+ machineNameP4 = BitConverter.ToInt64(machineName, 24);
+ }
+ }
+
+ public DATA()
+ {
+ }
+
+ public DATA(byte[] initialData)
+ {
+ Bytes = initialData;
+ }
+
+ internal byte[] Bytes
+ {
+ get
+ {
+ byte[] buf = new byte[IsBigPackage ? Package.PACKAGE_SIZE_EX : Package.PACKAGE_SIZE];
+ Array.Copy(StructToBytes(this), buf, IsBigPackage ? Package.PACKAGE_SIZE_EX : Package.PACKAGE_SIZE);
+
+ return buf;
+ }
+
+ set
+ {
+ Debug.Assert(value.Length <= Package.PACKAGE_SIZE_EX, "Length > package size");
+ byte[] buf = new byte[Package.PACKAGE_SIZE_EX];
+ Array.Copy(value, buf, value.Length);
+ BytesToStruct(buf, this);
+ }
+ }
+
+ internal bool IsBigPackage
+ {
+ get => Type == 0
+ ? throw new InvalidOperationException("Package type not set.")
+ : Type switch
+ {
+ PackageType.Hello or PackageType.Awake or PackageType.Heartbeat or PackageType.Heartbeat_ex or PackageType.Handshake or PackageType.HandshakeAck or PackageType.ClipboardPush or PackageType.Clipboard or PackageType.ClipboardAsk or PackageType.ClipboardImage or PackageType.ClipboardText or PackageType.ClipboardDataEnd => true,
+ _ => (Type & PackageType.Matrix) == PackageType.Matrix,
+ };
+ }
+
+ private byte[] StructToBytes(object structObject)
+ {
+ byte[] bytes = new byte[Package.PACKAGE_SIZE_EX];
+ GCHandle bHandle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
+ Marshal.StructureToPtr(structObject, Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0), false);
+ bHandle.Free();
+ return bytes;
+ }
+
+ private void BytesToStruct(byte[] value, object structObject)
+ {
+ GCHandle bHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
+ Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(value, 0), structObject);
+ bHandle.Free();
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs b/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs
index 2decb83261..d262e48f24 100644
--- a/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/DragDrop.cs
@@ -67,20 +67,20 @@ internal static class DragDrop
return;
}
- if (wParam == Common.WM_LBUTTONDOWN)
+ if (wParam == WM.WM_LBUTTONDOWN)
{
MouseDown = true;
DragMachine = MachineStuff.desMachineID;
MachineStuff.dropMachineID = ID.NONE;
Logger.LogDebug("DragDropStep01: MouseDown");
}
- else if (wParam == Common.WM_LBUTTONUP)
+ else if (wParam == WM.WM_LBUTTONUP)
{
MouseDown = false;
Logger.LogDebug("DragDropStep01: MouseUp");
}
- if (wParam == Common.WM_RBUTTONUP && IsDropping)
+ if (wParam == WM.WM_RBUTTONUP && IsDropping)
{
IsDropping = false;
Clipboard.LastIDWithClipboardData = ID.NONE;
@@ -252,7 +252,7 @@ internal static class DragDrop
internal static void DragDropStep09(int wParam)
{
- if (wParam == Common.WM_MOUSEMOVE && IsDropping)
+ if (wParam == WM.WM_MOUSEMOVE && IsDropping)
{
// Show/Move form
Common.DoSomethingInUIThread(() =>
@@ -260,7 +260,7 @@ internal static class DragDrop
_ = NativeMethods.PostMessage(Common.MainForm.Handle, NativeMethods.WM_SHOW_DRAG_DROP, (IntPtr)0, (IntPtr)0);
});
}
- else if (wParam == Common.WM_LBUTTONUP && (IsDropping || IsDragging))
+ else if (wParam == WM.WM_LBUTTONUP && (IsDropping || IsDragging))
{
if (IsDropping)
{
diff --git a/src/modules/MouseWithoutBorders/App/Core/Encryption.cs b/src/modules/MouseWithoutBorders/App/Core/Encryption.cs
new file mode 100644
index 0000000000..9d00b6bb40
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/Encryption.cs
@@ -0,0 +1,245 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Concurrent;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+
+//
+// Encrypt/decrypt implementation.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal static class Encryption
+{
+#pragma warning disable SYSLIB0021
+ private static AesCryptoServiceProvider symAl;
+#pragma warning restore SYSLIB0021
+#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
+ internal static string myKey;
+#pragma warning restore SA1307
+ private static uint magicNumber;
+ private static Random ran = new(); // Used for non encryption related functionality.
+ internal const int SymAlBlockSize = 16;
+
+ ///
+ /// This is used for the first encryption block, the following blocks will be combined with the cipher text of the previous block.
+ /// Thus identical blocks in the socket stream would be encrypted to different cipher text blocks.
+ /// The first block is a handshake one containing random data.
+ /// Related Unit Test: TestEncryptDecrypt
+ ///
+ private static readonly string InitialIV = ulong.MaxValue.ToString(CultureInfo.InvariantCulture);
+
+ internal static Random Ran
+ {
+ get => Encryption.ran ??= new Random();
+ set => Encryption.ran = value;
+ }
+
+ internal static uint MagicNumber
+ {
+ get => Encryption.magicNumber;
+ set => Encryption.magicNumber = value;
+ }
+
+ internal static string MyKey
+ {
+ get => Encryption.myKey;
+
+ set
+ {
+ if (Encryption.myKey != value)
+ {
+ Encryption.myKey = value;
+ _ = Task.Factory.StartNew(
+ () => Encryption.GenLegalKey(),
+ System.Threading.CancellationToken.None,
+ TaskCreationOptions.None,
+ TaskScheduler.Default); // Cache the key to improve UX.
+ }
+ }
+ }
+
+ private static string KeyDisplayedText(string key)
+ {
+ string displayedValue = string.Empty;
+ int i = 0;
+
+ do
+ {
+ int length = Math.Min(4, key.Length - i);
+ displayedValue += string.Concat(key.AsSpan(i, length), " ");
+ i += 4;
+ }
+ while (i < key.Length - 1);
+
+ return displayedValue.Trim();
+ }
+
+ internal static bool GeneratedKey { get; set; }
+
+ internal static bool KeyCorrupted { get; set; }
+
+ internal static void InitEncryption()
+ {
+ try
+ {
+ if (symAl == null)
+ {
+#pragma warning disable SYSLIB0021 // No proper replacement for now
+ symAl = new AesCryptoServiceProvider();
+#pragma warning restore SYSLIB0021
+ symAl.KeySize = 256;
+ symAl.BlockSize = SymAlBlockSize * 8;
+ symAl.Padding = PaddingMode.Zeros;
+ symAl.Mode = CipherMode.CBC;
+ symAl.GenerateIV();
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ }
+
+ private static readonly ConcurrentDictionary LegalKeyDictionary = new(StringComparer.OrdinalIgnoreCase);
+
+ private static byte[] GenLegalKey()
+ {
+ byte[] rv;
+ string myKey = Encryption.MyKey;
+
+ if (!LegalKeyDictionary.TryGetValue(myKey, out byte[] value))
+ {
+ Rfc2898DeriveBytes key = new(
+ myKey,
+ Common.GetBytesU(InitialIV),
+ 50000,
+ HashAlgorithmName.SHA512);
+ rv = key.GetBytes(32);
+ _ = LegalKeyDictionary.AddOrUpdate(myKey, rv, (k, v) => rv);
+ }
+ else
+ {
+ rv = value;
+ }
+
+ return rv;
+ }
+
+ private static byte[] GenLegalIV()
+ {
+ string st = InitialIV;
+ int ivLength = symAl.IV.Length;
+ if (st.Length > ivLength)
+ {
+ st = st[..ivLength];
+ }
+ else if (st.Length < ivLength)
+ {
+ st = st.PadRight(ivLength, ' ');
+ }
+
+ return Common.GetBytes(st);
+ }
+
+ internal static Stream GetEncryptedStream(Stream encryptedStream)
+ {
+ ICryptoTransform encryptor;
+ encryptor = symAl.CreateEncryptor(GenLegalKey(), GenLegalIV());
+ return new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write);
+ }
+
+ internal static Stream GetDecryptedStream(Stream encryptedStream)
+ {
+ ICryptoTransform decryptor;
+ decryptor = symAl.CreateDecryptor(GenLegalKey(), GenLegalIV());
+ return new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read);
+ }
+
+ internal static uint Get24BitHash(string st)
+ {
+ if (string.IsNullOrEmpty(st))
+ {
+ return 0;
+ }
+
+ byte[] bytes = new byte[Package.PACKAGE_SIZE];
+ for (int i = 0; i < Package.PACKAGE_SIZE; i++)
+ {
+ if (i < st.Length)
+ {
+ bytes[i] = (byte)st[i];
+ }
+ }
+
+ var hash = SHA512.Create();
+ byte[] hashValue = hash.ComputeHash(bytes);
+
+ for (int i = 0; i < 50000; i++)
+ {
+ hashValue = hash.ComputeHash(hashValue);
+ }
+
+ Logger.LogDebug(string.Format(CultureInfo.CurrentCulture, "magic: {0},{1},{2}", hashValue[0], hashValue[1], hashValue[^1]));
+ hash.Clear();
+ return (uint)((hashValue[0] << 23) + (hashValue[1] << 16) + (hashValue[^1] << 8) + hashValue[2]);
+ }
+
+ internal static string GetDebugInfo(string st)
+ {
+ return string.IsNullOrEmpty(st) ? st : ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
+ }
+
+ internal static string CreateDefaultKey()
+ {
+ return CreateRandomKey();
+ }
+
+ private const int PW_LENGTH = 16;
+
+ internal static string CreateRandomKey()
+ {
+ // Not including characters like "'`O0& since they are confusing to users.
+ string[] chars = new[] { "abcdefghjkmnpqrstuvxyz", "ABCDEFGHJKMNPQRSTUVXYZ", "123456789", "~!@#$%^*()_-+=:;<,>.?/\\|[]" };
+ char[][] charactersUsedForKey = chars.Select(charset => Enumerable.Range(0, charset.Length - 1).Select(i => charset[i]).ToArray()).ToArray();
+ byte[] randomData = new byte[1];
+ string key = string.Empty;
+
+ do
+ {
+ foreach (string set in chars)
+ {
+ randomData = RandomNumberGenerator.GetBytes(1);
+ key += set[randomData[0] % set.Length];
+
+ if (key.Length >= PW_LENGTH)
+ {
+ break;
+ }
+ }
+ }
+ while (key.Length < PW_LENGTH);
+
+ return key;
+ }
+
+ internal static bool IsKeyValid(string key, out string error)
+ {
+ error = string.IsNullOrEmpty(key) || key.Length < 16
+ ? "Key must have at least 16 characters in length (spaces are discarded). Key must be auto generated in one of the machines."
+ : null;
+
+ return error == null;
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Event.cs b/src/modules/MouseWithoutBorders/App/Core/Event.cs
index 1e6ee3e371..659a15526e 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Event.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Event.cs
@@ -70,7 +70,7 @@ internal static class Event
// Check if easy mouse setting is enabled.
bool isEasyMouseEnabled = IsSwitchingByMouseEnabled();
- if (isEasyMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == Common.WM_MOUSEMOVE)
+ if (isEasyMouseEnabled && Common.Sk != null && (Common.DesMachineID == Common.MachineID || !Setting.Values.MoveMouseRelatively) && e.dwFlags == WM.WM_MOUSEMOVE)
{
Point p = MachineStuff.MoveToMyNeighbourIfNeeded(e.X, e.Y, MachineStuff.desMachineID);
@@ -115,7 +115,7 @@ internal static class Event
Common.SkSend(MousePackage, null, false);
- if (MousePackage.Md.dwFlags is Common.WM_LBUTTONUP or Common.WM_RBUTTONUP)
+ if (MousePackage.Md.dwFlags is WM.WM_LBUTTONUP or WM.WM_RBUTTONUP)
{
Thread.Sleep(10);
}
@@ -265,7 +265,7 @@ internal static class Event
KeybdPackage.Kd = e;
KeybdPackage.DateTime = Common.GetTick();
Common.SkSend(KeybdPackage, null, false);
- if (KeybdPackage.Kd.dwFlags is Common.WM_KEYUP or Common.WM_SYSKEYUP)
+ if (KeybdPackage.Kd.dwFlags is WM.WM_KEYUP or WM.WM_SYSKEYUP)
{
Thread.Sleep(10);
}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Helper.cs b/src/modules/MouseWithoutBorders/App/Core/Helper.cs
index bd66c9a83f..8c291fb417 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Helper.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Helper.cs
@@ -290,7 +290,7 @@ internal static class Helper
return;
}
- if (!Common.IsMyDesktopActive())
+ if (!WinAPI.IsMyDesktopActive())
{
return;
}
@@ -314,7 +314,7 @@ internal static class Helper
_ = Launch.CreateProcessInInputDesktopSession(
$"\"{Path.GetDirectoryName(Application.ExecutablePath)}\\{HelperProcessName}.exe\"",
string.Empty,
- Common.GetInputDesktop(),
+ WinAPI.GetInputDesktop(),
0);
Clipboard.HasSwitchedMachineSinceLastCopy = true;
@@ -379,7 +379,7 @@ internal static class Helper
log += "=============================================================================================================================\r\n";
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
- log += $"{Setting.Values.Username}/{Common.GetDebugInfo(Common.MyKey)}\r\n";
+ log += $"{Setting.Values.Username}/{Encryption.GetDebugInfo(Encryption.MyKey)}\r\n";
log += $"{Common.MachineName}/{Common.MachineID}/{Common.DesMachineID}\r\n";
log += $"Id: {Setting.Values.DeviceId}\r\n";
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";
diff --git a/src/modules/MouseWithoutBorders/App/Core/ID.cs b/src/modules/MouseWithoutBorders/App/Core/ID.cs
new file mode 100644
index 0000000000..11dfcc22c8
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/ID.cs
@@ -0,0 +1,19 @@
+// 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.
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal enum ID : uint
+{
+ NONE = 0,
+ ALL = 255,
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs b/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs
index 963775cbca..510671ee95 100644
--- a/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/InitAndCleanup.cs
@@ -89,23 +89,23 @@ internal static class InitAndCleanup
internal static void Init()
{
_ = Helper.GetUserName();
- Common.GeneratedKey = true;
+ Encryption.GeneratedKey = true;
try
{
- Common.MyKey = Setting.Values.MyKey;
+ Encryption.MyKey = Setting.Values.MyKey;
int tmp = Setting.Values.MyKeyDaysToExpire;
}
catch (FormatException e)
{
- Common.KeyCorrupted = true;
- Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
+ Encryption.KeyCorrupted = true;
+ Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
Logger.Log(e.Message);
}
catch (CryptographicException e)
{
- Common.KeyCorrupted = true;
- Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
+ Encryption.KeyCorrupted = true;
+ Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
Logger.Log(e.Message);
}
@@ -127,14 +127,14 @@ internal static class InitAndCleanup
bool dummy = Setting.Values.DrawMouseEx;
Common.Is64bitOS = IntPtr.Size == 8;
Common.tcpPort = Setting.Values.TcpPort;
- Common.GetScreenConfig();
- Common.PackageSent = new PackageMonitor(0);
- Common.PackageReceived = new PackageMonitor(0);
+ WinAPI.GetScreenConfig();
+ Package.PackageSent = new PackageMonitor(0);
+ Package.PackageReceived = new PackageMonitor(0);
SetupMachineNameAndID();
- Common.InitEncryption();
+ Encryption.InitEncryption();
CreateHelperThreads();
- SystemEvents.DisplaySettingsChanged += new EventHandler(Common.SystemEvents_DisplaySettingsChanged);
+ SystemEvents.DisplaySettingsChanged += new EventHandler(WinAPI.SystemEvents_DisplaySettingsChanged);
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
PleaseReopenSocket = 9;
@@ -220,7 +220,7 @@ internal static class InitAndCleanup
lastReleaseAllKeysCall = Common.GetTick();
KEYBDDATA kd;
- kd.dwFlags = (int)Common.LLKHF.UP;
+ kd.dwFlags = (int)WM.LLKHF.UP;
VK[] keys = new VK[]
{
@@ -266,7 +266,7 @@ internal static class InitAndCleanup
true);
}
- if (!Common.IsMyDesktopActive())
+ if (!WinAPI.IsMyDesktopActive())
{
PleaseReopenSocket = 0;
}
diff --git a/src/modules/MouseWithoutBorders/App/Core/KEYBDDATA.cs b/src/modules/MouseWithoutBorders/App/Core/KEYBDDATA.cs
new file mode 100644
index 0000000000..244c069c98
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/KEYBDDATA.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct KEYBDDATA
+{
+ [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
+ internal int wVk;
+ [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
+ internal int dwFlags;
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Logger.cs b/src/modules/MouseWithoutBorders/App/Core/Logger.cs
index 86ce7605b5..4d39983c35 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Logger.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Logger.cs
@@ -121,52 +121,52 @@ internal static class Logger
{
string log;
- if (!lastPackageSent.Equals(Common.PackageSent))
+ if (!lastPackageSent.Equals(Package.PackageSent))
{
log = string.Format(
CultureInfo.CurrentCulture,
"SENT:" + HeaderSENT,
- Common.PackageSent.Heartbeat,
- Common.PackageSent.Keyboard,
- Common.PackageSent.Mouse,
- Common.PackageSent.Hello,
- Common.PackageSent.Matrix,
- Common.PackageSent.ClipboardText,
- Common.PackageSent.ClipboardImage,
- Common.PackageSent.ByeBye,
- Common.PackageSent.Clipboard,
- Common.PackageSent.ClipboardDragDrop,
- Common.PackageSent.ClipboardDragDropEnd,
- Common.PackageSent.ExplorerDragDrop,
+ Package.PackageSent.Heartbeat,
+ Package.PackageSent.Keyboard,
+ Package.PackageSent.Mouse,
+ Package.PackageSent.Hello,
+ Package.PackageSent.Matrix,
+ Package.PackageSent.ClipboardText,
+ Package.PackageSent.ClipboardImage,
+ Package.PackageSent.ByeBye,
+ Package.PackageSent.Clipboard,
+ Package.PackageSent.ClipboardDragDrop,
+ Package.PackageSent.ClipboardDragDropEnd,
+ Package.PackageSent.ExplorerDragDrop,
Event.inputEventCount,
- Common.PackageSent.Nil);
+ Package.PackageSent.Nil);
Log(log);
- lastPackageSent = Common.PackageSent; // Copy data
+ lastPackageSent = Package.PackageSent; // Copy data
}
- if (!lastPackageReceived.Equals(Common.PackageReceived))
+ if (!lastPackageReceived.Equals(Package.PackageReceived))
{
log = string.Format(
CultureInfo.CurrentCulture,
"RECEIVED:" + HeaderRECEIVED,
- Common.PackageReceived.Heartbeat,
- Common.PackageReceived.Keyboard,
- Common.PackageReceived.Mouse,
- Common.PackageReceived.Hello,
- Common.PackageReceived.Matrix,
- Common.PackageReceived.ClipboardText,
- Common.PackageReceived.ClipboardImage,
- Common.PackageReceived.ByeBye,
- Common.PackageReceived.Clipboard,
- Common.PackageReceived.ClipboardDragDrop,
- Common.PackageReceived.ClipboardDragDropEnd,
- Common.PackageReceived.ExplorerDragDrop,
+ Package.PackageReceived.Heartbeat,
+ Package.PackageReceived.Keyboard,
+ Package.PackageReceived.Mouse,
+ Package.PackageReceived.Hello,
+ Package.PackageReceived.Matrix,
+ Package.PackageReceived.ClipboardText,
+ Package.PackageReceived.ClipboardImage,
+ Package.PackageReceived.ByeBye,
+ Package.PackageReceived.Clipboard,
+ Package.PackageReceived.ClipboardDragDrop,
+ Package.PackageReceived.ClipboardDragDropEnd,
+ Package.PackageReceived.ExplorerDragDrop,
Event.invalidPackageCount,
- Common.PackageReceived.Nil,
+ Package.PackageReceived.Nil,
Receiver.processedPackageCount,
Receiver.skippedPackageCount);
Log(log);
- lastPackageReceived = Common.PackageReceived;
+ lastPackageReceived = Package.PackageReceived;
}
}
@@ -209,9 +209,9 @@ internal static class Logger
"Private Mem: " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024).ToString(CultureInfo.CurrentCulture) + "KB",
sb.ToString());
- if (!string.IsNullOrEmpty(Common.myKey))
+ if (!string.IsNullOrEmpty(Encryption.myKey))
{
- log = log.Replace(Common.MyKey, Common.GetDebugInfo(Common.MyKey));
+ log = log.Replace(Encryption.MyKey, Encryption.GetDebugInfo(Encryption.MyKey));
}
log += Thread.DumpThreadsStack();
@@ -251,14 +251,18 @@ internal static class Logger
{
typeof(Clipboard),
typeof(DragDrop),
+ typeof(Encryption),
typeof(Event),
typeof(InitAndCleanup),
typeof(Helper),
typeof(Launch),
typeof(Logger),
typeof(MachineStuff),
+ typeof(Package),
typeof(Receiver),
typeof(Service),
+ typeof(WinAPI),
+ typeof(WM),
};
foreach (var staticType in staticTypes)
{
@@ -294,7 +298,7 @@ internal static class Logger
// strArr[3] = t.FullName;
strArr[4] = " = ";
strArr[5] = objName.Equals("myKey", StringComparison.OrdinalIgnoreCase)
- ? Common.GetDebugInfo(objString)
+ ? Encryption.GetDebugInfo(objString)
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
? string.Empty
: objString
diff --git a/src/modules/MouseWithoutBorders/App/Core/MOUSEDATA.cs b/src/modules/MouseWithoutBorders/App/Core/MOUSEDATA.cs
new file mode 100644
index 0000000000..8f8e0f4267
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/MOUSEDATA.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct MOUSEDATA
+{
+ internal int X;
+ internal int Y;
+ internal int WheelDelta;
+ [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Same name as in winAPI")]
+ internal int dwFlags;
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs b/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs
index e5263aa788..add9a03b04 100644
--- a/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/MachineStuff.cs
@@ -221,9 +221,9 @@ internal static class MachineStuff
if (Setting.Values.BlockMouseAtCorners)
{
- lock (Common.SensitivePoints)
+ lock (WinAPI.SensitivePoints)
{
- foreach (Point p in Common.SensitivePoints)
+ foreach (Point p in WinAPI.SensitivePoints)
{
if (Math.Abs(p.X - x) < 100 && Math.Abs(p.Y - y) < 100)
{
@@ -793,8 +793,8 @@ internal static class MachineStuff
internal static void ShowSetupForm(bool reopenSockets = false)
{
Logger.LogDebug("========== BEGIN THE SETUP EXPERIENCE ==========", true);
- Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
- Common.GeneratedKey = true;
+ Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
+ Encryption.GeneratedKey = true;
if (Process.GetCurrentProcess().SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
{
@@ -1067,7 +1067,7 @@ internal static class MachineStuff
internal static void AssertOneInstancePerDesktopSession()
{
- string eventName = $"Global\\{Application.ProductName}-{FrmAbout.AssemblyVersion}-{Common.GetMyDesktop()}-{Common.CurrentProcess.SessionId}";
+ string eventName = $"Global\\{Application.ProductName}-{FrmAbout.AssemblyVersion}-{WinAPI.GetMyDesktop()}-{Common.CurrentProcess.SessionId}";
oneInstanceCheck = new EventWaitHandle(false, EventResetMode.ManualReset, eventName, out bool created);
if (!created)
diff --git a/src/modules/MouseWithoutBorders/App/Core/Package.cs b/src/modules/MouseWithoutBorders/App/Core/Package.cs
new file mode 100644
index 0000000000..54b5e3e467
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/Package.cs
@@ -0,0 +1,23 @@
+// 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.
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal static class Package
+{
+ internal const byte PACKAGE_SIZE = 32;
+ internal const byte PACKAGE_SIZE_EX = 64;
+ private const byte WP_PACKAGE_SIZE = 6;
+ internal static PackageMonitor PackageSent;
+ internal static PackageMonitor PackageReceived;
+ internal static int PackageID;
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/PackageMonitor.cs b/src/modules/MouseWithoutBorders/App/Core/PackageMonitor.cs
new file mode 100644
index 0000000000..e0ccf9ef75
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/PackageMonitor.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal struct PackageMonitor
+{
+ internal ulong Keyboard;
+ internal ulong Mouse;
+ internal ulong Heartbeat;
+ internal ulong ByeBye;
+ internal ulong Hello;
+ internal ulong Matrix;
+ internal ulong ClipboardText;
+ internal ulong ClipboardImage;
+ internal ulong Clipboard;
+ internal ulong ClipboardDragDrop;
+ internal ulong ClipboardDragDropEnd;
+ internal ulong ClipboardAsk;
+ internal ulong ExplorerDragDrop;
+ internal ulong Nil;
+
+ internal PackageMonitor(ulong value)
+ {
+ ClipboardDragDrop = ClipboardDragDropEnd = ExplorerDragDrop =
+ Keyboard = Mouse = Heartbeat = ByeBye = Hello = Clipboard =
+ Matrix = ClipboardImage = ClipboardText = Nil = ClipboardAsk = value;
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/PackageType.cs b/src/modules/MouseWithoutBorders/App/Core/PackageType.cs
new file mode 100644
index 0000000000..9b7b48fc12
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/PackageType.cs
@@ -0,0 +1,57 @@
+// 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.
+
+//
+// Package format/conversion.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal enum PackageType // : int
+{
+ // Search for PACKAGE_TYPE_RELATED before changing these!
+ Invalid = 0xFF,
+
+ Error = 0xFE,
+
+ Hi = 2,
+ Hello = 3,
+ ByeBye = 4,
+
+ Heartbeat = 20,
+ Awake = 21,
+ HideMouse = 50,
+ Heartbeat_ex = 51,
+ Heartbeat_ex_l2 = 52,
+ Heartbeat_ex_l3 = 53,
+
+ Clipboard = 69,
+ ClipboardDragDrop = 70,
+ ClipboardDragDropEnd = 71,
+ ExplorerDragDrop = 72,
+ ClipboardCapture = 73,
+ CaptureScreenCommand = 74,
+ ClipboardDragDropOperation = 75,
+ ClipboardDataEnd = 76,
+ MachineSwitched = 77,
+ ClipboardAsk = 78,
+ ClipboardPush = 79,
+
+ NextMachine = 121,
+ Keyboard = 122,
+ Mouse = 123,
+ ClipboardText = 124,
+ ClipboardImage = 125,
+
+ Handshake = 126,
+ HandshakeAck = 127,
+
+ Matrix = 128,
+ MatrixSwapFlag = 2,
+ MatrixTwoRowFlag = 4,
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Receiver.cs b/src/modules/MouseWithoutBorders/App/Core/Receiver.cs
index 1b1e0730b0..0a6aaad2ee 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Receiver.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Receiver.cs
@@ -93,7 +93,7 @@ internal static class Receiver
switch (package.Type)
{
case PackageType.Keyboard:
- Common.PackageReceived.Keyboard++;
+ Package.PackageReceived.Keyboard++;
if (package.Des == Common.MachineID || package.Des == ID.ALL)
{
JustGotAKey = Common.GetTick();
@@ -102,7 +102,7 @@ internal static class Receiver
bool nonElevated = Common.RunWithNoAdminRight && false;
if (nonElevated && Setting.Values.OneWayControlMode)
{
- if ((package.Kd.dwFlags & (int)Common.LLKHF.UP) == (int)Common.LLKHF.UP)
+ if ((package.Kd.dwFlags & (int)WM.LLKHF.UP) == (int)WM.LLKHF.UP)
{
Helper.ShowOneWayModeMessage();
}
@@ -116,7 +116,7 @@ internal static class Receiver
break;
case PackageType.Mouse:
- Common.PackageReceived.Mouse++;
+ Package.PackageReceived.Mouse++;
if (package.Des == Common.MachineID || package.Des == ID.ALL)
{
@@ -127,16 +127,16 @@ internal static class Receiver
// NOTE(@yuyoyuppe): disabled to drop elevation requirement
bool nonElevated = Common.RunWithNoAdminRight && false;
- if (nonElevated && Setting.Values.OneWayControlMode && package.Md.dwFlags != Common.WM_MOUSEMOVE)
+ if (nonElevated && Setting.Values.OneWayControlMode && package.Md.dwFlags != WM.WM_MOUSEMOVE)
{
if (!DragDrop.IsDropping)
{
- if (package.Md.dwFlags is Common.WM_LBUTTONDOWN or Common.WM_RBUTTONDOWN)
+ if (package.Md.dwFlags is WM.WM_LBUTTONDOWN or WM.WM_RBUTTONDOWN)
{
Helper.ShowOneWayModeMessage();
}
}
- else if (package.Md.dwFlags is Common.WM_LBUTTONUP or Common.WM_RBUTTONUP)
+ else if (package.Md.dwFlags is WM.WM_LBUTTONUP or WM.WM_RBUTTONUP)
{
DragDrop.IsDropping = false;
}
@@ -146,7 +146,7 @@ internal static class Receiver
if (Math.Abs(package.Md.X) >= Event.MOVE_MOUSE_RELATIVE && Math.Abs(package.Md.Y) >= Event.MOVE_MOUSE_RELATIVE)
{
- if (package.Md.dwFlags == Common.WM_MOUSEMOVE)
+ if (package.Md.dwFlags == WM.WM_MOUSEMOVE)
{
InputSimulation.MoveMouseRelative(
package.Md.X < 0 ? package.Md.X + Event.MOVE_MOUSE_RELATIVE : package.Md.X - Event.MOVE_MOUSE_RELATIVE,
@@ -203,19 +203,19 @@ internal static class Receiver
break;
case PackageType.ExplorerDragDrop:
- Common.PackageReceived.ExplorerDragDrop++;
+ Package.PackageReceived.ExplorerDragDrop++;
DragDrop.DragDropStep03(package);
break;
case PackageType.Heartbeat:
case PackageType.Heartbeat_ex:
- Common.PackageReceived.Heartbeat++;
+ Package.PackageReceived.Heartbeat++;
- Common.GeneratedKey = Common.GeneratedKey || package.Type == PackageType.Heartbeat_ex;
+ Encryption.GeneratedKey = Encryption.GeneratedKey || package.Type == PackageType.Heartbeat_ex;
- if (Common.GeneratedKey)
+ if (Encryption.GeneratedKey)
{
- Setting.Values.MyKey = Common.MyKey;
+ Setting.Values.MyKey = Encryption.MyKey;
Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l2);
}
@@ -230,26 +230,26 @@ internal static class Receiver
break;
case PackageType.Heartbeat_ex_l2:
- Common.GeneratedKey = true;
- Setting.Values.MyKey = Common.MyKey;
+ Encryption.GeneratedKey = true;
+ Setting.Values.MyKey = Encryption.MyKey;
Common.SendPackage(ID.ALL, PackageType.Heartbeat_ex_l3);
break;
case PackageType.Heartbeat_ex_l3:
- Common.GeneratedKey = true;
- Setting.Values.MyKey = Common.MyKey;
+ Encryption.GeneratedKey = true;
+ Setting.Values.MyKey = Encryption.MyKey;
break;
case PackageType.Awake:
- Common.PackageReceived.Heartbeat++;
+ Package.PackageReceived.Heartbeat++;
_ = MachineStuff.AddToMachinePool(package);
Common.HumanBeingDetected();
break;
case PackageType.Hello:
- Common.PackageReceived.Hello++;
+ Package.PackageReceived.Hello++;
Common.SendHeartBeat();
string newMachine = MachineStuff.AddToMachinePool(package);
if (Setting.Values.MachineMatrixString == null)
@@ -262,16 +262,16 @@ internal static class Receiver
break;
case PackageType.Hi:
- Common.PackageReceived.Hello++;
+ Package.PackageReceived.Hello++;
break;
case PackageType.ByeBye:
- Common.PackageReceived.ByeBye++;
+ Package.PackageReceived.ByeBye++;
Common.ProcessByeByeMessage(package);
break;
case PackageType.Clipboard:
- Common.PackageReceived.Clipboard++;
+ Package.PackageReceived.Clipboard++;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{
Clipboard.clipboardCopiedTime = Common.GetTick();
@@ -291,7 +291,7 @@ internal static class Receiver
break;
case PackageType.ClipboardCapture:
- Common.PackageReceived.Clipboard++;
+ Package.PackageReceived.Clipboard++;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{
if (package.Des == Common.MachineID || package.Des == ID.ALL)
@@ -304,7 +304,7 @@ internal static class Receiver
break;
case PackageType.CaptureScreenCommand:
- Common.PackageReceived.Clipboard++;
+ Package.PackageReceived.Clipboard++;
if (package.Des == Common.MachineID || package.Des == ID.ALL)
{
Common.SendImage(package.Src, Common.CaptureScreen());
@@ -313,7 +313,7 @@ internal static class Receiver
break;
case PackageType.ClipboardAsk:
- Common.PackageReceived.ClipboardAsk++;
+ Package.PackageReceived.ClipboardAsk++;
if (package.Des == Common.MachineID)
{
@@ -344,17 +344,17 @@ internal static class Receiver
break;
case PackageType.ClipboardDragDrop:
- Common.PackageReceived.ClipboardDragDrop++;
+ Package.PackageReceived.ClipboardDragDrop++;
DragDrop.DragDropStep08(package);
break;
case PackageType.ClipboardDragDropOperation:
- Common.PackageReceived.ClipboardDragDrop++;
+ Package.PackageReceived.ClipboardDragDrop++;
DragDrop.DragDropStep08_2(package);
break;
case PackageType.ClipboardDragDropEnd:
- Common.PackageReceived.ClipboardDragDropEnd++;
+ Package.PackageReceived.ClipboardDragDropEnd++;
DragDrop.DragDropStep12();
break;
@@ -363,11 +363,11 @@ internal static class Receiver
Clipboard.clipboardCopiedTime = 0;
if (package.Type == PackageType.ClipboardImage)
{
- Common.PackageReceived.ClipboardImage++;
+ Package.PackageReceived.ClipboardImage++;
}
else
{
- Common.PackageReceived.ClipboardText++;
+ Package.PackageReceived.ClipboardText++;
}
if (tcp != null)
@@ -390,7 +390,7 @@ internal static class Receiver
default:
if ((package.Type & PackageType.Matrix) == PackageType.Matrix)
{
- Common.PackageReceived.Matrix++;
+ Package.PackageReceived.Matrix++;
MachineStuff.UpdateMachineMatrix(package);
break;
}
diff --git a/src/modules/MouseWithoutBorders/App/Core/ShutdownWithPowerToys.cs b/src/modules/MouseWithoutBorders/App/Core/ShutdownWithPowerToys.cs
new file mode 100644
index 0000000000..a221aa1733
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/ShutdownWithPowerToys.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+using ManagedCommon;
+using Microsoft.PowerToys.Telemetry;
+
+namespace MouseWithoutBorders.Core;
+
+internal static class ShutdownWithPowerToys
+{
+ internal static void WaitForPowerToysRunner(ETWTrace etwTrace)
+ {
+ try
+ {
+ RunnerHelper.WaitForPowerToysRunnerExitFallback(() =>
+ {
+ etwTrace?.Dispose();
+ Common.MainForm.Quit(true, false);
+ });
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/VK.cs b/src/modules/MouseWithoutBorders/App/Core/VK.cs
new file mode 100644
index 0000000000..239692abed
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/VK.cs
@@ -0,0 +1,88 @@
+// 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.
+
+//
+// Virtual key constants.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal enum VK : ushort
+{
+ CAPITAL = 0x14,
+ NUMLOCK = 0x90,
+ SHIFT = 0x10,
+ CONTROL = 0x11,
+ MENU = 0x12,
+ ESCAPE = 0x1B,
+ BACK = 0x08,
+ TAB = 0x09,
+ RETURN = 0x0D,
+ PRIOR = 0x21,
+ NEXT = 0x22,
+ END = 0x23,
+ HOME = 0x24,
+ LEFT = 0x25,
+ UP = 0x26,
+ RIGHT = 0x27,
+ DOWN = 0x28,
+ SELECT = 0x29,
+ PRINT = 0x2A,
+ EXECUTE = 0x2B,
+ SNAPSHOT = 0x2C,
+ INSERT = 0x2D,
+ DELETE = 0x2E,
+ HELP = 0x2F,
+ NUMPAD0 = 0x60,
+ NUMPAD1 = 0x61,
+ NUMPAD2 = 0x62,
+ NUMPAD3 = 0x63,
+ NUMPAD4 = 0x64,
+ NUMPAD5 = 0x65,
+ NUMPAD6 = 0x66,
+ NUMPAD7 = 0x67,
+ NUMPAD8 = 0x68,
+ NUMPAD9 = 0x69,
+ MULTIPLY = 0x6A,
+ ADD = 0x6B,
+ SEPARATOR = 0x6C,
+ SUBTRACT = 0x6D,
+ DECIMAL = 0x6E,
+ DIVIDE = 0x6F,
+ F1 = 0x70,
+ F2 = 0x71,
+ F3 = 0x72,
+ F4 = 0x73,
+ F5 = 0x74,
+ F6 = 0x75,
+ F7 = 0x76,
+ F8 = 0x77,
+ F9 = 0x78,
+ F10 = 0x79,
+ F11 = 0x7A,
+ F12 = 0x7B,
+ OEM_1 = 0xBA,
+ OEM_PLUS = 0xBB,
+ OEM_COMMA = 0xBC,
+ OEM_MINUS = 0xBD,
+ OEM_PERIOD = 0xBE,
+ OEM_2 = 0xBF,
+ OEM_3 = 0xC0,
+ MEDIA_NEXT_TRACK = 0xB0,
+ MEDIA_PREV_TRACK = 0xB1,
+ MEDIA_STOP = 0xB2,
+ MEDIA_PLAY_PAUSE = 0xB3,
+ LWIN = 0x5B,
+ RWIN = 0x5C,
+ LSHIFT = 0xA0,
+ RSHIFT = 0xA1,
+ LCONTROL = 0xA2,
+ RCONTROL = 0xA3,
+ LMENU = 0xA4,
+ RMENU = 0xA5,
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/WM.cs b/src/modules/MouseWithoutBorders/App/Core/WM.cs
new file mode 100644
index 0000000000..e93897e93b
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/WM.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+//
+// Virtual key constants.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal partial class WM
+{
+ internal const ushort KEYEVENTF_KEYDOWN = 0x0001;
+ internal const ushort KEYEVENTF_KEYUP = 0x0002;
+
+ internal const int WH_MOUSE = 7;
+ internal const int WH_KEYBOARD = 2;
+ internal const int WH_MOUSE_LL = 14;
+ internal const int WH_KEYBOARD_LL = 13;
+
+ internal const int WM_MOUSEMOVE = 0x200;
+ internal const int WM_LBUTTONDOWN = 0x201;
+ internal const int WM_RBUTTONDOWN = 0x204;
+ internal const int WM_MBUTTONDOWN = 0x207;
+ internal const int WM_XBUTTONDOWN = 0x20B;
+ internal const int WM_LBUTTONUP = 0x202;
+ internal const int WM_RBUTTONUP = 0x205;
+ internal const int WM_MBUTTONUP = 0x208;
+ internal const int WM_XBUTTONUP = 0x20C;
+ internal const int WM_LBUTTONDBLCLK = 0x203;
+ internal const int WM_RBUTTONDBLCLK = 0x206;
+ internal const int WM_MBUTTONDBLCLK = 0x209;
+ internal const int WM_MOUSEWHEEL = 0x020A;
+ internal const int WM_MOUSEHWHEEL = 0x020E;
+
+ internal const int WM_KEYDOWN = 0x100;
+ internal const int WM_KEYUP = 0x101;
+ internal const int WM_SYSKEYDOWN = 0x104;
+ internal const int WM_SYSKEYUP = 0x105;
+
+ [Flags]
+ internal enum LLKHF
+ {
+ EXTENDED = 0x01,
+ INJECTED = 0x10,
+ ALTDOWN = 0x20,
+ UP = 0x80,
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/WinAPI.cs b/src/modules/MouseWithoutBorders/App/Core/WinAPI.cs
new file mode 100644
index 0000000000..4d14dcb973
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/WinAPI.cs
@@ -0,0 +1,359 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Windows.Forms;
+
+using MouseWithoutBorders.Class;
+
+//
+// Screen/Desktop helper functions.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+// Desktops, and GetScreenConfig routines
+internal static class WinAPI
+{
+ private static MyRectangle newDesktopBounds;
+ private static MyRectangle newPrimaryScreenBounds;
+ private static string activeDesktop;
+
+ private static string ActiveDesktop => WinAPI.activeDesktop;
+
+ internal static void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
+ {
+ GetScreenConfig();
+ }
+
+ internal static readonly List SensitivePoints = new();
+
+ private static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref NativeMethods.RECT lprcMonitor, IntPtr dwData)
+ {
+ // lprcMonitor is wrong!!! => using GetMonitorInfo(...)
+ // Log(String.Format( CultureInfo.CurrentCulture,"MONITOR: l{0}, t{1}, r{2}, b{3}", lprcMonitor.Left, lprcMonitor.Top, lprcMonitor.Right, lprcMonitor.Bottom));
+ NativeMethods.MonitorInfoEx mi = default;
+ mi.cbSize = Marshal.SizeOf(mi);
+ _ = NativeMethods.GetMonitorInfo(hMonitor, ref mi);
+
+ try
+ {
+ // For logging only
+ _ = NativeMethods.GetDpiForMonitor(hMonitor, 0, out uint dpiX, out uint dpiY);
+ Logger.Log(string.Format(CultureInfo.CurrentCulture, "MONITOR: ({0}, {1}, {2}, {3}). DPI: ({4}, {5})", mi.rcMonitor.Left, mi.rcMonitor.Top, mi.rcMonitor.Right, mi.rcMonitor.Bottom, dpiX, dpiY));
+ }
+ catch (DllNotFoundException)
+ {
+ Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
+ }
+ catch (EntryPointNotFoundException)
+ {
+ Logger.Log("GetDpiForMonitor is unsupported in Windows 7 and lower.");
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+
+ if (mi.rcMonitor.Left == 0 && mi.rcMonitor.Top == 0 && mi.rcMonitor.Right != 0 && mi.rcMonitor.Bottom != 0)
+ {
+ // Primary screen
+ _ = Interlocked.Exchange(ref Common.screenWidth, mi.rcMonitor.Right - mi.rcMonitor.Left);
+ _ = Interlocked.Exchange(ref Common.screenHeight, mi.rcMonitor.Bottom - mi.rcMonitor.Top);
+
+ newPrimaryScreenBounds.Left = mi.rcMonitor.Left;
+ newPrimaryScreenBounds.Top = mi.rcMonitor.Top;
+ newPrimaryScreenBounds.Right = mi.rcMonitor.Right;
+ newPrimaryScreenBounds.Bottom = mi.rcMonitor.Bottom;
+ }
+ else
+ {
+ if (mi.rcMonitor.Left < newDesktopBounds.Left)
+ {
+ newDesktopBounds.Left = mi.rcMonitor.Left;
+ }
+
+ if (mi.rcMonitor.Top < newDesktopBounds.Top)
+ {
+ newDesktopBounds.Top = mi.rcMonitor.Top;
+ }
+
+ if (mi.rcMonitor.Right > newDesktopBounds.Right)
+ {
+ newDesktopBounds.Right = mi.rcMonitor.Right;
+ }
+
+ if (mi.rcMonitor.Bottom > newDesktopBounds.Bottom)
+ {
+ newDesktopBounds.Bottom = mi.rcMonitor.Bottom;
+ }
+ }
+
+ lock (SensitivePoints)
+ {
+ SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Top));
+ SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Top));
+ SensitivePoints.Add(new Point(mi.rcMonitor.Right, mi.rcMonitor.Bottom));
+ SensitivePoints.Add(new Point(mi.rcMonitor.Left, mi.rcMonitor.Bottom));
+ }
+
+ return true;
+ }
+
+ internal static void GetScreenConfig()
+ {
+ try
+ {
+ Logger.LogDebug("==================== GetScreenConfig started");
+ newDesktopBounds = new MyRectangle();
+ newPrimaryScreenBounds = new MyRectangle();
+ newDesktopBounds.Left = newPrimaryScreenBounds.Left = Screen.PrimaryScreen.Bounds.Left;
+ newDesktopBounds.Top = newPrimaryScreenBounds.Top = Screen.PrimaryScreen.Bounds.Top;
+ newDesktopBounds.Right = newPrimaryScreenBounds.Right = Screen.PrimaryScreen.Bounds.Right;
+ newDesktopBounds.Bottom = newPrimaryScreenBounds.Bottom = Screen.PrimaryScreen.Bounds.Bottom;
+
+ Logger.Log(string.Format(
+ CultureInfo.CurrentCulture,
+ "logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
+ Common.RunOnLogonDesktop,
+ WinAPI.newPrimaryScreenBounds.Left,
+ WinAPI.newPrimaryScreenBounds.Top,
+ WinAPI.newPrimaryScreenBounds.Right,
+ WinAPI.newPrimaryScreenBounds.Bottom,
+ WinAPI.newDesktopBounds.Left,
+ WinAPI.newDesktopBounds.Top,
+ WinAPI.newDesktopBounds.Right,
+ WinAPI.newDesktopBounds.Bottom));
+
+#if USE_MANAGED_ROUTINES
+ // Managed routines do not work well when running on secure desktop:(
+ screenWidth = Screen.PrimaryScreen.Bounds.Width;
+ screenHeight = Screen.PrimaryScreen.Bounds.Height;
+ screenCount = Screen.AllScreens.Length;
+ for (int i = 0; i < Screen.AllScreens.Length; i++)
+ {
+ if (Screen.AllScreens[i].Bounds.Left < desktopBounds.Left) desktopBounds.Left = Screen.AllScreens[i].Bounds.Left;
+ if (Screen.AllScreens[i].Bounds.Top < desktopBounds.Top) desktopBounds.Top = Screen.AllScreens[i].Bounds.Top;
+ if (Screen.AllScreens[i].Bounds.Right > desktopBounds.Right) desktopBounds.Right = Screen.AllScreens[i].Bounds.Right;
+ if (Screen.AllScreens[i].Bounds.Bottom > desktopBounds.Bottom) desktopBounds.Bottom = Screen.AllScreens[i].Bounds.Bottom;
+ }
+#else
+ lock (SensitivePoints)
+ {
+ SensitivePoints.Clear();
+ }
+
+ NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
+
+ // 1000 calls to EnumDisplayMonitors cost a dozen of milliseconds
+#endif
+ Interlocked.Exchange(ref MachineStuff.desktopBounds, newDesktopBounds);
+ Interlocked.Exchange(ref MachineStuff.primaryScreenBounds, newPrimaryScreenBounds);
+
+ Logger.Log(string.Format(
+ CultureInfo.CurrentCulture,
+ "logon = {0} PrimaryScreenBounds = {1},{2},{3},{4} desktopBounds = {5},{6},{7},{8}",
+ Common.RunOnLogonDesktop,
+ MachineStuff.PrimaryScreenBounds.Left,
+ MachineStuff.PrimaryScreenBounds.Top,
+ MachineStuff.PrimaryScreenBounds.Right,
+ MachineStuff.PrimaryScreenBounds.Bottom,
+ MachineStuff.DesktopBounds.Left,
+ MachineStuff.DesktopBounds.Top,
+ MachineStuff.DesktopBounds.Right,
+ MachineStuff.DesktopBounds.Bottom));
+
+ Logger.Log("==================== GetScreenConfig ended");
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ }
+
+#if USING_SCREEN_SAVER_ROUTINES
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern int PostMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern IntPtr OpenDesktop(string hDesktop, int Flags, bool Inherit, UInt32 DesiredAccess);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool CloseDesktop(IntPtr hDesktop);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool EnumDesktopWindows( IntPtr hDesktop, EnumDesktopWindowsProc callback, IntPtr lParam);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ private static extern bool SystemParametersInfo(int uAction, int uParam, ref int pvParam, int flags);
+
+ private delegate bool EnumDesktopWindowsProc(IntPtr hDesktop, IntPtr lParam);
+ private const int WM_CLOSE = 16;
+ private const int SPI_GETSCREENSAVERRUNNING = 114;
+
+ internal static bool IsScreenSaverRunning()
+ {
+ int isRunning = 0;
+ SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0,ref isRunning, 0);
+ return (isRunning != 0);
+ }
+
+ internal static void CloseScreenSaver()
+ {
+ IntPtr hDesktop = OpenDesktop("Screen-saver", 0, false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
+ if (hDesktop != IntPtr.Zero)
+ {
+ LogDebug("Closing screen saver...");
+ EnumDesktopWindows(hDesktop, new EnumDesktopWindowsProc(CloseScreenSaverFunc), IntPtr.Zero);
+ CloseDesktop(hDesktop);
+ }
+ }
+
+ private static bool CloseScreenSaverFunc(IntPtr hWnd, IntPtr lParam)
+ {
+ if (IsWindowVisible(hWnd))
+ {
+ LogDebug("Posting WM_CLOSE to " + hWnd.ToString(CultureInfo.InvariantCulture));
+ PostMessage(hWnd, WM_CLOSE, 0, 0);
+ }
+ return true;
+ }
+#endif
+
+ internal static string GetMyDesktop()
+ {
+ byte[] arThreadDesktop = new byte[256];
+ IntPtr hD = NativeMethods.GetThreadDesktop(NativeMethods.GetCurrentThreadId());
+ if (hD != IntPtr.Zero)
+ {
+ _ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arThreadDesktop, arThreadDesktop.Length, out _);
+ return Common.GetString(arThreadDesktop).Replace("\0", string.Empty);
+ }
+
+ return string.Empty;
+ }
+
+ internal static string GetInputDesktop()
+ {
+ byte[] arInputDesktop = new byte[256];
+ IntPtr hD = NativeMethods.OpenInputDesktop(0, false, NativeMethods.DESKTOP_READOBJECTS);
+ if (hD != IntPtr.Zero)
+ {
+ _ = NativeMethods.GetUserObjectInformation(hD, NativeMethods.UOI_NAME, arInputDesktop, arInputDesktop.Length, out _);
+ return Common.GetString(arInputDesktop).Replace("\0", string.Empty);
+ }
+
+ return string.Empty;
+ }
+
+ private static void StartMMService(string desktopToRunMouseWithoutBordersOn)
+ {
+ if (!Common.RunWithNoAdminRight)
+ {
+ Logger.LogDebug("*** Starting on active Desktop: " + desktopToRunMouseWithoutBordersOn);
+ Service.StartMouseWithoutBordersService(desktopToRunMouseWithoutBordersOn);
+ }
+ }
+
+ internal static void CheckForDesktopSwitchEvent(bool cleanupIfExit)
+ {
+ try
+ {
+ if (!IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
+ {
+ Helper.RunDDHelper(true);
+ int waitCount = 20;
+
+ while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0)
+ {
+ waitCount--;
+ Logger.LogDebug("The session is detached/attached.");
+ Thread.Sleep(500);
+ }
+
+ string myDesktop = GetMyDesktop();
+ activeDesktop = GetInputDesktop();
+
+ Logger.LogDebug("*** Active Desktop = " + activeDesktop);
+ Logger.LogDebug("*** My Desktop = " + myDesktop);
+
+ if (myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.LogDebug("*** Active Desktop == My Desktop (TS session)");
+ }
+
+ if (!activeDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
+ !activeDesktop.Equals("default", StringComparison.OrdinalIgnoreCase) &&
+ !activeDesktop.Equals("disconnect", StringComparison.OrdinalIgnoreCase))
+ {
+ try
+ {
+ StartMMService(activeDesktop);
+ }
+ catch (Exception e)
+ {
+ Logger.Log($"{nameof(CheckForDesktopSwitchEvent)}: {e}");
+ }
+ }
+ else
+ {
+ if (!myDesktop.Equals(activeDesktop, StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Log("*** Active Desktop <> My Desktop");
+ }
+
+ uint sid = NativeMethods.WTSGetActiveConsoleSessionId();
+
+ if (Process.GetProcessesByName(Common.BinaryName).Any(p => (uint)p.SessionId == sid))
+ {
+ Logger.Log("Found MouseWithoutBorders on the active session!");
+ }
+ else
+ {
+ Logger.Log("MouseWithoutBorders not found on the active session!");
+ StartMMService(null);
+ }
+ }
+
+ if (!myDesktop.Equals("winlogon", StringComparison.OrdinalIgnoreCase) &&
+ !myDesktop.Equals("default", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.LogDebug("*** Desktop inactive, exiting: " + myDesktop);
+ Setting.Values.LastX = Common.JUST_GOT_BACK_FROM_SCREEN_SAVER;
+ if (cleanupIfExit)
+ {
+ InitAndCleanup.Cleanup();
+ }
+
+ Process.GetCurrentProcess().KillProcess();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ }
+
+ private static Point p;
+
+ internal static bool IsMyDesktopActive()
+ {
+ return NativeMethods.GetCursorPos(ref p);
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Form/Settings/SettingsFormPage.cs b/src/modules/MouseWithoutBorders/App/Form/Settings/SettingsFormPage.cs
index 39574ac8fe..81c04982a9 100644
--- a/src/modules/MouseWithoutBorders/App/Form/Settings/SettingsFormPage.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/Settings/SettingsFormPage.cs
@@ -42,7 +42,7 @@ namespace MouseWithoutBorders
protected string GetSecureKey()
{
- return Common.MyKey;
+ return Encryption.MyKey;
}
private void BackButton_Click(object sender, EventArgs e)
diff --git a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs
index c86df58143..5fcee5dc54 100644
--- a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2a.cs
@@ -89,8 +89,8 @@ namespace MouseWithoutBorders
{
if (GetSecureKey() != SecurityCodeField.Text)
{
- Common.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty);
- SecurityCode = Common.MyKey;
+ Encryption.MyKey = Regex.Replace(SecurityCodeField.Text, @"\s+", string.Empty);
+ SecurityCode = Encryption.MyKey;
}
MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { ComputerNameField.Text.Trim().ToUpper(CultureInfo.CurrentCulture), Common.MachineName.Trim(), string.Empty, string.Empty };
diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs b/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs
index 66301c52cb..703ad8ef91 100644
--- a/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/frmMatrix.cs
@@ -135,7 +135,7 @@ namespace MouseWithoutBorders
internal void UpdateKeyTextBox()
{
_ = Helper.GetUserName();
- textBoxEnc.Text = Common.MyKey;
+ textBoxEnc.Text = Encryption.MyKey;
}
private void InitAll()
@@ -505,19 +505,19 @@ namespace MouseWithoutBorders
private bool UpdateKey(string newKey)
{
- if (!Common.IsKeyValid(newKey, out string rv))
+ if (!Encryption.IsKeyValid(newKey, out string rv))
{
ShowKeyErrorMsg(rv);
return false;
}
- if (!newKey.Equals(Common.MyKey, StringComparison.OrdinalIgnoreCase))
+ if (!newKey.Equals(Encryption.MyKey, StringComparison.OrdinalIgnoreCase))
{
- Common.MyKey = newKey;
- Common.GeneratedKey = false;
+ Encryption.MyKey = newKey;
+ Encryption.GeneratedKey = false;
}
- Common.MagicNumber = Common.Get24BitHash(Common.MyKey);
+ Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);
return true;
}
@@ -1116,10 +1116,10 @@ namespace MouseWithoutBorders
if (MessageBox.Show(message, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.Yes)
{
- Setting.Values.MyKey = Common.MyKey = Common.CreateRandomKey();
- textBoxEnc.Text = Common.MyKey;
+ Setting.Values.MyKey = Encryption.MyKey = Encryption.CreateRandomKey();
+ textBoxEnc.Text = Encryption.MyKey;
checkBoxShowKey.Checked = true;
- Common.GeneratedKey = true;
+ Encryption.GeneratedKey = true;
ButtonOK_Click(null, null);
Common.ShowToolTip("New security key was generated, update other machines to the same key.", 10000, ToolTipIcon.Info, false);
}
diff --git a/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs b/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs
index 1ab0ce8cc7..ca123ec850 100644
--- a/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs
@@ -318,7 +318,7 @@ namespace MouseWithoutBorders
try
{
- if (!Common.IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
+ if (!WinAPI.IsMyDesktopActive() || Common.CurrentProcess.SessionId != NativeMethods.WTSGetActiveConsoleSessionId())
{
myDesktopNotActive = true;
@@ -348,7 +348,7 @@ namespace MouseWithoutBorders
Common.Hook?.ResetLastSwitchKeys();
});
- Common.CheckForDesktopSwitchEvent(true);
+ WinAPI.CheckForDesktopSwitchEvent(true);
}
}
else
@@ -369,21 +369,21 @@ namespace MouseWithoutBorders
if (myDesktopNotActive)
{
myDesktopNotActive = false;
- Common.MyKey = Setting.Values.MyKey;
+ Encryption.MyKey = Setting.Values.MyKey;
}
MachineStuff.UpdateMachinePoolStringSetting();
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && (Setting.Values.FirstRun || Common.KeyCorrupted))
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && (Setting.Values.FirstRun || Encryption.KeyCorrupted))
{
if (!shownSetupFormOneTime)
{
shownSetupFormOneTime = true;
MachineStuff.ShowMachineMatrix();
- if (Common.KeyCorrupted && !Setting.Values.FirstRun)
+ if (Encryption.KeyCorrupted && !Setting.Values.FirstRun)
{
- Common.KeyCorrupted = false;
+ Encryption.KeyCorrupted = false;
string msg = "The security key is corrupted for some reason, please re-setup.";
MessageBox.Show(msg, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
@@ -490,9 +490,9 @@ namespace MouseWithoutBorders
if (count == 600)
{
- if (!Common.GeneratedKey)
+ if (!Encryption.GeneratedKey)
{
- Common.MyKey = Setting.Values.MyKey;
+ Encryption.MyKey = Setting.Values.MyKey;
if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
{
@@ -505,7 +505,7 @@ namespace MouseWithoutBorders
Common.ShowToolTip("The security key must be auto generated in one of the machines.", 10000);
}
}
- else if (!Common.KeyCorrupted && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && !Setting.Values.FirstRun && Common.AtLeastOneSocketConnected())
+ else if (!Encryption.KeyCorrupted && !Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && !Setting.Values.FirstRun && Common.AtLeastOneSocketConnected())
{
int myKeyDaysToExpire = Setting.Values.MyKeyDaysToExpire;
@@ -531,7 +531,7 @@ namespace MouseWithoutBorders
#if SHOW_ON_WINLOGON
// if (Common.RunOnLogonDesktop) ShowMouseWithoutBordersUiOnWinLogonDesktop(false);
#endif
- Common.CheckForDesktopSwitchEvent(true);
+ WinAPI.CheckForDesktopSwitchEvent(true);
MachineStuff.UpdateClientSockets("helperTimer_Tick"); // Sockets may be closed by the remote host when both machines switch desktop at the same time.
}
@@ -582,7 +582,7 @@ namespace MouseWithoutBorders
int rv = 0;
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && Common.IsMyDesktopActive() && (rv = Helper.SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero)) <= 0)
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() && (rv = Helper.SendMessageToHelper(0x400, IntPtr.Zero, IntPtr.Zero)) <= 0)
{
Logger.TelemetryLogTrace($"{Helper.HELPER_FORM_TEXT} not found: {rv}", SeverityLevel.Warning);
}
diff --git a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt
index 1bbd8ba49c..34a83830cd 100644
--- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt
+++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt
@@ -46,79 +46,6 @@ avgSendTime = 0
maxSendTime = 0
totalSendCount = 0
totalSendTime = 0
-magicNumber = 0
-ran = System.Random
---_impl = System.Random+XoshiroImpl
-----_s0 = ????????????
-----_s1 = ????????????
-----_s2 = ????????????
-----_s3 = ????????????
---k__BackingField = System.Random+ThreadSafeRandom
-InitialIV = ????????????
-k__BackingField = False
-k__BackingField = False
-LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte[]]
---_tables = Concurrent.ConcurrentDictionary`2+Tables[System.String,System.Byte[]]
-----_comparer = Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
-----_buckets = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]
-------System.Collections.Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][] = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]: N/A
-----_fastModBucketsMultiplier = 498560650640798693
-----_locks = O[]
-------System.Object[] = O[]: N/A
-----_countPerLock = 32[]
-------[0] = 0
-------[1] = 0
-------[2] = 0
-------[3] = 0
-------[4] = 0
-------[5] = 0
-------[6] = 0
-------[7] = 0
---_budget = ????????????
---_growLockArray = True
---_comparerIsDefaultForClasses = False
-PackageSent = MouseWithoutBorders.PackageMonitor
---Keyboard = 0
---Mouse = 0
---Heartbeat = 0
---ByeBye = 0
---Hello = 0
---Matrix = 0
---ClipboardText = 0
---ClipboardImage = 0
---Clipboard = 0
---ClipboardDragDrop = 0
---ClipboardDragDropEnd = 0
---ClipboardAsk = 0
---ExplorerDragDrop = 0
---Nil = 0
-PackageReceived = MouseWithoutBorders.PackageMonitor
---Keyboard = 0
---Mouse = 0
---Heartbeat = 0
---ByeBye = 0
---Hello = 0
---Matrix = 0
---ClipboardText = 0
---ClipboardImage = 0
---Clipboard = 0
---ClipboardDragDrop = 0
---ClipboardDragDropEnd = 0
---ClipboardAsk = 0
---ExplorerDragDrop = 0
---Nil = 0
-PackageID = 0
-SensitivePoints = Generic.List`1[Point]
---_items = Point[]
-----System.Drawing.Point[] = Point[]: N/A
---_size = 0
---_version = 0
---s_emptyArray = Point[]
-----System.Drawing.Point[] = Point[]: N/A
-p = {X=0,Y=0}
---x = 0
---y = 0
---Empty = {X=0,Y=0}
k__BackingField = False
TOGGLE_ICONS_SIZE = 4
ICON_ONE = 0
@@ -128,34 +55,6 @@ ICON_BIG_CLIPBOARD = 3
ICON_ERROR = 4
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
NETWORK_STREAM_BUF_SIZE = 1048576
-SymAlBlockSize = 16
-PW_LENGTH = 16
-PACKAGE_SIZE = 32
-PACKAGE_SIZE_EX = 64
-WP_PACKAGE_SIZE = 6
-KEYEVENTF_KEYDOWN = 1
-KEYEVENTF_KEYUP = 2
-WH_MOUSE = 7
-WH_KEYBOARD = 2
-WH_MOUSE_LL = 14
-WH_KEYBOARD_LL = 13
-WM_MOUSEMOVE = 512
-WM_LBUTTONDOWN = 513
-WM_RBUTTONDOWN = 516
-WM_MBUTTONDOWN = 519
-WM_XBUTTONDOWN = 523
-WM_LBUTTONUP = 514
-WM_RBUTTONUP = 517
-WM_MBUTTONUP = 520
-WM_XBUTTONUP = 524
-WM_LBUTTONDBLCLK = 515
-WM_RBUTTONDBLCLK = 518
-WM_MBUTTONDBLCLK = 521
-WM_MOUSEWHEEL = 522
-WM_KEYDOWN = 256
-WM_KEYUP = 257
-WM_SYSKEYDOWN = 260
-WM_SYSKEYUP = 261
[Clipboard]
===============
Comma = System.Char[]
@@ -193,16 +92,51 @@ dragDropStep05ExCalledByIpc = 0
isDropping = False
dragMachine = NONE
k__BackingField = False
+[Encryption]
+===============
+magicNumber = 0
+ran = System.Random
+--_impl = System.Random+XoshiroImpl
+----_s0 = ????????????
+----_s1 = ????????????
+----_s2 = ????????????
+----_s3 = ????????????
+--k__BackingField = System.Random+ThreadSafeRandom
+InitialIV = ????????????
+k__BackingField = False
+k__BackingField = False
+LegalKeyDictionary = Concurrent.ConcurrentDictionary`2[System.String,System.Byte[]]
+--_tables = Concurrent.ConcurrentDictionary`2+Tables[System.String,System.Byte[]]
+----_comparer = Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
+----_buckets = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]
+------System.Collections.Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][] = Concurrent.ConcurrentDictionary`2+VolatileNode[System.String,System.Byte[]][]: N/A
+----_fastModBucketsMultiplier = 498560650640798693
+----_locks = O[]
+------System.Object[] = O[]: N/A
+----_countPerLock = 32[]
+------[0] = 0
+------[1] = 0
+------[2] = 0
+------[3] = 0
+------[4] = 0
+------[5] = 0
+------[6] = 0
+------[7] = 0
+--_budget = ????????????
+--_growLockArray = True
+--_comparerIsDefaultForClasses = False
+SymAlBlockSize = 16
+PW_LENGTH = 16
[Event]
===============
-KeybdPackage = MouseWithoutBorders.DATA
+KeybdPackage = MouseWithoutBorders.Core.DATA
--Type = 0
--Id = 0
--Src = NONE
--Des = NONE
--DateTime = 0
---Kd = MouseWithoutBorders.KEYBDDATA
---Md = MouseWithoutBorders.MOUSEDATA
+--Kd = MouseWithoutBorders.Core.KEYBDDATA
+--Md = MouseWithoutBorders.Core.MOUSEDATA
--Machine1 = NONE
--Machine2 = NONE
--Machine3 = NONE
@@ -212,14 +146,14 @@ KeybdPackage = MouseWithoutBorders.DATA
--machineNameP2 = 0
--machineNameP3 = 0
--machineNameP4 = 0
-MousePackage = MouseWithoutBorders.DATA
+MousePackage = MouseWithoutBorders.Core.DATA
--Type = 0
--Id = 0
--Src = NONE
--Des = NONE
--DateTime = 0
---Kd = MouseWithoutBorders.KEYBDDATA
---Md = MouseWithoutBorders.MOUSEDATA
+--Kd = MouseWithoutBorders.Core.KEYBDDATA
+--Md = MouseWithoutBorders.Core.MOUSEDATA
--Machine1 = NONE
--Machine2 = NONE
--Machine3 = NONE
@@ -296,7 +230,7 @@ LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
allLogsIndex = 0
lastHour = 0
exceptionCount = 0
-lastPackageSent = MouseWithoutBorders.PackageMonitor
+lastPackageSent = MouseWithoutBorders.Core.PackageMonitor
--Keyboard = 0
--Mouse = 0
--Heartbeat = 0
@@ -311,7 +245,7 @@ lastPackageSent = MouseWithoutBorders.PackageMonitor
--ClipboardAsk = 0
--ExplorerDragDrop = 0
--Nil = 0
-lastPackageReceived = MouseWithoutBorders.PackageMonitor
+lastPackageReceived = MouseWithoutBorders.Core.PackageMonitor
--Keyboard = 0
--Mouse = 0
--Heartbeat = 0
@@ -366,6 +300,42 @@ MAX_SOCKET = 8
HEARTBEAT_TIMEOUT = 1500000
SKIP_PIXELS = 1
JUMP_PIXELS = 2
+[Package]
+===============
+PackageSent = MouseWithoutBorders.Core.PackageMonitor
+--Keyboard = 0
+--Mouse = 0
+--Heartbeat = 0
+--ByeBye = 0
+--Hello = 0
+--Matrix = 0
+--ClipboardText = 0
+--ClipboardImage = 0
+--Clipboard = 0
+--ClipboardDragDrop = 0
+--ClipboardDragDropEnd = 0
+--ClipboardAsk = 0
+--ExplorerDragDrop = 0
+--Nil = 0
+PackageReceived = MouseWithoutBorders.Core.PackageMonitor
+--Keyboard = 0
+--Mouse = 0
+--Heartbeat = 0
+--ByeBye = 0
+--Hello = 0
+--Matrix = 0
+--ClipboardText = 0
+--ClipboardImage = 0
+--Clipboard = 0
+--ClipboardDragDrop = 0
+--ClipboardDragDropEnd = 0
+--ClipboardAsk = 0
+--ExplorerDragDrop = 0
+--Nil = 0
+PackageID = 0
+PACKAGE_SIZE = 32
+PACKAGE_SIZE_EX = 64
+WP_PACKAGE_SIZE = 6
[Receiver]
===============
QUEUE_SIZE = 50
@@ -436,3 +406,41 @@ lastStartServiceTime = ????????????
--MinValue = 01/01/0001 00:00:00
--MaxValue = 31/12/9999 23:59:59
--UnixEpoch = 01/01/1970 00:00:00
+[WinAPI]
+===============
+SensitivePoints = Generic.List`1[Point]
+--_items = Point[]
+----System.Drawing.Point[] = Point[]: N/A
+--_size = 0
+--_version = 0
+--s_emptyArray = Point[]
+----System.Drawing.Point[] = Point[]: N/A
+p = {X=0,Y=0}
+--x = 0
+--y = 0
+--Empty = {X=0,Y=0}
+[WM]
+===============
+KEYEVENTF_KEYDOWN = 1
+KEYEVENTF_KEYUP = 2
+WH_MOUSE = 7
+WH_KEYBOARD = 2
+WH_MOUSE_LL = 14
+WH_KEYBOARD_LL = 13
+WM_MOUSEMOVE = 512
+WM_LBUTTONDOWN = 513
+WM_RBUTTONDOWN = 516
+WM_MBUTTONDOWN = 519
+WM_XBUTTONDOWN = 523
+WM_LBUTTONUP = 514
+WM_RBUTTONUP = 517
+WM_MBUTTONUP = 520
+WM_XBUTTONUP = 524
+WM_LBUTTONDBLCLK = 515
+WM_RBUTTONDBLCLK = 518
+WM_MBUTTONDBLCLK = 521
+WM_MOUSEWHEEL = 522
+WM_KEYDOWN = 256
+WM_KEYUP = 257
+WM_SYSKEYDOWN = 260
+WM_SYSKEYUP = 261
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
index 16c6b4efbc..ea6f5ab7ba 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj
@@ -46,7 +46,7 @@
_DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
Use
- ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)
false
stdcpplatest
@@ -67,7 +67,7 @@
NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
true
Use
- ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu
+ ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)
false
stdcpplatest
@@ -92,6 +92,7 @@
+
@@ -99,7 +100,7 @@
-
+
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h
index 5018653070..711063d0cb 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/pch.h
@@ -3,6 +3,7 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
#define NOMCX
#define NOHELP
#define NOCOMM
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Helpers.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/Helpers.cpp
new file mode 100644
index 0000000000..945a516ca4
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/Helpers.cpp
@@ -0,0 +1,118 @@
+// 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.
+
+#include "pch.h"
+#include "Helpers.h"
+#include
+
+// Minimal subset of PowerRename Helpers used by NewPlus
+// This is a copy from PowerRename main branch to avoid cross-module dependencies
+
+HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime)
+{
+ std::locale::global(std::locale(""));
+ HRESULT hr = E_INVALIDARG;
+ if (source && wcslen(source) > 0)
+ {
+ std::wstring res(source);
+ wchar_t replaceTerm[MAX_PATH] = { 0 };
+ wchar_t formattedDate[MAX_PATH] = { 0 };
+
+ wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
+ if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) == 0)
+ {
+ StringCchCopy(localeName, LOCALE_NAME_MAX_LENGTH, L"en_US");
+ }
+
+ int hour12 = (fileTime.wHour % 12);
+ if (hour12 == 0)
+ {
+ hour12 = 12;
+ }
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%04d"), L"$01", fileTime.wYear);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YYYY"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", (fileTime.wYear % 100));
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$YY"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", (fileTime.wYear % 10));
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$Y"), replaceTerm);
+
+ GetDateFormatEx(localeName, NULL, &fileTime, L"MMMM", formattedDate, MAX_PATH, NULL);
+ formattedDate[0] = towupper(formattedDate[0]);
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMMM"), replaceTerm);
+
+ GetDateFormatEx(localeName, NULL, &fileTime, L"MMM", formattedDate, MAX_PATH, NULL);
+ formattedDate[0] = towupper(formattedDate[0]);
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MMM"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMonth);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$MM"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMonth);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$M"), replaceTerm);
+
+ GetDateFormatEx(localeName, NULL, &fileTime, L"dddd", formattedDate, MAX_PATH, NULL);
+ formattedDate[0] = towupper(formattedDate[0]);
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDDD"), replaceTerm);
+
+ GetDateFormatEx(localeName, NULL, &fileTime, L"ddd", formattedDate, MAX_PATH, NULL);
+ formattedDate[0] = towupper(formattedDate[0]);
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", formattedDate);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DDD"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wDay);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$DD"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wDay);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$D"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", hour12);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$HH"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", hour12);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$H"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"AM" : L"PM");
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$TT"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%s"), L"$01", (fileTime.wHour < 12) ? L"am" : L"pm");
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$tt"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wHour);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$hh"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wHour);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$h"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMinute);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$mm"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMinute);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$m"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wSecond);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ss"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wSecond);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$s"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%03d"), L"$01", fileTime.wMilliseconds);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$fff"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMilliseconds / 10);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ff"), replaceTerm);
+
+ StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMilliseconds / 100);
+ res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$f"), replaceTerm);
+
+ hr = StringCchCopy(result, cchMax, res.c_str());
+ }
+
+ return hr;
+}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/Helpers.h b/src/modules/NewPlus/NewShellExtensionContextMenu/Helpers.h
new file mode 100644
index 0000000000..540478856e
--- /dev/null
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/Helpers.h
@@ -0,0 +1,9 @@
+// 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.
+
+#pragma once
+
+// Minimal subset of PowerRename Helpers used by NewPlus
+// This is a copy from PowerRename's main branch to avoid cross-module dependencies
+HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SYSTEMTIME fileTime);
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
index 90058a503e..7dade586e7 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj
@@ -114,6 +114,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv
+
@@ -131,7 +132,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv
-
+
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/helpers_variables.h b/src/modules/NewPlus/NewShellExtensionContextMenu/helpers_variables.h
index 63f23c3e86..1d511f2afe 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu/helpers_variables.h
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/helpers_variables.h
@@ -1,7 +1,7 @@
#pragma once
#include
-#include "..\..\powerrename\lib\Helpers.h"
+#include "Helpers.h"
#include "helpers_filesystem.h"
#pragma comment(lib, "Pathcch.lib")
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h
index 50f92562d2..02874ab8f3 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h
@@ -302,9 +302,9 @@ namespace newplus::utilities
POINT mouse_position;
GetCursorPos(&mouse_position);
mouse_position.x -= GetSystemMetrics(SM_CXMENUSIZE);
- mouse_position.x = max(mouse_position.x, 20);
+ mouse_position.x = (std::max)(mouse_position.x, 20L);
mouse_position.y -= GetSystemMetrics(SM_CXMENUSIZE)/2;
- mouse_position.y = max(mouse_position.y, 20);
+ mouse_position.y = (std::max)(mouse_position.y, 20L);
POINT position[] = { mouse_position };
folder_view->SelectAndPositionItems(1, shell_item_to_select_and_position, position, common_select_flags | SVSI_POSITIONITEM);
}
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/pch.h b/src/modules/NewPlus/NewShellExtensionContextMenu/pch.h
index b766a837d5..13093e1d08 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu/pch.h
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/pch.h
@@ -3,6 +3,7 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
#define NOMCX
#define NOHELP
#define NOCOMM
@@ -13,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp
index a7ddfe835f..a178e00195 100644
--- a/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp
+++ b/src/modules/NewPlus/NewShellExtensionContextMenu/template_item.cpp
@@ -60,8 +60,8 @@ std::wstring template_item::get_target_filename(const bool include_starting_digi
std::wstring template_item::remove_starting_digits_from_filename(std::wstring filename) const
{
- filename.erase(0, min(filename.find_first_not_of(L"0123456789"), filename.size()));
- filename.erase(0, min(filename.find_first_not_of(L" ."), filename.size()));
+ filename.erase(0, std::min(filename.find_first_not_of(L"0123456789"), filename.size()));
+ filename.erase(0, std::min(filename.find_first_not_of(L" ."), filename.size()));
return filename;
}
diff --git a/src/modules/PowerOCR/PowerOCR-UITests/PowerOCRTests.cs b/src/modules/PowerOCR/PowerOCR-UITests/PowerOCRTests.cs
index 926729542a..18702eaaf6 100644
--- a/src/modules/PowerOCR/PowerOCR-UITests/PowerOCRTests.cs
+++ b/src/modules/PowerOCR/PowerOCR-UITests/PowerOCRTests.cs
@@ -2,8 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using Microsoft.PowerToys.UITest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using OpenQA.Selenium.Interactions;
using static Microsoft.PowerToys.UITest.UITestBase;
namespace PowerOCR.UITests;
@@ -19,41 +21,274 @@ public class PowerOCRTests : UITestBase
[TestInitialize]
public void TestInitialize()
{
- if (FindAll("Text Extractor").Count == 0)
+ if (FindAll(By.AccessibilityId("TextExtractorNavItem")).Count == 0)
{
- // Expand Advanced list-group if needed
- Find("System Tools").Click();
+ // Expand System Tools list-group if needed
+ Find(By.AccessibilityId("SystemToolsNavItem")).Click();
}
- Find("Text Extractor").Click();
+ Find(By.AccessibilityId("TextExtractorNavItem")).Click();
- Find("Enable Text Extractor").Toggle(true);
+ Find(By.AccessibilityId("EnableTextExtractorToggleSwitch")).Toggle(true);
- SendKeys(Key.Win, Key.D);
+ // Reset activation shortcut to default (Win+Shift+T)
+ var shortcutControl = Find(By.AccessibilityId("TextExtractorActivationShortcut"), 5000);
+ if (shortcutControl != null)
+ {
+ shortcutControl.Click();
+ Thread.Sleep(500);
+
+ // Set default shortcut Win+Shift+T
+ SendKeys(Key.Win, Key.Shift, Key.T);
+ Thread.Sleep(1000);
+
+ // Click Save to confirm
+ var saveButton = Find(By.Name("Save"), 3000);
+ if (saveButton != null)
+ {
+ saveButton.Click();
+ Thread.Sleep(1000);
+ }
+ }
}
[TestMethod("PowerOCR.DetectTextExtractor")]
[TestCategory("PowerOCR Detection")]
public void DetectTextExtractorTest()
{
- try
+ // Step 1: Press the activation shortcut and verify the overlay appears
+ SendKeys(Key.Win, Key.Shift, Key.T);
+ var textExtractorWindow = Find(By.AccessibilityId("TextExtractorWindow"), 10000, true);
+ Assert.IsNotNull(textExtractorWindow, "TextExtractor window should be found after hotkey activation");
+
+ // Step 2: Press Escape and verify the overlay disappears
+ SendKeys(Key.Esc);
+ Thread.Sleep(3000);
+
+ var windowsAfterEscape = FindAll(By.AccessibilityId("TextExtractorWindow"), 2000, true);
+ Assert.AreEqual(0, windowsAfterEscape.Count, "TextExtractor window should be dismissed after pressing Escape");
+
+ // Step 3: Press the activation shortcut again and verify the overlay appears
+ SendKeys(Key.Win, Key.Shift, Key.T);
+
+ textExtractorWindow = Find(By.AccessibilityId("TextExtractorWindow"), 10000, true);
+ Assert.IsNotNull(textExtractorWindow, "TextExtractor window should appear again after hotkey activation");
+
+ // Step 4: Right-click and select Cancel. Verify the overlay disappears
+ textExtractorWindow.Click(rightClick: true);
+ Thread.Sleep(500);
+
+ // Look for Cancel menu item using its AutomationId
+ var cancelMenuItem = Find(By.AccessibilityId("CancelMenuItem"), 3000, true);
+ Assert.IsNotNull(cancelMenuItem, "Cancel menu item should be available in context menu");
+
+ cancelMenuItem.Click();
+ Thread.Sleep(3000);
+
+ var windowsAfterCancel = FindAll(By.AccessibilityId("TextExtractorWindow"), 2000, true);
+ Assert.AreEqual(0, windowsAfterCancel.Count, "TextExtractor window should be dismissed after clicking Cancel");
+ }
+
+ [TestMethod("PowerOCR.DisableTextExtractorTest")]
+ [TestCategory("PowerOCR Settings")]
+ public void DisableTextExtractorTest()
+ {
+ Find(By.AccessibilityId("EnableTextExtractorToggleSwitch")).Toggle(false);
+
+ SendKeys(Key.Win, Key.Shift, Key.T);
+
+ // Verify that no TextExtractor window appears
+ var windowsWhenDisabled = FindAll(By.AccessibilityId("TextExtractorWindow"), 10000, true);
+ Assert.AreEqual(0, windowsWhenDisabled.Count, "TextExtractor window should not appear when the utility is disabled");
+ }
+
+ [TestMethod("PowerOCR.ActivationShortcutSettingsTest")]
+ [TestCategory("PowerOCR Settings")]
+ public void ActivationShortcutSettingsTest()
+ {
+ // Find the activation shortcut control
+ var shortcutControl = Find(By.AccessibilityId("TextExtractorActivationShortcut"), 5000);
+ Assert.IsNotNull(shortcutControl, "Activation shortcut control should be found");
+
+ // Click to focus the shortcut control
+ shortcutControl.Click();
+ Thread.Sleep(500);
+
+ // Test changing the shortcut to Ctrl+Shift+T
+ SendKeys(Key.Ctrl, Key.Shift, Key.T);
+ Thread.Sleep(1000);
+
+ // Click the Save button to confirm the shortcut change
+ var saveButton = Find(By.Name("Save"), 3000);
+ Assert.IsNotNull(saveButton, "Save button should be found in the shortcut dialog");
+ saveButton.Click();
+ Thread.Sleep(1000);
+
+ // Test the new shortcut
+ SendKeys(Key.Ctrl, Key.Shift, Key.T);
+ Thread.Sleep(3000);
+
+ var textExtractorWindow = FindAll(By.AccessibilityId("TextExtractorWindow"), 3000, true);
+ Assert.IsTrue(textExtractorWindow.Count > 0, "TextExtractor should activate with new shortcut Ctrl+Shift+T");
+ }
+
+ [TestMethod("PowerOCR.OCRLanguageSettingsTest")]
+ [TestCategory("PowerOCR Settings")]
+ public void OCRLanguageSettingsTest()
+ {
+ // Find the language combo box
+ var languageComboBox = Find(By.AccessibilityId("TextExtractorLanguageComboBox"), 5000);
+ Assert.IsNotNull(languageComboBox, "Language combo box should be found");
+
+ // Click to open the dropdown
+ languageComboBox.Click();
+
+ // Verify dropdown is opened by checking if dropdown items are available
+ var dropdownItems = FindAll(By.ClassName("ComboBoxItem"), 2000);
+ Assert.IsTrue(dropdownItems.Count > 0, "Dropdown should contain language options");
+
+ // Close dropdown by pressing Escape
+ SendKeys(Key.Esc);
+ }
+
+ [TestMethod("PowerOCR.OCRLanguageSelectionTest")]
+ [TestCategory("PowerOCR Language")]
+ public void OCRLanguageSelectionTest()
+ {
+ // Activate Text Extractor overlay
+ SendKeys(Key.Win, Key.Shift, Key.T);
+ Thread.Sleep(3000);
+
+ var textExtractorWindow = Find(By.AccessibilityId("TextExtractorWindow"), 10000, true);
+ Assert.IsNotNull(textExtractorWindow, "TextExtractor window should be found after hotkey activation");
+
+ // Right-click on the canvas to open context menu
+ textExtractorWindow.Click(rightClick: true);
+
+ // Look for language options that should appear after Cancel menu item
+ var allMenuItems = FindAll(By.ClassName("MenuItem"), 2000, true);
+ if (allMenuItems.Count > 4)
{
- SendKeys(Key.Win, Key.Shift, Key.T);
+ // Find the Cancel menu item first
+ Element? cancelItem = null;
+ int cancelIndex = -1;
+ for (int i = 0; i < allMenuItems.Count; i++)
+ {
+ if (allMenuItems[i].GetAttribute("AutomationId") == "CancelMenuItem")
+ {
+ cancelItem = allMenuItems[i];
+ cancelIndex = i;
+ break;
+ }
+ }
- Thread.Sleep(5000);
+ Assert.IsNotNull(cancelItem, "Cancel menu item should be found");
- var textExtractorWindow = Find("TextExtractor", 10000, true);
+ // Look for language options after Cancel menu item
+ if (cancelIndex >= 0 && cancelIndex < allMenuItems.Count - 1)
+ {
+ // Select the first language option after Cancel
+ var languageOption = allMenuItems[cancelIndex + 1];
+ languageOption.Click();
+ Thread.Sleep(1000);
- Assert.IsNotNull(textExtractorWindow, "TextExtractor window should be found after hotkey activation");
-
- Console.WriteLine("✓ TextExtractor window detected successfully after hotkey activation");
-
- SendKeys(Key.Esc);
+ Assert.IsTrue(true, "Language selection changed successfully through right-click menu");
+ }
}
- catch (Exception ex)
+
+ // Close the TextExtractor overlay
+ SendKeys(Key.Esc);
+ Thread.Sleep(1000);
+ }
+
+ [TestMethod("PowerOCR.TextSelectionAndClipboardTest")]
+ [TestCategory("PowerOCR Selection")]
+ public void TextSelectionAndClipboardTest()
+ {
+ // Clear clipboard first using STA thread
+ ClearClipboardSafely();
+ Thread.Sleep(500);
+
+ // Activate Text Extractor overlay
+ SendKeys(Key.Win, Key.Shift, Key.T);
+ Thread.Sleep(3000);
+
+ var textExtractorWindow = Find(By.AccessibilityId("TextExtractorWindow"), 10000, true);
+ Assert.IsNotNull(textExtractorWindow, "TextExtractor window should be found after hotkey activation");
+
+ // Click on the TextExtractor window to position cursor
+ textExtractorWindow.Click();
+ Thread.Sleep(500);
+
+ // Get screen dimensions for full screen selection
+ var primaryScreen = System.Windows.Forms.Screen.PrimaryScreen;
+ Assert.IsNotNull(primaryScreen, "Primary screen should be available");
+
+ var screenWidth = primaryScreen.Bounds.Width;
+ var screenHeight = primaryScreen.Bounds.Height;
+
+ // Define full screen selection area
+ var startX = 0;
+ var startY = 0;
+ var endX = screenWidth;
+ var endY = screenHeight;
+
+ // Perform continuous mouse drag to select entire screen
+ PerformSeleniumDrag(startX, startY, endX, endY);
+ Thread.Sleep(3000); // Wait longer for full screen OCR processing
+
+ // Verify text was copied to clipboard using STA thread
+ var clipboardText = GetClipboardTextSafely();
+
+ Assert.IsFalse(string.IsNullOrWhiteSpace(clipboardText), "Clipboard should contain extracted text after selection");
+
+ // Close the TextExtractor overlay
+ SendKeys(Key.Esc);
+ Thread.Sleep(1000);
+ }
+
+ private static void ClearClipboardSafely()
+ {
+ var thread = new System.Threading.Thread(() =>
{
- Console.WriteLine($"Failed to detect TextExtractor window: {ex.Message}");
- Assert.Fail("TextExtractor window was not found after hotkey activation");
- }
+ System.Windows.Forms.Clipboard.Clear();
+ });
+ thread.SetApartmentState(System.Threading.ApartmentState.STA);
+ thread.Start();
+ thread.Join();
+ }
+
+ private static string GetClipboardTextSafely()
+ {
+ string result = string.Empty;
+ var thread = new System.Threading.Thread(() =>
+ {
+ try
+ {
+ result = System.Windows.Forms.Clipboard.GetText();
+ }
+ catch (Exception)
+ {
+ result = string.Empty;
+ }
+ });
+ thread.SetApartmentState(System.Threading.ApartmentState.STA);
+ thread.Start();
+ thread.Join();
+ return result;
+ }
+
+ private void PerformSeleniumDrag(int startX, int startY, int endX, int endY)
+ {
+ // Use Selenium Actions for proper drag and drop operation
+ var actions = new Actions(Session.Root);
+
+ // Move to start position, click and hold, drag to end position, then release
+ actions.MoveByOffset(startX, startY)
+ .ClickAndHold()
+ .MoveByOffset(endX - startX, endY - startY)
+ .Release()
+ .Build()
+ .Perform();
}
}
diff --git a/src/modules/PowerOCR/PowerOCR-UITests/tests-checklist-text-extractor.md b/src/modules/PowerOCR/PowerOCR-UITests/tests-checklist-text-extractor.md
new file mode 100644
index 0000000000..5a1080e21b
--- /dev/null
+++ b/src/modules/PowerOCR/PowerOCR-UITests/tests-checklist-text-extractor.md
@@ -0,0 +1,15 @@
+## Text Extractor
+ * Enable Text Extractor. Then:
+ - [x] Press the activation shortcut and verify the overlay appears.
+ - [x] Press Escape and verify the overlay disappears.
+ - [x] Press the activation shortcut and verify the overlay appears.
+ - [x] Right-click and select Cancel. Verify the overlay disappears.
+ - [x] Disable Text Extractor and verify that the activation shortcut no longer activates the utility.
+ * With Text Extractor enabled and activated:
+ - [x] Try to select text and verify it is copied to the clipboard.
+ - [x] Try to select a different OCR language by right-clicking and verify the change is applied.
+ * In a multi-monitor setup with different DPIs on each monitor:
+ - [ ] Verify text is correctly captured on all monitors.
+ * Test the different settings and verify they are applied:
+ - [x] Activation shortcut
+ - [x] OCR Language
\ No newline at end of file
diff --git a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml
index f28d642b6b..4c2a5868c5 100644
--- a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml
+++ b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml
@@ -6,6 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:PowerOCR.Properties"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
+ x:Name="TextExtractorWindow"
Title="TextExtractor"
ui:Design.Background="Transparent"
AllowsTransparency="True"
@@ -87,6 +88,7 @@
@@ -117,6 +119,7 @@
diff --git a/src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
new file mode 100644
index 0000000000..22e2079f71
--- /dev/null
+++ b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
@@ -0,0 +1,548 @@
+//==============================================================================
+//
+// Zoomit
+// Sysinternals - www.sysinternals.com
+//
+// GIF recording support using Windows Imaging Component (WIC)
+//
+//==============================================================================
+#include "pch.h"
+#include "GifRecordingSession.h"
+#include "CaptureFrameWait.h"
+#include
+
+extern DWORD g_RecordScaling;
+
+namespace winrt
+{
+ using namespace Windows::Foundation;
+ using namespace Windows::Graphics;
+ using namespace Windows::Graphics::Capture;
+ using namespace Windows::Graphics::DirectX;
+ using namespace Windows::Graphics::DirectX::Direct3D11;
+ using namespace Windows::Storage;
+ using namespace Windows::UI::Composition;
+}
+
+namespace util
+{
+ using namespace robmikh::common::uwp;
+}
+
+const float CLEAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f };
+
+int32_t EnsureEvenGif(int32_t value)
+{
+ if (value % 2 == 0)
+ {
+ return value;
+ }
+ else
+ {
+ return value + 1;
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::GifRecordingSession
+//
+//----------------------------------------------------------------------------
+GifRecordingSession::GifRecordingSession(
+ winrt::IDirect3DDevice const& device,
+ winrt::GraphicsCaptureItem const& item,
+ RECT const cropRect,
+ uint32_t frameRate,
+ winrt::Streams::IRandomAccessStream const& stream)
+{
+ m_device = device;
+ m_d3dDevice = GetDXGIInterfaceFromObject(m_device);
+ m_d3dDevice->GetImmediateContext(m_d3dContext.put());
+ m_item = item;
+ m_frameRate = frameRate;
+ m_stream = stream;
+
+ auto itemSize = item.Size();
+ auto inputWidth = EnsureEvenGif(itemSize.Width);
+ auto inputHeight = EnsureEvenGif(itemSize.Height);
+ m_frameWait = std::make_shared(m_device, m_item, winrt::SizeInt32{ inputWidth, inputHeight });
+ auto weakPointer{ std::weak_ptr{ m_frameWait } };
+ m_itemClosed = item.Closed(winrt::auto_revoke, [weakPointer](auto&, auto&)
+ {
+ auto sharedPointer{ weakPointer.lock() };
+ if (sharedPointer)
+ {
+ sharedPointer->StopCapture();
+ }
+ });
+
+ // Get crop dimension
+ if ((cropRect.right - cropRect.left) != 0)
+ {
+ m_rcCrop = cropRect;
+ m_frameWait->ShowCaptureBorder(false);
+ }
+ else
+ {
+ m_rcCrop.left = 0;
+ m_rcCrop.top = 0;
+ m_rcCrop.right = inputWidth;
+ m_rcCrop.bottom = inputHeight;
+ }
+
+ // Apply scaling
+ constexpr int c_minimumSize = 34;
+ auto scaledWidth = MulDiv(m_rcCrop.right - m_rcCrop.left, g_RecordScaling, 100);
+ auto scaledHeight = MulDiv(m_rcCrop.bottom - m_rcCrop.top, g_RecordScaling, 100);
+ m_width = scaledWidth;
+ m_height = scaledHeight;
+ if (m_width < c_minimumSize)
+ {
+ m_width = c_minimumSize;
+ m_height = MulDiv(m_height, m_width, scaledWidth);
+ }
+ if (m_height < c_minimumSize)
+ {
+ m_height = c_minimumSize;
+ m_width = MulDiv(m_width, m_height, scaledHeight);
+ }
+ if (m_width > inputWidth)
+ {
+ m_width = inputWidth;
+ m_height = c_minimumSize, MulDiv(m_height, scaledWidth, m_width);
+ }
+ if (m_height > inputHeight)
+ {
+ m_height = inputHeight;
+ m_width = c_minimumSize, MulDiv(m_width, scaledHeight, m_height);
+ }
+ m_width = EnsureEvenGif(m_width);
+ m_height = EnsureEvenGif(m_height);
+
+ m_frameDelay = (frameRate > 0) ? (100 / frameRate) : 15;
+
+ // Initialize WIC
+ winrt::check_hresult(CoCreateInstance(
+ CLSID_WICImagingFactory,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(m_wicFactory.put())));
+
+ // Create WIC stream from IRandomAccessStream
+ winrt::check_hresult(m_wicFactory->CreateStream(m_wicStream.put()));
+
+ // Get the IStream from the IRandomAccessStream
+ winrt::com_ptr streamInterop;
+ winrt::check_hresult(CreateStreamOverRandomAccessStream(
+ winrt::get_unknown(stream),
+ IID_PPV_ARGS(streamInterop.put())));
+ winrt::check_hresult(m_wicStream->InitializeFromIStream(streamInterop.get()));
+
+ // Create GIF encoder
+ winrt::check_hresult(m_wicFactory->CreateEncoder(
+ GUID_ContainerFormatGif,
+ nullptr,
+ m_gifEncoder.put()));
+
+ winrt::check_hresult(m_gifEncoder->Initialize(m_wicStream.get(), WICBitmapEncoderNoCache));
+
+ // Set global GIF metadata for looping (NETSCAPE2.0 application extension)
+ try
+ {
+ winrt::com_ptr encoderMetadataWriter;
+ if (SUCCEEDED(m_gifEncoder->GetMetadataQueryWriter(encoderMetadataWriter.put())) && encoderMetadataWriter)
+ {
+ OutputDebugStringW(L"Setting NETSCAPE2.0 looping extension on encoder...\n");
+
+ // Set application extension
+ PROPVARIANT propValue;
+ PropVariantInit(&propValue);
+ propValue.vt = VT_UI1 | VT_VECTOR;
+ propValue.caub.cElems = 11;
+ propValue.caub.pElems = static_cast(CoTaskMemAlloc(11));
+ if (propValue.caub.pElems != nullptr)
+ {
+ memcpy(propValue.caub.pElems, "NETSCAPE2.0", 11);
+ HRESULT hr = encoderMetadataWriter->SetMetadataByName(L"/appext/application", &propValue);
+ if (SUCCEEDED(hr))
+ {
+ OutputDebugStringW(L"Encoder application extension set successfully\n");
+ }
+ else
+ {
+ OutputDebugStringW(L"Failed to set encoder application extension\n");
+ }
+ PropVariantClear(&propValue);
+
+ // Set loop count (0 = infinite)
+ PropVariantInit(&propValue);
+ propValue.vt = VT_UI1 | VT_VECTOR;
+ propValue.caub.cElems = 5;
+ propValue.caub.pElems = static_cast(CoTaskMemAlloc(5));
+ if (propValue.caub.pElems != nullptr)
+ {
+ propValue.caub.pElems[0] = 3;
+ propValue.caub.pElems[1] = 1;
+ propValue.caub.pElems[2] = 0;
+ propValue.caub.pElems[3] = 0;
+ propValue.caub.pElems[4] = 0;
+ hr = encoderMetadataWriter->SetMetadataByName(L"/appext/data", &propValue);
+ if (SUCCEEDED(hr))
+ {
+ OutputDebugStringW(L"Encoder loop count set successfully\n");
+ }
+ else
+ {
+ OutputDebugStringW(L"Failed to set encoder loop count\n");
+ }
+ PropVariantClear(&propValue);
+ }
+ }
+ }
+ else
+ {
+ OutputDebugStringW(L"Failed to get encoder metadata writer\n");
+ }
+ }
+ catch (...)
+ {
+ OutputDebugStringW(L"Warning: Failed to set GIF encoder looping metadata\n");
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::~GifRecordingSession
+//
+//----------------------------------------------------------------------------
+GifRecordingSession::~GifRecordingSession()
+{
+ Close();
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::Create
+//
+//----------------------------------------------------------------------------
+std::shared_ptr GifRecordingSession::Create(
+ winrt::IDirect3DDevice const& device,
+ winrt::GraphicsCaptureItem const& item,
+ RECT const& crop,
+ uint32_t frameRate,
+ winrt::Streams::IRandomAccessStream const& stream)
+{
+ return std::shared_ptr(new GifRecordingSession(device, item, crop, frameRate, stream));
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::EncodeFrame
+//
+//----------------------------------------------------------------------------
+HRESULT GifRecordingSession::EncodeFrame(ID3D11Texture2D* frameTexture)
+{
+ try
+ {
+ // Create a staging texture for CPU access
+ D3D11_TEXTURE2D_DESC frameDesc;
+ frameTexture->GetDesc(&frameDesc);
+
+ // GIF encoding with palette generation is VERY slow at high resolutions (4K takes 1 second per frame!)
+
+ UINT targetWidth = frameDesc.Width;
+ UINT targetHeight = frameDesc.Height;
+
+ if (frameDesc.Width > static_cast(m_width) || frameDesc.Height > static_cast(m_height))
+ {
+ float scaleX = static_cast(m_width) / frameDesc.Width;
+ float scaleY = static_cast(m_height) / frameDesc.Height;
+ float scale = min(scaleX, scaleY);
+
+ targetWidth = static_cast(frameDesc.Width * scale);
+ targetHeight = static_cast(frameDesc.Height * scale);
+
+ // Ensure even dimensions for GIF
+ targetWidth = (targetWidth / 2) * 2;
+ targetHeight = (targetHeight / 2) * 2;
+ }
+
+ D3D11_TEXTURE2D_DESC stagingDesc = frameDesc;
+ stagingDesc.Usage = D3D11_USAGE_STAGING;
+ stagingDesc.BindFlags = 0;
+ stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ stagingDesc.MiscFlags = 0;
+
+ winrt::com_ptr stagingTexture;
+ winrt::check_hresult(m_d3dDevice->CreateTexture2D(&stagingDesc, nullptr, stagingTexture.put()));
+
+ // Copy the frame to staging texture
+ m_d3dContext->CopyResource(stagingTexture.get(), frameTexture);
+
+ // Map the staging texture
+ D3D11_MAPPED_SUBRESOURCE mappedResource;
+ winrt::check_hresult(m_d3dContext->Map(stagingTexture.get(), 0, D3D11_MAP_READ, 0, &mappedResource));
+
+ // Create a new frame in the GIF
+ winrt::com_ptr frameEncode;
+ winrt::com_ptr propertyBag;
+ winrt::check_hresult(m_gifEncoder->CreateNewFrame(frameEncode.put(), propertyBag.put()));
+
+ // Initialize the frame encoder with property bag
+ winrt::check_hresult(frameEncode->Initialize(propertyBag.get()));
+
+ // CRITICAL: For GIF, we MUST set size and pixel format BEFORE WriteSource
+ // Use target dimensions (may be downsampled)
+ winrt::check_hresult(frameEncode->SetSize(targetWidth, targetHeight));
+
+ // Set the pixel format to 8-bit indexed (required for GIF)
+ WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppIndexed;
+ winrt::check_hresult(frameEncode->SetPixelFormat(&pixelFormat));
+
+ // Create a WIC bitmap from the BGRA texture data
+ winrt::com_ptr sourceBitmap;
+ winrt::check_hresult(m_wicFactory->CreateBitmapFromMemory(
+ frameDesc.Width,
+ frameDesc.Height,
+ GUID_WICPixelFormat32bppBGRA,
+ mappedResource.RowPitch,
+ frameDesc.Height * mappedResource.RowPitch,
+ static_cast(mappedResource.pData),
+ sourceBitmap.put()));
+
+ // If we need downsampling, use WIC scaler
+ winrt::com_ptr finalSource = sourceBitmap;
+ if (targetWidth != frameDesc.Width || targetHeight != frameDesc.Height)
+ {
+ winrt::com_ptr scaler;
+ winrt::check_hresult(m_wicFactory->CreateBitmapScaler(scaler.put()));
+ winrt::check_hresult(scaler->Initialize(
+ sourceBitmap.get(),
+ targetWidth,
+ targetHeight,
+ WICBitmapInterpolationModeHighQualityCubic));
+ finalSource = scaler;
+
+ OutputDebugStringW((L"Downsampled from " + std::to_wstring(frameDesc.Width) + L"x" + std::to_wstring(frameDesc.Height) +
+ L" to " + std::to_wstring(targetWidth) + L"x" + std::to_wstring(targetHeight) + L"\n").c_str());
+ }
+
+ // Use WriteSource - WIC will handle the BGRA to 8bpp indexed conversion
+ winrt::check_hresult(frameEncode->WriteSource(finalSource.get(), nullptr));
+
+ try
+ {
+ winrt::com_ptr frameMetadataWriter;
+ if (SUCCEEDED(frameEncode->GetMetadataQueryWriter(frameMetadataWriter.put())) && frameMetadataWriter)
+ {
+ // Set the frame delay in the metadata (in hundredths of a second)
+ PROPVARIANT propValue;
+ PropVariantInit(&propValue);
+ propValue.vt = VT_UI2;
+ propValue.uiVal = static_cast(m_frameDelay);
+ frameMetadataWriter->SetMetadataByName(L"/grctlext/Delay", &propValue);
+ PropVariantClear(&propValue);
+
+ // Set disposal method (2 = restore to background, needed for animation)
+ PropVariantInit(&propValue);
+ propValue.vt = VT_UI1;
+ propValue.bVal = 2; // Disposal method: restore to background color
+ frameMetadataWriter->SetMetadataByName(L"/grctlext/Disposal", &propValue);
+ PropVariantClear(&propValue);
+ }
+ }
+ catch (...)
+ {
+ // Metadata setting failed, continue anyway
+ OutputDebugStringW(L"Warning: Failed to set GIF frame metadata\n");
+ }
+
+ // Commit the frame
+ OutputDebugStringW(L"About to commit frame to encoder...\n");
+ winrt::check_hresult(frameEncode->Commit());
+ OutputDebugStringW(L"Frame committed successfully\n");
+
+ // Unmap the staging texture
+ m_d3dContext->Unmap(stagingTexture.get(), 0);
+
+ // Increment and log frame count
+ m_frameCount++;
+ OutputDebugStringW((L"GIF Frame #" + std::to_wstring(m_frameCount) + L" fully encoded and committed\n").c_str());
+
+ return S_OK;
+ }
+ catch (const winrt::hresult_error& error)
+ {
+ OutputDebugStringW(error.message().c_str());
+ return error.code();
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::StartAsync
+//
+//----------------------------------------------------------------------------
+winrt::IAsyncAction GifRecordingSession::StartAsync()
+{
+ auto expected = false;
+ if (m_isRecording.compare_exchange_strong(expected, true))
+ {
+ auto self = shared_from_this();
+
+ try
+ {
+ // Start capturing frames
+ auto frameStartTime = std::chrono::high_resolution_clock::now();
+ int captureAttempts = 0;
+ int successfulCaptures = 0;
+ int duplicatedFrames = 0;
+
+ // Keep track of the last frame to duplicate when needed
+ winrt::com_ptr lastCroppedTexture;
+
+ while (m_isRecording && !m_closed)
+ {
+ captureAttempts++;
+ auto frame = m_frameWait->TryGetNextFrame();
+
+ winrt::com_ptr croppedTexture;
+
+ if (frame)
+ {
+ successfulCaptures++;
+ auto contentSize = frame->ContentSize;
+ auto frameTexture = GetDXGIInterfaceFromObject(frame->FrameTexture);
+ D3D11_TEXTURE2D_DESC desc = {};
+ frameTexture->GetDesc(&desc);
+
+ // Use the smaller of the crop size or content size
+ auto width = min(m_rcCrop.right - m_rcCrop.left, contentSize.Width);
+ auto height = min(m_rcCrop.bottom - m_rcCrop.top, contentSize.Height);
+
+ D3D11_TEXTURE2D_DESC croppedDesc = {};
+ croppedDesc.Width = width;
+ croppedDesc.Height = height;
+ croppedDesc.MipLevels = 1;
+ croppedDesc.ArraySize = 1;
+ croppedDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ croppedDesc.SampleDesc.Count = 1;
+ croppedDesc.Usage = D3D11_USAGE_DEFAULT;
+ croppedDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+ winrt::check_hresult(m_d3dDevice->CreateTexture2D(&croppedDesc, nullptr, croppedTexture.put()));
+
+ // Set the content region to copy and clamp the coordinates
+ D3D11_BOX region = {};
+ region.left = std::clamp(m_rcCrop.left, static_cast(0), static_cast(desc.Width));
+ region.right = std::clamp(m_rcCrop.left + width, static_cast(0), static_cast(desc.Width));
+ region.top = std::clamp(m_rcCrop.top, static_cast(0), static_cast(desc.Height));
+ region.bottom = std::clamp(m_rcCrop.top + height, static_cast(0), static_cast(desc.Height));
+ region.back = 1;
+
+ // Copy the cropped region
+ m_d3dContext->CopySubresourceRegion(
+ croppedTexture.get(),
+ 0,
+ 0, 0, 0,
+ frameTexture.get(),
+ 0,
+ ®ion);
+
+ // Save this as the last frame for duplication
+ lastCroppedTexture = croppedTexture;
+ }
+ else if (lastCroppedTexture)
+ {
+ // No new frame, duplicate the last one
+ duplicatedFrames++;
+ croppedTexture = lastCroppedTexture;
+ }
+
+ // Encode the frame (either new or duplicated)
+ if (croppedTexture)
+ {
+ HRESULT hr = EncodeFrame(croppedTexture.get());
+ if (FAILED(hr))
+ {
+ CloseInternal();
+ break;
+ }
+ }
+
+ // Wait for the next frame interval
+ co_await winrt::resume_after(std::chrono::milliseconds(1000 / m_frameRate));
+ }
+
+ // Commit the GIF encoder
+ if (m_gifEncoder)
+ {
+ auto frameEndTime = std::chrono::high_resolution_clock::now();
+ auto duration = std::chrono::duration_cast(frameEndTime - frameStartTime).count();
+
+ OutputDebugStringW(L"Recording stopped. Committing GIF encoder...\n");
+ OutputDebugStringW((L"Total frames captured: " + std::to_wstring(m_frameCount) + L"\n").c_str());
+ OutputDebugStringW((L"Capture attempts: " + std::to_wstring(captureAttempts) + L"\n").c_str());
+ OutputDebugStringW((L"Successful captures: " + std::to_wstring(successfulCaptures) + L"\n").c_str());
+ OutputDebugStringW((L"Duplicated frames: " + std::to_wstring(duplicatedFrames) + L"\n").c_str());
+ OutputDebugStringW((L"Recording duration: " + std::to_wstring(duration) + L"ms\n").c_str());
+ OutputDebugStringW((L"Actual FPS: " + std::to_wstring(m_frameCount * 1000.0 / duration) + L"\n").c_str());
+
+ winrt::check_hresult(m_gifEncoder->Commit());
+ OutputDebugStringW(L"GIF encoder committed successfully\n");
+ }
+ }
+ catch (const winrt::hresult_error& error)
+ {
+ OutputDebugStringW(L"Error in GIF recording: ");
+ OutputDebugStringW(error.message().c_str());
+ OutputDebugStringW(L"\n");
+
+ // Try to commit the encoder even on error
+ if (m_gifEncoder)
+ {
+ try
+ {
+ m_gifEncoder->Commit();
+ }
+ catch (...) {}
+ }
+
+ CloseInternal();
+ }
+ }
+ co_return;
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::Close
+//
+//----------------------------------------------------------------------------
+void GifRecordingSession::Close()
+{
+ auto expected = false;
+ if (m_closed.compare_exchange_strong(expected, true))
+ {
+ expected = true;
+ if (!m_isRecording.compare_exchange_strong(expected, false))
+ {
+ CloseInternal();
+ }
+ else
+ {
+ m_frameWait->StopCapture();
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+//
+// GifRecordingSession::CloseInternal
+//
+//----------------------------------------------------------------------------
+void GifRecordingSession::CloseInternal()
+{
+ m_frameWait->StopCapture();
+ m_itemClosed.revoke();
+}
diff --git a/src/modules/ZoomIt/ZoomIt/GifRecordingSession.h b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
new file mode 100644
index 0000000000..90732f60f3
--- /dev/null
+++ b/src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
@@ -0,0 +1,69 @@
+//==============================================================================
+//
+// Zoomit
+// Sysinternals - www.sysinternals.com
+//
+// GIF recording support using Windows Imaging Component (WIC)
+//
+//==============================================================================
+#pragma once
+
+#include "CaptureFrameWait.h"
+#include
+#include
+
+class GifRecordingSession : public std::enable_shared_from_this
+{
+public:
+ [[nodiscard]] static std::shared_ptr Create(
+ winrt::Direct3D11::IDirect3DDevice const& device,
+ winrt::GraphicsCaptureItem const& item,
+ RECT const& cropRect,
+ uint32_t frameRate,
+ winrt::Streams::IRandomAccessStream const& stream);
+ ~GifRecordingSession();
+
+ winrt::IAsyncAction StartAsync();
+ void EnableCursorCapture(bool enable = true) { m_frameWait->EnableCursorCapture(enable); }
+ void Close();
+
+private:
+ GifRecordingSession(
+ winrt::Direct3D11::IDirect3DDevice const& device,
+ winrt::Capture::GraphicsCaptureItem const& item,
+ RECT const cropRect,
+ uint32_t frameRate,
+ winrt::Streams::IRandomAccessStream const& stream);
+ void CloseInternal();
+ HRESULT EncodeFrame(ID3D11Texture2D* texture);
+
+private:
+ winrt::Direct3D11::IDirect3DDevice m_device{ nullptr };
+ winrt::com_ptr m_d3dDevice;
+ winrt::com_ptr m_d3dContext;
+ RECT m_rcCrop;
+ uint32_t m_frameRate;
+
+ winrt::GraphicsCaptureItem m_item{ nullptr };
+ winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed;
+ std::shared_ptr m_frameWait;
+
+ winrt::Streams::IRandomAccessStream m_stream{ nullptr };
+
+ // WIC components for GIF encoding
+ winrt::com_ptr m_wicFactory;
+ winrt::com_ptr m_wicStream;
+ winrt::com_ptr m_gifEncoder;
+ winrt::com_ptr m_encoderMetadataWriter;
+
+ std::atomic m_isRecording = false;
+ std::atomic m_closed = false;
+
+ uint32_t m_frameWidth=0;
+ uint32_t m_frameHeight=0;
+ uint32_t m_frameDelay=0;
+ uint32_t m_frameCount = 0;
+
+ int32_t m_width=0;
+ int32_t m_height=0;
+};
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.h b/src/modules/ZoomIt/ZoomIt/ZoomIt.h
index 2687ba2b65..552af677ce 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.h
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.h
@@ -39,6 +39,10 @@ type_pEnableThemeDialogTexture pEnableThemeDialogTexture;
#define WIN7_VERSION 0x106
#define WIN10_VERSION 0x206
+// Default recording format frame rates
+#define RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE 15
+#define RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE 30
+
// Time that we'll cache live zoom window to avoid flicker
// of live zooming on Vista/ws2k8
#define LIVEZOOM_WINDOW_TIMEOUT 2*3600*1000
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc
index 382c93208e..5f5e9d16cf 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc
@@ -32,18 +32,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// TEXTINCLUDE
//
-1 TEXTINCLUDE
+1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
-2 TEXTINCLUDE
+2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
-3 TEXTINCLUDE
+3 TEXTINCLUDE
BEGIN
"#include ""binres.rc""\0"
END
@@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
- LTEXT "ZoomIt v9.10",IDC_VERSION,42,7,73,10
+ LTEXT "ZoomIt v9.21",IDC_VERSION,42,7,73,10
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
CONTROL "Sysinternals - www.sysinternals.com ",IDC_LINK,
"SysLink",WS_TABSTOP,42,26,150,9
@@ -272,13 +272,15 @@ BEGIN
LTEXT "Note: Recording is only available on Windows 10 (version 1903) and higher.",IDC_STATIC,7,77,246,19
LTEXT "Scaling:",IDC_STATIC,30,115,26,8
COMBOBOX IDC_RECORD_SCALING,61,114,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Format:",IDC_STATIC,30,132,26,8
+ COMBOBOX IDC_RECORD_FORMAT,61,131,60,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | WS_VSCROLL | WS_TABSTOP
LTEXT "Frame Rate:",IDC_STATIC,119,115,44,8,NOT WS_VISIBLE
COMBOBOX IDC_RECORD_FRAME_RATE,166,114,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,32,246,19
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,246,19
- CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,137,83,10
- COMBOBOX IDC_MICROPHONE,81,152,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
- LTEXT "Microphone:",IDC_STATIC,32,154,47,8
+ CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,149,83,10
+ COMBOBOX IDC_MICROPHONE,81,164,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Microphone:",IDC_STATIC,32,166,47,8
END
SNIP DIALOGEX 0, 0, 260, 68
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
index d054d2b4bd..b1f91a5228 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj
@@ -240,6 +240,7 @@
NotUsing
NotUsing
+
Use
@@ -294,6 +295,7 @@
+
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters
index 1754412d2d..e0416fe585 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters
+++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj.filters
@@ -54,6 +54,9 @@
Source Files
+
+ Source Files
+
@@ -95,6 +98,9 @@
Header Files
+
+ Header Files
+
diff --git a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h
index 96e2f19e5b..efd731cdce 100644
--- a/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h
+++ b/src/modules/ZoomIt/ZoomIt/ZoomItSettings.h
@@ -3,6 +3,13 @@
#include "Registry.h"
#include "DemoType.h"
+// Recording format enum
+enum class RecordingFormat
+{
+ GIF = 0,
+ MP4 = 1
+};
+
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
@@ -37,9 +44,11 @@ LOGFONT g_LogFont;
BOOLEAN g_DemoTypeUserDriven = false;
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
DWORD g_DemoTypeSpeedSlider = static_cast(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
-DWORD g_RecordFrameRate = 30;
-// Divide by 100 to get actual scaling
-DWORD g_RecordScaling = 100;
+DWORD g_RecordFrameRate = 30; // We default to 30 here, but g_RecordFrameRate can be different depending on recording format and gets set accordingly
+DWORD g_RecordScaling = 100;
+DWORD g_RecordScalingGIF = 50;
+DWORD g_RecordScalingMP4 = 100;
+RecordingFormat g_RecordingFormat = RecordingFormat::MP4;
BOOLEAN g_CaptureAudio = FALSE;
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
@@ -78,8 +87,9 @@ REG_SETTING RegSettings[] = {
{ L"SnapToGrid", SETTING_TYPE_BOOLEAN, 0, &g_SnapToGrid, static_cast(g_SnapToGrid) },
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast(g_SliderZoomLevel) },
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast(0) },
- { L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast(g_RecordFrameRate) },
- { L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast(g_RecordScaling) },
+ { L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast(g_RecordingFormat) },
+ { L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast(g_RecordScalingGIF) },
+ { L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast(g_RecordScalingMP4) },
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast(g_CaptureAudio) },
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast(0) },
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast(0) }
diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp
index fadc760339..71f09fc578 100644
--- a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp
+++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp
@@ -15,6 +15,7 @@
#include "Utility.h"
#include "WindowsVersions.h"
#include "ZoomItSettings.h"
+#include "GifRecordingSession.h"
#ifdef __ZOOMIT_POWERTOYS__
#include
@@ -68,6 +69,8 @@ COLORREF g_CustomColors[16];
#define SNIP_SAVE_HOTKEY 9
#define DEMOTYPE_HOTKEY 10
#define DEMOTYPE_RESET_HOTKEY 11
+#define RECORD_GIF_HOTKEY 12
+#define RECORD_GIF_WINDOW_HOTKEY 13
#define ZOOM_PAGE 0
#define LIVE_PAGE 1
@@ -89,6 +92,11 @@ OPTION_TABS g_OptionsTabs[] = {
{ _T("Snip"), NULL }
};
+static const TCHAR* g_RecordingFormats[] = {
+ _T("GIF"),
+ _T("MP4")
+};
+
float g_ZoomLevels[] = {
1.25,
1.50,
@@ -99,6 +107,8 @@ float g_ZoomLevels[] = {
};
DWORD g_FramerateOptions[] = {
+ 15,
+ 24,
30,
60
};
@@ -120,7 +130,7 @@ const float STRONG_BLUR_RADIUS = 40;
DWORD g_ToggleMod;
DWORD g_LiveZoomToggleMod;
DWORD g_DrawToggleMod;
-DWORD g_BreakToggleMod;
+DWORD g_BreakToggleMod;
DWORD g_DemoTypeToggleMod;
DWORD g_RecordToggleMod;
DWORD g_SnipToggleMod;
@@ -152,12 +162,16 @@ BOOLEAN g_running = TRUE;
// Screen recording globals
#define DEFAULT_RECORDING_FILE L"Recording.mp4"
+#define DEFAULT_GIF_RECORDING_FILE L"Recording.gif"
+
BOOL g_RecordToggle = FALSE;
BOOL g_RecordCropping = FALSE;
SelectRectangle g_SelectRectangle;
std::wstring g_RecordingSaveLocation;
+std::wstring g_RecordingSaveLocationGIF;
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
std::shared_ptr g_RecordingSession = nullptr;
+std::shared_ptr g_GifRecordingSession = nullptr;
type_pGetMonitorInfo pGetMonitorInfo;
type_MonitorFromPoint pMonitorFromPoint;
@@ -192,7 +206,7 @@ ComputerGraphicsInit g_GraphicsInit;
//----------------------------------------------------------------------------
//
-// Saves specified filePath to clipboard.
+// Saves specified filePath to clipboard.
//
//----------------------------------------------------------------------------
bool SaveToClipboard( const WCHAR* filePath, HWND hwnd )
@@ -207,18 +221,18 @@ bool SaveToClipboard( const WCHAR* filePath, HWND hwnd )
HDROP hDrop = static_cast(GlobalAlloc( GHND, size ));
if (hDrop == NULL)
{
- return false;
+ return false;
}
DROPFILES* dFiles = static_cast(GlobalLock( hDrop ));
if (dFiles == NULL)
{
GlobalFree( hDrop );
- return false;
+ return false;
}
dFiles->pFiles = sizeof(DROPFILES);
- dFiles->fWide = TRUE;
+ dFiles->fWide = TRUE;
wcscpy( reinterpret_cast(& dFiles[1]), filePath);
GlobalUnlock( hDrop );
@@ -345,7 +359,7 @@ VOID ErrorDialog( HWND hParent, PCTSTR message, DWORD _Error )
TCHAR errmsg[1024];
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
- NULL, _Error,
+ NULL, _Error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast(&lpMsgBuf), 0, NULL );
_stprintf( errmsg, L"%s: %s", message, lpMsgBuf );
@@ -391,7 +405,7 @@ VOID ErrorDialogString( HWND hParent, PCTSTR Message, const wchar_t *_Error )
// SetAutostartFilePath
//
// Sets the file path for later autostart config.
-//
+//
//--------------------------------------------------------------------
void SetAutostartFilePath()
{
@@ -417,32 +431,32 @@ void SetAutostartFilePath()
// ConfigureAutostart
//
// Enables or disables Zoomit autostart for the current image file.
-//
+//
//--------------------------------------------------------------------
-bool ConfigureAutostart( HWND hParent, bool Enable )
+bool ConfigureAutostart( HWND hParent, bool Enable )
{
HKEY hRunKey, hZoomit;
DWORD error, length, type;
TCHAR imageFile[MAX_PATH];
- error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
+ error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, KEY_SET_VALUE, &hRunKey );
if( error == ERROR_SUCCESS ) {
if( Enable ) {
-
- error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0,
+
+ error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0,
KEY_QUERY_VALUE, &hZoomit );
if( error == ERROR_SUCCESS ) {
length = sizeof(imageFile);
#ifdef _WIN64
// Unconditionally reset filepath in case this was already set by 32 bit version
- SetAutostartFilePath();
+ SetAutostartFilePath();
#endif
error = RegQueryValueEx( hZoomit, _T( "Filepath" ), 0, &type, (BYTE *) imageFile, &length );
RegCloseKey( hZoomit );
- if( error == ERROR_SUCCESS ) {
+ if( error == ERROR_SUCCESS ) {
error = RegSetValueEx( hRunKey, APPNAME, 0, REG_SZ, (BYTE *) imageFile,
static_cast(_tcslen(imageFile)+1) * sizeof(TCHAR));
@@ -454,7 +468,7 @@ bool ConfigureAutostart( HWND hParent, bool Enable )
if( error == ERROR_FILE_NOT_FOUND ) error = ERROR_SUCCESS;
}
RegCloseKey( hRunKey );
- }
+ }
if( error != ERROR_SUCCESS ) {
ErrorDialog( hParent, L"Error configuring auto start", error );
@@ -468,15 +482,15 @@ bool ConfigureAutostart( HWND hParent, bool Enable )
// IsAutostartConfigured
//
// Is this version of zoomit configured to autostart.
-//
+//
//--------------------------------------------------------------------
bool IsAutostartConfigured()
{
HKEY hRunKey;
- TCHAR imageFile[MAX_PATH];
+ TCHAR imageFile[MAX_PATH];
DWORD error, imageFileLength, type;
- error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
+ error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, KEY_QUERY_VALUE, &hRunKey );
if( error == ERROR_SUCCESS ) {
@@ -496,15 +510,15 @@ bool IsAutostartConfigured()
//
// Returns true if this is the 32-bit version of the executable
// and we're on 64-bit Windows.
-//
+//
//--------------------------------------------------------------------
typedef BOOL (__stdcall *P_IS_WOW64PROCESS)(
HANDLE hProcess,
PBOOL Wow64Process
);
-BOOL
+BOOL
RunningOnWin64(
- VOID
+ VOID
)
{
P_IS_WOW64PROCESS pIsWow64Process;
@@ -513,9 +527,9 @@ RunningOnWin64(
pIsWow64Process = (P_IS_WOW64PROCESS) GetProcAddress(GetModuleHandle(_T("kernel32.dll")),
"IsWow64Process");
if( pIsWow64Process ) {
-
+
pIsWow64Process( GetCurrentProcess(), &isWow64 );
- }
+ }
return isWow64;
}
@@ -524,7 +538,7 @@ RunningOnWin64(
//
// ExtractImageResource
//
-// Extracts the specified file that is located in a resource for
+// Extracts the specified file that is located in a resource for
// this executable.
//
//--------------------------------------------------------------------
@@ -532,15 +546,15 @@ BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
{
HRSRC hResource;
HGLOBAL hImageResource;
- DWORD dwImageSize;
+ DWORD dwImageSize;
LPVOID lpvImage;
FILE *hFile;
// Locate the resource
- hResource = FindResource( NULL, ResourceName, _T("BINRES") );
- if( !hResource )
+ hResource = FindResource( NULL, ResourceName, _T("BINRES") );
+ if( !hResource )
return FALSE;
-
+
hImageResource = LoadResource( NULL, hResource );
dwImageSize = SizeofResource( NULL, hResource );
lpvImage = LockResource( hImageResource );
@@ -562,10 +576,10 @@ BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
//
// Returns true if this is the 32-bit version of the executable
// and we're on 64-bit Windows.
-//
+//
//--------------------------------------------------------------------
-DWORD
-Run64bitVersion(
+DWORD
+Run64bitVersion(
void
)
{
@@ -636,13 +650,13 @@ BOOLEAN IsPresentationMode()
//----------------------------------------------------------------------------
//
// EnableDisableSecondaryDisplay
-//
+//
// Creates a second display on the secondary monitor for displaying the
-// break timer.
+// break timer.
//
//----------------------------------------------------------------------------
-LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
- PDEVMODE OriginalDevMode )
+LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
+ PDEVMODE OriginalDevMode )
{
LONG result;
DEVMODE devMode{};
@@ -654,7 +668,7 @@ LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
//
devMode.dmSize = sizeof(devMode);
devMode.dmDriverExtra = 0;
- EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
+ EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
*OriginalDevMode = devMode;
//
@@ -667,7 +681,7 @@ LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
DM_PELSWIDTH |
DM_PELSHEIGHT |
DM_DISPLAYFLAGS |
- DM_DISPLAYFREQUENCY;
+ DM_DISPLAYFREQUENCY;
result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2",
&devMode,
NULL,
@@ -723,11 +737,11 @@ LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
// GetLineBounds
//
// Gets the rectangle bounding a line, taking into account pen width
-//
+//
//----------------------------------------------------------------------------
Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth )
{
- Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y),
+ Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y),
abs(p1.x - p2.x), abs( p1.y - p2.y));
rect.Inflate( penWidth, penWidth );
return rect;
@@ -738,7 +752,7 @@ Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth )
// InvalidateGdiplusRect
//
// Invalidate portion of window specified by Gdiplus::Rect
-//
+//
//----------------------------------------------------------------------------
void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect)
{
@@ -756,8 +770,8 @@ void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect)
//
// CreateGdiplusBitmap
//
-// Creates a gdiplus bitmap of the specified region of the HDC.
-//
+// Creates a gdiplus bitmap of the specified region of the HDC.
+//
//----------------------------------------------------------------------------
Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Height )
{
@@ -772,7 +786,7 @@ Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Heig
Gdiplus::Bitmap *blurBitmap = new Gdiplus::Bitmap(hBitmap, NULL);
DeleteDC(hdcNewBitmap);
DeleteObject(hBitmap);
- return blurBitmap;
+ return blurBitmap;
}
@@ -781,7 +795,7 @@ Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Heig
// CreateBitmapMemoryDIB
//
// Creates a memory DC and DIB for the specified region of the screen.
-//
+//
//----------------------------------------------------------------------------
BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* lineBounds,
HDC* hdcMem, HBITMAP* hDIBOrig, HBITMAP* hPreviousBitmap)
@@ -819,8 +833,8 @@ BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* l
//
// LockGdiPlusBitmap
//
-// Locks the Gdi+ bitmap so that we can access its pixels in memory.
-//
+// Locks the Gdi+ bitmap so that we can access its pixels in memory.
+//
//----------------------------------------------------------------------------
#ifdef _MSC_VER
// Analyzers want us to use a scoped object instead of new. But given all the operations done in Bitmaps it seems better to leave it as a heap object.
@@ -835,7 +849,7 @@ Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap)
Gdiplus::Rect lineBitmapBounds(0, 0, Bitmap->GetWidth(), Bitmap->GetHeight());
Bitmap->LockBits(&lineBitmapBounds, Gdiplus::ImageLockModeRead,
Bitmap->GetPixelFormat(), lineData);
- return lineData;
+ return lineData;
}
#ifdef _MSC_VER
#pragma warning(pop)
@@ -846,11 +860,11 @@ Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap)
//
// BlurScreen
//
-// Blur the portion of the screen by copying a blurred bitmap with the
-// specified shape.
-//
+// Blur the portion of the screen by copying a blurred bitmap with the
+// specified shape.
+//
//----------------------------------------------------------------------------
-void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
+void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
Gdiplus::Bitmap *BlurBitmap, BYTE* pPixels)
{
HDC hdcDIB;
@@ -896,8 +910,8 @@ void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
//
// BitmapBlur
//
-// Blurs the bitmap.
-//
+// Blurs the bitmap.
+//
//----------------------------------------------------------------------------
void BitmapBlur(Gdiplus::Bitmap* hBitmap)
{
@@ -928,7 +942,7 @@ void BitmapBlur(Gdiplus::Bitmap* hBitmap)
// DrawBlurredShape
//
// Blur a shaped region of the screen.
-//
+//
//----------------------------------------------------------------------------
void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdiplus::Graphics *dstGraphics,
int x1, int y1, int x2, int y2)
@@ -937,7 +951,7 @@ void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdip
Gdiplus::Rect lineBounds( min( x1, x2 ), min( y1, y2 ), abs( x2 - x1 ), abs( y2 - y1 ) );
// Expand for line drawing
- if (Shape == DRAW_LINE)
+ if (Shape == DRAW_LINE)
lineBounds.Inflate( static_cast(g_PenWidth / 2), static_cast(g_PenWidth / 2) );
Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
@@ -978,7 +992,7 @@ void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdip
// CreateDrawingBitmap
//
// Create a bitmap to draw on.
-//
+//
//----------------------------------------------------------------------------
Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds )
{
@@ -992,8 +1006,8 @@ Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds )
//
// DrawBitmapLine
//
-// Creates a bitmap and draws a line on it.
-//
+// Creates a bitmap and draws a line on it.
+//
//----------------------------------------------------------------------------
Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gdiplus::Pen *pen)
{
@@ -1013,7 +1027,7 @@ Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gd
// ColorFromColorRef
//
// Returns a color object from the colorRef that includes the alpha channel
-//
+//
//----------------------------------------------------------------------------
Gdiplus::Color ColorFromColorRef(DWORD colorRef) {
BYTE a = (colorRef >> 24) & 0xFF; // Extract the alpha channel value
@@ -1028,8 +1042,8 @@ Gdiplus::Color ColorFromColorRef(DWORD colorRef) {
//
// AdjustHighlighterColor
//
-// Lighten the color.
-//
+// Lighten the color.
+//
//----------------------------------------------------------------------------
void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) {
@@ -1044,8 +1058,8 @@ void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) {
// BlendColors
//
// Blends two colors together using the alpha channel of the second color.
-// The highlighter is the second color.
-//
+// The highlighter is the second color.
+//
//----------------------------------------------------------------------------
COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
@@ -1068,7 +1082,7 @@ COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
// int maxValue = max(red1, max(green1, blue1));
if(TRUE) { // red1 > 0x10 && red1 < 0xC0 && (maxValue - minValue < 0x40)) {
- // This does a standard bright highlight
+ // This does a standard bright highlight
alpha2 = 0;
AdjustHighlighterColor( &red2, &green2, &blue2 );
redResult = red2 & red1;
@@ -1095,7 +1109,7 @@ COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
// Draws the shape with the highlighter color.
//
//----------------------------------------------------------------------------
-void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush,
+void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush,
Gdiplus::Pen *pPen, int x1, int y1, int x2, int y2)
{
// Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
@@ -1115,7 +1129,7 @@ void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBr
break;
case DRAW_ELLIPSE:
lineGraphics.FillEllipse( pBrush, 0, 0, lineBounds.Width, lineBounds.Height);
- break;
+ break;
case DRAW_LINE:
lineGraphics.DrawLine(pPen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y);
break;
@@ -1197,7 +1211,7 @@ void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBr
//
// CreateFadedDesktopBackground
//
-// Creates a snapshot of the desktop that's faded and alpha blended with
+// Creates a snapshot of the desktop that's faded and alpha blended with
// black.
//
//----------------------------------------------------------------------------
@@ -1211,7 +1225,7 @@ HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
HBITMAP hBitmap = CreateCompatibleBitmap( hdcScreen, width, height );
HBITMAP hOld = static_cast(SelectObject( hdcMem, hBitmap ));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 0));
-
+
// start with black background
FillRect( hdcMem, rcScreen, hBrush );
if(rcCrop != NULL && rcCrop->left != -1 ) {
@@ -1227,8 +1241,8 @@ HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 0x4F;
blend.AlphaFormat = 0;
- AlphaBlend( hdcMem,0, 0, width, height,
- hdcScreen, rcScreen->left, rcScreen->top,
+ AlphaBlend( hdcMem,0, 0, width, height,
+ hdcScreen, rcScreen->left, rcScreen->top,
width, height, blend );
SelectObject( hdcMem, hOld );
@@ -1249,9 +1263,9 @@ HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
void AdjustToMoveBoundary( float zoomLevel, int *coordinate, int cursor, int size, int max )
{
int diff = static_cast (static_cast(size)/ static_cast(LIVEZOOM_MOVE_REGIONS));
- if( cursor - *coordinate < diff )
- *coordinate = max( 0, cursor - diff );
- else if( (*coordinate + size) - cursor < diff )
+ if( cursor - *coordinate < diff )
+ *coordinate = max( 0, cursor - diff );
+ else if( (*coordinate + size) - cursor < diff )
*coordinate = min( cursor + diff - size, max - size );
}
@@ -1278,10 +1292,10 @@ void GetZoomedTopLeftCoordinates( float zoomLevel, POINT *cursorPos, int *x, int
//
// ScaleImage
//
-// Use gdi+ for anti-aliased bitmap stretching.
+// Use gdi+ for anti-aliased bitmap stretching.
//
//----------------------------------------------------------------------------
-void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
+void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc )
{
Gdiplus::Graphics dstGraphics( hdcDst );
@@ -1331,7 +1345,7 @@ using namespace Gdiplus;
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
- }
+ }
}
free(pImageCodecInfo);
@@ -1339,20 +1353,20 @@ using namespace Gdiplus;
}
//----------------------------------------------------------------------
-//
+//
// ConvertToUnicode
//
//----------------------------------------------------------------------
-void
-ConvertToUnicode(
- PCHAR aString,
- PWCHAR wString,
- DWORD wStringLength
+void
+ConvertToUnicode(
+ PCHAR aString,
+ PWCHAR wString,
+ DWORD wStringLength
)
{
size_t len;
- len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast(strlen(aString)),
+ len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast(strlen(aString)),
wString, wStringLength );
wString[len] = 0;
}
@@ -1411,14 +1425,14 @@ void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable )
NOTIFYICONDATA tNotifyIconData;
memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
- tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
- tNotifyIconData.hWnd = hWnd;
- tNotifyIconData.uID = 1;
- tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
- tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE;
- tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" );
+ tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
+ tNotifyIconData.hWnd = hWnd;
+ tNotifyIconData.uID = 1;
+ tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE;
+ tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" );
lstrcpyn(tNotifyIconData.szTip, APPNAME, sizeof(APPNAME));
- Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData);
+ Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData);
}
//----------------------------------------------------------------------------
@@ -1426,7 +1440,7 @@ void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable )
// EnableDisableOpacity
//
//----------------------------------------------------------------------------
-void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
+void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
{
DWORD exStyle;
@@ -1453,11 +1467,11 @@ void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
// EnableDisableScreenSaver
//
//----------------------------------------------------------------------------
-void EnableDisableScreenSaver( BOOLEAN Enable )
+void EnableDisableScreenSaver( BOOLEAN Enable )
{
- SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0);
- SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0);
- SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0);
+ SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0);
+ SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0);
+ SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0);
}
//----------------------------------------------------------------------------
@@ -1470,25 +1484,25 @@ void EnableDisableStickyKeys( BOOLEAN Enable )
static STICKYKEYS prevStickyKeyValue = {0};
STICKYKEYS newStickyKeyValue = {0};
- // Need to do this on Vista tablet to stop sticky key popup when you
+ // Need to do this on Vista tablet to stop sticky key popup when you
// hold down the shift key and draw with the pen.
if( Enable ) {
if( prevStickyKeyValue.cbSize == sizeof(STICKYKEYS)) {
- SystemParametersInfo(SPI_SETSTICKYKEYS,
+ SystemParametersInfo(SPI_SETSTICKYKEYS,
sizeof(STICKYKEYS), &prevStickyKeyValue, SPIF_SENDCHANGE);
}
} else {
prevStickyKeyValue.cbSize = sizeof(STICKYKEYS);
- if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS),
+ if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS),
&prevStickyKeyValue, 0)) {
newStickyKeyValue.cbSize = sizeof(STICKYKEYS);
newStickyKeyValue.dwFlags = 0;
- if( !SystemParametersInfo(SPI_SETSTICKYKEYS,
+ if( !SystemParametersInfo(SPI_SETSTICKYKEYS,
sizeof(STICKYKEYS), &newStickyKeyValue, SPIF_SENDCHANGE)) {
// DWORD error = GetLastError();
@@ -1520,7 +1534,7 @@ constexpr DWORD GetKeyMod( DWORD Key )
// AdvancedBreakProc
//
//----------------------------------------------------------------------------
-INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
+INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
TCHAR opacity[10];
static TCHAR newSoundFile[MAX_PATH];
@@ -1535,9 +1549,9 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
pSHAutoComplete( GetDlgItem( hDlg, IDC_SOUND_FILE), SHACF_FILESYSTEM );
pSHAutoComplete( GetDlgItem( hDlg, IDC_BACKGROUND_FILE), SHACF_FILESYSTEM );
}
- CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE,
+ CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE,
g_BreakShowBackgroundFile ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE,
+ CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE,
g_BreakPlaySoundFile ? BST_CHECKED: BST_UNCHECKED );
CheckDlgButton( hDlg, IDC_CHECK_SHOW_EXPIRED,
g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
@@ -1575,7 +1589,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ), FALSE );
EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ), FALSE );
}
- CheckDlgButton( hDlg,
+ CheckDlgButton( hDlg,
g_BreakShowDesktop ? IDC_STATIC_DESKTOP_BACKGROUND : IDC_STATIC_BACKGROUND_FILE, BST_CHECKED );
_tcscpy( newBackgroundFile, g_BreakBackgroundFile );
SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, g_BreakBackgroundFile );
@@ -1585,10 +1599,10 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
for( i = 10; i <= 100; i += 10) {
_stprintf( opacity, L"%d%%", i );
- SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0,
+ SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0,
reinterpret_cast(opacity));
}
- SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL,
+ SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL,
g_BreakOpacity / 10 - 1, 0 );
return TRUE;
@@ -1597,25 +1611,25 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
case BN_CLICKED:
if( LOWORD( wParam ) == IDC_CHECK_SOUND_FILE ) {
- EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ),
+ IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
+ EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ),
- IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
}
if( LOWORD( wParam ) == IDC_CHECK_BACKGROUND_FILE ) {
- EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ),
+ EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ),
+ EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ),
+ EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ),
+ IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
+ EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ),
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
- EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ),
- IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
}
break;
}
@@ -1704,7 +1718,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
#endif
if( g_BreakPlaySoundFile && GetFileAttributes( newSoundFile ) == -1 ) {
- MessageBox( hDlg, L"The specified sound file is inaccessible",
+ MessageBox( hDlg, L"The specified sound file is inaccessible",
L"Advanced Break Options Error", MB_ICONERROR );
break;
}
@@ -1715,7 +1729,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
if( !g_BreakShowDesktop && g_BreakShowBackgroundFile && GetFileAttributes( newBackgroundFile ) == -1 ) {
- MessageBox( hDlg, L"The specified background file is inaccessible",
+ MessageBox( hDlg, L"The specified background file is inaccessible",
L"Advanced Break Options Error", MB_ICONERROR );
break;
}
@@ -1729,7 +1743,7 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
break;
}
}
- GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0]));
+ GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0]));
_stscanf( opacity, L"%d%%", &g_BreakOpacity );
reg.WriteRegSettings( RegSettings );
EndDialog(hDlg, 0);
@@ -1753,14 +1767,14 @@ INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPAR
// OptionsTabProc
//
//----------------------------------------------------------------------------
-INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
- WPARAM wParam, LPARAM lParam )
+INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
+ WPARAM wParam, LPARAM lParam )
{
HDC hDC;
LOGFONT lf;
CHOOSEFONT chooseFont;
HFONT hFont;
- PAINTSTRUCT ps;
+ PAINTSTRUCT ps;
HWND hTextPreview;
HDC hDc;
RECT previewRc;
@@ -1771,6 +1785,58 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
+ // Handle combo box selection changes
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ if (LOWORD(wParam) == IDC_RECORD_SCALING) {
+
+ int format = static_cast(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT), CB_GETCURSEL, 0, 0));
+ int scale = static_cast(SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING), CB_GETCURSEL, 0, 0));
+ if(format == 0)
+ {
+ g_RecordScalingGIF = static_cast((scale + 1) * 10);
+ }
+ else
+ {
+ g_RecordScalingMP4 = static_cast((scale + 1) * 10);
+ }
+ }
+ else if (LOWORD(wParam) == IDC_RECORD_FORMAT) {
+ // Get the currently selected format
+ int selection = static_cast(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
+ CB_GETCURSEL, 0, 0));
+
+ // Get the selected text to check if it's GIF
+ TCHAR selectedText[32] = {0};
+ SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
+ CB_GETLBTEXT, selection, reinterpret_cast(selectedText));
+
+ // Check if GIF is selected by comparing the text
+ bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0);
+
+ // If GIF is selected, set the scaling to the g_RecordScalingGIF value; otherwise to the g_RecordScalingMP4 value
+ if (isGifSelected) {
+ g_RecordScaling = g_RecordScalingGIF;
+
+ } else {
+
+ g_RecordScaling = g_RecordScalingMP4;
+ }
+
+ for (int i = 0; i < 10; i++) {
+ int scalingValue = (i + 1) * 10;
+ if (scalingValue == static_cast(g_RecordScaling)) {
+ SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING),
+ CB_SETCURSEL, i, 0);
+ break;
+ }
+ }
+
+ // Enable/disable microphone controls based on selection
+ EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE), !isGifSelected);
+ EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_AUDIO), !isGifSelected);
+ }
+ }
+
switch ( LOWORD( wParam )) {
case IDC_ADVANCED_BREAK:
DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
@@ -1785,7 +1851,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
chooseFont.hwndOwner = hDlg;
chooseFont.lpLogFont = &lf;
chooseFont.Flags = CF_SCREENFONTS|CF_ENABLETEMPLATE|
- CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE;
+ CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE;
chooseFont.rgbColors = RGB (0, 0, 0);
chooseFont.lCustData = 0;
chooseFont.nSizeMin = 16;
@@ -1811,7 +1877,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
openFileName.nFilterIndex = 1;
openFileName.lpstrFilter = L"All Files\0*.*\0\0";
openFileName.lpstrFile = filePath;
-
+
if( GetOpenFileName( &openFileName ) )
{
if( GetFileAttributes( filePath ) == -1 )
@@ -1835,20 +1901,20 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
LOGFONT _lf = g_LogFont;
_lf.lfHeight = -21;
hFont = CreateFontIndirect( &_lf);
- hDc = BeginPaint(hDlg, &ps);
+ hDc = BeginPaint(hDlg, &ps);
SelectObject( hDc, hFont );
GetWindowRect( hTextPreview, &previewRc );
- MapWindowPoints( NULL, hDlg, reinterpret_cast(&previewRc), 2);
+ MapWindowPoints( NULL, hDlg, reinterpret_cast(&previewRc), 2);
previewRc.top += 6;
- DrawText( hDc, L"Sample", static_cast(_tcslen(L"Sample")), &previewRc,
+ DrawText( hDc, L"Sample", static_cast(_tcslen(L"Sample")), &previewRc,
DT_CENTER|DT_VCENTER|DT_SINGLELINE );
EndPaint( hDlg, &ps );
DeleteObject( hFont );
}
- break;
+ break;
default:
break;
}
@@ -1861,7 +1927,7 @@ INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
// OptionsAddTabs
//
//----------------------------------------------------------------------------
-VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
+VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
{
int i;
TCITEM tcItem;
@@ -1873,14 +1939,14 @@ VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
tcItem.mask = TCIF_TEXT;
tcItem.pszText = g_OptionsTabs[i].TabTitle;
TabCtrl_InsertItem( hTabCtrl, i, &tcItem );
- g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle,
+ g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle,
hOptionsDlg, OptionsTabProc );
}
TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc );
for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
pageRc = rc;
- MapWindowPoints( NULL, g_OptionsTabs[i].hPage, reinterpret_cast(&pageRc), 2);
+ MapWindowPoints( NULL, g_OptionsTabs[i].hPage, reinterpret_cast(&pageRc), 2);
SetWindowPos( g_OptionsTabs[i].hPage,
HWND_TOP,
@@ -1914,6 +1980,8 @@ void UnregisterAllHotkeys( HWND hWnd )
UnregisterHotKey( hWnd, SNIP_SAVE_HOTKEY);
UnregisterHotKey( hWnd, DEMOTYPE_HOTKEY );
UnregisterHotKey( hWnd, DEMOTYPE_RESET_HOTKEY );
+ UnregisterHotKey( hWnd, RECORD_GIF_HOTKEY );
+ UnregisterHotKey( hWnd, RECORD_GIF_WINDOW_HOTKEY );
}
//----------------------------------------------------------------------------
@@ -1943,6 +2011,9 @@ void RegisterAllHotkeys(HWND hWnd)
RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF);
}
+ // Register CTRL+8 for GIF recording and CTRL+ALT+8 for GIF window recording
+ RegisterHotKey(hWnd, RECORD_GIF_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 568 && 0xFF);
+ RegisterHotKey(hWnd, RECORD_GIF_WINDOW_HOTKEY, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 568 && 0xFF);
}
@@ -1996,8 +2067,8 @@ void UpdateDrawTabHeaderFont()
// OptionsProc
//
//----------------------------------------------------------------------------
-INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
- WPARAM wParam, LPARAM lParam )
+INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
+ WPARAM wParam, LPARAM lParam )
{
static HFONT hFontBold = nullptr;
PNMLINK notify = nullptr;
@@ -2026,7 +2097,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
SetForegroundWindow( hDlg );
SetActiveWindow( hDlg );
- SetWindowPos( hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
+ SetWindowPos( hDlg, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
#if 1
// set version info
TCHAR filePath[MAX_PATH];
@@ -2054,9 +2125,9 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UpdateDrawTabHeaderFont();
// Configure options
- SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES,
- static_cast(HKCOMB_NONE), // invalid key combinations
- MAKELPARAM(HOTKEYF_ALT, 0)); // add ALT to invalid entries
+ SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES,
+ static_cast(HKCOMB_NONE), // invalid key combinations
+ MAKELPARAM(HOTKEYF_ALT, 0)); // add ALT to invalid entries
if( g_ToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETHOTKEY, g_ToggleKey, 0 );
if( pMagInitialize ) {
@@ -2074,13 +2145,13 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
if( g_DemoTypeToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_SETHOTKEY, g_DemoTypeToggleKey, 0 );
if( g_RecordToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_SETHOTKEY, g_RecordToggleKey, 0 );
if( g_SnipToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_SETHOTKEY, g_SnipToggleKey, 0 );
- CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON,
+ CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON,
g_ShowTrayIcon ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( hDlg, IDC_AUTOSTART,
+ CheckDlgButton( hDlg, IDC_AUTOSTART,
IsAutostartConfigured() ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM,
+ CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM,
g_AnimateZoom ? BST_CHECKED: BST_UNCHECKED );
- CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE,
+ CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE,
g_SmoothImage ? BST_CHECKED: BST_UNCHECKED );
SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETRANGE, false, MAKELONG(0,_countof(g_ZoomLevels)-1) );
@@ -2089,21 +2160,24 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
_stprintf( text, L"%d", g_PenWidth );
SetDlgItemText( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH, text );
SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH ), EM_LIMITTEXT, 1, 0 );
- SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L,
+ SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L,
MAKELPARAM (19, 1));
_stprintf( text, L"%d", g_BreakTimeout );
SetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text );
SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER ), EM_LIMITTEXT, 2, 0 );
- SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L,
+ SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L,
MAKELPARAM (99, 1));
CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED,
g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
- CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
+ CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED );
- for (int i = 0; i < _countof(g_FramerateOptions); i++) {
+ //
+ // The framerate drop down list is not used in the current version (might be added in the future)
+ //
+ /*for (int i = 0; i < _countof(g_FramerateOptions); i++) {
_stprintf(text, L"%d", g_FramerateOptions[i]);
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast(CB_ADDSTRING),
@@ -2112,13 +2186,34 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast(i), static_cast(0));
}
+ }*/
+
+ // Add the recording format to the combo box and set the current selection
+ size_t selection = 0;
+ const wchar_t* currentFormatString = (g_RecordingFormat == RecordingFormat::GIF) ? L"GIF" : L"MP4";
+
+ for( size_t i = 0; i < (sizeof(g_RecordingFormats) / sizeof(g_RecordingFormats[0])); i++ )
+ {
+ SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(g_RecordingFormats[i]) );
+
+ if( selection == 0 && wcscmp( g_RecordingFormats[i], currentFormatString ) == 0 )
+ {
+ selection = i;
+ }
}
+ SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), CB_SETCURSEL, static_cast(selection), static_cast(0) );
+
for(unsigned int i = 1; i < 11; i++) {
_stprintf(text, L"%2.1f", (static_cast(i)) / 10 );
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast(CB_ADDSTRING),
static_cast(0), reinterpret_cast(text));
- if (g_RecordScaling == i*10 ) {
+
+ if (g_RecordingFormat == RecordingFormat::GIF && i*10 == g_RecordScalingGIF ) {
+
+ SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast(i)-1, static_cast(0));
+ }
+ if (g_RecordingFormat == RecordingFormat::MP4 && i*10 == g_RecordScalingMP4 ) {
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast(i)-1, static_cast(0));
}
@@ -2136,7 +2231,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
// Add the microphone devices to the combo box and set the current selection
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(L"Default"));
- size_t selection = 0;
+ selection = 0;
for( size_t i = 0; i < microphones.size(); i++ )
{
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast(CB_ADDSTRING), static_cast(0), reinterpret_cast(microphones[i].second.c_str()) );
@@ -2147,6 +2242,11 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
}
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), CB_SETCURSEL, static_cast(selection), static_cast(0) );
+ // Set initial state of microphone controls based on recording format
+ bool isGifSelected = (g_RecordingFormat == RecordingFormat::GIF);
+ EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE), !isGifSelected);
+ EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO), !isGifSelected);
+
if( GetFileAttributes( g_DemoTypeFile ) == -1 )
{
memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) );
@@ -2176,7 +2276,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
break;
case WM_CTLCOLORSTATIC:
- if( reinterpret_cast(lParam) == GetDlgItem( hDlg, IDC_TITLE ) ||
+ if( reinterpret_cast(lParam) == GetDlgItem( hDlg, IDC_TITLE ) ||
reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_DRAWING) ||
reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_ZOOM) ||
reinterpret_cast(lParam) == GetDlgItem(hDlg, IDC_BREAK) ||
@@ -2249,7 +2349,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
text[2] = 0;
newTimeout = _tstoi( text );
- g_RecordFrameRate = g_FramerateOptions[SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0))];
+ g_RecordingFormat = static_cast(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0)));
+ g_RecordFrameRate = (g_RecordingFormat == RecordingFormat::GIF) ? RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE : RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
g_RecordScaling = static_cast(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast(CB_GETCURSEL), static_cast(0), static_cast(0)) * 10 + 10);
// Get the selected microphone
@@ -2263,7 +2364,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UnregisterAllHotkeys(GetParent( hDlg ));
break;
- } else if(newLiveZoomToggleKey &&
+ } else if(newLiveZoomToggleKey &&
(!RegisterHotKey( GetParent( hDlg ), LIVE_HOTKEY, newLiveZoomToggleMod, newLiveZoomToggleKey & 0xFF ) ||
!RegisterHotKey(GetParent(hDlg), LIVE_DRAW_HOTKEY, (newLiveZoomToggleMod ^ MOD_SHIFT), newLiveZoomToggleKey & 0xFF))) {
@@ -2286,7 +2387,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UnregisterAllHotkeys(GetParent( hDlg ));
break;
- } else if( newDemoTypeToggleKey &&
+ } else if( newDemoTypeToggleKey &&
(!RegisterHotKey( GetParent( hDlg ), DEMOTYPE_HOTKEY, newDemoTypeToggleMod, newDemoTypeToggleKey & 0xFF ) ||
!RegisterHotKey(GetParent(hDlg), DEMOTYPE_RESET_HOTKEY, (newDemoTypeToggleMod ^ MOD_SHIFT), newDemoTypeToggleKey & 0xFF))) {
@@ -2296,7 +2397,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
break;
}
- else if (newSnipToggleKey &&
+ else if (newSnipToggleKey &&
(!RegisterHotKey(GetParent(hDlg), SNIP_HOTKEY, newSnipToggleMod, newSnipToggleKey & 0xFF) ||
!RegisterHotKey(GetParent(hDlg), SNIP_SAVE_HOTKEY, (newSnipToggleMod ^ MOD_SHIFT), newSnipToggleKey & 0xFF))) {
@@ -2305,8 +2406,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
UnregisterAllHotkeys(GetParent(hDlg));
break;
- }
- else if( newRecordToggleKey &&
+ }
+ else if( newRecordToggleKey &&
(!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY, newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
!RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, (newRecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
!RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, (newRecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF))) {
@@ -2317,7 +2418,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
break;
} else {
-
+
g_BreakTimeout = newTimeout;
g_ToggleKey = newToggleKey;
g_LiveZoomToggleKey = newLiveZoomToggleKey;
@@ -2337,7 +2438,7 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
hWndOptions = NULL;
EndDialog( hDlg, 0 );
- return TRUE;
+ return TRUE;
}
break;
}
@@ -2348,8 +2449,8 @@ INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
EndDialog( hDlg, 0 );
return TRUE;
}
- break;
-
+ break;
+
case WM_CLOSE:
hWndOptions = NULL;
RegisterAllHotkeys(GetParent(hDlg));
@@ -2388,7 +2489,7 @@ void DeleteDrawUndoList( P_DRAW_UNDO *DrawUndoList )
// PopDrawUndo
//
//----------------------------------------------------------------------------
-BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
+BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
int width, int height )
{
P_DRAW_UNDO nextUndo;
@@ -2396,7 +2497,7 @@ BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
nextUndo = *DrawUndoList;
if( nextUndo ) {
- BitBlt( hDc, 0, 0, width, height,
+ BitBlt( hDc, 0, 0, width, height,
nextUndo->hDc, 0, 0, SRCCOPY|CAPTUREBLT );
*DrawUndoList = nextUndo->Next;
DeleteObject( nextUndo->hBitmap );
@@ -2444,7 +2545,7 @@ void DeleteOldestUndo( P_DRAW_UNDO *DrawUndoList )
//----------------------------------------------------------------------------
//
// GetOldestUndo
-//
+//
//----------------------------------------------------------------------------
P_DRAW_UNDO GetOldestUndo(P_DRAW_UNDO DrawUndoList)
{
@@ -2509,7 +2610,7 @@ void PushDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList, int width, int height )
newUndo->Next = *DrawUndoList;
*DrawUndoList = newUndo;
}
- }
+ }
}
//----------------------------------------------------------------------------
@@ -2562,8 +2663,8 @@ void ClearTypingCursor( HDC hdcScreenCompat, HDC hdcScreenCursorCompat, RECT rc,
} else {
- BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left,
- rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT );
+ BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left,
+ rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT );
}
}
@@ -2618,7 +2719,7 @@ RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height,
int x, y;
GetZoomedTopLeftCoordinates( zoomLevel, cursorPos, &x, width, &y, height );
- rc.left = monInfo->rcMonitor.left + x;
+ rc.left = monInfo->rcMonitor.left + x;
rc.right = rc.left + static_cast(width/zoomLevel);
rc.top = monInfo->rcMonitor.top + y;
rc.bottom = rc.top + static_cast(height/zoomLevel);
@@ -2629,7 +2730,7 @@ RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height,
rc.left, rc.top, rc.right, rc.bottom);
OutputDebug( L"mon.left: %d mon.top: %d mon.right: %d mon.bottom: %d\n",
monInfo->rcMonitor.left, monInfo->rcMonitor.top, monInfo->rcMonitor.right, monInfo->rcMonitor.bottom);
-
+
ClipCursor( &rc );
return rc;
}
@@ -2657,7 +2758,7 @@ void DrawArrow( HDC hdc, int x1, int y1, int x2, int y2, double length, double w
// get midpoint of base
int xMid = x2 - static_cast(length*dx+0.5);
int yMid = y2 - static_cast(length*dy+0.5);
-
+
// get left wing
int xLeft = xMid - static_cast(dy*width+0.5);
int yLeft = yMid + static_cast(dx*width+0.5);
@@ -2763,7 +2864,7 @@ VOID DrawShape( DWORD Shape, HDC hDc, RECT *Rect, bool UseGdiPlus = false )
case DRAW_RECTANGLE:
if (UseGdiPlus)
if(pBrush)
- DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL,
+ DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL,
static_cast(Rect->left - 1), static_cast(Rect->top - 1),
static_cast(Rect->right), static_cast(Rect->bottom));
else if (isBlur)
@@ -2840,7 +2941,7 @@ VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam)
if(GetKeyState(VK_LCONTROL) < 0 ) {
wParam |= MK_CONTROL;
- }
+ }
if( GetKeyState( VK_LSHIFT) < 0 || GetKeyState( VK_RSHIFT) < 0 ) {
wParam |= MK_SHIFT;
@@ -2853,10 +2954,10 @@ VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam)
//----------------------------------------------------------------------------
//
// ScalePenPosition
-//
+//
// Maps pen input to mouse input coordinates based on zoom level. Returns
// 0 if pen is active but we didn't send this message to ourselves (pen
-// signature will be missing).
+// signature will be missing).
//
//----------------------------------------------------------------------------
LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
@@ -2867,7 +2968,7 @@ LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
LPARAM extraInfo;
extraInfo = GetMessageExtraInfo();
- if( g_PenDown ) {
+ if( g_PenDown ) {
// ignore messages we didn't tag as pen
if (extraInfo == MI_WP_SIGNATURE) {
@@ -2890,7 +2991,7 @@ LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
OutputDebug(L"Ignore pen message we didn't send\n");
lParam = 0;
}
-
+
} else {
if( !GetClipCursor( &rc )) {
@@ -2900,7 +3001,7 @@ LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
OutputDebug( L"Mouse message\n");
}
return lParam;
-}
+}
//----------------------------------------------------------------------------
@@ -2927,7 +3028,7 @@ BOOLEAN DrawHighlightedCursor( float ZoomLevel, int Width, int Height )
// InvalidateCursorMoveArea
//
//----------------------------------------------------------------------------
-void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height,
+void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height,
POINT currentPt, POINT prevPt, POINT cursorPos )
{
int x, y;
@@ -2935,7 +3036,7 @@ void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height
int invWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
if( DrawHighlightedCursor( zoomLevel, width, height ) ) {
-
+
invWidth = g_PenWidth * 3 + 1;
}
GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
@@ -3058,7 +3159,7 @@ void DrawCursor( HDC hDcTarget, POINT pt, float ZoomLevel, int Width, int Height
//
//----------------------------------------------------------------------------
void ResizePen( HWND hWnd, HDC hdcScreenCompat, HDC hdcScreenCursorCompat, POINT prevPt,
- BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel,
+ BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel,
BOOLEAN isUser, int newWidth )
{
if( !g_Tracing ) {
@@ -3117,7 +3218,7 @@ bool IsPenInverted( WPARAM wParam )
//----------------------------------------------------------------------------
//
// CaptureScreenshotAsync
-//
+//
// Captures the specified screen using the capture APIs
//
//----------------------------------------------------------------------------
@@ -3127,7 +3228,7 @@ std::future> CaptureScreenshotAsync(winrt::IDire
winrt::com_ptr d3dContext;
d3dDevice->GetImmediateContext(d3dContext.put());
- // Creating our frame pool with CreateFreeThreaded means that we
+ // Creating our frame pool with CreateFreeThreaded means that we
// will be called back from the frame pool's internal worker thread
// instead of the thread we are currently on. It also disables the
// DispatcherQueue requirement.
@@ -3165,7 +3266,7 @@ std::future> CaptureScreenshotAsync(winrt::IDire
//----------------------------------------------------------------------------
//
// CaptureScreenshot
-//
+//
// Captures the specified screen using the capture APIs
//
//----------------------------------------------------------------------------
@@ -3196,9 +3297,9 @@ winrt::com_ptrCaptureScreenshot(winrt::DirectXPixelFormat const
//----------------------------------------------------------------------------
//
// CopyD3DTexture
-//
+//
//----------------------------------------------------------------------------
-inline auto CopyD3DTexture(winrt::com_ptr const& device,
+inline auto CopyD3DTexture(winrt::com_ptr const& device,
winrt::com_ptr const& texture, bool asStagingTexture)
{
winrt::com_ptr context;
@@ -3224,9 +3325,9 @@ inline auto CopyD3DTexture(winrt::com_ptr const& device,
//----------------------------------------------------------------------------
//
// PrepareStagingTexture
-//
+//
//----------------------------------------------------------------------------
-inline auto PrepareStagingTexture(winrt::com_ptr const& device,
+inline auto PrepareStagingTexture(winrt::com_ptr const& device,
winrt::com_ptr const& texture)
{
// If our texture is already set up for staging, then use it.
@@ -3244,7 +3345,7 @@ inline auto PrepareStagingTexture(winrt::com_ptr const& device,
//----------------------------------------------------------------------------
//
// GetBytesPerPixel
-//
+//
//----------------------------------------------------------------------------
inline size_t
GetBytesPerPixel(DXGI_FORMAT pixelFormat)
@@ -3342,7 +3443,7 @@ GetBytesPerPixel(DXGI_FORMAT pixelFormat)
//----------------------------------------------------------------------------
//
// CopyBytesFromTexture
-//
+//
//----------------------------------------------------------------------------
inline auto CopyBytesFromTexture(winrt::com_ptr const& texture, uint32_t subresource = 0)
{
@@ -3395,6 +3496,12 @@ void StopRecording()
g_RecordingSession = nullptr;
}
+ if ( g_GifRecordingSession != nullptr ) {
+
+ g_GifRecordingSession->Close();
+ g_GifRecordingSession = nullptr;
+ }
+
g_RecordToggle = FALSE;
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
@@ -3424,7 +3531,16 @@ void StopRecording()
//----------------------------------------------------------------------------
auto GetUniqueRecordingFilename()
{
- std::filesystem::path path{ g_RecordingSaveLocation };
+ std::filesystem::path path;
+
+ if (g_RecordingFormat == RecordingFormat::GIF)
+ {
+ path = g_RecordingSaveLocationGIF;
+ }
+ else
+ {
+ path = g_RecordingSaveLocation;
+ }
// Chop off index if it's there
auto base = std::regex_replace( path.stem().wstring(), std::wregex( L" [(][0-9]+[)]$" ), L"" );
@@ -3442,7 +3558,7 @@ auto GetUniqueRecordingFilename()
//----------------------------------------------------------------------------
//
// StartRecordingAsync
-//
+//
// Starts the screen recording.
//
//----------------------------------------------------------------------------
@@ -3451,7 +3567,10 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists );
- auto file = co_await appFolder.CreateFileAsync( L"zoomit.mp4", winrt::CreationCollisionOption::ReplaceExisting );
+
+ // Choose temp file extension based on format
+ const wchar_t* tempFileName = (g_RecordingFormat == RecordingFormat::GIF) ? L"zoomit.gif" : L"zoomit.mp4";
+ auto file = co_await appFolder.CreateFileAsync( tempFileName, winrt::CreationCollisionOption::ReplaceExisting );
// Get the device
auto d3dDevice = util::CreateD3D11Device();
@@ -3460,7 +3579,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
// Get the active MONITOR capture device
HMONITOR hMon = NULL;
- POINT cursorPos = { 0, 0 };
+ POINT cursorPos = { 0, 0 };
if( pMonitorFromPoint ) {
GetCursorPos( &cursorPos );
@@ -3468,27 +3587,47 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{ nullptr };
- if( hWndRecord )
+ if( hWndRecord )
item = util::CreateCaptureItemForWindow( hWndRecord );
else
item = util::CreateCaptureItemForMonitor( hMon );
auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
- g_RecordingSession = VideoRecordingSession::Create(
- g_RecordDevice,
- item,
- *rcCrop,
- g_RecordFrameRate,
- g_CaptureAudio,
- stream );
- if( g_hWndLiveZoom != NULL )
- g_RecordingSession->EnableCursorCapture( false );
+ // Create the appropriate recording session based on format
+ OutputDebugStringW((L"Starting recording session. Framerate: " + std::to_wstring(g_RecordFrameRate) + L" scaling: " + std::to_wstring(g_RecordScaling) + L" Format: " + (g_RecordingFormat == RecordingFormat::GIF ? L"GIF" : L"MP4") + L"\n").c_str());
+ if (g_RecordingFormat == RecordingFormat::GIF)
+ {
+ g_GifRecordingSession = GifRecordingSession::Create(
+ g_RecordDevice,
+ item,
+ *rcCrop,
+ g_RecordFrameRate,
+ stream );
- co_await g_RecordingSession->StartAsync();
+ if( g_hWndLiveZoom != NULL )
+ g_GifRecordingSession->EnableCursorCapture( false );
- // g_RecordingSession isn't null if we're aborting a recording
- if( g_RecordingSession == nullptr ) {
+ co_await g_GifRecordingSession->StartAsync();
+ }
+ else
+ {
+ g_RecordingSession = VideoRecordingSession::Create(
+ g_RecordDevice,
+ item,
+ *rcCrop,
+ g_RecordFrameRate,
+ g_CaptureAudio,
+ stream );
+
+ if( g_hWndLiveZoom != NULL )
+ g_RecordingSession->EnableCursorCapture( false );
+
+ co_await g_RecordingSession->StartAsync();
+ }
+
+ // Check if recording was aborted
+ if( g_RecordingSession == nullptr && g_GifRecordingSession == nullptr ) {
g_bSaveInProgress = true;
@@ -3504,20 +3643,63 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
wil::com_ptr videosItem;
if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) )
saveDialog->SetDefaultFolder( videosItem.get() );
- saveDialog->SetDefaultExtension( L".mp4" );
- COMDLG_FILTERSPEC fileTypes[] = {
- { L"MP4 Video", L"*.mp4" }
- };
- saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
- if( g_RecordingSaveLocation.size() == 0) {
+ // Set file type based on the recording format
+ if (g_RecordingFormat == RecordingFormat::GIF)
+ {
+ saveDialog->SetDefaultExtension( L".gif" );
+ COMDLG_FILTERSPEC fileTypes[] = {
+ { L"GIF Animation", L"*.gif" }
+ };
+ saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
+ }
+ else
+ {
+ saveDialog->SetDefaultExtension( L".mp4" );
+ COMDLG_FILTERSPEC fileTypes[] = {
+ { L"MP4 Video", L"*.mp4" }
+ };
+ saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
+ }
+
+ // Peek the folder Windows has chosen to display
+ static std::filesystem::path lastSaveFolder;
+ wil::unique_cotaskmem_string chosenFolderPath;
+ wil::com_ptr currentSelectedFolder;
+ bool bFolderChanged = false;
+ if (SUCCEEDED(saveDialog->GetFolder(currentSelectedFolder.put())))
+ {
+ if (SUCCEEDED(currentSelectedFolder->GetDisplayName(SIGDN_FILESYSPATH, chosenFolderPath.put())))
+ {
+ if (lastSaveFolder != chosenFolderPath.get())
+ {
+ lastSaveFolder = chosenFolderPath.get() ? chosenFolderPath.get() : std::filesystem::path{};
+ bFolderChanged = true;
+ }
+ }
+ }
+
+ if( (g_RecordingFormat == RecordingFormat::GIF && g_RecordingSaveLocationGIF.size() == 0) || (g_RecordingFormat == RecordingFormat::MP4 && g_RecordingSaveLocation.size() == 0) || (bFolderChanged)) {
wil::com_ptr shellItem;
wil::unique_cotaskmem_string folderPath;
- if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put())))
- g_RecordingSaveLocation = folderPath.get();
- g_RecordingSaveLocation = std::filesystem::path{ g_RecordingSaveLocation } /= DEFAULT_RECORDING_FILE;
+ if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put()))) {
+ if (g_RecordingFormat == RecordingFormat::GIF) {
+ g_RecordingSaveLocationGIF = folderPath.get();
+ std::filesystem::path currentPath{ g_RecordingSaveLocationGIF };
+ g_RecordingSaveLocationGIF = currentPath / DEFAULT_GIF_RECORDING_FILE;
+ }
+ else {
+ g_RecordingSaveLocation = folderPath.get();
+ if (g_RecordingFormat == RecordingFormat::MP4) {
+ std::filesystem::path currentPath{ g_RecordingSaveLocation };
+ g_RecordingSaveLocation = currentPath / DEFAULT_RECORDING_FILE;
+ }
+ }
+ }
}
+
+ // Always use appropriate default filename based on current format
auto suggestedName = GetUniqueRecordingFilename();
saveDialog->SetFileName( suggestedName.c_str() );
@@ -3545,9 +3727,15 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
else {
- co_await file.MoveAndReplaceAsync( destFile );
- g_RecordingSaveLocation = file.Path();
- SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd);
+ co_await file.MoveAndReplaceAsync(destFile);
+ if (g_RecordingFormat == RecordingFormat::GIF) {
+ g_RecordingSaveLocationGIF = file.Path();
+ SaveToClipboard(g_RecordingSaveLocationGIF.c_str(), hWnd);
+ }
+ else {
+ g_RecordingSaveLocation = file.Path();
+ SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd);
+ }
}
g_bSaveInProgress = false;
@@ -3566,6 +3754,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
}
co_await file.DeleteAsync();
g_RecordingSession = nullptr;
+ g_GifRecordingSession = nullptr;
}
} catch( const winrt::hresult_error& error ) {
@@ -3681,9 +3870,9 @@ void ShowMainWindow(HWND hWnd, const MONITORINFO& monInfo, int width, int height
//
//----------------------------------------------------------------------------
LRESULT APIENTRY MainWndProc(
- HWND hWnd,
+ HWND hWnd,
UINT message,
- WPARAM wParam,
+ WPARAM wParam,
LPARAM lParam)
{
static int width, height;
@@ -3742,7 +3931,7 @@ LRESULT APIENTRY MainWndProc(
#endif
bool isCaptureSupported = false;
RECT rc, rc1;
- PAINTSTRUCT ps;
+ PAINTSTRUCT ps;
TCHAR timerText[16];
TCHAR negativeTimerText[16];
BOOLEAN penInverted;
@@ -3754,6 +3943,7 @@ LRESULT APIENTRY MainWndProc(
OPENFILENAME openFileName;
static TCHAR filePath[MAX_PATH] = {L"zoomit"};
NOTIFYICONDATA tNotifyIconData;
+ static DWORD64 g_TelescopingZoomLastTick = 0ull;
const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) {
rc.top = textPt.y - static_cast(g_TextBufferPreviousLines.size()) * lineHeight;
@@ -3780,19 +3970,119 @@ LRESULT APIENTRY MainWndProc(
}
};
+ const auto doTelescopingZoomTimer = [hWnd, wParam, lParam, &x, &y]( bool invalidate = true ) {
+ if( zoomTelescopeStep != 0.0f )
+ {
+ zoomLevel *= zoomTelescopeStep;
+ g_TelescopingZoomLastTick = GetTickCount64();
+ if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget) ||
+ (zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget) )
+ {
+ zoomLevel = zoomTelescopeTarget;
+
+ g_TelescopingZoomLastTick = 0ull;
+ KillTimer( hWnd, wParam );
+ OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
+ monInfo.rcMonitor.left,
+ monInfo.rcMonitor.top,
+ cursorPos.x,
+ cursorPos.y );
+ SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
+ monInfo.rcMonitor.top + cursorPos.y );
+ }
+ }
+ else
+ {
+ // Case where we didn't zoom at all
+ g_TelescopingZoomLastTick = 0ull;
+ KillTimer( hWnd, wParam );
+ }
+ if( wParam == 2 && zoomLevel == 1 )
+ {
+ g_Zoomed = FALSE;
+ if( g_ZoomOnLiveZoom )
+ {
+ GetCursorPos( &cursorPos );
+ cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
+ SetCursorPos( cursorPos.x, cursorPos.y );
+ SendMessage( hWnd, WM_HOTKEY, LIVE_HOTKEY, 0 );
+ }
+ else if( lParam != SHALLOW_ZOOM )
+ {
+ // Figure out where final unzoomed cursor should be
+ if( g_Drawing )
+ {
+ cursorPos = prevPt;
+ }
+ OutputDebug( L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
+ GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
+ cursorPos.x = monInfo.rcMonitor.left + x + static_cast((cursorPos.x - x) * zoomLevel);
+ cursorPos.y = monInfo.rcMonitor.top + y + static_cast((cursorPos.y - y) * zoomLevel);
+ SetCursorPos( cursorPos.x, cursorPos.y );
+ }
+ if( hTargetWindow )
+ {
+ SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top, rcTargetWindow.right - rcTargetWindow.left, rcTargetWindow.bottom - rcTargetWindow.top, 0 );
+ hTargetWindow = NULL;
+ }
+ DeleteDrawUndoList( &drawUndoList );
+
+ // Restore live zoom if we came from that mode
+ if( g_ZoomOnLiveZoom )
+ {
+ SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast(g_LiveZoomLevel), reinterpret_cast(&g_LiveZoomSourceRect) );
+ g_ZoomOnLiveZoom = FALSE;
+ forcePenResize = TRUE;
+ }
+
+ SetForegroundWindow( g_ActiveWindow );
+ ClipCursor( NULL );
+ g_HaveDrawn = FALSE;
+ g_TypeMode = TypeModeOff;
+ g_HaveTyped = FALSE;
+ g_Drawing = FALSE;
+ EnableDisableStickyKeys( TRUE );
+ DeleteObject( hTypingFont );
+ DeleteDC( hdcScreen );
+ DeleteDC( hdcScreenCompat );
+ DeleteDC( hdcScreenCursorCompat );
+ DeleteDC( hdcScreenSaveCompat );
+ DeleteObject( hbmpCompat );
+ DeleteObject (hbmpCursorCompat );
+ DeleteObject( hbmpDrawingCompat );
+ DeleteObject( hDrawingPen );
+
+ SetFocus( g_ActiveWindow );
+ ShowWindow( hWnd, SW_HIDE );
+ }
+ if( invalidate )
+ {
+ InvalidateRect( hWnd, NULL, FALSE );
+ }
+ };
+
switch (message) {
case WM_CREATE:
// get default font
- GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont );
+ GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont );
g_LogFont.lfWeight = FW_NORMAL;
hDc = CreateCompatibleDC( NULL );
g_LogFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDc, LOGPIXELSY), 72);
DeleteDC( hDc );
reg.ReadRegSettings( RegSettings );
-
- // to support migrating from
+
+ // Set g_RecordScaling based on the current recording format
+ if (g_RecordingFormat == RecordingFormat::GIF) {
+ g_RecordScaling = g_RecordScalingGIF;
+ g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
+ } else {
+ g_RecordScaling = g_RecordScalingMP4;
+ g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
+ }
+
+ // to support migrating from
if ((g_PenColor >> 24) == 0) {
g_PenColor |= 0xFF << 24;
}
@@ -3822,7 +4112,7 @@ LRESULT APIENTRY MainWndProc(
APPNAME, MB_ICONERROR );
showOptions = TRUE;
- } else if( g_LiveZoomToggleKey &&
+ } else if( g_LiveZoomToggleKey &&
(!RegisterHotKey( hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF))) {
@@ -3844,7 +4134,7 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
- else if( g_DemoTypeToggleKey &&
+ else if( g_DemoTypeToggleKey &&
(!RegisterHotKey( hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF ) ||
!RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))) {
@@ -3853,7 +4143,7 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
- else if (g_SnipToggleKey &&
+ else if (g_SnipToggleKey &&
(!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))) {
@@ -3862,7 +4152,7 @@ LRESULT APIENTRY MainWndProc(
showOptions = TRUE;
}
- else if (g_RecordToggleKey &&
+ else if (g_RecordToggleKey &&
(!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))) {
@@ -3985,7 +4275,7 @@ LRESULT APIENTRY MainWndProc(
// Highlight is not supported in LiveDraw
g_PenColor |= 0xFF << 24;
}
- }
+ }
break;
case SNIP_SAVE_HOTKEY:
@@ -4156,7 +4446,7 @@ LRESULT APIENTRY MainWndProc(
if( g_hWndLiveZoom == NULL ) {
OutputDebug(L"Create LIVEZOOM\n");
g_hWndLiveZoom = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TRANSPARENT,
- L"MagnifierClass", L"ZoomIt Live Zoom",
+ L"MagnifierClass", L"ZoomIt Live Zoom",
WS_POPUP | WS_CLIPSIBLINGS,
0, 0, 0, 0, NULL, NULL, g_hInstance, static_cast(GetForegroundWindow()) );
pSetLayeredWindowAttributes( hWnd, 0, 0, LWA_ALPHA );
@@ -4179,10 +4469,10 @@ LRESULT APIENTRY MainWndProc(
g_LiveZoomLevel = g_ZoomLevels[g_SliderZoomLevel];
#endif
// Unzoom
- SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 );
+ SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 );
} else {
-
+
OutputDebug(L"Show liveZoom\n");
ShowWindow( g_hWndLiveZoom, SW_SHOW );
}
@@ -4206,6 +4496,8 @@ LRESULT APIENTRY MainWndProc(
case RECORD_HOTKEY:
case RECORD_CROP_HOTKEY:
case RECORD_WINDOW_HOTKEY:
+ case RECORD_GIF_HOTKEY:
+ case RECORD_GIF_WINDOW_HOTKEY:
//
// Recording
@@ -4221,7 +4513,7 @@ LRESULT APIENTRY MainWndProc(
if( g_RecordCropping == TRUE )
{
break;
- }
+ }
// Start screen recording
try
@@ -4243,7 +4535,7 @@ LRESULT APIENTRY MainWndProc(
{
// Already recording
break;
- }
+ }
g_RecordCropping = TRUE;
@@ -4334,8 +4626,8 @@ LRESULT APIENTRY MainWndProc(
{
cropRc = {};
- // if we're recording a window, get the window
- if (wParam == RECORD_WINDOW_HOTKEY)
+ // if we're recording a window, get the window
+ if (wParam == RECORD_WINDOW_HOTKEY || wParam == RECORD_GIF_WINDOW_HOTKEY)
{
GetCursorPos(&cursorPos);
hWndRecord = WindowFromPoint(cursorPos);
@@ -4353,6 +4645,7 @@ LRESULT APIENTRY MainWndProc(
if( g_RecordToggle == FALSE )
{
g_RecordToggle = TRUE;
+
#ifdef __ZOOMIT_POWERTOYS__
if( g_StartedByPowerToys )
{
@@ -4379,7 +4672,7 @@ LRESULT APIENTRY MainWndProc(
break;
}
-
+
OutputDebug( L"ZOOM HOTKEY: %d\n", lParam);
if( g_TimerActive ) {
@@ -4444,9 +4737,9 @@ LRESULT APIENTRY MainWndProc(
// Get screen DCs
hdcScreen = CreateDC(L"DISPLAY", static_cast(NULL),
static_cast(NULL), static_cast(NULL));
- hdcScreenCompat = CreateCompatibleDC(hdcScreen);
- hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen);
- hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen);
+ hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen);
// Determine what monitor we're on
GetCursorPos(&cursorPos);
@@ -4461,13 +4754,13 @@ LRESULT APIENTRY MainWndProc(
bmp.bmPlanes = static_cast(GetDeviceCaps(hdcScreen, PLANES));
bmp.bmWidth = width;
bmp.bmHeight = height;
- bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
- hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
+ bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
+ hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL));
- SelectObject(hdcScreenCompat, hbmpCompat);
+ SelectObject(hdcScreenCompat, hbmpCompat);
// Create saved bitmap
- hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
+ hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
bmp.bmPlanes, bmp.bmBitsPixel, static_cast(NULL));
SelectObject(hdcScreenSaveCompat, hbmpDrawingCompat);
@@ -4542,7 +4835,7 @@ LRESULT APIENTRY MainWndProc(
g_TypeMode = TypeModeOff;
g_HaveDrawn = FALSE;
EnableDisableStickyKeys( TRUE );
-
+
// Go full screen
g_ActiveWindow = GetForegroundWindow();
OutputDebug( L"active window: %x\n", PtrToLong(g_ActiveWindow) );
@@ -4552,7 +4845,7 @@ LRESULT APIENTRY MainWndProc(
OutputDebug(L"Calling ShowMainWindow\n");
ShowMainWindow(hWnd, monInfo, width, height);
}
-
+
// Start telescoping zoom. Lparam is non-zero if this
// was a real hotkey and not the message we send ourself to enter
// unzoomed drawing mode.
@@ -4570,7 +4863,7 @@ LRESULT APIENTRY MainWndProc(
OutputDebug(L"Enter liveZoom draw\n");
g_LiveZoomSourceRect = *reinterpret_cast(SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ));
g_LiveZoomLevel = *reinterpret_cast