Compare commits

..

14 Commits

Author SHA1 Message Date
Clint Rutkas
5e6db12251 fix(settings): Remove OnPropertyChanged from background threads to prevent crashes 2026-01-21 13:50:16 -08:00
Clint Rutkas
3b68d5178f perf(settings): Defer AdvancedPaste ViewModel to async init 2026-01-21 13:42:49 -08:00
Clint Rutkas
9301db5225 perf(settings): Defer FancyZones ViewModel to async init 2026-01-21 13:40:49 -08:00
Clint Rutkas
6fd5e4226a perf(settings): Defer PowerRename ViewModel to async init 2026-01-21 13:33:52 -08:00
Clint Rutkas
3f4173501e perf(settings): Defer PowerAccent and Peek ViewModels to async init 2026-01-21 13:30:52 -08:00
Clint Rutkas
e91f7e303f perf(settings): Defer CmdPal file watcher to async init 2026-01-21 13:22:49 -08:00
Clint Rutkas
ca00aca6ce perf(settings): Defer ImageResizer and FileLocksmith ViewModels to async init 2026-01-21 13:21:12 -08:00
Clint Rutkas
46764e36c0 perf(settings): Defer CmdNotFound and KeyboardManager ViewModels to async init 2026-01-21 12:57:49 -08:00
Clint Rutkas
cc03cdc7f9 perf(settings): Defer GeneralViewModel heavy I/O to async init 2026-01-21 12:54:34 -08:00
Clint Rutkas
daf7002fcb docs(settings): Add Settings UI modernization guide 2026-01-21 12:48:21 -08:00
Clint Rutkas
6b68658f56 perf(settings): Defer logger and event waiter init to background threads 2026-01-21 12:47:38 -08:00
Clint Rutkas
5fb1dc6995 test(settings): Add unit tests for DI services and async initialization 2026-01-21 12:46:07 -08:00
Clint Rutkas
2fe9b70bd3 perf(settings): Optimize SearchIndexService with ReaderWriterLockSlim
- Replace lock() with ReaderWriterLockSlim for better concurrent read performance
- Add BuildIndexAsync() method for async index building
- Use read locks for cache lookups, write locks only when adding
- Update ShellPage to use BuildIndexAsync() for cleaner async pattern

This reduces lock contention during search operations since multiple
searches can now read concurrently.
2026-01-21 12:19:56 -08:00
Clint Rutkas
42027a51a4 feat(settings): Add DI infrastructure and page caching for improved performance
- Add Microsoft.Extensions.DependencyInjection and CommunityToolkit.Mvvm packages
- Create AppServices for centralized DI container configuration
- Add INavigationService interface with async navigation support
- Implement NavigationServiceAdapter with page caching (CacheSize=10)
- Enable NavigationCacheMode.Enabled in NavigablePage base class
- Add IIPCService/IPCService for IPC abstraction
- Add ISettingsService/SettingsService for async settings management
- Add IAsyncInitializable interface for async ViewModel initialization
- Update PageViewModelBase with IsLoading/IsInitialized properties
- Override InitializeCoreAsync in DashboardViewModel for async loading

This establishes the foundation for MVVM modernization and improves
navigation performance by caching pages instead of recreating them.
2026-01-21 12:16:50 -08:00
644 changed files with 6227 additions and 48814 deletions

View File

@@ -1 +0,0 @@
../.github/copilot-instructions.md

View File

@@ -1 +0,0 @@
../.github/agents

View File

@@ -1 +0,0 @@
../.github/prompts

View File

@@ -1 +0,0 @@
../.github/instructions

View File

@@ -1 +0,0 @@
../.github/skills

View File

@@ -1,63 +0,0 @@
acq
APPLYTOSUBMENUS
AUDCLNT
bitmaps
BUFFERFLAGS
centiseconds
Ctl
CTLCOLOR
CTLCOLORBTN
CTLCOLORDLG
CTLCOLOREDIT
CTLCOLORLISTBOX
CTrim
DFCS
dlg
dlu
DONTCARE
DRAWITEM
DRAWITEMSTRUCT
DWLP
EDITCONTROL
ENABLEHOOK
FDE
GETCHANNELRECT
GETCHECK
GETTHUMBRECT
GIFs
HTBOTTOMRIGHT
HTHEME
KSDATAFORMAT
LEFTNOWORDWRAP
letterbox
lld
logfont
lround
MENUINFO
mic
MMRESULT
OWNERDRAW
PBGRA
pfdc
playhead
pwfx
quantums
REFKNOWNFOLDERID
reposted
SCROLLSIZEGRIP
SETDEFID
SETRECT
SHAREMODE
SHAREVIOLATION
STREAMFLAGS
submix
tci
TEXTMETRIC
tme
TRACKMOUSEEVENT
Unadvise
WASAPI
WAVEFORMATEX
WAVEFORMATEXTENSIBLE
wil
WMU

View File

@@ -565,7 +565,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
# regex choice
# \(\?:[^)]+\|[^)]+\)
\(\?:[^)]+\|[^)]+\)
# proto
^\s*(\w+)\s\g{-1} =

View File

@@ -101,16 +101,11 @@
^doc/devdocs/akaLinks\.md$
^NOTICE\.md$
^src/common/CalculatorEngineCommon/exprtk\.hpp$
^src/common/UnitTests-CommonUtils/
^src/common/ManagedCommon/ColorFormatHelper\.cs$
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
^src/common/sysinternals/Eula/
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/.*\.TestData\.cs$
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$

View File

@@ -11,7 +11,6 @@ ACCESSDENIED
ACCESSTOKEN
acfs
ACIE
ACR
AClient
AColumn
acrt
@@ -23,6 +22,7 @@ ADate
ADDSTRING
ADDUNDORECORD
ADifferent
adjacents
ADMINS
adml
admx
@@ -45,7 +45,6 @@ ALLCHILDREN
ALLINPUT
Allman
Allmodule
ALLNOISE
ALLOWUNDO
ALLVIEW
ALPHATYPE
@@ -59,6 +58,7 @@ AOC
aocfnapldcnfbofgmbbllojgocaelgdd
AOklab
aot
APARTMENTTHREADED
APeriod
apicontract
apidl
@@ -96,7 +96,6 @@ asf
Ashcraft
AShortcut
ASingle
ASUS
ASSOCCHANGED
ASSOCF
ASSOCSTR
@@ -104,9 +103,9 @@ ASYNCWINDOWPLACEMENT
ASYNCWINDOWPOS
atl
ATRIOX
ATX
aumid
authenticode
AUO
AUTOBUDDY
AUTOCHECKBOX
AUTOHIDE
@@ -124,10 +123,6 @@ azureaiinference
azureinference
azureopenai
backticks
Backlight
Badflags
Badmode
Badparam
bbwe
BCIE
bck
@@ -136,7 +131,6 @@ bezelled
bhid
BIF
bigbar
BIGGERSIZEOK
bigobj
binlog
binres
@@ -201,7 +195,6 @@ Carlseibert
CAtl
caub
CBN
Cds
cch
CCHDEVICENAME
CCHFORMNAME
@@ -221,22 +214,20 @@ checkmarks
CHILDACTIVATE
CHILDWINDOW
CHOOSEFONT
Chunghwa
CIBUILD
cidl
CIELCh
cim
CImage
cla
claude
CLASSDC
classguid
classmethod
CLASSNOTAVAILABLE
claude
CLEARTYPE
clickable
clickonce
clientedge
CLIENTEDGE
clientid
clientside
CLIPBOARDUPDATE
@@ -248,7 +239,6 @@ CLSCTX
clsids
Clusion
cmder
CMN
CMDNOTFOUNDMODULEINTERFACE
cmdpal
CMIC
@@ -272,6 +262,7 @@ colorhistory
colorhistorylimit
COLORKEY
colorref
Convs
comctl
comdlg
comexp
@@ -292,7 +283,6 @@ CONTEXTHELP
CONTEXTMENUHANDLER
contractversion
CONTROLPARENT
Convs
copiedcolorrepresentation
coppied
copyable
@@ -303,12 +293,12 @@ Corpor
cotaskmem
COULDNOT
countof
Cowait
covrun
cpcontrols
cph
cplusplus
CPower
cppcoreguidelines
cpptools
cppvsdbg
cppwinrt
@@ -322,14 +312,11 @@ CRECT
CRH
critsec
cropandlock
crt
CROPTOSQUARE
Crossdevice
csdevkit
CSearch
CSettings
cso
CSOT
CSRW
CStyle
cswin
@@ -340,7 +327,7 @@ CURRENTDIR
CURSORINFO
cursorpos
CURSORSHOWING
cursorwrap
CURSORWRAP
customaction
CUSTOMACTIONTEST
CUSTOMFORMATPLACEHOLDER
@@ -363,23 +350,18 @@ datareader
datatracker
dataversion
Dayof
dbcc
DBID
DBLCLKS
DBLEPSILON
DBPROP
DBPROPIDSET
DBPROPSET
DBT
DCBA
DCapabilities
DCOM
DComposition
DCR
ddc
DDEIf
Deact
debouncer
debugbreak
decryptor
Dedup
@@ -391,13 +373,13 @@ DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTSIZE
defaulttonearest
DEFAULTTONEAREST
Defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
DEFPUSHBUTTON
deinitialization
DELA
DELETEDKEYIMAGE
DELETESCANS
DEMOTYPE
@@ -414,38 +396,31 @@ DESKTOPVERTRES
devblogs
devdocs
devenv
DEVICEINTERFACE
devicetype
DEVINTERFACE
devmgmt
DEVMODE
DEVMODEW
DEVNODES
devpal
DEVTYP
dfx
DIALOGEX
diffs
digicert
diffs
DINORMAL
DISABLEASACTIONKEY
DISABLENOSCROLL
diskmgmt
DISPLAYCHANGE
displayconfig
DISPLAYCONFIG
DISPLAYFLAGS
DISPLAYFREQUENCY
displayname
DISPLAYORIENTATION
diu
divyan
Dlg
DLGFRAME
dlgmodalframe
DLGMODALFRAME
dlib
dllhost
dllmain
Dmdo
DNLEN
DONOTROUND
DONTVALIDATEPATH
@@ -455,7 +430,6 @@ downsampling
downscale
DPICHANGED
DPIs
DPMS
DPSAPI
DQTAT
DQTYPE
@@ -493,19 +467,15 @@ DWMWINDOWMAXIMIZEDCHANGE
DWORDLONG
dworigin
dwrite
Dxva
dxgi
eab
EAccess
easeofaccess
ecount
edid
Edid
EDITKEYBOARD
EDITSHORTCUTS
EDITTEXT
EFile
EInvalid
eep
eku
emojis
ENABLEDELAYEDEXPANSION
@@ -515,15 +485,14 @@ ENABLETEMPLATE
encodedlaunch
encryptor
ENDSESSION
ENot
ENSUREVISIBLE
ENTERSIZEMOVE
ENTRYW
ENU
environmentvariables
EOAC
EPO
epu
EProvider
ERASEBKGND
EREOF
EResize
@@ -577,7 +546,7 @@ fdx
FErase
fesf
FFFF
FFh
FInc
Figma
FILEEXPLORER
fileexploreraddons
@@ -598,7 +567,6 @@ FILESYSPATH
Filetime
FILEVERSION
FILTERMODE
FInc
findfast
findmymouse
FIXEDFILEINFO
@@ -620,13 +588,11 @@ formatetc
FORPARSING
foundrylocal
FRAMECHANGED
Framechanged
FRestore
frm
FROMTOUCH
fsanitize
fsmgmt
ftps
fuzzingtesting
fxf
FZE
@@ -664,7 +630,6 @@ GMEM
GNumber
googleai
googlegemini
Gotchas
gpedit
gpo
GPOCA
@@ -682,8 +647,6 @@ gwl
GWLP
GWLSTYLE
hangeul
Hann
Hantai
Hanzi
Hardlines
hardlinks
@@ -705,14 +668,13 @@ HCRYPTPROV
hcursor
hcwhite
hdc
HDEVNOTIFY
hdr
hdrop
hdwwiz
Helpline
helptext
hgdiobj
HGFE
hgdiobj
hglobal
hhk
HHmmssfff
@@ -742,7 +704,6 @@ HKPD
HKU
HMD
hmenu
HMON
hmodule
hmonitor
homies
@@ -760,7 +721,6 @@ hotkeys
hotlight
hotspot
HPAINTBUFFER
HPhysical
HRAWINPUT
hredraw
hres
@@ -771,7 +731,6 @@ hsb
HSCROLL
hsi
HSpeed
HSync
HTCLIENT
hthumbnail
HTOUCHINPUT
@@ -781,7 +740,6 @@ HVal
HValue
Hvci
hwb
HWP
HWHEEL
HWINEVENTHOOK
hwnd
@@ -792,10 +750,9 @@ HWNDPARENT
HWNDPREV
hyjiacan
IAI
icf
ICONERROR
ICONLOCATION
ICONONLY
icf
IDCANCEL
IDD
idk
@@ -839,7 +796,6 @@ INITTOLOGFONTSTRUCT
INLINEPREFIX
inlines
Inno
Innolux
INPC
inproc
INPUTHARDWARE
@@ -881,21 +837,19 @@ istep
ith
ITHUMBNAIL
IUI
IVO
IUWP
IWIC
jeli
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
jjw
jobject
JOBOBJECT
jobject
jpe
jpnime
Jsons
jsonval
jxr
Kantai
keybd
KEYBDDATA
KEYBDINPUT
@@ -917,7 +871,6 @@ KILLFOCUS
killrunner
kmph
kvp
KVM
Kybd
LARGEICON
lastcodeanalysissucceeded
@@ -933,15 +886,11 @@ Lclean
Ldone
Ldr
LEFTALIGN
leftclick
LEFTSCROLLBAR
LEFTTEXT
LError
LEVELID
LExit
Lenovo
LGD
LFU
lhwnd
LIBFUZZER
LIBID
@@ -981,9 +930,9 @@ LOWORD
lparam
LPBITMAPINFOHEADER
LPCFHOOKPROC
lpch
LPCITEMIDLIST
LPCLSID
lpch
lpcmi
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
@@ -999,7 +948,6 @@ LPMONITORINFO
LPOSVERSIONINFOEXW
LPQUERY
lprc
LPrivate
LPSAFEARRAY
lpstr
lpsz
@@ -1009,6 +957,7 @@ lptpm
LPTR
LPTSTR
lpv
LPrivate
LPW
lpwcx
lpwndpl
@@ -1046,27 +995,24 @@ MAPTOSAMESHORTCUT
MAPVK
MARKDOWNPREVIEWHANDLERCPP
MAXIMIZEBOX
Maximizebox
MAXSHORTCUTSIZE
maxversiontested
mber
MBM
MBR
Mbuttondown
mcp
MDICHILD
MDL
mdtext
mdtxt
mdwn
mccs
meme
mcp
memicmp
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
metafile
@@ -1081,7 +1027,6 @@ mikeclayton
mindaro
Minimizable
MINIMIZEBOX
Minimizebox
MINIMIZEEND
MINIMIZESTART
MINMAXINFO
@@ -1097,8 +1042,8 @@ mmi
mmsys
mobileredirect
mockapi
MODALFRAME
modelcontextprotocol
MODALFRAME
MODESPRUNED
MONITORENUMPROC
MONITORINFO
@@ -1117,8 +1062,7 @@ mouseutils
MOVESIZEEND
MOVESIZESTART
MRM
Mrt
mrt
MRT
mru
MSAL
msc
@@ -1143,10 +1087,9 @@ MSLLHOOKSTRUCT
Mso
msrc
msstore
mstsc
mswhql
msvcp
MT
mstsc
MTND
MULTIPLEUSE
multizone
@@ -1156,13 +1099,12 @@ muxxc
muxxh
MVPs
mvvm
myorg
myrepo
MVVMTK
MWBEx
MYICON
myorg
myrepo
NAMECHANGE
Nanjing
namespaceanddescendants
nao
NCACTIVATE
@@ -1231,7 +1173,6 @@ NOMCX
NOMINMAX
NOMIRRORBITMAP
NOMOVE
Nomove
NONANTIALIASED
nonclient
NONCLIENTMETRICSW
@@ -1253,7 +1194,6 @@ NORMALUSER
NOSEARCH
NOSENDCHANGING
NOSIZE
Nosize
NOTHOUSANDS
NOTICKS
NOTIFICATIONSDLL
@@ -1261,11 +1201,9 @@ NOTIFYICONDATA
NOTIFYICONDATAW
NOTIMPL
NOTOPMOST
Notopmost
NOTRACK
NOTSRCCOPY
NOTSRCERASE
Notupdated
notwindows
NOTXORPEN
nowarn
@@ -1306,10 +1244,11 @@ opencode
OPENFILENAME
openrdp
opensource
openurl
openxmlformats
ollama
onnx
openurl
OPTIMIZEFORINVOKE
Optronics
ORPHANEDDIALOGTITLE
ORSCANS
oss
@@ -1345,7 +1284,6 @@ PATINVERT
PATPAINT
pbc
pbi
PBP
PBlob
pbrush
pcb
@@ -1360,7 +1298,6 @@ PDBs
PDEVMODE
pdisp
PDLL
pdmodels
pdo
pdto
pdtobj
@@ -1383,13 +1320,12 @@ pguid
phbm
phbmp
phicon
PHL
Photoshop
phwnd
pici
pidl
PIDLIST
pii
PII
pinfo
pinvoke
pipename
@@ -1416,8 +1352,6 @@ Popups
POPUPWINDOW
POSITIONITEM
POWERBROADCAST
powerdisplay
POWERDISPLAYMODULEINTERFACE
POWERRENAMECONTEXTMENU
powerrenameinput
POWERRENAMETEST
@@ -1472,7 +1406,6 @@ projectname
PROPERTYKEY
Propset
PROPVARIANT
prot
PRTL
prvpane
psapi
@@ -1500,16 +1433,12 @@ PTOKEN
PToy
ptstr
pui
pvct
PWAs
pwcs
PWSTR
pwsz
pwtd
Qdc
QDC
qdc
QDS
qit
QITAB
QITABENT
@@ -1535,6 +1464,7 @@ rbhid
Rbuttondown
rclsid
RCZOOMIT
remotedesktop
rdp
RDW
READMODE
@@ -1553,9 +1483,7 @@ regfile
REGISTERCLASSFAILED
REGISTRYHEADER
REGISTRYPREVIEWEXT
registryroot
regkey
regroot
regsvr
REINSTALLMODE
releaseblog
@@ -1565,7 +1493,6 @@ remappings
REMAPSUCCESSFUL
REMAPUNSUCCESSFUL
Remotable
remotedesktop
remoteip
Removelnk
renamable
@@ -1599,8 +1526,8 @@ RIGHTSCROLLBAR
riid
RKey
RNumber
rollups
rop
rollups
ROUNDSMALL
ROWSETEXT
rpcrt
@@ -1732,7 +1659,6 @@ sigdn
Signedness
SIGNINGSCENARIO
signtool
SIIGBF
SINGLEKEY
sipolicy
SIZEBOX
@@ -1785,7 +1711,6 @@ srw
srwlock
sse
ssf
Ssn
sszzz
STACKFRAME
stackoverflow
@@ -1797,7 +1722,6 @@ STARTUPINFOW
startupscreen
STATFLAG
STATICEDGE
Staticedge
staticmethod
STATSTG
stdafx
@@ -1834,9 +1758,9 @@ subkeys
sublang
SUBMODULEUPDATE
subresource
swp
Superbar
sut
swe
svchost
SVGIn
SVGIO
@@ -1863,7 +1787,8 @@ SYSKEY
syskeydown
SYSKEYUP
SYSLIB
sysmenu
SYSMENU
Sysmenu
systemai
SYSTEMAPPS
SYSTEMMODAL
@@ -1898,15 +1823,12 @@ TEXTBOXNEWLINE
textextractor
TEXTINCLUDE
tfopen
tgamma
tgz
THEMECHANGED
themeresources
THH
THICKFRAME
Thickframe
THISCOMPONENT
Tianma
throughs
TILEDWINDOW
TILLSON
@@ -1970,9 +1892,9 @@ uitests
UITo
ULONGLONG
Ultrawide
ums
UMax
UMin
ums
uncompilable
UNCPRIORITY
UNDNAME
@@ -1987,13 +1909,13 @@ UNLEN
UNORM
unremapped
Unsubscribes
unsubscribes
unvirtualized
unwide
unzoom
UOffset
UOI
UPDATENOW
UPDATEREGISTRY
updown
UPGRADINGPRODUCTCODE
upscaling
@@ -2020,8 +1942,6 @@ vcamp
vcenter
vcgtq
VCINSTALLDIR
vcp
vcpname
Vcpkg
VCRT
vcruntime
@@ -2034,8 +1954,6 @@ VERIFYCONTEXT
VERSIONINFO
VERTRES
VERTSIZE
VESA
vesa
VFT
vget
vgetq
@@ -2067,7 +1985,6 @@ VSM
vso
vsonline
VSpeed
VSync
vstemplate
vstest
VSTHRD
@@ -2109,7 +2026,7 @@ winapi
winappsdk
windir
WINDOWCREATED
windowedge
WINDOWEDGE
WINDOWINFO
WINDOWNAME
WINDOWPLACEMENT
@@ -2133,12 +2050,12 @@ WINL
winlogon
winmd
winml
WINNT
winres
winrt
winsdk
winsta
WINTHRESHOLD
WINNT
WINVER
winxamlmanager
withinrafael
@@ -2150,7 +2067,6 @@ WKSG
Wlkr
wmain
Wman
wmi
WMI
WMICIM
wmimgmt
@@ -2163,7 +2079,6 @@ WNDCLASSEX
WNDCLASSEXW
WNDCLASSW
WNDPROC
Wndproc
wnode
wom
WORKSPACESEDITOR

View File

@@ -274,18 +274,5 @@ St&yle
# 0x6f677548 is user name but user folder causes a flag
\bx6f677548\b
# Windows API constants and hardware interface terms
\bCOINIT[_A-Z]*\b
\bEOAC[_A-Z]*\b
\b(?:RPC_C_AUTHN_)?WINNT\b
\bUPDATEREGISTRY\b
\b(?:CDS_)?UPDATEREGISTRY\b
# Display interface terms (HDMI, DVI, DisplayPort)
\b(?:HDMI|DVI|DisplayPort)(?:-\d+)?\b
# 2D Region struct names
\bDisplayConfig2?D?Region\b
# Microsoft Store URLs and product IDs
ms-windows-store://\S+

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
---

View File

@@ -1,70 +0,0 @@
---
description: 'Fix active pull request comments with scoped changes'
name: 'fix-pr-active-comments'
agent: 'agent'
argument-hint: 'PR number or active PR URL'
---
# Fix Active PR Comments
## Mission
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
## Scope & Preconditions
- You must have an active pull request context or a provided PR number.
- Only implement simple changes. Do not implement large refactors.
- If required context is missing, request it and stop.
## Inputs
- Required: ${input:pr_number:PR number or URL}
- Optional: ${input:comment_scope:files or areas to focus on}
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
## Workflow
1. Locate all active (unresolved) PR review comments for the given PR.
2. For each comment, classify the change scope:
- Simple change: limited edits, localized fix, low risk, no broad redesign.
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
3. For each large refactor request:
- Do not modify code.
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
4. For each simple change request:
- Implement the fix with minimal edits.
- Run quick checks if needed.
- Commit and push the change.
5. For comments that seem invalid, unclear, or not applicable (even if simple):
- Do not change code.
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
- Consult back to the end user in a friendly, polite tone.
6. Respond to each comment that you fixed:
- Reply in the active conversation.
- Use a polite or friendly tone.
- Keep the response under 200 words.
- Resolve the comment after replying.
## Output Expectations
- Simple fixes: code changes committed and pushed.
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
- Each fixed comment has a reply under 200 words and is resolved.
## Plan File Template
Use this template for each large refactor item:
# Fix Plan: <short title>
## Context
- Comment link:
- Impacted areas:
## Overview Table Template
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
| Comment link | Summary | Reason not applied | Suggested follow-up |
| --- | --- | --- | --- |
| | | | |
## Quality Assurance
- Verify plan file path exists.
- Ensure no code changes were made for large refactor items.
- Confirm replies are under 200 words and comments are resolved.

