Compare commits
83 Commits
shortcutgu
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da880280be | ||
|
|
8d6a8e8a9e | ||
|
|
8dc4598786 | ||
|
|
fa8fbb60a1 | ||
|
|
ab4947579b | ||
|
|
d221f84d8f | ||
|
|
14152672c5 | ||
|
|
5688441127 | ||
|
|
d9216f0fc7 | ||
|
|
8c9bd8ba64 | ||
|
|
aaee51b90e | ||
|
|
ff3c1f9252 | ||
|
|
99ee955dfb | ||
|
|
4d01062f76 | ||
|
|
d7d1e543ae | ||
|
|
272b725ff0 | ||
|
|
7884f4217a | ||
|
|
92014c81b9 | ||
|
|
d57096af20 | ||
|
|
f136a4fe04 | ||
|
|
87fa204fd6 | ||
|
|
6ebfac8eab | ||
|
|
582f3eb5c3 | ||
|
|
4f14070a1a | ||
|
|
76eb6eaac5 | ||
|
|
97f2868481 | ||
|
|
9f82e018f6 | ||
|
|
f3d3abc552 | ||
|
|
0a64561ed5 | ||
|
|
0c2bae21d4 | ||
|
|
c914bdf509 | ||
|
|
3851e06f45 | ||
|
|
7f19817182 | ||
|
|
a33fd3c474 | ||
|
|
b712fa4d85 | ||
|
|
b66b044210 | ||
|
|
18919eaa40 | ||
|
|
e0854fbaf3 | ||
|
|
b27ca5a9bf | ||
|
|
ba20da1611 | ||
|
|
63701c9b91 | ||
|
|
35f2ed839e | ||
|
|
cbb5e985c9 | ||
|
|
e20b5b9c51 | ||
|
|
017e3278d9 | ||
|
|
e834031c7f | ||
|
|
981c1cefba | ||
|
|
c6a9ad2ad0 | ||
|
|
a67fc2d9b7 | ||
|
|
c78f6e52a0 | ||
|
|
9a55209d13 | ||
|
|
0cb6fe250b | ||
|
|
c0cb9417ad | ||
|
|
cd5027fa1a | ||
|
|
7da62cdb0a | ||
|
|
c46083dd8d | ||
|
|
65112a7b05 | ||
|
|
109c63ba33 | ||
|
|
6be6509c46 | ||
|
|
8a7933c0b2 | ||
|
|
6f5ea3bb95 | ||
|
|
fe985e7eea | ||
|
|
b3bf154fa5 | ||
|
|
ab553f9930 | ||
|
|
85b9191b7c | ||
|
|
cb174210cb | ||
|
|
f175a9c96a | ||
|
|
273d735a8b | ||
|
|
bcf0b685ac | ||
|
|
6a5e320749 | ||
|
|
83285e929a | ||
|
|
26108ff04b | ||
|
|
fed9e81fdc | ||
|
|
f998c38ac8 | ||
|
|
11083c9fb2 | ||
|
|
809601a33c | ||
|
|
2c9481e69a | ||
|
|
1d0917d06f | ||
|
|
4edfcee87e | ||
|
|
33cf6995fc | ||
|
|
81e251c2de | ||
|
|
f02b66c88d | ||
|
|
386fdcb1e9 |
8
.github/actions/spell-check/allow/code.txt
vendored
@@ -365,7 +365,10 @@ FILESYSONLY
|
||||
URLIS
|
||||
WAITTIMEOUT
|
||||
DEFAULTTONEAREST
|
||||
|
||||
DWRITE
|
||||
LWIN
|
||||
VCENTER
|
||||
VREDRAW
|
||||
|
||||
# COM/WinRT interface prefixes and type fragments
|
||||
BAlt
|
||||
@@ -429,3 +432,6 @@ SHELLEXPERIENCEHOST
|
||||
SHELLHOST
|
||||
STARTMENUEXPERIENCEHOST
|
||||
WIDGETBOARD
|
||||
|
||||
# URIs
|
||||
actioncenter
|
||||
|
||||
212
.github/actions/spell-check/allow/zoomit.txt
vendored
@@ -1,23 +1,51 @@
|
||||
accelscroll
|
||||
acq
|
||||
ADDTO
|
||||
ADDTOOL
|
||||
adr
|
||||
Adr
|
||||
ALWAYSTIP
|
||||
APPLYTOSUBMENUS
|
||||
ARCHMASK
|
||||
archs
|
||||
AUDCLNT
|
||||
autocorr
|
||||
avx
|
||||
axisdefer
|
||||
axisflip
|
||||
axisstart
|
||||
backlight
|
||||
BEOS
|
||||
bfi
|
||||
BFIN
|
||||
bfly
|
||||
BGRX
|
||||
bitmaps
|
||||
bitrev
|
||||
blits
|
||||
Borgerding
|
||||
Borland
|
||||
breakc
|
||||
BREAKSCR
|
||||
BUFFERFLAGS
|
||||
bugzilla
|
||||
Cands
|
||||
capturepath
|
||||
cbs
|
||||
centiseconds
|
||||
cexp
|
||||
cfx
|
||||
cfy
|
||||
cgem
|
||||
cifx
|
||||
cify
|
||||
CLASSW
|
||||
coeffs
|
||||
colblocks
|
||||
constantbuffer
|
||||
coprime
|
||||
cpuid
|
||||
cpx
|
||||
CREATEDIBSECTION
|
||||
CREATESTRUCTW
|
||||
crossfades
|
||||
@@ -28,109 +56,216 @@ CTLCOLORDLG
|
||||
CTLCOLOREDIT
|
||||
CTLCOLORLISTBOX
|
||||
CTrim
|
||||
CVTEPI
|
||||
DBuffer
|
||||
dcl
|
||||
dct
|
||||
ddx
|
||||
ddy
|
||||
Deinterleave
|
||||
denoise
|
||||
denoised
|
||||
DEVSOURCE
|
||||
DFCS
|
||||
DIVSCALAR
|
||||
DJGPP
|
||||
dlg
|
||||
dlu
|
||||
dnn
|
||||
DONTCARE
|
||||
downsample
|
||||
DRAWITEM
|
||||
DRAWITEMSTRUCT
|
||||
droppedband
|
||||
Droppedband
|
||||
DSPs
|
||||
dsum
|
||||
dupburst
|
||||
dupsegments
|
||||
DWLP
|
||||
eband
|
||||
ebx
|
||||
ECX
|
||||
EDITCONTROL
|
||||
EDSP
|
||||
emmintrin
|
||||
EMX
|
||||
ENABLEHOOK
|
||||
endloop
|
||||
ENDOFSTREAM
|
||||
ener
|
||||
enh
|
||||
ettings
|
||||
expectedlock
|
||||
expf
|
||||
fabs
|
||||
fabsf
|
||||
facbuf
|
||||
fastscroll
|
||||
FDE
|
||||
ffast
|
||||
FIXDIV
|
||||
floorf
|
||||
fmadd
|
||||
fout
|
||||
fstride
|
||||
fxc
|
||||
GETCHANNELRECT
|
||||
GETCHECK
|
||||
GETCOUNT
|
||||
GETDISPINFO
|
||||
GETSCREENSAVEACTIVE
|
||||
GETSCREENSAVETIMEOUT
|
||||
GETTHUMBRECT
|
||||
GIFs
|
||||
glu
|
||||
groupshared
|
||||
gru
|
||||
hcfdark
|
||||
hcfwhitespace
|
||||
hlsl
|
||||
Hsieh
|
||||
hstride
|
||||
HTBOTTOMRIGHT
|
||||
HTHEME
|
||||
htol
|
||||
ICONINFORMATION
|
||||
ICONWARNING
|
||||
idct
|
||||
IDIn
|
||||
IDISHWND
|
||||
ifft
|
||||
igc
|
||||
ilog
|
||||
imad
|
||||
imax
|
||||
imin
|
||||
immintrin
|
||||
Inj
|
||||
interp
|
||||
inttypes
|
||||
ishl
|
||||
itof
|
||||
jumprecover
|
||||
kfft
|
||||
kheight
|
||||
kissfft
|
||||
KSDATAFORMAT
|
||||
ksize
|
||||
ktime
|
||||
lastg
|
||||
latestcapture
|
||||
ldx
|
||||
LEFTNOWORDWRAP
|
||||
legitjumps
|
||||
lenmem
|
||||
letterbox
|
||||
lld
|
||||
lldx
|
||||
llu
|
||||
llums
|
||||
logfont
|
||||
lookback
|
||||
lpc
|
||||
lpcnet
|
||||
LPNMHDR
|
||||
LPNMTTDISPINFO
|
||||
lround
|
||||
lte
|
||||
luma
|
||||
Luma
|
||||
maj
|
||||
manualdrop
|
||||
maskcache
|
||||
maxabs
|
||||
maxcorr
|
||||
MAXFACTORS
|
||||
maxperiod
|
||||
maxstep
|
||||
memalign
|
||||
memid
|
||||
memneeded
|
||||
MENUINFO
|
||||
MFSTARTUP
|
||||
mfxhw
|
||||
mic
|
||||
middledrop
|
||||
minperiod
|
||||
MIPSr
|
||||
MJPEG
|
||||
MMRESULT
|
||||
momentumreversal
|
||||
movc
|
||||
mrate
|
||||
mrt
|
||||
MULBYSCALAR
|
||||
MULC
|
||||
MWERKS
|
||||
mycfg
|
||||
narrowstrip
|
||||
nbak
|
||||
nbytes
|
||||
ncapture
|
||||
nchw
|
||||
ncm
|
||||
nduplicates
|
||||
nfft
|
||||
NHWC
|
||||
niterations
|
||||
nmonitor
|
||||
nnet
|
||||
NONCLIENTMETRICS
|
||||
NONOTIFY
|
||||
nonvle
|
||||
normf
|
||||
nredraw
|
||||
nstop
|
||||
nsubpixel
|
||||
ntorn
|
||||
numthreads
|
||||
nvw
|
||||
Octasic
|
||||
osc
|
||||
OSCE
|
||||
ovflw
|
||||
OWNERDRAW
|
||||
PBGRA
|
||||
periodictrap
|
||||
pillarbox
|
||||
pfdc
|
||||
pillarbox
|
||||
playhead
|
||||
pnmh
|
||||
pointerreuse
|
||||
PPW
|
||||
prereq
|
||||
PSHR
|
||||
pstdint
|
||||
PSWA
|
||||
pwfx
|
||||
QCONST
|
||||
qpc
|
||||
Qpc
|
||||
quantums
|
||||
qweight
|
||||
RCSEGMODEL
|
||||
RCZOOMITSCR
|
||||
readback
|
||||
READERF
|
||||
realcapture
|
||||
REFKNOWNFOLDERID
|
||||
relu
|
||||
reposted
|
||||
RETURNCMD
|
||||
rnn
|
||||
rnnoise
|
||||
rotateleft
|
||||
rsqrt
|
||||
rtcd
|
||||
RTEXT
|
||||
RTH
|
||||
rtvs
|
||||
SCALEIN
|
||||
SCALEOUT
|
||||
SCREENSAVE
|
||||
SCRNSAVE
|
||||
SCRNSAVECONFIGURE
|
||||
@@ -138,43 +273,80 @@ scrnsavw
|
||||
Scrnsavw
|
||||
scrollramp
|
||||
SCROLLSIZEGRIP
|
||||
selfie
|
||||
selftest
|
||||
SETBARCOLOR
|
||||
SETBKCOLOR
|
||||
SETDEFID
|
||||
SETRECT
|
||||
SETSCREENSAVETIMEOUT
|
||||
SETTIPSIDE
|
||||
sgem
|
||||
sgemv
|
||||
sgv
|
||||
SHAREMODE
|
||||
SHAREVIOLATION
|
||||
shortlist
|
||||
simde
|
||||
siv
|
||||
slowthenfast
|
||||
smallstart
|
||||
SNIPOCR
|
||||
softmax
|
||||
sqrtf
|
||||
SROUND
|
||||
srvs
|
||||
ssi
|
||||
startuprecovery
|
||||
stdint
|
||||
stf
|
||||
stopafter
|
||||
STREAMFLAGS
|
||||
SUBFROM
|
||||
subias
|
||||
submix
|
||||
sxx
|
||||
sxy
|
||||
symbian
|
||||
synthesising
|
||||
syy
|
||||
tallportal
|
||||
TBTS
|
||||
tci
|
||||
tcsicmp
|
||||
TEXTCALLBACK
|
||||
TEXTMETRIC
|
||||
tgsm
|
||||
THIRDPARTY
|
||||
tinystep
|
||||
tme
|
||||
toolbars
|
||||
TOOLINFO
|
||||
TRACKMOUSEEVENT
|
||||
TRIANGLELIST
|
||||
TTM
|
||||
TTN
|
||||
TWID
|
||||
UADD
|
||||
uav
|
||||
uavs
|
||||
uge
|
||||
Unadvise
|
||||
upscaled
|
||||
upscales
|
||||
USUB
|
||||
utof
|
||||
vad
|
||||
vaddq
|
||||
vaddvq
|
||||
valgrind
|
||||
Valin
|
||||
vandq
|
||||
vblank
|
||||
vcgeq
|
||||
vdup
|
||||
vectorizer
|
||||
VERTID
|
||||
VIDCAP
|
||||
vld
|
||||
vle
|
||||
@@ -184,6 +356,7 @@ vminq
|
||||
vmlal
|
||||
vmull
|
||||
vqaddq
|
||||
VSHR
|
||||
vshrn
|
||||
vsntprintf
|
||||
vsnwprintf
|
||||
@@ -194,7 +367,9 @@ WAVEFORMATEXTENSIBLE
|
||||
webcam
|
||||
Webcam
|
||||
webcams
|
||||
Wextra
|
||||
wfopen
|
||||
WGC
|
||||
wideportal
|
||||
wil
|
||||
WMU
|
||||
@@ -202,11 +377,46 @@ wrapjump
|
||||
wtol
|
||||
WTSSESSION
|
||||
WTSUn
|
||||
wxyz
|
||||
xchg
|
||||
xcorr
|
||||
XEnd
|
||||
Xfl
|
||||
Xiang
|
||||
Xiph
|
||||
xmmintrin
|
||||
xptr
|
||||
xshift
|
||||
XStart
|
||||
XStep
|
||||
xxxy
|
||||
xxyx
|
||||
xxyz
|
||||
xyw
|
||||
xywx
|
||||
xyxx
|
||||
xyxz
|
||||
xyzw
|
||||
xyzx
|
||||
xzwx
|
||||
xzxx
|
||||
Yfl
|
||||
YInternal
|
||||
yshift
|
||||
YUV
|
||||
yyyx
|
||||
yyzw
|
||||
yzw
|
||||
yzwy
|
||||
yzyy
|
||||
Zhou
|
||||
Zhu
|
||||
ZMBS
|
||||
zncc
|
||||
Zncc
|
||||
ZNCC
|
||||
zrh
|
||||
zwzz
|
||||
zyzw
|
||||
zzwz
|
||||
zzzw
|
||||
|
||||
8
.github/actions/spell-check/excludes.txt
vendored
@@ -105,13 +105,13 @@
|
||||
^src/common/ManagedCommon/ColorFormatHelper\.cs$
|
||||
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
|
||||
^src/common/sysinternals/Eula/
|
||||
^doc/devdocs/modules/cmdpal/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^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\.Common\.UnitTests/.*\.TestData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/Text/.*\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
|
||||
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
||||
@@ -135,6 +135,8 @@
|
||||
^src/modules/previewpane/SvgPreviewHandler/SvgHTMLPreviewGenerator\.cs$
|
||||
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag\.txt$
|
||||
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
|
||||
^src/modules/ZoomIt/ZoomIt/rnnoise/
|
||||
^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$
|
||||
^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$
|
||||
^src/Monaco/
|
||||
^tools/project_template/ModuleTemplate/resource\.h$
|
||||
|
||||
167
.github/actions/spell-check/expect.txt
vendored
@@ -1,7 +1,6 @@
|
||||
AAAAs
|
||||
abcdefghjkmnpqrstuvxyz
|
||||
abgr
|
||||
ABlocked
|
||||
ABORTIFHUNG
|
||||
ABOUTBOX
|
||||
Abug
|
||||
@@ -11,18 +10,13 @@ ACCESSDENIED
|
||||
ACCESSTOKEN
|
||||
acfs
|
||||
ACIE
|
||||
AClient
|
||||
AColumn
|
||||
ACR
|
||||
acrt
|
||||
ACTIVATEAPP
|
||||
ACTIVATEOPTIONS
|
||||
activationaction
|
||||
adaptivecards
|
||||
ADate
|
||||
ADDSTRING
|
||||
ADDUNDORECORD
|
||||
ADifferent
|
||||
ADMINS
|
||||
adml
|
||||
admx
|
||||
@@ -36,10 +30,8 @@ AFX
|
||||
agentskills
|
||||
AGGREGATABLE
|
||||
AHK
|
||||
AHybrid
|
||||
AIUI
|
||||
akv
|
||||
ALarger
|
||||
ALIGNRIGHT
|
||||
ALLAPPS
|
||||
ALLCHILDREN
|
||||
@@ -51,17 +43,13 @@ ALLOWUNDO
|
||||
ALLVIEW
|
||||
ALPHATYPE
|
||||
altkey
|
||||
AModifier
|
||||
amr
|
||||
ANDSCANS
|
||||
animatedvisuals
|
||||
Animnate
|
||||
ANull
|
||||
AOC
|
||||
aocfnapldcnfbofgmbbllojgocaelgdd
|
||||
AOklab
|
||||
aot
|
||||
APeriod
|
||||
apicontract
|
||||
apidl
|
||||
APIENTRY
|
||||
@@ -75,6 +63,7 @@ APPEXECLINK
|
||||
appext
|
||||
apphost
|
||||
APPLICATIONFRAMEHOST
|
||||
Applocal
|
||||
appmanifest
|
||||
APPMODEL
|
||||
APPNAME
|
||||
@@ -87,9 +76,7 @@ appxpackage
|
||||
APSTUDIO
|
||||
AQS
|
||||
Aquadrant
|
||||
ARandom
|
||||
ARCHITEW
|
||||
ARemapped
|
||||
ARPINSTALLLOCATION
|
||||
ARPPRODUCTICON
|
||||
ARRAYSIZE
|
||||
@@ -100,11 +87,9 @@ ARTIFACTSTAGINGDIRECTORY
|
||||
asf
|
||||
Ashcraft
|
||||
AShortcut
|
||||
ASingle
|
||||
ASSOCCHANGED
|
||||
ASSOCF
|
||||
ASSOCSTR
|
||||
ASUS
|
||||
ASYNCWINDOWPLACEMENT
|
||||
ASYNCWINDOWPOS
|
||||
atl
|
||||
@@ -128,14 +113,12 @@ azman
|
||||
azureaiinference
|
||||
azureinference
|
||||
azureopenai
|
||||
Backlight
|
||||
backticks
|
||||
Badflags
|
||||
Badmode
|
||||
Badparam
|
||||
bbwe
|
||||
BCIE
|
||||
bck
|
||||
BESTEFFORT
|
||||
bezelled
|
||||
bhid
|
||||
@@ -162,9 +145,7 @@ bluelightreductionstate
|
||||
BLURBEHIND
|
||||
BLURREGION
|
||||
bmi
|
||||
BNumber
|
||||
BODGY
|
||||
BOklab
|
||||
BOOTSTRAPPERINSTALLFOLDER
|
||||
Bootstrappers
|
||||
BOTTOMALIGN
|
||||
@@ -184,11 +165,10 @@ bugreport
|
||||
bugreportfile
|
||||
BUILDARCH
|
||||
BUILDNUMBER
|
||||
buildsystems
|
||||
buildtransitive
|
||||
builttoroam
|
||||
BUNDLEINFO
|
||||
BVal
|
||||
BValue
|
||||
byapp
|
||||
BYCOMMAND
|
||||
BYPOSITION
|
||||
@@ -204,18 +184,13 @@ CAPTURECHANGED
|
||||
CARETBLINKING
|
||||
carlos
|
||||
Carlseibert
|
||||
CAtl
|
||||
caub
|
||||
CBN
|
||||
cch
|
||||
CCHDEVICENAME
|
||||
CCHFORMNAME
|
||||
CCom
|
||||
CContext
|
||||
CDeclaration
|
||||
CDPX
|
||||
Cds
|
||||
CElems
|
||||
CENTERALIGN
|
||||
cer
|
||||
certlm
|
||||
@@ -229,12 +204,10 @@ CHILDACTIVATE
|
||||
CHILDWINDOW
|
||||
CHOOSEFONT
|
||||
chu
|
||||
Chunghwa
|
||||
CIBUILD
|
||||
cidl
|
||||
CIELCh
|
||||
cim
|
||||
CImage
|
||||
cla
|
||||
CLASSDC
|
||||
classguid
|
||||
@@ -263,7 +236,6 @@ CMIC
|
||||
CMINVOKECOMMANDINFO
|
||||
CMINVOKECOMMANDINFOEX
|
||||
CMN
|
||||
CMock
|
||||
CMONITORS
|
||||
cmph
|
||||
CNF
|
||||
@@ -320,7 +292,6 @@ Cowait
|
||||
cpcontrols
|
||||
cph
|
||||
cplusplus
|
||||
CPower
|
||||
cpptools
|
||||
cppvsdbg
|
||||
cppwinrt
|
||||
@@ -339,14 +310,9 @@ CROPTOSQUARE
|
||||
Crossdevice
|
||||
crt
|
||||
csdevkit
|
||||
CSearch
|
||||
CSettings
|
||||
cso
|
||||
CSOT
|
||||
CSRW
|
||||
CStyle
|
||||
cswin
|
||||
CTest
|
||||
CTEXT
|
||||
CTLCOLORSTATIC
|
||||
CURRENTDIR
|
||||
@@ -357,9 +323,7 @@ cursorwrap
|
||||
customaction
|
||||
CUSTOMACTIONTEST
|
||||
CUSTOMFORMATPLACEHOLDER
|
||||
CVal
|
||||
cvd
|
||||
CVirtual
|
||||
CWMO
|
||||
CXSCREEN
|
||||
CXSMICON
|
||||
@@ -372,7 +336,6 @@ Dac
|
||||
dacl
|
||||
DAffine
|
||||
DAFFINETRANSFORM
|
||||
DArchitectures
|
||||
datareader
|
||||
Datasheet
|
||||
datatracker
|
||||
@@ -388,7 +351,6 @@ DBT
|
||||
DCapabilities
|
||||
DCBA
|
||||
DCOM
|
||||
DComposition
|
||||
DCR
|
||||
ddc
|
||||
DDEIf
|
||||
@@ -405,7 +367,6 @@ DEFAULTICON
|
||||
defaultlib
|
||||
DEFAULTONLY
|
||||
DEFAULTSIZE
|
||||
defaulttonearest
|
||||
DEFAULTTONULL
|
||||
DEFAULTTOPRIMARY
|
||||
DEFERERASE
|
||||
@@ -438,6 +399,7 @@ DEVMODE
|
||||
DEVMODEW
|
||||
DEVNODES
|
||||
devpal
|
||||
devpackages
|
||||
DEVTYP
|
||||
dfx
|
||||
DIALOGEX
|
||||
@@ -454,7 +416,6 @@ DISPLAYFREQUENCY
|
||||
displayname
|
||||
DISPLAYORIENTATION
|
||||
DISPLAYPORT
|
||||
diu
|
||||
divyan
|
||||
DLGFRAME
|
||||
dlgmodalframe
|
||||
@@ -482,9 +443,9 @@ drawingcolor
|
||||
dreamsofameaningfullife
|
||||
drivedetectionwarning
|
||||
DROPFILES
|
||||
DSPDLOG
|
||||
DSTINVERT
|
||||
DString
|
||||
DSVG
|
||||
dto
|
||||
DUMMYUNIONNAME
|
||||
dumpbin
|
||||
@@ -510,12 +471,9 @@ DWMWINDOWATTRIBUTE
|
||||
DWMWINDOWMAXIMIZEDCHANGE
|
||||
DWORDLONG
|
||||
dworigin
|
||||
dwrite
|
||||
DWRITE
|
||||
dxgi
|
||||
Dxva
|
||||
eab
|
||||
EAccess
|
||||
easeofaccess
|
||||
ecount
|
||||
edid
|
||||
@@ -523,8 +481,6 @@ EDITKEYBOARD
|
||||
EDITSHORTCUTS
|
||||
EDITTEXT
|
||||
eep
|
||||
EFile
|
||||
EInvalid
|
||||
eku
|
||||
emojis
|
||||
ENABLEDELAYEDEXPANSION
|
||||
@@ -534,28 +490,24 @@ ENABLETEMPLATE
|
||||
encodedlaunch
|
||||
encryptor
|
||||
ENDSESSION
|
||||
ENot
|
||||
ENSUREVISIBLE
|
||||
ENTERSIZEMOVE
|
||||
ENTRYW
|
||||
ENU
|
||||
environmentvariables
|
||||
EPO
|
||||
EProvider
|
||||
epu
|
||||
ERASEBKGND
|
||||
EREOF
|
||||
EResize
|
||||
ERRORIMAGE
|
||||
ERRORTITLE
|
||||
ESettings
|
||||
esrp
|
||||
etd
|
||||
ETDT
|
||||
etl
|
||||
etw
|
||||
eula
|
||||
eurochange
|
||||
eventvwr
|
||||
evt
|
||||
EWXFORCE
|
||||
@@ -591,7 +543,6 @@ FANCYZONESEDITOR
|
||||
FARPROC
|
||||
fdw
|
||||
fdx
|
||||
FErase
|
||||
fesf
|
||||
FFFF
|
||||
fffffffzzz
|
||||
@@ -618,16 +569,13 @@ FILESYSPATH
|
||||
Filetime
|
||||
FILEVERSION
|
||||
FILTERMODE
|
||||
FInc
|
||||
findfast
|
||||
findmymouse
|
||||
FIXEDFILEINFO
|
||||
FIXEDSYS
|
||||
flac
|
||||
flyouts
|
||||
FMask
|
||||
fmtid
|
||||
FNumber
|
||||
FOF
|
||||
FOFX
|
||||
FOLDERID
|
||||
@@ -640,7 +588,6 @@ formatetc
|
||||
FORPARSING
|
||||
foundrylocal
|
||||
framechanged
|
||||
FRestore
|
||||
frm
|
||||
FROMTOUCH
|
||||
fsanitize
|
||||
@@ -680,7 +627,6 @@ gfx
|
||||
GHND
|
||||
gitmodules
|
||||
GMEM
|
||||
GNumber
|
||||
googleai
|
||||
googlegemini
|
||||
Gotchas
|
||||
@@ -701,12 +647,10 @@ GSM
|
||||
gtm
|
||||
guiddata
|
||||
GUITHREADINFO
|
||||
GValue
|
||||
gwl
|
||||
GWLP
|
||||
GWLSTYLE
|
||||
hangeul
|
||||
Hann
|
||||
Hantai
|
||||
Hanzi
|
||||
Hardlines
|
||||
@@ -733,13 +677,13 @@ HDEVNOTIFY
|
||||
hdr
|
||||
HDROP
|
||||
hdwwiz
|
||||
Headered
|
||||
Helpline
|
||||
helptext
|
||||
hgdiobj
|
||||
HGFE
|
||||
hglobal
|
||||
hhk
|
||||
HHmmssfff
|
||||
hhx
|
||||
Hiber
|
||||
Hiberboot
|
||||
@@ -779,7 +723,6 @@ HORZSIZE
|
||||
Hostbackdropbrush
|
||||
hostfxr
|
||||
hostsfileeditor
|
||||
Hostx
|
||||
hotfixes
|
||||
hotkeycontrol
|
||||
HOTKEYF
|
||||
@@ -787,7 +730,6 @@ hotkeys
|
||||
hotlight
|
||||
hotspot
|
||||
HPAINTBUFFER
|
||||
HPhysical
|
||||
HPS
|
||||
HRAWINPUT
|
||||
HREDRAW
|
||||
@@ -798,15 +740,11 @@ HROW
|
||||
hsb
|
||||
HSCROLL
|
||||
hsi
|
||||
HSpeed
|
||||
HSync
|
||||
HTCLIENT
|
||||
hthumbnail
|
||||
HTOUCHINPUT
|
||||
HTTRANSPARENT
|
||||
hutchinsoniana
|
||||
HVal
|
||||
HValue
|
||||
Hvci
|
||||
hwb
|
||||
HWHEEL
|
||||
@@ -817,7 +755,6 @@ HWNDLAST
|
||||
HWNDNEXT
|
||||
HWNDPARENT
|
||||
HWNDPREV
|
||||
HWP
|
||||
hyjiacan
|
||||
IAI
|
||||
icf
|
||||
@@ -899,7 +836,6 @@ INVALIDARG
|
||||
invalidoperatioexception
|
||||
invokecommand
|
||||
ipcmanager
|
||||
IPREVIEW
|
||||
ipreviewhandlervisualssetfont
|
||||
IPTC
|
||||
irow
|
||||
@@ -915,10 +851,8 @@ issuecomment
|
||||
istep
|
||||
Italicise
|
||||
ith
|
||||
ITHUMBNAIL
|
||||
IUI
|
||||
IUWP
|
||||
IVO
|
||||
IWIC
|
||||
jeli
|
||||
jfif
|
||||
@@ -936,7 +870,6 @@ jsonval
|
||||
jxr
|
||||
Kantai
|
||||
KBSC
|
||||
kdc
|
||||
keybd
|
||||
KEYBDDATA
|
||||
KEYBDINPUT
|
||||
@@ -954,6 +887,7 @@ keynum
|
||||
keyremaps
|
||||
keyring
|
||||
keyvault
|
||||
kfull
|
||||
KILLFOCUS
|
||||
killrunner
|
||||
kmph
|
||||
@@ -980,7 +914,6 @@ LEFTTEXT
|
||||
Lenovo
|
||||
LError
|
||||
LEVELID
|
||||
LExit
|
||||
LFU
|
||||
LGD
|
||||
lhwnd
|
||||
@@ -1020,7 +953,6 @@ lowlevel
|
||||
LOWORD
|
||||
lparam
|
||||
LPBITMAPINFOHEADER
|
||||
LPCFHOOKPROC
|
||||
lpch
|
||||
LPCITEMIDLIST
|
||||
LPCLSID
|
||||
@@ -1039,7 +971,6 @@ LPMONITORINFO
|
||||
LPOSVERSIONINFOEXW
|
||||
LPQUERY
|
||||
lprc
|
||||
LPrivate
|
||||
LPSAFEARRAY
|
||||
lpstr
|
||||
lpsz
|
||||
@@ -1053,7 +984,6 @@ LPW
|
||||
lpwcx
|
||||
lpwndpl
|
||||
lquadrant
|
||||
LReader
|
||||
LRESULT
|
||||
LSTATUS
|
||||
lstrcmp
|
||||
@@ -1061,15 +991,12 @@ lstrcmpi
|
||||
lstrcpyn
|
||||
lstrlen
|
||||
LTEXT
|
||||
LTM
|
||||
LTRREADING
|
||||
luid
|
||||
LUMA
|
||||
lusrmgr
|
||||
LVal
|
||||
LVDS
|
||||
LWA
|
||||
lwin
|
||||
LWIN
|
||||
LZero
|
||||
MAGTRANSFORM
|
||||
makeappx
|
||||
@@ -1138,7 +1065,6 @@ mkdn
|
||||
mlcfg
|
||||
mmc
|
||||
mmcexe
|
||||
MMdd
|
||||
mmi
|
||||
mmsys
|
||||
mobileredirect
|
||||
@@ -1164,7 +1090,6 @@ mouseutils
|
||||
MOVESIZEEND
|
||||
MOVESIZESTART
|
||||
MRM
|
||||
MRT
|
||||
mru
|
||||
msaccess
|
||||
MSAL
|
||||
@@ -1175,8 +1100,6 @@ msdata
|
||||
msdia
|
||||
MSDL
|
||||
MSGFLT
|
||||
MSHCTX
|
||||
MSHLFLAGS
|
||||
msiexec
|
||||
MSIFASTINSTALL
|
||||
MSIHANDLE
|
||||
@@ -1213,7 +1136,6 @@ myorg
|
||||
myrepo
|
||||
NAMECHANGE
|
||||
namespaceanddescendants
|
||||
Nanjing
|
||||
nao
|
||||
NCACTIVATE
|
||||
ncc
|
||||
@@ -1239,7 +1161,6 @@ netcpl
|
||||
netframework
|
||||
netsetup
|
||||
netsh
|
||||
newcolor
|
||||
NEWDIALOGSTYLE
|
||||
NEWFILE
|
||||
NEWFILEHEADER
|
||||
@@ -1252,7 +1173,6 @@ newrow
|
||||
nicksnettravels
|
||||
NIF
|
||||
nightlight
|
||||
NLog
|
||||
NLSTEXT
|
||||
NMAKE
|
||||
NNN
|
||||
@@ -1286,7 +1206,6 @@ nonclient
|
||||
NONCLIENTMETRICSW
|
||||
NONELEVATED
|
||||
nonspace
|
||||
nonstd
|
||||
NOOWNERZORDER
|
||||
NOPARENTNOTIFY
|
||||
NOPREFIX
|
||||
@@ -1342,7 +1261,6 @@ OFN
|
||||
ofs
|
||||
OICI
|
||||
OICIIO
|
||||
oldcolor
|
||||
olditem
|
||||
oldpath
|
||||
oldtheme
|
||||
@@ -1373,7 +1291,6 @@ OUTOFCONTEXT
|
||||
Outptr
|
||||
outputtype
|
||||
outsettings
|
||||
outsourced
|
||||
OVERLAPPEDWINDOW
|
||||
Oversampling
|
||||
OVERWRITEPROMPT
|
||||
@@ -1400,7 +1317,6 @@ PATINVERT
|
||||
PATPAINT
|
||||
pbc
|
||||
pbi
|
||||
PBlob
|
||||
PBP
|
||||
pbrush
|
||||
pcb
|
||||
@@ -1411,6 +1327,7 @@ pchast
|
||||
PCIDLIST
|
||||
PCTSTR
|
||||
PCWSTR
|
||||
pdbs
|
||||
PDBs
|
||||
PDEVMODE
|
||||
PDFs
|
||||
@@ -1422,7 +1339,6 @@ pdto
|
||||
pdtobj
|
||||
pdw
|
||||
Peb
|
||||
PElems
|
||||
Pels
|
||||
PELSHEIGHT
|
||||
PELSWIDTH
|
||||
@@ -1439,7 +1355,6 @@ pguid
|
||||
phbm
|
||||
phbmp
|
||||
phicon
|
||||
PHL
|
||||
Photoshop
|
||||
photoshop
|
||||
phwnd
|
||||
@@ -1447,7 +1362,6 @@ pici
|
||||
pidl
|
||||
PIDLIST
|
||||
pii
|
||||
pinboard
|
||||
pinfo
|
||||
pinvoke
|
||||
pipename
|
||||
@@ -1472,6 +1386,7 @@ Pokedex
|
||||
Pomodoro
|
||||
popups
|
||||
POPUPWINDOW
|
||||
portfile
|
||||
POSITIONITEM
|
||||
POWERBROADCAST
|
||||
powerdisplay
|
||||
@@ -1504,7 +1419,6 @@ Prefixer
|
||||
Premul
|
||||
prependpath
|
||||
prepopulate
|
||||
Prereq
|
||||
prevhost
|
||||
previewer
|
||||
PREVIEWHANDLERFRAMEINFO
|
||||
@@ -1548,7 +1462,6 @@ psrm
|
||||
psrree
|
||||
pstatstg
|
||||
pstm
|
||||
PStr
|
||||
pstream
|
||||
pstrm
|
||||
PSYSTEM
|
||||
@@ -1559,7 +1472,6 @@ PTCHAR
|
||||
ptcontrols
|
||||
ptd
|
||||
PTOKEN
|
||||
PToy
|
||||
ptstr
|
||||
ptsym
|
||||
pui
|
||||
@@ -1570,7 +1482,6 @@ PWSTR
|
||||
pwsz
|
||||
pwtd
|
||||
qdc
|
||||
QDS
|
||||
qit
|
||||
QITAB
|
||||
QITABENT
|
||||
@@ -1585,9 +1496,7 @@ quicklinks
|
||||
quickmask
|
||||
QUNS
|
||||
RAII
|
||||
RAlt
|
||||
randi
|
||||
RAquadrant
|
||||
rasterization
|
||||
Rasterize
|
||||
rasterizing
|
||||
@@ -1605,7 +1514,6 @@ READMODE
|
||||
READOBJECTS
|
||||
recents
|
||||
RECTDESTINATION
|
||||
rectp
|
||||
RECTSOURCE
|
||||
recursesubdirs
|
||||
recyclebin
|
||||
@@ -1664,8 +1572,6 @@ RIDEV
|
||||
RIGHTBUTTON
|
||||
RIGHTSCROLLBAR
|
||||
riid
|
||||
RKey
|
||||
RNumber
|
||||
rollups
|
||||
rop
|
||||
ROUNDSMALL
|
||||
@@ -1695,9 +1601,9 @@ SAMESHORTCUTPREVIOUSLYMAPPED
|
||||
samsung
|
||||
sancov
|
||||
SAVEFAILED
|
||||
scanled
|
||||
schedtasks
|
||||
SCID
|
||||
SCL
|
||||
Scode
|
||||
SCREENFONTS
|
||||
screenruler
|
||||
@@ -1873,6 +1779,7 @@ STDAPI
|
||||
stdc
|
||||
stdcpp
|
||||
stdcpplatest
|
||||
stdext
|
||||
STDMETHODCALLTYPE
|
||||
STDMETHODIMP
|
||||
steamapps
|
||||
@@ -1903,7 +1810,6 @@ sublang
|
||||
SUBMODULEUPDATE
|
||||
subresource
|
||||
sug
|
||||
suntimes
|
||||
Superbar
|
||||
SUPPRESSMSGBOXES
|
||||
sut
|
||||
@@ -1979,7 +1885,6 @@ thickframe
|
||||
THISCOMPONENT
|
||||
threadpool
|
||||
throughs
|
||||
Tianma
|
||||
TILEDWINDOW
|
||||
TILLSON
|
||||
timedate
|
||||
@@ -1997,6 +1902,7 @@ TNP
|
||||
Toggleable
|
||||
tontrager
|
||||
Toolhelp
|
||||
toolsets
|
||||
toolwindow
|
||||
TOPDOWNDIB
|
||||
TOUCHEVENTF
|
||||
@@ -2029,16 +1935,11 @@ UACUI
|
||||
UAL
|
||||
uap
|
||||
UBR
|
||||
UBreak
|
||||
ubrk
|
||||
UCallback
|
||||
ucrt
|
||||
ucrtd
|
||||
uefi
|
||||
UError
|
||||
uesc
|
||||
UFlags
|
||||
UHash
|
||||
UIA
|
||||
UIDs
|
||||
UIEx
|
||||
@@ -2047,8 +1948,6 @@ uitests
|
||||
UITo
|
||||
ULONGLONG
|
||||
Ultrawide
|
||||
UMax
|
||||
UMin
|
||||
ums
|
||||
uncompilable
|
||||
UNCPRIORITY
|
||||
@@ -2067,12 +1966,11 @@ UNORM
|
||||
unparsable
|
||||
unremapped
|
||||
Unsend
|
||||
unsubscribes
|
||||
Unsubscribes
|
||||
untriaged
|
||||
unvirtualized
|
||||
unwide
|
||||
unzoom
|
||||
UOffset
|
||||
UOI
|
||||
UPDATENOW
|
||||
updown
|
||||
@@ -2088,7 +1986,6 @@ USEINSTALLERFORTEST
|
||||
USESHOWWINDOW
|
||||
USESTDHANDLES
|
||||
USRDLL
|
||||
UType
|
||||
uuidv
|
||||
uwp
|
||||
uxt
|
||||
@@ -2099,17 +1996,15 @@ valuegenerator
|
||||
VARTYPE
|
||||
vbcscompiler
|
||||
vcamp
|
||||
vcenter
|
||||
VCENTER
|
||||
vcgtq
|
||||
VCINSTALLDIR
|
||||
vcp
|
||||
Vcpkg
|
||||
vcpkg
|
||||
vcpname
|
||||
VCRT
|
||||
vcruntime
|
||||
vcvars
|
||||
VDesktop
|
||||
vdupq
|
||||
VERBSONLY
|
||||
VERBW
|
||||
@@ -2138,8 +2033,6 @@ vorrq
|
||||
VOS
|
||||
vpaddlq
|
||||
vqsubq
|
||||
vredraw
|
||||
VREDRAW
|
||||
vreinterpretq
|
||||
VSC
|
||||
VSCBD
|
||||
@@ -2151,24 +2044,20 @@ VSINSTALLDIR
|
||||
VSM
|
||||
vso
|
||||
vsonline
|
||||
VSpeed
|
||||
vstemplate
|
||||
vstest
|
||||
VSTHRD
|
||||
vstprintf
|
||||
VSTT
|
||||
vswhere
|
||||
VSync
|
||||
Vtbl
|
||||
WANTNUKEWARNING
|
||||
WANTPALM
|
||||
WASDK
|
||||
wbem
|
||||
WBounds
|
||||
Wca
|
||||
WCE
|
||||
wcex
|
||||
WClass
|
||||
WCRAPI
|
||||
wcsicmp
|
||||
wcsncpy
|
||||
@@ -2251,7 +2140,6 @@ WNDCLASSW
|
||||
wndproc
|
||||
wnode
|
||||
wom
|
||||
workerw
|
||||
WORKSPACESEDITOR
|
||||
WORKSPACESLAUNCHER
|
||||
WORKSPACESSNAPSHOTTOOL
|
||||
@@ -2266,7 +2154,6 @@ wpr
|
||||
wprp
|
||||
wql
|
||||
wregex
|
||||
WReserved
|
||||
WResize
|
||||
WRITEOBJECTS
|
||||
Wrk
|
||||
@@ -2284,49 +2171,23 @@ Wubi
|
||||
WUX
|
||||
Wwanpp
|
||||
xap
|
||||
XAxis
|
||||
XButton
|
||||
Xbuttondown
|
||||
xclip
|
||||
xcopy
|
||||
XDeployment
|
||||
xdf
|
||||
XDimension
|
||||
XDocument
|
||||
XElement
|
||||
xfd
|
||||
XFile
|
||||
XIncrement
|
||||
XLoc
|
||||
xmp
|
||||
XNamespace
|
||||
Xoshiro
|
||||
XPels
|
||||
XPixel
|
||||
XPos
|
||||
XResource
|
||||
xsi
|
||||
XSpeed
|
||||
XStr
|
||||
xstyler
|
||||
XTimer
|
||||
XUP
|
||||
XVIRTUALSCREEN
|
||||
XXL
|
||||
xxxxxx
|
||||
YAxis
|
||||
ycombinator
|
||||
YDimension
|
||||
YIncrement
|
||||
yinle
|
||||
yinyue
|
||||
yoko
|
||||
YPels
|
||||
YPos
|
||||
YResolution
|
||||
YSpeed
|
||||
YStr
|
||||
YTimer
|
||||
YVIRTUALSCREEN
|
||||
zamora
|
||||
Zenbook
|
||||
|
||||
6
.github/actions/spell-check/patterns.txt
vendored
@@ -312,3 +312,9 @@ ms-windows-store://\S+
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
|
||||
|
||||
# Special licenses text from RNNoise (BSD-style disclaimer: ``AS IS'')
|
||||
``AS IS''
|
||||
|
||||
# Old school moniker for macOS from RNNoise
|
||||
MacOS
|
||||
|
||||
2
.github/policies/resourceManagement.yml
vendored
@@ -163,7 +163,7 @@ configuration:
|
||||
association: Collaborator
|
||||
then:
|
||||
- addReply:
|
||||
reply: Hi! We've identified this issue as a duplicate of another one that already exists on this Issue Tracker. This specific instance is being closed in favor of tracking the concern over on the referenced thread. Thanks for your report!
|
||||
reply: We've identified this issue as a duplicate of an existing one and are closing this thread so discussion stays in one place.<br/><br/>Please see the comment above for the link to the original tracking issue, and feel free to subscribe there for updates.
|
||||
- closeIssue
|
||||
- removeLabel:
|
||||
label: Needs-Triage
|
||||
|
||||
67
.github/scripts/telemetry-pr-check.js
vendored
@@ -9,21 +9,22 @@
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
const REVIEWER_LOGIN = 'chatasweetie';
|
||||
const REVIEWER_MENTION = `@${REVIEWER_LOGIN}`;
|
||||
|
||||
const COMMENT_MARKER = '<!-- telemetry-event-check -->';
|
||||
const COMMENT_BODY_WITH_PRIVACY_UPDATE = `${COMMENT_MARKER}
|
||||
THIS IS A TEST | @chatasweetie is testing this functionality
|
||||
Thanks for contributing to PowerToys. This change might include a new or modified telemetry event, and we want to help make sure you can get your data end to end.
|
||||
Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. After this PR is merged, please follow these next steps:
|
||||
|
||||
1. Reach out to Jessica (@chatasweetie) to follow up on the next steps to add these telemetry events to our pipelines.`;
|
||||
- [ ] Reach out to Jessica (${REVIEWER_MENTION}) to follow up on the next steps: https://aka.ms/pt-telemetry-process
|
||||
`;
|
||||
|
||||
const COMMENT_BODY_WITHOUT_PRIVACY_UPDATE = `${COMMENT_MARKER}
|
||||
THIS IS A TEST | @chatasweetie is testing this functionality
|
||||
Thanks for contributing to PowerToys. This change might include a new or modified telemetry event, and we want to help make sure you can get your data end to end.
|
||||
Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. Please ensure the following before merging:
|
||||
|
||||
1. Make sure to add your telemetry events to DATA_AND_PRIVACY.md.
|
||||
- [ ] Add your telemetry events to [DATA_AND_PRIVACY](https://github.com/microsoft/PowerToys/blob/main/DATA_AND_PRIVACY.md).md within this PR.
|
||||
|
||||
2. Reach out to Jessica (@chatasweetie) to follow up on the next steps to add these telemetry events to our pipelines.`;
|
||||
- [ ] Reach out to Jessica (${REVIEWER_MENTION}) to follow up on the next steps: https://aka.ms/pt-telemetry-process`;
|
||||
|
||||
const TELEMETRY_PATH_PATTERNS = [
|
||||
/(^|\/)trace\.(h|hpp|cpp|cs)$/i,
|
||||
@@ -191,6 +192,48 @@ async function getAllPullFiles(apiBaseUrl, repository, pullNumber) {
|
||||
return files;
|
||||
}
|
||||
|
||||
async function getPullRequest(apiBaseUrl, repository, pullNumber) {
|
||||
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}`;
|
||||
const pullRequest = await apiRequest(url);
|
||||
if (!pullRequest || typeof pullRequest !== 'object') {
|
||||
throw new Error('Unexpected response while fetching pull request details.');
|
||||
}
|
||||
return pullRequest;
|
||||
}
|
||||
|
||||
async function ensureReviewerRequested(apiBaseUrl, repository, pullNumber, pullRequest) {
|
||||
const authorLogin = String(pullRequest?.user?.login || '').toLowerCase();
|
||||
const targetReviewer = REVIEWER_LOGIN.toLowerCase();
|
||||
|
||||
if (authorLogin === targetReviewer) {
|
||||
console.log(`Skipping reviewer request: ${REVIEWER_LOGIN} is the PR author.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const requestedReviewers = Array.isArray(pullRequest?.requested_reviewers)
|
||||
? pullRequest.requested_reviewers
|
||||
: [];
|
||||
const alreadyRequested = requestedReviewers.some(
|
||||
(reviewer) => String(reviewer?.login || '').toLowerCase() === targetReviewer
|
||||
);
|
||||
|
||||
if (alreadyRequested) {
|
||||
console.log(`Reviewer ${REVIEWER_LOGIN} is already requested.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}/requested_reviewers`;
|
||||
try {
|
||||
await apiRequest(url, 'POST', { reviewers: [REVIEWER_LOGIN] });
|
||||
console.log(`Requested reviewer ${REVIEWER_LOGIN}.`);
|
||||
} catch (error) {
|
||||
// Reviewer request should not fail the telemetry guidance workflow.
|
||||
console.warn(
|
||||
`Unable to request reviewer ${REVIEWER_LOGIN}: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function findExistingTelemetryComment(apiBaseUrl, repository, pullNumber) {
|
||||
let page = 1;
|
||||
|
||||
@@ -310,6 +353,16 @@ async function main() {
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const pullRequest = await getPullRequest(parsedApiBaseUrl.origin, repository, pullNumber);
|
||||
await ensureReviewerRequested(parsedApiBaseUrl.origin, repository, pullNumber, pullRequest);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Failed to fetch PR details or request reviewer; continuing to post telemetry guidance comment.'
|
||||
);
|
||||
console.warn(error instanceof Error ? error.stack || error.message : error);
|
||||
}
|
||||
|
||||
const commentBody = dataAndPrivacyChanged
|
||||
? COMMENT_BODY_WITH_PRIVACY_UPDATE
|
||||
: COMMENT_BODY_WITHOUT_PRIVACY_UPDATE;
|
||||
|
||||
229
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
@@ -31,7 +31,9 @@ Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.U
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Recommended Order
|
||||
### Phase-by-Phase Scope
|
||||
|
||||
Work on bounded problems, not the entire codebase at once. Each phase should compile before moving to the next.
|
||||
|
||||
1. **Project file** — Update TFM, NuGet packages, set `<UseWinUI>true</UseWinUI>`
|
||||
2. **Data models and business logic** — No UI dependencies, migrate first
|
||||
@@ -45,79 +47,213 @@ Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.U
|
||||
10. **Installer & build pipeline** — Update WiX, signing, build events
|
||||
11. **Tests** — Adapt for WinUI 3 runtime, async patterns
|
||||
|
||||
### Key Principles
|
||||
### Migration Contract: Prohibited Patterns
|
||||
|
||||
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different application lifecycle boilerplate. Merge your resources and initialization code into the generated WinUI 3 App class.
|
||||
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. This causes phantom build artifacts.
|
||||
- **Use `Lazy<T>` for resource-dependent statics** — `ResourceLoader` is not available at class-load time in all contexts.
|
||||
These rules capture human judgment and must be applied consistently across every file. Do NOT deviate.
|
||||
|
||||
**Architecture prohibitions:**
|
||||
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different lifecycle boilerplate. Merge resources and init code into the generated WinUI 3 App class.
|
||||
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. Causes phantom build artifacts.
|
||||
- **Do NOT instantiate services directly** — Use DI and CommunityToolkit.Mvvm patterns.
|
||||
- **Do NOT create a `Window` subclass for every dialog or sub-page** — use `ContentDialog` for in-app dialogs and `Frame`/`Page` navigation for sub-views. Separate `Window` classes are reserved for distinct top-level surfaces (e.g., FancyZones editor, OOBE).
|
||||
- **Do NOT omit `WindowsPackageType=None` and `WindowsAppSDKSelfContained=true`** — Both are mandatory in the csproj for every WinUI 3 module in PowerToys. Without them the app crashes at startup with `COMException: ClassFactory cannot supply requested class` because the WinUI 3 runtime DLLs are not found.
|
||||
|
||||
**XAML prohibitions:**
|
||||
- **Do NOT use `{DynamicResource}`** — Replace with `{ThemeResource}` (theme-reactive) or `{StaticResource}`.
|
||||
- **Do NOT use `{Binding}` in `Setter.Value`** — Not supported in WinUI 3. Use `{StaticResource}`.
|
||||
- **Do NOT use `{x:Static}`** — Replace with `{x:Bind}`, `x:Uid`, or code-behind.
|
||||
- **Do NOT use `{x:Type}`** — Not supported. Use `x:DataType` for DataTemplate, or code-behind.
|
||||
- **Do NOT use `clr-namespace:`** — Replace with `using:` in all xmlns declarations.
|
||||
- **Do NOT use `Style.Triggers` / `DataTrigger` / `EventTrigger`** — Replace with `VisualStateManager`.
|
||||
- **Do NOT use `MultiBinding`** — Replace with `x:Bind` function binding or computed ViewModel property.
|
||||
- **Do NOT use `Visibility="Hidden"`** — WinUI only has `Visible` and `Collapsed`. Use `Opacity="0"` if layout must be preserved.
|
||||
- **Do NOT use `IsDefault` / `IsCancel`** — Use `AccentButtonStyle` for primary button; handle Enter/Escape in code-behind.
|
||||
- **Do NOT omit `BasedOn` when overriding default styles** — Without it, your style replaces the entire default. Always use `BasedOn="{StaticResource DefaultButtonStyle}"` etc.
|
||||
- **Do NOT omit `XamlControlsResources` as first merged dictionary** — It provides default Fluent styles. Without it, controls have no visual appearance.
|
||||
|
||||
**Code-behind prohibitions:**
|
||||
- **Do NOT use `Application.Current.Dispatcher`** — Store `DispatcherQueue` in a static field explicitly.
|
||||
- **Do NOT use `Window.Current`** — Not supported. Use a custom `App.Window` static property.
|
||||
- **Do NOT put `DataContext`, `Resources`, or `VisualStateManager` on `Window`** — WinUI 3 `Window` is NOT a `DependencyObject`. Use a root `Page`/`UserControl`/`Grid`.
|
||||
- **Do NOT use tunneling/preview events** (`PreviewMouseDown`, `PreviewKeyDown`) — WinUI has no tunneling. Use bubbling equivalents with `Handled` property or `AddHandler(handledEventsToo: true)`.
|
||||
|
||||
**Resource prohibitions:**
|
||||
- **Do NOT use `Properties.Resources.MyString`** — Replace with `ResourceLoaderInstance.ResourceLoader.GetString("MyString")`.
|
||||
- **Do NOT initialize `ResourceLoader`-dependent values as static fields** — Wrap in `Lazy<T>` or null-coalescing property.
|
||||
- **Do NOT use `pack://` URIs** — Replace with `ms-appx:///` scheme.
|
||||
|
||||
## Quick Reference Tables
|
||||
|
||||
### Namespace Mapping
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` | Root namespace |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` | Core controls |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` | Low-level primitives |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` | Brushes, transforms |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` | Storyboard, animations |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) | Split by purpose |
|
||||
| `System.Windows.Media.Media3D` | **No equivalent** | Use Win2D or Composition APIs |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` | Rectangle, Ellipse, Path |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` | Pointer, keyboard, focus |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` | Binding, IValueConverter |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` | Limited — RichTextBlock + Paragraph |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` | XAML parsing, markup extensions |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` | Accessibility / UI Automation |
|
||||
| `System.Windows.Navigation` | **No direct equivalent** | Use `Frame.Navigate()` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` | Dispatcher → DispatcherQueue |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` / `Microsoft.UI.Xaml.Hosting` | HWND interop |
|
||||
|
||||
### Control Replacements (No 1:1 Mapping)
|
||||
|
||||
These WPF controls have no direct counterpart and require a different control or third-party package:
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | Notes |
|
||||
|-------------|---------------------|-------|
|
||||
| `DataGrid` | [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) | Community library; the Toolkit `DataGrid` is no longer maintained. Legacy code may still pin v7 `CommunityToolkit.WinUI.UI.Controls.DataGrid` 7.1.2 |
|
||||
| `Ribbon` | `CommandBar` / `NavigationView`, or [Toolkit Labs Ribbon](https://github.com/CommunityToolkit/Labs-Windows/tree/main/components/Ribbon) | No first-party Ribbon in WinUI; Labs component is experimental/partial |
|
||||
| `Menu` / `MenuItem` | `MenuBar` / `MenuBarItem` / `MenuFlyout` | `MenuBar` for classic menu, `MenuFlyout` for context |
|
||||
| `ContextMenu` | `MenuFlyout` | Assign to `ContextFlyout` property |
|
||||
| `ToolBar` / `ToolBarTray` | `CommandBar` + `AppBarButton` | |
|
||||
| `StatusBar` | Custom `Grid`/`StackPanel` or `InfoBar` | No StatusBar control |
|
||||
| `TabControl` | `TabView` or `NavigationView` (top mode) | `TabView` for closeable tabs |
|
||||
| `DocumentViewer` | `WebView2` | Render PDFs/XPS inside WebView2 |
|
||||
| `FlowDocument` | `RichTextBlock` | Partial replacement only |
|
||||
| `RichTextBox` | `RichEditBox` | Rich text editing |
|
||||
| `GroupBox` | `Expander` (built-in) or `HeaderedContentControl` (Toolkit) | See [Layout & Header Controls from CommunityToolkit.WinUI](#layout--header-controls-from-communitytoolkitwinui) below |
|
||||
| `Label` | `TextBlock` | WPF `Label` is a `ContentControl`; use `TextBlock` + `AccessKey` |
|
||||
| `TreeView` | `TreeView` (native) | Available natively, but data binding model differs significantly |
|
||||
| `MessageBox` | `ContentDialog` | Must set `XamlRoot` before `ShowAsync()` |
|
||||
| `MediaElement` | `MediaPlayerElement` | Different API |
|
||||
| `AccessText` | Not available | Use `AccessKey` property on target control |
|
||||
|
||||
### Layout & Header Controls from CommunityToolkit.WinUI
|
||||
|
||||
These WPF controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. **The NuGet package id and the XAML namespace differ intentionally**: package names end in `.Primitives` / `.HeaderedControls`, but the registered XAML namespace is the shorter `CommunityToolkit.WinUI.Controls` (confirmed in the [official Microsoft Q&A](https://learn.microsoft.com/en-us/answers/questions/5746230/why-does-communitytoolkit-uwp-controls-primitive-u)).
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | NuGet Package | XAML Namespace |
|
||||
|-------------|---------------------|---------------|----------------|
|
||||
| `WrapPanel` | `WrapPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `UniformGrid` | `UniformGrid` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `DockPanel` | `DockPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `GroupBox` (alt.) | `HeaderedContentControl` | `CommunityToolkit.WinUI.Controls.HeaderedControls` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
|
||||
### No Equivalent — Requires Architectural Rework
|
||||
|
||||
These WPF features have no WinUI counterpart and require redesign, not find-and-replace:
|
||||
|
||||
| WPF Feature | WinUI 3 Replacement Strategy |
|
||||
|-------------|------------------------------|
|
||||
| `Style.Triggers` / `DataTrigger` | `VisualStateManager` with `StateTrigger` — see [XAML Migration](./references/xaml-migration.md) |
|
||||
| `MultiBinding` | `x:Bind` function binding: `{x:Bind local:Converters.Format(VM.A, VM.B), Mode=OneWay}` |
|
||||
| `RoutedUICommand` / `CommandBinding` | `ICommand` / `[RelayCommand]` from CommunityToolkit.Mvvm. WinUI also has `StandardUICommand` / `XamlUICommand` for platform commands. |
|
||||
| `AdornerLayer` / `Adorner` | Depends on use case: `TeachingTip`/`InfoBar` (validation), `Popup` (overlays), `PlaceholderText` (watermarks), Canvas overlay (decorations) |
|
||||
| `Visibility.Hidden` | `Opacity="0"` with `Visibility="Visible"` (preserves layout space) |
|
||||
| `Window.Resources` / `Window.DataContext` | Move to root `Grid.Resources` / root `Page`/`UserControl` — WinUI `Window` is NOT a DependencyObject |
|
||||
| Tunneling events (`Preview*`) | Use bubbling equivalents + `Handled` property or `AddHandler(handledEventsToo: true)` |
|
||||
|
||||
### Critical API Replacements
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`) |
|
||||
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`), async by default |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` | Property vs method |
|
||||
| `Application.Current.Dispatcher` | Store `DispatcherQueue` in static field | See [Threading](./references/threading-and-windowing.md) |
|
||||
| `Window.Current` | Custom `App.Window` static property | Not supported in Windows App SDK |
|
||||
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
|
||||
| `MessageBox.Show()` | `ContentDialog` | Must set `XamlRoot` |
|
||||
| `System.Windows.Clipboard` | `Windows.ApplicationModel.DataTransfer.Clipboard` | Different API surface |
|
||||
| `RoutedUICommand` / `CommandBinding` | `ICommand` / `[RelayCommand]` | Remove `CommandBinding`; bind `ICommand` directly |
|
||||
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
|
||||
| `DynamicResource` | `ThemeResource` | Theme-reactive only |
|
||||
| `clr-namespace:` | `using:` | XAML namespace prefix |
|
||||
| `{x:Static props:Resources.Key}` | `x:Uid` or `ResourceLoader.GetString()` | .resx → .resw |
|
||||
| `DataType="{x:Type m:Foo}"` | Remove or use code-behind | `x:Type` not supported |
|
||||
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
|
||||
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
|
||||
| `DataType="{x:Type m:Foo}"` | `x:DataType="m:Foo"` | `x:Type` not supported |
|
||||
| `SizeToContent="Height"` | Custom `SizeToContent()` via `AppWindow.Resize()` | See [Windowing](./references/threading-and-windowing.md) |
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Mouse → Pointer events |
|
||||
| `Pack URI (pack://...)` | `ms-appx:///` | Resource URI scheme |
|
||||
| `Observable` (custom base) | `ObservableObject` + `[ObservableProperty]` | CommunityToolkit.Mvvm |
|
||||
| `RelayCommand` (custom) | `[RelayCommand]` source generator | CommunityToolkit.Mvvm |
|
||||
| `JpegBitmapEncoder` | `BitmapEncoder.CreateAsync(JpegEncoderId, stream)` | Async, unified API |
|
||||
| `encoder.QualityLevel = 85` | `BitmapPropertySet { "ImageQuality", 0.85f }` | int 1-100 → float 0-1 |
|
||||
|
||||
### Event Replacements (Mouse → Pointer)
|
||||
|
||||
| WPF Event | WinUI 3 Event | Notes |
|
||||
|-----------|--------------|-------|
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Check `IsLeftButtonPressed` on args |
|
||||
| `MouseLeftButtonUp` | `PointerReleased` | Check pointer properties |
|
||||
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` with right button check |
|
||||
| `MouseMove` | `PointerMoved` | `MouseEventArgs` → `PointerRoutedEventArgs` |
|
||||
| `MouseWheel` | `PointerWheelChanged` | Different event args |
|
||||
| `MouseEnter` / `MouseLeave` | `PointerEntered` / `PointerExited` | |
|
||||
| `MouseDoubleClick` | `DoubleTapped` | Different event args |
|
||||
| `PreviewMouseDown` | `PointerPressed` | No tunneling — use `Handled` or `AddHandler` |
|
||||
| `PreviewKeyDown` | `KeyDown` | `KeyEventArgs` → `KeyRoutedEventArgs` |
|
||||
|
||||
### Property Replacements
|
||||
|
||||
| WPF | WinUI 3 | Context |
|
||||
|-----|---------|---------|
|
||||
| `Visibility.Hidden` | `Visibility.Collapsed` or `Opacity="0"` | Use `Opacity="0"` to preserve layout |
|
||||
| `TextWrapping.WrapWithOverflow` | `TextWrapping.Wrap` | WinUI doesn't distinguish |
|
||||
| `Focusable="True"` | `IsTabStop="True"` | Different property name |
|
||||
| `ContextMenu=` | `ContextFlyout=` | On any `UIElement` |
|
||||
| `MediaElement` | `MediaPlayerElement` | Different API |
|
||||
| `SnapsToDevicePixels` | Not available | WinUI handles pixel snapping internally |
|
||||
|
||||
### NuGet Package Migration
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` |
|
||||
| `WPF-UI` (Lepo) | Remove — use native WinUI 3 controls |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` |
|
||||
| (none) | `Microsoft.WindowsAppSDK` |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` |
|
||||
| (none) | `WinUIEx` (optional, for window helpers) |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` |
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` | |
|
||||
| `WPF-UI` (Lepo) | **Remove** — use native WinUI 3 controls | |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) | |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` | |
|
||||
| (none) | `Microsoft.WindowsAppSDK` | Required |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` | Required |
|
||||
| (none) | `WinUIEx` | Optional, window helpers |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Primitives` | Optional — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.HeaderedControls` | Optional — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.SettingsControls` | Optional — `SettingsCard`, `SettingsExpander` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Sizers` | Optional — `GridSplitter` |
|
||||
| (none) | `CommunityToolkit.WinUI.UI.Controls.DataGrid` | Legacy v7 — only for migrating existing `DataGrid` code; prefer `WinUI.TableView` |
|
||||
|
||||
### XAML Syntax Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` |
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind |
|
||||
| `{x:Type local:MyType}` | Not supported |
|
||||
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` |
|
||||
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` |
|
||||
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` |
|
||||
| `<ui:FluentWindow>` / `<ui:Button>` (WPF-UI) | Native `<Window>` / `<Button>` |
|
||||
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` |
|
||||
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / handle via KeyDown |
|
||||
| `<AccessText>` | Not available — use `AccessKey` property |
|
||||
| `<behaviors:Interaction.Triggers>` | Migrate to code-behind or WinUI behaviors |
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` | CLR → using syntax |
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` | Re-evaluates on theme change |
|
||||
| `{StaticResource Key}` | `{StaticResource Key}` | Same — resolved once at load |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind | |
|
||||
| `{x:Type local:MyType}` | Not supported | Use `x:DataType` for DataTemplate |
|
||||
| `{x:Array}` | Not supported | Create collections in code-behind |
|
||||
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` | See [XAML Migration](./references/xaml-migration.md) |
|
||||
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` | |
|
||||
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` | |
|
||||
| `sys:String` / `sys:Int32` / etc. | `x:String` / `x:Int32` / etc. | XAML intrinsic types |
|
||||
| `<ui:FluentWindow>` (WPF-UI) | `<Window>` | Native + `ExtendsContentIntoTitleBar` |
|
||||
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` | |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` | Named style keys |
|
||||
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / KeyDown | |
|
||||
| `<AccessText>` | Not available — use `AccessKey` property | |
|
||||
| `<behaviors:Interaction.Triggers>` | Code-behind or WinUI behaviors | |
|
||||
| `Window.Resources` | Root container's `Resources` (e.g. `Grid.Resources`) | Window is not a DependencyObject |
|
||||
|
||||
### Binding: {Binding} vs {x:Bind}
|
||||
|
||||
Both work in WinUI 3. Prefer `{x:Bind}` for new/migrated code.
|
||||
|
||||
| Feature | `{Binding}` | `{x:Bind}` |
|
||||
|---------|------------|------------|
|
||||
| Default mode | `OneWay` | **`OneTime`** — add `Mode=OneWay` explicitly! |
|
||||
| Default source | `DataContext` | Page/UserControl code-behind |
|
||||
| Compile-time validation | No | Yes |
|
||||
| Function binding | No | Yes (replaces `MultiBinding`) |
|
||||
| Performance | Reflection-based | Compiled, no reflection |
|
||||
| `MultiBinding` support | No (not in WinUI) | Use function binding |
|
||||
|
||||
## Detailed Reference Docs
|
||||
|
||||
@@ -151,6 +287,8 @@ Read only the section relevant to your current task:
|
||||
| JPEG quality value wrong after migration | WPF: int 1-100; WinRT: float 0.0-1.0 |
|
||||
| MSIX packaging fails in PreBuildEvent | Move to PostBuildEvent; artifacts not ready at PreBuild time |
|
||||
| RC file icon path with forward slashes | Use double-backslash escaping: `..\\ui\\Assets\\icon.ico` |
|
||||
| `COMException: ClassFactory cannot supply requested class` at startup | Missing `<WindowsPackageType>None</WindowsPackageType>` and/or `<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>` in csproj. Without these, the app tries to locate the Windows App SDK framework package (not installed) instead of using bundled runtime DLLs. **Both properties are mandatory for every WinUI 3 module in PowerToys.** |
|
||||
| `CombinedGeometry` not available in WinUI 3 | WinUI 3 `UIElement.Clip` only accepts `RectangleGeometry`. For overlay hole effects (exclude region), use a `Path` element with `GeometryGroup FillRule="EvenOdd"` containing two `RectangleGeometry` children — the EvenOdd rule creates a transparent hole where geometries overlap. |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -163,3 +301,4 @@ Read only the section relevant to your current task:
|
||||
| NuGet restore failures | Run `build-essentials.cmd` after adding `Microsoft.WindowsAppSDK` package |
|
||||
| `Parallel.ForEach` compilation error | Migrate to `Parallel.ForEachAsync` for async imaging operations |
|
||||
| Signing check fails on leaked artifacts | Run `generateAllFileComponents.ps1`; verify only `WinUI3Apps\\` paths in signing config |
|
||||
| `COMException` / `ClassFactory` error at app launch | Ensure csproj has `<WindowsPackageType>None</WindowsPackageType>` and `<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>`. These are required for all unpackaged WinUI 3 apps in PowerToys — without them the WinUI 3 COM runtime cannot be found. |
|
||||
|
||||
@@ -4,24 +4,26 @@ Complete reference for mapping WPF types to WinUI 3 equivalents, based on the Im
|
||||
|
||||
## Root Namespace Mapping
|
||||
|
||||
| WPF Namespace | WinUI 3 Namespace |
|
||||
|---------------|-------------------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` |
|
||||
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` |
|
||||
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
| WPF Namespace | WinUI 3 Namespace | Notes |
|
||||
|---------------|-------------------|-------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` | Root namespace |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` | Accessibility / UI Automation |
|
||||
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` | |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` | Core controls |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` | Low-level primitives |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` | Binding, IValueConverter |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` | Limited — RichTextBlock + Paragraph only |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` | Pointer, keyboard, focus |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` | XAML parsing, markup extensions |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` | Brushes, transforms |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` | Storyboard, animations |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` | UI display only — use `Windows.Graphics.Imaging` for processing |
|
||||
| `System.Windows.Media.Media3D` | **No equivalent** | Use Win2D or Composition APIs |
|
||||
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` | For Frame navigation events; no `NavigationService` |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` | Rectangle, Ellipse, Path |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` | Dispatcher → DispatcherQueue |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` | HWND interop |
|
||||
| `System.Windows.Interop` | `Microsoft.UI.Xaml.Hosting` | XAML Islands |
|
||||
|
||||
## Core Type Mapping
|
||||
|
||||
@@ -45,7 +47,7 @@ Complete reference for mapping WPF types to WinUI 3 equivalents, based on the Im
|
||||
|
||||
These controls exist in both frameworks with the same name — change `System.Windows.Controls` to `Microsoft.UI.Xaml.Controls`:
|
||||
|
||||
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
|
||||
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
|
||||
|
||||
### Controls With Different Names or Behavior
|
||||
|
||||
@@ -56,6 +58,7 @@ These controls exist in both frameworks with the same name — change `System.Wi
|
||||
| `TabControl` | `TabView` | Different API |
|
||||
| `Menu` | `MenuBar` | Different API |
|
||||
| `StatusBar` | Custom `StackPanel` layout | No built-in equivalent |
|
||||
| `ListBox` | `ListView` (or `ItemsView`) | **Deprecated in WinUI 3 — do not use.** Prefer `ListView`, or `ItemsView` (WinUI 1.5+) for modern collection scenarios |
|
||||
| `AccessText` | Not available | Use `AccessKey` property on target control |
|
||||
|
||||
### WPF-UI (Lepo) to Native WinUI 3
|
||||
@@ -109,6 +112,43 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
|
||||
| `System.Windows.Interop.WindowInteropHelper` | `WinRT.Interop.WindowNative.GetWindowHandle()` | |
|
||||
| `System.Windows.SystemColors` | Resource keys via `ThemeResource` | No direct static class |
|
||||
| `System.Windows.SystemParameters` | Win32 API or `DisplayInformation` | No direct equivalent |
|
||||
| `System.Windows.Clipboard` | `Windows.ApplicationModel.DataTransfer.Clipboard` | Different API surface |
|
||||
| `System.Windows.Input.RoutedUICommand` | `Microsoft.UI.Xaml.Input.StandardUICommand` / `XamlUICommand` | Or use `ICommand` / `[RelayCommand]` |
|
||||
| `System.Windows.Input.CommandBinding` | **Remove** | Bind `ICommand` directly in XAML |
|
||||
|
||||
## Controls That Need Translation (No 1:1 Mapping)
|
||||
|
||||
These controls exist in WPF but require a different control, third-party library, or Community Toolkit package in WinUI 3:
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | Package / Notes |
|
||||
|-------------|---------------------|-----------------|
|
||||
| `DataGrid` | [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) | Community library; the Toolkit `DataGrid` is no longer maintained. PowerToys legacy modules may still pin v7 `CommunityToolkit.WinUI.UI.Controls.DataGrid` 7.1.2 — prefer `WinUI.TableView` for new work. |
|
||||
| `Ribbon` | `CommandBar` / `NavigationView`, or [Toolkit Labs Ribbon](https://github.com/CommunityToolkit/Labs-Windows/tree/main/components/Ribbon) | No first-party Ribbon in WinUI; the Labs component is experimental and partial |
|
||||
| `Menu` / `MenuItem` | `MenuBar` / `MenuBarItem` / `MenuFlyoutItem` | `MenuBar` for classic menu, `MenuFlyout` for context |
|
||||
| `ContextMenu` | `MenuFlyout` | Assign to `ContextFlyout` property |
|
||||
| `ToolBar` / `ToolBarTray` | `CommandBar` + `AppBarButton` | |
|
||||
| `StatusBar` | Custom `Grid`/`StackPanel` or `InfoBar` | No StatusBar control |
|
||||
| `TabControl` | `TabView` or `NavigationView` (top mode) | `TabView` for closeable tabs |
|
||||
| `DocumentViewer` | `WebView2` | `Microsoft.Web.WebView2` — render PDFs/XPS |
|
||||
| `FlowDocument` | `RichTextBlock` | Partial replacement only |
|
||||
| `RichTextBox` | `RichEditBox` | Rich text editing |
|
||||
| `GroupBox` | `Expander` (built-in) or `HeaderedContentControl` (Toolkit) | See [Layout & Header Controls from CommunityToolkit.WinUI](#layout--header-controls-from-communitytoolkitwinui) below |
|
||||
| `Label` | `TextBlock` | WPF `Label` is a `ContentControl`; use `TextBlock` + `AccessKey` |
|
||||
| `TreeView` | `TreeView` (native) | Available natively, but data binding model differs significantly |
|
||||
| `MediaElement` | `MediaPlayerElement` | Different API |
|
||||
|
||||
## Layout & Header Controls from CommunityToolkit.WinUI
|
||||
|
||||
These WPF layout/header controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. **The NuGet package id and the XAML namespace differ intentionally**: package names end in `.Primitives` / `.HeaderedControls`, but both packages register their controls in the shorter `CommunityToolkit.WinUI.Controls` XAML namespace (confirmed in the [official Microsoft Q&A](https://learn.microsoft.com/en-us/answers/questions/5746230/why-does-communitytoolkit-uwp-controls-primitive-u): *"all controls live under `CommunityToolkit.WinUI.Controls` … This is intentional"*).
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | NuGet Package | XAML Namespace |
|
||||
|-------------|---------------------|---------------|----------------|
|
||||
| `WrapPanel` | `WrapPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `UniformGrid` | `UniformGrid` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `DockPanel` | `DockPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `GroupBox` (alt.) | `HeaderedContentControl` | `CommunityToolkit.WinUI.Controls.HeaderedControls` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
|
||||
Other primitives in `Primitives`: `ConstrainedBox`, `SwitchPresenter`, `WrapLayout`, `StaggeredPanel`. Other Headered controls in `HeaderedControls`: `HeaderedItemsControl`, `HeaderedTreeView`.
|
||||
|
||||
## NuGet Package Migration
|
||||
|
||||
@@ -124,6 +164,11 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
|
||||
| (none) | `WinUIEx` | Optional, window helpers |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Extensions` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Primitives` | Optional — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox`, `SwitchPresenter` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.HeaderedControls` | Optional — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.SettingsControls` | Optional — `SettingsCard`, `SettingsExpander` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Sizers` | Optional — `GridSplitter`, `PropertySizer` |
|
||||
| (none) | `CommunityToolkit.WinUI.UI.Controls.DataGrid` | Legacy v7 — only if migrating existing `DataGrid` code; prefer [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) for new work |
|
||||
| (none) | `Microsoft.Web.WebView2` | If using WebView |
|
||||
|
||||
## Project File Changes
|
||||
@@ -167,8 +212,9 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
|
||||
Key changes:
|
||||
- `UseWPF` → `UseWinUI`
|
||||
- TFM: `net8.0-windows` → `net8.0-windows10.0.19041.0`
|
||||
- Add `WindowsPackageType=None` for unpackaged desktop apps
|
||||
- Add `SelfContained=true` + `WindowsAppSDKSelfContained=true`
|
||||
- **CRITICAL: Add `WindowsPackageType=None`** — marks the app as unpackaged (no MSIX). Without this, the build produces an MSIX-style package that won't run as a standalone PowerToys module.
|
||||
- **CRITICAL: Add `WindowsAppSDKSelfContained=true`** — bundles the Windows App SDK runtime DLLs (e.g. `Microsoft.UI.Xaml.dll`) into the output directory. Without this, the app throws `COMException: ClassFactory cannot supply requested class` at startup because the WinUI 3 COM classes cannot be found.
|
||||
- Add `SelfContained=true` (usually via `Common.SelfContained.props`)
|
||||
- Add `DISABLE_XAML_GENERATED_MAIN` if using custom `Program.cs` entry point
|
||||
- Set `ProjectPriFileName` to match your module's assembly name
|
||||
- Move icon from `Resources/` to `Assets/<Module>/`
|
||||
|
||||
@@ -127,6 +127,74 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
|
||||
</Window>
|
||||
```
|
||||
|
||||
#### Recommended: Use WindowEx from WinUIEx
|
||||
|
||||
> **Tip:** Prefer `WinUIEx.WindowEx` over bare `Window`. It restores many WPF-like window properties directly in XAML, avoiding boilerplate code-behind for common windowing tasks.
|
||||
|
||||
```xml
|
||||
<!-- WinUI 3 with WindowEx (preferred in PowerToys) -->
|
||||
<winuiex:WindowEx
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
x:Class="MyApp.MainWindow"
|
||||
MinWidth="480"
|
||||
MinHeight="320"
|
||||
IsShownInSwitchers="True"
|
||||
IsTitleBarVisible="True">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
<Grid>
|
||||
...
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
```
|
||||
|
||||
Properties available on `WindowEx` that mirror WPF `Window`:
|
||||
|
||||
| WPF Window Property | WindowEx Property | Notes |
|
||||
|---------------------|-------------------|-------|
|
||||
| `MinWidth` / `MinHeight` | `MinWidth` / `MinHeight` | Set directly in XAML |
|
||||
| `Width` / `Height` | `Width` / `Height` | Initial window size |
|
||||
| `WindowState` | `WindowState` | Minimized, Maximized, Normal |
|
||||
| `Title` | `Title` or `x:Uid` | Window title |
|
||||
| `Icon` | Use `TitleBar.IconSource` | Via WinUI TitleBar control |
|
||||
| `ShowInTaskbar` | `IsShownInSwitchers` | Alt-Tab visibility |
|
||||
| `TopMost` | `IsAlwaysOnTop` | Always-on-top window |
|
||||
|
||||
NuGet: `WinUIEx` — already referenced by most PowerToys modules.
|
||||
|
||||
#### Recommended: Page-in-Window Architecture
|
||||
|
||||
> **Tip:** WinUI 3 `Window` is NOT a `FrameworkElement` — it does not support `Resources`, `DataContext`, `x:Bind`, or `VisualStateManager` directly. Place a `Page` as the Window's root content to regain these WPF-like capabilities.
|
||||
|
||||
```xml
|
||||
<!-- WinUI 3 — Window contains a Page for full FrameworkElement support -->
|
||||
<winuiex:WindowEx x:Class="MyApp.MainWindow"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
xmlns:views="using:MyApp.Views">
|
||||
<views:MainPage x:Name="mainPage" />
|
||||
</winuiex:WindowEx>
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- MainPage.xaml — has full FrameworkElement capabilities -->
|
||||
<Page x:Class="MyApp.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<Page.Resources>
|
||||
<!-- Resources work here (unlike on Window) -->
|
||||
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<!-- VisualStateManager works here (unlike on Window) -->
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
...
|
||||
</Grid>
|
||||
</Page>
|
||||
```
|
||||
|
||||
This is the standard pattern in PowerToys (e.g., FileLocksmith, EnvironmentVariables).
|
||||
|
||||
### App.xaml Resources
|
||||
|
||||
```xml
|
||||
@@ -150,6 +218,20 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### CommunityToolkit.WinUI — WPF Replacement Controls
|
||||
|
||||
> **Tip:** The `CommunityToolkit.WinUI` package provides many controls and helpers familiar to WPF developers that are missing from WinUI 3 out of the box. Before writing custom replacements, check whether CommunityToolkit already provides what you need.
|
||||
|
||||
Key packages (XAML namespace is `using:CommunityToolkit.WinUI.Controls` for the `Controls.*` family):
|
||||
- **`CommunityToolkit.WinUI.Controls.Primitives`** — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox`, `SwitchPresenter`
|
||||
- **`CommunityToolkit.WinUI.Controls.HeaderedControls`** — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView`
|
||||
- **`CommunityToolkit.WinUI.Controls.SettingsControls`** — `SettingsCard`, `SettingsExpander`
|
||||
- **`CommunityToolkit.WinUI.Controls.Sizers`** — `GridSplitter`, `PropertySizer`, `ContentSizer`
|
||||
- **`CommunityToolkit.WinUI.UI.Controls.DataGrid`** — legacy v7 `DataGrid` (no longer maintained); prefer [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) for new work
|
||||
- **`CommunityToolkit.WinUI.Converters`** — Common value converters (`BoolToVisibilityConverter`, `StringFormatConverter`, etc.)
|
||||
- **`CommunityToolkit.WinUI.Behaviors`** — XAML behaviors for animations and interactions
|
||||
- **`CommunityToolkit.WinUI.Extensions`** — Extension methods for WinUI types
|
||||
|
||||
### Common Control Replacements
|
||||
|
||||
```xml
|
||||
@@ -187,11 +269,135 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
|
||||
<!-- Handle Enter/Escape keys in code-behind if needed -->
|
||||
```
|
||||
|
||||
## No-Equivalent Patterns (Requires Architectural Rework)
|
||||
|
||||
These WPF features demand design changes, not find-and-replace. Read this section BEFORE attempting to migrate any file that uses these patterns.
|
||||
|
||||
### MultiBinding → x:Bind Function Binding
|
||||
|
||||
WinUI does not support `MultiBinding`. Replace with `x:Bind` function binding (most direct replacement), a computed ViewModel property, or multiple simple bindings.
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} {1}">
|
||||
<Binding Path="FirstName" />
|
||||
<Binding Path="LastName" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<TextBlock Text="{x:Bind local:Converters.FormatFullName(ViewModel.FirstName, ViewModel.LastName), Mode=OneWay}" />
|
||||
```
|
||||
|
||||
```csharp
|
||||
public static class Converters
|
||||
{
|
||||
public static string FormatFullName(string first, string last) => $"{first} {last}";
|
||||
}
|
||||
```
|
||||
|
||||
### Adorners → Context-Dependent Replacements
|
||||
|
||||
WPF's `AdornerLayer` has no WinUI equivalent. Choose replacement by use case:
|
||||
|
||||
| Adorner Use Case | WinUI 3 Replacement |
|
||||
|------------------|---------------------|
|
||||
| Validation indicators | `TeachingTip`, `InfoBar`, or InputValidation templates |
|
||||
| Resize handles | `Popup` positioned relative to target |
|
||||
| Drag preview | `DragItemsStarting` event with custom DragUI |
|
||||
| Overlay decorations | Canvas overlay or Popup layer |
|
||||
| Watermark / Placeholder | `TextBox.PlaceholderText` (built-in) |
|
||||
|
||||
### RoutedUICommand → ICommand / RelayCommand
|
||||
|
||||
WinUI does not support routed commands or `CommandBinding`. Replace with standard `ICommand` pattern:
|
||||
|
||||
```csharp
|
||||
// CommunityToolkit.Mvvm
|
||||
[RelayCommand(CanExecute = nameof(CanSave))]
|
||||
private void Save() { /* save logic */ }
|
||||
private bool CanSave() => IsDirty;
|
||||
```
|
||||
|
||||
WinUI 3 also provides `StandardUICommand` and `XamlUICommand` for pre-defined platform commands (Cut, Copy, Paste, Delete) with built-in icons and keyboard accelerators.
|
||||
|
||||
### Tunneling / Preview Events
|
||||
|
||||
WinUI has no tunneling event model. `PreviewMouseDown`, `PreviewKeyDown`, etc. do not exist.
|
||||
|
||||
- Replace with the bubbling equivalent (`PointerPressed`, `KeyDown`)
|
||||
- If you relied on tunneling to intercept events before children, restructure using the `Handled` property
|
||||
- For must-handle scenarios, use `AddHandler` with `handledEventsToo: true`:
|
||||
```csharp
|
||||
myElement.AddHandler(UIElement.PointerPressedEvent,
|
||||
new PointerEventHandler(OnPointerPressed), handledEventsToo: true);
|
||||
```
|
||||
|
||||
## Style and Template Changes
|
||||
|
||||
### Implicit Styles — Always Use BasedOn
|
||||
|
||||
> **Warning:** In WinUI 3, always use `BasedOn` when overriding default control styles. Without it, your style **replaces the entire default style** rather than extending it.
|
||||
|
||||
```xml
|
||||
<!-- WRONG — replaces entire default style, control may lose all visual appearance -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="Red" />
|
||||
</Style>
|
||||
|
||||
<!-- CORRECT — extends the default style -->
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
|
||||
<Setter Property="Background" Value="Red" />
|
||||
</Style>
|
||||
```
|
||||
|
||||
### Triggers → VisualStateManager
|
||||
|
||||
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported. Two replacement approaches:
|
||||
|
||||
#### Approach 1: StateTrigger (direct DataTrigger replacement — simpler)
|
||||
|
||||
Use this for data-driven state changes. This is the closest equivalent to WPF `DataTrigger`:
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||
<Setter Property="Background" Value="Green" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<Border x:Name="MyBorder">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="Active">
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind ViewModel.IsActive, Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MyBorder.Background" Value="Green" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
```
|
||||
|
||||
Note: `VisualStateManager` must be placed on a control inside the Window, NOT on the Window itself.
|
||||
|
||||
#### Approach 2: ControlTemplate (for property triggers like IsMouseOver)
|
||||
|
||||
Use this when replacing `<Trigger Property="IsMouseOver">` or similar control-state triggers:
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
@@ -200,9 +406,6 @@ WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="LightBlue"/>
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
```
|
||||
@@ -249,11 +452,42 @@ WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
| `Disabled` | `Disabled` |
|
||||
| `Pressed` | `Pressed` |
|
||||
|
||||
## Visibility.Hidden — No Equivalent
|
||||
|
||||
WinUI only has `Visible` and `Collapsed`. There is no `Hidden`.
|
||||
|
||||
| WPF | WinUI 3 | Behavior |
|
||||
|-----|---------|----------|
|
||||
| `Visibility.Visible` | `Visibility.Visible` | Rendered and occupies layout space |
|
||||
| `Visibility.Hidden` | **Not available** | Use `Opacity="0"` with `Visibility="Visible"` to hide but keep layout |
|
||||
| `Visibility.Collapsed` | `Visibility.Collapsed` | Not rendered, no layout space |
|
||||
|
||||
## Resource Dictionary Changes
|
||||
|
||||
### Window.Resources → Grid.Resources
|
||||
### XamlControlsResources Must Be First
|
||||
|
||||
WinUI 3 `Window` is NOT a `DependencyObject` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
|
||||
> **Warning:** `XamlControlsResources` must be the **first** merged dictionary in `App.xaml`. It provides the default Fluent styles. Omitting it gives you controls with no visual appearance. Resource paths use `ms-appx:///` instead of relative paths.
|
||||
|
||||
```xml
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- MUST be first -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Then your custom dictionaries -->
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### Window.Resources → Grid.Resources (or use Page)
|
||||
|
||||
WinUI 3 `Window` is NOT a `FrameworkElement` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
|
||||
|
||||
**Preferred approach:** Use the [Page-in-Window architecture](#recommended-page-in-window-architecture) described above. A `Page` inside the `Window` gives you full `FrameworkElement` capabilities (Resources, DataContext, x:Bind, VisualStateManager).
|
||||
|
||||
**Fallback** (for simple windows without a Page):
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
@@ -317,19 +551,25 @@ Both are available. Prefer `{x:Bind}` for compile-time safety and performance.
|
||||
| Performance | Reflection-based | Compiled |
|
||||
| Function binding | No | Yes |
|
||||
|
||||
### WPF-Specific Binding Features to Remove
|
||||
### Binding Differences from WPF
|
||||
|
||||
These WPF binding patterns behave differently in WinUI 3 — review on a case-by-case basis rather than mechanically removing.
|
||||
|
||||
```xml
|
||||
<!-- These WPF-only features must be removed or rewritten -->
|
||||
<!-- UpdateSourceTrigger: limited support in WinUI 3 -->
|
||||
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<!-- WinUI 3: UpdateSourceTrigger not needed; TextBox uses PropertyChanged by default -->
|
||||
<!-- WinUI 3: UpdateSourceTrigger=LostFocus does NOT exist; PropertyChanged is the TextBox default.
|
||||
Prefer x:Bind, which binds with PropertyChanged semantics by default for TwoWay. -->
|
||||
<TextBox Text="{x:Bind ViewModel.Value, Mode=TwoWay}" />
|
||||
|
||||
{Binding RelativeSource={RelativeSource Self}, ...}
|
||||
<!-- WinUI 3: Use x:Bind which binds to the page itself, or use ElementName -->
|
||||
<!-- RelativeSource: Self and TemplatedParent ARE supported in WinUI 3 -->
|
||||
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource Self}}" />
|
||||
<!-- Works in WinUI 3. FindAncestor mode is NOT supported — use ElementName, x:Bind,
|
||||
or the CommunityToolkit FrameworkElementExtensions.Ancestor attached property to reach ancestors. -->
|
||||
|
||||
<!-- {Binding} empty path: works in WinUI 3 (binds to the current DataContext) -->
|
||||
<ItemsControl ItemsSource="{Binding}" />
|
||||
<!-- WinUI 3: Must specify explicit path -->
|
||||
<!-- This is valid. Note: x:Bind requires an explicit path — there is no empty-path x:Bind. -->
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.Items}" />
|
||||
```
|
||||
|
||||
@@ -355,6 +595,64 @@ ExtendsContentIntoTitleBar="True" <!-- Set in code-behind -->
|
||||
| `IsHitTestVisible` | `IsHitTestVisible` | Same |
|
||||
| `TextBox.VerticalScrollBarVisibility` | `ScrollViewer.VerticalScrollBarVisibility` (attached) | Attached property |
|
||||
|
||||
## Complete Find-and-Replace Reference
|
||||
|
||||
Use this table for mechanical batch translation. Apply these rules consistently to every file.
|
||||
|
||||
### XAML Attribute Replacements
|
||||
|
||||
| Find | Replace With | Context |
|
||||
|------|-------------|---------|
|
||||
| `ContextMenu=` | `ContextFlyout=` | On any UIElement |
|
||||
| `{DynamicResource ` | `{ThemeResource ` | Theme-responsive references |
|
||||
| `{x:Static prefix:Resources.Key}` | `x:Uid="Key"` (with `.resw`) | Resource string — most common WPF case; mechanical `{x:Bind}` will NOT compile here |
|
||||
| `{x:Static prefix:Type.Member}` | `{x:Bind prefix:Type.Member}` | Static field/property reference (function binding) |
|
||||
| `Visibility="Hidden"` | `Visibility="Collapsed"` | Or use `Opacity="0"` for layout |
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Event handlers |
|
||||
| `MouseLeftButtonUp` | `PointerReleased` | Event handlers |
|
||||
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` + check `IsRightButtonPressed` |
|
||||
| `MouseRightButtonUp` | `PointerReleased` + check `IsRightButtonPressed` | No direct WinUI event; use `RightTapped` only for context-menu-open semantics |
|
||||
| `MouseEnter` | `PointerEntered` | Event handlers |
|
||||
| `MouseLeave` | `PointerExited` | Event handlers |
|
||||
| `MouseMove` | `PointerMoved` | Event handlers |
|
||||
| `MouseWheel` | `PointerWheelChanged` | Event handlers |
|
||||
| `MouseDoubleClick` | `DoubleTapped` | Event handlers |
|
||||
| `PreviewMouseDown` | `PointerPressed` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewMouseUp` | `PointerReleased` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewKeyDown` | `KeyDown` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewKeyUp` | `KeyUp` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewMouseWheel` | `PointerWheelChanged` | No tunneling in WinUI — remove Preview |
|
||||
| `Focusable="True"` | `IsTabStop="True"` | Focus behavior |
|
||||
| `Focusable="False"` | `IsTabStop="False"` | Focus behavior |
|
||||
| `TextWrapping="WrapWithOverflow"` | `TextWrapping="Wrap"` | TextBlock, TextBox |
|
||||
| `MediaElement` | `MediaPlayerElement` | Media playback |
|
||||
| `clr-namespace:` | `using:` | xmlns declarations |
|
||||
| `;assembly=` | (remove) | Assembly qualification not needed |
|
||||
|
||||
### Code-Behind Replacements
|
||||
|
||||
| Find | Replace With |
|
||||
|------|-------------|
|
||||
| `using System.Windows;` | `using Microsoft.UI.Xaml;` |
|
||||
| `using System.Windows.Controls;` | `using Microsoft.UI.Xaml.Controls;` |
|
||||
| `using System.Windows.Media;` | `using Microsoft.UI.Xaml.Media;` |
|
||||
| `using System.Windows.Data;` | `using Microsoft.UI.Xaml.Data;` |
|
||||
| `using System.Windows.Input;` | `using Microsoft.UI.Xaml.Input;` |
|
||||
| `using System.Windows.Threading;` | `using Microsoft.UI.Dispatching;` |
|
||||
| `using System.Windows.Shapes;` | `using Microsoft.UI.Xaml.Shapes;` |
|
||||
| `using System.Windows.Markup;` | `using Microsoft.UI.Xaml.Markup;` |
|
||||
| `using System.Windows.Automation;` | `using Microsoft.UI.Xaml.Automation;` |
|
||||
| `using System.Windows.Media.Animation;` | `using Microsoft.UI.Xaml.Media.Animation;` |
|
||||
| `using System.Windows.Documents;` | `using Microsoft.UI.Xaml.Documents;` |
|
||||
| `using System.Windows.Navigation;` | `using Microsoft.UI.Xaml.Navigation;` |
|
||||
| `Dispatcher.Invoke(` | `DispatcherQueue.TryEnqueue(` |
|
||||
| `Dispatcher.BeginInvoke(` | `DispatcherQueue.TryEnqueue(` |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` |
|
||||
| `MouseEventArgs` | `PointerRoutedEventArgs` |
|
||||
| `KeyEventArgs` | `KeyRoutedEventArgs` |
|
||||
| `RoutedUICommand` | `RelayCommand` (CommunityToolkit.Mvvm) |
|
||||
| `CommandBinding` | Remove; bind ICommand directly |
|
||||
|
||||
## XAML Formatting (XamlStyler)
|
||||
|
||||
After migration, run XamlStyler to normalize formatting:
|
||||
|
||||
11
.gitignore
vendored
@@ -19,6 +19,9 @@
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
!**/rnnoise/
|
||||
!**/rnnoise/x86/
|
||||
!**/rnnoise/x86/**
|
||||
ARM64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
@@ -370,3 +373,11 @@ installer/*/*.wxs.bk
|
||||
.squad-workstream
|
||||
.github/agents/**squad**.md
|
||||
.github/workflows/**squad**.yml
|
||||
|
||||
# vcpkg manifest mode installed packages
|
||||
vcpkg_installed/
|
||||
|
||||
deps/vcpkg/
|
||||
|
||||
# Superpowers-generated docs (specs, design, plans) — local-only, not committed
|
||||
docs/superpowers/
|
||||
|
||||
6
.gitmodules
vendored
@@ -1,6 +0,0 @@
|
||||
[submodule "deps/spdlog"]
|
||||
path = deps/spdlog
|
||||
url = https://github.com/gabime/spdlog.git
|
||||
[submodule "deps/expected-lite"]
|
||||
path = deps/expected-lite
|
||||
url = https://github.com/martinmoene/expected-lite.git
|
||||
@@ -212,6 +212,7 @@
|
||||
"WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll",
|
||||
|
||||
"PowerAccent.Core.dll",
|
||||
"PowerAccent.Common.dll",
|
||||
"PowerToys.PowerAccent.dll",
|
||||
"PowerToys.PowerAccent.exe",
|
||||
"PowerToys.PowerAccentModuleInterface.dll",
|
||||
@@ -391,6 +392,7 @@
|
||||
"WinUI3Apps\\Google.Apis.Auth.dll",
|
||||
"WinUI3Apps\\Google.Apis.Core.dll",
|
||||
"WinUI3Apps\\Google.GenAI.dll",
|
||||
"WinUI3Apps\\YamlDotNet.dll",
|
||||
|
||||
"boost_regex-vc143-mt-gd-x32-1_87.dll",
|
||||
"boost_regex-vc143-mt-gd-x64-1_87.dll",
|
||||
|
||||
@@ -104,6 +104,10 @@ extends:
|
||||
# Have msbuild use the release nuget config profile
|
||||
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=true
|
||||
beforeBuildSteps:
|
||||
# Install the Terrapin retrieval tool, which replaces vcpkg's download handler
|
||||
# to redirect it to a safe Microsoft-controlled location
|
||||
- template: .pipelines/v2/templates/steps-install-terrapin.yml@self
|
||||
|
||||
# Sets versions for all PowerToy created DLLs
|
||||
- pwsh: |-
|
||||
.pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment ''
|
||||
@@ -140,6 +144,10 @@ extends:
|
||||
signCertName: $(SigningSignCertName)
|
||||
useManagedIdentity: $(SigningUseManagedIdentity)
|
||||
clientId: $(SigningOriginalClientId)
|
||||
beforeBuildSteps:
|
||||
# Install the Terrapin retrieval tool, which replaces vcpkg's download handler
|
||||
# to redirect it to a safe Microsoft-controlled location
|
||||
- template: .pipelines/v2/templates/steps-install-terrapin.yml@self
|
||||
|
||||
- stage: Publish
|
||||
displayName: Publish
|
||||
|
||||
@@ -270,6 +270,34 @@ jobs:
|
||||
parameters:
|
||||
directory: $(build.sourcesdirectory)\src\modules\cmdpal
|
||||
|
||||
# --- vcpkg detection + binary cache --------------------------------------
|
||||
# PowerToys consumes spdlog (and, over time, other native deps) via vcpkg in
|
||||
# manifest mode. steps-install-vcpkg.yml prefers the vcpkg shipped with
|
||||
# Visual Studio (Microsoft.VisualStudio.Component.Vcpkg) and falls back to a
|
||||
# fresh clone of microsoft/vcpkg into deps/vcpkg if VS doesn't have it.
|
||||
# Either way it sets the VCPKG_ROOT pipeline variable; MSBuild integration
|
||||
# is wired globally from Cpp.Build.props (with vcpkg.targets in
|
||||
# Cpp.Build.targets) using the three-tier VcpkgRoot fallback
|
||||
# (env var > VS-shipped > deps/vcpkg runtime clone).
|
||||
#
|
||||
# Vcpkg's MSBuild integration runs `vcpkg install` once per project, so the
|
||||
# binary cache below saves ~3-5 minutes per triplet on cache hits.
|
||||
- template: .\steps-install-vcpkg.yml
|
||||
parameters:
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
|
||||
- ${{ if eq(parameters.enablePackageCaching, true) }}:
|
||||
- task: Cache@2
|
||||
displayName: 'Cache vcpkg binary archives'
|
||||
inputs:
|
||||
# Key on the inputs vcpkg uses to compute its package ABI: the manifest,
|
||||
# configuration, every overlay-port file, and the agent OS.
|
||||
key: '"vcpkg" | "$(Agent.OS)" | vcpkg.json | vcpkg-configuration.json | deps/vcpkg-overlays/**'
|
||||
restoreKeys: |
|
||||
"vcpkg" | "$(Agent.OS)"
|
||||
"vcpkg"
|
||||
path: $(LOCALAPPDATA)\vcpkg\archives
|
||||
|
||||
|
||||
|
||||
- ${{ parameters.beforeBuildSteps }}
|
||||
|
||||
@@ -15,6 +15,9 @@ parameters:
|
||||
- name: signingIdentity
|
||||
type: object
|
||||
default: {}
|
||||
- name: beforeBuildSteps
|
||||
type: stepList
|
||||
default: []
|
||||
|
||||
jobs:
|
||||
- job: "BuildSDK"
|
||||
@@ -45,6 +48,8 @@ jobs:
|
||||
parameters:
|
||||
directory: $(build.sourcesdirectory)\src\modules\cmdpal
|
||||
|
||||
- ${{ parameters.beforeBuildSteps }}
|
||||
|
||||
- pwsh: |-
|
||||
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "build" -IsAzurePipelineBuild
|
||||
displayName: Build SDK
|
||||
|
||||
6
.pipelines/v2/templates/steps-install-terrapin.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
steps:
|
||||
- pwsh: |-
|
||||
nuget install -source "https://microsoft.pkgs.visualstudio.com/Dart/_packaging/PowerToysDependencies/nuget/v3/index.json" TerrapinRetrievalTool -Prerelease -OutputDirectory _trt -Config "$(Build.SourcesDirectory)\.pipelines\release-nuget.config"
|
||||
$TerrapinRetrievalToolPath = (Get-Item _trt\TerrapinRetrievalTool.*\win-x64\TerrapinRetrievalTool.exe).FullName
|
||||
Write-Host "##vso[task.setvariable variable=X_VCPKG_ASSET_SOURCES]x-script,${TerrapinRetrievalToolPath} -b https://vcpkg.storage.devpackages.microsoft.io/artifacts/ -a true -u None -p {url} -s {sha512} -d {dst};x-block-origin"
|
||||
displayName: Set up the Terrapin Retrieval Tool (vcpkg cache)
|
||||
41
.pipelines/v2/templates/steps-install-vcpkg.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
# Adapted from microsoft/terminal build/pipelines/templates-v2/steps-install-vcpkg.yml.
|
||||
#
|
||||
# Detects vcpkg from (in order):
|
||||
# 1. The Visual Studio installation (Microsoft.VisualStudio.Component.Vcpkg,
|
||||
# declared in the repo-root .vsconfig).
|
||||
# 2. A local clone at deps/vcpkg, cloned and bootstrapped on demand.
|
||||
#
|
||||
# Sets the pipeline-scoped VCPKG_ROOT variable; the rest of the build
|
||||
# resolves vcpkg through it (see the three-tier VcpkgRoot fallback in
|
||||
# Cpp.Build.props). No repo-level vcpkg submodule required.
|
||||
parameters:
|
||||
- name: useVSPreview
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
steps:
|
||||
- pwsh: |-
|
||||
# vswhere -prerelease is opt-in via the useVSPreview parameter so CI on
|
||||
# stable VS doesn't accidentally pick up a Preview install when both
|
||||
# are present. Matches the existing useVSPreview plumbing for
|
||||
# verifyAndSetLatestVCToolsVersion.ps1.
|
||||
$vswhereArgs = @('-latest', '-requires', 'Microsoft.VisualStudio.Component.Vcpkg', '-property', 'installationPath')
|
||||
$useVSPreview = '${{ parameters.useVSPreview }}' -eq 'True'
|
||||
if ($useVSPreview) { $vswhereArgs = @('-prerelease') + $vswhereArgs }
|
||||
$VsInstallRoot = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vswhereArgs
|
||||
If ([String]::IsNullOrEmpty($VsInstallRoot)) {
|
||||
Remove-Item -Recurse -Force deps/vcpkg -ErrorAction:Ignore
|
||||
git clone https://github.com/microsoft/vcpkg deps/vcpkg
|
||||
if ($LASTEXITCODE -ne 0) { throw "git clone vcpkg failed (exit $LASTEXITCODE)" }
|
||||
Push-Location deps/vcpkg
|
||||
& ./bootstrap-vcpkg.bat -disableMetrics
|
||||
if ($LASTEXITCODE -ne 0) { Pop-Location; throw "bootstrap-vcpkg failed (exit $LASTEXITCODE)" }
|
||||
$VcpkgRoot = $PWD
|
||||
Pop-Location
|
||||
Write-Host "Using vcpkg from local checkout ($VcpkgRoot)"
|
||||
} Else {
|
||||
$VcpkgRoot = Join-Path $VsInstallRoot 'VC\vcpkg'
|
||||
Write-Host "Using vcpkg from Visual Studio installation ($VcpkgRoot)"
|
||||
}
|
||||
Write-Host "##vso[task.setvariable variable=VCPKG_ROOT]$VcpkgRoot"
|
||||
displayName: Detect VS vcpkg or bootstrap locally
|
||||
@@ -48,6 +48,11 @@ foreach ($csprojFile in $csprojFilesArray) {
|
||||
continue
|
||||
}
|
||||
|
||||
# The PowerAccent.Common project does not target WinRT, so skip it
|
||||
if ($csprojFile -like '*PowerAccent.Common.csproj') {
|
||||
continue
|
||||
}
|
||||
|
||||
$importExists = Test-ImportSharedCsWinRTProps -filePath $csprojFile
|
||||
if (!$importExists) {
|
||||
Write-Output "$csprojFile need to import 'Common.Dotnet.CsWinRT.props'."
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre",
|
||||
"Microsoft.VisualStudio.Component.VC.ATL",
|
||||
"Microsoft.VisualStudio.Component.VC.ATL.Spectre",
|
||||
"Microsoft.VisualStudio.Component.Vcpkg",
|
||||
"Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs"
|
||||
]
|
||||
}
|
||||
@@ -39,7 +39,8 @@
|
||||
<PropertyGroup>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
|
||||
<VcpkgEnabled>false</VcpkgEnabled>
|
||||
<!-- vcpkg.targets is imported via Cpp.Build.targets after Microsoft.Cpp.targets. -->
|
||||
<ForceImportAfterCppTargets>$(MSBuildThisFileDirectory)Cpp.Build.targets</ForceImportAfterCppTargets>
|
||||
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
|
||||
<ExternalIncludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath)</ExternalIncludePath>
|
||||
<!-- Enable control flow guard for C++ projects that don't consume any C++ files -->
|
||||
@@ -121,6 +122,48 @@
|
||||
<SpectreMitigation>Spectre</SpectreMitigation>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
vcpkg integration. Set globally and loaded before Microsoft.Cpp.props (via
|
||||
ForceImportBeforeCppProps) so that vcpkg.props' ClCompile hook is in place
|
||||
before the C++ targets run. VcpkgRoot is resolved via the same three-tier
|
||||
fallback used by microsoft/terminal (env var → VS-shipped → deps/vcpkg).
|
||||
-->
|
||||
<PropertyGroup Label="vcpkg">
|
||||
<VcpkgEnabled>true</VcpkgEnabled>
|
||||
<VcpkgEnableManifest>true</VcpkgEnableManifest>
|
||||
<VcpkgManifestEnabled>true</VcpkgManifestEnabled>
|
||||
<VcpkgManifestRoot>$(MSBuildThisFileDirectory)</VcpkgManifestRoot>
|
||||
<VcpkgOSTarget>windows</VcpkgOSTarget>
|
||||
<VcpkgUseStatic>true</VcpkgUseStatic>
|
||||
<!--
|
||||
Force VcpkgConfiguration to follow $(Configuration). Without this,
|
||||
vcpkg.props infers VcpkgConfiguration from $(UseDebugLibraries), which
|
||||
Microsoft.Cpp.Default.props has already defaulted to 'false' by the
|
||||
time vcpkg.props is imported here (the PowerToys-wide Debug override
|
||||
below runs LATER). That would silently link the Release-built spdlog
|
||||
into Debug consumers and trigger LNK2038 (MT/MTd, _ITERATOR_DEBUG_LEVEL).
|
||||
-->
|
||||
<VcpkgConfiguration>$(Configuration)</VcpkgConfiguration>
|
||||
<!-- vcpkg validates triplets case-sensitively; PowerToys uses ARM64 capital-case. -->
|
||||
<VcpkgPlatformTarget Condition="'$(Platform)' == 'ARM64'">arm64</VcpkgPlatformTarget>
|
||||
<VcpkgApplocalDeps>false</VcpkgApplocalDeps>
|
||||
<VcpkgInstalledDir>$(MSBuildThisFileDirectory)vcpkg_installed\$(Platform)\</VcpkgInstalledDir>
|
||||
<VcpkgRoot Condition="'$(VcpkgRoot)' == ''">$(VCPKG_ROOT)</VcpkgRoot>
|
||||
<VcpkgRoot Condition="'$(VcpkgRoot)' == '' and '$(VsInstallRoot)' != ''">$(VsInstallRoot)\VC\vcpkg</VcpkgRoot>
|
||||
<VcpkgRoot Condition="'$(VcpkgRoot)' == '' or !Exists('$(VcpkgRoot)\vcpkg.exe')">$(MSBuildThisFileDirectory)deps\vcpkg</VcpkgRoot>
|
||||
<CAExcludePath>$(CAExcludePath);$(VcpkgInstalledDir)</CAExcludePath>
|
||||
<VCPkgLocalAppDataDisabled>true</VCPkgLocalAppDataDisabled>
|
||||
</PropertyGroup>
|
||||
<!-- Fail fast with an actionable message instead of opaque C1083 spdlog/spdlog.h errors. -->
|
||||
<Target Name="PowerToysEnsureVcpkgAvailable"
|
||||
BeforeTargets="PrepareForBuild"
|
||||
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and '$(VcpkgEnabled)' == 'true' and !Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props')">
|
||||
<Error Text="PowerToys requires the 'vcpkg' Visual Studio component, but it was not found.%0A%0AOpen the Visual Studio Installer, click Modify on your VS install, search for 'vcpkg', enable 'C++ vcpkg package manager', and click Modify. (Visual Studio will also prompt you to install missing .vsconfig components when you open PowerToys.slnx.)%0A%0AIf you have vcpkg installed elsewhere, set the VCPKG_ROOT environment variable to its root before building.%0A%0ASearched: '$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props'" />
|
||||
</Target>
|
||||
<Import Project="$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props"
|
||||
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props')" />
|
||||
|
||||
|
||||
<!-- Debug/Release props -->
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
|
||||
16
Cpp.Build.targets
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<!--
|
||||
PowerToys global C++ post-targets. Wired in via
|
||||
<ForceImportAfterCppTargets> in Cpp.Build.props so MSBuild loads this
|
||||
file AFTER Microsoft.Cpp.targets for every .vcxproj.
|
||||
|
||||
Conditionally imports vcpkg.targets to hook ClCompile into vcpkg's
|
||||
VcpkgInstallManifestDependencies target so spdlog headers are
|
||||
auto-discovered on the include path and spdlog.lib is auto-linked.
|
||||
vcpkg.props is imported in Cpp.Build.props (before Microsoft.Cpp.props);
|
||||
vcpkg.targets needs the matching "after" hook here.
|
||||
-->
|
||||
<Import Project="$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.targets"
|
||||
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and '$(VcpkgEnabled)' == 'true' and Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.targets')" />
|
||||
</Project>
|
||||
@@ -41,21 +41,21 @@
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.102" />
|
||||
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.9.260303001" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.8" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.2.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.1-preview.1.25571.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="0.3.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.71.0" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.71.0" />
|
||||
@@ -66,9 +66,9 @@
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3719.77" />
|
||||
<!-- Package Microsoft.Win32.SystemEvents 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.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
|
||||
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.7" />
|
||||
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.8" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.269" />
|
||||
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
|
||||
<!--
|
||||
@@ -78,10 +78,10 @@
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.250325.1"/>
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.0.20" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.0.185" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.0.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.1.0" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.2.3" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||
@@ -106,28 +106,28 @@
|
||||
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
|
||||
<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="10.0.7" />
|
||||
<PackageVersion Include="System.CodeDom" Version="10.0.8" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
<PackageVersion Include="System.ComponentModel.Composition" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="10.0.7" />
|
||||
<PackageVersion Include="System.ComponentModel.Composition" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Data.OleDb" Version="10.0.8" />
|
||||
<!-- Package System.Diagnostics.EventLog 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.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.8" />
|
||||
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
|
||||
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="10.0.8" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="10.0.8" />
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
|
||||
<PackageVersion Include="System.Management" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Management" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Numerics.Tensors" Version="10.0.2" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="10.0.7" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.7" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="10.0.8" />
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.8" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<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/updating/UnitTests/UpdatingUnitTests.vcxproj" Id="a1b2c3d4-e5f6-7890-abcd-ef1234567890" />
|
||||
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
|
||||
</Folder>
|
||||
<Folder Name="/common/interop/">
|
||||
@@ -67,10 +68,7 @@
|
||||
<Project Path="src/common/interop/PowerToys.Interop.vcxproj" Id="f055103b-f80b-4d0c-bf48-057c55620033" />
|
||||
</Folder>
|
||||
<Folder Name="/common/log/">
|
||||
<Project Path="src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd">
|
||||
<BuildDependency Project="src/logging/logging.vcxproj" />
|
||||
</Project>
|
||||
<Project Path="src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f" />
|
||||
<Project Path="src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd" />
|
||||
</Folder>
|
||||
<Folder Name="/common/notifications/">
|
||||
<Project Path="src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj" Id="0b593a6c-4143-4337-860e-db5710fb87db" />
|
||||
@@ -801,6 +799,14 @@
|
||||
<Project Path="src/modules/peek/peek/peek.vcxproj" Id="a1425b53-3d61-4679-8623-e64a0d3d0a48" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/PowerAccent/">
|
||||
<Project Path="src/modules/poweraccent/PowerAccent.Common.UnitTests/PowerAccent.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/poweraccent/PowerAccent.Common/PowerAccent.Common.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
@@ -1133,3 +1139,5 @@
|
||||
<Project Path="src/Update/PowerToys.Update.vcxproj" Id="44ce9ae1-4390-42c5-bacc-0fd6b40aa203" />
|
||||
<Project Path="tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj" Id="64a80062-4d8b-4229-8a38-dfa1d7497749" />
|
||||
</Solution>
|
||||
|
||||
|
||||
|
||||
1
deps/expected-lite
vendored
7
deps/expected.props
vendored
@@ -1,7 +0,0 @@
|
||||
<Project>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)expected-lite\include\nonstd\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
1
deps/spdlog
vendored
94
deps/spdlog-msvc-fix/include/spdlog-msvc-fix.h
vendored
@@ -1,94 +0,0 @@
|
||||
// spdlog-msvc-fix.h
|
||||
//
|
||||
// Workaround for MSVC 14.51 (compiler version 19.51, _MSC_VER >= 1951) removing
|
||||
// stdext::checked_array_iterator. Force-included for all spdlog consumers via
|
||||
// deps/spdlog.props, because spdlog v1.8.5's bundled fmt format.h(357) still
|
||||
// references this type inside #if defined(_SECURE_SCL) && _SECURE_SCL -- a
|
||||
// branch entered in Debug builds where _ITERATOR_DEBUG_LEVEL > 0.
|
||||
//
|
||||
// On MSVC 14.50 and earlier, the type still exists in <iterator>, so this shim
|
||||
// is a no-op via the _MSC_VER guard. On MSVC 14.51+, it provides a minimal
|
||||
// pointer-backed substitute that satisfies the bundled fmt's usage:
|
||||
//
|
||||
// template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
|
||||
// template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {
|
||||
// return {p, size};
|
||||
// }
|
||||
// ... return make_checked(get_data(c) + size, n);
|
||||
//
|
||||
// When deps/spdlog is bumped past v1.14 (which ships fmt 10.2 and drops this
|
||||
// dependency), this shim and its <ForcedIncludeFiles> entry in deps/spdlog.props
|
||||
// can be deleted.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1951
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace stdext
|
||||
{
|
||||
template <typename _Ptr>
|
||||
class checked_array_iterator
|
||||
{
|
||||
_Ptr _Myarray = nullptr;
|
||||
std::size_t _Mysize = 0;
|
||||
std::size_t _Myindex = 0;
|
||||
|
||||
public:
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using value_type = std::remove_cv_t<std::remove_pointer_t<_Ptr>>;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = _Ptr;
|
||||
using reference = std::remove_pointer_t<_Ptr>&;
|
||||
|
||||
constexpr checked_array_iterator() = default;
|
||||
|
||||
constexpr checked_array_iterator(_Ptr arr, std::size_t size, std::size_t idx = 0) noexcept
|
||||
: _Myarray(arr), _Mysize(size), _Myindex(idx)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr reference operator*() const noexcept { return _Myarray[_Myindex]; }
|
||||
constexpr pointer operator->() const noexcept { return _Myarray + _Myindex; }
|
||||
constexpr reference operator[](difference_type n) const noexcept
|
||||
{
|
||||
return _Myarray[_Myindex + static_cast<std::size_t>(n)];
|
||||
}
|
||||
|
||||
constexpr checked_array_iterator& operator++() noexcept { ++_Myindex; return *this; }
|
||||
constexpr checked_array_iterator operator++(int) noexcept { auto t = *this; ++_Myindex; return t; }
|
||||
constexpr checked_array_iterator& operator--() noexcept { --_Myindex; return *this; }
|
||||
constexpr checked_array_iterator operator--(int) noexcept { auto t = *this; --_Myindex; return t; }
|
||||
|
||||
constexpr checked_array_iterator& operator+=(difference_type n) noexcept
|
||||
{
|
||||
_Myindex = static_cast<std::size_t>(static_cast<difference_type>(_Myindex) + n);
|
||||
return *this;
|
||||
}
|
||||
constexpr checked_array_iterator& operator-=(difference_type n) noexcept
|
||||
{
|
||||
_Myindex = static_cast<std::size_t>(static_cast<difference_type>(_Myindex) - n);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend constexpr checked_array_iterator operator+(checked_array_iterator it, difference_type n) noexcept { it += n; return it; }
|
||||
friend constexpr checked_array_iterator operator+(difference_type n, checked_array_iterator it) noexcept { return it + n; }
|
||||
friend constexpr checked_array_iterator operator-(checked_array_iterator it, difference_type n) noexcept { it -= n; return it; }
|
||||
friend constexpr difference_type operator-(checked_array_iterator a, checked_array_iterator b) noexcept
|
||||
{
|
||||
return static_cast<difference_type>(a._Myindex) - static_cast<difference_type>(b._Myindex);
|
||||
}
|
||||
|
||||
friend constexpr bool operator==(checked_array_iterator a, checked_array_iterator b) noexcept { return a._Myindex == b._Myindex; }
|
||||
friend constexpr bool operator!=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(a == b); }
|
||||
friend constexpr bool operator<(checked_array_iterator a, checked_array_iterator b) noexcept { return a._Myindex < b._Myindex; }
|
||||
friend constexpr bool operator>(checked_array_iterator a, checked_array_iterator b) noexcept { return b < a; }
|
||||
friend constexpr bool operator<=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(b < a); }
|
||||
friend constexpr bool operator>=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(a < b); }
|
||||
};
|
||||
} // namespace stdext
|
||||
|
||||
#endif // __cplusplus && _MSC_VER >= 1951
|
||||
19
deps/spdlog.props
vendored
@@ -1,9 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)spdlog\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ForcedIncludeFiles>$(MSBuildThisFileDirectory)spdlog-msvc-fix\include\spdlog-msvc-fix.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<!--
|
||||
SPDLOG_* preprocessor defines for spdlog consumers. The actual vcpkg
|
||||
integration (VcpkgEnabled, VcpkgRoot, triplet, manifest install) lives
|
||||
in Cpp.Build.props; this file just carries the defines that match how
|
||||
the pre-vcpkg in-tree build was configured.
|
||||
-->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
|
||||
16
deps/vcpkg-overlays/spdlog/msvc-14.51-stdext-checked-array-iterator.patch
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
--- a/include/spdlog/fmt/bundled/format.h
|
||||
+++ b/include/spdlog/fmt/bundled/format.h
|
||||
@@ -354,7 +354,12 @@ inline typename Container::value_type* get_data(Container& c) {
|
||||
return c.data();
|
||||
}
|
||||
|
||||
-#if defined(_SECURE_SCL) && _SECURE_SCL
|
||||
+// PowerToys: stdext::checked_array_iterator was deprecated in VS 2019 16.10
|
||||
+// and removed entirely in MSVC 14.51 (compiler 19.51, _MSC_VER >= 1951;
|
||||
+// see microsoft/STL STL4043). Skip the broken branch on those toolsets so the
|
||||
+// pointer-based fallback below is used instead. Drop this guard once
|
||||
+// deps/spdlog is bumped past v1.14 (which ships fmt 10.2 and removes this code).
|
||||
+#if defined(_SECURE_SCL) && _SECURE_SCL && (!defined(_MSC_VER) || _MSC_VER < 1951)
|
||||
// Make a checked iterator to avoid MSVC warnings.
|
||||
template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
|
||||
template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {
|
||||
43
deps/vcpkg-overlays/spdlog/portfile.cmake
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# PowerToys overlay port for spdlog.
|
||||
#
|
||||
# Pinned to the same git commit that the deleted deps/spdlog submodule pointed
|
||||
# at, so this is a 1:1 submodule->vcpkg migration with no version change
|
||||
# (per the maintainer guidance: convert one submodule at a time, atomic
|
||||
# commit, don't also bump the version).
|
||||
#
|
||||
# A single hunk patch works around MSVC 14.51 STL4043 (removal of
|
||||
# stdext::checked_array_iterator) in spdlog's bundled fmt 7. Drop this overlay
|
||||
# (and switch to upstream vcpkg's spdlog port) once PowerToys bumps spdlog
|
||||
# past v1.14, which ships fmt 10.2 and removes the affected code path.
|
||||
|
||||
vcpkg_from_github(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
REPO gabime/spdlog
|
||||
REF 616866fcf40340ea25a8f218369bad810ef58e72
|
||||
SHA512 2076c527c7768627e6856b2f7ef663b185fd6251894cffd9299203d00f3d2de5696461060442dd72b96c9d3f0fd27f7f63ad2edfdf295e9b06c5fac6d6212faf
|
||||
HEAD_REF v1.x
|
||||
PATCHES
|
||||
msvc-14.51-stdext-checked-array-iterator.patch
|
||||
)
|
||||
|
||||
vcpkg_cmake_configure(
|
||||
SOURCE_PATH "${SOURCE_PATH}"
|
||||
OPTIONS
|
||||
-DSPDLOG_BUILD_EXAMPLE=OFF
|
||||
-DSPDLOG_BUILD_TESTS=OFF
|
||||
-DSPDLOG_BUILD_BENCH=OFF
|
||||
-DSPDLOG_FMT_EXTERNAL=OFF
|
||||
-DSPDLOG_WCHAR_SUPPORT=ON
|
||||
-DSPDLOG_WCHAR_FILENAMES=ON
|
||||
-DSPDLOG_NO_EXCEPTIONS=OFF
|
||||
-DSPDLOG_BUILD_SHARED=OFF
|
||||
)
|
||||
|
||||
vcpkg_cmake_install()
|
||||
vcpkg_cmake_config_fixup(PACKAGE_NAME spdlog CONFIG_PATH lib/cmake/spdlog)
|
||||
vcpkg_fixup_pkgconfig()
|
||||
vcpkg_copy_pdbs()
|
||||
|
||||
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
|
||||
|
||||
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")
|
||||
18
deps/vcpkg-overlays/spdlog/vcpkg.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "spdlog",
|
||||
"version-string": "1.8.5-pt-616866fc",
|
||||
"port-version": 0,
|
||||
"description": "Very fast, header-only/compiled, C++ logging library. PowerToys overlay pinned to gabime/spdlog@616866fc (the exact submodule commit before this migration), with a single-hunk patch that works around MSVC 14.51 removing stdext::checked_array_iterator (STL4043).",
|
||||
"homepage": "https://github.com/gabime/spdlog",
|
||||
"license": "MIT",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "vcpkg-cmake",
|
||||
"host": true
|
||||
},
|
||||
{
|
||||
"name": "vcpkg-cmake-config",
|
||||
"host": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -97,6 +97,10 @@ The Shell Process Debugging Tool is a Visual Studio extension that helps debug m
|
||||
- Check Event Viewer for application crashes related to `PowerToys.Settings.exe`
|
||||
- Crash dumps can be obtained from Event Viewer
|
||||
|
||||
### Debugging Command Palette
|
||||
Command Palette can be easily debugged using the solution filter in `src/modules/cmdpal/Command Palette.slnf`. This will open Command Palette as its own Visual Studio solution that can be run and debugged directly in Visual Studio without the need for the Shell Process Debugging Tool.
|
||||
|
||||
|
||||
## Troubleshooting Build Errors
|
||||
|
||||
### Missing Image Files or Corrupted Build State
|
||||
|
||||
|
Before Width: | Height: | Size: 275 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 134 KiB |
@@ -87,7 +87,7 @@ Per Application/Package one or more Keyboard manifests can be declared. Every ma
|
||||
<details>
|
||||
<summary><b>WindowFilter</b> - The filter of window processes to which the shortcuts apply to</summary>
|
||||
|
||||
This field declares for which process name the shortcuts should be showed (To rephrase: For which processes the shortcut will have an effect if pressed). You can use an asterisk to leave out a certain part. For example `*.PowerToys.*.exe` targets all PowerToys processes and `*` apply to any process.
|
||||
This field declares for which process name the shortcuts should be shown (To rephrase: For which processes the shortcut will have an effect if pressed). The value can be either an exact process executable name, for example `explorer.exe` or `chrome.exe`, or a single asterisk (`*`) to apply to any process. No other wildcard patterns are supported by this specification.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -79,3 +79,4 @@ Below are community created plugins that target a website or software. They are
|
||||
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
|
||||
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
|
||||
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |
|
||||
| [DiskAnalyzer](https://github.com/valley-soft/powertoys-diskanalyzer) | [ValleySoft](https://github.com/valley-soft) | Scan folders, find the largest files, and view drive space usage. |
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
</Project>
|
||||
<Project Path="../src/common/Telemetry/EtwTrace/EtwTrace.vcxproj" Id="8f021b46-362b-485c-bfba-ccf83e820cbd" />
|
||||
<Project Path="../src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
|
||||
<Project Path="../src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f">
|
||||
<Build Solution="Debug|ARM64" Project="false" />
|
||||
</Project>
|
||||
<Project Path="PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj" Id="b3a354b0-1e54-4b55-a962-fb5af9330c19">
|
||||
<Build Solution="Debug|ARM64" Project="false" />
|
||||
</Project>
|
||||
|
||||
@@ -4,15 +4,23 @@
|
||||
|
||||
<?define ShortcutGuideAssetsFiles=?>
|
||||
<?define ShortcutGuideAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\?>
|
||||
<?define ShortcutGuideManifestsFiles=?>
|
||||
<?define ShortcutGuideManifestsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\Manifests\?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="WinUI3AppsAssetsFolder">
|
||||
<Directory Id="ShortcutGuideAssetsFolder" Name="ShortcutGuide" />
|
||||
<Directory Id="ShortcutGuideAssetsFolder" Name="ShortcutGuide">
|
||||
<Directory Id="ShortcutGuideManifestsFolder" Name="Manifests" />
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="ShortcutGuideAssetsFolder" FileSource="$(var.ShortcutGuideAssetsFilesPath)">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--ShortcutGuideAssetsFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="ShortcutGuideManifestsFolder" FileSource="$(var.ShortcutGuideManifestsFilesPath)">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--ShortcutGuideManifestsFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- Shortcut guide -->
|
||||
<ComponentGroup Id="ShortcutGuideComponentGroup" >
|
||||
@@ -22,6 +30,12 @@
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveFolderShortcutGuideAssetsInstallFolder" Directory="ShortcutGuideAssetsFolder" On="uninstall"/>
|
||||
</Component>
|
||||
<Component Id="RemoveShortcutGuideManifestsFolder" Guid="F47E2C3A-8D91-4B6F-A2E5-9C8D7F6A1B3E" Directory="ShortcutGuideManifestsFolder" >
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveShortcutGuideManifestsFolder" Value="" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveFolderShortcutGuideManifestsInstallFolder" Directory="ShortcutGuideManifestsFolder" On="uninstall"/>
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
</Fragment>
|
||||
|
||||
@@ -28,7 +28,7 @@ Function Generate-FileList() {
|
||||
|
||||
$fileExclusionList = @("*.pdb", "*.lastcodeanalysissucceeded", "createdump.exe", "powertoys.exe")
|
||||
|
||||
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri")
|
||||
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri", "*.yml")
|
||||
|
||||
# MFC DLLs leak into the output via WindowsAppSDKSelfContained but no PowerToys binary imports them.
|
||||
# Verified with dumpbin /dependents across all 2176 binaries — zero consumers.
|
||||
@@ -112,6 +112,8 @@ Function Generate-FileComponents() {
|
||||
|
||||
foreach ($file in $fileList) {
|
||||
$fileTmp = $file -replace "-", "_"
|
||||
$fileTmp = $fileTmp -replace "[^A-Za-z0-9_.]", "_"
|
||||
if ($fileTmp -match "^[^A-Za-z_]") { $fileTmp = "_$fileTmp" }
|
||||
$componentDefs +=
|
||||
@"
|
||||
<File Id="$($fileListName)_File_$($fileTmp)" Source="`$(var.$($fileListName)Path)\$($file)" />`r`n
|
||||
@@ -397,8 +399,24 @@ Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePat
|
||||
## Plugins
|
||||
|
||||
#ShortcutGuide
|
||||
# Ensure manifest yml files are in the build output (the Build target's CopyToOutputDirectory
|
||||
# may not run reliably under -graph mode in solution builds).
|
||||
$sgManifestsSrc = "$PSScriptRoot..\..\..\src\modules\ShortcutGuide\ShortcutGuide.Ui\Assets\ShortcutGuide\Manifests"
|
||||
$sgManifestsDst = "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\Manifests"
|
||||
Write-Host "ShortcutGuide manifests: src=$sgManifestsSrc exists=$(Test-Path $sgManifestsSrc)"
|
||||
Write-Host "ShortcutGuide manifests: dst=$sgManifestsDst exists=$(Test-Path $sgManifestsDst)"
|
||||
if (Test-Path $sgManifestsSrc) {
|
||||
New-Item -Path $sgManifestsDst -ItemType Directory -Force | Out-Null
|
||||
Copy-Item "$sgManifestsSrc\*.yml" -Destination $sgManifestsDst -Force
|
||||
$copied = (Get-ChildItem "$sgManifestsDst\*.yml" -ErrorAction SilentlyContinue).Count
|
||||
Write-Host "ShortcutGuide manifests: copied $copied yml files to build output"
|
||||
} else {
|
||||
Write-Host "WARNING: ShortcutGuide manifest source not found at $sgManifestsSrc"
|
||||
}
|
||||
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideAssetsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\"
|
||||
Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot
|
||||
Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
|
||||
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideManifestsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\Manifests\"
|
||||
Generate-FileComponents -fileListName "ShortcutGuideManifestsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
|
||||
|
||||
#Settings
|
||||
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\"
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)deps\expected.props" />
|
||||
<PropertyGroup>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <common/updating/updating.h>
|
||||
#include <common/updating/updateState.h>
|
||||
#include <common/updating/installer.h>
|
||||
#include <common/updating/configBackup.h>
|
||||
#include <common/updating/updateLifecycle.h>
|
||||
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/utils/HttpClient.h>
|
||||
@@ -21,6 +23,8 @@
|
||||
#include <common/utils/resources.h>
|
||||
#include <common/utils/timeutil.h>
|
||||
|
||||
#include <wil/resource.h>
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
|
||||
#include <common/logger/logger.h>
|
||||
@@ -36,17 +40,59 @@ using namespace cmdArg;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
void CleanupStaleTempUpdaters()
|
||||
{
|
||||
// Remove orphaned PowerToys.Update.*.exe files from previous runs
|
||||
try
|
||||
{
|
||||
std::error_code ec;
|
||||
const auto tempDir = fs::temp_directory_path();
|
||||
for (const auto& entry : fs::directory_iterator(tempDir, ec))
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!entry.is_regular_file())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto filename = entry.path().filename().wstring();
|
||||
if (filename.starts_with(L"PowerToys.Update.") && filename.ends_with(L".exe"))
|
||||
{
|
||||
// Skip our own file (current PID)
|
||||
const auto ownFilename = L"PowerToys.Update." + std::to_wstring(GetCurrentProcessId()) + L".exe";
|
||||
if (filename == ownFilename)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fs::remove(entry.path(), ec);
|
||||
// Failure to delete is expected if another updater is still running
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Best-effort cleanup; don't block the update
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<fs::path> CopySelfToTempDir()
|
||||
{
|
||||
CleanupStaleTempUpdaters();
|
||||
|
||||
std::error_code error;
|
||||
auto dst_path = fs::temp_directory_path() / "PowerToys.Update.exe";
|
||||
auto dst_path = fs::temp_directory_path() / (L"PowerToys.Update." + std::to_wstring(GetCurrentProcessId()) + L".exe");
|
||||
fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error);
|
||||
if (error)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::move(dst_path);
|
||||
return dst_path;
|
||||
}
|
||||
|
||||
std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
@@ -57,34 +103,9 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
|
||||
auto state = UpdateState::read();
|
||||
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
if (std::holds_alternative<version_up_to_date>(*new_version_info))
|
||||
{
|
||||
isUpToDate = true;
|
||||
Logger::error("Invoked with -update_now argument, but no update was available");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
|
||||
{
|
||||
if (!new_version_info)
|
||||
{
|
||||
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Cleanup old updates before downloading the latest
|
||||
updating::cleanup_updates();
|
||||
|
||||
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
|
||||
if (!downloaded_installer)
|
||||
{
|
||||
Logger::error("Couldn't download new installer");
|
||||
}
|
||||
|
||||
return downloaded_installer;
|
||||
}
|
||||
else if (state.state == UpdateState::readyToInstall)
|
||||
// Handle readyToInstall first — the installer is already on disk,
|
||||
// so we don't need a GitHub API call (which may fail if offline).
|
||||
if (state.state == UpdateState::readyToInstall)
|
||||
{
|
||||
fs::path installer{ get_pending_updates_path() / state.downloadedInstallerFilename };
|
||||
if (fs::is_regular_file(installer))
|
||||
@@ -97,12 +118,44 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else if (state.state == UpdateState::upToDate)
|
||||
|
||||
if (state.state == UpdateState::upToDate)
|
||||
{
|
||||
isUpToDate = true;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
|
||||
// Check for error BEFORE dereferencing — the old code crashed here
|
||||
// when GitHub API was unreachable (new_version_info held an error string).
|
||||
if (!new_version_info)
|
||||
{
|
||||
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<version_up_to_date>(*new_version_info))
|
||||
{
|
||||
isUpToDate = true;
|
||||
Logger::error("Invoked with -update_now argument, but no update was available");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
|
||||
{
|
||||
// Cleanup old updates before downloading the latest
|
||||
updating::cleanup_updates();
|
||||
|
||||
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
|
||||
if (!downloaded_installer)
|
||||
{
|
||||
Logger::error("Couldn't download new installer");
|
||||
}
|
||||
|
||||
return downloaded_installer;
|
||||
}
|
||||
|
||||
Logger::error("Invoked with -update_now argument, but update state was invalid");
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -116,13 +169,32 @@ bool InstallNewVersionStage1(fs::path installer)
|
||||
|
||||
if (pt_main_window != nullptr)
|
||||
{
|
||||
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
|
||||
// Get the process that owns the tray window so we can wait for it to exit
|
||||
DWORD ptProcessId = 0;
|
||||
GetWindowThreadProcessId(pt_main_window, &ptProcessId);
|
||||
|
||||
// Use SendMessageTimeoutW to avoid blocking indefinitely if the
|
||||
// tray window thread is hung or unresponsive.
|
||||
DWORD_PTR result = 0;
|
||||
SendMessageTimeoutW(pt_main_window, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 5000, &result);
|
||||
|
||||
// Wait for PT to actually exit before launching installer.
|
||||
// Without this, the installer may find PT files locked.
|
||||
if (ptProcessId != 0)
|
||||
{
|
||||
wil::unique_handle ptProcess{ OpenProcess(SYNCHRONIZE, FALSE, ptProcessId) };
|
||||
if (ptProcess)
|
||||
{
|
||||
WaitForSingleObject(ptProcess.get(), 10000); // 10 second timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2 };
|
||||
arguments += L" \"";
|
||||
arguments += installer.c_str();
|
||||
arguments += L"\"";
|
||||
// Pass the install directory so Stage 2 can relaunch PowerToys after install
|
||||
const std::wstring installDir = get_module_folderpath();
|
||||
|
||||
std::wstring arguments = updating::BuildStage2Arguments(
|
||||
UPDATE_NOW_LAUNCH_STAGE2, installer, fs::path(installDir));
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
|
||||
sei.lpFile = copy_in_temp->c_str();
|
||||
@@ -190,9 +262,16 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &nArgs);
|
||||
if (!args || nArgs < 2)
|
||||
{
|
||||
if (args)
|
||||
{
|
||||
LocalFree(args);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// D3 fix: ensure args is freed on all exit paths
|
||||
auto freeArgs = wil::scope_exit([&] { LocalFree(args); });
|
||||
|
||||
std::wstring_view action{ args[1] };
|
||||
|
||||
std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location());
|
||||
@@ -201,6 +280,11 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
|
||||
if (action == UPDATE_NOW_LAUNCH_STAGE1)
|
||||
{
|
||||
// Backup config files before the update to protect against corruption
|
||||
Logger::info("Backing up config files before update");
|
||||
auto backupResult = updating::BackupConfigFiles(fs::path(PTSettingsHelper::get_root_save_folder_location()));
|
||||
Logger::info("Config backup complete: {} files backed up, {} errors", backupResult.filesBackedUp, backupResult.errors);
|
||||
|
||||
bool isUpToDate = false;
|
||||
auto installerPath = ObtainInstaller(isUpToDate);
|
||||
bool failed = !installerPath.has_value();
|
||||
@@ -217,6 +301,12 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
}
|
||||
else if (action == UPDATE_NOW_LAUNCH_STAGE2)
|
||||
{
|
||||
if (nArgs < 3)
|
||||
{
|
||||
Logger::error("Stage 2 invoked without installer path argument");
|
||||
return 1;
|
||||
}
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
const bool failed = !InstallNewVersionStage2(args[2]);
|
||||
if (failed)
|
||||
@@ -227,6 +317,39 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
state.state = UpdateState::errorDownloading;
|
||||
});
|
||||
}
|
||||
|
||||
// Always check for corrupted configs after Stage 2, regardless
|
||||
// of install success/failure. A failed install may still corrupt configs.
|
||||
Logger::info("Checking for corrupted config files after update");
|
||||
auto restoreResult = updating::RestoreCorruptedConfigs(fs::path(PTSettingsHelper::get_root_save_folder_location()));
|
||||
Logger::info("Config restore check complete: {}/{} files restored, {} errors",
|
||||
restoreResult.filesRestored, restoreResult.filesChecked, restoreResult.errors);
|
||||
|
||||
if (!failed)
|
||||
{
|
||||
// Relaunch PowerToys from the install directory
|
||||
if (updating::CanRelaunchAfterUpdate(nArgs))
|
||||
{
|
||||
std::wstring ptExePath = updating::BuildPowerToysExePath(args[3]);
|
||||
|
||||
Logger::info(L"Relaunching PowerToys after update: {}", ptExePath);
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
|
||||
sei.lpFile = ptExePath.c_str();
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = UPDATE_REPORT_SUCCESS;
|
||||
|
||||
if (!ShellExecuteExW(&sei))
|
||||
{
|
||||
Logger::error(L"Failed to relaunch PowerToys after update");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn("Install directory not provided to Stage 2 - cannot relaunch PowerToys");
|
||||
}
|
||||
}
|
||||
return failed;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)deps\expected.props" />
|
||||
<PropertyGroup>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
|
||||
<PreprocessorDefinitions>_WINRT_DLL;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<!-- TODO: _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS: suppress VS 2026 STL hard error for <experimental/coroutine> until the code is ported to <coroutine> -->
|
||||
<PreprocessorDefinitions>_WINRT_DLL;WINRT_LEAN_AND_MEAN;_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>../../..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
|
||||
</ClCompile>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\deps\spdlog\include;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp23</LanguageStandard>
|
||||
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_HEADER_ONLY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
|
||||
@@ -70,9 +70,6 @@
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\logging\logging.vcxproj">
|
||||
<Project>{7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\version\version.vcxproj">
|
||||
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
679
src/common/updating/UnitTests/UpdatingTests.cpp
Normal file
@@ -0,0 +1,679 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <common/updating/configBackup.h>
|
||||
#include <common/updating/updateLifecycle.h>
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace UpdatingUnitTests
|
||||
{
|
||||
// Helper to create a temp directory for test isolation.
|
||||
// Each instance gets a unique subdirectory to prevent test interference.
|
||||
class TempDir
|
||||
{
|
||||
public:
|
||||
TempDir()
|
||||
{
|
||||
wchar_t tempPath[MAX_PATH + 1];
|
||||
GetTempPathW(MAX_PATH, tempPath);
|
||||
static std::atomic<int> counter{0};
|
||||
m_path = fs::path(tempPath) / (L"PowerToysUpdateTests_" + std::to_wstring(counter++));
|
||||
|
||||
// Ensure clean state
|
||||
std::error_code ec;
|
||||
fs::remove_all(m_path, ec);
|
||||
fs::create_directories(m_path, ec);
|
||||
}
|
||||
|
||||
~TempDir()
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::remove_all(m_path, ec);
|
||||
}
|
||||
|
||||
const fs::path& path() const { return m_path; }
|
||||
|
||||
// Write a file with the given content
|
||||
void WriteFile(const fs::path& relativePath, const std::string& content)
|
||||
{
|
||||
auto fullPath = m_path / relativePath;
|
||||
fs::create_directories(fullPath.parent_path());
|
||||
std::ofstream file(fullPath, std::ios::binary);
|
||||
file.write(content.data(), content.size());
|
||||
}
|
||||
|
||||
// Write a file with raw bytes (including null bytes for corruption testing)
|
||||
void WriteFileBytes(const fs::path& relativePath, const std::vector<char>& bytes)
|
||||
{
|
||||
auto fullPath = m_path / relativePath;
|
||||
fs::create_directories(fullPath.parent_path());
|
||||
std::ofstream file(fullPath, std::ios::binary);
|
||||
file.write(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
// Read file content as string
|
||||
std::string ReadFile(const fs::path& relativePath)
|
||||
{
|
||||
auto fullPath = m_path / relativePath;
|
||||
std::ifstream file(fullPath, std::ios::binary);
|
||||
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
bool FileExists(const fs::path& relativePath)
|
||||
{
|
||||
return fs::exists(m_path / relativePath);
|
||||
}
|
||||
|
||||
private:
|
||||
fs::path m_path;
|
||||
};
|
||||
|
||||
TEST_CLASS(IsJsonFileCorruptedTests)
|
||||
{
|
||||
public:
|
||||
// Tests IsJsonFileCorrupted: valid JSON with no null bytes returns false.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — happy path, full file scan.
|
||||
TEST_METHOD(CleanJsonFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark","startup":true})");
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: zero-length file returns false (empty is not corrupted).
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — file.read returns 0 bytes immediately.
|
||||
TEST_METHOD(EmptyFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"empty.json", "");
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"empty.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: file containing embedded null bytes returns true.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — null byte detection within buffer.
|
||||
TEST_METHOD(FileWithNullBytesIsCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
std::vector<char> corrupted = { '{', '"', 'a', '"', ':', '\0', '\0', '\0', '}' };
|
||||
dir.WriteFileBytes(L"corrupted.json", corrupted);
|
||||
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"corrupted.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: file entirely filled with 0x00 bytes returns true.
|
||||
// Reproduces the exact bug from #46179 where installer zeroed out JSON files.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — first byte is null.
|
||||
TEST_METHOD(FileFilledWithNullBytesIsCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
std::vector<char> allNulls(1024, '\0');
|
||||
dir.WriteFileBytes(L"workspaces.json", allNulls);
|
||||
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"workspaces.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: path that does not exist returns false.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — file.is_open() check.
|
||||
TEST_METHOD(NonExistentFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"missing.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: file larger than the 4096-byte read chunk
|
||||
// with no null bytes returns false.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — multi-chunk while loop.
|
||||
TEST_METHOD(LargeCleanFileIsNotCorrupted)
|
||||
{
|
||||
TempDir dir;
|
||||
std::string largeContent(8192, 'x');
|
||||
dir.WriteFile(L"large.json", largeContent);
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"large.json"));
|
||||
}
|
||||
|
||||
// Tests IsJsonFileCorrupted: null byte placed after the first 4096-byte
|
||||
// chunk boundary is still detected.
|
||||
// Covers: configBackup.h IsJsonFileCorrupted — second chunk scan.
|
||||
TEST_METHOD(NullByteAtEndOfLargeFileIsDetected)
|
||||
{
|
||||
TempDir dir;
|
||||
std::string content(5000, 'x');
|
||||
content[4999] = '\0';
|
||||
std::vector<char> bytes(content.begin(), content.end());
|
||||
dir.WriteFileBytes(L"sneaky.json", bytes);
|
||||
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"sneaky.json"));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(BackupConfigFilesTests)
|
||||
{
|
||||
public:
|
||||
// Tests BackupConfigFiles: root-level .json files are copied to ConfigBackup.
|
||||
// Covers: configBackup.h BackupConfigFiles — root directory_iterator,
|
||||
// is_regular_file && extension == ".json" branch.
|
||||
// Setup: Two root-level JSON files.
|
||||
TEST_METHOD(BackupCopiesRootJsonFiles)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"UpdateState.json", R"({"state":0})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\UpdateState.json"));
|
||||
Assert::AreEqual(std::string(R"({"theme":"dark"})"), dir.ReadFile(L"ConfigBackup\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: .json files inside module subdirectories are
|
||||
// copied to ConfigBackup/<module>/.
|
||||
// Covers: configBackup.h BackupConfigFiles — is_directory branch,
|
||||
// module directory_iterator with extension filter.
|
||||
// Setup: Root JSON + two module directories with JSON files.
|
||||
TEST_METHOD(BackupCopiesModuleJsonFiles)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[]})");
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json", R"({"workspaces":[]})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\Workspaces\\workspaces.json"));
|
||||
Assert::AreEqual(std::string(R"({"zones":[]})"),
|
||||
dir.ReadFile(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: non-.json files at root level are not copied.
|
||||
// Covers: configBackup.h BackupConfigFiles — extension filter excludes .log.
|
||||
// Setup: One JSON file + one .log file at root.
|
||||
TEST_METHOD(BackupSkipsNonJsonFiles)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"debug.log", "log data");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\debug.log"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: the "Updates" directory is explicitly skipped.
|
||||
// Covers: configBackup.h BackupConfigFiles — dirName == L"Updates" continue.
|
||||
// Setup: Root JSON + Updates directory containing a file.
|
||||
TEST_METHOD(BackupSkipsUpdatesDirectory)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
dir.WriteFile(L"Updates\\installer.exe", "fake exe");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\Updates"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: running backup twice overwrites the previous
|
||||
// backup with current file content.
|
||||
// Covers: configBackup.h BackupConfigFiles — fs::remove_all(backupDir) +
|
||||
// copy_options::overwrite_existing.
|
||||
// Setup: Backup, modify original, backup again.
|
||||
TEST_METHOD(BackupOverwritesPreviousBackup)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"version":1})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Update the original
|
||||
dir.WriteFile(L"settings.json", R"({"version":2})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::AreEqual(std::string(R"({"version":2})"), dir.ReadFile(L"ConfigBackup\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: non-.json files inside module subdirectories
|
||||
// (e.g., FancyZones/zones.dat) should NOT be backed up.
|
||||
// Covers: configBackup.h BackupConfigFiles — extension filter in module loop.
|
||||
TEST_METHOD(BackupSkipsNonJsonFilesInModuleDirs)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[]})");
|
||||
dir.WriteFile(L"FancyZones\\zones.dat", "binary data");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\FancyZones\\zones.dat"));
|
||||
}
|
||||
|
||||
// Tests BackupConfigFiles: empty root directory with no files produces
|
||||
// an empty ConfigBackup dir without errors.
|
||||
// Covers: configBackup.h BackupConfigFiles — empty directory_iterator.
|
||||
TEST_METHOD(BackupEmptyRootDirSucceeds)
|
||||
{
|
||||
TempDir dir;
|
||||
// Root dir exists but has no files
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup"));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CLASS(RestoreCorruptedConfigsTests)
|
||||
{
|
||||
public:
|
||||
// Tests RestoreCorruptedConfigs: corrupted root-level JSON file is restored
|
||||
// from the good backup copy.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — root file restore branch,
|
||||
// fs::exists + IsJsonFileCorrupted + backup integrity check.
|
||||
// Setup: Good file -> backup -> corrupt original -> restore.
|
||||
TEST_METHOD(RestoreFixesCorruptedRootFile)
|
||||
{
|
||||
TempDir dir;
|
||||
const std::string goodContent = R"({"theme":"dark"})";
|
||||
dir.WriteFile(L"settings.json", goodContent);
|
||||
|
||||
// Backup
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt the original
|
||||
std::vector<char> corrupted(goodContent.size(), '\0');
|
||||
dir.WriteFileBytes(L"settings.json", corrupted);
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
|
||||
// Restore
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
Assert::AreEqual(goodContent, dir.ReadFile(L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: corrupted module-level JSON file is restored
|
||||
// from the good backup copy.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — module directory branch,
|
||||
// moduleBackupEntry restore with integrity check.
|
||||
// Setup: Module file + root file -> backup -> corrupt module file -> restore.
|
||||
TEST_METHOD(RestoreFixesCorruptedModuleFile)
|
||||
{
|
||||
TempDir dir;
|
||||
const std::string goodContent = R"({"workspaces":[]})";
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json", goodContent);
|
||||
dir.WriteFile(L"settings.json", R"({})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt the module file
|
||||
std::vector<char> corrupted(goodContent.size(), '\0');
|
||||
dir.WriteFileBytes(L"Workspaces\\workspaces.json", corrupted);
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
Assert::AreEqual(goodContent, dir.ReadFile(L"Workspaces\\workspaces.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: clean (non-corrupted) files are NOT
|
||||
// overwritten by backup — preserves user changes made after backup.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — IsJsonFileCorrupted
|
||||
// returns false, copy_file is skipped.
|
||||
// Setup: File -> backup -> modify (but keep valid) -> restore.
|
||||
TEST_METHOD(RestoreLeavesCleanFilesUntouched)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"version":1})");
|
||||
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Modify original (but keep it clean JSON)
|
||||
dir.WriteFile(L"settings.json", R"({"version":2})");
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Should NOT have been restored since it's not corrupted
|
||||
Assert::AreEqual(std::string(R"({"version":2})"), dir.ReadFile(L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: when no ConfigBackup directory exists,
|
||||
// restore silently does nothing (no crash, no data loss).
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — !fs::exists(backupDir)
|
||||
// early return.
|
||||
// Setup: File with no prior backup.
|
||||
TEST_METHOD(RestoreHandlesMissingBackupDirectory)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
|
||||
// No backup was created - restore should silently do nothing
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
Assert::AreEqual(std::string(R"({"theme":"dark"})"), dir.ReadFile(L"settings.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: end-to-end scenario with multiple modules,
|
||||
// some corrupted and some clean, verifying selective restore.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — both root and module
|
||||
// branches, selective restore based on corruption status.
|
||||
// Setup: 4 modules -> backup -> corrupt 2 -> restore -> verify all 4.
|
||||
TEST_METHOD(FullBackupAndRestoreRoundTrip)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
// Set up a realistic config structure
|
||||
dir.WriteFile(L"settings.json", R"({"startup":true,"theme":"dark"})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[{"id":1}]})");
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json", R"({"workspaces":[{"name":"dev"}]})");
|
||||
dir.WriteFile(L"KeyboardManager\\default.json", R"({"remaps":[]})");
|
||||
|
||||
// Backup
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt some files (simulating #46179 scenario)
|
||||
dir.WriteFileBytes(L"Workspaces\\workspaces.json", std::vector<char>(100, '\0'));
|
||||
dir.WriteFileBytes(L"settings.json", std::vector<char>(50, '\0'));
|
||||
// Leave FancyZones and KBM clean
|
||||
|
||||
// Restore
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Corrupted files should be restored
|
||||
Assert::AreEqual(std::string(R"({"startup":true,"theme":"dark"})"), dir.ReadFile(L"settings.json"));
|
||||
Assert::AreEqual(std::string(R"({"workspaces":[{"name":"dev"}]})"), dir.ReadFile(L"Workspaces\\workspaces.json"));
|
||||
|
||||
// Clean files should be unchanged
|
||||
Assert::AreEqual(std::string(R"({"zones":[{"id":1}]})"), dir.ReadFile(L"FancyZones\\settings.json"));
|
||||
Assert::AreEqual(std::string(R"({"remaps":[]})"), dir.ReadFile(L"KeyboardManager\\default.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: when the original file has been deleted
|
||||
// (not corrupted), restore should NOT recreate it from backup. The installer
|
||||
// may have intentionally removed obsolete config files.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — fs::exists guard.
|
||||
TEST_METHOD(RestoreSkipsDeletedOriginals)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"obsolete.json", R"({"old":true})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Installer deletes the file
|
||||
std::error_code ec;
|
||||
fs::remove(dir.path() / L"obsolete.json", ec);
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Should NOT be recreated
|
||||
Assert::IsFalse(dir.FileExists(L"obsolete.json"));
|
||||
}
|
||||
|
||||
// Tests RestoreCorruptedConfigs: when the backup file itself is corrupted
|
||||
// (e.g., disk error during backup), restore should NOT copy corrupted
|
||||
// backup over the original — that would make things worse.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — backup integrity check (B2 fix).
|
||||
TEST_METHOD(RestoreSkipsCorruptedBackup)
|
||||
{
|
||||
TempDir dir;
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Corrupt BOTH the original AND the backup
|
||||
std::vector<char> nulls(50, '\0');
|
||||
dir.WriteFileBytes(L"settings.json", nulls);
|
||||
dir.WriteFileBytes(L"ConfigBackup\\settings.json", nulls);
|
||||
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Original should still be corrupted — we don't restore from bad backup
|
||||
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
}
|
||||
};
|
||||
|
||||
// Simulates what actually happens during a PowerToys upgrade:
|
||||
// 1. User has settings from normal use
|
||||
// 2. Updater backs up before install (Stage 1)
|
||||
// 3. Installer runs and corrupts some files (simulated)
|
||||
// 4. Updater restores corrupted files (Stage 2)
|
||||
// 5. PT relaunches and finds working configs
|
||||
TEST_CLASS(UpgradeSimulationTests)
|
||||
{
|
||||
public:
|
||||
// Tests full upgrade simulation: backup -> installer corrupts files -> restore.
|
||||
// Verifies that corrupted files are restored and clean files are untouched.
|
||||
// Covers: configBackup.h BackupConfigFiles + RestoreCorruptedConfigs —
|
||||
// end-to-end with 5 modules, 2 corrupted, 3 clean.
|
||||
// Setup: Realistic config structure with multiple modules.
|
||||
TEST_METHOD(SimulateUpgradeWithCorruption)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
// === User's real config state before upgrade ===
|
||||
dir.WriteFile(L"settings.json",
|
||||
R"({"startup":true,"theme":"dark","run_elevated":false,"download_updates_automatically":true})");
|
||||
dir.WriteFile(L"FancyZones\\settings.json",
|
||||
R"({"zones":[{"id":1,"rect":{"x":0,"y":0,"w":960,"h":1080}}]})");
|
||||
dir.WriteFile(L"Workspaces\\workspaces.json",
|
||||
R"({"workspaces":[{"name":"dev","apps":["code","terminal"]}]})");
|
||||
dir.WriteFile(L"KeyboardManager\\default.json",
|
||||
R"({"remapKeys":{"inProcess":[{"original":"0x41","new":"0x42"}]}})");
|
||||
dir.WriteFile(L"MouseWithoutBorders\\settings.json",
|
||||
R"({"machineKey":"abc123","connectToAll":true})");
|
||||
|
||||
// Non-JSON files that should be left alone
|
||||
dir.WriteFile(L"update.log", "2026-04-11 update started");
|
||||
|
||||
// === Stage 1: Backup before killing PT ===
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// Verify backup was created correctly
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\Workspaces\\workspaces.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\KeyboardManager\\default.json"));
|
||||
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\MouseWithoutBorders\\settings.json"));
|
||||
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\update.log"));
|
||||
|
||||
// === Installer runs: some files get corrupted (the #46179 scenario) ===
|
||||
// Workspaces JSON filled with null bytes
|
||||
dir.WriteFileBytes(L"Workspaces\\workspaces.json", std::vector<char>(512, '\0'));
|
||||
// Main settings partially corrupted (null bytes injected)
|
||||
std::vector<char> partialCorrupt = { '{', '"', 's', '\0', '\0', '\0', '\0', '}' };
|
||||
dir.WriteFileBytes(L"settings.json", partialCorrupt);
|
||||
|
||||
// FancyZones, KBM, and MWB survive the install fine
|
||||
// (this is realistic - not all files get corrupted)
|
||||
|
||||
// === Stage 2: Restore after install completes ===
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// === Verify: PT relaunches and finds working configs ===
|
||||
|
||||
// Corrupted files should be restored from backup
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
|
||||
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"Workspaces\\workspaces.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"startup":true,"theme":"dark","run_elevated":false,"download_updates_automatically":true})"),
|
||||
dir.ReadFile(L"settings.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"workspaces":[{"name":"dev","apps":["code","terminal"]}]})"),
|
||||
dir.ReadFile(L"Workspaces\\workspaces.json"));
|
||||
|
||||
// Clean files should be untouched (not overwritten with backup)
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"zones":[{"id":1,"rect":{"x":0,"y":0,"w":960,"h":1080}}]})"),
|
||||
dir.ReadFile(L"FancyZones\\settings.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"remapKeys":{"inProcess":[{"original":"0x41","new":"0x42"}]}})"),
|
||||
dir.ReadFile(L"KeyboardManager\\default.json"));
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"machineKey":"abc123","connectToAll":true})"),
|
||||
dir.ReadFile(L"MouseWithoutBorders\\settings.json"));
|
||||
}
|
||||
|
||||
// Tests upgrade from an old version that has fewer modules than the new version.
|
||||
// Verifies that new module configs (created by the installer) are not touched
|
||||
// by restore, while corrupted old configs are restored.
|
||||
// Covers: configBackup.h RestoreCorruptedConfigs — module dir in root that
|
||||
// has no corresponding backup entry.
|
||||
// Setup: Old version with 1 module -> backup -> new installer adds module -> corrupt old -> restore.
|
||||
TEST_METHOD(SimulateUpgradeFromVeryOldVersion)
|
||||
{
|
||||
TempDir dir;
|
||||
|
||||
// Old version had fewer modules - only settings.json
|
||||
dir.WriteFile(L"settings.json", R"({"theme":"dark","powertoys_version":"v0.60.0"})");
|
||||
|
||||
// Backup
|
||||
updating::BackupConfigFiles(dir.path());
|
||||
|
||||
// New installer creates new module dirs that didn't exist before
|
||||
dir.WriteFile(L"NewModule\\settings.json", R"({"enabled":true})");
|
||||
|
||||
// Old settings get corrupted during upgrade
|
||||
dir.WriteFileBytes(L"settings.json", std::vector<char>(100, '\0'));
|
||||
|
||||
// Restore
|
||||
updating::RestoreCorruptedConfigs(dir.path());
|
||||
|
||||
// Old settings restored
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"theme":"dark","powertoys_version":"v0.60.0"})"),
|
||||
dir.ReadFile(L"settings.json"));
|
||||
|
||||
// New module settings untouched (no backup existed for them)
|
||||
Assert::AreEqual(
|
||||
std::string(R"({"enabled":true})"),
|
||||
dir.ReadFile(L"NewModule\\settings.json"));
|
||||
}
|
||||
};
|
||||
|
||||
// Tests for the update lifecycle: argument passing between Stage 1 and Stage 2,
|
||||
// relaunch path construction, and the handoff that was broken in #42004/#43011/#44071.
|
||||
TEST_CLASS(UpdateLifecycleTests)
|
||||
{
|
||||
public:
|
||||
// Tests BuildStage2Arguments: output contains the stage 2 flag, installer path,
|
||||
// and install directory — all three components needed for Stage 2.
|
||||
// Covers: updateLifecycle.h BuildStage2Arguments — concatenation logic.
|
||||
// Setup: Typical paths with spaces (Program Files).
|
||||
TEST_METHOD(BuildStage2ArgumentsContainsInstallerAndInstallDir)
|
||||
{
|
||||
const auto args = updating::BuildStage2Arguments(
|
||||
L"-update_now_stage_2",
|
||||
L"C:\\Users\\test\\AppData\\Local\\PowerToys\\Updates\\powertoyssetup-x64.exe",
|
||||
L"C:\\Program Files\\PowerToys");
|
||||
|
||||
// Must contain the stage 2 flag
|
||||
Assert::IsTrue(args.find(L"-update_now_stage_2") != std::wstring::npos);
|
||||
// Must contain the installer path (quoted)
|
||||
Assert::IsTrue(args.find(L"powertoyssetup-x64.exe") != std::wstring::npos);
|
||||
// Must contain the install directory (quoted) — this was MISSING before our fix
|
||||
Assert::IsTrue(args.find(L"C:\\Program Files\\PowerToys") != std::wstring::npos);
|
||||
}
|
||||
|
||||
// Tests BuildStage2Arguments: both paths are wrapped in double quotes to
|
||||
// survive CommandLineToArgvW parsing when paths contain spaces.
|
||||
// Covers: updateLifecycle.h BuildStage2Arguments — quote wrapping.
|
||||
// Setup: Installer path with spaces.
|
||||
TEST_METHOD(BuildStage2ArgumentsQuotesBothPaths)
|
||||
{
|
||||
const auto args = updating::BuildStage2Arguments(
|
||||
L"-update_now_stage_2",
|
||||
L"C:\\path with spaces\\installer.exe",
|
||||
L"C:\\Program Files\\PowerToys");
|
||||
|
||||
// Count quotes — should have 4 (open/close for each path)
|
||||
size_t quoteCount = std::count(args.begin(), args.end(), L'"');
|
||||
Assert::AreEqual(size_t{ 4 }, quoteCount);
|
||||
}
|
||||
|
||||
// Tests BuildPowerToysExePath: appends "PowerToys.exe" to the install dir.
|
||||
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path / operator.
|
||||
// Setup: Standard install path without trailing backslash.
|
||||
TEST_METHOD(BuildPowerToysExePathAppendsExeName)
|
||||
{
|
||||
const auto path = updating::BuildPowerToysExePath(L"C:\\Program Files\\PowerToys");
|
||||
Assert::AreEqual(std::wstring(L"C:\\Program Files\\PowerToys\\PowerToys.exe"), path);
|
||||
}
|
||||
|
||||
// Tests BuildPowerToysExePath: trailing backslash does not produce double
|
||||
// backslash (e.g., "...PowerToys\\PowerToys.exe").
|
||||
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path normalizes separators.
|
||||
// Setup: Install path with trailing backslash.
|
||||
TEST_METHOD(BuildPowerToysExePathHandlesTrailingBackslash)
|
||||
{
|
||||
const auto path = updating::BuildPowerToysExePath(L"C:\\Program Files\\PowerToys\\");
|
||||
Assert::AreEqual(std::wstring(L"C:\\Program Files\\PowerToys\\PowerToys.exe"), path);
|
||||
}
|
||||
|
||||
// Tests BuildPowerToysExePath: empty string produces just "PowerToys.exe".
|
||||
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path with empty input.
|
||||
// Setup: Empty install directory string.
|
||||
TEST_METHOD(BuildPowerToysExePathHandlesEmptyString)
|
||||
{
|
||||
const auto path = updating::BuildPowerToysExePath(L"");
|
||||
Assert::AreEqual(std::wstring(L"PowerToys.exe"), path);
|
||||
}
|
||||
|
||||
// Tests CanRelaunchAfterUpdate: returns true when Stage 2 receives
|
||||
// the install directory (argCount >= 4), false otherwise.
|
||||
// This is the gate that prevents relaunch when using an old Stage 1
|
||||
// that didn't pass the install dir (#42004/#43011/#44071).
|
||||
// Covers: updateLifecycle.h CanRelaunchAfterUpdate.
|
||||
TEST_METHOD(CanRelaunchReflectsArgCount)
|
||||
{
|
||||
// Old Stage 1 (pre-fix): only passed action + installer = 3 args
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(0));
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(1));
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(2));
|
||||
Assert::IsFalse(updating::CanRelaunchAfterUpdate(3));
|
||||
|
||||
// New Stage 1 (post-fix): passes action + installer + installDir = 4 args
|
||||
Assert::IsTrue(updating::CanRelaunchAfterUpdate(4));
|
||||
Assert::IsTrue(updating::CanRelaunchAfterUpdate(5));
|
||||
}
|
||||
|
||||
// Tests BuildStage2Arguments + CommandLineToArgvW round-trip: the exact
|
||||
// scenario where Stage 1 builds args and Windows parses them in Stage 2.
|
||||
// Verifies quoting is correct so paths with spaces survive the round trip.
|
||||
// Covers: updateLifecycle.h BuildStage2Arguments — quote correctness.
|
||||
// Setup: Realistic paths with spaces and version numbers.
|
||||
TEST_METHOD(Stage2ArgumentsCanBeRoundTrippedThroughCommandLineToArgvW)
|
||||
{
|
||||
const std::wstring installerPath = L"C:\\Users\\test user\\AppData\\Local\\PowerToys\\Updates\\powertoyssetup-0.86.0-x64.exe";
|
||||
const std::wstring installDir = L"C:\\Program Files\\PowerToys";
|
||||
|
||||
const auto args = updating::BuildStage2Arguments(L"-update_now_stage_2", installerPath, installDir);
|
||||
|
||||
// Simulate what Windows does: prepend a fake exe name and parse
|
||||
std::wstring commandLine = L"PowerToys.Update.exe " + args;
|
||||
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(commandLine.c_str(), &argc);
|
||||
Assert::IsNotNull(argv);
|
||||
Assert::AreEqual(4, argc);
|
||||
Assert::AreEqual(std::wstring(L"-update_now_stage_2"), std::wstring(argv[1]));
|
||||
Assert::AreEqual(installerPath, std::wstring(argv[2]));
|
||||
Assert::AreEqual(installDir, std::wstring(argv[3]));
|
||||
|
||||
LocalFree(argv);
|
||||
}
|
||||
};
|
||||
}
|
||||
45
src/common/updating/UnitTests/UpdatingUnitTests.vcxproj
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>UpdatingUnitTests</RootNamespace>
|
||||
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
|
||||
<ProjectName>Updating.UnitTests</ProjectName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\tests\UpdatingUnitTests\</OutDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\;..\..\;..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="UpdatingTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
5
src/common/updating/UnitTests/pch.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#include "pch.h"
|
||||
17
src/common/updating/UnitTests/pch.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#ifndef PCH_H
|
||||
#define PCH_H
|
||||
|
||||
#include <atomic>
|
||||
#include <Windows.h>
|
||||
|
||||
// Suppressing 26466 - Don't use static_cast downcasts - in CppUnitTest.h
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26466)
|
||||
#include "CppUnitTest.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
#endif //PCH_H
|
||||
228
src/common/updating/configBackup.h
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace updating
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct BackupResult
|
||||
{
|
||||
int filesBackedUp{ 0 };
|
||||
int errors{ 0 };
|
||||
};
|
||||
|
||||
struct RestoreResult
|
||||
{
|
||||
int filesRestored{ 0 };
|
||||
int filesChecked{ 0 };
|
||||
int errors{ 0 };
|
||||
};
|
||||
|
||||
// Check if a JSON file is corrupted (contains null bytes, as seen in #46179)
|
||||
inline bool IsJsonFileCorrupted(const fs::path& filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream file(filePath, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr size_t c_readChunkSize{ 4096 };
|
||||
char buffer[c_readChunkSize];
|
||||
while (file.read(buffer, c_readChunkSize) || file.gcount() > 0)
|
||||
{
|
||||
const auto bytesRead = file.gcount();
|
||||
for (std::streamsize i = 0; i < bytesRead; ++i)
|
||||
{
|
||||
if (buffer[i] == '\0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Backup all JSON config files before update to protect against corruption (#46179)
|
||||
inline BackupResult BackupConfigFiles(const fs::path& rootPath)
|
||||
{
|
||||
BackupResult result{};
|
||||
try
|
||||
{
|
||||
const fs::path backupDir = rootPath / L"ConfigBackup";
|
||||
|
||||
std::error_code ec;
|
||||
fs::remove_all(backupDir, ec);
|
||||
fs::create_directories(backupDir, ec);
|
||||
if (ec)
|
||||
{
|
||||
result.errors++;
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(rootPath, ec))
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
result.errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry.is_regular_file() && entry.path().extension() == L".json")
|
||||
{
|
||||
std::error_code copyEc;
|
||||
fs::copy_file(entry.path(), backupDir / entry.path().filename(), fs::copy_options::overwrite_existing, copyEc);
|
||||
if (copyEc)
|
||||
{
|
||||
result.errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.filesBackedUp++;
|
||||
}
|
||||
}
|
||||
else if (entry.is_directory())
|
||||
{
|
||||
const auto dirName = entry.path().filename().wstring();
|
||||
if (dirName == L"ConfigBackup" || dirName == L"Updates")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto moduleBackup = backupDir / entry.path().filename();
|
||||
fs::create_directories(moduleBackup, ec);
|
||||
|
||||
std::error_code moduleEc;
|
||||
for (const auto& moduleEntry : fs::directory_iterator(entry.path(), moduleEc))
|
||||
{
|
||||
if (moduleEc)
|
||||
{
|
||||
result.errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (moduleEntry.is_regular_file() && moduleEntry.path().extension() == L".json")
|
||||
{
|
||||
std::error_code copyEc;
|
||||
fs::copy_file(moduleEntry.path(), moduleBackup / moduleEntry.path().filename(), fs::copy_options::overwrite_existing, copyEc);
|
||||
if (copyEc)
|
||||
{
|
||||
result.errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.filesBackedUp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
result.errors++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Restore JSON configs from backup if corruption is detected after update.
|
||||
// Cleans up the backup directory afterward.
|
||||
inline RestoreResult RestoreCorruptedConfigs(const fs::path& rootPath)
|
||||
{
|
||||
RestoreResult result{};
|
||||
try
|
||||
{
|
||||
const fs::path backupDir = rootPath / L"ConfigBackup";
|
||||
|
||||
if (!fs::exists(backupDir))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
for (const auto& backupEntry : fs::directory_iterator(backupDir, ec))
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
result.errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (backupEntry.is_regular_file() && backupEntry.path().extension() == L".json")
|
||||
{
|
||||
const auto originalPath = rootPath / backupEntry.path().filename();
|
||||
result.filesChecked++;
|
||||
if (fs::exists(originalPath) && IsJsonFileCorrupted(originalPath) && !IsJsonFileCorrupted(backupEntry.path()))
|
||||
{
|
||||
std::error_code copyEc;
|
||||
fs::copy_file(backupEntry.path(), originalPath, fs::copy_options::overwrite_existing, copyEc);
|
||||
if (copyEc)
|
||||
{
|
||||
result.errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.filesRestored++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (backupEntry.is_directory())
|
||||
{
|
||||
const auto moduleDir = rootPath / backupEntry.path().filename();
|
||||
|
||||
std::error_code moduleEc;
|
||||
for (const auto& moduleBackupEntry : fs::directory_iterator(backupEntry.path(), moduleEc))
|
||||
{
|
||||
if (moduleEc)
|
||||
{
|
||||
result.errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (moduleBackupEntry.is_regular_file() && moduleBackupEntry.path().extension() == L".json")
|
||||
{
|
||||
const auto originalModulePath = moduleDir / moduleBackupEntry.path().filename();
|
||||
result.filesChecked++;
|
||||
if (fs::exists(originalModulePath) && IsJsonFileCorrupted(originalModulePath) && !IsJsonFileCorrupted(moduleBackupEntry.path()))
|
||||
{
|
||||
std::error_code copyEc;
|
||||
fs::copy_file(moduleBackupEntry.path(), originalModulePath, fs::copy_options::overwrite_existing, copyEc);
|
||||
if (copyEc)
|
||||
{
|
||||
result.errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.filesRestored++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up backup directory after restore check
|
||||
fs::remove_all(backupDir, ec);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
result.errors++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@
|
||||
#include <regex>
|
||||
#include <charconv>
|
||||
|
||||
#include <expected.hpp>
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.ApplicationModel.h>
|
||||
|
||||
47
src/common/updating/updateLifecycle.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace updating
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Build the command-line arguments for Stage 2.
|
||||
// Stage 1 passes the installer path and the PT install directory
|
||||
// so Stage 2 can run the installer and relaunch PowerToys afterward.
|
||||
// Note: paths containing embedded double-quote characters are not supported.
|
||||
// This is safe because install paths come from get_module_folderpath().
|
||||
inline std::wstring BuildStage2Arguments(
|
||||
const std::wstring& stage2Flag,
|
||||
const fs::path& installerPath,
|
||||
const fs::path& installDir)
|
||||
{
|
||||
std::wstring arguments{ stage2Flag };
|
||||
arguments += L" \"";
|
||||
arguments += installerPath.c_str();
|
||||
arguments += L"\" \"";
|
||||
arguments += installDir.c_str();
|
||||
arguments += L"\"";
|
||||
return arguments;
|
||||
}
|
||||
|
||||
// Build the full path to PowerToys.exe from the install directory.
|
||||
// Used by Stage 2 to relaunch PT after a successful update.
|
||||
inline std::wstring BuildPowerToysExePath(const std::wstring& installDir)
|
||||
{
|
||||
return (std::filesystem::path(installDir) / L"PowerToys.exe").wstring();
|
||||
}
|
||||
|
||||
// Determine whether Stage 2 has enough information to relaunch PT.
|
||||
// Returns true if the install directory argument was provided.
|
||||
inline bool CanRelaunchAfterUpdate(int argCount)
|
||||
{
|
||||
// args[0]=exe, args[1]=action, args[2]=installer, args[3]=installDir
|
||||
return argCount >= 4;
|
||||
}
|
||||
}
|
||||
@@ -87,11 +87,7 @@ namespace updating
|
||||
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
|
||||
if constexpr (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
|
||||
{
|
||||
#if USE_STD_EXPECTED
|
||||
co_return std::unexpected(LOCAL_BUILD_ERROR);
|
||||
#else
|
||||
co_return nonstd::make_unexpected(LOCAL_BUILD_ERROR);
|
||||
#endif
|
||||
}
|
||||
|
||||
try
|
||||
@@ -143,11 +139,7 @@ namespace updating
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
#if USE_STD_EXPECTED
|
||||
co_return std::unexpected(NETWORK_ERROR);
|
||||
#else
|
||||
co_return nonstd::make_unexpected(NETWORK_ERROR);
|
||||
#endif
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
|
||||
@@ -5,14 +5,7 @@
|
||||
#include <filesystem>
|
||||
#include <variant>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
//#if __MSVC_VERSION__ >= 1933 // MSVC begin to support std::unexpected in 19.33
|
||||
#if __has_include(<expected> ) // use the same way with excepted-lite to detect std::unexcepted, as using it as backup
|
||||
#include <expected>
|
||||
#define USE_STD_EXPECTED 1
|
||||
#else
|
||||
#include <expected.hpp>
|
||||
#define USE_STD_EXPECTED 0
|
||||
#endif
|
||||
|
||||
#include <common/version/helper.h>
|
||||
#include <wil/coroutine.h>
|
||||
@@ -31,12 +24,7 @@ namespace updating
|
||||
std::wstring installer_filename;
|
||||
};
|
||||
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
|
||||
|
||||
#if USE_STD_EXPECTED
|
||||
using github_version_result = std::expected<github_version_info, std::wstring>;
|
||||
#else
|
||||
using github_version_result = nonstd::expected<github_version_info, std::wstring>;
|
||||
#endif
|
||||
|
||||
wil::task<github_version_result> get_github_version_info_async(bool prerelease = false);
|
||||
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<ProjectName>ApplicationUpdate</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\..\..\deps\expected.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectName>spdlog</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)deps\spdlog.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
|
||||
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<ExceptionHandling>Sync</ExceptionHandling>
|
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PreprocessorDefinitions>WIN32;_WINDOWS;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<AdditionalOptions>%(AdditionalOptions)</AdditionalOptions>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\spdlog.cpp" />
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\stdout_sinks.cpp" />
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\color_sinks.cpp" />
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\file_sinks.cpp" />
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\async.cpp" />
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\cfg.cpp" />
|
||||
<ClCompile Include="$(RepoRoot)deps\spdlog\src\fmt.cpp" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\async.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\async_logger-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\async_logger.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\common-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\common.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\formatter.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fwd.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\logger-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\logger.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\pattern_formatter-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\pattern_formatter.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\spdlog-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\spdlog.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\stopwatch.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\tweakme.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\version.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\backtracer-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\backtracer.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\circular_q.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\console_globals.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\file_helper-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\file_helper.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\fmt_helper.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg_buffer-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\log_msg_buffer.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\mpmc_blocking_q.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\null_mutex.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\os-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\os.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\periodic_worker-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\periodic_worker.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\registry-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\registry.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\synchronous_factory.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\tcp_client-windows.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\tcp_client.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\thread_pool-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\thread_pool.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\details\windows_include.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\android_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ansicolor_sink-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ansicolor_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\base_sink-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\base_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\basic_file_sink-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\basic_file_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\daily_file_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\dist_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\dup_filter_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\msvc_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\null_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ostream_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\ringbuffer_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\rotating_file_sink-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\rotating_file_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\sink-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_color_sinks-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_color_sinks.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_sinks-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\stdout_sinks.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\syslog_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\systemd_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\tcp_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\win_eventlog_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\wincolor_sink-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\sinks\wincolor_sink.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bin_to_hex.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\chrono.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\fmt.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\ostr.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\chrono.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\color.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\compile.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\core.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\format-inl.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\format.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\locale.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\os.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\ostream.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\posix.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\printf.h" />
|
||||
<ClInclude Include="$(RepoRoot)deps\spdlog\include\spdlog\fmt\bundled\ranges.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,122 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="16.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\spdlog.cpp" />
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\stdout_sinks.cpp" />
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\color_sinks.cpp" />
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\file_sinks.cpp" />
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\async.cpp" />
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\cfg.cpp" />
|
||||
<ClCompile Include="$(ProjectDir)..\..\deps\spdlog\src\fmt.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\async.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\async_logger-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\async_logger.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\common-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\common.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\formatter.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fwd.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\logger-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\logger.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\pattern_formatter-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\pattern_formatter.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\spdlog-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\spdlog.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\stopwatch.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\tweakme.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\version.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\backtracer-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\backtracer.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\circular_q.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\console_globals.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\file_helper-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\file_helper.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\fmt_helper.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg_buffer-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\log_msg_buffer.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\mpmc_blocking_q.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\null_mutex.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\os-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\os.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\periodic_worker-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\periodic_worker.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\registry-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\registry.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\synchronous_factory.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\tcp_client-windows.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\tcp_client.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\thread_pool-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\thread_pool.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\details\windows_include.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\android_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ansicolor_sink-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ansicolor_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\base_sink-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\base_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\basic_file_sink-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\basic_file_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\daily_file_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\dist_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\dup_filter_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\msvc_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\null_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ostream_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\ringbuffer_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\rotating_file_sink-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\rotating_file_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\sink-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_color_sinks-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_color_sinks.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_sinks-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\stdout_sinks.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\syslog_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\systemd_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\tcp_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\win_eventlog_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\wincolor_sink-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\sinks\wincolor_sink.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bin_to_hex.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\chrono.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\fmt.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\ostr.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\chrono.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\color.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\compile.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\core.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\format-inl.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\format.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\locale.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\os.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\ostream.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\posix.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\printf.h" />
|
||||
<ClInclude Include="$(ProjectDir)..\..\deps\spdlog\include\spdlog\fmt\bundled\ranges.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{CDF4BA23-560C-3A6F-8D1C-2F5ACA434329}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\spdlog">
|
||||
<UniqueIdentifier>{EFFE8123-D806-3145-8ABC-B48562A6C8F2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\spdlog\details">
|
||||
<UniqueIdentifier>{C546A431-88F1-390F-B0F0-D9CAC274B7F5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\spdlog\fmt">
|
||||
<UniqueIdentifier>{08320F28-6D0D-3217-B0B3-A98758C02C97}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\spdlog\fmt\bundled">
|
||||
<UniqueIdentifier>{C856528D-4506-3A62-B279-CBB4558CB61D}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Header Files\spdlog\sinks">
|
||||
<UniqueIdentifier>{A5EE33C4-AB64-38F0-BF4A-CCD02FFAB715}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{8B480F42-A230-3344-A387-2D050CFF7D9C}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -2,6 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text;
|
||||
|
||||
using AdvancedPaste.Helpers;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
@@ -13,15 +15,26 @@ namespace AdvancedPaste.FuzzTests
|
||||
{
|
||||
public static void FuzzToJsonFromXmlOrCsv(ReadOnlySpan<byte> input)
|
||||
{
|
||||
// Decode the input bytes as UTF-8 text. `ReadOnlySpan<byte>.ToString()`
|
||||
// returns the type name (e.g. "System.ReadOnlySpan<Byte>[N]") rather
|
||||
// than the bytes, so an explicit decode is required to actually exercise
|
||||
// the helper with the provided input.
|
||||
string text = Encoding.UTF8.GetString(input);
|
||||
|
||||
var dataPackage = new DataPackage();
|
||||
dataPackage.SetText(text);
|
||||
|
||||
// Use GetAwaiter().GetResult() so any thrown exception surfaces with its
|
||||
// original type. `Task.Run(...).Result` wraps thrown exceptions in an
|
||||
// AggregateException, which would prevent the
|
||||
// `when (ex is ArgumentException)` filter below from matching.
|
||||
try
|
||||
{
|
||||
var dataPackage = new DataPackage();
|
||||
dataPackage.SetText(input.ToString());
|
||||
_ = Task.Run(async () => await JsonHelper.ToJsonFromXmlOrCsvAsync(dataPackage.GetView())).Result;
|
||||
_ = Task.Run(async () => await JsonHelper.ToJsonFromXmlOrCsvAsync(dataPackage.GetView())).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException)
|
||||
{
|
||||
// This is an example. It's important to filter out any *expected* exceptions from our code here.
|
||||
// It's important to filter out any *expected* exceptions from our code here.
|
||||
// However, catching all exceptions is considered an anti-pattern because it may suppress legitimate
|
||||
// issues, such as a NullReferenceException thrown by our code. In this case, we still re-throw
|
||||
// the exception, as the ToJsonFromXmlOrCsvAsync method is not expected to throw any exceptions.
|
||||
|
||||
@@ -57,7 +57,21 @@ namespace AdvancedPaste.Helpers
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var text = await clipboardData.GetTextAsync();
|
||||
string text;
|
||||
try
|
||||
{
|
||||
text = await clipboardData.GetTextAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// GetTextAsync goes through WinRT/COM and can fail for reasons outside
|
||||
// our control (e.g. clipboard contention, malformed payloads from other
|
||||
// apps). The contract for this helper is that it does not throw — any
|
||||
// failure to read clipboard text should be treated as "no text".
|
||||
Logger.LogError("Failed reading text from clipboard", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string jsonText = string.Empty;
|
||||
|
||||
// If the text is already JSON, return it
|
||||
|
||||
@@ -28,7 +28,12 @@ namespace ipc
|
||||
|
||||
try
|
||||
{
|
||||
m_stream = std::ofstream(path);
|
||||
m_stream = std::ofstream(path, std::ios::binary);
|
||||
if (!m_stream.is_open())
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
catch (...)
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
|
||||
|
||||
com_array<hstring> NativeMethods::ReadPathsFromFile()
|
||||
{
|
||||
std::ifstream stream(paths_file());
|
||||
std::ifstream stream(paths_file(), std::ios::binary);
|
||||
|
||||
std::vector<std::wstring> result_cpp;
|
||||
std::wstring line;
|
||||
@@ -98,7 +98,7 @@ namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
|
||||
|
||||
bool NativeMethods::StartAsElevated(array_view<hstring const> paths)
|
||||
{
|
||||
std::ofstream stream(paths_file());
|
||||
std::ofstream stream(paths_file(), std::ios::binary);
|
||||
const WCHAR newline = L'\n';
|
||||
|
||||
for (uint32_t i = 0; i < paths.size(); i++)
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
..\..\..\common;
|
||||
..\..\..\common\logger;
|
||||
..\..\..\common\utils;
|
||||
..\..\..\..\deps\spdlog\include;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
$(RepoRoot)src\common\SettingsAPI;
|
||||
$(RepoRoot)src\common\Telemetry;
|
||||
$(RepoRoot)src\;
|
||||
..\..\..\..\deps\spdlog\include;
|
||||
./;
|
||||
%(AdditionalIncludeDirectories)
|
||||
</AdditionalIncludeDirectories>
|
||||
|
||||
@@ -196,11 +196,6 @@ internal static class Encryption
|
||||
return (uint)((hashValue[0] << 23) + (hashValue[1] << 16) + (hashValue[^1] << 8) + hashValue[2]);
|
||||
}
|
||||
|
||||
internal static string GetDebugInfo(string st)
|
||||
{
|
||||
return string.IsNullOrEmpty(st) ? st : ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static string CreateDefaultKey()
|
||||
{
|
||||
return CreateRandomKey();
|
||||
|
||||
@@ -62,7 +62,7 @@ internal static class Helper
|
||||
{
|
||||
Process p = Process.GetCurrentProcess();
|
||||
string procInfo = $"{p.PrivateMemorySize64 / 1024 / 1024}MB, {p.TotalProcessorTime}, {Environment.ProcessorCount}.";
|
||||
string threadStacks = $"{procInfo} {Thread.DumpThreadsStack()}";
|
||||
string threadStacks = $"{procInfo}\r\n{Thread.DumpThreadsStack()}\r\n";
|
||||
Logger.TelemetryLogTrace(threadStacks, SeverityLevel.Error);
|
||||
break;
|
||||
}
|
||||
@@ -379,7 +379,7 @@ internal static class Helper
|
||||
log += "=============================================================================================================================\r\n";
|
||||
log += $"{Application.ProductName} version {Application.ProductVersion}\r\n";
|
||||
|
||||
log += $"{Setting.Values.Username}/{Encryption.GetDebugInfo(Encryption.MyKey)}\r\n";
|
||||
log += $"{Setting.Values.Username}/{Logger.GetChecksum(Encryption.MyKey)}\r\n";
|
||||
log += $"{Common.MachineName}/{Common.MachineID}/{Common.DesMachineID}\r\n";
|
||||
log += $"Id: {Setting.Values.DeviceId}\r\n";
|
||||
log += $"Matrix: {string.Join(",", MachineStuff.MachineMatrix)}\r\n";
|
||||
@@ -428,9 +428,12 @@ internal static class Helper
|
||||
log += Setting.Values.LastPersonalizeLogonScr + "\r\n";
|
||||
log += "Name2IP =\r\n" + Setting.Values.Name2IP + "\r\n";
|
||||
|
||||
// note - this doesn't actually log the last 10 messages - it really concatenates the counts of
|
||||
// the first 10 unique messages that were logged, which isn't really very useful so we'll remove it
|
||||
/*
|
||||
log += "Last 10 trace messages:\r\n";
|
||||
|
||||
log += string.Join(Environment.NewLine, Logger.LogCounter.Select(item => $"({item.Value}): {item.Key}").Take(10));
|
||||
*/
|
||||
|
||||
log += "\r\n=============================================================================================================================";
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
@@ -30,29 +31,60 @@ namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal static class Logger
|
||||
{
|
||||
internal static readonly string[] AllLogs = new string[MAX_LOG];
|
||||
private static readonly Lock AllLogsLock = new();
|
||||
internal static readonly ConcurrentDictionary<string, int> LogCounter = new();
|
||||
// keep a count of unique lines of text that get logged
|
||||
private static readonly ConcurrentDictionary<string, int> LogCounter = new();
|
||||
|
||||
// implements a simple ring buffer to store recent log entries in memory
|
||||
private const int MAX_LOG = 10000;
|
||||
private static readonly string[] AllLogs = new string[MAX_LOG];
|
||||
private static readonly Lock AllLogsLock = new();
|
||||
private static int allLogsIndex;
|
||||
|
||||
// used for throttling the number of exceptions that get logged
|
||||
// so that high-volume exceptions don't flood the logs
|
||||
private const int MaxLogExceptionPerHour = 1000;
|
||||
private static int lastHour;
|
||||
private static int exceptionCount;
|
||||
|
||||
// track some application statistics
|
||||
private static PackageMonitor lastPackageSent;
|
||||
private static PackageMonitor lastPackageReceived;
|
||||
|
||||
internal static void TelemetryLogTrace(string log, SeverityLevel severityLevel, bool flush = false)
|
||||
{
|
||||
int logCount = LogCounter.AddOrUpdate(log, 1, (key, value) => value + 1);
|
||||
var logCount = Logger.LogCounter.AddOrUpdate(log, 1, (key, value) => value + 1);
|
||||
Logger.Log(log);
|
||||
}
|
||||
|
||||
internal static void Log(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
private static void Log(string format, params object[] args)
|
||||
{
|
||||
Logger.Log(string.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
|
||||
internal static void Log(string log, bool clearLog = false, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
log = DateTime.Now.ToString("MM/dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + $"({Thread.CurrentThread.ManagedThreadId})" + log;
|
||||
|
||||
ManagedCommon.Logger.LogInfo(log, memberName, sourceFilePath, sourceLineNumber);
|
||||
lock (AllLogsLock)
|
||||
{
|
||||
if (clearLog)
|
||||
{
|
||||
allLogsIndex = 0;
|
||||
}
|
||||
|
||||
AllLogs[allLogsIndex] = log;
|
||||
allLogsIndex = (allLogsIndex + 1) % MAX_LOG;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Log(Exception e, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
if (e is not KnownException)
|
||||
{
|
||||
string exText = e.ToString();
|
||||
var exText = e.ToString();
|
||||
|
||||
Log($"!Exception!: {exText}", memberName, sourceFilePath, sourceLineNumber);
|
||||
Logger.Log($"!Exception!: {exText}", memberName, sourceFilePath, sourceLineNumber);
|
||||
|
||||
if (DateTime.UtcNow.Hour != lastHour)
|
||||
{
|
||||
@@ -71,172 +103,209 @@ internal static class Logger
|
||||
}
|
||||
}
|
||||
|
||||
private const string HeaderSENT =
|
||||
"Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},Ie{12},Ni{13}";
|
||||
|
||||
private const string HeaderRECEIVED =
|
||||
"Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},In{12},Ni{13},Pc{14}/{15}";
|
||||
|
||||
internal static void LogDebug(string log, bool clearLog = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
#if DEBUG
|
||||
Log(log, clearLog, memberName, sourceFilePath, sourceLineNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void Log(string log, bool clearLog = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
log = DateTime.Now.ToString("MM/dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + $"({Thread.CurrentThread.ManagedThreadId})" + log;
|
||||
|
||||
ManagedCommon.Logger.LogInfo(log, memberName, sourceFilePath, sourceLineNumber);
|
||||
lock (AllLogsLock)
|
||||
{
|
||||
if (clearLog)
|
||||
{
|
||||
allLogsIndex = 0;
|
||||
}
|
||||
|
||||
AllLogs[allLogsIndex] = log;
|
||||
allLogsIndex = (allLogsIndex + 1) % MAX_LOG;
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogDebug(string format, params object[] args)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.Log(format, args);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void Log(string format, params object[] args)
|
||||
{
|
||||
Logger.Log(string.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
|
||||
private static PackageMonitor lastPackageSent;
|
||||
private static PackageMonitor lastPackageReceived;
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogAll()
|
||||
internal static void LogDebug(string log, bool clearLog = false, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
string log;
|
||||
Logger.Log(log, clearLog, memberName, sourceFilePath, sourceLineNumber);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal static void LogStatistics()
|
||||
{
|
||||
if (!lastPackageSent.Equals(Package.PackageSent))
|
||||
{
|
||||
log = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"SENT:" + HeaderSENT,
|
||||
Package.PackageSent.Heartbeat,
|
||||
Package.PackageSent.Keyboard,
|
||||
Package.PackageSent.Mouse,
|
||||
Package.PackageSent.Hello,
|
||||
Package.PackageSent.Matrix,
|
||||
Package.PackageSent.ClipboardText,
|
||||
Package.PackageSent.ClipboardImage,
|
||||
Package.PackageSent.ByeBye,
|
||||
Package.PackageSent.Clipboard,
|
||||
Package.PackageSent.ClipboardDragDrop,
|
||||
Package.PackageSent.ClipboardDragDropEnd,
|
||||
Package.PackageSent.ExplorerDragDrop,
|
||||
Event.inputEventCount,
|
||||
Package.PackageSent.Nil);
|
||||
Log(log);
|
||||
lastPackageSent = Package.PackageSent; // Copy data
|
||||
var log =
|
||||
$"SENT:" +
|
||||
$"Be{Package.PackageSent.Heartbeat}," +
|
||||
$"Ke{Package.PackageSent.Keyboard}," +
|
||||
$"Mo{Package.PackageSent.Mouse}," +
|
||||
$"He{Package.PackageSent.Hello}," +
|
||||
$"Mx{Package.PackageSent.Matrix}," +
|
||||
$"Tx{Package.PackageSent.ClipboardText}," +
|
||||
$"Im{Package.PackageSent.ClipboardImage}," +
|
||||
$"By{Package.PackageSent.ByeBye}," +
|
||||
$"Cl{Package.PackageSent.Clipboard}," +
|
||||
$"Dr{Package.PackageSent.ClipboardDragDrop}," +
|
||||
$"De{Package.PackageSent.ClipboardDragDropEnd}," +
|
||||
$"Ed{Package.PackageSent.ExplorerDragDrop}," +
|
||||
$"Ie{Event.inputEventCount}," +
|
||||
$"Ni{Package.PackageSent.Nil}";
|
||||
Logger.Log(log);
|
||||
lastPackageSent = Package.PackageSent;
|
||||
}
|
||||
|
||||
if (!lastPackageReceived.Equals(Package.PackageReceived))
|
||||
{
|
||||
log = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"RECEIVED:" + HeaderRECEIVED,
|
||||
Package.PackageReceived.Heartbeat,
|
||||
Package.PackageReceived.Keyboard,
|
||||
Package.PackageReceived.Mouse,
|
||||
Package.PackageReceived.Hello,
|
||||
Package.PackageReceived.Matrix,
|
||||
Package.PackageReceived.ClipboardText,
|
||||
Package.PackageReceived.ClipboardImage,
|
||||
Package.PackageReceived.ByeBye,
|
||||
Package.PackageReceived.Clipboard,
|
||||
Package.PackageReceived.ClipboardDragDrop,
|
||||
Package.PackageReceived.ClipboardDragDropEnd,
|
||||
Package.PackageReceived.ExplorerDragDrop,
|
||||
Event.invalidPackageCount,
|
||||
Package.PackageReceived.Nil,
|
||||
Receiver.processedPackageCount,
|
||||
Receiver.skippedPackageCount);
|
||||
Log(log);
|
||||
var log =
|
||||
$"RECEIVED:" +
|
||||
$"Be{Package.PackageReceived.Heartbeat}," +
|
||||
$"Ke{Package.PackageReceived.Keyboard}," +
|
||||
$"Mo{Package.PackageReceived.Mouse}," +
|
||||
$"He{Package.PackageReceived.Hello}," +
|
||||
$"Mx{Package.PackageReceived.Matrix}," +
|
||||
$"Tx{Package.PackageReceived.ClipboardText}," +
|
||||
$"Im{Package.PackageReceived.ClipboardImage}," +
|
||||
$"By{Package.PackageReceived.ByeBye}," +
|
||||
$"Cl{Package.PackageReceived.Clipboard}," +
|
||||
$"Dr{Package.PackageReceived.ClipboardDragDrop}," +
|
||||
$"De{Package.PackageReceived.ClipboardDragDropEnd}," +
|
||||
$"Ed{Package.PackageReceived.ExplorerDragDrop}," +
|
||||
$"Ie{Event.invalidPackageCount}," +
|
||||
$"Ni{Package.PackageReceived.Nil}" +
|
||||
$"Pc{Receiver.processedPackageCount}/{Receiver.skippedPackageCount}";
|
||||
Logger.Log(log);
|
||||
lastPackageReceived = Package.PackageReceived;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void GenerateLog()
|
||||
{
|
||||
int l = Setting.Values.DumpObjectsLevel;
|
||||
if (l is > 0 and < 10)
|
||||
{
|
||||
Logger.DumpObjects(l);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ProcessThread> myThreads;
|
||||
|
||||
internal static void DumpObjects(int level)
|
||||
{
|
||||
try
|
||||
{
|
||||
string logFile = Path.Combine(Common.RunWithNoAdminRight ? Path.GetTempPath() : Path.GetDirectoryName(Application.ExecutablePath), "MagicMouse.log");
|
||||
|
||||
StringBuilder sb = new(1000000);
|
||||
string log;
|
||||
|
||||
myThreads = new List<ProcessThread>();
|
||||
|
||||
foreach (ProcessThread t in Process.GetCurrentProcess().Threads)
|
||||
{
|
||||
myThreads.Add(t);
|
||||
}
|
||||
|
||||
Logger.DumpProgramLogs(sb, level);
|
||||
Logger.DumpStaticTypes(sb, level);
|
||||
|
||||
log = string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
"{0} {1}\r\n{2}\r\n\r\n{3}",
|
||||
Application.ProductName,
|
||||
Application.ProductVersion,
|
||||
"Private Mem: " + (Process.GetCurrentProcess().PrivateMemorySize64 / 1024).ToString(CultureInfo.CurrentCulture) + "KB",
|
||||
sb.ToString());
|
||||
|
||||
if (!string.IsNullOrEmpty(Encryption.myKey))
|
||||
{
|
||||
log = log.Replace(Encryption.MyKey, Encryption.GetDebugInfo(Encryption.MyKey));
|
||||
}
|
||||
|
||||
log += Thread.DumpThreadsStack();
|
||||
log += $"\r\nCurrent process session: {Process.GetCurrentProcess().SessionId}, active console session: {NativeMethods.WTSGetActiveConsoleSessionId()}.";
|
||||
|
||||
File.WriteAllText(logFile, log);
|
||||
|
||||
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
_ = MessageBox.Show("Dump file created: " + logFile, Application.ProductName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.ShowToolTip("Dump file created: " + logFile + " and placed in the Clipboard.", 10000);
|
||||
Clipboard.SetText(logFile);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_ = MessageBox.Show(e.Message + "\r\n" + e.StackTrace, Application.ProductName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DumpProgramLogs(StringBuilder sb, int level)
|
||||
{
|
||||
_ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false);
|
||||
_ = Logger.PrivateDump(sb, AllLogs, "[Program Logs]\r\n===============\r\n", 0, level, true);
|
||||
}
|
||||
|
||||
internal static string DumpObjects(int level)
|
||||
{
|
||||
var sb = new StringBuilder(1000000);
|
||||
|
||||
Logger.DumpProgramLogs(sb, level);
|
||||
Logger.DumpStaticTypes(sb, level);
|
||||
|
||||
var log =
|
||||
$"{Application.ProductName} {Application.ProductVersion}\r\n" +
|
||||
$"Private Mem: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024}KB\r\n" +
|
||||
$"\r\n" +
|
||||
$"{sb}\r\n";
|
||||
|
||||
// obfuscate the current encryption key
|
||||
if (!string.IsNullOrEmpty(Encryption.myKey))
|
||||
{
|
||||
log = log.Replace(Encryption.MyKey, Logger.GetChecksum(Encryption.MyKey));
|
||||
}
|
||||
|
||||
log += Thread.DumpThreadsStack();
|
||||
log += "\r\n";
|
||||
log += $"Current process session: {Process.GetCurrentProcess().SessionId}\r\n";
|
||||
log += $"Active console session: {NativeMethods.WTSGetActiveConsoleSessionId()}";
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private static void DumpObject(StringBuilder sb, object obj, int level, Type t, int maxLevel)
|
||||
{
|
||||
if (t == typeof(Delegate))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is PackageType or string or AddressFamily or ID or IPAddress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var typeName = obj.GetType().ToString();
|
||||
if (typeName.EndsWith("type", StringComparison.CurrentCultureIgnoreCase)
|
||||
|| typeName.Contains("Cryptography")
|
||||
|| typeName.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var recurse = (obj is not null)
|
||||
&& (obj is not DATA)
|
||||
&& (obj.GetType().BaseType != typeof(ValueType))
|
||||
&& !obj.GetType().Namespace.Contains("System.Windows");
|
||||
var fi = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
foreach (var f in fi)
|
||||
{
|
||||
if (f.GetValue(obj) != AllLogs)
|
||||
{
|
||||
_ = PrivateDump(sb, f.GetValue(obj), f.Name, level + 1, maxLevel, recurse);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj is Dictionary<string, List<IPAddress>> dict)
|
||||
{
|
||||
foreach (var kvp in dict)
|
||||
{
|
||||
foreach (var ipAddress in kvp.Value)
|
||||
{
|
||||
_ = PrivateDump(sb, ipAddress, $"[{kvp.Key}]", level + 1, maxLevel, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (obj is Array arr)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj is string[] or int[] or uint[] or short[] or ushort[]
|
||||
or MachineInf[] or TcpClient[] or IPAddress[] or TcpSk[]
|
||||
or TcpServer[] or ProcessThread[] or Thread[])
|
||||
{
|
||||
for (var i = 0; i < arr.GetLength(0); i++)
|
||||
{
|
||||
_ = PrivateDump(sb, arr.GetValue(i), $"[{i}]", level + 1, maxLevel, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = PrivateDump(sb, $"{typeName}: N/A", typeName, level + 1, maxLevel, true);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool recurse)
|
||||
{
|
||||
if (obj == null || ((maxLevel is >= 0) && (level >= maxLevel)) || obj is Cursor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var objString = obj.ToString();
|
||||
var values = new string[7];
|
||||
values[0] = new string('-', Math.Max(level - 1, 0) * 2);
|
||||
values[1] = objName;
|
||||
/* values[2] = " "; */
|
||||
/* values[3] = t.FullName; */
|
||||
values[4] = " = ";
|
||||
values[5] = objName.Equals(nameof(Encryption.myKey), StringComparison.OrdinalIgnoreCase)
|
||||
? Logger.GetChecksum(objString)
|
||||
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
|
||||
? string.Empty
|
||||
: objString
|
||||
.Replace("System.Windows.Forms.", string.Empty)
|
||||
.Replace("System.Net.Sockets.", string.Empty)
|
||||
.Replace("System.Security.Cryptography.", string.Empty)
|
||||
.Replace("System.Threading.", string.Empty)
|
||||
.Replace("System.ComponentModel.", string.Empty)
|
||||
.Replace("System.Runtime.", string.Empty)
|
||||
.Replace("System.Drawing.", string.Empty)
|
||||
.Replace("System.Object", "O")
|
||||
.Replace("System.Diagnostics.", string.Empty)
|
||||
.Replace("System.Collections.", string.Empty)
|
||||
.Replace("System.Drawing.", string.Empty)
|
||||
.Replace("System.Int", string.Empty)
|
||||
.Replace("System.EventHandler.", string.Empty);
|
||||
values[6] = "\r\n";
|
||||
_ = sb.Append(string.Concat(values).Replace(Common.BinaryName, "MM"));
|
||||
|
||||
var t = obj.GetType();
|
||||
if (!recurse || t.IsPrimitive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.DumpObject(sb, obj, level, t, maxLevel);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void DumpStaticTypes(StringBuilder sb, int level)
|
||||
@@ -267,169 +336,7 @@ internal static class Logger
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool PrivateDump(StringBuilder sb, object obj, string objName, int level, int maxLevel, bool stop)
|
||||
{
|
||||
Type t;
|
||||
string padStr = string.Empty;
|
||||
string[] strArr;
|
||||
string objString;
|
||||
|
||||
if (obj == null || (maxLevel >= 0 && level >= maxLevel) || obj is Cursor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < level; i++)
|
||||
{
|
||||
padStr += i < level - 1 ? "-" : padStr += string.Empty;
|
||||
}
|
||||
|
||||
objString = obj.ToString();
|
||||
t = obj.GetType();
|
||||
strArr = new string[7];
|
||||
strArr[0] = padStr;
|
||||
strArr[1] = objName;
|
||||
|
||||
// strArr[2] = " ";
|
||||
// strArr[3] = t.FullName;
|
||||
strArr[4] = " = ";
|
||||
strArr[5] = objName.Equals("myKey", StringComparison.OrdinalIgnoreCase)
|
||||
? Encryption.GetDebugInfo(objString)
|
||||
: objName.Equals("lastClipboardObject", StringComparison.OrdinalIgnoreCase)
|
||||
? string.Empty
|
||||
: objString
|
||||
.Replace("System.Windows.Forms.", string.Empty)
|
||||
.Replace("System.Net.Sockets.", string.Empty)
|
||||
.Replace("System.Security.Cryptography.", string.Empty)
|
||||
.Replace("System.Threading.", string.Empty)
|
||||
.Replace("System.ComponentModel.", string.Empty)
|
||||
.Replace("System.Runtime.", string.Empty)
|
||||
.Replace("System.Drawing.", string.Empty)
|
||||
.Replace("System.Object", "O")
|
||||
.Replace("System.Diagnostics.", string.Empty)
|
||||
.Replace("System.Collections.", string.Empty)
|
||||
.Replace("System.Drawing.", string.Empty)
|
||||
.Replace("System.Int", string.Empty)
|
||||
.Replace("System.EventHandler.", string.Empty);
|
||||
strArr[6] = "\r\n";
|
||||
_ = sb.Append(string.Concat(strArr).Replace(Common.BinaryName, "MM"));
|
||||
|
||||
if (stop || t.IsPrimitive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.DumpObject(sb, obj, level, t, maxLevel);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void DumpObject(StringBuilder sb, object obj, int level, Type t, int maxLevel)
|
||||
{
|
||||
int i;
|
||||
bool stop;
|
||||
if (t == typeof(Delegate))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldInfo[] fi;
|
||||
string type;
|
||||
|
||||
if (obj is PackageType or string or AddressFamily or ID or IPAddress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
type = obj.GetType().ToString();
|
||||
|
||||
if (type.EndsWith("type", StringComparison.CurrentCultureIgnoreCase) || type.Contains("Cryptography")
|
||||
|| type.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
stop = obj == null || obj is DATA || obj.GetType().BaseType == typeof(ValueType)
|
||||
|| obj.GetType().Namespace.Contains("System.Windows");
|
||||
fi = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
|
||||
foreach (FieldInfo f in fi)
|
||||
{
|
||||
if (f.GetValue(obj) != AllLogs)
|
||||
{
|
||||
_ = PrivateDump(sb, f.GetValue(obj), f.Name, level + 1, maxLevel, stop);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj is Dictionary<string, List<IPAddress>>)
|
||||
{
|
||||
Dictionary<string, List<IPAddress>> d = obj as Dictionary<string, List<IPAddress>>;
|
||||
|
||||
foreach (string k in d.Keys)
|
||||
{
|
||||
if (d.TryGetValue(k, out List<IPAddress> l))
|
||||
{
|
||||
foreach (IPAddress ip in l)
|
||||
{
|
||||
_ = PrivateDump(sb, ip, "[" + k + "]", level + 1, maxLevel, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj is Array)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj is MachineInf[])
|
||||
{
|
||||
MachineInf[] os = (MachineInf[])obj;
|
||||
|
||||
for (i = 0; i < os.GetLength(0); i++)
|
||||
{
|
||||
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
|
||||
}
|
||||
}
|
||||
else if (obj is int[] || obj is uint[])
|
||||
{
|
||||
int[] os = (int[])obj;
|
||||
|
||||
for (i = 0; i < os.GetLength(0); i++)
|
||||
{
|
||||
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
|
||||
}
|
||||
}
|
||||
else if (obj is short[] || obj is ushort[])
|
||||
{
|
||||
short[] os = (short[])obj;
|
||||
|
||||
for (i = 0; i < os.GetLength(0); i++)
|
||||
{
|
||||
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
|
||||
}
|
||||
}
|
||||
else if (obj is TcpClient[] || obj is IPAddress[] || obj is TcpSk[] || obj is string[]
|
||||
|| obj is TcpServer[]
|
||||
|| obj is ProcessThread[] || obj is Thread[])
|
||||
{
|
||||
object[] os = (object[])obj;
|
||||
|
||||
for (i = 0; i < os.GetLength(0); i++)
|
||||
{
|
||||
_ = PrivateDump(sb, os[i], "[" + i + "]", level + 1, maxLevel, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = PrivateDump(sb, obj.GetType().ToString() + ": N/A", obj.GetType().ToString(), level + 1, maxLevel, false);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DumpType(StringBuilder sb, Type typeToDump, int level, int maxLevel)
|
||||
private static void DumpType(StringBuilder sb, Type typeToDump, int level, int maxLevel)
|
||||
{
|
||||
if ((typeToDump == typeof(Delegate))
|
||||
|| (typeToDump == typeof(PackageType))
|
||||
@@ -441,39 +348,45 @@ internal static class Logger
|
||||
return;
|
||||
}
|
||||
|
||||
var typeFullName = typeToDump.ToString();
|
||||
if (typeFullName.EndsWith("type", StringComparison.CurrentCultureIgnoreCase)
|
||||
|| typeFullName.Contains("Cryptography")
|
||||
|| typeFullName.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
|
||||
var typeName = typeToDump.ToString();
|
||||
if (typeName.EndsWith("type", StringComparison.CurrentCultureIgnoreCase)
|
||||
|| typeName.Contains("Cryptography")
|
||||
|| typeName.EndsWith("AsyncEventBits", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stop = (typeToDump == null)
|
||||
|| (typeToDump == typeof(DATA))
|
||||
|| (typeToDump.BaseType == typeof(ValueType))
|
||||
|| typeToDump.Namespace.Contains("System.Windows");
|
||||
var recurse = (typeToDump is not null)
|
||||
&& (typeToDump != typeof(DATA))
|
||||
&& (typeToDump.BaseType != typeof(ValueType))
|
||||
&& !typeToDump.Namespace.Contains("System.Windows");
|
||||
|
||||
var fieldInfos = typeToDump.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
foreach (var fieldInfo in fieldInfos)
|
||||
{
|
||||
if (fieldInfo.GetValue(null) != AllLogs)
|
||||
var fieldValue = fieldInfo.GetValue(null);
|
||||
if (fieldValue != AllLogs)
|
||||
{
|
||||
_ = Logger.PrivateDump(sb, fieldInfo.GetValue(null), fieldInfo.Name, level + 1, maxLevel, stop);
|
||||
_ = Logger.PrivateDump(sb, fieldValue, fieldInfo.Name, level + 1, maxLevel, recurse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a basic checksum of the given string to be written to logs
|
||||
/// for quick verification without revealing the original sensitive value.
|
||||
/// </summary>
|
||||
internal static string GetChecksum(string st)
|
||||
{
|
||||
return string.IsNullOrEmpty(st)
|
||||
? st
|
||||
: ((byte)(Common.GetBytesU(st).Sum(value => value) % 256)).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static string GetStackTrace(StackTrace st)
|
||||
{
|
||||
string rv = string.Empty;
|
||||
|
||||
for (int i = 0; i < st.FrameCount; i++)
|
||||
{
|
||||
StackFrame sf = st.GetFrame(i);
|
||||
rv += sf.GetMethod() + " <= ";
|
||||
}
|
||||
|
||||
return rv;
|
||||
return string.Join(
|
||||
" <= ",
|
||||
st.GetFrames().Select(frame => frame.GetMethod().ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
// 2009-... modified by Truong Do (TruongDo).
|
||||
// 2023- Included in PowerToys.
|
||||
// </history>
|
||||
namespace MouseWithoutBorders.Class
|
||||
namespace MouseWithoutBorders.Core;
|
||||
|
||||
internal enum SeverityLevel
|
||||
{
|
||||
internal class SeverityLevel
|
||||
{
|
||||
internal static readonly SeverityLevel Information = new SeverityLevel();
|
||||
internal static readonly SeverityLevel Error = new SeverityLevel();
|
||||
internal static readonly SeverityLevel Warning = new SeverityLevel();
|
||||
}
|
||||
Information,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
@@ -68,7 +68,7 @@ internal sealed class Thread
|
||||
|
||||
internal static string DumpThreadsStack()
|
||||
{
|
||||
string stack = "\r\nMANAGED THREADS: " + threads.Count.ToString(CultureInfo.InvariantCulture) + "\r\n";
|
||||
string stack = "MANAGED THREADS: " + threads.Count.ToString(CultureInfo.InvariantCulture);
|
||||
stack += Logger.GetStackTrace(new StackTrace());
|
||||
return stack;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ using MouseWithoutBorders.Class;
|
||||
using MouseWithoutBorders.Core;
|
||||
using MouseWithoutBorders.Properties;
|
||||
|
||||
using Clipboard = MouseWithoutBorders.Core.Clipboard;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.frmScreen.#ShowMouseWithoutBordersUiOnWinLogonDesktop(System.Boolean)", Justification = "Dotnet port with style preservation")]
|
||||
@@ -550,7 +551,7 @@ namespace MouseWithoutBorders
|
||||
|
||||
if (count % 20 == 0)
|
||||
{
|
||||
Logger.LogAll();
|
||||
Logger.LogStatistics();
|
||||
|
||||
// Need to review this code on why it is needed (moved from MoveToMyNeighbourIfNeeded(...))
|
||||
for (int i = 0; i < MachineStuff.MachineMatrix.Length; i++)
|
||||
@@ -1215,7 +1216,29 @@ namespace MouseWithoutBorders
|
||||
|
||||
private void MenuGenDumpFile_Click(object sender, EventArgs e)
|
||||
{
|
||||
Logger.GenerateLog();
|
||||
int l = Setting.Values.DumpObjectsLevel;
|
||||
if (l is > 0 and < 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
string logFile = Path.Combine(Common.RunWithNoAdminRight ? Path.GetTempPath() : Path.GetDirectoryName(Application.ExecutablePath), "MagicMouse.log");
|
||||
var log = Logger.DumpObjects(l);
|
||||
File.WriteAllText(logFile, log);
|
||||
if (Common.RunOnLogonDesktop || Common.RunOnScrSaverDesktop)
|
||||
{
|
||||
_ = MessageBox.Show("Dump file created: " + logFile, Application.ProductName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Common.ShowToolTip("Dump file created: " + logFile + " and placed in the Clipboard.", 10000);
|
||||
Clipboard.SetText(logFile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_ = MessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Application.ProductName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MainMenu_Opening(object sender, CancelEventArgs e)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Program logs]
|
||||
[Program Logs]
|
||||
===============
|
||||
= System.String[]
|
||||
[Clipboard]
|
||||
@@ -199,15 +199,6 @@ HelperProcessName = PowerToys.MouseWithoutBordersHelper
|
||||
===============
|
||||
[Logger]
|
||||
===============
|
||||
AllLogsLock = Lock
|
||||
--_owningThreadId = 0
|
||||
--_state = 0
|
||||
--_recursionCount = 0
|
||||
--_spinCount = 22
|
||||
--_waiterStartTimeMs = 0
|
||||
--s_contentionCount = 0
|
||||
--s_maxSpinCount = 22
|
||||
--s_minSpinCountForAdaptiveSpin = -100
|
||||
LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
|
||||
--_tables = Concurrent.ConcurrentDictionary`2+Tables[System.String,32]
|
||||
----_comparer = Generic.NonRandomizedStringEqualityComparer+OrdinalComparer
|
||||
@@ -228,6 +219,15 @@ LogCounter = Concurrent.ConcurrentDictionary`2[System.String,32]
|
||||
--_budget = ????????????
|
||||
--_growLockArray = True
|
||||
--_comparerIsDefaultForClasses = False
|
||||
AllLogsLock = Lock
|
||||
--_owningThreadId = 0
|
||||
--_state = 0
|
||||
--_recursionCount = 0
|
||||
--_spinCount = 22
|
||||
--_waiterStartTimeMs = 0
|
||||
--s_contentionCount = 0
|
||||
--s_maxSpinCount = 22
|
||||
--s_minSpinCountForAdaptiveSpin = -100
|
||||
allLogsIndex = 0
|
||||
lastHour = 0
|
||||
exceptionCount = 0
|
||||
@@ -263,8 +263,6 @@ lastPackageReceived = MouseWithoutBorders.Core.PackageMonitor
|
||||
--Nil = 0
|
||||
MAX_LOG = 10000
|
||||
MaxLogExceptionPerHour = 1000
|
||||
HeaderSENT = Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},Ie{12},Ni{13}
|
||||
HeaderRECEIVED = Be{0},Ke{1},Mo{2},He{3},Mx{4},Tx{5},Im{6},By{7},Cl{8},Dr{9},De{10},Ed{11},In{12},Ni{13},Pc{14}/{15}
|
||||
[MachineStuff]
|
||||
===============
|
||||
McMatrixLock = Lock
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -14,6 +16,94 @@ namespace MouseWithoutBorders.UnitTests.Core;
|
||||
|
||||
public static class LoggerTests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class GetStackTraceTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetStackTraceShouldReturnCorrectValue()
|
||||
{
|
||||
// if we get the stack trace from the current test method with
|
||||
// "new StackTrace()" it's incredibly deep as it contains the
|
||||
// full MSTest call stack so we'll create a Task to capture a
|
||||
// much shallower stack trace from inside the task
|
||||
var stackTrace = default(StackTrace);
|
||||
Task.Run(() =>
|
||||
{
|
||||
void MyMethod1()
|
||||
{
|
||||
MyMethod2();
|
||||
}
|
||||
|
||||
void MyMethod2()
|
||||
{
|
||||
stackTrace = new StackTrace();
|
||||
}
|
||||
|
||||
MyMethod1();
|
||||
}).Wait();
|
||||
|
||||
/*
|
||||
// even with the above, the stack trace can still vary depending on the test runner host
|
||||
// being used (e.g. visual studio vs dotnet test) and the version of dotnet being used -
|
||||
// e.g.
|
||||
//
|
||||
// Void<GetStackTraceShouldReturnCorrectValue> g__MyMethod2| 2()
|
||||
// <= Void <GetStackTraceShouldReturnCorrectValue> g__MyMethod1 | 1()
|
||||
// <= Void <GetStackTraceShouldReturnCorrectValue> b__0()
|
||||
// <= Void RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// <= Void ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
|
||||
// ... etc ...
|
||||
//
|
||||
// versus
|
||||
//
|
||||
// Void<GetStackTraceShouldReturnCorrectValue> g__MyMethod2| 2()
|
||||
// <= Void <GetStackTraceShouldReturnCorrectValue> g__MyMethod1 | 1()
|
||||
// <= Void <GetStackTraceShouldReturnCorrectValue> b__0()
|
||||
// <= Void RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
|
||||
// ^^^^^^^^^^^
|
||||
// <= Void ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
|
||||
// ... etc ...
|
||||
//
|
||||
// so we're going to ignore everything after our task code.
|
||||
//
|
||||
// however, this is *still* inherently unstable due to compiler-generated uniqueness keys - e.g.
|
||||
//
|
||||
// Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod1|2()
|
||||
// ^
|
||||
//
|
||||
// versus
|
||||
//
|
||||
// Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod1|1()
|
||||
// ^
|
||||
// but we'll try to suppress the differences with a normalization regex
|
||||
// (which is giving diminishing returns on the value of this test, but
|
||||
// we'll persevere...)
|
||||
*/
|
||||
|
||||
string NormalizeStackTrace(string stackTrace, [CallerMemberName] string caller = "")
|
||||
{
|
||||
return string.Join(
|
||||
" <= ",
|
||||
stackTrace
|
||||
.Split(" <= ")
|
||||
.TakeWhile(line => line.Contains(caller, StringComparison.InvariantCulture))
|
||||
.Select(f => f.Split('|')[0])
|
||||
.ToList());
|
||||
}
|
||||
|
||||
var expected = NormalizeStackTrace(
|
||||
"Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod2|2()" +
|
||||
" <= Void <GetStackTraceShouldReturnCorrectValue>g__MyMethod1|1()" +
|
||||
" <= Void <GetStackTraceShouldReturnCorrectValue>b__0()");
|
||||
|
||||
var actual = NormalizeStackTrace(
|
||||
Logger.GetStackTrace(stackTrace));
|
||||
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public sealed class PrivateDumpTests
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PackageName: GIMP.GIMP
|
||||
Name: GIMP
|
||||
WindowFilter: "gimp*.exe"
|
||||
WindowFilter: "gimp-3.exe"
|
||||
BackgroundProcess: false
|
||||
Shortcuts:
|
||||
- SectionName: Help
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PackageName: JetBrains.IntelliJIDEA.Community
|
||||
Name: IntelliJ IDEA
|
||||
WindowFilter: "idea*.exe"
|
||||
WindowFilter: "idea64.exe"
|
||||
BackgroundProcess: false
|
||||
Shortcuts:
|
||||
- SectionName: Top shortcuts
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using ShortcutGuide.Models;
|
||||
using Windows.System;
|
||||
|
||||
namespace ShortcutGuide.Converters
|
||||
{
|
||||
@@ -58,10 +56,22 @@ namespace ShortcutGuide.Converters
|
||||
|
||||
foreach (var key in description.Keys)
|
||||
{
|
||||
// Try to parse a string key number to a VirtualKey
|
||||
// Try to parse a string key number to a key code
|
||||
if (int.TryParse(key, out int keyCode))
|
||||
{
|
||||
shortcutList.Add(keyCode);
|
||||
switch (keyCode)
|
||||
{
|
||||
// https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348
|
||||
case 38: // The Up Arrow key or button.
|
||||
case 40: // The Down Arrow key or button.
|
||||
case 37: // The Left Arrow key or button.
|
||||
case 39: // The Right Arrow key or button.
|
||||
shortcutList.Add(keyCode);
|
||||
break;
|
||||
default:
|
||||
shortcutList.Add(Microsoft.PowerToys.Settings.UI.Library.Utilities.Helper.GetKeyName((uint)keyCode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,10 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using ShortcutGuide.Models;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
@@ -39,15 +37,20 @@ namespace ShortcutGuide.Helpers
|
||||
public static ShortcutFile GetShortcutsOfApplication(string applicationName)
|
||||
{
|
||||
string path = PathOfManifestFiles;
|
||||
IEnumerable<string> files = Directory.EnumerateFiles(path, applicationName + ".*.yml") ??
|
||||
throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}'.");
|
||||
string localizedPath = Path.Combine(path, applicationName + $".{Language}.yml");
|
||||
string fallbackPath = Path.Combine(path, applicationName + ".en-US.yml");
|
||||
|
||||
IEnumerable<string> filesEnumerable = files as string[] ?? [.. files];
|
||||
return filesEnumerable.Any(f => f.EndsWith($".{Language}.yml", StringComparison.InvariantCulture))
|
||||
? YamlToShortcutList(File.ReadAllText(Path.Combine(path, applicationName + $".{Language}.yml")))
|
||||
: filesEnumerable.Any(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture))
|
||||
? YamlToShortcutList(File.ReadAllText(filesEnumerable.First(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture))))
|
||||
: throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}' with the language '{Language}' or 'en-US'.");
|
||||
if (File.Exists(localizedPath))
|
||||
{
|
||||
return YamlToShortcutList(File.ReadAllText(localizedPath));
|
||||
}
|
||||
|
||||
if (File.Exists(fallbackPath))
|
||||
{
|
||||
return YamlToShortcutList(File.ReadAllText(fallbackPath));
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}' with the language '{Language}' or 'en-US'.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,41 +64,56 @@ namespace ShortcutGuide.Helpers
|
||||
return deserializer.Deserialize<ShortcutFile>(content);
|
||||
}
|
||||
|
||||
private static readonly object IndexLock = new();
|
||||
private static IndexFile? cachedIndexFile;
|
||||
private static DateTime cachedIndexLastWriteTimeUtc;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the index YAML file that contains the list of all applications and their shortcuts from the cache.
|
||||
/// </summary>
|
||||
/// <returns>A deserialized <see cref="IndexFile"/> object.</returns>
|
||||
public static IndexFile GetCachedIndexYamlFile()
|
||||
{
|
||||
string indexPath = Path.Combine(PathOfManifestFiles, "index.yml");
|
||||
|
||||
lock (IndexLock)
|
||||
{
|
||||
DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(indexPath);
|
||||
if (cachedIndexFile is not null && cachedIndexLastWriteTimeUtc == lastWriteTimeUtc)
|
||||
{
|
||||
return cachedIndexFile.Value;
|
||||
}
|
||||
|
||||
string content = File.ReadAllText(indexPath);
|
||||
Deserializer deserializer = new();
|
||||
|
||||
cachedIndexFile = deserializer.Deserialize<IndexFile>(content);
|
||||
cachedIndexLastWriteTimeUtc = lastWriteTimeUtc;
|
||||
|
||||
return cachedIndexFile.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the directory where the manifest files are stored.
|
||||
/// </summary>
|
||||
public static string PathOfManifestFiles => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WinGet", "KeyboardShortcuts");
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the index YAML file that contains the list of all applications and their shortcuts.
|
||||
/// </summary>
|
||||
/// <returns>A deserialized <see cref="IndexFile"/> object.</returns>
|
||||
public static IndexFile GetIndexYamlFile()
|
||||
{
|
||||
string path = PathOfManifestFiles;
|
||||
string content = File.ReadAllText(Path.Combine(path, "index.yml"));
|
||||
Deserializer deserializer = new();
|
||||
return deserializer.Deserialize<IndexFile>(content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all application IDs that should be displayed, based on the foreground window and background processes.
|
||||
/// </summary>
|
||||
/// <param name="foregroundWindowHandle">The window handle captured before Shortcut Guide UI takes focus.</param>
|
||||
/// <returns>
|
||||
/// A dictionary mapping each application ID to the full path of the executable
|
||||
/// that caused the match (used for icon extraction), or <c>null</c> when no
|
||||
/// specific executable is associated (for example, wildcard filters like the
|
||||
/// default shell).
|
||||
/// </returns>
|
||||
public static Dictionary<string, string?> GetAllCurrentApplicationIds()
|
||||
public static Dictionary<string, string?> GetAllCurrentApplicationIds(nint foregroundWindowHandle)
|
||||
{
|
||||
nint handle = NativeMethods.GetForegroundWindow();
|
||||
|
||||
Dictionary<string, string?> applicationIds = new(StringComparer.Ordinal);
|
||||
|
||||
Process[] processes = Process.GetProcesses();
|
||||
|
||||
if (NativeMethods.GetWindowThreadProcessId(handle, out uint processId) > 0)
|
||||
if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out uint processId) > 0)
|
||||
{
|
||||
string? name = null;
|
||||
string? executablePath = null;
|
||||
@@ -119,7 +137,7 @@ namespace ShortcutGuide.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
IndexFile.IndexItem match = GetIndexYamlFile().Index.First((s) => !s.BackgroundProcess && IsMatch(name, s.WindowFilter));
|
||||
IndexFile.IndexItem match = ManifestInterpreter.GetCachedIndexYamlFile().Index.First((s) => !s.BackgroundProcess && IsMatch(name, s.WindowFilter));
|
||||
string? pathForApp = match.WindowFilter == "*" ? null : executablePath;
|
||||
foreach (var item in match.Apps)
|
||||
{
|
||||
@@ -132,48 +150,52 @@ namespace ShortcutGuide.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in GetIndexYamlFile().Index.Where((s) => s.BackgroundProcess))
|
||||
foreach (var item in GetCachedIndexYamlFile().Index.Where((s) => s.BackgroundProcess))
|
||||
{
|
||||
try
|
||||
{
|
||||
string? matchedExecutablePath = null;
|
||||
bool matched = false;
|
||||
foreach (var p in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsMatch(p.MainModule!.ModuleName, item.WindowFilter))
|
||||
{
|
||||
matched = true;
|
||||
if (item.WindowFilter != "*")
|
||||
{
|
||||
matchedExecutablePath = p.MainModule!.FileName;
|
||||
}
|
||||
string filter = item.WindowFilter;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Win32Exception)
|
||||
{
|
||||
// Access denied for elevated processes; skip.
|
||||
}
|
||||
if (filter.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
filter = filter[..^4];
|
||||
}
|
||||
|
||||
if (filter == "*")
|
||||
{
|
||||
foreach (var app in item.Apps)
|
||||
{
|
||||
applicationIds[app] = null;
|
||||
}
|
||||
|
||||
if (matched)
|
||||
continue;
|
||||
}
|
||||
|
||||
Process[] foundProcesses = [];
|
||||
|
||||
try
|
||||
{
|
||||
foundProcesses = Process.GetProcessesByName(filter);
|
||||
if (foundProcesses.Length > 0)
|
||||
{
|
||||
foreach (var app in item.Apps)
|
||||
{
|
||||
// Preserve an existing (foreground) path if one was already set;
|
||||
// only fill in a path when the slot is currently null.
|
||||
if (!applicationIds.TryGetValue(app, out string? existing) || existing is null)
|
||||
{
|
||||
applicationIds[app] = matchedExecutablePath;
|
||||
}
|
||||
applicationIds[app] = foundProcesses[0].MainModule?.FileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
catch (Win32Exception ex)
|
||||
{
|
||||
Trace.WriteLine($"Failed to inspect background process '{filter}': {ex.Message}");
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Trace.WriteLine($"Failed to inspect background process '{filter}': {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var process in foundProcesses)
|
||||
{
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,10 +203,22 @@ namespace ShortcutGuide.Helpers
|
||||
|
||||
static bool IsMatch(string input, string filter)
|
||||
{
|
||||
input = input.ToLower(CultureInfo.InvariantCulture);
|
||||
filter = filter.ToLower(CultureInfo.InvariantCulture);
|
||||
string regexPattern = "^" + Regex.Escape(filter).Replace("\\*", ".*") + "$";
|
||||
return Regex.IsMatch(input, regexPattern);
|
||||
if (filter == "*")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (input.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
input = input[..^4];
|
||||
}
|
||||
|
||||
if (filter.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filter = filter[..^4];
|
||||
}
|
||||
|
||||
return string.Equals(input, filter, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using ShortcutGuide.Models;
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using static ShortcutGuide.Helpers.ResourceLoaderInstance;
|
||||
|
||||
@@ -24,6 +26,12 @@ namespace ShortcutGuide.Helpers
|
||||
{
|
||||
string path = Path.Combine(ManifestInterpreter.PathOfManifestFiles, $"Microsoft.PowerToys.{ManifestInterpreter.Language}.yml");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Logger.LogWarning($"PowerToys manifest file not found: '{path}'. PowerToys-specific shortcuts will not appear in ShortcutGuide.");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder content = new(File.ReadAllText(path));
|
||||
|
||||
const string populateStartString = "# <Populate start>";
|
||||
@@ -32,7 +40,9 @@ namespace ShortcutGuide.Helpers
|
||||
content = new(PopulateRegex().Replace(content.ToString(), populateStartString + Environment.NewLine));
|
||||
|
||||
SettingsUtils settingsUtils = SettingsUtils.Default;
|
||||
EnabledModules enabledModules = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.Enabled;
|
||||
SettingsRepository<GeneralSettings> settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
|
||||
settingsRepository.ReloadSettings();
|
||||
EnabledModules enabledModules = settingsRepository.SettingsConfig.Enabled;
|
||||
if (enabledModules.AdvancedPaste)
|
||||
{
|
||||
AdvancedPasteProperties advancedPasteProperties = SettingsRepository<AdvancedPasteSettings>.GetInstance(settingsUtils).SettingsConfig.Properties;
|
||||
@@ -57,11 +67,19 @@ namespace ShortcutGuide.Helpers
|
||||
content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp3.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp3/Header")));
|
||||
content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp4.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp4/Header")));
|
||||
}
|
||||
|
||||
foreach (var action in advancedPasteProperties.CustomActions.Value.Where(a => a.IsShown))
|
||||
{
|
||||
content.Append(HotkeySettingsToYaml(action.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), action.Name));
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledModules.AlwaysOnTop)
|
||||
{
|
||||
content.Append(HotkeySettingsToYaml(SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.Hotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_ShortDescription")));
|
||||
AlwaysOnTopProperties alwaysOnTopProperties = SettingsRepository<AlwaysOnTopSettings>.GetInstance(settingsUtils).SettingsConfig.Properties;
|
||||
content.Append(HotkeySettingsToYaml(alwaysOnTopProperties.Hotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_ShortDescription")));
|
||||
content.Append(HotkeySettingsToYaml(alwaysOnTopProperties.IncreaseOpacityHotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_IncreaseOpacityShortcut/Header")));
|
||||
content.Append(HotkeySettingsToYaml(alwaysOnTopProperties.DecreaseOpacityHotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_DecreaseOpacityShortcut/Header")));
|
||||
}
|
||||
|
||||
if (enabledModules.ColorPicker)
|
||||
@@ -79,6 +97,7 @@ namespace ShortcutGuide.Helpers
|
||||
CropAndLockProperties cropAndLockProperties = SettingsRepository<CropAndLockSettings>.GetInstance(settingsUtils).SettingsConfig.Properties;
|
||||
content.Append(HotkeySettingsToYaml(cropAndLockProperties.ThumbnailHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Thumbnail")));
|
||||
content.Append(HotkeySettingsToYaml(cropAndLockProperties.ReparentHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Reparent")));
|
||||
content.Append(HotkeySettingsToYaml(cropAndLockProperties.ScreenshotHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Screenshot")));
|
||||
}
|
||||
|
||||
if (enabledModules.CursorWrap)
|
||||
@@ -116,6 +135,11 @@ namespace ShortcutGuide.Helpers
|
||||
content.Append(HotkeySettingsToYaml(SettingsRepository<PeekSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("Peek/ModuleTitle")));
|
||||
}
|
||||
|
||||
if (enabledModules.PowerDisplay)
|
||||
{
|
||||
content.Append(HotkeySettingsToYaml(SettingsRepository<PowerDisplaySettings>.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("PowerDisplay/ModuleTitle"), SettingsResourceLoader.GetString("Launch_PowerDisplay/Content")));
|
||||
}
|
||||
|
||||
if (enabledModules.PowerLauncher)
|
||||
{
|
||||
content.Append(HotkeySettingsToYaml(SettingsRepository<PowerLauncherSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.OpenPowerLauncher, SettingsResourceLoader.GetString("PowerLauncher/ModuleTitle")));
|
||||
@@ -128,7 +152,7 @@ namespace ShortcutGuide.Helpers
|
||||
|
||||
if (enabledModules.ShortcutGuide)
|
||||
{
|
||||
content.Append(HotkeySettingsToYaml(SettingsRepository<ShortcutGuideSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.DefaultOpenShortcutGuide, SettingsResourceLoader.GetString("ShortcutGuide/ModuleTitle"), SettingsResourceLoader.GetString("ShortcutGuide_ShortDescription")));
|
||||
content.Append(HotkeySettingsToYaml(SettingsRepository<ShortcutGuideSettings>.GetInstance(settingsUtils).SettingsConfig.Properties.OpenShortcutGuide, SettingsResourceLoader.GetString("ShortcutGuide/ModuleTitle"), SettingsResourceLoader.GetString("ShortcutGuide_ShortDescription")));
|
||||
}
|
||||
|
||||
if (enabledModules.PowerOcr)
|
||||
@@ -188,6 +212,11 @@ namespace ShortcutGuide.Helpers
|
||||
/// <inheritdoc cref="HotkeySettingsToYaml(HotkeySettings, string, string?)"/>
|
||||
private static string HotkeySettingsToYaml(KeyboardKeysProperty hotkeySettings, string moduleName, string? description = null)
|
||||
{
|
||||
if (hotkeySettings is null)
|
||||
{
|
||||
return HotkeySettingsToYaml(new HotkeySettings(), moduleName, description);
|
||||
}
|
||||
|
||||
return HotkeySettingsToYaml(hotkeySettings.Value, moduleName, description);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ShortcutGuide.Models
|
||||
{
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
using Windows.UI.Text;
|
||||
using static ShortcutGuide.Models.ShortcutEntry;
|
||||
using Orientation = Microsoft.UI.Xaml.Controls.Orientation;
|
||||
|
||||
namespace ShortcutGuide.Models
|
||||
{
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ShortcutGuide.Models
|
||||
{
|
||||
internal sealed class ShortcutPageNavParam
|
||||
|
||||
@@ -19,9 +19,14 @@ namespace ShortcutGuide
|
||||
{
|
||||
public sealed class Program
|
||||
{
|
||||
public static Thread CopyAndIndexGenerationThread { get; private set; } = null!;
|
||||
|
||||
public static nint ForegroundWindowHandle { get; private set; } = nint.Zero;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
ForegroundWindowHandle = NativeMethods.GetForegroundWindow();
|
||||
Logger.InitializeLogger("\\ShortcutGuide\\Logs");
|
||||
|
||||
// The module interface passes: <powertoys_pid> [telemetry]
|
||||
@@ -54,28 +59,60 @@ namespace ShortcutGuide
|
||||
"ShortcutGuide",
|
||||
"Manifests");
|
||||
|
||||
try
|
||||
CopyAndIndexGenerationThread = new Thread(() =>
|
||||
{
|
||||
foreach (string sourceFile in Directory.EnumerateFiles(sourceManifestFolder, "*.yml"))
|
||||
try
|
||||
{
|
||||
string destinationFile = Path.Combine(ManifestInterpreter.PathOfManifestFiles, Path.GetFileName(sourceFile));
|
||||
File.Copy(sourceFile, destinationFile, true);
|
||||
foreach (string sourceFile in Directory.EnumerateFiles(sourceManifestFolder, "*.yml"))
|
||||
{
|
||||
string destinationFile = Path.Combine(ManifestInterpreter.PathOfManifestFiles, Path.GetFileName(sourceFile));
|
||||
File.Copy(sourceFile, destinationFile, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to copy bundled shortcut manifests from '{sourceManifestFolder}'.", ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to copy bundled shortcut manifests from '{sourceManifestFolder}'.", ex);
|
||||
}
|
||||
|
||||
Process indexGeneration = Process.Start(Path.GetDirectoryName(Environment.ProcessPath) + "\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe");
|
||||
indexGeneration.WaitForExit();
|
||||
if (indexGeneration.ExitCode != 0)
|
||||
{
|
||||
Logger.LogError($"Index generation failed with exit code {indexGeneration.ExitCode}. There may be a corrupt shortcuts file in \"{ManifestInterpreter.PathOfManifestFiles}\".");
|
||||
return;
|
||||
}
|
||||
string indexGeneratorPath = Path.Combine(
|
||||
Path.GetDirectoryName(Environment.ProcessPath)!,
|
||||
"PowerToys.ShortcutGuide.IndexYmlGenerator.exe");
|
||||
|
||||
PowerToysShortcutsPopulator.Populate();
|
||||
try
|
||||
{
|
||||
using Process? indexGeneration = Process.Start(indexGeneratorPath);
|
||||
|
||||
if (indexGeneration is null)
|
||||
{
|
||||
Logger.LogError($"Failed to start index generation process '{indexGeneratorPath}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
indexGeneration.WaitForExit();
|
||||
|
||||
if (indexGeneration.ExitCode != 0)
|
||||
{
|
||||
Logger.LogError($"Index generation failed with exit code {indexGeneration.ExitCode}. There may be a corrupt shortcuts file in \"{ManifestInterpreter.PathOfManifestFiles}\".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to start or wait for index generation process '{indexGeneratorPath}'.", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PowerToysShortcutsPopulator.Populate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to populate PowerToys shortcuts in manifest.", ex);
|
||||
}
|
||||
});
|
||||
CopyAndIndexGenerationThread.IsBackground = true;
|
||||
CopyAndIndexGenerationThread.Start();
|
||||
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<Content Remove="Assets\CopilotKey.png" />
|
||||
<Content Remove="Assets\ShortcutGuide\HeroImage.png" />
|
||||
<Content Remove="Assets\ShortcutGuide\ShortcutGuide.ico" />
|
||||
<Content Remove="Assets\ShortcutGuide\Manifests\*.yml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -29,6 +29,20 @@
|
||||
TrueValue="Collapsed" />
|
||||
<tkconverters:StringVisibilityConverter x:Name="StringVisibilityConverter" />
|
||||
<converters:ShortcutDescriptionToKeysConverter x:Name="ShortcutDescriptionToKeysConverter" />
|
||||
|
||||
<!--
|
||||
Path data for the 4-square Windows logo icon used by the "Windows" nav entry in MainWindow.
|
||||
Sized for a 20x20 viewbox to match the icon slot in CustomNavigationViewStyle.
|
||||
Same geometry as src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/ShortcutConflictWindow.xaml.
|
||||
Defined here because the nav item is created in code-behind.
|
||||
-->
|
||||
<x:String x:Key="WindowsLogoPathData">M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z</x:String>
|
||||
|
||||
<!--
|
||||
Path data for the PowerToys app icon (rounded square with menu bar and three dots) used in the apps nav list.
|
||||
Scaled from the 64x64 source SVG to fit a 20x20 viewbox; F0 = even-odd fill so the frame strokes render correctly.
|
||||
-->
|
||||
<x:String x:Key="PowerToysLogoPathData">F0 M17.5 0C18.8807 0 20 1.1193 20 2.5V17.5C20 18.8807 18.8807 20 17.5 20H2.5C1.1193 20 0 18.8807 0 17.5V2.5C0 1.1193 1.1193 0 2.5 0H17.5ZM2.5 1.25C1.8096 1.25 1.25 1.8096 1.25 2.5V17.5C1.25 18.1903 1.8096 18.75 2.5 18.75H17.5C18.1903 18.75 18.75 18.1903 18.75 17.5V2.5C18.75 1.8096 18.1903 1.25 17.5 1.25H2.5ZM3.75 15a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 1 0 -2.5 0ZM8.75 15a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 1 0 -2.5 0ZM13.75 15a1.25 1.25 0 1 0 2.5 0a1.25 1.25 0 1 0 -2.5 0ZM15 3.75C15.6903 3.75 16.25 4.3097 16.25 5V10C16.25 10.6903 15.6903 11.25 15 11.25H5C4.3097 11.25 3.75 10.6903 3.75 10V5C3.75 4.3097 4.3097 3.75 5 3.75H15ZM5 5V10H15V5H5Z</x:String>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace ShortcutGuide
|
||||
PowerToysTelemetry.Log.WriteEvent(new ShortcutGuideSessionEvent(
|
||||
MainWindow.SessionDurationMs,
|
||||
MainWindow.CloseType));
|
||||
Current.Exit();
|
||||
TaskBarWindow.Close();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Windows.Markup;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using ShortcutGuide.Helpers;
|
||||
|
||||
namespace ShortcutGuide.Controls;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
|
||||
@@ -38,13 +38,6 @@
|
||||
Style="{StaticResource RailNavigationViewStyle}">
|
||||
<NavigationView.MenuItems />
|
||||
<NavigationView.FooterMenuItems>
|
||||
<!-- If footer only has one item, a visual bug can occur. This fake settings button will have set height to zero as soon as the content is loaded -->
|
||||
<NavigationViewItem
|
||||
x:Name="FakeSettingsButton"
|
||||
Icon="Setting"
|
||||
SelectsOnInvoked="False"
|
||||
Tag="Settings"
|
||||
Tapped="Settings_Tapped" />
|
||||
<NavigationViewItem
|
||||
x:Uid="SettingsButton"
|
||||
Icon="Setting"
|
||||
|
||||
@@ -7,19 +7,20 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using ShortcutGuide.Helpers;
|
||||
using ShortcutGuide.Models;
|
||||
using ShortcutGuide.Pages;
|
||||
using ShortcutGuide.Telemetry;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics;
|
||||
using Windows.System;
|
||||
@@ -30,10 +31,11 @@ using WinUIEx.Messaging;
|
||||
|
||||
namespace ShortcutGuide
|
||||
{
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
public sealed partial class MainWindow : WindowEx, IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, string?> _currentApplicationIds;
|
||||
private readonly Stopwatch _sessionStopwatch = Stopwatch.StartNew();
|
||||
private readonly Task<Dictionary<string, string?>> _getAppIdsTask;
|
||||
private Dictionary<string, string?> _currentApplicationIds = [];
|
||||
private ShortcutFile? _shortcutFile;
|
||||
private string _selectedAppName = null!;
|
||||
private string _closeType = "Unknown";
|
||||
@@ -46,10 +48,15 @@ namespace ShortcutGuide
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this._currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds();
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
_getAppIdsTask = Task.Run(() =>
|
||||
{
|
||||
Program.CopyAndIndexGenerationThread.Join();
|
||||
_currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds(Program.ForegroundWindowHandle);
|
||||
return _currentApplicationIds;
|
||||
});
|
||||
|
||||
Title = ResourceLoaderInstance.ResourceLoader.GetString("Title")!;
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
|
||||
@@ -131,12 +138,6 @@ namespace ShortcutGuide
|
||||
// The code below sets the position of the window to the center of the monitor, but only if it hasn't been set before.
|
||||
if (!this._setPosition)
|
||||
{
|
||||
Content.GettingFocus += (_, _) =>
|
||||
{
|
||||
this.FakeSettingsButton.Height = 10;
|
||||
this.FakeSettingsButton.Height = 0;
|
||||
};
|
||||
|
||||
this.SetWindowPosition();
|
||||
this._setPosition = true;
|
||||
|
||||
@@ -151,7 +152,22 @@ namespace ShortcutGuide
|
||||
};
|
||||
}
|
||||
|
||||
this.SetNavItems();
|
||||
_ = this.InitializeNavItemsAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeNavItemsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentApplicationIds = await _getAppIdsTask.ConfigureAwait(true);
|
||||
this.SetNavItems();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to initialize navigation items.", ex);
|
||||
_closeType = "InitializationFailed";
|
||||
this.DispatcherQueue.TryEnqueue(() => this.Close());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNavItems()
|
||||
@@ -160,13 +176,19 @@ namespace ShortcutGuide
|
||||
// TO DO: Check if Settings button is considered an item too.
|
||||
if (this.WindowSelector.MenuItems.Count == 0)
|
||||
{
|
||||
string defaultShellName = ManifestInterpreter.GetIndexYamlFile().DefaultShellName;
|
||||
string defaultShellName = ManifestInterpreter.GetCachedIndexYamlFile().DefaultShellName;
|
||||
|
||||
foreach (var (item, executablePath) in this._currentApplicationIds)
|
||||
{
|
||||
if (item == defaultShellName)
|
||||
{
|
||||
this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = "Windows", Icon = new FontIcon() { Glyph = "\xE770" } });
|
||||
var pathData = (string)Application.Current.Resources["WindowsLogoPathData"];
|
||||
this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = "Windows", Icon = CreatePathIcon(pathData) });
|
||||
}
|
||||
else if (item == "Microsoft.PowerToys")
|
||||
{
|
||||
var pathData = (string)Application.Current.Resources["PowerToysLogoPathData"];
|
||||
this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = ManifestInterpreter.GetShortcutsOfApplication(item).Name, Icon = CreatePathIcon(pathData) });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -200,6 +222,17 @@ namespace ShortcutGuide
|
||||
return new FontIcon { Glyph = "\uEB91" };
|
||||
}
|
||||
|
||||
private static PathIcon CreatePathIcon(string pathData)
|
||||
{
|
||||
var geometry = (Geometry)XamlBindingHelper.ConvertValue(typeof(Geometry), pathData);
|
||||
return new PathIcon
|
||||
{
|
||||
Data = geometry,
|
||||
Width = 20,
|
||||
Height = 20,
|
||||
};
|
||||
}
|
||||
|
||||
private bool _hasMovedToRightMonitor;
|
||||
|
||||
private void SetWindowPosition()
|
||||
@@ -275,5 +308,10 @@ namespace ShortcutGuide
|
||||
{
|
||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ShortcutGuide);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_getAppIdsTask.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Common.UI;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using ShortcutGuide.Helpers;
|
||||
|
||||
@@ -800,9 +800,10 @@
|
||||
MinHeight="{ThemeResource RailNavigationViewItemOnLeftMinHeight}"
|
||||
RowSpacing="6">
|
||||
<!-- Store: Added Grid.RowDefinitions & Removed Grid.ColumnDefinitions -->
|
||||
<!-- Row 0 fixed height tuned so the 20px icon vertically aligns with the selection pill center (~y=28). -->
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="12" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
using ShortcutGuide.Controls;
|
||||
using ShortcutGuide.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
@@ -34,15 +34,17 @@ namespace winrt
|
||||
using namespace Windows::Devices::Enumeration;
|
||||
}
|
||||
|
||||
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool micMonoMix)
|
||||
AudioSampleGenerator::AudioSampleGenerator(bool captureMicrophone, bool captureSystemAudio, bool mixMicrophoneMono, bool useNoiseCancellation)
|
||||
: m_captureMicrophone(captureMicrophone)
|
||||
, m_captureSystemAudio(captureSystemAudio)
|
||||
, m_micMonoMix(micMonoMix)
|
||||
, m_mixMicrophoneMono(mixMicrophoneMono)
|
||||
, m_useNoiseCancellation(useNoiseCancellation)
|
||||
{
|
||||
OutputDebugStringA(("AudioSampleGenerator created, captureMicrophone=" +
|
||||
std::string(captureMicrophone ? "true" : "false") +
|
||||
", captureSystemAudio=" + std::string(captureSystemAudio ? "true" : "false") +
|
||||
", micMonoMix=" + std::string(micMonoMix ? "true" : "false") + "\n").c_str());
|
||||
", mixMicrophoneMono=" + std::string(mixMicrophoneMono ? "true" : "false") +
|
||||
", useNoiseCancellation=" + std::string(useNoiseCancellation ? "true" : "false") + "\n").c_str());
|
||||
m_audioEvent.create(wil::EventOptions::ManualReset);
|
||||
m_endEvent.create(wil::EventOptions::ManualReset);
|
||||
m_startEvent.create(wil::EventOptions::ManualReset);
|
||||
@@ -158,8 +160,24 @@ winrt::IAsyncAction AudioSampleGenerator::InitializeAsync()
|
||||
throw winrt::hresult_error(E_FAIL, L"Failed to initialize loopback audio capture!");
|
||||
}
|
||||
|
||||
// Initialize noise suppressor for microphone audio if enabled
|
||||
if (m_useNoiseCancellation && m_captureMicrophone)
|
||||
{
|
||||
m_noiseSuppressor = std::make_unique<NoiseSuppressor>();
|
||||
OutputDebugStringA("Noise cancellation enabled for microphone\n");
|
||||
}
|
||||
|
||||
m_audioGraph.QuantumStarted({ this, &AudioSampleGenerator::OnAudioQuantumStarted });
|
||||
|
||||
// Start the AudioGraph now so the microphone device begins warming up
|
||||
// during the remaining recording initialization (transcoder setup, etc.).
|
||||
// OnAudioQuantumStarted returns early while m_started is false, so audio
|
||||
// samples are discarded until Start() is called. The side-effect of
|
||||
// starting the graph early is that the system mic-active icon appears
|
||||
// sooner, which also triggers a desktop-content change that helps
|
||||
// unblock the WGC frame pool wait in OnMediaStreamSourceStarting.
|
||||
m_audioGraph.Start();
|
||||
|
||||
m_asyncInitialized.SetEvent();
|
||||
}
|
||||
}
|
||||
@@ -205,67 +223,65 @@ std::optional<winrt::MediaStreamSample> AudioSampleGenerator::TryGetNextSample()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for audio samples to become available, retrying on spurious wakes
|
||||
// (e.g. when OnAudioQuantumStarted signals m_audioEvent but the quantum
|
||||
// produced an empty buffer so m_samples is still empty).
|
||||
for (;;)
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (m_samples.empty() && m_endEvent.is_signaled())
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (m_samples.empty() && m_endEvent.is_signaled())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
m_audioEvent.ResetEvent();
|
||||
std::vector<HANDLE> events = { m_endEvent.get(), m_audioEvent.get() };
|
||||
auto waitResult = WaitForMultipleObjectsEx(static_cast<DWORD>(events.size()), events.data(), false, INFINITE, false);
|
||||
auto eventIndex = -1;
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
eventIndex = waitResult - WAIT_OBJECT_0;
|
||||
break;
|
||||
}
|
||||
WINRT_VERIFY(eventIndex >= 0);
|
||||
|
||||
auto signaledEvent = events[eventIndex];
|
||||
if (signaledEvent == m_endEvent.get())
|
||||
{
|
||||
// End was signaled, but check for any remaining samples before returning nullopt
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
m_audioEvent.ResetEvent();
|
||||
std::vector<HANDLE> events = { m_endEvent.get(), m_audioEvent.get() };
|
||||
auto waitResult = WaitForMultipleObjectsEx(static_cast<DWORD>(events.size()), events.data(), false, INFINITE, false);
|
||||
auto eventIndex = -1;
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
eventIndex = waitResult - WAIT_OBJECT_0;
|
||||
break;
|
||||
}
|
||||
WINRT_VERIFY(eventIndex >= 0);
|
||||
|
||||
auto signaledEvent = events[eventIndex];
|
||||
if (signaledEvent == m_endEvent.get())
|
||||
{
|
||||
// End was signaled, but check for any remaining samples before returning nullopt
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (!m_samples.empty())
|
||||
{
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
if (m_samples.empty())
|
||||
{
|
||||
// Spurious wake or race - no samples available
|
||||
// If end is signaled, return nullopt
|
||||
return m_endEvent.is_signaled() ? std::nullopt : std::optional<winrt::MediaStreamSample>{};
|
||||
}
|
||||
std::optional result(m_samples.front());
|
||||
m_samples.pop_front();
|
||||
return result;
|
||||
// m_audioEvent was signaled — loop back to check m_samples again.
|
||||
// If the quantum produced an empty buffer, m_samples will still be
|
||||
// empty and we'll wait for the next quantum.
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSampleGenerator::Start()
|
||||
void AudioSampleGenerator::Start(int64_t videoStartTimestamp)
|
||||
{
|
||||
CheckInitialized();
|
||||
m_videoStartTimestamp = videoStartTimestamp;
|
||||
auto expected = false;
|
||||
if (m_started.compare_exchange_strong(expected, true))
|
||||
{
|
||||
OutputDebugStringW( L"[AudioGen] Start(): m_started set to true, setting m_startEvent\n" );
|
||||
m_endEvent.ResetEvent();
|
||||
m_startEvent.SetEvent();
|
||||
|
||||
@@ -284,7 +300,7 @@ void AudioSampleGenerator::Start()
|
||||
m_loopbackCapture->Start();
|
||||
}
|
||||
|
||||
m_audioGraph.Start();
|
||||
// AudioGraph was already started in InitializeAsync for mic warmup.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,12 +627,21 @@ void AudioSampleGenerator::CombineQueuedSamples()
|
||||
|
||||
void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender, winrt::IInspectable const& args)
|
||||
{
|
||||
// Don't process if we're not actively recording
|
||||
// Don't process if we're not actively recording, but DO drain the
|
||||
// output node so stale audio doesn't accumulate during mic warmup.
|
||||
// Without this, the first GetFrame() after m_started becomes true
|
||||
// would return several seconds of buffered audio, confusing the
|
||||
// transcoder's A/V interleaving.
|
||||
if (!m_started.load())
|
||||
{
|
||||
auto frame = m_audioOutputNode.GetFrame();
|
||||
(void)frame; // discard
|
||||
return;
|
||||
}
|
||||
|
||||
static int s_quantumCount = 0;
|
||||
s_quantumCount++;
|
||||
|
||||
{
|
||||
auto lock = m_lock.lock_exclusive();
|
||||
|
||||
@@ -628,6 +653,14 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
auto sampleBuffer = winrt::Buffer::CreateCopyFromMemoryBuffer(audioBuffer);
|
||||
sampleBuffer.Length(audioBuffer.Length());
|
||||
|
||||
if( s_quantumCount <= 5 )
|
||||
{
|
||||
wchar_t dbg[256];
|
||||
swprintf_s( dbg, L"[AudioGen] quantum #%d: audioBuffer.Length=%u sampleBuffer.Length=%u started=%d\n",
|
||||
s_quantumCount, audioBuffer.Length(), sampleBuffer.Length(), m_started.load() ? 1 : 0 );
|
||||
OutputDebugStringW( dbg );
|
||||
}
|
||||
|
||||
// Calculate expected samples per quantum (~10ms at graph sample rate)
|
||||
// AudioGraph uses 10ms quantums by default
|
||||
uint32_t expectedSamplesPerQuantum = (m_graphSampleRate / 100) * m_graphChannels;
|
||||
@@ -636,7 +669,7 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
// Apply mono mixing to microphone audio if enabled
|
||||
// This converts stereo mic input (with same signal on both channels) to true mono
|
||||
// by averaging the channels and writing the result to both channels
|
||||
if (m_micMonoMix && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2)
|
||||
if (m_mixMicrophoneMono && m_captureMicrophone && numMicSamples > 0 && m_graphChannels >= 2)
|
||||
{
|
||||
float* micData = reinterpret_cast<float*>(sampleBuffer.data());
|
||||
uint32_t numFrames = numMicSamples / m_graphChannels;
|
||||
@@ -656,6 +689,12 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply noise suppression to microphone audio before mixing with loopback
|
||||
if (m_noiseSuppressor && m_captureMicrophone && numMicSamples > 0)
|
||||
{
|
||||
float* micData = reinterpret_cast<float*>(sampleBuffer.data());
|
||||
m_noiseSuppressor->Process(micData, numMicSamples, m_graphChannels);
|
||||
}
|
||||
|
||||
// Drain loopback samples regardless of whether we have mic audio
|
||||
if (m_loopbackCapture)
|
||||
@@ -733,13 +772,25 @@ void AudioSampleGenerator::OnAudioQuantumStarted(winrt::AudioGraph const& sender
|
||||
|
||||
if (sampleBuffer.Length() > 0)
|
||||
{
|
||||
auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, timestamp.value());
|
||||
// Rebase audio timestamps to the video's SystemRelativeTime domain.
|
||||
// AudioGraph RelativeTime starts near 0 (or a few hundred ms after
|
||||
// warmup draining), while video uses absolute SRT (~hours since boot).
|
||||
// Without rebasing, the transcoder sees audio far behind video and
|
||||
// starves video while trying to fill the gap with audio.
|
||||
if (!m_hasTimestampOffset && timestamp.has_value())
|
||||
{
|
||||
m_timestampOffset = m_videoStartTimestamp - timestamp.value().count();
|
||||
m_hasTimestampOffset = true;
|
||||
}
|
||||
auto adjustedTs = winrt::TimeSpan{ timestamp.value().count() + m_timestampOffset };
|
||||
|
||||
auto sample = winrt::MediaStreamSample::CreateFromBuffer(sampleBuffer, adjustedTs);
|
||||
m_samples.push_back(sample);
|
||||
|
||||
const uint32_t sampleCount = sampleBuffer.Length() / sizeof(float);
|
||||
const uint32_t frames = (m_graphChannels > 0) ? (sampleCount / m_graphChannels) : 0;
|
||||
const int64_t durationTicks = (m_graphSampleRate > 0) ? (static_cast<int64_t>(frames) * 10000000LL / m_graphSampleRate) : 0;
|
||||
m_lastSampleTimestamp = timestamp.value();
|
||||
m_lastSampleTimestamp = adjustedTs;
|
||||
m_lastSampleDuration = winrt::TimeSpan{ durationTicks };
|
||||
m_hasLastSampleTimestamp = true;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "LoopbackCapture.h"
|
||||
#include "NoiseSuppressor.h"
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
class AudioSampleGenerator
|
||||
{
|
||||
public:
|
||||
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool micMonoMix = false);
|
||||
AudioSampleGenerator(bool captureMicrophone = true, bool captureSystemAudio = true, bool mixMicrophoneMono = false, bool useNoiseCancellation = false);
|
||||
~AudioSampleGenerator();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncAction InitializeAsync();
|
||||
winrt::Windows::Media::MediaProperties::AudioEncodingProperties GetEncodingProperties();
|
||||
|
||||
std::optional<winrt::Windows::Media::Core::MediaStreamSample> TryGetNextSample();
|
||||
void Start();
|
||||
void Start(int64_t videoStartTimestamp = 0);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
@@ -70,5 +75,14 @@ private:
|
||||
std::atomic<bool> m_started = false;
|
||||
bool m_captureMicrophone = true;
|
||||
bool m_captureSystemAudio = true;
|
||||
bool m_micMonoMix = false;
|
||||
};
|
||||
bool m_mixMicrophoneMono = false;
|
||||
bool m_useNoiseCancellation = false;
|
||||
std::unique_ptr<NoiseSuppressor> m_noiseSuppressor;
|
||||
|
||||
// Timestamp rebasing: audio RelativeTime → video SystemRelativeTime domain.
|
||||
// Without this, the transcoder sees audio timestamps (~350ms) far below video
|
||||
// timestamps (~111 billion ticks) and starves video while trying to fill the gap.
|
||||
int64_t m_videoStartTimestamp = 0;
|
||||
int64_t m_timestampOffset = 0;
|
||||
bool m_hasTimestampOffset = false;
|
||||
};
|
||||
|
||||
859
src/modules/ZoomIt/ZoomIt/BackgroundBlur.cpp
Normal file
@@ -0,0 +1,859 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// BackgroundBlur.cpp
|
||||
//
|
||||
// Windows ML-based person segmentation and background blur for the
|
||||
// webcam overlay. Uses the built-in Windows.AI.MachineLearning API
|
||||
// to load an ONNX segmentation model (e.g. MediaPipe SelfieSegmentation)
|
||||
// and produce a per-pixel person mask, then blurs or replaces the
|
||||
// background using an iterated box blur or a user-chosen image.
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
#include "pch.h"
|
||||
#include "BackgroundBlur.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <wincodec.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
namespace winml = winrt::Windows::AI::MachineLearning;
|
||||
namespace wf = winrt::Windows::Foundation::Collections;
|
||||
|
||||
// Defined in Zoomit.cpp; compiles to nothing in Release builds.
|
||||
void OutputDebug(const TCHAR* format, ...);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::Initialize
|
||||
//
|
||||
// Loads the ONNX segmentation model via Windows ML and inspects its
|
||||
// input/output tensor shapes to auto-configure preprocessing.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::Initialize( const wchar_t* modelPath )
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load the model from file.
|
||||
m_model = winml::LearningModel::LoadFromFilePath( modelPath );
|
||||
|
||||
// Try GPU (DirectML) first for faster inference; fall back to CPU
|
||||
// if no suitable GPU is available.
|
||||
try
|
||||
{
|
||||
winml::LearningModelDevice gpuDevice( winml::LearningModelDeviceKind::DirectXHighPerformance );
|
||||
m_session = winml::LearningModelSession( m_model, gpuDevice );
|
||||
m_usingGpu = true;
|
||||
OutputDebug( L"[BackgroundBlur] Using DirectML (GPU) for inference\n" );
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
winml::LearningModelDevice cpuDevice( winml::LearningModelDeviceKind::Cpu );
|
||||
m_session = winml::LearningModelSession( m_model, cpuDevice );
|
||||
m_usingGpu = false;
|
||||
OutputDebug( L"[BackgroundBlur] GPU unavailable, falling back to CPU\n" );
|
||||
}
|
||||
m_binding = winml::LearningModelBinding( m_session );
|
||||
|
||||
// Get input feature descriptor.
|
||||
auto inputFeatures = m_model.InputFeatures();
|
||||
if( inputFeatures.Size() == 0 )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Model has no input features\n" );
|
||||
return false;
|
||||
}
|
||||
auto inputDesc = inputFeatures.GetAt( 0 );
|
||||
m_inputName = inputDesc.Name();
|
||||
|
||||
// Inspect input tensor shape.
|
||||
auto tensorDesc = inputDesc.as<winml::ITensorFeatureDescriptor>();
|
||||
auto shape = tensorDesc.Shape();
|
||||
if( shape.Size() == 4 )
|
||||
{
|
||||
if( shape.GetAt( 1 ) == 3 || shape.GetAt( 1 ) == 1 )
|
||||
{
|
||||
// NCHW layout.
|
||||
m_inputIsNchw = true;
|
||||
m_modelInputChannels = shape.GetAt( 1 );
|
||||
m_modelInputHeight = shape.GetAt( 2 ) > 0 ? shape.GetAt( 2 ) : 256;
|
||||
m_modelInputWidth = shape.GetAt( 3 ) > 0 ? shape.GetAt( 3 ) : 256;
|
||||
}
|
||||
else
|
||||
{
|
||||
// NHWC layout.
|
||||
m_inputIsNchw = false;
|
||||
m_modelInputHeight = shape.GetAt( 1 ) > 0 ? shape.GetAt( 1 ) : 256;
|
||||
m_modelInputWidth = shape.GetAt( 2 ) > 0 ? shape.GetAt( 2 ) : 256;
|
||||
m_modelInputChannels = shape.GetAt( 3 );
|
||||
}
|
||||
}
|
||||
|
||||
// Get output feature name.
|
||||
auto outputFeatures = m_model.OutputFeatures();
|
||||
if( outputFeatures.Size() == 0 )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Model has no output features\n" );
|
||||
return false;
|
||||
}
|
||||
m_outputName = outputFeatures.GetAt( 0 ).Name();
|
||||
|
||||
OutputDebug( L"[BackgroundBlur] Model loaded: input=%s %lldx%lld (ch=%lld, %s)\n",
|
||||
m_inputName.c_str(), m_modelInputWidth, m_modelInputHeight,
|
||||
m_modelInputChannels, m_inputIsNchw ? L"NCHW" : L"NHWC" );
|
||||
|
||||
// Pre-allocate input tensor buffer.
|
||||
m_inputTensor.resize( static_cast<size_t>( m_modelInputChannels * m_modelInputHeight * m_modelInputWidth ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
catch( winrt::hresult_error const& ex )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Initialize failed: %s (0x%08X)\n", ex.message().c_str(), ex.code().value );
|
||||
m_session = nullptr;
|
||||
m_model = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::RunSegmentation
|
||||
//
|
||||
// Resizes the BGRA frame to the model's expected input size, converts
|
||||
// to float RGB, runs inference via Windows ML, and produces a float mask
|
||||
// in m_mask where 1.0 = person, 0.0 = background.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::RunSegmentation( const uint8_t* bgraPixels, uint32_t width, uint32_t height,
|
||||
bool modelResOnly )
|
||||
{
|
||||
const int64_t mW = m_modelInputWidth;
|
||||
const int64_t mH = m_modelInputHeight;
|
||||
const int64_t mC = m_modelInputChannels;
|
||||
|
||||
// Resize BGRA → model-sized float RGB using nearest-neighbor.
|
||||
for( int64_t y = 0; y < mH; y++ )
|
||||
{
|
||||
uint32_t srcY = static_cast<uint32_t>( y * height / mH );
|
||||
for( int64_t x = 0; x < mW; x++ )
|
||||
{
|
||||
uint32_t srcX = static_cast<uint32_t>( x * width / mW );
|
||||
const uint8_t* px = bgraPixels + ( static_cast<size_t>( srcY ) * width + srcX ) * 4;
|
||||
float b = px[0] / 255.0f;
|
||||
float g = px[1] / 255.0f;
|
||||
float r = px[2] / 255.0f;
|
||||
|
||||
if( m_inputIsNchw )
|
||||
{
|
||||
m_inputTensor[static_cast<size_t>(0ll * mH * mW + y * mW + x)] = r;
|
||||
if( mC > 1 ) m_inputTensor[static_cast<size_t>(1ll * mH * mW + y * mW + x)] = g;
|
||||
if( mC > 2 ) m_inputTensor[static_cast<size_t>(2ll * mH * mW + y * mW + x)] = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t idx = static_cast<size_t>( (y * mW + x) * mC );
|
||||
m_inputTensor[idx + 0] = r;
|
||||
if( mC > 1 ) m_inputTensor[idx + 1] = g;
|
||||
if( mC > 2 ) m_inputTensor[idx + 2] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create the input tensor shape.
|
||||
std::vector<int64_t> inputShape;
|
||||
if( m_inputIsNchw )
|
||||
inputShape = { 1, mC, mH, mW };
|
||||
else
|
||||
inputShape = { 1, mH, mW, mC };
|
||||
|
||||
// Create a TensorFloat from our data.
|
||||
auto inputTensor = winml::TensorFloat::CreateFromArray(
|
||||
inputShape, winrt::array_view<const float>( m_inputTensor.data(),
|
||||
m_inputTensor.data() + m_inputTensor.size() ) );
|
||||
|
||||
// Bind input and evaluate.
|
||||
m_binding.Clear();
|
||||
m_binding.Bind( m_inputName, inputTensor );
|
||||
|
||||
auto result = m_session.Evaluate( m_binding, L"" );
|
||||
|
||||
// Extract output tensor — bulk-copy to a raw float array so we
|
||||
// avoid per-element WinRT/COM dispatch in the hot loop.
|
||||
auto outputTensor = result.Outputs().Lookup( m_outputName ).as<winml::TensorFloat>();
|
||||
auto outputShape = outputTensor.Shape();
|
||||
auto outputView = outputTensor.GetAsVectorView();
|
||||
const uint32_t outputSize = outputView.Size();
|
||||
m_outputBuf.resize( outputSize );
|
||||
outputView.GetMany( 0, m_outputBuf );
|
||||
const float* outData = m_outputBuf.data();
|
||||
|
||||
// Determine output mask dimensions.
|
||||
int64_t outH = mH, outW = mW;
|
||||
int64_t numClasses = 1;
|
||||
if( outputShape.Size() == 4 )
|
||||
{
|
||||
if( outputShape.GetAt( 1 ) <= 2 && outputShape.GetAt( 2 ) > 2 )
|
||||
{
|
||||
// [1, classes, H, W]
|
||||
numClasses = outputShape.GetAt( 1 );
|
||||
outH = outputShape.GetAt( 2 );
|
||||
outW = outputShape.GetAt( 3 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// [1, H, W, classes]
|
||||
outH = outputShape.GetAt( 1 );
|
||||
outW = outputShape.GetAt( 2 );
|
||||
numClasses = outputShape.GetAt( 3 );
|
||||
}
|
||||
}
|
||||
else if( outputShape.Size() == 3 )
|
||||
{
|
||||
outH = outputShape.GetAt( 1 );
|
||||
outW = outputShape.GetAt( 2 );
|
||||
}
|
||||
|
||||
// Store actual output dimensions for GetModelMaskWidth/Height.
|
||||
m_modelOutputWidth = outW;
|
||||
m_modelOutputHeight = outH;
|
||||
|
||||
// Build model-resolution mask first, apply sigmoid sharpening
|
||||
// at model resolution (e.g. 256×256 = 65K pixels), then upscale
|
||||
// to frame resolution.
|
||||
const size_t modelPixels = static_cast<size_t>( outH * outW );
|
||||
m_erodeBuf.resize( modelPixels );
|
||||
|
||||
// Extract person scores at model resolution from the raw array.
|
||||
// Apply a hard threshold to produce a binary mask. This is much
|
||||
// faster than a sigmoid (no expf) and eliminates the partial-blur
|
||||
// halo that was bleeding onto body/head edges.
|
||||
for( int64_t y = 0; y < outH; y++ )
|
||||
{
|
||||
for( int64_t x = 0; x < outW; x++ )
|
||||
{
|
||||
float personScore;
|
||||
if( numClasses == 1 )
|
||||
{
|
||||
personScore = outData[y * outW + x];
|
||||
}
|
||||
else
|
||||
{
|
||||
float bg = outData[0 * outH * outW + y * outW + x];
|
||||
float fg = outData[1 * outH * outW + y * outW + x];
|
||||
personScore = ( fg > bg ) ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
m_erodeBuf[static_cast<size_t>( y * outW + x )] = ( personScore > 0.5f ) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ── GPU path: model-resolution post-processing only ────────
|
||||
// When modelResOnly is true, apply feathering and temporal
|
||||
// smoothing at model resolution (e.g. 256×256 = 65K pixels)
|
||||
// and return early. The GPU's hardware bilinear sampler will
|
||||
// handle upscaling to frame resolution for free.
|
||||
if( modelResOnly )
|
||||
{
|
||||
// Small box blur on m_erodeBuf for edge feathering.
|
||||
// Radius 1 at 256×256 provides similar smoothing to
|
||||
// radius 3 at 960×540 after bilinear upscale.
|
||||
const int modelBlurRadius = 1;
|
||||
const int modelDiam = modelBlurRadius * 2 + 1;
|
||||
m_maskBlurBuf.resize( modelPixels );
|
||||
|
||||
// Horizontal pass.
|
||||
for( int64_t y = 0; y < outH; y++ )
|
||||
{
|
||||
const float* srcRow = m_erodeBuf.data() + y * outW;
|
||||
float* dstRow = m_maskBlurBuf.data() + y * outW;
|
||||
float sum = 0.0f;
|
||||
for( int i = -modelBlurRadius; i <= modelBlurRadius; i++ )
|
||||
sum += srcRow[(std::max)( static_cast<int64_t>(0), (std::min)( outW - 1, static_cast<int64_t>( i ) ) )];
|
||||
for( int64_t x = 0; x < outW; x++ )
|
||||
{
|
||||
dstRow[x] = sum / modelDiam;
|
||||
int64_t remX = (std::max)( static_cast<int64_t>(0), x - modelBlurRadius );
|
||||
int64_t addX = (std::min)( outW - 1, x + modelBlurRadius + 1 );
|
||||
sum += srcRow[addX] - srcRow[remX];
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical pass.
|
||||
for( int64_t x = 0; x < outW; x++ )
|
||||
{
|
||||
float sum = 0.0f;
|
||||
for( int i = -modelBlurRadius; i <= modelBlurRadius; i++ )
|
||||
{
|
||||
int64_t iy = (std::max)( static_cast<int64_t>(0), (std::min)( outH - 1, static_cast<int64_t>( i ) ) );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( iy * outW + x )];
|
||||
}
|
||||
for( int64_t y = 0; y < outH; y++ )
|
||||
{
|
||||
m_erodeBuf[static_cast<size_t>( y * outW + x )] = sum / modelDiam;
|
||||
int64_t remY = (std::max)( static_cast<int64_t>(0), y - modelBlurRadius );
|
||||
int64_t addY = (std::min)( outH - 1, y + modelBlurRadius + 1 );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( addY * outW + x )] -
|
||||
m_maskBlurBuf[static_cast<size_t>( remY * outW + x )];
|
||||
}
|
||||
}
|
||||
|
||||
// Temporal smoothing at model resolution.
|
||||
if( m_prevModelMask.size() == modelPixels )
|
||||
{
|
||||
constexpr float alpha = 0.6f;
|
||||
constexpr float beta = 0.4f;
|
||||
for( size_t i = 0; i < modelPixels; i++ )
|
||||
m_erodeBuf[i] = alpha * m_erodeBuf[i] + beta * m_prevModelMask[i];
|
||||
}
|
||||
m_prevModelMask = m_erodeBuf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upscale processed mask to frame dimensions via bilinear interpolation
|
||||
// to produce smooth edges instead of staircase artifacts.
|
||||
const size_t maskPixels = static_cast<size_t>( width ) * height;
|
||||
m_mask.resize( maskPixels );
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
float srcYf = ( y + 0.5f ) * outH / static_cast<float>( height ) - 0.5f;
|
||||
srcYf = (std::max)( 0.0f, (std::min)( srcYf, static_cast<float>( outH - 1 ) ) );
|
||||
int64_t y0 = static_cast<int64_t>( srcYf );
|
||||
int64_t y1 = (std::min)( y0 + 1, outH - 1 );
|
||||
float fy = srcYf - y0;
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float srcXf = ( x + 0.5f ) * outW / static_cast<float>( width ) - 0.5f;
|
||||
srcXf = (std::max)( 0.0f, (std::min)( srcXf, static_cast<float>( outW - 1 ) ) );
|
||||
int64_t x0 = static_cast<int64_t>( srcXf );
|
||||
int64_t x1 = (std::min)( x0 + 1, outW - 1 );
|
||||
float fx = srcXf - x0;
|
||||
|
||||
float v00 = m_erodeBuf[static_cast<size_t>(y0 * outW + x0)];
|
||||
float v01 = m_erodeBuf[static_cast<size_t>(y0 * outW + x1)];
|
||||
float v10 = m_erodeBuf[static_cast<size_t>(y1 * outW + x0)];
|
||||
float v11 = m_erodeBuf[static_cast<size_t>(y1 * outW + x1)];
|
||||
|
||||
m_mask[static_cast<size_t>( y ) * width + x] =
|
||||
v00 * ( 1.0f - fx ) * ( 1.0f - fy ) +
|
||||
v01 * fx * ( 1.0f - fy ) +
|
||||
v10 * ( 1.0f - fx ) * fy +
|
||||
v11 * fx * fy;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a small box blur to the upscaled mask to feather edges.
|
||||
const int maskBlurRadius = 3;
|
||||
const int maskDiam = maskBlurRadius * 2 + 1;
|
||||
m_maskBlurBuf.resize( maskPixels );
|
||||
|
||||
// Horizontal pass.
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
const float* srcRow = m_mask.data() + static_cast<size_t>( y ) * width;
|
||||
float* dstRow = m_maskBlurBuf.data() + static_cast<size_t>( y ) * width;
|
||||
float sum = 0.0f;
|
||||
|
||||
for( int i = -maskBlurRadius; i <= maskBlurRadius; i++ )
|
||||
sum += srcRow[(std::max)( 0, (std::min)( static_cast<int>( width ) - 1, i ) )];
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
dstRow[x] = sum / maskDiam;
|
||||
int remX = (std::max)( 0, static_cast<int>( x ) - maskBlurRadius );
|
||||
int addX = (std::min)( static_cast<int>( width ) - 1, static_cast<int>( x ) + maskBlurRadius + 1 );
|
||||
sum += srcRow[addX] - srcRow[remX];
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical pass.
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float sum = 0.0f;
|
||||
|
||||
for( int i = -maskBlurRadius; i <= maskBlurRadius; i++ )
|
||||
{
|
||||
int iy = (std::max)( 0, (std::min)( static_cast<int>( height ) - 1, i ) );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( iy ) * width + x];
|
||||
}
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
m_mask[static_cast<size_t>( y ) * width + x] = sum / maskDiam;
|
||||
int remY = (std::max)( 0, static_cast<int>( y ) - maskBlurRadius );
|
||||
int addY = (std::min)( static_cast<int>( height ) - 1, static_cast<int>( y ) + maskBlurRadius + 1 );
|
||||
sum += m_maskBlurBuf[static_cast<size_t>( addY ) * width + x] -
|
||||
m_maskBlurBuf[static_cast<size_t>( remY ) * width + x];
|
||||
}
|
||||
}
|
||||
|
||||
// Temporal smoothing: blend the current mask with the previous
|
||||
// frame's mask to stabilize edges and reduce flicker. A weight
|
||||
// of 0.6 current + 0.4 previous keeps edges responsive while
|
||||
// dampening the frame-to-frame jitter around fine details like
|
||||
// ears, hair, and fingers.
|
||||
{
|
||||
const size_t maskPixelsInner = static_cast<size_t>( width ) * height;
|
||||
if( m_prevMask.size() == maskPixelsInner )
|
||||
{
|
||||
constexpr float alpha = 0.6f; // current frame weight
|
||||
constexpr float beta = 0.4f; // previous frame weight
|
||||
for( size_t i = 0; i < maskPixelsInner; i++ )
|
||||
{
|
||||
m_mask[i] = alpha * m_mask[i] + beta * m_prevMask[i];
|
||||
}
|
||||
}
|
||||
m_prevMask = m_mask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch( winrt::hresult_error const& ex )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Evaluate failed: %s (0x%08X)\n", ex.message().c_str(), ex.code().value );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// HorizontalBoxBlur / VerticalBoxBlur
|
||||
//
|
||||
// Separable box blur passes used to build an approximate Gaussian.
|
||||
//----------------------------------------------------------------------------
|
||||
static void HorizontalBoxBlur(
|
||||
const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t height, int radius )
|
||||
{
|
||||
const int diameter = radius * 2 + 1;
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
int rSum = 0, gSum = 0, bSum = 0;
|
||||
const uint8_t* row = src + static_cast<size_t>( y ) * width * 4;
|
||||
|
||||
// Initialize window with clamped left edge.
|
||||
for( int i = -radius; i <= radius; i++ )
|
||||
{
|
||||
int ix = (std::max)( 0, (std::min)( static_cast<int>( width ) - 1, i ) );
|
||||
const uint8_t* px = row + ix * 4;
|
||||
bSum += px[0];
|
||||
gSum += px[1];
|
||||
rSum += px[2];
|
||||
}
|
||||
|
||||
uint8_t* dstRow = dst + static_cast<size_t>( y ) * width * 4;
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
dstRow[x * 4 + 0] = static_cast<uint8_t>( bSum / diameter );
|
||||
dstRow[x * 4 + 1] = static_cast<uint8_t>( gSum / diameter );
|
||||
dstRow[x * 4 + 2] = static_cast<uint8_t>( rSum / diameter );
|
||||
dstRow[x * 4 + 3] = 0xFF;
|
||||
|
||||
// Slide window: add right, remove left.
|
||||
int removeX = (std::max)( 0, static_cast<int>( x ) - radius );
|
||||
int addX = (std::min)( static_cast<int>( width ) - 1, static_cast<int>( x ) + radius + 1 );
|
||||
const uint8_t* remPx = row + removeX * 4;
|
||||
const uint8_t* addPx = row + addX * 4;
|
||||
bSum += addPx[0] - remPx[0];
|
||||
gSum += addPx[1] - remPx[1];
|
||||
rSum += addPx[2] - remPx[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void VerticalBoxBlur(
|
||||
const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t height, int radius )
|
||||
{
|
||||
const int diameter = radius * 2 + 1;
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
int rSum = 0, gSum = 0, bSum = 0;
|
||||
|
||||
// Initialize window with clamped top edge.
|
||||
for( int i = -radius; i <= radius; i++ )
|
||||
{
|
||||
int iy = (std::max)( 0, (std::min)( static_cast<int>( height ) - 1, i ) );
|
||||
const uint8_t* px = src + ( static_cast<size_t>( iy ) * width + x ) * 4;
|
||||
bSum += px[0];
|
||||
gSum += px[1];
|
||||
rSum += px[2];
|
||||
}
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint8_t* dstPx = dst + ( static_cast<size_t>( y ) * width + x ) * 4;
|
||||
dstPx[0] = static_cast<uint8_t>( bSum / diameter );
|
||||
dstPx[1] = static_cast<uint8_t>( gSum / diameter );
|
||||
dstPx[2] = static_cast<uint8_t>( rSum / diameter );
|
||||
dstPx[3] = 0xFF;
|
||||
|
||||
int removeY = (std::max)( 0, static_cast<int>( y ) - radius );
|
||||
int addY = (std::min)( static_cast<int>( height ) - 1, static_cast<int>( y ) + radius + 1 );
|
||||
const uint8_t* remPx = src + ( static_cast<size_t>( removeY ) * width + x ) * 4;
|
||||
const uint8_t* addPx = src + ( static_cast<size_t>( addY ) * width + x ) * 4;
|
||||
bSum += addPx[0] - remPx[0];
|
||||
gSum += addPx[1] - remPx[1];
|
||||
rSum += addPx[2] - remPx[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ApplyBlurWithMask
|
||||
//
|
||||
// Downscales the frame to a small working size, blurs there, then
|
||||
// performs a single full-resolution pass that blends the original
|
||||
// pixels with the upscaled blurred pixels according to the mask.
|
||||
//----------------------------------------------------------------------------
|
||||
void BackgroundBlur::ApplyBlurWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius )
|
||||
{
|
||||
const size_t frameBytes = static_cast<size_t>( width ) * height * 4;
|
||||
m_blurredFrame.resize( frameBytes );
|
||||
m_tempFrame.resize( frameBytes );
|
||||
|
||||
// The input is already capped at 960×540 by WebcamCapture, so blur
|
||||
// directly — no need for a secondary downscale.
|
||||
int effectiveRadius = (std::max)( 3, blurRadius );
|
||||
|
||||
// 2 iterations of box blur → approximate Gaussian.
|
||||
HorizontalBoxBlur( bgraPixels, m_blurredFrame.data(), width, height, effectiveRadius );
|
||||
VerticalBoxBlur( m_blurredFrame.data(), m_tempFrame.data(), width, height, effectiveRadius );
|
||||
HorizontalBoxBlur( m_tempFrame.data(), m_blurredFrame.data(), width, height, effectiveRadius );
|
||||
VerticalBoxBlur( m_blurredFrame.data(), m_tempFrame.data(), width, height, effectiveRadius );
|
||||
|
||||
// Blend pass with alpha support for smooth mask edges.
|
||||
const uint8_t* blurData = m_tempFrame.data();
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint8_t* dstRow = bgraPixels + static_cast<size_t>( y ) * width * 4;
|
||||
const uint8_t* blurRow = blurData + static_cast<size_t>( y ) * width * 4;
|
||||
const float* maskRow = m_mask.data() + static_cast<size_t>( y ) * width;
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float maskVal = maskRow[x];
|
||||
|
||||
// Fast path: fully person → keep original pixel untouched.
|
||||
if( maskVal >= 1.0f )
|
||||
continue;
|
||||
|
||||
uint8_t* dp = dstRow + x * 4;
|
||||
const uint8_t* bp = blurRow + x * 4;
|
||||
|
||||
// Fast path: fully background → copy blurred pixel.
|
||||
if( maskVal <= 0.0f )
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>( dp ) = *reinterpret_cast<const uint32_t*>( bp );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge pixel → alpha blend original and blurred.
|
||||
float inv = 1.0f - maskVal;
|
||||
dp[0] = static_cast<uint8_t>( dp[0] * maskVal + bp[0] * inv + 0.5f );
|
||||
dp[1] = static_cast<uint8_t>( dp[1] * maskVal + bp[1] * inv + 0.5f );
|
||||
dp[2] = static_cast<uint8_t>( dp[2] * maskVal + bp[2] * inv + 0.5f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::SetBackgroundImage
|
||||
//
|
||||
// Loads an image file via WIC and stores it as a BGRA pixel buffer.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::SetBackgroundImage( const wchar_t* imagePath )
|
||||
{
|
||||
m_bgImage.clear();
|
||||
m_bgImageWidth = 0;
|
||||
m_bgImageHeight = 0;
|
||||
m_scaledBgImage.clear();
|
||||
m_scaledBgW = 0;
|
||||
m_scaledBgH = 0;
|
||||
|
||||
if( !imagePath || !*imagePath )
|
||||
return false;
|
||||
|
||||
auto factory = wil::CoCreateInstance<IWICImagingFactory>( CLSID_WICImagingFactory );
|
||||
if( !factory )
|
||||
return false;
|
||||
|
||||
wil::com_ptr<IWICBitmapDecoder> decoder;
|
||||
HRESULT hr = factory->CreateDecoderFromFilename(
|
||||
imagePath, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder );
|
||||
if( FAILED( hr ) )
|
||||
{
|
||||
OutputDebug( L"[BackgroundBlur] Failed to decode image: %s (hr=0x%08X)\n", imagePath, hr );
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::com_ptr<IWICBitmapFrameDecode> frame;
|
||||
hr = decoder->GetFrame( 0, &frame );
|
||||
if( FAILED( hr ) )
|
||||
return false;
|
||||
|
||||
// Convert to BGRA 32bpp.
|
||||
wil::com_ptr<IWICFormatConverter> converter;
|
||||
hr = factory->CreateFormatConverter( &converter );
|
||||
if( FAILED( hr ) )
|
||||
return false;
|
||||
|
||||
hr = converter->Initialize(
|
||||
frame.get(), GUID_WICPixelFormat32bppBGRA,
|
||||
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom );
|
||||
if( FAILED( hr ) )
|
||||
return false;
|
||||
|
||||
UINT w = 0, h = 0;
|
||||
converter->GetSize( &w, &h );
|
||||
if( w == 0 || h == 0 )
|
||||
return false;
|
||||
|
||||
m_bgImage.resize( static_cast<size_t>( w ) * h * 4 );
|
||||
hr = converter->CopyPixels( nullptr, w * 4, static_cast<UINT>( m_bgImage.size() ), m_bgImage.data() );
|
||||
if( FAILED( hr ) )
|
||||
{
|
||||
m_bgImage.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bgImageWidth = w;
|
||||
m_bgImageHeight = h;
|
||||
|
||||
OutputDebug( L"[BackgroundBlur] Background image loaded: %ux%u from %s\n", w, h, imagePath );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::EnsureScaledBgImage
|
||||
//
|
||||
// Scales the loaded background image to the specified dimensions using
|
||||
// nearest-neighbor. The result is cached and only recomputed when the
|
||||
// target dimensions change. The image is center-cropped to preserve
|
||||
// aspect ratio (like "cover" scaling).
|
||||
//----------------------------------------------------------------------------
|
||||
void BackgroundBlur::EnsureScaledBgImage( uint32_t width, uint32_t height )
|
||||
{
|
||||
if( m_scaledBgW == width && m_scaledBgH == height && !m_scaledBgImage.empty() )
|
||||
return;
|
||||
|
||||
m_scaledBgImage.resize( static_cast<size_t>( width ) * height * 4 );
|
||||
m_scaledBgW = width;
|
||||
m_scaledBgH = height;
|
||||
|
||||
// Compute center-crop of the source image to match the target aspect ratio.
|
||||
double targetAspect = static_cast<double>( width ) / height;
|
||||
double srcAspect = static_cast<double>( m_bgImageWidth ) / m_bgImageHeight;
|
||||
|
||||
uint32_t cropW, cropH, cropX, cropY;
|
||||
if( srcAspect > targetAspect )
|
||||
{
|
||||
// Source is wider — crop horizontally.
|
||||
cropH = m_bgImageHeight;
|
||||
cropW = static_cast<uint32_t>( m_bgImageHeight * targetAspect + 0.5 );
|
||||
cropX = ( m_bgImageWidth - cropW ) / 2;
|
||||
cropY = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source is taller — crop vertically.
|
||||
cropW = m_bgImageWidth;
|
||||
cropH = static_cast<uint32_t>( m_bgImageWidth / targetAspect + 0.5 );
|
||||
cropX = 0;
|
||||
cropY = ( m_bgImageHeight - cropH ) / 2;
|
||||
}
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint32_t srcY = cropY + y * cropH / height;
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
uint32_t srcX = cropX + x * cropW / width;
|
||||
size_t srcIdx = ( static_cast<size_t>( srcY ) * m_bgImageWidth + srcX ) * 4;
|
||||
size_t dstIdx = ( static_cast<size_t>( y ) * width + x ) * 4;
|
||||
m_scaledBgImage[dstIdx + 0] = m_bgImage[srcIdx + 0];
|
||||
m_scaledBgImage[dstIdx + 1] = m_bgImage[srcIdx + 1];
|
||||
m_scaledBgImage[dstIdx + 2] = m_bgImage[srcIdx + 2];
|
||||
m_scaledBgImage[dstIdx + 3] = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ApplyImageWithMask
|
||||
//
|
||||
// Replaces background pixels with the loaded background image using the
|
||||
// segmentation mask. Person pixels are preserved, background pixels come
|
||||
// from the scaled image.
|
||||
//----------------------------------------------------------------------------
|
||||
void BackgroundBlur::ApplyImageWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
EnsureScaledBgImage( width, height );
|
||||
|
||||
const uint8_t* bgData = m_scaledBgImage.data();
|
||||
|
||||
for( uint32_t y = 0; y < height; y++ )
|
||||
{
|
||||
uint8_t* dstRow = bgraPixels + static_cast<size_t>( y ) * width * 4;
|
||||
const uint8_t* bgRow = bgData + static_cast<size_t>( y ) * width * 4;
|
||||
const float* maskRow = m_mask.data() + static_cast<size_t>( y ) * width;
|
||||
|
||||
for( uint32_t x = 0; x < width; x++ )
|
||||
{
|
||||
float maskVal = maskRow[x];
|
||||
|
||||
// Fully person → keep original pixel.
|
||||
if( maskVal >= 1.0f )
|
||||
continue;
|
||||
|
||||
uint8_t* dp = dstRow + x * 4;
|
||||
const uint8_t* bp = bgRow + x * 4;
|
||||
|
||||
// Fully background → copy background image pixel.
|
||||
if( maskVal <= 0.0f )
|
||||
{
|
||||
*reinterpret_cast<uint32_t*>( dp ) = *reinterpret_cast<const uint32_t*>( bp );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge pixel → alpha blend person and background image.
|
||||
float inv = 1.0f - maskVal;
|
||||
dp[0] = static_cast<uint8_t>( dp[0] * maskVal + bp[0] * inv + 0.5f );
|
||||
dp[1] = static_cast<uint8_t>( dp[1] * maskVal + bp[1] * inv + 0.5f );
|
||||
dp[2] = static_cast<uint8_t>( dp[2] * maskVal + bp[2] * inv + 0.5f );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ShouldRunInference
|
||||
//
|
||||
// Decides whether segmentation inference should run this frame.
|
||||
// Uses a combination of periodic fallback and motion detection:
|
||||
// motion is estimated by comparing luminance at a sparse grid of
|
||||
// sample points with the previous frame. When the scene changes
|
||||
// quickly (fast head movement), inference runs every frame.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::ShouldRunInference( const uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
// Always run if no cached mask or dimensions changed.
|
||||
if( !m_hasCachedMask || m_lastMaskWidth != width || m_lastMaskHeight != height )
|
||||
return true;
|
||||
|
||||
// Periodic fallback: run at least every N frames.
|
||||
const uint32_t pixels = width * height;
|
||||
const int inferenceInterval = ( pixels > 500000 ) ? 6 : 3;
|
||||
if( ( m_frameCounter % inferenceInterval ) == 0 )
|
||||
return true;
|
||||
|
||||
// Motion detection: sample luminance at a sparse grid and compare
|
||||
// with the previous frame.
|
||||
constexpr int gridSize = MOTION_GRID_SIZE;
|
||||
constexpr int numSamples = gridSize * gridSize;
|
||||
float curSamples[numSamples];
|
||||
|
||||
for( int gy = 0; gy < gridSize; gy++ )
|
||||
{
|
||||
uint32_t sy = ( gy * 2 + 1 ) * height / ( gridSize * 2 );
|
||||
for( int gx = 0; gx < gridSize; gx++ )
|
||||
{
|
||||
uint32_t sx = ( gx * 2 + 1 ) * width / ( gridSize * 2 );
|
||||
const uint8_t* px = bgraPixels + ( static_cast<size_t>( sy ) * width + sx ) * 4;
|
||||
curSamples[gy * gridSize + gx] = 0.299f * px[2] + 0.587f * px[1] + 0.114f * px[0];
|
||||
}
|
||||
}
|
||||
|
||||
float motionScore = 0.0f;
|
||||
if( m_hasPrevSamples )
|
||||
{
|
||||
for( int i = 0; i < numSamples; i++ )
|
||||
{
|
||||
float diff = curSamples[i] - m_prevSamples[i];
|
||||
motionScore += diff > 0.0f ? diff : -diff;
|
||||
}
|
||||
motionScore /= numSamples;
|
||||
}
|
||||
|
||||
memcpy( m_prevSamples, curSamples, sizeof( curSamples ) );
|
||||
m_hasPrevSamples = true;
|
||||
|
||||
// Average per-sample luminance change > 5/255 indicates significant motion.
|
||||
return motionScore > 5.0f;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::ApplyImageReplacement
|
||||
//
|
||||
// Main entry point for background image replacement mode.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::ApplyImageReplacement( uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
if( !m_session || !bgraPixels || width == 0 || height == 0 )
|
||||
return false;
|
||||
|
||||
if( m_bgImage.empty() )
|
||||
return false;
|
||||
|
||||
if( ShouldRunInference( bgraPixels, width, height ) )
|
||||
{
|
||||
if( !RunSegmentation( bgraPixels, width, height ) )
|
||||
return false;
|
||||
m_lastMaskWidth = width;
|
||||
m_lastMaskHeight = height;
|
||||
m_hasCachedMask = true;
|
||||
}
|
||||
m_frameCounter++;
|
||||
|
||||
ApplyImageWithMask( bgraPixels, width, height );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::Apply
|
||||
//
|
||||
// Main entry point: runs segmentation and applies blur to the background.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::Apply( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius )
|
||||
{
|
||||
if( !m_session || !bgraPixels || width == 0 || height == 0 )
|
||||
return false;
|
||||
|
||||
if( ShouldRunInference( bgraPixels, width, height ) )
|
||||
{
|
||||
if( !RunSegmentation( bgraPixels, width, height ) )
|
||||
return false;
|
||||
m_lastMaskWidth = width;
|
||||
m_lastMaskHeight = height;
|
||||
m_hasCachedMask = true;
|
||||
}
|
||||
m_frameCounter++;
|
||||
|
||||
ApplyBlurWithMask( bgraPixels, width, height, blurRadius );
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// BackgroundBlur::RunSegmentationOnly
|
||||
//
|
||||
// Runs the segmentation model and produces the mask, but does NOT blur
|
||||
// or modify the pixel buffer. Used when the GPU compute shader will
|
||||
// perform the box blur instead of the CPU.
|
||||
//----------------------------------------------------------------------------
|
||||
bool BackgroundBlur::RunSegmentationOnly( const uint8_t* bgraPixels, uint32_t width, uint32_t height )
|
||||
{
|
||||
if( !m_session || !bgraPixels || width == 0 || height == 0 )
|
||||
return false;
|
||||
|
||||
if( ShouldRunInference( bgraPixels, width, height ) )
|
||||
{
|
||||
// Model-resolution only: skip CPU upscale+feather at frame
|
||||
// resolution — the GPU bilinear sampler handles that.
|
||||
if( !RunSegmentation( bgraPixels, width, height, /*modelResOnly=*/ true ) )
|
||||
return false;
|
||||
m_lastMaskWidth = width;
|
||||
m_lastMaskHeight = height;
|
||||
m_hasCachedMask = true;
|
||||
}
|
||||
m_frameCounter++;
|
||||
|
||||
return m_hasCachedMask;
|
||||
}
|
||||
160
src/modules/ZoomIt/ZoomIt/BackgroundBlur.h
Normal file
@@ -0,0 +1,160 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// BackgroundBlur.h
|
||||
//
|
||||
// Performs person segmentation using Windows ML (Windows.AI.MachineLearning)
|
||||
// and applies either a Gaussian blur or a custom background image to the
|
||||
// background of a BGRA webcam frame. The segmentation model runs on CPU
|
||||
// via the Windows ML default device, keeping the GPU free for recording.
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <winrt/Windows.AI.MachineLearning.h>
|
||||
|
||||
// Background processing mode for the webcam overlay.
|
||||
enum class WebcamBackgroundMode : uint32_t
|
||||
{
|
||||
None = 0, // No background processing
|
||||
Blur = 1, // Gaussian blur on the background
|
||||
Image = 2, // Replace background with a user-chosen image
|
||||
};
|
||||
|
||||
class BackgroundBlur
|
||||
{
|
||||
public:
|
||||
BackgroundBlur() = default;
|
||||
~BackgroundBlur() = default;
|
||||
|
||||
// Initialize the ONNX model. modelPath must point to a valid .onnx
|
||||
// segmentation model file. Returns true on success.
|
||||
bool Initialize( const wchar_t* modelPath );
|
||||
|
||||
// Load a background replacement image from the given file path.
|
||||
// The image is decoded via WIC and stored as a BGRA buffer.
|
||||
// Returns true on success.
|
||||
bool SetBackgroundImage( const wchar_t* imagePath );
|
||||
|
||||
// Returns true if a background image has been loaded.
|
||||
bool HasBackgroundImage() const { return !m_bgImage.empty(); }
|
||||
|
||||
// Apply background blur to a BGRA pixel buffer in-place.
|
||||
// width/height are the frame dimensions. blurRadius controls
|
||||
// the strength of the Gaussian blur (in pixels).
|
||||
// Returns true if segmentation + blur was applied successfully.
|
||||
bool Apply( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius = 21 );
|
||||
|
||||
// Apply background image replacement to a BGRA pixel buffer in-place.
|
||||
// Uses the previously loaded background image (via SetBackgroundImage).
|
||||
// Returns true if segmentation + image replacement was applied.
|
||||
bool ApplyImageReplacement( uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Returns true if the model has been loaded successfully.
|
||||
bool IsInitialized() const { return m_session != nullptr; }
|
||||
|
||||
// Access the segmentation mask after Apply()/ApplyImageReplacement().
|
||||
// The mask has one float [0..1] per pixel at the processing resolution
|
||||
// (1.0 = person / foreground, 0.0 = background).
|
||||
const std::vector<float>& GetMask() const { return m_mask; }
|
||||
uint32_t GetMaskWidth() const { return m_lastMaskWidth; }
|
||||
uint32_t GetMaskHeight() const { return m_lastMaskHeight; }
|
||||
bool HasCachedMask() const { return m_hasCachedMask; }
|
||||
|
||||
// Run segmentation only (no CPU blur or mask blend). Use this when
|
||||
// the blur will be performed on the GPU via a compute shader.
|
||||
// Populates the mask (GetMask) but does NOT touch bgraPixels.
|
||||
bool RunSegmentationOnly( const uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Access the fully-blurred frame after Apply().
|
||||
// Contains all pixels blurred (before mask-based compositing).
|
||||
// Only valid after Apply() — NOT after ApplyImageReplacement().
|
||||
const std::vector<uint8_t>& GetBlurredFrame() const { return m_tempFrame; }
|
||||
|
||||
// Access the model-resolution mask before upscaling (e.g. 256×256).
|
||||
// Useful when the GPU handles upscaling via hardware bilinear filtering.
|
||||
const std::vector<float>& GetModelMask() const { return m_erodeBuf; }
|
||||
int64_t GetModelMaskWidth() const { return m_modelOutputWidth; }
|
||||
int64_t GetModelMaskHeight() const { return m_modelOutputHeight; }
|
||||
|
||||
private:
|
||||
// Run the segmentation model and produce a float mask [0..1] per pixel.
|
||||
// When modelResOnly is true, stops after model-resolution post-processing
|
||||
// (feathering + temporal smoothing at 256×256) and skips the CPU upscale
|
||||
// to frame resolution — the GPU bilinear sampler handles that instead.
|
||||
bool RunSegmentation( const uint8_t* bgraPixels, uint32_t width, uint32_t height,
|
||||
bool modelResOnly = false );
|
||||
|
||||
// Apply box blur (iterated for Gaussian approximation) to bgraPixels
|
||||
// only where the mask indicates background.
|
||||
void ApplyBlurWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height, int blurRadius );
|
||||
|
||||
// Replace background pixels with the loaded background image.
|
||||
void ApplyImageWithMask( uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Scale the loaded background image to the given dimensions (cached).
|
||||
void EnsureScaledBgImage( uint32_t width, uint32_t height );
|
||||
|
||||
// Decide whether inference is needed this frame (periodic + motion-adaptive).
|
||||
bool ShouldRunInference( const uint8_t* bgraPixels, uint32_t width, uint32_t height );
|
||||
|
||||
// Windows ML objects.
|
||||
winrt::Windows::AI::MachineLearning::LearningModel m_model{ nullptr };
|
||||
winrt::Windows::AI::MachineLearning::LearningModelSession m_session{ nullptr };
|
||||
winrt::Windows::AI::MachineLearning::LearningModelBinding m_binding{ nullptr };
|
||||
winrt::hstring m_inputName;
|
||||
winrt::hstring m_outputName;
|
||||
|
||||
// Model metadata (detected from the loaded model).
|
||||
int64_t m_modelInputWidth = 256;
|
||||
int64_t m_modelInputHeight = 256;
|
||||
int64_t m_modelInputChannels = 3;
|
||||
bool m_inputIsNchw = true; // true = [1,C,H,W], false = [1,H,W,C]
|
||||
bool m_usingGpu = false; // true if DirectML session is active
|
||||
|
||||
// Actual model output dimensions (may differ from input dimensions).
|
||||
int64_t m_modelOutputWidth = 256;
|
||||
int64_t m_modelOutputHeight = 256;
|
||||
|
||||
// Reusable buffers to avoid per-frame allocations.
|
||||
std::vector<float> m_inputTensor; // RGB float [1,3,H,W] or [1,H,W,3]
|
||||
std::vector<float> m_outputBuf; // Raw copy of output tensor data
|
||||
std::vector<float> m_mask; // Segmentation mask [width*height]
|
||||
std::vector<float> m_erodeBuf; // Model-resolution mask buffer
|
||||
std::vector<float> m_maskBlurBuf; // Temp buffer for mask edge smoothing
|
||||
std::vector<uint8_t> m_blurredFrame; // Temporary blurred copy
|
||||
std::vector<uint8_t> m_tempFrame; // Second temp buffer for blur passes
|
||||
|
||||
// Background image (original resolution, BGRA).
|
||||
std::vector<uint8_t> m_bgImage;
|
||||
uint32_t m_bgImageWidth = 0;
|
||||
uint32_t m_bgImageHeight = 0;
|
||||
|
||||
// Scaled background image (cached at overlay dimensions).
|
||||
std::vector<uint8_t> m_scaledBgImage;
|
||||
uint32_t m_scaledBgW = 0;
|
||||
uint32_t m_scaledBgH = 0;
|
||||
|
||||
// Frame-skipping: reuse the segmentation mask for N frames.
|
||||
int m_frameCounter = 0;
|
||||
uint32_t m_lastMaskWidth = 0;
|
||||
uint32_t m_lastMaskHeight = 0;
|
||||
bool m_hasCachedMask = false;
|
||||
|
||||
// Motion detection: luminance samples from the previous frame.
|
||||
static constexpr int MOTION_GRID_SIZE = 8; // 8×8 = 64 sample points
|
||||
float m_prevSamples[MOTION_GRID_SIZE * MOTION_GRID_SIZE] = {};
|
||||
bool m_hasPrevSamples = false;
|
||||
|
||||
// Temporal smoothing: previous frame's mask blended with current
|
||||
// to stabilize edges and reduce flicker.
|
||||
std::vector<float> m_prevMask;
|
||||
|
||||
// Model-resolution previous mask for GPU path temporal smoothing.
|
||||
std::vector<float> m_prevModelMask;
|
||||
};
|
||||
606
src/modules/ZoomIt/ZoomIt/BoxBlurCS.h
Normal file
@@ -0,0 +1,606 @@
|
||||
#if 0
|
||||
//
|
||||
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
|
||||
//
|
||||
//
|
||||
// Buffer Definitions:
|
||||
//
|
||||
// cbuffer BlurConstants
|
||||
// {
|
||||
//
|
||||
// uint Direction; // Offset: 0 Size: 4
|
||||
// int Radius; // Offset: 4 Size: 4
|
||||
// uint Width; // Offset: 8 Size: 4
|
||||
// uint Height; // Offset: 12 Size: 4
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// Resource Bindings:
|
||||
//
|
||||
// Name Type Format Dim HLSL Bind Count
|
||||
// ------------------------------ ---------- ------- ----------- -------------- ------
|
||||
// InputTex texture float4 2d t0 1
|
||||
// OutputTex UAV float4 2d u0 1
|
||||
// BlurConstants cbuffer NA NA cb0 1
|
||||
//
|
||||
//
|
||||
//
|
||||
// Input signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// no Input
|
||||
//
|
||||
// Output signature:
|
||||
//
|
||||
// Name Index Mask Register SysValue Format Used
|
||||
// -------------------- ----- ------ -------- -------- ------- ------
|
||||
// no Output
|
||||
cs_5_0
|
||||
dcl_globalFlags refactoringAllowed
|
||||
dcl_constantbuffer CB0[1], immediateIndexed
|
||||
dcl_resource_texture2d (float,float,float,float) t0
|
||||
dcl_uav_typed_texture2d (float,float,float,float) u0
|
||||
dcl_input vThreadGroupID.xy
|
||||
dcl_input vThreadIDInGroup.x
|
||||
dcl_temps 4
|
||||
dcl_tgsm_structured g0, 16, 320
|
||||
dcl_thread_group 256, 1, 1
|
||||
imin r0.x, cb0[0].y, l(32)
|
||||
ishl r0.y, r0.x, l(1)
|
||||
iadd r0.z, r0.y, l(256)
|
||||
if_z cb0[0].x
|
||||
uge r0.w, vThreadGroupID.y, cb0[0].w
|
||||
if_nz r0.w
|
||||
ret
|
||||
endif
|
||||
ishl r0.w, vThreadGroupID.x, l(8)
|
||||
iadd r1.x, cb0[0].z, l(-1)
|
||||
mov r2.y, vThreadGroupID.y
|
||||
mov r2.zw, l(0,0,0,0)
|
||||
mov r1.y, vThreadIDInGroup.x
|
||||
loop
|
||||
ige r1.z, r1.y, r0.z
|
||||
breakc_nz r1.z
|
||||
iadd r1.z, r0.w, r1.y
|
||||
iadd r1.z, -r0.x, r1.z
|
||||
imax r1.z, r1.z, l(0)
|
||||
imin r2.x, r1.x, r1.z
|
||||
ld_indexable(texture2d)(float,float,float,float) r3.xyzw, r2.xyzw, t0.xyzw
|
||||
store_structured g0.xyzw, r1.y, l(0), r3.xyzw
|
||||
iadd r1.y, r1.y, l(256)
|
||||
endloop
|
||||
sync_g_t
|
||||
imad r1.x, vThreadGroupID.x, l(256), vThreadIDInGroup.x
|
||||
uge r0.w, r1.x, cb0[0].z
|
||||
if_nz r0.w
|
||||
ret
|
||||
endif
|
||||
mov r2.xyzw, l(0,0,0,0)
|
||||
mov r0.w, l(0)
|
||||
loop
|
||||
ilt r3.x, r0.y, r0.w
|
||||
breakc_nz r3.x
|
||||
iadd r3.x, r0.w, vThreadIDInGroup.x
|
||||
ld_structured r3.xyzw, r3.x, l(0), g0.xyzw
|
||||
add r2.xyzw, r2.xyzw, r3.xyzw
|
||||
iadd r0.w, r0.w, l(1)
|
||||
endloop
|
||||
bfi r0.w, l(31), l(1), r0.x, l(1)
|
||||
itof r0.w, r0.w
|
||||
div r2.xyzw, r2.xyzw, r0.wwww
|
||||
mov r1.yzw, vThreadGroupID.yyyy
|
||||
store_uav_typed u0.xyzw, r1.xyzw, r2.xyzw
|
||||
else
|
||||
uge r0.w, vThreadGroupID.y, cb0[0].z
|
||||
if_nz r0.w
|
||||
ret
|
||||
endif
|
||||
ishl r0.w, vThreadGroupID.x, l(8)
|
||||
iadd r1.x, cb0[0].w, l(-1)
|
||||
mov r2.x, vThreadGroupID.y
|
||||
mov r2.zw, l(0,0,0,0)
|
||||
mov r1.y, vThreadIDInGroup.x
|
||||
loop
|
||||
ige r1.z, r1.y, r0.z
|
||||
breakc_nz r1.z
|
||||
iadd r1.z, r0.w, r1.y
|
||||
iadd r1.z, -r0.x, r1.z
|
||||
imax r1.z, r1.z, l(0)
|
||||
imin r2.y, r1.x, r1.z
|
||||
ld_indexable(texture2d)(float,float,float,float) r3.xyzw, r2.xyzw, t0.xyzw
|
||||
store_structured g0.xyzw, r1.y, l(0), r3.xyzw
|
||||
iadd r1.y, r1.y, l(256)
|
||||
endloop
|
||||
sync_g_t
|
||||
imad r1.yzw, vThreadGroupID.xxxx, l(0, 256, 256, 256), vThreadIDInGroup.xxxx
|
||||
uge r0.z, r1.w, cb0[0].w
|
||||
if_nz r0.z
|
||||
ret
|
||||
endif
|
||||
mov r2.xyzw, l(0,0,0,0)
|
||||
mov r0.z, l(0)
|
||||
loop
|
||||
ilt r0.w, r0.y, r0.z
|
||||
breakc_nz r0.w
|
||||
iadd r0.w, r0.z, vThreadIDInGroup.x
|
||||
ld_structured r3.xyzw, r0.w, l(0), g0.xyzw
|
||||
add r2.xyzw, r2.xyzw, r3.xyzw
|
||||
iadd r0.z, r0.z, l(1)
|
||||
endloop
|
||||
bfi r0.x, l(31), l(1), r0.x, l(1)
|
||||
itof r0.x, r0.x
|
||||
div r0.xyzw, r2.xyzw, r0.xxxx
|
||||
mov r1.x, vThreadGroupID.y
|
||||
store_uav_typed u0.xyzw, r1.xyzw, r0.xyzw
|
||||
endif
|
||||
ret
|
||||
// Approximately 89 instruction slots used
|
||||
#endif
|
||||
|
||||
const BYTE g_BoxBlurCS[] =
|
||||
{
|
||||
68, 88, 66, 67, 109, 156,
|
||||
172, 79, 78, 198, 69, 61,
|
||||
87, 5, 27, 232, 85, 229,
|
||||
69, 181, 1, 0, 0, 0,
|
||||
212, 10, 0, 0, 5, 0,
|
||||
0, 0, 52, 0, 0, 0,
|
||||
80, 2, 0, 0, 96, 2,
|
||||
0, 0, 112, 2, 0, 0,
|
||||
56, 10, 0, 0, 82, 68,
|
||||
69, 70, 20, 2, 0, 0,
|
||||
1, 0, 0, 0, 192, 0,
|
||||
0, 0, 3, 0, 0, 0,
|
||||
60, 0, 0, 0, 0, 5,
|
||||
83, 67, 0, 1, 0, 0,
|
||||
233, 1, 0, 0, 82, 68,
|
||||
49, 49, 60, 0, 0, 0,
|
||||
24, 0, 0, 0, 32, 0,
|
||||
0, 0, 40, 0, 0, 0,
|
||||
36, 0, 0, 0, 12, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
156, 0, 0, 0, 2, 0,
|
||||
0, 0, 5, 0, 0, 0,
|
||||
4, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 13, 0,
|
||||
0, 0, 165, 0, 0, 0,
|
||||
4, 0, 0, 0, 5, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
13, 0, 0, 0, 175, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
73, 110, 112, 117, 116, 84,
|
||||
101, 120, 0, 79, 117, 116,
|
||||
112, 117, 116, 84, 101, 120,
|
||||
0, 66, 108, 117, 114, 67,
|
||||
111, 110, 115, 116, 97, 110,
|
||||
116, 115, 0, 171, 171, 171,
|
||||
175, 0, 0, 0, 4, 0,
|
||||
0, 0, 216, 0, 0, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
120, 1, 0, 0, 0, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
2, 0, 0, 0, 136, 1,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 172, 1,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
4, 0, 0, 0, 2, 0,
|
||||
0, 0, 184, 1, 0, 0,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 220, 1, 0, 0,
|
||||
8, 0, 0, 0, 4, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
136, 1, 0, 0, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 255, 255,
|
||||
255, 255, 0, 0, 0, 0,
|
||||
226, 1, 0, 0, 12, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
2, 0, 0, 0, 136, 1,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
255, 255, 255, 255, 0, 0,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
0, 0, 0, 0, 68, 105,
|
||||
114, 101, 99, 116, 105, 111,
|
||||
110, 0, 100, 119, 111, 114,
|
||||
100, 0, 0, 0, 19, 0,
|
||||
1, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 130, 1,
|
||||
0, 0, 82, 97, 100, 105,
|
||||
117, 115, 0, 105, 110, 116,
|
||||
0, 171, 0, 0, 2, 0,
|
||||
1, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 179, 1,
|
||||
0, 0, 87, 105, 100, 116,
|
||||
104, 0, 72, 101, 105, 103,
|
||||
104, 116, 0, 77, 105, 99,
|
||||
114, 111, 115, 111, 102, 116,
|
||||
32, 40, 82, 41, 32, 72,
|
||||
76, 83, 76, 32, 83, 104,
|
||||
97, 100, 101, 114, 32, 67,
|
||||
111, 109, 112, 105, 108, 101,
|
||||
114, 32, 49, 48, 46, 49,
|
||||
0, 171, 171, 171, 73, 83,
|
||||
71, 78, 8, 0, 0, 0,
|
||||
0, 0, 0, 0, 8, 0,
|
||||
0, 0, 79, 83, 71, 78,
|
||||
8, 0, 0, 0, 0, 0,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
83, 72, 69, 88, 192, 7,
|
||||
0, 0, 80, 0, 5, 0,
|
||||
240, 1, 0, 0, 106, 8,
|
||||
0, 1, 89, 0, 0, 4,
|
||||
70, 142, 32, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
88, 24, 0, 4, 0, 112,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
85, 85, 0, 0, 156, 24,
|
||||
0, 4, 0, 224, 17, 0,
|
||||
0, 0, 0, 0, 85, 85,
|
||||
0, 0, 95, 0, 0, 2,
|
||||
50, 16, 2, 0, 95, 0,
|
||||
0, 2, 18, 32, 2, 0,
|
||||
104, 0, 0, 2, 4, 0,
|
||||
0, 0, 160, 0, 0, 5,
|
||||
0, 240, 17, 0, 0, 0,
|
||||
0, 0, 16, 0, 0, 0,
|
||||
64, 1, 0, 0, 155, 0,
|
||||
0, 4, 0, 1, 0, 0,
|
||||
1, 0, 0, 0, 1, 0,
|
||||
0, 0, 37, 0, 0, 8,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 128, 32, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
32, 0, 0, 0, 41, 0,
|
||||
0, 7, 34, 0, 16, 0,
|
||||
0, 0, 0, 0, 10, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
31, 0, 0, 4, 10, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 80, 0,
|
||||
0, 7, 130, 0, 16, 0,
|
||||
0, 0, 0, 0, 26, 16,
|
||||
2, 0, 58, 128, 32, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 31, 0, 4, 3,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 62, 0, 0, 1,
|
||||
21, 0, 0, 1, 41, 0,
|
||||
0, 6, 130, 0, 16, 0,
|
||||
0, 0, 0, 0, 10, 16,
|
||||
2, 0, 1, 64, 0, 0,
|
||||
8, 0, 0, 0, 30, 0,
|
||||
0, 8, 18, 0, 16, 0,
|
||||
1, 0, 0, 0, 42, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 255, 255, 255, 255,
|
||||
54, 0, 0, 4, 34, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
26, 16, 2, 0, 54, 0,
|
||||
0, 8, 194, 0, 16, 0,
|
||||
2, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 4, 34, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
10, 32, 2, 0, 48, 0,
|
||||
0, 1, 33, 0, 0, 7,
|
||||
66, 0, 16, 0, 1, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
3, 0, 4, 3, 42, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
30, 0, 0, 7, 66, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 30, 0,
|
||||
0, 8, 66, 0, 16, 0,
|
||||
1, 0, 0, 0, 10, 0,
|
||||
16, 128, 65, 0, 0, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
36, 0, 0, 7, 66, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 0, 0, 37, 0,
|
||||
0, 7, 18, 0, 16, 0,
|
||||
2, 0, 0, 0, 10, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 45, 0, 0, 137,
|
||||
194, 0, 0, 128, 67, 85,
|
||||
21, 0, 242, 0, 16, 0,
|
||||
3, 0, 0, 0, 70, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 126, 16, 0, 0, 0,
|
||||
0, 0, 168, 0, 0, 9,
|
||||
242, 240, 17, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
70, 14, 16, 0, 3, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
34, 0, 16, 0, 1, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
1, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
22, 0, 0, 1, 190, 24,
|
||||
0, 1, 35, 0, 0, 7,
|
||||
18, 0, 16, 0, 1, 0,
|
||||
0, 0, 10, 16, 2, 0,
|
||||
1, 64, 0, 0, 0, 1,
|
||||
0, 0, 10, 32, 2, 0,
|
||||
80, 0, 0, 8, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 0, 16, 0, 1, 0,
|
||||
0, 0, 42, 128, 32, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 31, 0, 4, 3,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 62, 0, 0, 1,
|
||||
21, 0, 0, 1, 54, 0,
|
||||
0, 8, 242, 0, 16, 0,
|
||||
2, 0, 0, 0, 2, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 5, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 48, 0, 0, 1,
|
||||
34, 0, 0, 7, 18, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
26, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 3, 0,
|
||||
4, 3, 10, 0, 16, 0,
|
||||
3, 0, 0, 0, 30, 0,
|
||||
0, 6, 18, 0, 16, 0,
|
||||
3, 0, 0, 0, 58, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 32, 2, 0, 167, 0,
|
||||
0, 9, 242, 0, 16, 0,
|
||||
3, 0, 0, 0, 10, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 70, 254, 17, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 7, 242, 0, 16, 0,
|
||||
2, 0, 0, 0, 70, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 14, 16, 0, 3, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
130, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
22, 0, 0, 1, 140, 0,
|
||||
0, 11, 130, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 31, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 1, 64,
|
||||
0, 0, 1, 0, 0, 0,
|
||||
43, 0, 0, 5, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 14, 0, 0, 7,
|
||||
242, 0, 16, 0, 2, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
2, 0, 0, 0, 246, 15,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
54, 0, 0, 4, 226, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
86, 21, 2, 0, 164, 0,
|
||||
0, 7, 242, 224, 17, 0,
|
||||
0, 0, 0, 0, 70, 14,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
70, 14, 16, 0, 2, 0,
|
||||
0, 0, 18, 0, 0, 1,
|
||||
80, 0, 0, 7, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
26, 16, 2, 0, 42, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 31, 0,
|
||||
4, 3, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 62, 0,
|
||||
0, 1, 21, 0, 0, 1,
|
||||
41, 0, 0, 6, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
10, 16, 2, 0, 1, 64,
|
||||
0, 0, 8, 0, 0, 0,
|
||||
30, 0, 0, 8, 18, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
58, 128, 32, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 255, 255,
|
||||
255, 255, 54, 0, 0, 4,
|
||||
18, 0, 16, 0, 2, 0,
|
||||
0, 0, 26, 16, 2, 0,
|
||||
54, 0, 0, 8, 194, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 4,
|
||||
34, 0, 16, 0, 1, 0,
|
||||
0, 0, 10, 32, 2, 0,
|
||||
48, 0, 0, 1, 33, 0,
|
||||
0, 7, 66, 0, 16, 0,
|
||||
1, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 3, 0, 4, 3,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 30, 0, 0, 7,
|
||||
66, 0, 16, 0, 1, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
0, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
30, 0, 0, 8, 66, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
10, 0, 16, 128, 65, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
42, 0, 16, 0, 1, 0,
|
||||
0, 0, 36, 0, 0, 7,
|
||||
66, 0, 16, 0, 1, 0,
|
||||
0, 0, 42, 0, 16, 0,
|
||||
1, 0, 0, 0, 1, 64,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
37, 0, 0, 7, 34, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
10, 0, 16, 0, 1, 0,
|
||||
0, 0, 42, 0, 16, 0,
|
||||
1, 0, 0, 0, 45, 0,
|
||||
0, 137, 194, 0, 0, 128,
|
||||
67, 85, 21, 0, 242, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
70, 14, 16, 0, 2, 0,
|
||||
0, 0, 70, 126, 16, 0,
|
||||
0, 0, 0, 0, 168, 0,
|
||||
0, 9, 242, 240, 17, 0,
|
||||
0, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
3, 0, 0, 0, 30, 0,
|
||||
0, 7, 34, 0, 16, 0,
|
||||
1, 0, 0, 0, 26, 0,
|
||||
16, 0, 1, 0, 0, 0,
|
||||
1, 64, 0, 0, 0, 1,
|
||||
0, 0, 22, 0, 0, 1,
|
||||
190, 24, 0, 1, 35, 0,
|
||||
0, 10, 226, 0, 16, 0,
|
||||
1, 0, 0, 0, 6, 16,
|
||||
2, 0, 2, 64, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 6, 32,
|
||||
2, 0, 80, 0, 0, 8,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 58, 0, 16, 0,
|
||||
1, 0, 0, 0, 58, 128,
|
||||
32, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 31, 0,
|
||||
4, 3, 42, 0, 16, 0,
|
||||
0, 0, 0, 0, 62, 0,
|
||||
0, 1, 21, 0, 0, 1,
|
||||
54, 0, 0, 8, 242, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
2, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 5,
|
||||
66, 0, 16, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 0, 0, 48, 0,
|
||||
0, 1, 34, 0, 0, 7,
|
||||
130, 0, 16, 0, 0, 0,
|
||||
0, 0, 26, 0, 16, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
3, 0, 4, 3, 58, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
30, 0, 0, 6, 130, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
42, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 32, 2, 0,
|
||||
167, 0, 0, 9, 242, 0,
|
||||
16, 0, 3, 0, 0, 0,
|
||||
58, 0, 16, 0, 0, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
0, 0, 0, 0, 70, 254,
|
||||
17, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 7, 242, 0,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
70, 14, 16, 0, 2, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
3, 0, 0, 0, 30, 0,
|
||||
0, 7, 66, 0, 16, 0,
|
||||
0, 0, 0, 0, 42, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 22, 0, 0, 1,
|
||||
140, 0, 0, 11, 18, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 31, 0,
|
||||
0, 0, 1, 64, 0, 0,
|
||||
1, 0, 0, 0, 10, 0,
|
||||
16, 0, 0, 0, 0, 0,
|
||||
1, 64, 0, 0, 1, 0,
|
||||
0, 0, 43, 0, 0, 5,
|
||||
18, 0, 16, 0, 0, 0,
|
||||
0, 0, 10, 0, 16, 0,
|
||||
0, 0, 0, 0, 14, 0,
|
||||
0, 7, 242, 0, 16, 0,
|
||||
0, 0, 0, 0, 70, 14,
|
||||
16, 0, 2, 0, 0, 0,
|
||||
6, 0, 16, 0, 0, 0,
|
||||
0, 0, 54, 0, 0, 4,
|
||||
18, 0, 16, 0, 1, 0,
|
||||
0, 0, 26, 16, 2, 0,
|
||||
164, 0, 0, 7, 242, 224,
|
||||
17, 0, 0, 0, 0, 0,
|
||||
70, 14, 16, 0, 1, 0,
|
||||
0, 0, 70, 14, 16, 0,
|
||||
0, 0, 0, 0, 21, 0,
|
||||
0, 1, 62, 0, 0, 1,
|
||||
83, 84, 65, 84, 148, 0,
|
||||
0, 0, 89, 0, 0, 0,
|
||||
4, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
4, 0, 0, 0, 27, 0,
|
||||
0, 0, 4, 0, 0, 0,
|
||||
7, 0, 0, 0, 8, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
12, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
2, 0, 0, 0, 0, 0,
|
||||
0, 0, 2, 0, 0, 0
|
||||
};
|
||||
115
src/modules/ZoomIt/ZoomIt/BoxBlurCS.hlsl
Normal file
@@ -0,0 +1,115 @@
|
||||
//==============================================================================
|
||||
//
|
||||
// BoxBlurCS.hlsl
|
||||
//
|
||||
// D3D11 compute shader for separable box blur. Each dispatch performs
|
||||
// either a horizontal or a vertical pass controlled by the Direction
|
||||
// constant. Run four dispatches (H→V→H→V) to approximate a Gaussian.
|
||||
//
|
||||
// Uses group-shared memory so each texel is loaded once per thread
|
||||
// group, then reused across the sliding window.
|
||||
//
|
||||
// Entry point:
|
||||
// CSMain (cs_5_0)
|
||||
//
|
||||
// Recompile with:
|
||||
// fxc /T cs_5_0 /E CSMain /Fh BoxBlurCS.h /Vn g_BoxBlurCS BoxBlurCS.hlsl
|
||||
//
|
||||
// Copyright (C) Mark Russinovich
|
||||
// Sysinternals - www.sysinternals.com
|
||||
//
|
||||
//==============================================================================
|
||||
|
||||
// Input texture (read-only).
|
||||
Texture2D<float4> InputTex : register(t0);
|
||||
|
||||
// Output texture (write-only).
|
||||
RWTexture2D<float4> OutputTex : register(u0);
|
||||
|
||||
cbuffer BlurConstants : register(b0)
|
||||
{
|
||||
uint Direction; // 0 = horizontal, 1 = vertical
|
||||
int Radius; // Box blur radius in pixels
|
||||
uint Width; // Image width
|
||||
uint Height; // Image height
|
||||
};
|
||||
|
||||
// Thread group: 256 threads along the blur axis.
|
||||
#define GROUP_SIZE 256
|
||||
|
||||
// Max radius we support. Shared memory = (GROUP_SIZE + 2*MAX_RADIUS) * 4 floats * 4 bytes
|
||||
// = (256 + 64) * 16 = ~5 KB, well within the 32 KB limit.
|
||||
#define MAX_RADIUS 32
|
||||
|
||||
// Shared memory tile: enough for the group + apron on both sides.
|
||||
groupshared float4 tile[GROUP_SIZE + 2 * MAX_RADIUS];
|
||||
|
||||
[numthreads(GROUP_SIZE, 1, 1)]
|
||||
void CSMain( uint3 groupId : SV_GroupID,
|
||||
uint3 groupTid : SV_GroupThreadID,
|
||||
uint3 dispatchId : SV_DispatchThreadID )
|
||||
{
|
||||
int r = min( Radius, MAX_RADIUS );
|
||||
int tileSize = GROUP_SIZE + 2 * r;
|
||||
int tid = (int)groupTid.x;
|
||||
|
||||
if( Direction == 0 )
|
||||
{
|
||||
// ── Horizontal pass ────────────────────────────────────────
|
||||
int row = (int)groupId.y;
|
||||
if( (uint)row >= Height )
|
||||
return;
|
||||
|
||||
int groupStart = (int)groupId.x * GROUP_SIZE;
|
||||
|
||||
// Load tile: each thread loads its primary texel + apron.
|
||||
for( int i = tid; i < tileSize; i += GROUP_SIZE )
|
||||
{
|
||||
int srcX = clamp( groupStart + i - r, 0, (int)Width - 1 );
|
||||
tile[i] = InputTex[int2( srcX, row )];
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
int outX = groupStart + tid;
|
||||
if( (uint)outX >= Width )
|
||||
return;
|
||||
|
||||
// Sum the window from shared memory.
|
||||
float4 sum = (float4)0;
|
||||
int windowStart = tid; // tile index = tid + r - r
|
||||
for( int k = 0; k <= 2 * r; k++ )
|
||||
{
|
||||
sum += tile[windowStart + k];
|
||||
}
|
||||
OutputTex[int2( outX, row )] = sum / (float)( 2 * r + 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// ── Vertical pass ──────────────────────────────────────────
|
||||
int col = (int)groupId.y;
|
||||
if( (uint)col >= Width )
|
||||
return;
|
||||
|
||||
int groupStart = (int)groupId.x * GROUP_SIZE;
|
||||
|
||||
// Load tile.
|
||||
for( int i = tid; i < tileSize; i += GROUP_SIZE )
|
||||
{
|
||||
int srcY = clamp( groupStart + i - r, 0, (int)Height - 1 );
|
||||
tile[i] = InputTex[int2( col, srcY )];
|
||||
}
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
int outY = groupStart + tid;
|
||||
if( (uint)outY >= Height )
|
||||
return;
|
||||
|
||||
float4 sum = (float4)0;
|
||||
int windowStart = tid;
|
||||
for( int k = 0; k <= 2 * r; k++ )
|
||||
{
|
||||
sum += tile[windowStart + k];
|
||||
}
|
||||
OutputTex[int2( col, outY )] = sum / (float)( 2 * r + 1 );
|
||||
}
|
||||
}
|
||||