View File

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

View File

@@ -1,5 +1,6 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
---

View File

@@ -1,5 +1,6 @@
---
agent: 'agent'
model: 'GPT-5.1-Codex-Max'
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
---

View File

@@ -210,11 +210,6 @@
"PowerToys.PowerAccentModuleInterface.dll",
"PowerToys.PowerAccentKeyboardService.dll",
"PowerToys.PowerDisplayModuleInterface.dll",
"WinUI3Apps\\PowerToys.PowerDisplay.dll",
"WinUI3Apps\\PowerToys.PowerDisplay.exe",
"PowerDisplay.Lib.dll",
"WinUI3Apps\\PowerToys.PowerRenameExt.dll",
"WinUI3Apps\\PowerToys.PowerRename.exe",
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
@@ -383,8 +378,6 @@
"UnitsNet.dll",
"UtfUnknown.dll",
"Wpf.Ui.dll",
"WmiLight.dll",
"WmiLight.Native.dll",
"Shmuelie.WinRTServer.dll",
"ToolGood.Words.Pinyin.dll"
],

View File

@@ -35,9 +35,7 @@ stages:
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS18-Preview
${{ else }}:
demands: ImageOverride -equals SHINE-VS18-Latest
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ parameters.platform }}
buildConfigurations: [Release]

View File

@@ -51,9 +51,7 @@ extends:
pool:
name: SHINE-INT-S
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS18-Preview
${{ else }}:
demands: ImageOverride -equals SHINE-VS18-Latest
demands: ImageOverride -equals SHINE-VS17-Preview
os: windows
sdl:
tsa:
@@ -76,9 +74,7 @@ extends:
demands:
# Our INT agents have a large disk mounted at P:\
- ${{ if eq(parameters.useVSPreview, true) }}:
- ImageOverride -equals SHINE-VS18-Latest-Preview
- ${{ else }}:
- ImageOverride -equals SHINE-VS18-Latest
- ImageOverride -equals SHINE-VS17-Preview
os: windows
variables:
IsPipeline: 1 # The installer uses this to detect whether it should pick up localizations
@@ -91,7 +87,6 @@ extends:
official: true
codeSign: true
runTests: false
buildTests: false
signingIdentity:
serviceName: $(SigningServiceName)
appId: $(SigningAppId)

View File

@@ -253,12 +253,11 @@ jobs:
displayName: Build PowerToys main project
inputs:
solution: 'PowerToys.slnx'
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore -graph
/p:RestorePackagesConfig=true
/p:CIBuild=true
/p:BuildTests=${{ parameters.buildTests }}
/bl:$(LogOutputDirectory)\build-0-main.binlog
${{ parameters.additionalBuildOptions }}
$(MSBuildCacheParameters)
@@ -277,7 +276,7 @@ jobs:
condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
inputs:
solution: PowerToys.slnx
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore
/p:Configuration=$(BuildConfiguration)
@@ -339,7 +338,7 @@ jobs:
displayName: Build BugReportTool
inputs:
solution: '**/tools/BugReportTool/BugReportTool.sln'
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore -graph
/p:RestorePackagesConfig=true
@@ -360,7 +359,7 @@ jobs:
displayName: Build StylesReportTool
inputs:
solution: '**/tools/StylesReportTool/StylesReportTool.sln'
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore -graph
/p:RestorePackagesConfig=true
@@ -382,7 +381,7 @@ jobs:
displayName: Publish ${{ project }} for Packaging
inputs:
solution: ${{ project }}
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
/target:Publish
/graph

View File

@@ -82,7 +82,7 @@ jobs:
displayName: Build UI Test Projects
inputs:
solution: '**/*UITest*.csproj'
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore
-graph
@@ -103,7 +103,7 @@ jobs:
displayName: 'Build UI Test Module: ${{ module }}'
inputs:
solution: '**/*${{ module }}*.csproj'
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore
-graph

View File

@@ -49,9 +49,7 @@ stages:
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS18-Preview
${{ else }}:
demands: ImageOverride -equals SHINE-VS18-Latest
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ platform }}
buildConfigurations: [Release]
@@ -59,7 +57,6 @@ stages:
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
runTests: ${{ parameters.runTests }}
buildTests: true
useVSPreview: ${{ parameters.useVSPreview }}
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
@@ -79,9 +76,7 @@ stages:
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS18-Preview
${{ else }}:
demands: ImageOverride -equals SHINE-VS18-Latest
demands: ImageOverride -equals SHINE-VS17-Preview
buildConfigurations: [Release]
official: false
codeSign: false

View File

@@ -29,9 +29,7 @@ stages:
${{ else }}:
name: SHINE-OSS-L
${{ if eq(parameters.useVSPreview, true) }}:
demands: ImageOverride -equals SHINE-VS18-Preview
${{ else }}:
demands: ImageOverride -equals SHINE-VS18-Latest
demands: ImageOverride -equals SHINE-VS17-Preview
buildPlatforms:
- ${{ parameters.platform }}
buildConfigurations: [Release]

View File

@@ -36,7 +36,7 @@ steps:
displayName: Build Shared Support DLLs
inputs:
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
/t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction
/p:RunBuildEvents=true;RestorePackagesConfig=true;CIBuild=true
@@ -75,7 +75,7 @@ steps:
displayName: 💻 Build VNext MSI
inputs:
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore
/t:PowerToysInstallerVNext
@@ -92,7 +92,7 @@ steps:
displayName: 👤 Build VNext MSI
inputs:
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
/t:PowerToysInstallerVNext
/p:RunBuildEvents=false;PerUser=true;BuildProjectReferences=false;CIBuild=true
@@ -143,7 +143,7 @@ steps:
displayName: 💻 Build VNext Bootstrapper
inputs:
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
-restore
/t:PowerToysBootstrapperVNext
@@ -160,7 +160,7 @@ steps:
displayName: 👤 Build VNext Bootstrapper
inputs:
solution: "**/installer/PowerToysSetup.slnx"
vsVersion: 18.0
vsVersion: 17.0
msbuildArgs: >-
/t:PowerToysBootstrapperVNext
/p:PerUser=true;BuildProjectReferences=false;CIBuild=true

View File

@@ -1,16 +1,9 @@
# Build common vswhere base arguments
$vsWhereBaseArgs = @('-latest', '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64')
if ($env:VCWhereExtraVersionTarget) {
# Add version target if specified (e.g., '-version [18.0,19.0)' for VS2026)
$vsWhereBaseArgs += $env:VCWhereExtraVersionTarget.Split(' ')
}
$VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vsWhereBaseArgs -include packages -format xml))
$VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -include packages -format xml))
$VSPackages = $VSInstances.instances.instance.packages.package
$LatestVCPackage = ($VSPackages | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" })
$LatestVCToolsVersion = $LatestVCPackage.version;
$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vsWhereBaseArgs -property 'resolvedInstallationPath')
$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property 'resolvedInstallationPath')
$VCToolsRoot = Join-Path $VSRoot "VC\Tools\MSVC"
# We have observed a few instances where the VC tools package version actually
@@ -31,12 +24,5 @@ If ($Null -Eq (Get-Item $PackageVCToolPath -ErrorAction:Ignore)) {
}
Write-Output "Latest VCToolsVersion: $LatestVCToolsVersion"
# VS2026 (MSVC 14.50+) doesn't need explicit VCToolsVersion - let MSBuild auto-select
$MajorMinorVersion = [Version]::Parse($LatestVCToolsVersion)
If ($MajorMinorVersion.Major -eq 14 -and $MajorMinorVersion.Minor -ge 50) {
Write-Output "VS2026 detected (MSVC 14.50+). Skipping VCToolsVersion override to allow MSBuild auto-selection."
} Else {
Write-Output "Updating VCToolsVersion environment variable for job"
Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion"
}
Write-Output "Updating VCToolsVersion environment variable for job"
Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion"

View File

@@ -90,15 +90,9 @@ if ($noticeMatch.Success) {
$currentNoticePackageList = ""
}
# Test-only packages that are allowed to be in NOTICE.md but not in the build
# (e.g., when BuildTests=false, these packages won't appear in the NuGet list)
$allowedExtraPackages = @(
"- Moq"
)
if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
{
Write-Host -ForegroundColor Yellow "Notice.md does not exactly match NuGet list. Analyzing differences..."
Write-Host -ForegroundColor Red "Notice.md does not match NuGet list."
# Show detailed differences
$generatedPackages = $returnList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | Sort-Object
@@ -111,7 +105,7 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
# Find packages in proj file list but not in NOTICE.md
$missingFromNotice = $generatedPackages | Where-Object { $noticePackages -notcontains $_ }
if ($missingFromNotice.Count -gt 0) {
Write-Host -ForegroundColor Red "MissingFromNotice (ERROR - these must be added to NOTICE.md):"
Write-Host -ForegroundColor Red "MissingFromNotice:"
foreach ($pkg in $missingFromNotice) {
Write-Host -ForegroundColor Red " $pkg"
}
@@ -120,23 +114,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
# Find packages in NOTICE.md but not in proj file list
$extraInNotice = $noticePackages | Where-Object { $generatedPackages -notcontains $_ }
# Filter out allowed extra packages (test-only dependencies)
$unexpectedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -notcontains $_ }
$allowedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -contains $_ }
if ($allowedExtra.Count -gt 0) {
Write-Host -ForegroundColor Green "ExtraInNotice (OK - allowed test-only packages):"
foreach ($pkg in $allowedExtra) {
Write-Host -ForegroundColor Green " $pkg"
}
Write-Host ""
}
if ($unexpectedExtra.Count -gt 0) {
Write-Host -ForegroundColor Red "ExtraInNotice (ERROR - unexpected packages in NOTICE.md):"
foreach ($pkg in $unexpectedExtra) {
Write-Host -ForegroundColor Red " $pkg"
if ($extraInNotice.Count -gt 0) {
Write-Host -ForegroundColor Yellow "ExtraInNotice:"
foreach ($pkg in $extraInNotice) {
Write-Host -ForegroundColor Yellow " $pkg"
}
Write-Host ""
}
@@ -146,17 +127,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
Write-Host " Proj file list has $($generatedPackages.Count) packages"
Write-Host " NOTICE.md has $($noticePackages.Count) packages"
Write-Host " MissingFromNotice: $($missingFromNotice.Count) packages"
Write-Host " ExtraInNotice (allowed): $($allowedExtra.Count) packages"
Write-Host " ExtraInNotice (unexpected): $($unexpectedExtra.Count) packages"
Write-Host " ExtraInNotice: $($extraInNotice.Count) packages"
Write-Host ""
# Fail if there are missing packages OR unexpected extra packages
if ($missingFromNotice.Count -gt 0 -or $unexpectedExtra.Count -gt 0) {
Write-Host -ForegroundColor Red "FAILED: NOTICE.md mismatch detected."
exit 1
} else {
Write-Host -ForegroundColor Green "PASSED: NOTICE.md matches (with allowed test-only packages)."
}
exit 1
}
exit 0

View File

@@ -40,7 +40,7 @@ These instruction files are automatically applied when working in their respecti
### Prerequisites
- Visual Studio 2022 17.4+ or Visual Studio 2026
- Visual Studio 2022 17.4+
- Windows 10 1803+ (April 2018 Update or newer)
- Initialize submodules once: `git submodule update --init --recursive`

View File

@@ -2,12 +2,6 @@
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Skip building C++ test projects when BuildTests=false -->
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<UsePrecompiledHeaders>false</UsePrecompiledHeaders>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<!-- Project configurations -->
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
@@ -57,7 +51,7 @@
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<WarningLevel>Level4</WarningLevel>
<DisableSpecificWarnings>4679;4706;4874;5271;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<DisableSpecificWarnings>4679;5271;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<DisableAnalyzeExternal >true</DisableAnalyzeExternal>
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
<ConformanceMode>false</ConformanceMode>
@@ -116,7 +110,6 @@
<!-- Props that are constant for both Debug and Release configurations -->
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '18.0'">v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<DesktopCompatible>true</DesktopCompatible>
<SpectreMitigation>Spectre</SpectreMitigation>

View File

@@ -30,11 +30,7 @@ _If you want to find diagnostic data events in the source code, these two links
- [C++ events](https://github.com/search?q=repo%3Amicrosoft%2FPowerToys+ProjectTelemetryPrivacyDataTag&type=code)
### General
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -47,18 +43,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.GeneralSettingsChanged</td>
<td>Logs changes made to general settings within PowerToys.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Install_Fail</td>
<td>Triggered when the PowerToys installation process encounters an error and fails to complete.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Repair_Cancel</td>
<td>Triggered when a PowerToys repair operation is cancelled by the user before completion.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Repair_Fail</td>
<td>Triggered when the PowerToys repair operation fails to complete successfully due to an error.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Runner_Launch</td>
<td>Indicates when the PowerToys Runner is launched.</td>
@@ -75,18 +59,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.ScoobeStartedEvent</td>
<td>Triggered when SCOOBE (Secondary Out-of-box experience) starts.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutConflictControlClickedEvent</td>
<td>Triggered when a user clicks on the Shortcut Conflict Control button in the PowerToys Settings UI Dashboard.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutConflictDetectedEvent</td>
<td>Triggered when keyboard shortcut conflicts are detected in the PowerToys Settings UI Dashboard.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutConflictResolvedEvent</td>
<td>Triggered when a keyboard shortcut conflict is resolved in the PowerToys Settings UI.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.TrayFlyoutActivatedEvent</td>
<td>Indicates when the tray flyout menu is activated.</td>
@@ -95,14 +67,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.TrayFlyoutModuleRunEvent</td>
<td>Logs when a utility from the tray flyout menu is run.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.UnInstall_Cancel</td>
<td>Triggered when the PowerToys uninstallation process is cancelled by the user before completion.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.UnInstall_Fail</td>
<td>Triggered when the PowerToys uninstallation process fails to complete successfully due to an error. </td>
</tr>
<tr>
<td>Microsoft.PowerToys.Uninstall_Success</td>
<td>Logs when PowerToys is successfully uninstalled (who would do such a thing!).</td>
@@ -118,19 +82,11 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### OOBE (Out-of-box experience)
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.OobeModuleRunEvent</td>
<td>Triggered when a user clicks to run or launch a PowerToys module directly from the OOBE (out-of-box experience) interface.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.OobeSectionEvent</td>
<td>Occurs when OOBE is shown to the user.</td>
@@ -143,18 +99,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.OobeStartedEvent</td>
<td>Indicates when the out-of-box experience has been initiated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.OobeVariantAssignmentEvent</td>
<td>This event logs A/B testing assignments for experimental features, helping track which users are in control or alternate groups for feature experiments. </td>
</tr>
</table>
### Advanced Paste
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -222,11 +170,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Always on Top
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -246,11 +190,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Awake
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -278,11 +218,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Color Picker
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -299,14 +235,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.ColorPicker_Settings</td>
<td>Triggered when the settings for the Color Picker are accessed or modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ColorPickerCancelledEvent</td>
<td>Occurs when a color picking action is cancelled by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ColorPickerShowEvent</td>
<td>Triggered when the Color Picker UI is displayed on the screen.</td>
</tr>
</table>
### Command Not Found
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -319,6 +259,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td>
<td>Triggered when a Command Not Found is installed.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFoundInstanceCreatedEvent</td>
<td>Occurs when an instance of a Command Not Found is created.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFoundUninstallEvent</td>
<td>Triggered when Command Not Found is uninstalled after being previously installed.</td>
@@ -327,11 +271,7 @@ _If you want to find diagnostic data events in the source code, these two links
### Command Palette
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -395,11 +335,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Crop And Lock
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -408,26 +344,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.CropAndLock_ActivateReparent</td>
<td>Triggered when the cropping interface is activated for reparenting the cropped content.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_ActivateScreenshot</td>
<td>Triggered when the screenshot mode is activated in Crop and Lock.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_ActivateThumbnail</td>
<td>Occurs when the thumbnail view for cropped content is activated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_CreateReparentWindow</td>
<td>Triggered when a reparent window is created in Crop and Lock mode.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_CreateScreenshotWindow</td>
<td>Triggered when a screenshot window is created in Crop and Lock mode.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_CreateThumbnailWindow</td>
<td>Triggered when a thumbnail window is created in Crop and Lock mode.<-/td>
</tr>
<tr>
<td>Microsoft.PowerToys.CropAndLock_EnableCropAndLock</td>
<td>Triggered when Crop and Lock is enabled.</td>
@@ -438,28 +358,8 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### Cursor Wrap
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CursorWrap_EnableCursorWrap</td>
<td>Triggered when Cursor Wrap is enabled or disabled.</td>
</tr>
</table>
### Environment Variables
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -487,11 +387,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### FancyZones
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -508,10 +404,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_EnableFancyZones</td>
<td>Occurs when FancyZones is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_Error</td>
<td>Triggered when an error occurs within the FancyZones module. This event logs critical errors to help diagnose and troubleshoot issues with FancyZones functionality, such as failures to set up Windows hooks or other system-level operations required for window management.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_KeyboardSnapWindowToZone</td>
<td>Triggered when a window is snapped to a zone using the keyboard.</td>
@@ -524,6 +416,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_MoveOrResizeStarted</td>
<td>Triggered when a window move or resize action is initiated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_MoveSizeEnd</td>
<td>Occurs when the moving or resizing of a window has ended.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_OnKeyDown</td>
<td>Triggered when a key is pressed down while interacting with zones.</td>
@@ -536,6 +432,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_Settings</td>
<td>Triggered when FancyZones settings are accessed or modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_SettingsChanged</td>
<td>Occurs when there is a change in the FancyZones settings.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZones_SnapNewWindowIntoZone</td>
<td>Triggered when a new window is snapped into a zone.</td>
@@ -556,50 +456,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.FancyZones_CLICommand</td>
<td>Triggered when a FancyZones CLI command is executed, logging the command name and success status.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZonesEditorStartEvent</td>
<td>Triggered when the FancyZones Editor application starts. This logs the initialization of the editor UI, which is used to create and configure custom zone layouts.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FancyZonesEditorStartFinishEvent</td>
<td>Triggered when the FancyZones Editor has completed loading and is ready for user interaction.</td>
</tr>
</table>
### File Locksmith
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_EnableFileLocksmith</td>
<td>Triggered when File Locksmith is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_Invoked</td>
<td>Occurs when File Locksmith is invoked.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_InvokedRet</td>
<td>Triggered when File Locksmith invocation returns a result.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_QueryContextMenuError</td>
<td>Occurs when there is an error querying the context menu for File Locksmith.</td>
</tr>
</table>
### FileExplorerAddOns
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -636,10 +496,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.MarkdownFilePreviewed</td>
<td>Triggered when a Markdown file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.MarkdownFilePreviewError</td>
<td>Triggered when there is an error previewing a Markdown file in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PdfFileHandlerLoaded</td>
<td>Occurs when a PDF file handler is loaded.</td>
@@ -648,10 +504,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.PdfFilePreviewed</td>
<td>Triggered when a PDF file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PdfFilePreviewError</td>
<td>Triggered when there is an error previewing a PDF file in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PowerPreview_Enabled</td>
<td>Occurs when preview is enabled.</td>
@@ -668,10 +520,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.PowerPreview_TweakUISettings_InitSet__ErrorLoadingFile</td>
<td>Triggered when there is an error loading a file during Tweak UI settings initialization.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PowerPreview_TweakUISettings_SetConfig__InvalidJSONGiven</td>
<td>Triggered when invalid JSON is provided to the Power Preview settings configuration</td>
</tr>
<tr>
<td>Microsoft.PowerToys.PowerPreview_TweakUISettings_SuccessfullyUpdatedSettings</td>
<td>Occurs when the Tweak UI settings for Power Preview are successfully updated.</td>
@@ -680,10 +528,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.QoiFilePreviewed</td>
<td>Triggered when a QOI file is previewed in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.QoiFilePreviewError</td>
<td>Triggered when there is an error previewing a QOI (Quite OK Image) file in File Explorer.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.SvgFileHandlerLoaded</td>
<td>Occurs when an SVG file handler is loaded.</td>
@@ -698,12 +542,32 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### File Locksmith
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_EnableFileLocksmith</td>
<td>Triggered when File Locksmith is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_Invoked</td>
<td>Occurs when File Locksmith is invoked.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_InvokedRet</td>
<td>Triggered when File Locksmith invocation returns a result.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.FileLocksmith_QueryContextMenuError</td>
<td>Occurs when there is an error querying the context menu for File Locksmith.</td>
</tr>
</table>
### Find My Mouse
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -719,11 +583,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Hosts File Editor
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -740,22 +600,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.HostsFileEditorOpenedEvent</td>
<td>Fires when Hosts File Editor is opened.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.HostEditorStartEvent</td>
<td>Triggered when the Hosts File Editor application starts. This logs the initialization of the Hosts File Editor UI with a timestamp.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.HostEditorStartFinishEvent</td>
<td>Triggered when the Hosts File Editor has completed loading and is ready for user interaction.</td>
</tr>
</table>
### Image Resizer
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -772,18 +620,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.ImageResizer_InvokedRet</td>
<td>Fires when the Image Resizer operation is completed and returns a result.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ImageResizer_QueryContextMenuError</td>
<td>Triggered when there is an error querying the context menu for Image Resizer.</td>
</tr>
</table>
### Keyboard Manager
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -796,22 +636,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.KeyboardManager_AppSpecificShortcutRemapCount</td>
<td>Logs the number of application-specific shortcut remaps configured by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_AppSpecificShortcutToKeyRemapInvoked</td>
<td>Logs each instance when an application-specific shortcut-to-key remap is used.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_AppSpecificShortcutToShortcutRemapInvoked</td>
<td>Logs each instance when an application-specific shortcut-to-shortcut remap is used.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_Error</td>
<td>Triggered when an error occurs in Keyboard Manager. This logs the error code, error message, and the method name where the error occurred.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_ErrorSendingKeyAndShortcutRemapLoadedConfiguration</td>
<td>Triggered when there is an error sending remapping configuration telemetry. This occurs when Keyboard Manager fails to report the loaded key and shortcut remap configurations</td>
</tr>
<tr>
<td>Microsoft.PowerToys.KeyboardManager_DailyAppSpecificShortcutToKeyRemapInvoked</td>
<td>Logs the daily count of application-specific shortcut-to-key remaps executed by the user.</td>
@@ -875,11 +703,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Light Switch
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -903,11 +727,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Mouse Highlighter
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -920,18 +740,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.MouseHighlighter_StartHighlightingSession</td>
<td>Occurs when a new highlighting session is started.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.MouseHighlighter_StartSpotlightSession</td>
<td>Triggered when a spotlight session is started in Mouse Highlighter. This occurs when the user activates the spotlight mode.</td>
</tr>
</table>
### Mouse Jump
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -955,11 +767,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Mouse Pointer Crosshairs
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -975,11 +783,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Mouse Without Borders
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1031,11 +835,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### New+
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1067,11 +867,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Peek
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1104,18 +900,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.Peek_Settings</td>
<td>Triggered when the settings for Peek are modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Peek_SpaceModeEnabled</td>
<td>Triggered when the Space key activation mode is enabled or disabled in Peek</td>
</tr>
</table>
### PowerRename
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1147,11 +935,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### PowerToys Run
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1192,14 +976,14 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.RunPluginsSettingsEvent</td>
<td>Triggered when the settings for PowerToys Run plugins are accessed or modified.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.WindowWalker_EnableWindowWalker</td>
<td>Triggered when the Window Walker plugin is enabled.</td>
</tr>
</table>
### Quick Accent
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1215,11 +999,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Registry Preview
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1232,22 +1012,10 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.RegistryPreview_EnableRegistryPreview</td>
<td>Occurs when Registry Preview is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.RegistryPreviewEditorStartEvent</td>
<td>Triggered when the Registry Preview application starts. This logs the initialization of the Registry Preview UI with a timestamp.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.RegistryPreviewEditorStartFinishEvent</td>
<td>Triggered when the Registry Preview application has completed loading and is ready for user interaction.</td>
</tr>
</table>
### Screen Ruler
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1267,11 +1035,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Shortcut Guide
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1287,11 +1051,7 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Text Extractor
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
@@ -1315,15 +1075,15 @@ _If you want to find diagnostic data events in the source code, these two links
</table>
### Workspaces
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.Projects_CLIUsage</td>
<td>Logs usage of command-line arguments for launching apps.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.Workspaces_CreateEvent</td>
<td>Triggered when a new workspace is created.</td>
@@ -1345,21 +1105,13 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Triggered when a workspace is launched.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.WorkspacesEditorStartEvent</td>
<td>Triggered when the Workspaces Editor application starts. This logs the initialization of the Workspaces Editor UI with a timestamp.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.WorkspacesEditorStartFinishEvent</td>
<td>Triggered when the Workspaces Editor has completed loading and is ready for user interaction.</td>
<td>Microsoft.PowerToys.Workspaces_Settings</td>
<td>Logs changes to workspaces settings.</td>
</tr>
</table>
### ZoomIt
<table style="width:100%; table-layout:fixed">
<colgroup>
<col style="width:40%">
<col style="width:60%">
</colgroup>
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>

View File

@@ -19,39 +19,6 @@
<PlatformTarget>$(Platform)</PlatformTarget>
</PropertyGroup>
<!--
Completely skip building test projects when BuildTests=false (e.g., Release pipeline).
This avoids InternalsVisibleTo/signing issues by not compiling test code at all.
Match: projects ending in Test, Tests, UnitTests, UITests, FuzzTests, or in a folder named Tests.
Also matches projects starting with UnitTests- (e.g., UnitTests-CommonLib).
Also removes all PackageReference/ProjectReference to prevent NuGet restore and dependency builds.
Note: Checking both 'false' and 'False' to handle YAML boolean serialization.
-->
<PropertyGroup Condition="'$(BuildTests)' == 'false' or '$(BuildTests)' == 'False'">
<_ProjectName>$(MSBuildProjectName)</_ProjectName>
<!-- Match any project ending with "Test" or "Tests" (covers UnitTests, UITests, FuzzTests, etc.) -->
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Test'))">true</_IsSkippedTestProject>
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Tests'))">true</_IsSkippedTestProject>
<!-- Match projects starting with UnitTests- or UITest- prefix -->
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UnitTests-'))">true</_IsSkippedTestProject>
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UITest-'))">true</_IsSkippedTestProject>
<!-- Match projects in a Tests folder -->
<_IsSkippedTestProject Condition="$(MSBuildProjectDirectory.Contains('\Tests\'))">true</_IsSkippedTestProject>
</PropertyGroup>
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<EnableDefaultItems>false</EnableDefaultItems>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateGlobalUsings>false</GenerateGlobalUsings>
<ImplicitUsings>disable</ImplicitUsings>
<!-- Disable all code analysis for skipped test projects -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<RunAnalyzers>false</RunAnalyzers>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<Version>$(Version).0</Version>
<RepositoryUrl>https://github.com/microsoft/PowerToys</RepositoryUrl>
@@ -63,9 +30,7 @@
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
</PropertyGroup>
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj' and '$(_IsSkippedTestProject)' != 'true'">
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -28,41 +28,4 @@
<PropertyGroup Condition="'$(IgnoreExperimentalWarnings)' == 'true'">
<NoWarn>$(NoWarn);CS8305;SA1500;CA1852</NoWarn>
</PropertyGroup>
<!-- Skipped test projects when BuildTests=false: no-op build and remove references.
This must be in targets (not props) so it runs AFTER the project file adds its items. -->
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<BuildDependsOn />
<CoreBuildDependsOn />
<RebuildDependsOn />
</PropertyGroup>
<!-- For C# projects: remove all items -->
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.csproj'">
<PackageReference Remove="@(PackageReference)" />
<ProjectReference Remove="@(ProjectReference)" />
<Reference Remove="@(Reference)" />
<Compile Remove="@(Compile)" />
<Content Remove="@(Content)" />
<EmbeddedResource Remove="@(EmbeddedResource)" />
<None Remove="@(None)" />
<Using Remove="@(Using)" />
<GlobalUsing Remove="@(GlobalUsing)" />
</ItemGroup>
<!-- For C++ projects (vcxproj): remove all compile/link items to prevent build -->
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.vcxproj'">
<ClCompile Remove="@(ClCompile)" />
<ClInclude Remove="@(ClInclude)" />
<Link Remove="@(Link)" />
<Lib Remove="@(Lib)" />
<ProjectReference Remove="@(ProjectReference)" />
<None Remove="@(None)" />
<ResourceCompile Remove="@(ResourceCompile)" />
<Midl Remove="@(Midl)" />
</ItemGroup>
<!-- Note: For C++ skipped test projects, build is effectively skipped by removing all compile items above.
We don't define empty Build/Rebuild/Clean targets here because MSBuild Target definitions with Condition
on the Target element still override the default targets even when condition is false. -->
</Project>
</Project>

View File

@@ -93,7 +93,6 @@
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
<PackageVersion Include="OpenAI" Version="2.5.0" />
<PackageVersion Include="Polly.Core" Version="8.6.5" />
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
<PackageVersion Include="RtfPipe" Version="2.0.7677.4303" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
@@ -105,7 +104,6 @@
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="9.0.10" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="9.0.10" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="9.0.10" />
@@ -135,7 +133,6 @@
<PackageVersion Include="UnitsNet" Version="5.56.0" />
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
<PackageVersion Include="WinUIEx" Version="2.8.0" />
<PackageVersion Include="WmiLight" Version="6.14.0" />
<PackageVersion Include="WPF-UI" Version="3.0.5" />
<PackageVersion Include="WyHash" Version="1.0.5" />
<PackageVersion Include="WixToolset.Heat" Version="5.0.2" />

View File

@@ -10,7 +10,6 @@ This software incorporates material from third parties.
- Installer/Runner
- Measure tool
- Peek
- PowerDisplay
- Registry Preview
## Utility: Color Picker
@@ -1520,35 +1519,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## Utility: PowerDisplay
### Twinkle Tray
PowerDisplay's DDC/CI implementation references techniques from Twinkle Tray.
**Source**: https://github.com/xanderfrangos/twinkle-tray
MIT License
Copyright © 2020 Xander Frangos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## NuGet Packages used by PowerToys
@@ -1587,7 +1557,6 @@ SOFTWARE.
- NLog.Extensions.Logging
- NLog.Schema
- OpenAI
- Polly.Core
- ReverseMarkdown
- ScipBe.Common.Office.OneNote
- SharpCompress
@@ -1600,6 +1569,5 @@ SOFTWARE.
- UnitsNet
- UTF.Unknown
- WinUIEx
- WmiLight
- WPF-UI
- WyHash

View File

@@ -55,7 +55,6 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj" Id="1a066c63-64b3-45f8-92fe-664e1cce8077" />
<Project Path="src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj" Id="8b5cfb38-ccba-40a8-ad7a-89c57b070884" />
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
</Folder>
@@ -301,10 +300,6 @@
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Tests/">
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Microsoft.CmdPal.Core.Common.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Apps.UnitTests/Microsoft.CmdPal.Ext.Apps.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -361,10 +356,6 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Tests/Microsoft.CommandPalette.Extensions.Toolkit.UnitTests/Microsoft.CommandPalette.Extensions.Toolkit.UnitTests.csproj" Id="2eca18b7-33b7-4829-88f1-439b20fd60f6">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/UI/">
<Project Path="src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj">
@@ -685,23 +676,6 @@
<Deploy />
</Project>
</Folder>
<Folder Name="/modules/PowerDisplay/">
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib/PowerDisplay.Lib.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/powerdisplay/PowerDisplayModuleInterface/PowerDisplayModuleInterface.vcxproj" Id="d1234567-8901-2345-6789-abcdef012345" />
</Folder>
<Folder Name="/modules/PowerDisplay/Tests/">
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/PowerDisplay.Lib.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/MeasureTool/">
<Project Path="src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj" Id="54a93af7-60c7-4f6c-99d2-fbb1f75f853a">
<BuildDependency Project="src/common/Display/Display.vcxproj" />

View File

@@ -51,19 +51,19 @@ But to get started quickly, choose one of the installation methods below:
Go to the <a href="https://aka.ms/installPowerToys">PowerToys GitHub releases</a>, 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.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.98%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysUserSetup-0.97.1-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysUserSetup-0.97.1-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysSetup-0.97.1-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysSetup-0.97.1-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.97.0/PowerToysUserSetup-0.97.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysUserSetup-0.97.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysSetup-0.97.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.0/PowerToysSetup-0.97.0-arm64.exe
| Description | Filename |
|----------------|----------|
| Per user - x64 | [PowerToysUserSetup-0.97.1-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.97.1-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.97.1-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.97.1-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.97.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.97.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.97.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.97.0-arm64.exe][ptMachineArm64] |
</details>
@@ -103,38 +103,18 @@ There are <a href="https://learn.microsoft.com/windows/powertoys/install#communi
</details>
## ✨ What's new
**Version 0.97.1 (January 2026)**
**Version 0.97 (January 2026)**
This patch release fixes several important stability issues identified in v0.97.0 based on incoming reports. Check out the [v0.97.0](https://github.com/microsoft/PowerToys/releases/tag/v0.97.0) notes for the full list of changes.
For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
**Highlights**
### Advanced Paste
- #44862: Fixed Settings UI advanced paste page crash by using correct settings repository for null checking.
### Command Palette
- #44886: Fixed personalization section not appearing by using latest MSIX for installation.
- #44938: Fixed loading of icons from internet shortcuts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- #45076: Fixed potential deadlock from lazy-loading AppListItem details. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Cursor Wrap
- #44936: Added improved multi-monitor support; Added laptop lid close detection for dynamic monitor topology updates. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
- #44936: Added new settings dropdown to constrain wrapping to horizontal-only, vertical-only, or both directions. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
### Peek
- #44995: Fixed Space key triggering Peek during file rename, search, or address bar typing.
### PowerRename
- #44944: Fixed regex `$` not working, preventing users from adding text at the end of filenames.
### Runner
- #44931: Monochrome tray icon now adapts to Windows system theme instead of app theme.
- #44982: Fixed right-click menu to dynamically update based on Quick Access enabled/disabled state.
### GPO / Enterprise
- #45028: Added CursorWrap policy definition to ADMX templates. Thanks [@htcfreek](https://github.com/htcfreek)!
For the full list of v0.97 changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
**Highlights**
- **Command Palette**: Major expansion with PowerToys extension (Windows 11 only), Remote Desktop built-in extension, theme customization, drag-and-drop support, fallback ranking controls, sections/separators for pages, pinyin Chinese matching, and many UX refinements.
- **Settings**: Quick Access flyout is now a standalone process for significantly faster startup, theme-adaptive tray icon, AOT serialization, and multiple UI/accessibility fixes
- **CursorWrap (New!)**: New mouse utility that lets your cursor wrap around screen edges, making multi-monitor navigation faster and more seamless.
- **Advanced Paste**: Image input for AI, color detection in clipboard history, Foundry Local improvements, Azure AI icons, and multiple bug fixes
- **CLI Support Expanded**: FancyZones, Image Resizer, and File Locksmith can now be controlled from the command line for layout management, batch image resizing, and file lock inspection.
- **LightSwitch**: Added support for automatically following Windows Night Light mode.
- **Release Experience & Quality**: Refreshed "Whats new" dialog, plus many performance improvements, stability fixes, and refinements across PowerToys.
## Advanced Paste
@@ -309,7 +289,7 @@ For the full list of v0.97 changes, visit the [Windows Command Line blog](https:
- Stabilized FancyZones UI tests with more reliable selectors and screen recordings.
## 🛣️ Roadmap
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.98][github-next-release-work]!
We are planning some nice new features and improvements for the next releases PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.97][github-next-release-work]!
## ❤️ PowerToys Community
The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldn't be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Your contributions and feedback improve PowerToys month after month!

View File

@@ -88,7 +88,7 @@
### Building PowerToys Locally
#### One stop script for building installer
1. Open `Developer Powershell for VS 2022` or `Developer PowerShell for VS` for VS 2026.
1. Open developer powershell for vs 2022
2. Run tools\build\build-installer.ps1
> For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages.
@@ -109,7 +109,7 @@ dotnet tool install --global wix --version 5.0.2
##### From the command line
1. From the start menu, open a `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS`
1. From the start menu, open a `Developer Command Prompt for VS 2022`
1. Ensure `nuget.exe` is in your `%path%`
1. In the repo root, run these commands:
@@ -140,7 +140,7 @@ If you prefer, you can alternatively build prerequisite projects for the install
The resulting installer will be available in the `installer\PowerToysSetupVNext\x64\Release\` folder.
To build the installer from the command line, run `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
To build the installer from the command line, run `Developer Command Prompt for VS 2022` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`.
```
git clean -xfd -e *exe -- .\installer\

View File

@@ -15,7 +15,7 @@ Before you can start debugging PowerToys, you need to set up your development en
You can build the entire solution from the command line, which is sometimes faster than building within Visual Studio:
1. Open `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS`
1. Open Developer Command Prompt for VS 2022
2. Navigate to the repository root directory
3. Run the following command(don't forget to set the correct platform):
```pwsh
@@ -105,7 +105,7 @@ If you encounter build errors about missing image files (e.g., `.png`, `.ico`, o
1. **Clean the solution in Visual Studio**: Build > Clean Solution
Or from the command line (Developer Command Prompt for VS 2022 or Developer Command Prompt for VS):
Or from the command line (Developer Command Prompt for VS 2022):
```pwsh
msbuild PowerToys.slnx /t:Clean /p:Platform=x64 /p:Configuration=Debug
```

View File

@@ -15,11 +15,9 @@ VS Code extensions Needed:
---
## Building in VS Code
### Configure Developer Powershell for VS 2022 or Developer Powershell for VS for more convenient dev in vscode.
### Configure developer powershell for vs2022 for more convenient dev in vscode.
1. Configure profile in in settings, entry: "terminal.integrated.profiles.windows"
2. Add below config as entry (choose VS 2022 or VS 2026 based on your installation):
**For Visual Studio 2022:**
2. Add below config as entry:
```json
"Developer PowerShell for VS 2022": {
// Configure based on your preference
@@ -29,35 +27,16 @@ VS Code extensions Needed:
"-Command",
"& {",
"$orig = Get-Location;",
// Adjust path based on your edition (Community/Professional/Enterprise)
// Configure based on your environment
"& 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
"Set-Location $orig",
"}"
]
},
```
3. [Optional] Set Developer PowerShell for VS 2022 as your default profile, so that you can get a deep integration with vscode coding agent.
**For Visual Studio 2026:**
```json
"Developer PowerShell for VS": {
// Configure based on your preference
"path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe",
"args": [
"-NoExit",
"-Command",
"& {",
"$orig = Get-Location;",
// Adjust path based on your edition (Community/Professional/Enterprise)
"& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';",
"Set-Location $orig",
"}"
]
},
```
3. [Optional] Set your Developer PowerShell profile as the default, so that you can get a deep integration with vscode coding agent.
4. Now you can build with plain `msbuild` or configure tasks.json in below section.
4. Now You can build with plain `msbuild` or configure tasks.json in below section
Or reach out to "tools\build\BUILD-GUIDELINES.md"
### Sample plain msbuild command

View File

@@ -1,311 +0,0 @@
# 🧭 Creating a new PowerToy: end-to-end developer guide
First of all, thank you for wanting to contribute to PowerToys. The work we do would not be possible without the support of community supporters like you.
This guide documents the process of building a new PowerToys utility from scratch, including architecture decisions, integration steps, and common pitfalls.
---
## 1. Overview and prerequisites
A PowerToy module is a self-contained utility integrated into the PowerToys ecosystem. It can be UI-based, service-based, or both.
### Requirements
- [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) and the following workloads/individual components:
- Desktop Development with C++
- WinUI application development
- .NET desktop development
- Windows 10 SDK (10.0.22621.0)
- Windows 11 SDK (10.0.26100.3916)
- .NET 8 SDK
- Fork the [PowerToys repository](https://github.com/microsoft/PowerToys/tree/main) locally
- [Validate that you are able to build and run](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md) `PowerToys.slnx`.
Optional:
- [WiX v5 toolset](https://github.com/microsoft/PowerToys/tree/main) for the installer
> [!NOTE]
> To ensure all the correct VS Workloads are installed, use [the WinGet configuration files](https://github.com/microsoft/PowerToys/tree/e13d6a78aafbcf32a4bb5f8581d041e1d057c3f1/.config) in the project repository. (Use the one that matches your VS distribution. ie: VS Community would use `configuration.winget`)
### Folder structure
```
src/
modules/
your_module/
YourModule.sln
YourModuleInterface/
YourModuleUI/ (if needed)
YourModuleService/ (if needed)
```
---
## 2. Design and planning
### Decide the type of module
Think about how your module works and which existing modules behave similarly. You are going to want to think about the UI needed for the application, the lifecycle, whether it is a service that is always running or event based. Below are some basic scenarios with some modules to explore. You can write your application in C++ or C#.
- **UI-only:** e.g., ColorPicker
- **Background service:** e.g., LightSwitch, Awake
- **Hybrid (UI + background logic):** e.g., ShortcutGuide
- **C++/C# interop:** e.g., PowerRename
### Write your module interface
Begin by setting up the [PowerToy module template project](https://github.com/microsoft/PowerToys/tree/main/tools/project_template). This will generate boilerplate for you to begin your new module. Below are the key headers in the Module Interface (`dllmain.cpp`) and an explanation of their purpose:
1. This is where module settings are defined. These can be anything from strings, bools, ints, and even custom Enums.
```c++
struct ModuleSettings {};
```
2. This is the header for the full class. It inherits the PowerToyModuleIface
```c++
class ModuleInterface : public PowertoyModuleIface
{
private:
// the private members of the class
// Can include the enabled variable, logic for event handlers, or hotkeys.
public:
// the public members of the class
// Will include the constructor and initialization logic.
}
```
> [!NOTE]
> Many of the class functions are boilerplate and need simple string replacements with your module name. The rest of the functions below will require bigger changes.
3. GPO stands for "Group Policy Object" and allows for administrators to configure settings across a network of machines. It is required that your module is on this list of settings. You can right click the `powertoys_gpo` object to go to the definition and set up the `getConfiguredModuleEnabledValue` for your module.
```c++
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredModuleEnabledValue();
}
```
4. `init_settings()` initializes the settings for the interface. Will either pull from existing settings.json or use defaults.
```c++
void ModuleInterface::init_settings()
```
5. `get_config` retrieves the settings from the settings.json file.
```c++
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
```
6. `set_config` sets the new settings to the settings.json file.
```c++
virtual void set_config(const wchar_t* config) override
```
7. `call_custom_action` allows custom actions to be called based on signals from the settings app.
```c++
void call_custom_action(const wchar_t* action) override
```
8. Lifecycle events control whether the module is enabled or not, as well as the default status of the module.
```c++
virtual void enable() // starts the module
virtual void disable() // terminates the module and performs any cleanup
virtual bool is_enabled() // returns if the module is currently enabled
virtual bool is_enabled_by_default() const override // allows the module to dictate whether it should be enabled by default in the PowerToys app.
```
9. Hotkey functions control the status of the hotkey.
```c++
// takes the hotkey from settings into a format that the interface can understand
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
// returns the hotkeys from settings
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
// performs logic when the hotkey event is fired
virtual bool on_hotkey(size_t hotkeyId) override
```
### Notes
- Keep module logic isolated under `/modules/<YourModule>`
- Use shared utilities from [`common`](https://github.com/microsoft/PowerToys/tree/main/src/common) instead of cross-module dependencies
- init/set/get config use preset functions to access the settings. Check out the [`settings_objects.h`](https://github.com/microsoft/PowerToys/blob/main/src/common/SettingsAPI/settings_helpers.h) in `src\common\SettingsAPI`
---
## 3. Bootstrapping your module
1. Use the [template](https://github.com/microsoft/PowerToys/tree/main/tools/project_template) to generate the module interface starter code.
2. Update all projects and namespaces with your module name.
3. Update GUIDs in `.vcxproj` and solution files.
4. Update the functions mentioned in the above section with your custom logic.
5. In order for your module to be detected by the runner you are required to add references to various lists. In order to register your module, add the corresponding module reference to the lists that can be found in the following files. (Hint: search other modules names to find the lists quicker)
- `src/runner/modules.h`
- `src/runner/modules.cpp`
- `src/runner/resource.h`
- `src/runner/settings_window.h`
- `src/runner/settings_window.cpp`
- `src/runner/main.cpp`
- `src/common/logger.h` (for logging)
6. ModuleInterface should build your `ModuleInterface.dll`. This will allow the runner to interact with your service.
> [!TIP]
> Mismatched module IDs are one of the most common causes of load failures. Keep your ID consistent across manifest, registry, and service.
---
## 4. Write your service
This is going to look different for every PowerToy. It may be easier to develop the application independently, and then link in the PowerToys settings logic later. But you have to write the service first, before connecting it to the runner.
### Notes
- This is a separate project from the Module Interface.
- You can develop this project using C# or C++.
- Set the service icon using the `.rc` file.
- Set the service name in the `.vcxproj` by setting the `<TargetName>`
```
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutDir>
<TargetName>PowerToys.LightSwitchService</TargetName>
</PropertyGroup>
```
- To view the code of the `.vcxproj`, right click the item and select **Unload project**
- Use the following functions to interact with settings from your service
```
ModuleSettings::instance().InitFileWatcher();
ModuleSettings::instance().LoadSettings();
auto& settings = ModuleSettings::instance().settings();
```
These come from the `ModuleSettings.h` file that lives with the Service. You can copy this from another module (e.g., Light Switch) and adjust to fit your needs.
If your module has a user interface:
- Use the **WinUI Blank App** template when setting up your project
- Use [Windows design best practices](https://learn.microsoft.com/windows/apps/design/basics/)
- Use the [WinUI 3 Gallery](https://apps.microsoft.com/detail/9p3jfpwwdzrc) for help with your UI code, and additional guidance.
## 5. Settings integration
PowerToys settings are stored per-module as JSON under:
```
%LOCALAPPDATA%\Microsoft\PowerToys\<module>\settings.json
```
### Implementation steps
- In `src\settings-ui\Settings.UI.Library\` create `<module>Properties.cs` and `<module>Settings.cs`
- `<module>Properties.cs` is where you will define your defaults. Every setting needs to be represented here. This should match what was set in the Module Interface.
- `<module>Settings.cs`is where your settings.json will be built from. The structure should match the following
```cs
public ModuleSettings()
{
Name = ModuleName;
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
Properties = new ModuleProperties(); // settings properties you set above.
}
```
- In `src\settings-ui\Settings.UI\ViewModels` create `<module>ViewModel.cs` this is where the interaction happens between your settings page in the PowerToys app and the settings file that is stored on the device. Changes here will trigger the settings watcher via a `NotifyPropertyChanged` event.
- Create a `SettingsPage.xaml` at `src\settings-ui\Settings.UI\SettingsXAML\Views`. This will be the page where the user interacts with the settings of your module.
- Be sure to use resource strings for user facing strings so they can be localized. (`x:Uid` connects to Resources.resw)
```xaml
// LightSwitch.xaml
<ComboBoxItem
x:Uid="LightSwitch_ModeOff"
AutomationProperties.AutomationId="OffCBItem_LightSwitch"
Tag="Off" />
// Resources.resw
<data name="LightSwitch_ModeOff.Content" xml:space="preserve">
<value>Off</value>
</data>
```
> [!IMPORTANT]
> In the above example we use `.Content` to target the content of the Combobox. This can change per UI element (e.g., `.Text`, `.Header`, etc.)
> **Reminder:** Manual changes via external editors (VS Code, Notepad) do **not** trigger the settings watcher. Only changes written through PowerToys trigger reloads.
---
### Gotchas:
- Only use the WinUI 3 framework, _not_ UWP.
- Use [`DispatcherQueue`](https://learn.microsoft.com/windows/apps/develop/dispatcherqueue) when updating UI from non-UI threads.
---
## 6. Building and debugging
### Debugging steps
1. If this is your first time debugging PowerToys, be sure to follow [these steps first](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md#pre-debugging-setup).
2. Set "runner" as the start up project and ensure your build configuration is set to match your system (ARM64/x64)
3. Select <kbd>F5</kbd> or the **Local Windows Debugger** button to begin debugging. This should start the PowerToys runner.
4. To set breakpoints in your service, select Ctrl+Alt+P and search for your service to attach to the runner.
5. Use logs to document changes. The logs live at `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs` and `%LOCALAPPDATA%\Microsoft\PowerToys\Module\Service\<version>` for the specific module.
> [!TIP]
> PowerToys caches `.nuget` artifacts aggressively. Use `git clean -xfd` when builds behave unexpectedly.
---
## 7. Installer and packaging (WiX)
### Add your module to installer
1. Install [`WixToolset.Heat`](https://www.nuget.org/packages/WixToolset.Heat/) for Wix5 via nuget
2. Inside `installer\PowerToysInstallerVNext` add a new file for your module: `Module.wxs`
3. Inside of this file you will need copy the format from another module (ie: Light Switch) and replace the strings and GUID values.
4. The key part will be `<!--ModuleNameFiles_Component_Def-->` which is a placeholder for code that will be generated by `generateFileComponents.ps1`.
5. Inside `Product.wxs` add a line item in the `<Feature Id="CoreFeature" ... >` section. It will look like a list of ` <ComponentGroupRef Id="ModuleComponentGroup" />` items.
6. Inside `generateFileComponents.ps1` you will need to add an entry to the bottom for your new module. It will follow the following format. `-fileListName <Module>Files` will match the string you set in `Module.wxs`, `<ModuleServiceName>` will match the name of your exe.
```bash
# Module Name
Generate-FileList -fileDepsJson "" -fileListName <Module>Files -wxsFilePath $PSScriptRoot\<Module>.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\<ModuleServiceName>"
Generate-FileComponents -fileListName "<Module>Files" -wxsFilePath $PSScriptRoot\<Module>.wxs -regroot $registryroot
```
---
## 8. Testing and validation
### UI tests
- Place under `/modules/<YourModule>/Tests`
- Create a new [WinUI Unit Test App](https://learn.microsoft.com/windows/apps/winui/winui3/testing/create-winui-unit-test-project)
- Write unit tests following the format from previous modules (ie: Light Switch). This can be to test your standalone UI (if you're a module like Color Picker) or to verify that the Settings UI in the PowerToys app is controlling your service.
### Manual validation
- Enable/disable in PowerToys Settings
- Check initialization in logs
- Confirm icons, tooltips, and OOBE page appear correctly
### Pro tips
1. Validate wake/sleep and elevation states. Background modules often fail silently after resume if event handles arent recreated.
2. Use Windows Sandbox to simulate clean install environments
3. To simulate a "new user" you can delete the PowerToys folder from `%LOCALAPPDATA%\Microsoft`
### Shortcut conflict detection
If your module has a shortcut, ensure that it is properly registered following [the steps listed in the documentation](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/core/settings/settings-implementation.md#shortcut-conflict-detection) for conflict detection.
---
## 9. The final touches
### Out-of-Box experience (OOBE) page
The OOBE page is a custom settings page that gives the user at a glance information about each module. This window opens before the Settings application for new users and after updates. Create `OOBE<ModuleName>.xaml` at `src\settings-ui\Settings.UI\SettingsXAML\OOBE\Views`. You will also need to add your module name to the enum at `src\settings-ui\Settings.UI\OOBE\Enums\PowerToysModules.cs`.
### Module assets
Now that your PowerToy is _done_ you can start to think about the assets that will represent your module.
- Module Icon: This will be displayed in a number of places: OOBE page, in the README, on the home screen of PowerToys, on your individual module settings page, etc.
- Module Image: This is the image you see at the top of each individual settings page.
- OOBE Image: This is the header you see on the OOBE page for each module
> [!NOTE]
> This step is something that the Design team will handle internally to ensure consistency throughout the application. If you have ideas or recommendations on what the icon or screenshots should be for your module feel free to leave it in the "Additional Comments" section of the PR and the team will take it into consideration.
### Documentation
There are two types of documentation that will be required when submitting a new PowerToy:
1. Developer documentation: This will live in the [PowerToys repo](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/modules) at `/doc/devdocs/modules/` and should tell a developer how to work on your app. It should outline the module architecture, key files, testing, and tips on debugging if necessary.
2. Microsoft Learn documentation: When your new Module is ready to be merged into the PowerToys repository, an internal team member will create Microsoft Learn documentation so that users will understand how to use your module. There is not much work on your end as the developer for this step, but keep an eye on your PR in case we need more information about your PowerToy for this step.
---
Thank you again for contributing! If you need help, feel free to [open an issue](https://github.com/microsoft/PowerToys/issues/new/choose) and use the `Needs-Team-Response` label so we know you need attention.

View File

@@ -18,28 +18,13 @@ Advanced Paste is a PowerToys module that provides enhanced clipboard pasting wi
TODO: Add implementation details
### Paste with AI Preview
The "Show preview" setting (`ShowCustomPreview`) controls whether AI-generated results are displayed in a preview window before pasting. **The preview feature does not consume additional AI credits**—the preview displays the same AI response that was already generated, cached locally from a single API call.
The implementation flow:
1. User initiates "Paste with AI" action
2. A single AI API call is made via `ExecutePasteFormatAsync`
3. The result is cached in `GeneratedResponses`
4. If preview is enabled, the cached result is displayed in the preview UI
5. User can paste the cached result without any additional API calls
See the `ExecutePasteFormatAsync(PasteFormat, PasteActionSource)` method in `OptionsViewModel.cs` for the implementation.
## Debugging
TODO: Add debugging information
## Settings
| Setting | Description |
|---------|-------------|
| `ShowCustomPreview` | When enabled, shows AI-generated results in a preview window before pasting. Does not affect AI credit consumption. |
TODO: Add settings documentation
## Future Improvements

View File

@@ -152,7 +152,7 @@ FancyZones is divided into several projects:
## Development Environment Setup
### Prerequisites
- Visual Studio 2022 or 2026: Required for building and debugging
- Visual Studio 2022: Required for building and debugging
- Windows 10 SDK: Ensure the latest version is installed
- PowerToys Repository: Clone from GitHub
@@ -183,7 +183,7 @@ FancyZones is divided into several projects:
## Debugging
### Setup for Debugging
1. In Visual Studio 2022 or 2026, set FancyZonesEditor as the startup project
1. In Visual Studio 2022, set FancyZonesEditor as the startup project
2. Set breakpoints in the code where needed
3. Click Run to start debugging

File diff suppressed because it is too large Load Diff

View File

@@ -1,223 +0,0 @@
# MCCS Capabilities String Parser - Recursive Descent Design
## Overview
This document describes the recursive descent parser implementation for DDC/CI MCCS (Monitor Control Command Set) capabilities strings.
### Attention!
This document and the code implement are generated by Copilot.
## Grammar Definition (BNF)
```bnf
capabilities ::= ['('] segment* [')']
segment ::= identifier '(' segment_content ')'
segment_content ::= text | vcp_entries | hex_list
vcp_entries ::= vcp_entry*
vcp_entry ::= hex_byte [ '(' hex_list ')' ]
hex_list ::= hex_byte*
hex_byte ::= [0-9A-Fa-f]{2}
identifier ::= [a-z_A-Z]+
text ::= [^()]+
```
## Example Input
```
(prot(monitor)type(lcd)model(PD3220U)cmds(01 02 03 07)vcp(10 12 14(04 05 06) 16 60(11 12 0F) DC DF)mccs_ver(2.2)vcpname(F0(Custom Setting)))
```
## Parser Architecture
### Component Hierarchy
```
MccsCapabilitiesParser (main parser)
├── ParseCapabilities() → MccsParseResult
├── ParseSegment() → ParsedSegment?
├── ParseBalancedContent() → string
├── ParseIdentifier() → ReadOnlySpan<char>
├── ApplySegment() → void
│ ├── ParseHexList() → List<byte>
│ ├── ParseVcpEntries() → Dictionary<byte, VcpCodeInfo>
│ └── ParseVcpNames() → void
├── VcpEntryParser (sub-parser for vcp() content)
│ └── TryParseEntry() → VcpEntry
├── VcpNameParser (sub-parser for vcpname() content)
│ └── TryParseEntry() → (byte code, string name)
└── WindowParser (sub-parser for windowN() content)
├── Parse() → WindowCapability
└── ParseSubSegment() → (name, content)?
```
### Design Principles
1. **ref struct for Zero Allocation**
- Main parser uses `ref struct` to avoid heap allocation
- Works with `ReadOnlySpan<char>` for efficient string slicing
- No intermediate string allocations during parsing
2. **Recursive Descent Pattern**
- Each grammar rule has a corresponding parse method
- Methods call each other recursively for nested structures
- Single-character lookahead via `Peek()`
3. **Error Recovery**
- Errors are accumulated, not thrown
- Parser attempts to continue after errors
- Returns partial results when possible
4. **Sub-parsers for Specialized Content**
- `VcpEntryParser` for VCP code entries
- `VcpNameParser` for custom VCP names
- Each sub-parser handles its own grammar subset
## Parse Methods Detail
### ParseCapabilities()
Entry point. Handles optional outer parentheses and iterates through segments.
```csharp
private MccsParseResult ParseCapabilities()
{
// Handle optional outer parens
// while (!IsAtEnd()) { ParseSegment() }
// Return result with accumulated errors
}
```
### ParseSegment()
Parses a single `identifier(content)` segment.
```csharp
private ParsedSegment? ParseSegment()
{
// 1. ParseIdentifier()
// 2. Expect '('
// 3. ParseBalancedContent()
// 4. Expect ')'
}
```
### ParseBalancedContent()
Extracts content between balanced parentheses, handling nested parens.
```csharp
private string ParseBalancedContent()
{
int depth = 1;
while (depth > 0) {
if (char == '(') depth++;
if (char == ')') depth--;
}
}
```
### ParseVcpEntries()
Delegates to `VcpEntryParser` for the specialized VCP entry grammar.
```csharp
vcp_entry ::= hex_byte [ '(' hex_list ')' ]
Examples:
- "10" code=0x10, values=[]
- "14(04 05 06)" code=0x14, values=[4, 5, 6]
- "60(11 12 0F)" code=0x60, values=[0x11, 0x12, 0x0F]
```
## Comparison with Other Approaches
| Approach | Pros | Cons |
|----------|------|------|
| **Recursive Descent** (this) | Clear structure, handles nesting, extensible | More code |
| **Regex** (DDCSharp) | Concise | Hard to debug, limited nesting |
| **Mixed** (original) | Pragmatic | Inconsistent, hard to maintain |
## Performance Characteristics
- **Time Complexity**: O(n) where n = input length
- **Space Complexity**: O(1) for parsing + O(m) for output where m = number of VCP codes
- **Allocations**: Minimal - only for output structures
## Supported Segments
| Segment | Description | Parser |
|---------|-------------|--------|
| `prot(...)` | Protocol type | Direct assignment |
| `type(...)` | Display type (lcd/crt) | Direct assignment |
| `model(...)` | Model name | Direct assignment |
| `cmds(...)` | Supported commands | ParseHexList |
| `vcp(...)` | VCP code entries | VcpEntryParser |
| `mccs_ver(...)` | MCCS version | Direct assignment |
| `vcpname(...)` | Custom VCP names | VcpNameParser |
| `windowN(...)` | PIP/PBP window capabilities | WindowParser |
### Window Segment Format
The `windowN` segment (where N is 1, 2, 3, etc.) describes PIP/PBP window capabilities:
```
window1(type(PIP) area(25 25 1895 1175) max(640 480) min(10 10) window(10))
```
| Sub-field | Format | Description |
|-----------|--------|-------------|
| `type` | `type(PIP)` or `type(PBP)` | Window type (Picture-in-Picture or Picture-by-Picture) |
| `area` | `area(x1 y1 x2 y2)` | Window area coordinates in pixels |
| `max` | `max(width height)` | Maximum window dimensions |
| `min` | `min(width height)` | Minimum window dimensions |
| `window` | `window(id)` | Window identifier |
All sub-fields are optional; missing fields default to zero values.
## Error Handling
```csharp
public readonly struct ParseError
{
public int Position { get; } // Character position
public string Message { get; } // Human-readable error
}
public sealed class MccsParseResult
{
public VcpCapabilities Capabilities { get; }
public IReadOnlyList<ParseError> Errors { get; }
public bool HasErrors => Errors.Count > 0;
public bool IsValid => !HasErrors && Capabilities.SupportedVcpCodes.Count > 0;
}
```
## Usage Example
```csharp
// Parse capabilities string
var result = MccsCapabilitiesParser.Parse(capabilitiesString);
if (result.IsValid)
{
var caps = result.Capabilities;
Console.WriteLine($"Model: {caps.Model}");
Console.WriteLine($"MCCS Version: {caps.MccsVersion}");
Console.WriteLine($"VCP Codes: {caps.SupportedVcpCodes.Count}");
}
if (result.HasErrors)
{
foreach (var error in result.Errors)
{
Console.WriteLine($"Parse error at {error.Position}: {error.Message}");
}
}
```
## Edge Cases Handled
1. **Missing outer parentheses** (Apple Cinema Display)
2. **No spaces between hex bytes** (`010203` vs `01 02 03`)
3. **Nested parentheses** in VCP values
4. **Unknown segments** (logged but not fatal)
5. **Malformed input** (partial results returned)

View File

@@ -79,7 +79,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
### Prerequisites for Compiling PowerToys
1. Windows 10 April 2018 Update (version 1803) or newer
1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer, or Visual Studio 2026
1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer
1. A local clone of the PowerToys repository
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)

View File

@@ -0,0 +1,226 @@
# Settings UI Modernization Guide
This document describes the modernization patterns implemented in the PowerToys Settings UI to improve startup performance and maintainability.
## Overview
The Settings UI has been modernized with the following improvements:
1. **Dependency Injection (DI)** - Microsoft.Extensions.DependencyInjection for service resolution
2. **Page Caching** - Navigation caching to avoid page reconstruction
3. **Async ViewModel Initialization** - Non-blocking startup with IAsyncInitializable pattern
4. **Optimized Search** - ReaderWriterLockSlim for concurrent access
## Dependency Injection
### Configuration
Services are configured in `Services/AppServices.cs`:
```csharp
public static void Configure(Action<IServiceCollection> configureServices = null)
{
var services = new ServiceCollection();
ConfigureCoreServices(services);
configureServices?.Invoke(services);
_serviceProvider = services.BuildServiceProvider();
}
```
### Registered Services
| Interface | Implementation | Lifetime |
|-----------|----------------|----------|
| `INavigationService` | `NavigationServiceAdapter` | Singleton |
| `ISettingsService` | `SettingsService` | Singleton |
| `IIPCService` | `IPCService` | Singleton |
| `ViewModelLocator` | `ViewModelLocator` | Singleton |
### Usage
To resolve services:
```csharp
// In App.xaml.cs or any code
var navigationService = App.GetService<INavigationService>();
// Or directly from AppServices
var settingsService = AppServices.GetService<ISettingsService>();
```
## Page Caching
Pages are cached to avoid reconstruction on every navigation.
### Configuration
1. **Frame.CacheSize** - Set in `ShellPage.xaml.cs`:
```csharp
navigationView.Frame.CacheSize = 10;
```
2. **NavigationCacheMode** - Enabled in `NavigablePage` base class:
```csharp
protected NavigablePage()
{
NavigationCacheMode = NavigationCacheMode.Enabled;
}
```
All pages inheriting from `NavigablePage` automatically get caching.
## Async ViewModel Initialization
### IAsyncInitializable Interface
```csharp
public interface IAsyncInitializable
{
bool IsInitialized { get; }
bool IsLoading { get; }
Task InitializeAsync(CancellationToken cancellationToken = default);
}
```
### PageViewModelBase Implementation
The base ViewModel class implements this pattern:
```csharp
public abstract class PageViewModelBase : IAsyncInitializable
{
public bool IsInitialized { get; private set; }
public bool IsLoading { get; private set; }
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (IsInitialized) return;
IsLoading = true;
try
{
await InitializeCoreAsync(cancellationToken);
IsInitialized = true;
}
finally
{
IsLoading = false;
}
}
protected virtual Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
}
```
### Usage in ViewModels
Override `InitializeCoreAsync()` for async initialization:
```csharp
public class DashboardViewModel : PageViewModelBase
{
protected override async Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
await Task.Run(() =>
{
// Heavy initialization work
BuildModuleList();
}, cancellationToken);
}
}
```
## Search Service Optimization
### ReaderWriterLockSlim
The `SearchIndexService` uses `ReaderWriterLockSlim` for concurrent access:
- **Read operations**: Use `EnterReadLock()` for cache lookups
- **Write operations**: Use `EnterWriteLock()` for cache mutations
```csharp
private static readonly ReaderWriterLockSlim _cacheLock = new(LockRecursionPolicy.SupportsRecursion);
public static List<SettingEntry> Search(string query)
{
_cacheLock.EnterReadLock();
try
{
// Read from cache
}
finally
{
_cacheLock.ExitReadLock();
}
}
```
### Async Index Building
Search index is built asynchronously after first paint:
```csharp
private void ShellPage_Loaded(object sender, RoutedEventArgs e)
{
_ = SearchIndexService.BuildIndexAsync();
}
```
## Migration Guide
### Migrating Existing Pages
1. **Ensure page inherits from NavigablePage** (for caching)
2. **Update ViewModel to use InitializeCoreAsync()** for heavy initialization
3. **Replace static service calls** with DI-resolved services where feasible
### Example Migration
Before:
```csharp
public class MyViewModel
{
public MyViewModel()
{
// Heavy sync initialization
LoadSettings();
BuildList();
}
}
```
After:
```csharp
public class MyViewModel : PageViewModelBase
{
protected override string ModuleName => "MyModule";
protected override async Task InitializeCoreAsync(CancellationToken cancellationToken = default)
{
await Task.Run(() =>
{
LoadSettings();
BuildList();
}, cancellationToken);
}
}
```
## Performance Metrics
| Metric | Before | After |
|--------|--------|-------|
| Dashboard → General navigation | ~500-800ms | <100ms (cached) |
| Search during navigation | Blocked | Concurrent |
| App startup | Blocking | Non-blocking init |
## Future Work
- [ ] Migrate remaining 30+ pages to new patterns
- [ ] Add loading indicators during async initialization
- [ ] Further reduce GeneralViewModel constructor parameters (13 → 4)
- [ ] Add startup telemetry for performance monitoring

View File

@@ -1549,7 +1549,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 45> processesToTerminate = {
std::array<std::wstring_view, 44> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.AdvancedPaste.exe",
@@ -1565,7 +1565,6 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.PowerRename.exe",
L"PowerToys.ImageResizer.exe",
L"PowerToys.LightSwitchService.exe",
L"PowerToys.PowerDisplay.exe",
L"PowerToys.GcodeThumbnailProvider.exe",
L"PowerToys.BgcodeThumbnailProvider.exe",
L"PowerToys.PdfThumbnailProvider.exe",

View File

@@ -14,13 +14,13 @@
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="..\..\deps\spdlog.props" />

View File

@@ -18,7 +18,6 @@
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="Module_CmdPal" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFile Id="RemoveOldCmdPalMsix" Name="Microsoft.CmdPal.UI_*.msix" On="install" />
<?if $(sys.BUILDARCH) = x64 ?>
<File Id="Microsoft.CmdPal.UI___var.CmdPalVersion_._x64.msix" Source="$(var.CmdPalBuildDir)AppPackages\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_Test\Microsoft.CmdPal.UI_$(var.CmdPalVersion)_x64.msix" />
<?else?>

View File

@@ -1,5 +1,5 @@
<Project>
<Import Project="..\..\Directory.Build.props" />
<Import Project="..\..\src\Version.props" Condition="Exists('..\..\src\Version.props')" />
<PropertyGroup>
<!-- Set BaseIntermediateOutputPath for each project to avoid conflicts -->
<BaseIntermediateOutputPath Condition="'$(MSBuildProjectName)' == 'PowerToysInstallerVNext'">obj\Installer\</BaseIntermediateOutputPath>

View File

@@ -1,29 +0,0 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util" >
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define PowerDisplayAssetsFiles=?>
<?define PowerDisplayAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\PowerDisplay?>
<Fragment>
<!-- Power Display -->
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="PowerDisplayAssetsInstallFolder" Name="PowerDisplay" />
</DirectoryRef>
<DirectoryRef Id="PowerDisplayAssetsInstallFolder" FileSource="$(var.PowerDisplayAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--PowerDisplayAssetsFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="PowerDisplayComponentGroup">
<Component Id="RemovePowerDisplayFolder" Guid="B8F2E3A5-72C1-4A2D-9B3F-8E5D7C6A4F9B" Directory="PowerDisplayAssetsInstallFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemovePowerDisplayFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderPowerDisplayAssetsFolder" Directory="PowerDisplayAssetsInstallFolder" On="uninstall"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -47,7 +47,6 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
call move /Y ..\..\..\NewPlus.wxs.bk ..\..\..\NewPlus.wxs
call move /Y ..\..\..\Peek.wxs.bk ..\..\..\Peek.wxs
call move /Y ..\..\..\PowerRename.wxs.bk ..\..\..\PowerRename.wxs
call move /Y ..\..\..\PowerDisplay.wxs.bk ..\..\..\PowerDisplay.wxs
call move /Y ..\..\..\Product.wxs.bk ..\..\..\Product.wxs
call move /Y ..\..\..\RegistryPreview.wxs.bk ..\..\..\RegistryPreview.wxs
call move /Y ..\..\..\Resources.wxs.bk ..\..\..\Resources.wxs
@@ -124,7 +123,6 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Compile Include="KeyboardManager.wxs" />
<Compile Include="Peek.wxs" />
<Compile Include="PowerRename.wxs" />
<Compile Include="PowerDisplay.wxs" />
<Compile Include="DscResources.wxs" />
<Compile Include="RegistryPreview.wxs" />
<Compile Include="Run.wxs" />

View File

@@ -53,7 +53,6 @@
<ComponentGroupRef Id="LightSwitchComponentGroup" />
<ComponentGroupRef Id="PeekComponentGroup" />
<ComponentGroupRef Id="PowerRenameComponentGroup" />
<ComponentGroupRef Id="PowerDisplayComponentGroup" />
<ComponentGroupRef Id="RegistryPreviewComponentGroup" />
<ComponentGroupRef Id="RunComponentGroup" />
<ComponentGroupRef Id="SettingsComponentGroup" />

View File

@@ -367,12 +367,6 @@
</RegistryKey>
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
</Component>
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
</RegistryKey>
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>

View File

@@ -37,14 +37,14 @@
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@@ -68,10 +68,11 @@
<ClCompile Include="SilentFilesInUseBAFunctions.cpp" />
<ClCompile Include="bafunctions.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="precomp.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
#include "pch.h"
#include "precomp.h"
#include "BalBaseBAFunctions.h"
#include "BalBaseBAFunctionsProc.h"
@@ -18,6 +18,7 @@ public: // IBootstrapperApplication
BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "*** CUSTOM BA FUNCTION SYSTEM ACTIVE *** Running detect begin BA function. fCached=%d, registrationType=%d, cPackages=%u, fCancel=%d", fCached, registrationType, cPackages, *pfCancel);
LExit:
return hr;
}
@@ -36,6 +37,7 @@ public: // IBAFunctions
// BalExitOnFailure(hr, "Change this message to represent real error handling.");
//-------------------------------------------------------------------------------------------------
LExit:
return hr;
}
@@ -56,7 +58,7 @@ public: // IBAFunctions
__in DWORD cFiles,
__in_ecount_z(cFiles) LPCWSTR* rgwzFiles,
__in int nRecommendation,
__in BOOTSTRAPPER_FILES_IN_USE_TYPE /* source */,
__in BOOTSTRAPPER_FILES_IN_USE_TYPE source,
__inout int* pResult
)
{

View File

@@ -1,6 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
#include "pch.h"
#include "precomp.h"
static HINSTANCE vhInstance = NULL;

View File

@@ -176,10 +176,6 @@ Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PS
Generate-FileList -fileDepsJson "" -fileListName LightSwitchFiles -wxsFilePath $PSScriptRoot\LightSwitch.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\LightSwitchService"
Generate-FileComponents -fileListName "LightSwitchFiles" -wxsFilePath $PSScriptRoot\LightSwitch.wxs
#PowerDisplay
Generate-FileList -fileDepsJson "" -fileListName PowerDisplayAssetsFiles -wxsFilePath $PSScriptRoot\PowerDisplay.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\PowerDisplay"
Generate-FileComponents -fileListName "PowerDisplayAssetsFiles" -wxsFilePath $PSScriptRoot\PowerDisplay.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

View File

@@ -12,7 +12,7 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="..\..\deps\expected.props" />
<PropertyGroup>

View File

@@ -5,6 +5,6 @@
As a temporary workaround, create a .NET 8 project and use file links
to include the code that needs testing. -->
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@@ -55,26 +55,26 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Utility</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Utility</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">
<ConfigurationType>Utility</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>Utility</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>

View File

@@ -12,7 +12,7 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="..\..\deps\expected.props" />
<PropertyGroup>

View File

@@ -10,7 +10,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">

View File

@@ -40,7 +40,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>

View File

@@ -3,27 +3,9 @@
#include <iomanip>
#include <iostream>
#include <sstream>
#include <cmath>
#include <limits>
namespace ExprtkCalculator::internal
{
static double factorial(const double n)
{
// Only allow non-negative integers
if (n < 0.0 || std::floor(n) != n)
{
return std::numeric_limits<double>::quiet_NaN();
}
return std::tgamma(n + 1.0);
}
static double sign(const double n)
{
if (n > 0.0) return 1.0;
if (n < 0.0) return -1.0;
return 0.0;
}
std::wstring ToWStringFullPrecision(double value)
{
@@ -43,9 +25,6 @@ namespace ExprtkCalculator::internal
symbol_table.add_constant(name, value);
}
symbol_table.add_function("factorial", factorial);
symbol_table.add_function("sign", sign);
exprtk::expression<double> expression;
expression.register_symbol_table(symbol_table);

View File

@@ -45,7 +45,6 @@ namespace Common.UI
NewPlus,
CmdPal,
ZoomIt,
PowerDisplay,
}
private static string SettingsWindowNameToString(SettingsWindow value)
@@ -116,8 +115,6 @@ namespace Common.UI
return "CmdPal";
case SettingsWindow.ZoomIt:
return "ZoomIt";
case SettingsWindow.PowerDisplay:
return "PowerDisplay";
default:
{
return string.Empty;

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{CABA8DFB-823B-4BF2-93AC-3F31984150D9}</ProjectGuid>
@@ -11,7 +10,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
@@ -40,18 +39,5 @@
<ClCompile Include="monitors.cpp" />
<ClCompile Include="dpi_aware.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
</packages>

View File

@@ -32,10 +32,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredLightSwitchEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredPowerDisplayEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredPowerDisplayEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());

View File

@@ -14,7 +14,6 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();

View File

@@ -18,7 +18,6 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
static GpoRuleConfigured GetConfiguredLightSwitchEnabledValue();
static GpoRuleConfigured GetConfiguredPowerDisplayEnabledValue();
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();

View File

@@ -19,7 +19,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
<DesktopCompatible>true</DesktopCompatible>

View File

@@ -30,7 +30,6 @@ namespace ManagedCommon
PowerRename,
PowerLauncher,
PowerAccent,
PowerDisplay,
RegistryPreview,
MeasureTool,
ShortcutGuide,

View File

@@ -12,7 +12,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">

View File

@@ -11,7 +11,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">

View File

@@ -11,7 +11,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">

View File

@@ -16,50 +16,13 @@ DWORD WINAPI _checkTheme(LPVOID lpParam)
void ThemeListener::AddChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
handles.push_back(handle);
}
void ThemeListener::DelChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
auto it = std::find(handles.begin(), handles.end(), handle);
if (it != handles.end())
{
handles.erase(it);
}
}
void ThemeListener::AddAppThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
appThemeHandles.push_back(handle);
}
void ThemeListener::DelAppThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
auto it = std::find(appThemeHandles.begin(), appThemeHandles.end(), handle);
if (it != appThemeHandles.end())
{
appThemeHandles.erase(it);
}
}
void ThemeListener::AddSystemThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
systemThemeHandles.push_back(handle);
}
void ThemeListener::DelSystemThemeChangedHandler(THEME_HANDLE handle)
{
std::lock_guard<std::mutex> lock(handlesMutex);
auto it = std::find(systemThemeHandles.begin(), systemThemeHandles.end(), handle);
if (it != systemThemeHandles.end())
{
systemThemeHandles.erase(it);
}
handles.erase(it);
}
void ThemeListener::CheckTheme()
@@ -85,51 +48,13 @@ void ThemeListener::CheckTheme()
WaitForSingleObject(hEvent, INFINITE);
auto _appTheme = ThemeHelpers::GetAppTheme();
auto _systemTheme = ThemeHelpers::GetSystemTheme();
bool appThemeChanged = (AppTheme != _appTheme);
bool systemThemeChanged = (SystemTheme != _systemTheme);
if (appThemeChanged || systemThemeChanged)
auto _theme = ThemeHelpers::GetAppTheme();
if (AppTheme != _theme)
{
AppTheme = _appTheme;
SystemTheme = _systemTheme;
// Copy handlers under lock, then invoke outside lock to avoid deadlock
std::vector<THEME_HANDLE> handlesCopy;
std::vector<THEME_HANDLE> appThemeHandlesCopy;
std::vector<THEME_HANDLE> systemThemeHandlesCopy;
AppTheme = _theme;
for (int i = 0; i < handles.size(); i++)
{
std::lock_guard<std::mutex> lock(handlesMutex);
handlesCopy = handles;
if (appThemeChanged)
{
appThemeHandlesCopy = appThemeHandles;
}
if (systemThemeChanged)
{
systemThemeHandlesCopy = systemThemeHandles;
}
}
// Call generic handlers (backward compatible)
for (const auto& handler : handlesCopy)
{
handler();
}
// Call app theme specific handlers
for (const auto& handler : appThemeHandlesCopy)
{
handler();
}
// Call system theme specific handlers
for (const auto& handler : systemThemeHandlesCopy)
{
handler();
handles[i]();
}
}
}

View File

@@ -3,7 +3,6 @@
#include <windows.h>
#include <iostream>
#include <vector>
#include <mutex>
typedef void (*THEME_HANDLE)();
DWORD WINAPI _checkTheme(LPVOID lpParam);
@@ -15,7 +14,6 @@ public:
ThemeListener()
{
AppTheme = ThemeHelpers::GetAppTheme();
SystemTheme = ThemeHelpers::GetSystemTheme();
dwThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_checkTheme, this, 0, &dwThreadId);
}
~ThemeListener()
@@ -25,20 +23,12 @@ public:
}
Theme AppTheme;
Theme SystemTheme;
void ThemeListener::AddChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelChangedHandler(THEME_HANDLE handle);
void ThemeListener::AddAppThemeChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelAppThemeChangedHandler(THEME_HANDLE handle);
void ThemeListener::AddSystemThemeChangedHandler(THEME_HANDLE handle);
void ThemeListener::DelSystemThemeChangedHandler(THEME_HANDLE handle);
void CheckTheme();
private:
HANDLE dwThreadHandle;
DWORD dwThreadId;
std::vector<THEME_HANDLE> handles;
std::vector<THEME_HANDLE> appThemeHandles;
std::vector<THEME_HANDLE> systemThemeHandles;
mutable std::mutex handlesMutex;
};

View File

@@ -13,7 +13,7 @@
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<PlatformToolset>v143</PlatformToolset>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\tests\UnitTestsCommonLib\</OutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />

View File

@@ -1,120 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <appMutex.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(AppMutexTests)
{
public:
TEST_METHOD(CreateAppMutex_ValidName_ReturnsHandle)
{
std::wstring mutexName = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_1";
auto handle = createAppMutex(mutexName);
Assert::IsNotNull(handle.get());
}
TEST_METHOD(CreateAppMutex_SameName_ReturnsExistingHandle)
{
std::wstring mutexName = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_2";
auto handle1 = createAppMutex(mutexName);
Assert::IsNotNull(handle1.get());
auto handle2 = createAppMutex(mutexName);
Assert::IsNull(handle2.get());
}
TEST_METHOD(CreateAppMutex_DifferentNames_ReturnsDifferentHandles)
{
std::wstring mutexName1 = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_A";
std::wstring mutexName2 = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_B";
auto handle1 = createAppMutex(mutexName1);
auto handle2 = createAppMutex(mutexName2);
Assert::IsNotNull(handle1.get());
Assert::IsNotNull(handle2.get());
Assert::AreNotEqual(handle1.get(), handle2.get());
}
TEST_METHOD(CreateAppMutex_EmptyName_ReturnsHandle)
{
// Empty name creates unnamed mutex
auto handle = createAppMutex(L"");
// CreateMutexW with empty string should still work
Assert::IsTrue(true);
// Test passes regardless - just checking it doesn't crash
Assert::IsTrue(true);
}
TEST_METHOD(CreateAppMutex_LongName_ReturnsHandle)
{
// Create a long mutex name
std::wstring mutexName = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_";
for (int i = 0; i < 50; ++i)
{
mutexName += L"LongNameSegment";
}
auto handle = createAppMutex(mutexName);
// Long names might fail, but shouldn't crash
Assert::IsTrue(true);
}
TEST_METHOD(CreateAppMutex_SpecialCharacters_ReturnsHandle)
{
std::wstring mutexName = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_Special!@#$%";
auto handle = createAppMutex(mutexName);
// Some special characters might not be valid in mutex names
Assert::IsTrue(true);
}
TEST_METHOD(CreateAppMutex_GlobalPrefix_ReturnsHandle)
{
// Global prefix for cross-session mutex
std::wstring mutexName = L"Global\\TestMutex_" + std::to_wstring(GetCurrentProcessId());
auto handle = createAppMutex(mutexName);
// Might require elevation, but shouldn't crash
Assert::IsTrue(true);
}
TEST_METHOD(CreateAppMutex_LocalPrefix_ReturnsHandle)
{
std::wstring mutexName = L"Local\\TestMutex_" + std::to_wstring(GetCurrentProcessId());
auto handle = createAppMutex(mutexName);
Assert::IsNotNull(handle.get());
}
TEST_METHOD(CreateAppMutex_MultipleCalls_AllSucceed)
{
std::vector<wil::unique_mutex_nothrow> handles;
for (int i = 0; i < 10; ++i)
{
std::wstring mutexName = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) +
L"_Multi_" + std::to_wstring(i);
auto handle = createAppMutex(mutexName);
Assert::IsNotNull(handle.get());
handles.push_back(std::move(handle));
}
}
TEST_METHOD(CreateAppMutex_ReleaseAndRecreate_Works)
{
std::wstring mutexName = L"TestMutex_" + std::to_wstring(GetCurrentProcessId()) + L"_Recreate";
auto handle1 = createAppMutex(mutexName);
Assert::IsNotNull(handle1.get());
handle1.reset();
// After closing, should be able to create again
auto handle2 = createAppMutex(mutexName);
Assert::IsNotNull(handle2.get());
}
};
}

View File

@@ -1,220 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <color.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ColorUtilsTests)
{
public:
// checkValidRGB tests
TEST_METHOD(CheckValidRGB_ValidBlack_ReturnsTrue)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#000000", &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0), r);
Assert::AreEqual(static_cast<uint8_t>(0), g);
Assert::AreEqual(static_cast<uint8_t>(0), b);
}
TEST_METHOD(CheckValidRGB_ValidWhite_ReturnsTrue)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#FFFFFF", &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(255), r);
Assert::AreEqual(static_cast<uint8_t>(255), g);
Assert::AreEqual(static_cast<uint8_t>(255), b);
}
TEST_METHOD(CheckValidRGB_ValidRed_ReturnsTrue)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#FF0000", &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(255), r);
Assert::AreEqual(static_cast<uint8_t>(0), g);
Assert::AreEqual(static_cast<uint8_t>(0), b);
}
TEST_METHOD(CheckValidRGB_ValidGreen_ReturnsTrue)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#00FF00", &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0), r);
Assert::AreEqual(static_cast<uint8_t>(255), g);
Assert::AreEqual(static_cast<uint8_t>(0), b);
}
TEST_METHOD(CheckValidRGB_ValidBlue_ReturnsTrue)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#0000FF", &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0), r);
Assert::AreEqual(static_cast<uint8_t>(0), g);
Assert::AreEqual(static_cast<uint8_t>(255), b);
}
TEST_METHOD(CheckValidRGB_ValidMixed_ReturnsTrue)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#AB12CD", &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0xAB), r);
Assert::AreEqual(static_cast<uint8_t>(0x12), g);
Assert::AreEqual(static_cast<uint8_t>(0xCD), b);
}
TEST_METHOD(CheckValidRGB_MissingHash_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"FFFFFF", &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidRGB_TooShort_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#FFF", &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidRGB_TooLong_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#FFFFFFFF", &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidRGB_InvalidChars_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#GGHHII", &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidRGB_LowercaseInvalid_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#ffffff", &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidRGB_EmptyString_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"", &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidRGB_OnlyHash_ReturnsFalse)
{
uint8_t r, g, b;
bool result = checkValidRGB(L"#", &r, &g, &b);
Assert::IsFalse(result);
}
// checkValidARGB tests
TEST_METHOD(CheckValidARGB_ValidBlackOpaque_ReturnsTrue)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#FF000000", &a, &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(255), a);
Assert::AreEqual(static_cast<uint8_t>(0), r);
Assert::AreEqual(static_cast<uint8_t>(0), g);
Assert::AreEqual(static_cast<uint8_t>(0), b);
}
TEST_METHOD(CheckValidARGB_ValidWhiteOpaque_ReturnsTrue)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#FFFFFFFF", &a, &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(255), a);
Assert::AreEqual(static_cast<uint8_t>(255), r);
Assert::AreEqual(static_cast<uint8_t>(255), g);
Assert::AreEqual(static_cast<uint8_t>(255), b);
}
TEST_METHOD(CheckValidARGB_ValidTransparent_ReturnsTrue)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#00FFFFFF", &a, &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0), a);
Assert::AreEqual(static_cast<uint8_t>(255), r);
Assert::AreEqual(static_cast<uint8_t>(255), g);
Assert::AreEqual(static_cast<uint8_t>(255), b);
}
TEST_METHOD(CheckValidARGB_ValidSemiTransparent_ReturnsTrue)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#80FF0000", &a, &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0x80), a);
Assert::AreEqual(static_cast<uint8_t>(255), r);
Assert::AreEqual(static_cast<uint8_t>(0), g);
Assert::AreEqual(static_cast<uint8_t>(0), b);
}
TEST_METHOD(CheckValidARGB_ValidMixed_ReturnsTrue)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#12345678", &a, &r, &g, &b);
Assert::IsTrue(result);
Assert::AreEqual(static_cast<uint8_t>(0x12), a);
Assert::AreEqual(static_cast<uint8_t>(0x34), r);
Assert::AreEqual(static_cast<uint8_t>(0x56), g);
Assert::AreEqual(static_cast<uint8_t>(0x78), b);
}
TEST_METHOD(CheckValidARGB_MissingHash_ReturnsFalse)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"FFFFFFFF", &a, &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidARGB_TooShort_ReturnsFalse)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#FFFFFF", &a, &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidARGB_TooLong_ReturnsFalse)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#FFFFFFFFFF", &a, &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidARGB_InvalidChars_ReturnsFalse)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#GGHHIIJJ", &a, &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidARGB_LowercaseInvalid_ReturnsFalse)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"#ffffffff", &a, &r, &g, &b);
Assert::IsFalse(result);
}
TEST_METHOD(CheckValidARGB_EmptyString_ReturnsFalse)
{
uint8_t a, r, g, b;
bool result = checkValidARGB(L"", &a, &r, &g, &b);
Assert::IsFalse(result);
}
};
}

View File

@@ -1,228 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <com_object_factory.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
// Test COM object for testing the factory
class TestComObject : public IUnknown
{
public:
TestComObject() : m_refCount(1) {}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override
{
if (riid == IID_IUnknown)
{
*ppvObject = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef() override
{
return InterlockedIncrement(&m_refCount);
}
ULONG STDMETHODCALLTYPE Release() override
{
ULONG count = InterlockedDecrement(&m_refCount);
if (count == 0)
{
delete this;
}
return count;
}
private:
LONG m_refCount;
};
TEST_CLASS(ComObjectFactoryTests)
{
public:
TEST_METHOD(ComObjectFactory_Construction_DoesNotCrash)
{
com_object_factory<TestComObject> factory;
Assert::IsTrue(true);
}
TEST_METHOD(ComObjectFactory_QueryInterface_IUnknown_Succeeds)
{
com_object_factory<TestComObject> factory;
IUnknown* pUnknown = nullptr;
HRESULT hr = factory.QueryInterface(IID_IUnknown, reinterpret_cast<void**>(&pUnknown));
Assert::AreEqual(S_OK, hr);
Assert::IsNotNull(pUnknown);
if (pUnknown)
{
pUnknown->Release();
}
}
TEST_METHOD(ComObjectFactory_QueryInterface_IClassFactory_Succeeds)
{
com_object_factory<TestComObject> factory;
IClassFactory* pFactory = nullptr;
HRESULT hr = factory.QueryInterface(IID_IClassFactory, reinterpret_cast<void**>(&pFactory));
Assert::AreEqual(S_OK, hr);
Assert::IsNotNull(pFactory);
if (pFactory)
{
pFactory->Release();
}
}
TEST_METHOD(ComObjectFactory_QueryInterface_InvalidInterface_Fails)
{
com_object_factory<TestComObject> factory;
void* pInterface = nullptr;
// Random GUID that we don't support
GUID randomGuid = { 0x12345678, 0x1234, 0x1234, { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 } };
HRESULT hr = factory.QueryInterface(randomGuid, &pInterface);
Assert::AreEqual(E_NOINTERFACE, hr);
Assert::IsNull(pInterface);
}
TEST_METHOD(ComObjectFactory_AddRef_IncreasesRefCount)
{
com_object_factory<TestComObject> factory;
ULONG count1 = factory.AddRef();
ULONG count2 = factory.AddRef();
Assert::IsTrue(count2 > count1);
// Clean up
factory.Release();
factory.Release();
}
TEST_METHOD(ComObjectFactory_Release_DecreasesRefCount)
{
com_object_factory<TestComObject> factory;
factory.AddRef();
factory.AddRef();
ULONG count1 = factory.Release();
ULONG count2 = factory.Release();
Assert::IsTrue(count2 < count1);
}
TEST_METHOD(ComObjectFactory_CreateInstance_NoAggregation_Succeeds)
{
com_object_factory<TestComObject> factory;
IUnknown* pObj = nullptr;
HRESULT hr = factory.CreateInstance(nullptr, IID_IUnknown, reinterpret_cast<void**>(&pObj));
Assert::AreEqual(S_OK, hr);
Assert::IsNotNull(pObj);
if (pObj)
{
pObj->Release();
}
}
TEST_METHOD(ComObjectFactory_CreateInstance_WithAggregation_Fails)
{
com_object_factory<TestComObject> factory;
TestComObject outer;
IUnknown* pObj = nullptr;
// Aggregation should fail for our simple test object
HRESULT hr = factory.CreateInstance(&outer, IID_IUnknown, reinterpret_cast<void**>(&pObj));
Assert::AreEqual(CLASS_E_NOAGGREGATION, hr);
Assert::IsNull(pObj);
}
TEST_METHOD(ComObjectFactory_CreateInstance_NullOutput_Fails)
{
com_object_factory<TestComObject> factory;
HRESULT hr = factory.CreateInstance(nullptr, IID_IUnknown, nullptr);
Assert::AreEqual(E_POINTER, hr);
}
TEST_METHOD(ComObjectFactory_LockServer_Lock_Succeeds)
{
com_object_factory<TestComObject> factory;
HRESULT hr = factory.LockServer(TRUE);
Assert::AreEqual(S_OK, hr);
// Unlock
factory.LockServer(FALSE);
}
TEST_METHOD(ComObjectFactory_LockServer_Unlock_Succeeds)
{
com_object_factory<TestComObject> factory;
factory.LockServer(TRUE);
HRESULT hr = factory.LockServer(FALSE);
Assert::AreEqual(S_OK, hr);
}
TEST_METHOD(ComObjectFactory_LockServer_MultipleLocks_Work)
{
com_object_factory<TestComObject> factory;
factory.LockServer(TRUE);
factory.LockServer(TRUE);
factory.LockServer(TRUE);
factory.LockServer(FALSE);
factory.LockServer(FALSE);
HRESULT hr = factory.LockServer(FALSE);
Assert::AreEqual(S_OK, hr);
}
// Thread safety tests
TEST_METHOD(ComObjectFactory_ConcurrentCreateInstance_Works)
{
com_object_factory<TestComObject> factory;
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&factory, &successCount]() {
IUnknown* pObj = nullptr;
HRESULT hr = factory.CreateInstance(nullptr, IID_IUnknown, reinterpret_cast<void**>(&pObj));
if (SUCCEEDED(hr) && pObj)
{
successCount++;
pObj->Release();
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(10, successCount.load());
}
};
}

View File

@@ -1,146 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <elevation.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ElevationTests)
{
public:
// is_process_elevated tests
TEST_METHOD(IsProcessElevated_ReturnsBoolean)
{
bool result = is_process_elevated(false);
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsProcessElevated_CachedValue_ReturnsSameResult)
{
bool result1 = is_process_elevated(true);
bool result2 = is_process_elevated(true);
// Cached value should be consistent
Assert::AreEqual(result1, result2);
}
TEST_METHOD(IsProcessElevated_UncachedValue_ReturnsBoolean)
{
bool result = is_process_elevated(false);
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsProcessElevated_CachedAndUncached_AreConsistent)
{
// Both should return the same value for the same process
bool cached = is_process_elevated(true);
bool uncached = is_process_elevated(false);
Assert::AreEqual(cached, uncached);
}
// check_user_is_admin tests
TEST_METHOD(CheckUserIsAdmin_ReturnsBoolean)
{
bool result = check_user_is_admin();
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(CheckUserIsAdmin_ConsistentResults)
{
bool result1 = check_user_is_admin();
bool result2 = check_user_is_admin();
bool result3 = check_user_is_admin();
Assert::AreEqual(result1, result2);
Assert::AreEqual(result2, result3);
}
// Relationship between elevation and admin
TEST_METHOD(ElevationAndAdmin_Relationship)
{
bool elevated = is_process_elevated(false);
bool admin = check_user_is_admin();
(void)admin;
// If elevated, user should typically be admin
// But user can be admin without process being elevated
if (elevated)
{
// Elevated process usually means admin user
// (though there are edge cases)
}
// Just verify both functions return without crashing
Assert::IsTrue(true);
}
// IsProcessOfWindowElevated tests
TEST_METHOD(IsProcessOfWindowElevated_DesktopWindow_ReturnsBoolean)
{
HWND desktop = GetDesktopWindow();
if (desktop)
{
bool result = IsProcessOfWindowElevated(desktop);
Assert::IsTrue(result == true || result == false);
}
Assert::IsTrue(true);
}
TEST_METHOD(IsProcessOfWindowElevated_InvalidHwnd_DoesNotCrash)
{
bool result = IsProcessOfWindowElevated(nullptr);
// Should handle null HWND gracefully
Assert::IsTrue(result == true || result == false);
}
// ProcessInfo struct tests
TEST_METHOD(ProcessInfo_DefaultConstruction)
{
ProcessInfo info{};
Assert::AreEqual(static_cast<DWORD>(0), info.processID);
}
// Thread safety tests
TEST_METHOD(IsProcessElevated_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 10; ++j)
{
is_process_elevated(j % 2 == 0);
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
// Performance of cached value
TEST_METHOD(IsProcessElevated_CachedPerformance)
{
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; ++i)
{
is_process_elevated(true);
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// Cached calls should be very fast
Assert::IsTrue(duration.count() < 1000);
}
};
}

View File

@@ -1,182 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <excluded_apps.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ExcludedAppsTests)
{
public:
// find_app_name_in_path tests
TEST_METHOD(FindAppNameInPath_ExactMatch_ReturnsTrue)
{
std::wstring path = L"C:\\Program Files\\App\\notepad.exe";
std::vector<std::wstring> apps = { L"notepad.exe" };
Assert::IsTrue(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_NoMatch_ReturnsFalse)
{
std::wstring path = L"C:\\Program Files\\App\\notepad.exe";
std::vector<std::wstring> apps = { L"calc.exe" };
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_MultipleApps_FindsMatch)
{
std::wstring path = L"C:\\Program Files\\App\\notepad.exe";
std::vector<std::wstring> apps = { L"calc.exe", L"notepad.exe", L"word.exe" };
Assert::IsTrue(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_EmptyPath_ReturnsFalse)
{
std::wstring path = L"";
std::vector<std::wstring> apps = { L"notepad.exe" };
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_EmptyApps_ReturnsFalse)
{
std::wstring path = L"C:\\Program Files\\App\\notepad.exe";
std::vector<std::wstring> apps = {};
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_PartialMatchInFolder_ReturnsFalse)
{
// "notepad" appears in folder name but not as the exe name
std::wstring path = L"C:\\notepad\\other.exe";
std::vector<std::wstring> apps = { L"notepad.exe" };
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_CaseSensitive_ReturnsFalse)
{
std::wstring path = L"C:\\Program Files\\App\\NOTEPAD.EXE";
std::vector<std::wstring> apps = { L"notepad.exe" };
// The function does rfind which is case-sensitive
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_MatchWithDifferentExtension_ReturnsFalse)
{
std::wstring path = L"C:\\Program Files\\App\\notepad.com";
std::vector<std::wstring> apps = { L"notepad.exe" };
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_MatchAtEndOfPath_ReturnsTrue)
{
std::wstring path = L"C:\\Windows\\System32\\notepad.exe";
std::vector<std::wstring> apps = { L"notepad.exe" };
Assert::IsTrue(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_UNCPath_Works)
{
std::wstring path = L"\\\\server\\share\\folder\\app.exe";
std::vector<std::wstring> apps = { L"app.exe" };
Assert::IsTrue(find_app_name_in_path(path, apps));
}
// find_folder_in_path tests
TEST_METHOD(FindFolderInPath_FolderExists_ReturnsTrue)
{
std::wstring path = L"C:\\Program Files\\MyApp\\app.exe";
std::vector<std::wstring> folders = { L"Program Files" };
Assert::IsTrue(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_FolderNotExists_ReturnsFalse)
{
std::wstring path = L"C:\\Windows\\System32\\app.exe";
std::vector<std::wstring> folders = { L"Program Files" };
Assert::IsFalse(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_MultipleFolders_FindsMatch)
{
std::wstring path = L"C:\\Windows\\System32\\app.exe";
std::vector<std::wstring> folders = { L"Program Files", L"System32", L"Users" };
Assert::IsTrue(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_EmptyPath_ReturnsFalse)
{
std::wstring path = L"";
std::vector<std::wstring> folders = { L"Windows" };
Assert::IsFalse(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_EmptyFolders_ReturnsFalse)
{
std::wstring path = L"C:\\Windows\\app.exe";
std::vector<std::wstring> folders = {};
Assert::IsFalse(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_PartialMatch_ReturnsTrue)
{
// find_folder_in_path uses rfind which finds substrings
std::wstring path = L"C:\\Windows\\System32\\app.exe";
std::vector<std::wstring> folders = { L"System" };
Assert::IsTrue(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_NestedFolder_ReturnsTrue)
{
std::wstring path = L"C:\\Program Files\\Company\\Product\\bin\\app.exe";
std::vector<std::wstring> folders = { L"Product" };
Assert::IsTrue(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_RootDrive_ReturnsTrue)
{
std::wstring path = L"C:\\folder\\app.exe";
std::vector<std::wstring> folders = { L"C:\\" };
Assert::IsTrue(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_UNCPath_Works)
{
std::wstring path = L"\\\\server\\share\\folder\\app.exe";
std::vector<std::wstring> folders = { L"share" };
Assert::IsTrue(find_folder_in_path(path, folders));
}
TEST_METHOD(FindFolderInPath_CaseSensitive_ReturnsFalse)
{
std::wstring path = L"C:\\WINDOWS\\app.exe";
std::vector<std::wstring> folders = { L"windows" };
// rfind is case-sensitive
Assert::IsFalse(find_folder_in_path(path, folders));
}
// Edge case tests
TEST_METHOD(FindAppNameInPath_AppNameInMiddleOfPath_HandlesCorrectly)
{
// The app name appears both in folder and as filename
std::wstring path = L"C:\\notepad\\bin\\notepad.exe";
std::vector<std::wstring> apps = { L"notepad.exe" };
Assert::IsTrue(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindAppNameInPath_JustFilename_ReturnsFalse)
{
std::wstring path = L"notepad.exe";
std::vector<std::wstring> apps = { L"notepad.exe" };
// find_app_name_in_path expects a path separator to validate the executable segment
Assert::IsFalse(find_app_name_in_path(path, apps));
}
TEST_METHOD(FindFolderInPath_JustFilename_ReturnsFalse)
{
std::wstring path = L"app.exe";
std::vector<std::wstring> folders = { L"Windows" };
Assert::IsFalse(find_folder_in_path(path, folders));
}
};
}

View File

@@ -1,148 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <exec.h>
#include <cctype>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ExecTests)
{
public:
TEST_METHOD(ExecAndReadOutput_EchoCommand_ReturnsOutput)
{
auto result = exec_and_read_output(L"cmd /c echo hello", 5000);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
// Output should contain "hello"
Assert::IsTrue(result->find("hello") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_WhereCommand_ReturnsPath)
{
auto result = exec_and_read_output(L"where cmd", 5000);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
// Should contain path to cmd.exe
Assert::IsTrue(result->find("cmd") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_DirCommand_ReturnsListing)
{
auto result = exec_and_read_output(L"cmd /c dir /b C:\\Windows", 5000);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
// Should contain some common Windows folder names
std::string output = *result;
std::transform(output.begin(), output.end(), output.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
Assert::IsTrue(output.find("system32") != std::string::npos ||
output.find("system") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_InvalidCommand_ReturnsEmptyOrError)
{
auto result = exec_and_read_output(L"nonexistentcommand12345", 5000);
// Invalid command should either return nullopt or an error message
Assert::IsTrue(!result.has_value() || result->empty() ||
result->find("not recognized") != std::string::npos ||
result->find("error") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_EmptyCommand_DoesNotCrash)
{
auto result = exec_and_read_output(L"", 5000);
// Should handle empty command gracefully
Assert::IsTrue(true);
}
TEST_METHOD(ExecAndReadOutput_TimeoutExpires_ReturnsAvailableOutput)
{
// Use a command that produces output slowly
// ping localhost will run for a while
auto start = std::chrono::steady_clock::now();
// Very short timeout
auto result = exec_and_read_output(L"ping localhost -n 10", 100);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
// Should return within reasonable time
Assert::IsTrue(elapsed.count() < 5000);
}
TEST_METHOD(ExecAndReadOutput_MultilineOutput_PreservesLines)
{
auto result = exec_and_read_output(L"cmd /c \"echo line1 & echo line2 & echo line3\"", 5000);
Assert::IsTrue(result.has_value());
// Should contain multiple lines
Assert::IsTrue(result->find("line1") != std::string::npos);
Assert::IsTrue(result->find("line2") != std::string::npos);
Assert::IsTrue(result->find("line3") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_UnicodeOutput_Works)
{
// Echo a simple ASCII string (Unicode test depends on system codepage)
auto result = exec_and_read_output(L"cmd /c echo test123", 5000);
Assert::IsTrue(result.has_value());
Assert::IsTrue(result->find("test123") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_LongTimeout_Works)
{
auto result = exec_and_read_output(L"cmd /c echo test", 60000);
Assert::IsTrue(result.has_value());
Assert::IsTrue(result->find("test") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_QuotedArguments_Work)
{
auto result = exec_and_read_output(L"cmd /c echo \"hello world\"", 5000);
Assert::IsTrue(result.has_value());
Assert::IsTrue(result->find("hello") != std::string::npos);
}
TEST_METHOD(ExecAndReadOutput_EnvironmentVariable_Expanded)
{
auto result = exec_and_read_output(L"cmd /c echo %USERNAME%", 5000);
Assert::IsTrue(result.has_value());
// Should not contain the literal %USERNAME% but the actual username
// Or if not expanded, still should not crash
Assert::IsFalse(result->empty());
}
TEST_METHOD(ExecAndReadOutput_ExitCode_CommandFails)
{
// Command that exits with error
auto result = exec_and_read_output(L"cmd /c exit 1", 5000);
// Should still return something (possibly empty)
// Just verify it doesn't crash
Assert::IsTrue(true);
}
TEST_METHOD(ExecAndReadOutput_ZeroTimeout_DoesNotHang)
{
auto start = std::chrono::steady_clock::now();
auto result = exec_and_read_output(L"cmd /c echo test", 0);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
// Should complete quickly with zero timeout
Assert::IsTrue(elapsed.count() < 5000);
}
};
}

View File

@@ -1,68 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <game_mode.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(GameModeTests)
{
public:
TEST_METHOD(DetectGameMode_ReturnsBoolean)
{
// This function queries Windows game mode status
bool result = detect_game_mode();
// Result depends on current system state, but should be a valid boolean
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(DetectGameMode_ConsistentResults)
{
// Multiple calls should return consistent results (unless game mode changes)
bool result1 = detect_game_mode();
bool result2 = detect_game_mode();
bool result3 = detect_game_mode();
// Results should be consistent across rapid calls
Assert::AreEqual(result1, result2);
Assert::AreEqual(result2, result3);
}
TEST_METHOD(DetectGameMode_DoesNotCrash)
{
// Call multiple times to ensure no crash or memory leak
for (int i = 0; i < 100; ++i)
{
detect_game_mode();
}
Assert::IsTrue(true);
}
TEST_METHOD(DetectGameMode_ThreadSafe)
{
// Test that calling from multiple threads is safe
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 10; ++j)
{
detect_game_mode();
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
};
}

View File

@@ -1,218 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <gpo.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace powertoys_gpo;
namespace UnitTestsCommonUtils
{
TEST_CLASS(GpoTests)
{
public:
// Helper to check if result is a valid gpo_rule_configured_t value
static constexpr bool IsValidGpoResult(gpo_rule_configured_t result)
{
return result == gpo_rule_configured_wrong_value ||
result == gpo_rule_configured_unavailable ||
result == gpo_rule_configured_not_configured ||
result == gpo_rule_configured_disabled ||
result == gpo_rule_configured_enabled;
}
// gpo_rule_configured_t enum tests
TEST_METHOD(GpoRuleConfigured_EnumValues_AreDistinct)
{
Assert::AreNotEqual(static_cast<int>(gpo_rule_configured_not_configured),
static_cast<int>(gpo_rule_configured_enabled));
Assert::AreNotEqual(static_cast<int>(gpo_rule_configured_enabled),
static_cast<int>(gpo_rule_configured_disabled));
Assert::AreNotEqual(static_cast<int>(gpo_rule_configured_not_configured),
static_cast<int>(gpo_rule_configured_disabled));
}
// getConfiguredValue tests
TEST_METHOD(GetConfiguredValue_NonExistentKey_ReturnsNotConfigured)
{
auto result = getConfiguredValue(L"NonExistentPolicyValue12345");
Assert::IsTrue(result == gpo_rule_configured_not_configured ||
result == gpo_rule_configured_unavailable);
}
// Utility enabled getters - these all follow the same pattern
TEST_METHOD(GetAllowExperimentationValue_ReturnsValidState)
{
auto result = getAllowExperimentationValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredAlwaysOnTopEnabledValue_ReturnsValidState)
{
auto result = getConfiguredAlwaysOnTopEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredAwakeEnabledValue_ReturnsValidState)
{
auto result = getConfiguredAwakeEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredColorPickerEnabledValue_ReturnsValidState)
{
auto result = getConfiguredColorPickerEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredFancyZonesEnabledValue_ReturnsValidState)
{
auto result = getConfiguredFancyZonesEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredFileLocksmithEnabledValue_ReturnsValidState)
{
auto result = getConfiguredFileLocksmithEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredImageResizerEnabledValue_ReturnsValidState)
{
auto result = getConfiguredImageResizerEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredKeyboardManagerEnabledValue_ReturnsValidState)
{
auto result = getConfiguredKeyboardManagerEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredPowerRenameEnabledValue_ReturnsValidState)
{
auto result = getConfiguredPowerRenameEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredPowerLauncherEnabledValue_ReturnsValidState)
{
auto result = getConfiguredPowerLauncherEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredShortcutGuideEnabledValue_ReturnsValidState)
{
auto result = getConfiguredShortcutGuideEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredTextExtractorEnabledValue_ReturnsValidState)
{
auto result = getConfiguredTextExtractorEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredHostsFileEditorEnabledValue_ReturnsValidState)
{
auto result = getConfiguredHostsFileEditorEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredMousePointerCrosshairsEnabledValue_ReturnsValidState)
{
auto result = getConfiguredMousePointerCrosshairsEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredMouseHighlighterEnabledValue_ReturnsValidState)
{
auto result = getConfiguredMouseHighlighterEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredMouseJumpEnabledValue_ReturnsValidState)
{
auto result = getConfiguredMouseJumpEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredFindMyMouseEnabledValue_ReturnsValidState)
{
auto result = getConfiguredFindMyMouseEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredMouseWithoutBordersEnabledValue_ReturnsValidState)
{
auto result = getConfiguredMouseWithoutBordersEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredAdvancedPasteEnabledValue_ReturnsValidState)
{
auto result = getConfiguredAdvancedPasteEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredPeekEnabledValue_ReturnsValidState)
{
auto result = getConfiguredPeekEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredRegistryPreviewEnabledValue_ReturnsValidState)
{
auto result = getConfiguredRegistryPreviewEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredScreenRulerEnabledValue_ReturnsValidState)
{
auto result = getConfiguredScreenRulerEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredCropAndLockEnabledValue_ReturnsValidState)
{
auto result = getConfiguredCropAndLockEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
TEST_METHOD(GetConfiguredEnvironmentVariablesEnabledValue_ReturnsValidState)
{
auto result = getConfiguredEnvironmentVariablesEnabledValue();
Assert::IsTrue(IsValidGpoResult(result));
}
// All GPO functions should not crash
TEST_METHOD(AllGpoFunctions_DoNotCrash)
{
getAllowExperimentationValue();
getConfiguredAlwaysOnTopEnabledValue();
getConfiguredAwakeEnabledValue();
getConfiguredColorPickerEnabledValue();
getConfiguredFancyZonesEnabledValue();
getConfiguredFileLocksmithEnabledValue();
getConfiguredImageResizerEnabledValue();
getConfiguredKeyboardManagerEnabledValue();
getConfiguredPowerRenameEnabledValue();
getConfiguredPowerLauncherEnabledValue();
getConfiguredShortcutGuideEnabledValue();
getConfiguredTextExtractorEnabledValue();
getConfiguredHostsFileEditorEnabledValue();
getConfiguredMousePointerCrosshairsEnabledValue();
getConfiguredMouseHighlighterEnabledValue();
getConfiguredMouseJumpEnabledValue();
getConfiguredFindMyMouseEnabledValue();
getConfiguredMouseWithoutBordersEnabledValue();
getConfiguredAdvancedPasteEnabledValue();
getConfiguredPeekEnabledValue();
getConfiguredRegistryPreviewEnabledValue();
getConfiguredScreenRulerEnabledValue();
getConfiguredCropAndLockEnabledValue();
getConfiguredEnvironmentVariablesEnabledValue();
Assert::IsTrue(true);
}
};
}

View File

@@ -1,200 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <HDropIterator.h>
#include <shlobj.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(HDropIteratorTests)
{
public:
// Helper to create a test HDROP structure
static HGLOBAL CreateTestHDrop(const std::vector<std::wstring>& files)
{
// Calculate required size
size_t size = sizeof(DROPFILES);
for (const auto& file : files)
{
size += (file.length() + 1) * sizeof(wchar_t);
}
size += sizeof(wchar_t); // Double null terminator
HGLOBAL hGlobal = GlobalAlloc(GHND, size);
if (!hGlobal) return nullptr;
DROPFILES* pDropFiles = static_cast<DROPFILES*>(GlobalLock(hGlobal));
if (!pDropFiles)
{
GlobalFree(hGlobal);
return nullptr;
}
pDropFiles->pFiles = sizeof(DROPFILES);
pDropFiles->fWide = TRUE;
wchar_t* pData = reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(pDropFiles) + sizeof(DROPFILES));
for (const auto& file : files)
{
wcscpy_s(pData, file.length() + 1, file.c_str());
pData += file.length() + 1;
}
*pData = L'\0'; // Double null terminator
GlobalUnlock(hGlobal);
return hGlobal;
}
TEST_METHOD(HDropIterator_EmptyDrop_IsDoneImmediately)
{
HGLOBAL hGlobal = CreateTestHDrop({});
if (!hGlobal)
{
Assert::IsTrue(true); // Skip if allocation failed
return;
}
STGMEDIUM medium = {};
medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = hGlobal;
// Without a proper IDataObject, we can't fully test
// Just verify the class can be instantiated conceptually
GlobalFree(hGlobal);
Assert::IsTrue(true);
}
TEST_METHOD(HDropIterator_Iteration_Conceptual)
{
// This test verifies the concept of iteration
// Full integration testing requires a proper IDataObject
std::vector<std::wstring> testFiles = {
L"C:\\test\\file1.txt",
L"C:\\test\\file2.txt",
L"C:\\test\\file3.txt"
};
HGLOBAL hGlobal = CreateTestHDrop(testFiles);
if (!hGlobal)
{
Assert::IsTrue(true);
return;
}
// Verify we can create the HDROP structure
DROPFILES* pDropFiles = static_cast<DROPFILES*>(GlobalLock(hGlobal));
Assert::IsNotNull(pDropFiles);
Assert::IsTrue(pDropFiles->fWide);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
Assert::IsTrue(true);
}
TEST_METHOD(HDropIterator_SingleFile_Works)
{
std::vector<std::wstring> testFiles = { L"C:\\test\\single.txt" };
HGLOBAL hGlobal = CreateTestHDrop(testFiles);
if (!hGlobal)
{
Assert::IsTrue(true);
return;
}
// Verify structure
DROPFILES* pDropFiles = static_cast<DROPFILES*>(GlobalLock(hGlobal));
Assert::IsNotNull(pDropFiles);
// Read back the file name
wchar_t* pData = reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(pDropFiles) + pDropFiles->pFiles);
Assert::AreEqual(std::wstring(L"C:\\test\\single.txt"), std::wstring(pData));
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
TEST_METHOD(HDropIterator_MultipleFiles_Structure)
{
std::vector<std::wstring> testFiles = {
L"C:\\file1.txt",
L"C:\\file2.txt",
L"C:\\file3.txt"
};
HGLOBAL hGlobal = CreateTestHDrop(testFiles);
if (!hGlobal)
{
Assert::IsTrue(true);
return;
}
DROPFILES* pDropFiles = static_cast<DROPFILES*>(GlobalLock(hGlobal));
Assert::IsNotNull(pDropFiles);
// Count files by iterating through null-terminated strings
wchar_t* pData = reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(pDropFiles) + pDropFiles->pFiles);
int count = 0;
while (*pData)
{
count++;
pData += wcslen(pData) + 1;
}
Assert::AreEqual(3, count);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
TEST_METHOD(HDropIterator_UnicodeFilenames_Work)
{
std::vector<std::wstring> testFiles = {
L"C:\\test\\file.txt"
};
HGLOBAL hGlobal = CreateTestHDrop(testFiles);
if (!hGlobal)
{
Assert::IsTrue(true);
return;
}
DROPFILES* pDropFiles = static_cast<DROPFILES*>(GlobalLock(hGlobal));
Assert::IsTrue(pDropFiles->fWide == TRUE);
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
TEST_METHOD(HDropIterator_LongFilenames_Work)
{
std::wstring longPath = L"C:\\";
for (int i = 0; i < 20; ++i)
{
longPath += L"LongFolderName\\";
}
longPath += L"file.txt";
std::vector<std::wstring> testFiles = { longPath };
HGLOBAL hGlobal = CreateTestHDrop(testFiles);
if (!hGlobal)
{
Assert::IsTrue(true);
return;
}
DROPFILES* pDropFiles = static_cast<DROPFILES*>(GlobalLock(hGlobal));
Assert::IsNotNull(pDropFiles);
wchar_t* pData = reinterpret_cast<wchar_t*>(reinterpret_cast<BYTE*>(pDropFiles) + pDropFiles->pFiles);
Assert::AreEqual(longPath, std::wstring(pData));
GlobalUnlock(hGlobal);
GlobalFree(hGlobal);
}
};
}

View File

@@ -1,152 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <HttpClient.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(HttpClientTests)
{
public:
// Note: Network tests may fail in offline environments
// These tests are designed to verify the API doesn't crash
TEST_METHOD(HttpClient_DefaultConstruction)
{
http::HttpClient client;
// Should not crash
Assert::IsTrue(true);
}
TEST_METHOD(HttpClient_Request_InvalidUri_ReturnsEmpty)
{
http::HttpClient client;
try
{
// Invalid URI should not crash, may throw or return empty
auto result = client.request(winrt::Windows::Foundation::Uri(L"invalid://not-a-valid-uri"));
// If we get here, result may be empty or contain error
}
catch (...)
{
// Exception is acceptable for invalid URI
}
Assert::IsTrue(true);
}
TEST_METHOD(HttpClient_Download_InvalidUri_DoesNotCrash)
{
http::HttpClient client;
TestHelpers::TempFile tempFile;
try
{
auto result = client.download(
winrt::Windows::Foundation::Uri(L"https://invalid.invalid.invalid"),
tempFile.path());
// May return false or throw
}
catch (...)
{
// Exception is acceptable for invalid/unreachable URI
}
Assert::IsTrue(true);
}
TEST_METHOD(HttpClient_Download_WithCallback_DoesNotCrash)
{
http::HttpClient client;
TestHelpers::TempFile tempFile;
std::atomic<int> callbackCount{ 0 };
try
{
auto result = client.download(
winrt::Windows::Foundation::Uri(L"https://invalid.invalid.invalid"),
tempFile.path(),
[&callbackCount]([[maybe_unused]] float progress) {
callbackCount++;
});
}
catch (...)
{
// Exception is acceptable
}
Assert::IsTrue(true);
}
TEST_METHOD(HttpClient_Download_EmptyPath_DoesNotCrash)
{
http::HttpClient client;
try
{
auto result = client.download(
winrt::Windows::Foundation::Uri(L"https://example.com"),
L"");
}
catch (...)
{
// Exception is acceptable for empty path
}
Assert::IsTrue(true);
}
// These tests require network access and may be skipped in offline environments
TEST_METHOD(HttpClient_Request_ValidUri_ReturnsResult)
{
// Skip this test in most CI environments
// Only run manually to verify network functionality
http::HttpClient client;
try
{
// Use a reliable, fast-responding URL
auto result = client.request(winrt::Windows::Foundation::Uri(L"https://www.microsoft.com"));
// Result may or may not be successful depending on network
}
catch (...)
{
// Network errors are acceptable in test environment
}
Assert::IsTrue(true);
}
// Thread safety test (doesn't require network)
TEST_METHOD(HttpClient_MultipleInstances_DoNotCrash)
{
std::vector<std::unique_ptr<http::HttpClient>> clients;
for (int i = 0; i < 10; ++i)
{
clients.push_back(std::make_unique<http::HttpClient>());
}
// All clients should coexist without crashing
Assert::AreEqual(static_cast<size_t>(10), clients.size());
}
TEST_METHOD(HttpClient_ConcurrentConstruction_DoesNotCrash)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 5; ++i)
{
threads.emplace_back([&successCount]() {
http::HttpClient client;
successCount++;
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(5, successCount.load());
}
};
}

View File

@@ -1,283 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <json.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace winrt::Windows::Data::Json;
namespace UnitTestsCommonUtils
{
TEST_CLASS(JsonTests)
{
public:
// from_file tests
TEST_METHOD(FromFile_NonExistentFile_ReturnsNullopt)
{
auto result = json::from_file(L"C:\\NonExistent\\File\\Path.json");
Assert::IsFalse(result.has_value());
}
TEST_METHOD(FromFile_ValidJsonFile_ReturnsJsonObject)
{
TestHelpers::TempFile tempFile(L"", L".json");
tempFile.write("{\"key\": \"value\"}");
auto result = json::from_file(tempFile.path());
Assert::IsTrue(result.has_value());
}
TEST_METHOD(FromFile_InvalidJson_ReturnsNullopt)
{
TestHelpers::TempFile tempFile(L"", L".json");
tempFile.write("not valid json {{{");
auto result = json::from_file(tempFile.path());
Assert::IsFalse(result.has_value());
}
TEST_METHOD(FromFile_EmptyFile_ReturnsNullopt)
{
TestHelpers::TempFile tempFile(L"", L".json");
// File is empty
auto result = json::from_file(tempFile.path());
Assert::IsFalse(result.has_value());
}
TEST_METHOD(FromFile_ValidComplexJson_ParsesCorrectly)
{
TestHelpers::TempFile tempFile(L"", L".json");
tempFile.write("{\"name\": \"test\", \"value\": 42, \"enabled\": true}");
auto result = json::from_file(tempFile.path());
Assert::IsTrue(result.has_value());
auto& obj = *result;
Assert::IsTrue(obj.HasKey(L"name"));
Assert::IsTrue(obj.HasKey(L"value"));
Assert::IsTrue(obj.HasKey(L"enabled"));
}
// to_file tests
TEST_METHOD(ToFile_ValidObject_WritesFile)
{
TestHelpers::TempFile tempFile(L"", L".json");
JsonObject obj;
obj.SetNamedValue(L"key", JsonValue::CreateStringValue(L"value"));
json::to_file(tempFile.path(), obj);
// Read back and verify
auto result = json::from_file(tempFile.path());
Assert::IsTrue(result.has_value());
Assert::IsTrue(result->HasKey(L"key"));
}
TEST_METHOD(ToFile_ComplexObject_WritesFile)
{
TestHelpers::TempFile tempFile(L"", L".json");
JsonObject obj;
obj.SetNamedValue(L"name", JsonValue::CreateStringValue(L"test"));
obj.SetNamedValue(L"value", JsonValue::CreateNumberValue(42));
obj.SetNamedValue(L"enabled", JsonValue::CreateBooleanValue(true));
json::to_file(tempFile.path(), obj);
auto result = json::from_file(tempFile.path());
Assert::IsTrue(result.has_value());
Assert::AreEqual(std::wstring(L"test"), std::wstring(result->GetNamedString(L"name")));
Assert::AreEqual(42.0, result->GetNamedNumber(L"value"));
Assert::IsTrue(result->GetNamedBoolean(L"enabled"));
}
// has tests
TEST_METHOD(Has_ExistingKey_ReturnsTrue)
{
JsonObject obj;
obj.SetNamedValue(L"key", JsonValue::CreateStringValue(L"value"));
Assert::IsTrue(json::has(obj, L"key", JsonValueType::String));
}
TEST_METHOD(Has_NonExistingKey_ReturnsFalse)
{
JsonObject obj;
Assert::IsFalse(json::has(obj, L"key", JsonValueType::String));
}
TEST_METHOD(Has_WrongType_ReturnsFalse)
{
JsonObject obj;
obj.SetNamedValue(L"key", JsonValue::CreateStringValue(L"value"));
Assert::IsFalse(json::has(obj, L"key", JsonValueType::Number));
}
TEST_METHOD(Has_NumberType_ReturnsTrue)
{
JsonObject obj;
obj.SetNamedValue(L"key", JsonValue::CreateNumberValue(42));
Assert::IsTrue(json::has(obj, L"key", JsonValueType::Number));
}
TEST_METHOD(Has_BooleanType_ReturnsTrue)
{
JsonObject obj;
obj.SetNamedValue(L"key", JsonValue::CreateBooleanValue(true));
Assert::IsTrue(json::has(obj, L"key", JsonValueType::Boolean));
}
TEST_METHOD(Has_ObjectType_ReturnsTrue)
{
JsonObject obj;
JsonObject nested;
obj.SetNamedValue(L"key", nested);
Assert::IsTrue(json::has(obj, L"key", JsonValueType::Object));
}
// value function tests
TEST_METHOD(Value_IntegerType_CreatesNumberValue)
{
auto val = json::value(42);
Assert::IsTrue(val.ValueType() == JsonValueType::Number);
Assert::AreEqual(42.0, val.GetNumber());
}
TEST_METHOD(Value_DoubleType_CreatesNumberValue)
{
auto val = json::value(3.14);
Assert::IsTrue(val.ValueType() == JsonValueType::Number);
Assert::AreEqual(3.14, val.GetNumber());
}
TEST_METHOD(Value_BooleanTrue_CreatesBooleanValue)
{
auto val = json::value(true);
Assert::IsTrue(val.ValueType() == JsonValueType::Boolean);
Assert::IsTrue(val.GetBoolean());
}
TEST_METHOD(Value_BooleanFalse_CreatesBooleanValue)
{
auto val = json::value(false);
Assert::IsTrue(val.ValueType() == JsonValueType::Boolean);
Assert::IsFalse(val.GetBoolean());
}
TEST_METHOD(Value_String_CreatesStringValue)
{
auto val = json::value(L"hello");
Assert::IsTrue(val.ValueType() == JsonValueType::String);
Assert::AreEqual(std::wstring(L"hello"), std::wstring(val.GetString()));
}
TEST_METHOD(Value_JsonObject_ReturnsJsonValue)
{
JsonObject obj;
obj.SetNamedValue(L"key", JsonValue::CreateStringValue(L"value"));
auto val = json::value(obj);
Assert::IsTrue(val.ValueType() == JsonValueType::Object);
}
TEST_METHOD(Value_JsonValue_ReturnsIdentity)
{
auto original = JsonValue::CreateStringValue(L"test");
auto result = json::value(original);
Assert::AreEqual(std::wstring(L"test"), std::wstring(result.GetString()));
}
// get function tests
TEST_METHOD(Get_BooleanValue_ReturnsValue)
{
JsonObject obj;
obj.SetNamedValue(L"enabled", JsonValue::CreateBooleanValue(true));
bool result = false;
json::get(obj, L"enabled", result);
Assert::IsTrue(result);
}
TEST_METHOD(Get_IntValue_ReturnsValue)
{
JsonObject obj;
obj.SetNamedValue(L"count", JsonValue::CreateNumberValue(42));
int result = 0;
json::get(obj, L"count", result);
Assert::AreEqual(42, result);
}
TEST_METHOD(Get_DoubleValue_ReturnsValue)
{
JsonObject obj;
obj.SetNamedValue(L"ratio", JsonValue::CreateNumberValue(3.14));
double result = 0.0;
json::get(obj, L"ratio", result);
Assert::AreEqual(3.14, result);
}
TEST_METHOD(Get_StringValue_ReturnsValue)
{
JsonObject obj;
obj.SetNamedValue(L"name", JsonValue::CreateStringValue(L"test"));
std::wstring result;
json::get(obj, L"name", result);
Assert::AreEqual(std::wstring(L"test"), result);
}
TEST_METHOD(Get_MissingKey_UsesDefault)
{
JsonObject obj;
int result = 0;
json::get(obj, L"missing", result, 99);
Assert::AreEqual(99, result);
}
TEST_METHOD(Get_MissingKeyNoDefault_PreservesOriginal)
{
JsonObject obj;
int result = 42;
json::get(obj, L"missing", result);
// When key is missing and no default, original value is preserved
Assert::AreEqual(42, result);
}
TEST_METHOD(Get_JsonObject_ReturnsObject)
{
JsonObject obj;
JsonObject nested;
nested.SetNamedValue(L"inner", JsonValue::CreateStringValue(L"value"));
obj.SetNamedValue(L"nested", nested);
JsonObject result;
json::get(obj, L"nested", result);
Assert::IsTrue(result.HasKey(L"inner"));
}
// Roundtrip tests
TEST_METHOD(Roundtrip_ComplexObject_PreservesData)
{
TestHelpers::TempFile tempFile(L"", L".json");
JsonObject original;
original.SetNamedValue(L"string", JsonValue::CreateStringValue(L"hello"));
original.SetNamedValue(L"number", JsonValue::CreateNumberValue(42));
original.SetNamedValue(L"boolean", JsonValue::CreateBooleanValue(true));
JsonObject nested;
nested.SetNamedValue(L"inner", JsonValue::CreateStringValue(L"world"));
original.SetNamedValue(L"object", nested);
json::to_file(tempFile.path(), original);
auto loaded = json::from_file(tempFile.path());
Assert::IsTrue(loaded.has_value());
Assert::AreEqual(std::wstring(L"hello"), std::wstring(loaded->GetNamedString(L"string")));
Assert::AreEqual(42.0, loaded->GetNamedNumber(L"number"));
Assert::IsTrue(loaded->GetNamedBoolean(L"boolean"));
Assert::AreEqual(std::wstring(L"world"), std::wstring(loaded->GetNamedObject(L"object").GetNamedString(L"inner")));
}
};
}

View File

@@ -1,180 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <logger_helper.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace LoggerHelpers;
namespace UnitTestsCommonUtils
{
TEST_CLASS(LoggerHelperTests)
{
public:
// get_log_folder_path tests
TEST_METHOD(GetLogFolderPath_ValidAppPath_ReturnsPath)
{
auto result = get_log_folder_path(L"TestApp");
Assert::IsFalse(result.empty());
// Should contain the app name or be a valid path
auto pathStr = result.wstring();
Assert::IsTrue(pathStr.length() > 0);
}
TEST_METHOD(GetLogFolderPath_EmptyAppPath_ReturnsPath)
{
auto result = get_log_folder_path(L"");
// Should still return a base path
Assert::IsTrue(true); // Just verify no crash
}
TEST_METHOD(GetLogFolderPath_SpecialCharacters_Works)
{
auto result = get_log_folder_path(L"Test App With Spaces");
// Should handle spaces in path
Assert::IsTrue(true);
}
TEST_METHOD(GetLogFolderPath_ConsistentResults)
{
auto result1 = get_log_folder_path(L"TestApp");
auto result2 = get_log_folder_path(L"TestApp");
Assert::AreEqual(result1.wstring(), result2.wstring());
}
// dir_exists tests
TEST_METHOD(DirExists_WindowsDirectory_ReturnsTrue)
{
bool result = dir_exists(std::filesystem::path(L"C:\\Windows"));
Assert::IsTrue(result);
}
TEST_METHOD(DirExists_NonExistentDirectory_ReturnsFalse)
{
bool result = dir_exists(std::filesystem::path(L"C:\\NonExistentDir12345"));
Assert::IsFalse(result);
}
TEST_METHOD(DirExists_FileInsteadOfDir_ReturnsTrue)
{
// notepad.exe is a file, not a directory
bool result = dir_exists(std::filesystem::path(L"C:\\Windows\\notepad.exe"));
Assert::IsTrue(result);
}
TEST_METHOD(DirExists_EmptyPath_ReturnsFalse)
{
bool result = dir_exists(std::filesystem::path(L""));
Assert::IsFalse(result);
}
TEST_METHOD(DirExists_TempDirectory_ReturnsTrue)
{
wchar_t tempPath[MAX_PATH];
GetTempPathW(MAX_PATH, tempPath);
bool result = dir_exists(std::filesystem::path(tempPath));
Assert::IsTrue(result);
}
// delete_old_log_folder tests
TEST_METHOD(DeleteOldLogFolder_NonExistentFolder_DoesNotCrash)
{
delete_old_log_folder(std::filesystem::path(L"C:\\NonExistentLogFolder12345"));
Assert::IsTrue(true);
}
TEST_METHOD(DeleteOldLogFolder_ValidEmptyFolder_Works)
{
TestHelpers::TempDirectory tempDir;
// Create a subfolder structure
auto logFolder = std::filesystem::path(tempDir.path()) / L"logs";
std::filesystem::create_directories(logFolder);
Assert::IsTrue(std::filesystem::exists(logFolder));
delete_old_log_folder(logFolder);
// Folder may or may not be deleted depending on implementation
Assert::IsTrue(true);
}
// delete_other_versions_log_folders tests
TEST_METHOD(DeleteOtherVersionsLogFolders_NonExistentPath_DoesNotCrash)
{
delete_other_versions_log_folders(L"C:\\NonExistent\\Path", L"1.0.0");
Assert::IsTrue(true);
}
TEST_METHOD(DeleteOtherVersionsLogFolders_EmptyVersion_DoesNotCrash)
{
wchar_t tempPath[MAX_PATH];
GetTempPathW(MAX_PATH, tempPath);
delete_other_versions_log_folders(tempPath, L"");
Assert::IsTrue(true);
}
// Thread safety tests
TEST_METHOD(GetLogFolderPath_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount, i]() {
auto path = get_log_folder_path(L"TestApp" + std::to_wstring(i));
if (!path.empty())
{
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(10, successCount.load());
}
TEST_METHOD(DirExists_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 10; ++j)
{
dir_exists(std::filesystem::path(L"C:\\Windows"));
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
// Path construction tests
TEST_METHOD(GetLogFolderPath_ReturnsValidFilesystemPath)
{
auto result = get_log_folder_path(L"TestApp");
// Should be a valid path that we can use with filesystem operations
Assert::IsTrue(result.is_absolute() || result.has_root_name() || !result.empty());
}
};
}

View File

@@ -1,173 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <modulesRegistry.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
static std::wstring GetInstallDir()
{
wchar_t path[MAX_PATH];
GetModuleFileNameW(nullptr, path, MAX_PATH);
return std::filesystem::path{ path }.parent_path().wstring();
}
TEST_CLASS(ModulesRegistryTests)
{
public:
// Test that all changeset generator functions return valid changesets
TEST_METHOD(GetSvgPreviewHandlerChangeSet_ReturnsChangeSet)
{
auto changeSet = getSvgPreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetSvgThumbnailProviderChangeSet_ReturnsChangeSet)
{
auto changeSet = getSvgThumbnailHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetMarkdownPreviewHandlerChangeSet_ReturnsChangeSet)
{
auto changeSet = getMdPreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetMonacoPreviewHandlerChangeSet_ReturnsChangeSet)
{
auto changeSet = getMonacoPreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetPdfPreviewHandlerChangeSet_ReturnsChangeSet)
{
auto changeSet = getPdfPreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetPdfThumbnailProviderChangeSet_ReturnsChangeSet)
{
auto changeSet = getPdfThumbnailHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetGcodePreviewHandlerChangeSet_ReturnsChangeSet)
{
auto changeSet = getGcodePreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetGcodeThumbnailProviderChangeSet_ReturnsChangeSet)
{
auto changeSet = getGcodeThumbnailHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetStlThumbnailProviderChangeSet_ReturnsChangeSet)
{
auto changeSet = getStlThumbnailHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetQoiPreviewHandlerChangeSet_ReturnsChangeSet)
{
auto changeSet = getQoiPreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
TEST_METHOD(GetQoiThumbnailProviderChangeSet_ReturnsChangeSet)
{
auto changeSet = getQoiThumbnailHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
// Test enabled vs disabled state
TEST_METHOD(ChangeSet_EnabledVsDisabled_MayDiffer)
{
auto enabledSet = getSvgPreviewHandlerChangeSet(GetInstallDir(), true);
auto disabledSet = getSvgPreviewHandlerChangeSet(GetInstallDir(), false);
// Both should be valid change sets
Assert::IsFalse(enabledSet.changes.empty());
Assert::IsFalse(disabledSet.changes.empty());
}
// Test getAllOnByDefaultModulesChangeSets
TEST_METHOD(GetAllOnByDefaultModulesChangeSets_ReturnsMultipleChangeSets)
{
auto changeSets = getAllOnByDefaultModulesChangeSets(GetInstallDir());
// Should return multiple changesets for all default-enabled modules
Assert::IsTrue(changeSets.size() > 0);
}
// Test getAllModulesChangeSets
TEST_METHOD(GetAllModulesChangeSets_ReturnsChangeSets)
{
auto changeSets = getAllModulesChangeSets(GetInstallDir());
// Should return changesets for all modules
Assert::IsTrue(changeSets.size() > 0);
}
TEST_METHOD(GetAllModulesChangeSets_ContainsMoreThanOnByDefault)
{
auto allSets = getAllModulesChangeSets(GetInstallDir());
auto defaultSets = getAllOnByDefaultModulesChangeSets(GetInstallDir());
// All modules should be >= on-by-default modules
Assert::IsTrue(allSets.size() >= defaultSets.size());
}
// Test that changesets have valid structure
TEST_METHOD(ChangeSet_HasValidKeyPath)
{
auto changeSet = getSvgPreviewHandlerChangeSet(GetInstallDir(), false);
Assert::IsFalse(changeSet.changes.empty());
}
// Test all changeset functions don't crash
TEST_METHOD(AllChangeSetFunctions_DoNotCrash)
{
auto installDir = GetInstallDir();
getSvgPreviewHandlerChangeSet(installDir, true);
getSvgPreviewHandlerChangeSet(installDir, false);
getSvgThumbnailHandlerChangeSet(installDir, true);
getSvgThumbnailHandlerChangeSet(installDir, false);
getMdPreviewHandlerChangeSet(installDir, true);
getMdPreviewHandlerChangeSet(installDir, false);
getMonacoPreviewHandlerChangeSet(installDir, true);
getMonacoPreviewHandlerChangeSet(installDir, false);
getPdfPreviewHandlerChangeSet(installDir, true);
getPdfPreviewHandlerChangeSet(installDir, false);
getPdfThumbnailHandlerChangeSet(installDir, true);
getPdfThumbnailHandlerChangeSet(installDir, false);
getGcodePreviewHandlerChangeSet(installDir, true);
getGcodePreviewHandlerChangeSet(installDir, false);
getGcodeThumbnailHandlerChangeSet(installDir, true);
getGcodeThumbnailHandlerChangeSet(installDir, false);
getStlThumbnailHandlerChangeSet(installDir, true);
getStlThumbnailHandlerChangeSet(installDir, false);
getQoiPreviewHandlerChangeSet(installDir, true);
getQoiPreviewHandlerChangeSet(installDir, false);
getQoiThumbnailHandlerChangeSet(installDir, true);
getQoiThumbnailHandlerChangeSet(installDir, false);
Assert::IsTrue(true);
}
};
}

View File

@@ -1,65 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <MsWindowsSettings.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(MsWindowsSettingsTests)
{
public:
TEST_METHOD(GetAnimationsEnabled_ReturnsBoolean)
{
bool result = GetAnimationsEnabled();
// Should return a valid boolean
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(GetAnimationsEnabled_ConsistentResults)
{
// Multiple calls should return consistent results
bool result1 = GetAnimationsEnabled();
bool result2 = GetAnimationsEnabled();
bool result3 = GetAnimationsEnabled();
Assert::AreEqual(result1, result2);
Assert::AreEqual(result2, result3);
}
TEST_METHOD(GetAnimationsEnabled_DoesNotCrash)
{
// Call multiple times to ensure stability
for (int i = 0; i < 100; ++i)
{
GetAnimationsEnabled();
}
Assert::IsTrue(true);
}
TEST_METHOD(GetAnimationsEnabled_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 10; ++j)
{
GetAnimationsEnabled();
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
};
}

View File

@@ -1,146 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <MsiUtils.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(MsiUtilsTests)
{
public:
// GetMsiPackageInstalledPath tests
TEST_METHOD(GetMsiPackageInstalledPath_PerUser_DoesNotCrash)
{
auto result = GetMsiPackageInstalledPath(true);
// Result depends on installation state, but should not crash
Assert::IsTrue(true);
}
TEST_METHOD(GetMsiPackageInstalledPath_PerMachine_DoesNotCrash)
{
auto result = GetMsiPackageInstalledPath(false);
// Result depends on installation state, but should not crash
Assert::IsTrue(true);
}
TEST_METHOD(GetMsiPackageInstalledPath_ConsistentResults)
{
auto result1 = GetMsiPackageInstalledPath(true);
auto result2 = GetMsiPackageInstalledPath(true);
// Results should be consistent
Assert::AreEqual(result1.has_value(), result2.has_value());
if (result1.has_value() && result2.has_value())
{
Assert::AreEqual(*result1, *result2);
}
}
TEST_METHOD(GetMsiPackageInstalledPath_PerUserVsPerMachine_MayDiffer)
{
auto perUser = GetMsiPackageInstalledPath(true);
auto perMachine = GetMsiPackageInstalledPath(false);
// These may or may not be equal depending on installation
// Just verify they don't crash
Assert::IsTrue(true);
}
// GetMsiPackagePath tests
TEST_METHOD(GetMsiPackagePath_DoesNotCrash)
{
auto result = GetMsiPackagePath();
// Result depends on installation state, but should not crash
Assert::IsTrue(true);
}
TEST_METHOD(GetMsiPackagePath_ConsistentResults)
{
auto result1 = GetMsiPackagePath();
auto result2 = GetMsiPackagePath();
// Results should be consistent
Assert::AreEqual(result1, result2);
}
// Thread safety tests
TEST_METHOD(GetMsiPackageInstalledPath_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 5; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 5; ++j)
{
GetMsiPackageInstalledPath(j % 2 == 0);
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(25, successCount.load());
}
TEST_METHOD(GetMsiPackagePath_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 5; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 5; ++j)
{
GetMsiPackagePath();
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(25, successCount.load());
}
// Return value format tests
TEST_METHOD(GetMsiPackageInstalledPath_ReturnsValidPathOrEmpty)
{
auto path = GetMsiPackageInstalledPath(true);
if (path.has_value() && !path->empty())
{
// If a path is returned, it should contain backslash or be a valid path format
Assert::IsTrue(path->find(L'\\') != std::wstring::npos ||
path->find(L'/') != std::wstring::npos ||
path->length() >= 2); // At minimum drive letter + colon
}
// No value or empty is also valid (not installed)
Assert::IsTrue(true);
}
TEST_METHOD(GetMsiPackagePath_ReturnsValidPathOrEmpty)
{
auto path = GetMsiPackagePath();
if (!path.empty())
{
// If a path is returned, it should be a valid path format
Assert::IsTrue(path.find(L'\\') != std::wstring::npos ||
path.find(L'/') != std::wstring::npos ||
path.length() >= 2);
}
Assert::IsTrue(true);
}
};
}

View File

@@ -1,107 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <os-detect.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(OsDetectTests)
{
public:
// IsAPIContractVxAvailable tests
TEST_METHOD(IsAPIContractV8Available_ReturnsBoolean)
{
// This test verifies the function runs without crashing
// The actual result depends on the OS version
bool result = IsAPIContractV8Available();
// Result is either true or false, both are valid
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsAPIContractVxAvailable_V1_ReturnsTrue)
{
// API contract v1 should be available on any modern Windows
bool result = IsAPIContractVxAvailable<1>();
Assert::IsTrue(result);
}
TEST_METHOD(IsAPIContractVxAvailable_V5_ReturnsBooleanConsistently)
{
// Call multiple times to verify caching works correctly
bool result1 = IsAPIContractVxAvailable<5>();
bool result2 = IsAPIContractVxAvailable<5>();
bool result3 = IsAPIContractVxAvailable<5>();
Assert::AreEqual(result1, result2);
Assert::AreEqual(result2, result3);
}
TEST_METHOD(IsAPIContractVxAvailable_V10_ReturnsBoolean)
{
bool result = IsAPIContractVxAvailable<10>();
// Result depends on Windows version, but should not crash
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsAPIContractVxAvailable_V15_ReturnsBoolean)
{
bool result = IsAPIContractVxAvailable<15>();
// Higher API versions, may or may not be available
Assert::IsTrue(result == true || result == false);
}
// Is19H1OrHigher tests
TEST_METHOD(Is19H1OrHigher_ReturnsBoolean)
{
bool result = Is19H1OrHigher();
// Result depends on OS version, but should not crash
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(Is19H1OrHigher_ReturnsSameAsV8Contract)
{
// Is19H1OrHigher is implemented as IsAPIContractV8Available
bool is19H1 = Is19H1OrHigher();
bool isV8 = IsAPIContractV8Available();
Assert::AreEqual(is19H1, isV8);
}
TEST_METHOD(Is19H1OrHigher_ConsistentAcrossMultipleCalls)
{
bool result1 = Is19H1OrHigher();
bool result2 = Is19H1OrHigher();
bool result3 = Is19H1OrHigher();
Assert::AreEqual(result1, result2);
Assert::AreEqual(result2, result3);
}
// Static caching behavior tests
TEST_METHOD(StaticCaching_DifferentContractVersions_IndependentResults)
{
// Each template instantiation has its own static variable
bool v1 = IsAPIContractVxAvailable<1>();
(void)v1; // Suppress unused variable warning
// v1 should be true on any modern Windows
Assert::IsTrue(v1);
}
// Performance test (optional - verifies caching)
TEST_METHOD(Performance_MultipleCallsAreFast)
{
// The static caching should make subsequent calls very fast
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; ++i)
{
Is19H1OrHigher();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// 10000 calls should complete in well under 1 second due to caching
Assert::IsTrue(duration.count() < 1000);
}
};
}

View File

@@ -1,180 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <package.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace package;
namespace UnitTestsCommonUtils
{
TEST_CLASS(PackageTests)
{
public:
// IsWin11OrGreater tests
TEST_METHOD(IsWin11OrGreater_ReturnsBoolean)
{
bool result = IsWin11OrGreater();
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsWin11OrGreater_ConsistentResults)
{
bool result1 = IsWin11OrGreater();
bool result2 = IsWin11OrGreater();
bool result3 = IsWin11OrGreater();
Assert::AreEqual(result1, result2);
Assert::AreEqual(result2, result3);
}
// PACKAGE_VERSION struct tests
TEST_METHOD(PackageVersion_DefaultConstruction)
{
PACKAGE_VERSION version{};
Assert::AreEqual(static_cast<UINT16>(0), version.Major);
Assert::AreEqual(static_cast<UINT16>(0), version.Minor);
Assert::AreEqual(static_cast<UINT16>(0), version.Build);
Assert::AreEqual(static_cast<UINT16>(0), version.Revision);
}
TEST_METHOD(PackageVersion_Assignment)
{
PACKAGE_VERSION version{};
version.Major = 1;
version.Minor = 2;
version.Build = 3;
version.Revision = 4;
Assert::AreEqual(static_cast<UINT16>(1), version.Major);
Assert::AreEqual(static_cast<UINT16>(2), version.Minor);
Assert::AreEqual(static_cast<UINT16>(3), version.Build);
Assert::AreEqual(static_cast<UINT16>(4), version.Revision);
}
// ComInitializer tests
TEST_METHOD(ComInitializer_InitializesAndUninitializesCom)
{
{
ComInitializer comInit;
// COM should be initialized within this scope
}
// COM should be uninitialized after scope
// Verify we can initialize again
{
ComInitializer comInit2;
}
Assert::IsTrue(true);
}
TEST_METHOD(ComInitializer_MultipleInstances)
{
ComInitializer init1;
ComInitializer init2;
ComInitializer init3;
// Multiple initializations should work (COM uses reference counting)
Assert::IsTrue(true);
}
// GetRegisteredPackage tests
TEST_METHOD(GetRegisteredPackage_NonExistentPackage_ReturnsEmpty)
{
auto result = GetRegisteredPackage(L"NonExistentPackage12345", false);
// Should return empty for non-existent package
Assert::IsFalse(result.has_value());
}
TEST_METHOD(GetRegisteredPackage_EmptyName_DoesNotCrash)
{
auto result = GetRegisteredPackage(L"", false);
// Behavior may vary based on package enumeration; just ensure it doesn't crash.
Assert::IsTrue(true);
}
// IsPackageRegisteredWithPowerToysVersion tests
TEST_METHOD(IsPackageRegisteredWithPowerToysVersion_NonExistentPackage_ReturnsFalse)
{
bool result = IsPackageRegisteredWithPowerToysVersion(L"NonExistentPackage12345");
Assert::IsFalse(result);
}
TEST_METHOD(IsPackageRegisteredWithPowerToysVersion_EmptyName_ReturnsFalse)
{
bool result = IsPackageRegisteredWithPowerToysVersion(L"");
Assert::IsFalse(result);
}
// FindMsixFile tests
TEST_METHOD(FindMsixFile_NonExistentDirectory_ReturnsEmpty)
{
auto result = FindMsixFile(L"C:\\NonExistentDirectory12345", false);
Assert::IsTrue(result.empty());
}
TEST_METHOD(FindMsixFile_SystemDirectory_DoesNotCrash)
{
// System32 probably doesn't have MSIX files, but shouldn't crash
auto result = FindMsixFile(L"C:\\Windows\\System32", false);
// May or may not find files, but should not crash
Assert::IsTrue(true);
}
TEST_METHOD(FindMsixFile_RecursiveSearch_DoesNotCrash)
{
// Use temp directory which should exist
wchar_t tempPath[MAX_PATH];
GetTempPathW(MAX_PATH, tempPath);
auto result = FindMsixFile(tempPath, true);
// May or may not find files, but should not crash
Assert::IsTrue(true);
}
// GetPackageNameAndVersionFromAppx tests
TEST_METHOD(GetPackageNameAndVersionFromAppx_NonExistentFile_ReturnsFalse)
{
std::wstring name;
PACKAGE_VERSION version{};
bool result = GetPackageNameAndVersionFromAppx(L"C:\\NonExistent\\file.msix", name, version);
Assert::IsFalse(result);
}
TEST_METHOD(GetPackageNameAndVersionFromAppx_EmptyPath_ReturnsFalse)
{
std::wstring name;
PACKAGE_VERSION version{};
bool result = GetPackageNameAndVersionFromAppx(L"", name, version);
Assert::IsFalse(result);
}
// Thread safety
TEST_METHOD(IsWin11OrGreater_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 10; ++j)
{
IsWin11OrGreater();
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
};
}

View File

@@ -1,136 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <processApi.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ProcessApiTests)
{
public:
TEST_METHOD(GetProcessHandlesByName_CurrentProcess_ReturnsHandles)
{
// Get current process executable name
wchar_t path[MAX_PATH];
GetModuleFileNameW(nullptr, path, MAX_PATH);
// Extract just the filename
std::wstring fullPath(path);
auto lastSlash = fullPath.rfind(L'\\');
std::wstring exeName = (lastSlash != std::wstring::npos) ?
fullPath.substr(lastSlash + 1) : fullPath;
auto handles = getProcessHandlesByName(exeName, PROCESS_QUERY_LIMITED_INFORMATION);
// Should find at least our own process
Assert::IsFalse(handles.empty());
// Handles are RAII-managed
}
TEST_METHOD(GetProcessHandlesByName_NonExistentProcess_ReturnsEmpty)
{
auto handles = getProcessHandlesByName(L"NonExistentProcess12345.exe", PROCESS_QUERY_LIMITED_INFORMATION);
Assert::IsTrue(handles.empty());
}
TEST_METHOD(GetProcessHandlesByName_EmptyName_ReturnsEmpty)
{
auto handles = getProcessHandlesByName(L"", PROCESS_QUERY_LIMITED_INFORMATION);
Assert::IsTrue(handles.empty());
}
TEST_METHOD(GetProcessHandlesByName_Explorer_ReturnsHandles)
{
// Explorer.exe should typically be running
auto handles = getProcessHandlesByName(L"explorer.exe", PROCESS_QUERY_LIMITED_INFORMATION);
// Handles are RAII-managed
// May or may not find explorer depending on system state
// Just verify it doesn't crash
Assert::IsTrue(true);
}
TEST_METHOD(GetProcessHandlesByName_CaseInsensitive_Works)
{
// Get current process name in uppercase
wchar_t path[MAX_PATH];
GetModuleFileNameW(nullptr, path, MAX_PATH);
std::wstring fullPath(path);
auto lastSlash = fullPath.rfind(L'\\');
std::wstring exeName = (lastSlash != std::wstring::npos) ?
fullPath.substr(lastSlash + 1) : fullPath;
// Convert to uppercase
std::wstring upperName = exeName;
std::transform(upperName.begin(), upperName.end(), upperName.begin(), ::towupper);
auto handles = getProcessHandlesByName(upperName, PROCESS_QUERY_LIMITED_INFORMATION);
// Handles are RAII-managed
// The function may or may not be case insensitive - just don't crash
Assert::IsTrue(true);
}
TEST_METHOD(GetProcessHandlesByName_DifferentAccessRights_Works)
{
wchar_t path[MAX_PATH];
GetModuleFileNameW(nullptr, path, MAX_PATH);
std::wstring fullPath(path);
auto lastSlash = fullPath.rfind(L'\\');
std::wstring exeName = (lastSlash != std::wstring::npos) ?
fullPath.substr(lastSlash + 1) : fullPath;
// Try with different access rights
auto handles1 = getProcessHandlesByName(exeName, PROCESS_QUERY_INFORMATION);
auto handles2 = getProcessHandlesByName(exeName, PROCESS_VM_READ);
// Handles are RAII-managed
// Just verify no crashes
Assert::IsTrue(true);
}
TEST_METHOD(GetProcessHandlesByName_SystemProcess_MayRequireElevation)
{
// System processes might require elevation
auto handles = getProcessHandlesByName(L"System", PROCESS_QUERY_LIMITED_INFORMATION);
// Handles are RAII-managed
// Just verify no crashes
Assert::IsTrue(true);
}
TEST_METHOD(GetProcessHandlesByName_ValidHandles_AreUsable)
{
wchar_t path[MAX_PATH];
GetModuleFileNameW(nullptr, path, MAX_PATH);
std::wstring fullPath(path);
auto lastSlash = fullPath.rfind(L'\\');
std::wstring exeName = (lastSlash != std::wstring::npos) ?
fullPath.substr(lastSlash + 1) : fullPath;
auto handles = getProcessHandlesByName(exeName, PROCESS_QUERY_LIMITED_INFORMATION);
bool foundValidHandle = false;
for (auto& handle : handles)
{
// Try to use the handle
DWORD exitCode;
if (GetExitCodeProcess(handle.get(), &exitCode))
{
foundValidHandle = true;
}
}
Assert::IsTrue(foundValidHandle || handles.empty());
}
};
}

View File

@@ -1,153 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <process_path.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ProcessPathTests)
{
public:
// get_process_path (by PID) tests
TEST_METHOD(GetProcessPath_CurrentProcess_ReturnsPath)
{
DWORD pid = GetCurrentProcessId();
auto path = get_process_path(pid);
Assert::IsFalse(path.empty());
Assert::IsTrue(path.find(L".exe") != std::wstring::npos ||
path.find(L".dll") != std::wstring::npos);
}
TEST_METHOD(GetProcessPath_InvalidPid_ReturnsEmpty)
{
DWORD invalidPid = 0xFFFFFFFF;
auto path = get_process_path(invalidPid);
// Should return empty for invalid PID
Assert::IsTrue(path.empty());
}
TEST_METHOD(GetProcessPath_ZeroPid_ReturnsEmpty)
{
auto path = get_process_path(static_cast<DWORD>(0));
// PID 0 is the System Idle Process, might return empty or a path
// Just verify it doesn't crash
Assert::IsTrue(true);
}
TEST_METHOD(GetProcessPath_SystemPid_DoesNotCrash)
{
// PID 4 is typically the System process
auto path = get_process_path(static_cast<DWORD>(4));
// May return empty due to access rights, but shouldn't crash
Assert::IsTrue(true);
}
// get_module_filename tests
TEST_METHOD(GetModuleFilename_NullModule_ReturnsExePath)
{
auto path = get_module_filename(nullptr);
Assert::IsFalse(path.empty());
Assert::IsTrue(path.find(L".exe") != std::wstring::npos ||
path.find(L".dll") != std::wstring::npos);
}
TEST_METHOD(GetModuleFilename_Kernel32_ReturnsPath)
{
HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
Assert::IsNotNull(kernel32);
auto path = get_module_filename(kernel32);
Assert::IsFalse(path.empty());
// Should contain kernel32 (case insensitive check)
std::wstring lowerPath = path;
std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(), ::towlower);
Assert::IsTrue(lowerPath.find(L"kernel32") != std::wstring::npos);
}
TEST_METHOD(GetModuleFilename_InvalidModule_ReturnsEmpty)
{
auto path = get_module_filename(reinterpret_cast<HMODULE>(0x12345678));
// Invalid module should return empty
Assert::IsTrue(path.empty());
}
// get_module_folderpath tests
TEST_METHOD(GetModuleFolderpath_NullModule_ReturnsFolder)
{
auto folder = get_module_folderpath(nullptr, true);
Assert::IsFalse(folder.empty());
// Should not end with .exe when removeFilename is true
Assert::IsTrue(folder.find(L".exe") == std::wstring::npos);
// Should end with backslash or be a valid folder path
Assert::IsTrue(folder.back() == L'\\' || folder.find(L"\\") != std::wstring::npos);
}
TEST_METHOD(GetModuleFolderpath_KeepFilename_ReturnsFullPath)
{
auto fullPath = get_module_folderpath(nullptr, false);
Assert::IsFalse(fullPath.empty());
// Should contain .exe or .dll when not removing filename
Assert::IsTrue(fullPath.find(L".exe") != std::wstring::npos ||
fullPath.find(L".dll") != std::wstring::npos);
}
TEST_METHOD(GetModuleFolderpath_Kernel32_ReturnsSystem32)
{
HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
Assert::IsNotNull(kernel32);
auto folder = get_module_folderpath(kernel32, true);
Assert::IsFalse(folder.empty());
// Should be in system32 folder
std::wstring lowerPath = folder;
std::transform(lowerPath.begin(), lowerPath.end(), lowerPath.begin(), ::towlower);
Assert::IsTrue(lowerPath.find(L"system32") != std::wstring::npos ||
lowerPath.find(L"syswow64") != std::wstring::npos);
}
// get_process_path (by HWND) tests
TEST_METHOD(GetProcessPath_DesktopWindow_ReturnsPath)
{
HWND desktop = GetDesktopWindow();
Assert::IsNotNull(desktop);
auto path = get_process_path(desktop);
// Desktop window should return a path
// (could be explorer.exe or empty depending on system)
Assert::IsTrue(true); // Just verify it doesn't crash
}
TEST_METHOD(GetProcessPath_InvalidHwnd_ReturnsEmpty)
{
auto path = get_process_path(reinterpret_cast<HWND>(0x12345678));
Assert::IsTrue(path.empty());
}
TEST_METHOD(GetProcessPath_NullHwnd_ReturnsEmpty)
{
auto path = get_process_path(static_cast<HWND>(nullptr));
Assert::IsTrue(path.empty());
}
// Consistency tests
TEST_METHOD(Consistency_ModuleFilenameAndFolderpath_AreRelated)
{
auto fullPath = get_module_filename(nullptr);
auto folder = get_module_folderpath(nullptr, true);
Assert::IsFalse(fullPath.empty());
Assert::IsFalse(folder.empty());
// Full path should start with the folder
Assert::IsTrue(fullPath.find(folder) == 0 || folder.find(fullPath.substr(0, folder.length())) == 0);
}
};
}

View File

@@ -1,127 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <ProcessWaiter.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace ProcessWaiter;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ProcessWaiterTests)
{
public:
TEST_METHOD(OnProcessTerminate_InvalidPid_DoesNotCrash)
{
std::atomic<bool> called{ false };
// Use a very unlikely PID (negative value as string will fail conversion)
OnProcessTerminate(L"invalid", [&called](DWORD) {
called = true;
});
// Wait briefly
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Should not crash, callback may or may not be called depending on implementation
Assert::IsTrue(true);
}
TEST_METHOD(OnProcessTerminate_NonExistentPid_DoesNotCrash)
{
std::atomic<bool> called{ false };
// Use a PID that likely doesn't exist
OnProcessTerminate(L"999999999", [&called](DWORD) {
called = true;
});
// Wait briefly
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Should not crash
Assert::IsTrue(true);
}
TEST_METHOD(OnProcessTerminate_ZeroPid_DoesNotCrash)
{
std::atomic<bool> called{ false };
OnProcessTerminate(L"0", [&called](DWORD) {
called = true;
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Assert::IsTrue(true);
}
TEST_METHOD(OnProcessTerminate_CurrentProcessPid_DoesNotTerminate)
{
std::atomic<bool> called{ false };
// Use current process PID - it shouldn't terminate during test
std::wstring pid = std::to_wstring(GetCurrentProcessId());
OnProcessTerminate(pid, [&called](DWORD) {
called = true;
});
// Wait briefly - current process should not terminate
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// Callback should not have been called since process is still running
Assert::IsFalse(called);
}
TEST_METHOD(OnProcessTerminate_EmptyCallback_DoesNotCrash)
{
// Test with an empty function
OnProcessTerminate(L"999999999", std::function<void(DWORD)>());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Assert::IsTrue(true);
}
TEST_METHOD(OnProcessTerminate_MultipleCallsForSamePid_DoesNotCrash)
{
std::atomic<int> counter{ 0 };
std::wstring pid = std::to_wstring(GetCurrentProcessId());
// Multiple waits on same (running) process
for (int i = 0; i < 5; ++i)
{
OnProcessTerminate(pid, [&counter](DWORD) {
counter++;
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// None should have been called since process is running
Assert::AreEqual(0, counter.load());
}
TEST_METHOD(OnProcessTerminate_NegativeNumberString_DoesNotCrash)
{
std::atomic<bool> called{ false };
OnProcessTerminate(L"-1", [&called](DWORD) {
called = true;
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Assert::IsTrue(true);
}
TEST_METHOD(OnProcessTerminate_LargeNumber_DoesNotCrash)
{
std::atomic<bool> called{ false };
OnProcessTerminate(L"18446744073709551615", [&called](DWORD) {
called = true;
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Assert::IsTrue(true);
}
};
}

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