Compare commits

..

154 Commits

Author SHA1 Message Date
Noraa Junker
9f9d221eab Adjust docs 2026-02-02 01:40:19 +01:00
Noraa Junker
b9c5c15be2 Fix taskbar indicator to work with the latest canary builds 2026-02-02 01:35:08 +01:00
Noraa Junker
3a4f9c2274 Move cpp project files to powertoys.interop 2026-02-02 00:14:14 +01:00
Noraa Junker
a51fe19f82 merge main and adapt to new modules (aswell fix no taskbar bug) 2026-02-01 22:46:35 +01:00
Noraa Junker
efc814a610 merge main 2026-02-01 12:10:14 +01:00
Noraa Junker
1d1ae0d191 Merge main 2025-10-31 11:32:22 +01:00
Noraa Junker
3df4b45849 Merge remote-tracking branch 'origin/main' into feature/shortcutguidev2 2025-10-29 20:21:45 +01:00
Noraa Junker
88357a5a99 Fixed spawning on wrong display and taskbar window not working under certain circumstances on secondary displays 2025-10-29 20:21:20 +01:00
Noraa Junker
b4773affa7 Fix installer 2025-10-28 21:47:11 +01:00
Noraa Junker
e82c2d20cb Fix spelling again 2025-10-28 14:59:33 +01:00
Noraa Junker
e3a1f97ef3 Fix spelling 2025-10-28 14:51:48 +01:00
Noraa Junker
b26ded5370 Fix spelling 2025-10-28 14:51:36 +01:00
Noraa Junker
2e8ad4827b Fix some spelling issues 2025-10-28 14:43:54 +01:00
Noraa Junker
877626ef45 Added manifest file in 2025-10-28 14:30:41 +01:00
Noraa Junker
e0f72df36c Update ShortcutGuide image 2025-10-28 10:18:40 +01:00
Noraa Junker
552b02d596 Fix spawning window on the right monitor 2025-10-28 00:25:08 +01:00
Noraa Junker
c184acbada Fix xaml styling 2025-10-27 23:42:45 +01:00
Noraa Junker
55038c3c5e Fix missing localization on settings button 2025-10-27 23:42:26 +01:00
Noraa Junker
f537c43139 Added right key visuals and fixed some UI bugs 2025-10-27 23:36:17 +01:00
Noraa Junker
f867323677 Update OOBE string, supress window resizing and fix auto closing when taskbar window opened 2025-10-25 21:01:39 +02:00
Noraa Junker
d58145eb8c add zoomit shortcuts comment 2025-10-25 12:07:15 +02:00
Noraa Junker
486bec0ebd Fix empty windows key 2025-10-18 23:26:53 +02:00
Noraa Junker
e76506dffd Localization 2025-10-18 23:26:16 +02:00
Noraa Junker
841a5c5555 Fixed (un)pinning shortcuts 2025-10-18 23:04:47 +02:00
Noraa Junker
f646e0328e Fix wrong number for Taskbar item 10 2025-10-18 22:06:58 +02:00
Noraa Junker
5054a776dd Fix solution merge error 2025-10-18 22:06:46 +02:00
Noraa Junker
9d480c8e2c Merge conflicts 2025-10-18 21:51:43 +02:00
Niels Laute
d652285a81 [UX] Vertical shortcutguide (#41161)
- Exploring a vertical version for Shortcut Guide 2, to make better use
of the screen real estate
- Cleanup code and improved maintainability

<img width="1391" height="1439" alt="image"
src="https://github.com/user-attachments/assets/7ee3c925-71f1-46ee-83f6-4bc43b69db4c"
/>

<img width="715" height="1065" alt="image"
src="https://github.com/user-attachments/assets/e59684b2-2063-453e-93c7-df770eaa6999"
/>


To do:
- Shortcut visualizations are broken (well.. sometimes!)
- A lot of UX nits

---------

Co-authored-by: Aaron Junker <Aaron.Junker@outlook.com>
2025-10-17 14:39:35 +02:00
Aaron Junker
200afb5c4b merge main 2025-08-20 21:15:49 +02:00
Gordon Lam (SH)
e7582ebd6a Add back empty line which is missing during rebase main 2025-08-19 08:24:08 +08:00
Gordon Lam (SH)
b9c1181d9f Add back missing project because of rebase from main: 2025-08-19 08:16:20 +08:00
Aaron Junker
4aff3418e4 Change display name of File Explorer, hide taskbar indicators on other pages then Windows -> Overview and fix height of Windows -> Overview 2025-08-19 08:09:35 +08:00
Aaron Junker
e53e1b4376 fix xaml styling 2025-08-19 08:09:35 +08:00
Aaron Junker
73f718c233 git messed up 2025-08-19 08:09:34 +08:00
Aaron Junker
0917a64e7d close infotip when link is clicked and remove unneccesairy grid 2025-08-19 08:09:34 +08:00
Aaron Junker
a764bf3e0c Change oobe design 2025-08-19 08:09:34 +08:00
Aaron Junker
4853bd0345 Change some StaticResources to ThemeResources and change style of taskbar indicators 2025-08-19 08:09:34 +08:00
Aaron Junker
dceb1d7730 Fix styling 2025-08-19 08:09:34 +08:00
Aaron Junker
72be09554e Factor out TaskbarIndicator 2025-08-19 08:09:34 +08:00
Aaron Junker
19f95066c3 Fix xaml styling 2025-08-19 08:09:34 +08:00
Aaron Junker
bb16ae1709 Adress some PR comments and fix some bugs 2025-08-19 08:09:34 +08:00
Aaron Junker
dc0877ebe5 Make some adjustants to how the windows key is displayed 2025-08-19 08:09:34 +08:00
Aaron Junker
cd844e3889 Forgot notice.md and CPPProject has some trouble buildinng release x64 2025-08-19 08:09:34 +08:00
Aaron Junker
ac789a7fbe Remove failing test 2025-08-19 08:09:34 +08:00
Aaron Junker
3b7df37ac2 That is a kinda embarrasing error 2025-08-19 08:09:34 +08:00
Aaron Junker
387b7e9795 So something was not right. Hopefully this fixes it. 2025-08-19 08:09:34 +08:00
Aaron Junker
b553addcdd Fix arm64 configuration 2025-08-19 08:09:28 +08:00
Aaron Junker
5c11c751fe Fix xaml styling 2025-08-19 08:05:58 +08:00
Aaron Junker
6d7d5f9cde Fix building in release mode and some other stuff 2025-08-19 08:05:58 +08:00
Aaron Junker
4cb9c53809 Add documentation and only export in tasklist_positions what needs to be exported 2025-08-19 08:05:58 +08:00
Aaron Junker
84d4cbb16d Refactoring, commenting and fixing some little lefrover bugs 2025-08-19 08:05:49 +08:00
Aaron Junker
97cba618da Add explorer shortcuts, fix animation stopping and add an error when index.yml generation fails 2025-08-19 08:05:26 +08:00
Aaron Junker
145247c4fb Add attribution in settings 2025-08-19 08:04:42 +08:00
Aaron Junker
84ab12027b Add welcome screen and update settings and OOBE 2025-08-19 08:04:42 +08:00
Aaron Junker
3458d01d4c Respect excluded apps 2025-08-19 08:04:41 +08:00
Aaron Junker
2e6f80f944 Respect theme selection 2025-08-19 08:04:41 +08:00
Aaron Junker
f8cc513f9c Localization 2025-08-19 08:04:41 +08:00
Aaron Junker
b1d5233622 Fix spelling 2025-08-19 08:04:41 +08:00
Aaron Junker
68b7b4183f Make UI better 2025-08-19 08:04:41 +08:00
Aaron Junker
68a10d0488 Add disclaimers 2025-08-19 08:04:41 +08:00
Aaron Junker
e70ca56e9d Add taskbar launch shortcuts and make powertoys shortcuts empty by default 2025-08-19 08:04:34 +08:00
Aaron Junker
16c4a56ca1 Make settings button work and add settings placeholder 2025-08-19 08:02:30 +08:00
Aaron Junker
0d5c85a00d Add taskbar launch shortcuts and make powertoys shortcuts empty by default 2025-08-19 08:02:29 +08:00
Aaron Junker
509ad636fe Fix spelling 2025-08-19 08:02:29 +08:00
Aaron Junker
26f76105d4 Delete weird file 2025-08-19 08:02:29 +08:00
Aaron Junker
639b29eb8c Fix pinning and unpinning shortcuts 2025-08-19 08:02:29 +08:00
Aaron Junker
bff3874b5f Only display powertoys shortcuts if the modules are enabled 2025-08-19 08:02:29 +08:00
Aaron Junker
eff58e1df5 Fix closing by shortcut add closing by ESC and fix missing files from CPPProject 2025-08-19 08:02:29 +08:00
Aaron Junker
411f4df2c0 Remove legacy shortcut behaviour 2025-08-19 08:02:29 +08:00
Aaron Junker
7dc8c1000b Remove old Shortcut Guide 2025-08-19 08:02:23 +08:00
Aaron Junker
48d8e33375 Refactoring 2025-08-19 08:00:02 +08:00
Aaron Junker
afc27e873f Remove some hosts references and fix close button 2025-08-19 08:00:02 +08:00
Aaron Junker
3302e61d72 Refactoring and localisation 2025-08-19 08:00:02 +08:00
Aaron Junker
e6edca93e7 Add taskbar indicators 2025-08-19 07:59:56 +08:00
Aaron Junker
7acab452d5 Handle errors displaying app and close window automatically on focus change 2025-08-19 07:59:29 +08:00
Aaron Junker
0a07811233 Add keyboard accelerator to the search box 2025-08-19 07:59:29 +08:00
Aaron Junker
9ecf82d2ea Add copying keyboard manifests and other improvements 2025-08-19 07:59:19 +08:00
Aaron Junker
44d12c6e63 Add support for multiple shortcuts 2025-08-19 07:58:42 +08:00
Aaron Junker
6b8a3e65f7 push 2025-08-19 07:58:42 +08:00
Aaron Junker
2b16068a7d Rename YmlInterpreter to ManifestInterpreter 2025-08-19 07:58:42 +08:00
Aaron Junker
440e75184a Fix error messages and read application titles out of index manifest 2025-08-19 07:58:42 +08:00
Aaron Junker
acf510dff5 Fix display on monitor with mouse and move all NaticeMethods to NativeMethods.cs 2025-08-19 07:58:42 +08:00
Aaron Junker
ddd090cc81 Code cleanup 2025-08-19 07:58:42 +08:00
Aaron Junker
2f4766df19 Push 2025-08-19 07:58:42 +08:00
Aaron Junker
6558260c53 Push 2025-08-19 07:58:42 +08:00
Aaron Junker
55b3e15f10 Changed style a little bit 2025-08-19 07:58:42 +08:00
Aaron Junker
69c6475e15 [WIP] Shortcut Guide V2 2025-08-19 07:58:21 +08:00
Aaron Junker
8e7be164a9 Change display name of File Explorer, hide taskbar indicators on other pages then Windows -> Overview and fix height of Windows -> Overview 2025-08-07 11:57:04 +02:00
Aaron Junker
f42b3922c7 fix xaml styling 2025-08-05 10:33:42 +02:00
Aaron Junker
d568d16560 git messed up 2025-08-05 00:06:07 +02:00
Aaron Junker
0abae1d190 close infotip when link is clicked and remove unneccesairy grid 2025-08-04 23:51:18 +02:00
Aaron Junker
271e0c0533 Change oobe design 2025-08-04 23:34:23 +02:00
Aaron Junker
6bd5c4c811 Change some StaticResources to ThemeResources and change style of taskbar indicators 2025-08-03 22:37:40 +02:00
Aaron Junker
a41be807a4 Fix styling 2025-08-03 16:31:57 +02:00
Aaron Junker
e11626550e Factor out TaskbarIndicator 2025-08-03 16:30:59 +02:00
Aaron Junker
7266745124 Fix xaml styling 2025-08-01 17:53:23 +02:00
Aaron Junker
77a5bc2ff5 Adress some PR comments and fix some bugs 2025-08-01 17:51:32 +02:00
Aaron Junker
0b6683eb34 Make some adjustants to how the windows key is displayed 2025-08-01 17:06:54 +02:00
Aaron Junker
3796fdb706 Forgot notice.md and CPPProject has some trouble buildinng release x64 2025-07-30 18:17:26 +02:00
Aaron Junker
2d12932e44 Remove failing test 2025-07-30 17:31:31 +02:00
Aaron Junker
1da76e55bb Merge branch 'feature/shortcutguidev2' of https://github.com/microsoft/PowerToys into feature/shortcutguidev2 2025-07-30 17:21:46 +02:00
Aaron Junker
3c1a6a5b16 That is a kinda embarrasing error 2025-07-30 17:21:37 +02:00
Noraa Junker-Wildi
3b77feb879 Merge branch 'main' into feature/shortcutguidev2 2025-07-30 16:58:28 +02:00
Aaron Junker
7e7bb04d48 So something was not right. Hopefully this fixes it. 2025-07-30 16:50:20 +02:00
Aaron Junker
273b50cb16 Fix arm64 configuration 2025-07-29 18:51:21 +02:00
Aaron Junker
498c8d534f Fix xaml styling 2025-07-29 18:01:23 +02:00
Aaron Junker
0e2f466454 Fix building in release mode and some other stuff 2025-07-29 17:48:28 +02:00
Aaron Junker
1982f2615d Add documentation and only export in tasklist_positions what needs to be exported 2025-07-29 15:37:48 +02:00
Aaron Junker
2f89281178 Refactoring, commenting and fixing some little lefrover bugs 2025-07-28 16:12:36 +02:00
Aaron Junker
56f056e492 Add explorer shortcuts, fix animation stopping and add an error when index.yml generation fails 2025-07-28 14:08:42 +02:00
Aaron Junker
71dd8fe83f Add attribution in settings 2025-07-28 02:07:14 +02:00
Aaron Junker
f9183af53d Add welcome screen and update settings and OOBE 2025-07-28 02:02:55 +02:00
Aaron Junker
0f85f8bad6 Respect excluded apps 2025-07-27 23:48:49 +02:00
Aaron Junker
e0e7bf4df2 Respect theme selection 2025-07-27 22:45:14 +02:00
Aaron Junker
b12fcf6699 Localization 2025-07-27 22:06:15 +02:00
Aaron Junker
2ee02c4bbe Fix spelling 2025-07-27 21:12:12 +02:00
Aaron Junker
a306797d21 Make UI better 2025-07-27 21:11:16 +02:00
Aaron Junker
7e50caa04e Add disclaimers 2025-07-27 20:43:27 +02:00
Aaron Junker
e3b1ec356e commit 2025-07-27 20:30:03 +02:00
Aaron Junker
fa54b49fca Make settings button work and add settings placeholder 2025-07-27 20:29:36 +02:00
Aaron Junker
7e2fc4481d Add taskbar launch shortcuts and make powertoys shortcuts empty by default 2025-07-27 20:15:01 +02:00
Aaron Junker
201a27d2bb Add taskbar launch shortcuts and make powertoys shortcuts empty by default 2025-07-27 20:09:54 +02:00
Aaron Junker
fe3d481407 Fix spelling 2025-07-27 19:48:55 +02:00
Aaron Junker
3475c92f32 Delete weird file 2025-07-27 19:42:18 +02:00
Aaron Junker
82b0ca71fd Fix pinning and unpinning shortcuts 2025-07-27 19:39:43 +02:00
Aaron Junker
3a8431ae9d Only display powertoys shortcuts if the modules are enabled 2025-07-27 18:48:32 +02:00
Aaron Junker
eed6dc6f8d Merge branch 'feature/shortcutguidev2' of https://github.com/microsoft/PowerToys into feature/shortcutguidev2 2025-07-27 18:17:41 +02:00
Aaron Junker
038cd23423 Merge branch 'feature/shortcutguidev2' of https://github.com/microsoft/PowerToys into feature/shortcutguidev2 2025-07-27 18:17:39 +02:00
Aaron Junker
be799ddd82 Merge branch 'feature/shortcutguidev2' of https://github.com/microsoft/PowerToys into feature/shortcutguidev2 2025-07-27 18:00:52 +02:00
Aaron Junker
e26bf2acd6 Merge branch 'feature/shortcutguidev2' of https://github.com/microsoft/PowerToys into feature/shortcutguidev2 2025-07-27 18:00:49 +02:00
Aaron Junker
95e0a20444 Merge branch 'feature/shortcutguidev2' of https://github.com/microsoft/PowerToys into feature/shortcutguidev2 2025-07-27 17:47:27 +02:00
Aaron Junker
3e75fb0c52 Merge main in 2025-07-27 17:46:58 +02:00
Aaron Junker
ba4098960c Merge main in 2025-07-27 17:46:48 +02:00
Aaron Junker
1580279be1 Fix closing by shortcut add closing by ESC and fix missing files from CPPProject 2025-07-27 17:42:41 +02:00
Aaron Junker
07760e4730 Remove legacy shortcut behaviour 2025-07-27 16:59:25 +02:00
Aaron Junker
d73ab4f2d3 Remove old Shortcut Guide 2025-07-27 16:40:44 +02:00
Aaron Junker
a6b761433e Refactoring 2025-06-18 21:05:34 +02:00
Aaron Junker
da77396da5 Remove some hosts references and fix close button 2025-06-15 23:20:26 +02:00
Aaron Junker
46df48684d Refactoring and localisation 2025-06-15 22:13:41 +02:00
Aaron Junker
7596e965ef Add taskbar indicators 2025-06-13 23:37:54 +02:00
Aaron Junker
0b823ea8bd Handle errors displaying app and close window automatically on focus change 2025-06-11 15:41:39 +02:00
Aaron Junker
54d176c4c4 Add keyboard accelerator to the search box 2025-06-11 15:33:57 +02:00
Aaron Junker
569f07268b Add copying keyboard manifests and other improvements 2025-06-11 15:26:34 +02:00
Aaron Junker
7ce5182695 Add support for multiple shortcuts 2025-06-07 02:21:34 +02:00
Aaron Junker
b16b4579fa push 2025-06-06 23:32:40 +02:00
Aaron Junker
a292a92f4d Merge main 2025-06-06 22:20:01 +02:00
Aaron Junker
6952deb4ae Rename YmlInterpreter to ManifestInterpreter 2024-11-10 15:06:36 +01:00
Aaron Junker
da7b789bfe Fix error messages and read application titles out of index manifest 2024-11-10 15:05:34 +01:00
Aaron Junker
a14c458f19 Fix display on monitor with mouse and move all NaticeMethods to NativeMethods.cs 2024-11-04 00:17:43 +01:00
Aaron Junker
22dc870991 Code cleanup 2024-11-04 00:11:16 +01:00
Aaron Junker
d5f5500347 Push 2024-11-03 22:06:38 +01:00
Aaron Junker
c81a423880 Push 2024-11-02 15:10:33 +01:00
Aaron Junker
7a6189ba3e Changed style a little bit 2024-10-14 20:37:55 +02:00
Aaron Junker
03587ae800 [WIP] Shortcut Guide V2 2024-10-14 19:02:22 +02:00
428 changed files with 10203 additions and 35886 deletions

View File

@@ -1,8 +0,0 @@
{
"permissions": {
"allow": [
"Bash(grep:*)",
"Bash(ls:*)"
]
}
}

View File

@@ -47,6 +47,7 @@ resw
resx
srt
Stereolithography
taskmgr
terabyte
UYVY
xbf
@@ -320,6 +321,10 @@ MRUCMPPROC
MRUINFO
REGSTR
#Xaml
NVI
Storyboards
# Misc Win32 APIs and PInvokes
INVOKEIDLIST

View File

@@ -101,7 +101,6 @@
^doc/devdocs/akaLinks\.md$
^NOTICE\.md$
^src/common/CalculatorEngineCommon/exprtk\.hpp$
^src/common/UnitTests-CommonUtils/
^src/common/ManagedCommon/ColorFormatHelper\.cs$
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
^src/common/sysinternals/Eula/

View File

@@ -11,7 +11,6 @@ ACCESSDENIED
ACCESSTOKEN
acfs
ACIE
ACR
AClient
AColumn
acrt
@@ -45,7 +44,6 @@ ALLCHILDREN
ALLINPUT
Allman
Allmodule
ALLNOISE
ALLOWUNDO
ALLVIEW
ALPHATYPE
@@ -59,6 +57,7 @@ AOC
aocfnapldcnfbofgmbbllojgocaelgdd
AOklab
aot
APARTMENTTHREADED
APeriod
apicontract
apidl
@@ -96,7 +95,6 @@ asf
Ashcraft
AShortcut
ASingle
ASUS
ASSOCCHANGED
ASSOCF
ASSOCSTR
@@ -106,7 +104,6 @@ atl
ATRIOX
aumid
authenticode
AUO
AUTOBUDDY
AUTOCHECKBOX
AUTOHIDE
@@ -124,10 +121,6 @@ azureaiinference
azureinference
azureopenai
backticks
Backlight
Badflags
Badmode
Badparam
bbwe
BCIE
bck
@@ -136,7 +129,6 @@ bezelled
bhid
BIF
bigbar
BIGGERSIZEOK
bigobj
binlog
binres
@@ -201,7 +193,6 @@ Carlseibert
CAtl
caub
CBN
Cds
cch
CCHDEVICENAME
CCHFORMNAME
@@ -221,7 +212,6 @@ checkmarks
CHILDACTIVATE
CHILDWINDOW
CHOOSEFONT
Chunghwa
CIBUILD
cidl
CIELCh
@@ -236,7 +226,7 @@ claude
CLEARTYPE
clickable
clickonce
clientedge
CLIENTEDGE
clientid
clientside
CLIPBOARDUPDATE
@@ -248,7 +238,6 @@ CLSCTX
clsids
Clusion
cmder
CMN
CMDNOTFOUNDMODULEINTERFACE
cmdpal
CMIC
@@ -303,7 +292,6 @@ Corpor
cotaskmem
COULDNOT
countof
Cowait
covrun
cpcontrols
cph
@@ -322,14 +310,11 @@ CRECT
CRH
critsec
cropandlock
crt
CROPTOSQUARE
Crossdevice
csdevkit
CSearch
CSettings
cso
CSOT
CSRW
CStyle
cswin
@@ -372,14 +357,11 @@ DBPROPIDSET
DBPROPSET
DBT
DCBA
DCapabilities
DCOM
DComposition
DCR
ddc
DDEIf
Deact
debouncer
debugbreak
decryptor
Dedup
@@ -397,7 +379,6 @@ DEFAULTTOPRIMARY
DEFERERASE
DEFPUSHBUTTON
deinitialization
DELA
DELETEDKEYIMAGE
DELETESCANS
DEMOTYPE
@@ -432,20 +413,18 @@ DISABLEASACTIONKEY
DISABLENOSCROLL
diskmgmt
DISPLAYCHANGE
displayconfig
DISPLAYCONFIG
DISPLAYFLAGS
DISPLAYFREQUENCY
displayname
DISPLAYORIENTATION
diu
divyan
Dlg
DLGFRAME
dlgmodalframe
DLGMODALFRAME
dlib
dllhost
dllmain
Dmdo
DNLEN
DONOTROUND
DONTVALIDATEPATH
@@ -455,7 +434,6 @@ downsampling
downscale
DPICHANGED
DPIs
DPMS
DPSAPI
DQTAT
DQTYPE
@@ -493,19 +471,15 @@ DWMWINDOWMAXIMIZEDCHANGE
DWORDLONG
dworigin
dwrite
Dxva
dxgi
eab
EAccess
easeofaccess
ecount
edid
Edid
EDITKEYBOARD
EDITSHORTCUTS
EDITTEXT
EFile
EInvalid
eep
eku
emojis
ENABLEDELAYEDEXPANSION
@@ -515,15 +489,14 @@ ENABLETEMPLATE
encodedlaunch
encryptor
ENDSESSION
ENot
ENSUREVISIBLE
ENTERSIZEMOVE
ENTRYW
ENU
environmentvariables
EOAC
EPO
epu
EProvider
ERASEBKGND
EREOF
EResize
@@ -577,7 +550,6 @@ fdx
FErase
fesf
FFFF
FFh
Figma
FILEEXPLORER
fileexploreraddons
@@ -620,7 +592,6 @@ formatetc
FORPARSING
foundrylocal
FRAMECHANGED
Framechanged
FRestore
frm
FROMTOUCH
@@ -664,7 +635,6 @@ GMEM
GNumber
googleai
googlegemini
Gotchas
gpedit
gpo
GPOCA
@@ -677,13 +647,13 @@ GSM
gtm
guiddata
GUITHREADINFO
Gotcha
Gotchas
GValue
gwl
GWLP
GWLSTYLE
hangeul
Hann
Hantai
Hanzi
Hardlines
hardlinks
@@ -742,7 +712,6 @@ HKPD
HKU
HMD
hmenu
HMON
hmodule
hmonitor
homies
@@ -760,7 +729,6 @@ hotkeys
hotlight
hotspot
HPAINTBUFFER
HPhysical
HRAWINPUT
hredraw
hres
@@ -771,7 +739,6 @@ hsb
HSCROLL
hsi
HSpeed
HSync
HTCLIENT
hthumbnail
HTOUCHINPUT
@@ -781,7 +748,6 @@ HVal
HValue
Hvci
hwb
HWP
HWHEEL
HWINEVENTHOOK
hwnd
@@ -795,7 +761,6 @@ IAI
icf
ICONERROR
ICONLOCATION
ICONONLY
IDCANCEL
IDD
idk
@@ -839,7 +804,6 @@ INITTOLOGFONTSTRUCT
INLINEPREFIX
inlines
Inno
Innolux
INPC
inproc
INPUTHARDWARE
@@ -881,7 +845,6 @@ istep
ith
ITHUMBNAIL
IUI
IVO
IUWP
IWIC
jeli
@@ -895,7 +858,8 @@ jpnime
Jsons
jsonval
jxr
Kantai
KBSC
kdc
keybd
KEYBDDATA
KEYBDINPUT
@@ -917,7 +881,6 @@ KILLFOCUS
killrunner
kmph
kvp
KVM
Kybd
LARGEICON
lastcodeanalysissucceeded
@@ -933,15 +896,12 @@ Lclean
Ldone
Ldr
LEFTALIGN
leftclick
LEFTSCROLLBAR
LEFTTEXT
leftclick
LError
LEVELID
LExit
Lenovo
LGD
LFU
lhwnd
LIBFUZZER
LIBID
@@ -1046,7 +1006,6 @@ MAPTOSAMESHORTCUT
MAPVK
MARKDOWNPREVIEWHANDLERCPP
MAXIMIZEBOX
Maximizebox
MAXSHORTCUTSIZE
maxversiontested
mber
@@ -1059,14 +1018,12 @@ MDL
mdtext
mdtxt
mdwn
mccs
meme
memicmp
MENUITEMINFO
MENUITEMINFOW
MERGECOPY
MERGEPAINT
Metacharacter
metadatamatters
Metadatas
metafile
@@ -1081,7 +1038,6 @@ mikeclayton
mindaro
Minimizable
MINIMIZEBOX
Minimizebox
MINIMIZEEND
MINIMIZESTART
MINMAXINFO
@@ -1117,8 +1073,7 @@ mouseutils
MOVESIZEEND
MOVESIZESTART
MRM
Mrt
mrt
MRT
mru
MSAL
msc
@@ -1144,7 +1099,6 @@ Mso
msrc
msstore
mstsc
mswhql
msvcp
MT
MTND
@@ -1162,7 +1116,6 @@ MYICON
myorg
myrepo
NAMECHANGE
Nanjing
namespaceanddescendants
nao
NCACTIVATE
@@ -1231,7 +1184,6 @@ NOMCX
NOMINMAX
NOMIRRORBITMAP
NOMOVE
Nomove
NONANTIALIASED
nonclient
NONCLIENTMETRICSW
@@ -1253,7 +1205,6 @@ NORMALUSER
NOSEARCH
NOSENDCHANGING
NOSIZE
Nosize
NOTHOUSANDS
NOTICKS
NOTIFICATIONSDLL
@@ -1261,11 +1212,9 @@ NOTIFYICONDATA
NOTIFYICONDATAW
NOTIMPL
NOTOPMOST
Notopmost
NOTRACK
NOTSRCCOPY
NOTSRCERASE
Notupdated
notwindows
NOTXORPEN
nowarn
@@ -1309,7 +1258,6 @@ opensource
openurl
openxmlformats
OPTIMIZEFORINVOKE
Optronics
ORPHANEDDIALOGTITLE
ORSCANS
oss
@@ -1322,6 +1270,7 @@ OUTOFCONTEXT
Outptr
outputtype
outsettings
outsourced
OVERLAPPEDWINDOW
Oversampling
OVERWRITEPROMPT
@@ -1345,7 +1294,6 @@ PATINVERT
PATPAINT
pbc
pbi
PBP
PBlob
pbrush
pcb
@@ -1360,7 +1308,6 @@ PDBs
PDEVMODE
pdisp
PDLL
pdmodels
pdo
pdto
pdtobj
@@ -1383,13 +1330,13 @@ pguid
phbm
phbmp
phicon
PHL
Photoshop
phwnd
pici
pidl
PIDLIST
pii
pinboard
pinfo
pinvoke
pipename
@@ -1416,8 +1363,6 @@ Popups
POPUPWINDOW
POSITIONITEM
POWERBROADCAST
powerdisplay
POWERDISPLAYMODULEINTERFACE
POWERRENAMECONTEXTMENU
powerrenameinput
POWERRENAMETEST
@@ -1472,7 +1417,7 @@ projectname
PROPERTYKEY
Propset
PROPVARIANT
prot
Prt
PRTL
prvpane
psapi
@@ -1500,16 +1445,12 @@ PTOKEN
PToy
ptstr
pui
pvct
PWAs
pwcs
PWSTR
pwsz
pwtd
Qdc
QDC
qdc
QDS
qit
QITAB
QITABENT
@@ -1553,9 +1494,7 @@ regfile
REGISTERCLASSFAILED
REGISTRYHEADER
REGISTRYPREVIEWEXT
registryroot
regkey
regroot
regsvr
REINSTALLMODE
releaseblog
@@ -1600,6 +1539,7 @@ riid
RKey
RNumber
rollups
ROOTOWNER
rop
ROUNDSMALL
ROWSETEXT
@@ -1732,7 +1672,6 @@ sigdn
Signedness
SIGNINGSCENARIO
signtool
SIIGBF
SINGLEKEY
sipolicy
SIZEBOX
@@ -1797,7 +1736,6 @@ STARTUPINFOW
startupscreen
STATFLAG
STATICEDGE
Staticedge
staticmethod
STATSTG
stdafx
@@ -1834,7 +1772,6 @@ subkeys
sublang
SUBMODULEUPDATE
subresource
swp
Superbar
sut
svchost
@@ -1843,8 +1780,7 @@ SVGIO
svgz
SVSI
SWFO
SWP
Swp
swp
SWPNOSIZE
SWPNOZORDER
SWRESTORE
@@ -1904,9 +1840,7 @@ THEMECHANGED
themeresources
THH
THICKFRAME
Thickframe
THISCOMPONENT
Tianma
throughs
TILEDWINDOW
TILLSON
@@ -1987,13 +1921,13 @@ UNLEN
UNORM
unremapped
Unsubscribes
unsubscribes
unvirtualized
unwide
unzoom
UOffset
UOI
UPDATENOW
UPDATEREGISTRY
updown
UPGRADINGPRODUCTCODE
upscaling
@@ -2020,8 +1954,6 @@ vcamp
vcenter
vcgtq
VCINSTALLDIR
vcp
vcpname
Vcpkg
VCRT
vcruntime
@@ -2034,8 +1966,6 @@ VERIFYCONTEXT
VERSIONINFO
VERTRES
VERTSIZE
VESA
vesa
VFT
vget
vgetq
@@ -2067,7 +1997,6 @@ VSM
vso
vsonline
VSpeed
VSync
vstemplate
vstest
VSTHRD
@@ -2109,7 +2038,7 @@ winapi
winappsdk
windir
WINDOWCREATED
windowedge
WINDOWEDGE
WINDOWINFO
WINDOWNAME
WINDOWPLACEMENT
@@ -2133,12 +2062,12 @@ WINL
winlogon
winmd
winml
WINNT
winres
winrt
winsdk
winsta
WINTHRESHOLD
WINNT
WINVER
winxamlmanager
withinrafael
@@ -2150,7 +2079,6 @@ WKSG
Wlkr
wmain
Wman
wmi
WMI
WMICIM
wmimgmt
@@ -2163,9 +2091,9 @@ WNDCLASSEX
WNDCLASSEXW
WNDCLASSW
WNDPROC
Wndproc
wnode
wom
workerw
WORKSPACESEDITOR
WORKSPACESLAUNCHER
WORKSPACESSNAPSHOTTOOL

View File

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

View File

@@ -210,11 +210,6 @@
"PowerToys.PowerAccentModuleInterface.dll",
"PowerToys.PowerAccentKeyboardService.dll",
"PowerToys.PowerDisplayModuleInterface.dll",
"WinUI3Apps\\PowerToys.PowerDisplay.dll",
"WinUI3Apps\\PowerToys.PowerDisplay.exe",
"PowerDisplay.Lib.dll",
"WinUI3Apps\\PowerToys.PowerRenameExt.dll",
"WinUI3Apps\\PowerToys.PowerRename.exe",
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
@@ -235,8 +230,12 @@
"WinUI3Apps\\PowerToys.RegistryPreview.dll",
"WinUI3Apps\\PowerToys.RegistryPreview.exe",
"PowerToys.ShortcutGuide.exe",
"PowerToys.ShortcutGuideModuleInterface.dll",
"WinUI3Apps\\PowerToys.ShortcutGuide.exe",
"WinUI3Apps\\PowerToys.ShortcutGuide.dll",
"WinUI3Apps\\PowerToys.ShortcutGuideModuleInterface.dll",
"WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.dll",
"WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe",
"WinUI3Apps\\ShortcutGuide.CPPProject.dll",
"PowerToys.ZoomIt.exe",
"PowerToys.ZoomItModuleInterface.dll",
@@ -383,8 +382,6 @@
"UnitsNet.dll",
"UtfUnknown.dll",
"Wpf.Ui.dll",
"WmiLight.dll",
"WmiLight.Native.dll",
"Shmuelie.WinRTServer.dll",
"ToolGood.Words.Pinyin.dll"
],

View File

@@ -91,7 +91,6 @@ extends:
official: true
codeSign: true
runTests: false
buildTests: false
signingIdentity:
serviceName: $(SigningServiceName)
appId: $(SigningAppId)

View File

@@ -258,7 +258,6 @@ jobs:
-restore -graph
/p:RestorePackagesConfig=true
/p:CIBuild=true
/p:BuildTests=${{ parameters.buildTests }}
/bl:$(LogOutputDirectory)\build-0-main.binlog
${{ parameters.additionalBuildOptions }}
$(MSBuildCacheParameters)

View File

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

View File

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

View File

@@ -2,12 +2,6 @@
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Skip building C++ test projects when BuildTests=false -->
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
<UsePrecompiledHeaders>false</UsePrecompiledHeaders>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<!-- Project configurations -->
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">

View File

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

View File

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

View File

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

View File

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

View File

@@ -55,7 +55,6 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj" Id="1a066c63-64b3-45f8-92fe-664e1cce8077" />
<Project Path="src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj" Id="8b5cfb38-ccba-40a8-ad7a-89c57b070884" />
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
</Folder>
@@ -429,7 +428,7 @@
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49D456D3-F485-45AF-8875-45B44F193DDC" />
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj" Id="49d456d3-f485-45af-8875-45b44f193ddc" />
<Project Path="src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj" Id="799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca" />
<Project Path="src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj" Id="57175ec7-92a5-4c1e-8244-e3fbca2a81de" />
<Project Path="src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj" Id="9d52fd25-ef90-4f9a-a015-91efc5daf54f" />
@@ -440,7 +439,7 @@
</Project>
</Folder>
<Folder Name="/modules/FileLocksmith/Tests/">
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj" Id="A1B2C3D4-E5F6-7890-1234-567890ABCDEF" />
<Project Path="src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj" Id="a1b2c3d4-e5f6-7890-1234-567890abcdef" />
</Folder>
<Folder Name="/modules/Hosts/">
<Project Path="src/modules/Hosts/Hosts/Hosts.csproj">
@@ -466,13 +465,13 @@
</Folder>
<Folder Name="/modules/imageresizer/">
<Project Path="src/modules/imageresizer/dll/ImageResizerExt.vcxproj" Id="0b43679e-edfa-4da0-ad30-f4628b308b1b" />
<Project Path="src/modules/imageresizer/ImageResizerContextMenu/ImageResizerContextMenu.vcxproj" Id="93b72a06-c8bd-484f-a6f7-c9f280b150bf" />
<Project Path="src/modules/imageresizer/ImageResizerLib/ImageResizerLib.vcxproj" Id="18b3db45-4ffe-4d01-97d6-5223feee1853" />
<Project Path="src/modules/imageresizer/ui/ImageResizerUI.csproj">
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
<Project Path="src/modules/imageresizer/ImageResizerContextMenu/ImageResizerContextMenu.vcxproj" Id="93b72a06-c8bd-484f-a6f7-c9f280b150bf" />
<Project Path="src/modules/imageresizer/ImageResizerLib/ImageResizerLib.vcxproj" Id="18b3db45-4ffe-4d01-97d6-5223feee1853" />
<Project Path="src/modules/imageresizer/ui/ImageResizerUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
@@ -685,23 +684,6 @@
<Deploy />
</Project>
</Folder>
<Folder Name="/modules/PowerDisplay/">
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib/PowerDisplay.Lib.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/powerdisplay/PowerDisplayModuleInterface/PowerDisplayModuleInterface.vcxproj" Id="d1234567-8901-2345-6789-abcdef012345" />
</Folder>
<Folder Name="/modules/PowerDisplay/Tests/">
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/PowerDisplay.Lib.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/MeasureTool/">
<Project Path="src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj" Id="54a93af7-60c7-4f6c-99d2-fbb1f75f853a">
<BuildDependency Project="src/common/Display/Display.vcxproj" />
@@ -979,9 +961,16 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/shortcutguide/">
<Project Path="src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj" Id="2edb3eb4-fa92-4bff-b2d8-566584837231" />
<Project Path="src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj" Id="2d604c07-51fc-46bb-9eb7-75aecc7f5e81" />
<Folder Name="/modules/ShortcutGuide/">
<Project Path="src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj" Id="e487304a-b1fb-4e6b-8e70-014051af5b99" />
</Folder>
<Folder Name="/modules/Workspaces/">
<Project Path="src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -9,12 +9,14 @@
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Shortcut+Guide%22+)
## Overview
Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when the Windows key is pressed and held. It provides a visual reference for Windows key combinations, helping users discover and utilize built-in Windows shortcuts.
Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when a user-set keyboard shortcut is pressed. It helps users discover and remember keyboard shortcuts for Windows and apps.
> [!NOTE]
> The spec for the manifest files is in development and will be linked here once available.
## Usage
- Press and hold the Windows key to display the overlay of available shortcuts
- Press the hotkey again to dismiss the overlay
- The overlay displays Windows shortcuts with their corresponding actions
- Press the user-defined hotkey to display the overlay
- Press the hotkey again or press ESC to dismiss the overlay
## Build and Debug Instructions
@@ -25,67 +27,89 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor
4. The executable is named PowerToys.ShortcutGuide.exe
### Debug
1. Right-click the ShortcutGuide project and select 'Set as Startup Project'
1. Right-click the ShortcutGuide.Ui project and select 'Set as Startup Project'
2. Right-click the project again and select 'Debug'
## Code Structure
> [!NOTE]
> When run in debug mode, the window behaves differently than in release mode. It will not automatically close when loosing focus, it will be displayed on top of all other windows, and it is not hidden from the taskbar.
![Diagram](../images/shortcutguide/diagram.png)
## Project Structure
### Core Files
The Shortcut Guide module consists of the following 4 projects:
#### [`dllmain.cpp`](/src/modules/shortcut_guide/dllmain.cpp)
Contains DLL boilerplate code. Implements the PowertoyModuleIface, including enable/disable functionality and GPO policy handling. Captures hotkey events and starts the PowerToys.ShortcutGuide.exe process to display the shortcut guide window.
### [`ShortcutGuide.Ui`](/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj
#### [`shortcut_guide.cpp`](/src/modules/shortcut_guide/shortcut_guide.cpp)
Contains the module interface code. It initializes the settings values and the keyboard event listener. Defines the OverlayWindow class, which manages the overall logic and event handling for the PowerToys Shortcut Guide.
This is the main UI project for the Shortcut Guide module. Upon startup it does the following tasks:
#### [`overlay_window.cpp`](/src/modules/shortcut_guide/overlay_window.cpp)
Contains the code for loading the SVGs, creating and rendering of the overlay window. Manages and displays overlay windows with SVG graphics through two main classes:
- D2DOverlaySVG: Handles loading, resizing, and manipulation of SVG graphics
- D2DOverlayWindow: Manages the display and behavior of the overlay window
1. Copies the built-in manifest files to the users manifest directory (overwriting existing files).
2. Generate the `index.yml` manifest file.
3. Populate the PowerToys shortcut manifest with the user-defined shortcuts.
4. Starts the UI.
#### [`keyboard_state.cpp`](/src/modules/shortcut_guide/keyboard_state.cpp)
Contains helper methods for checking the current state of the keyboard.
### Related files in PowerToys.Interop
#### [`target_state.cpp`](/src/modules/shortcut_guide/target_state.cpp)
State machine that handles the keyboard events. It's responsible for deciding when to show the overlay, when to suppress the Start menu (if the overlay is displayed long enough), etc. Handles state transitions and synchronization to ensure the overlay is shown or hidden appropriately based on user interactions.
#### [`excluded_app.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp)
#### [`trace.cpp`](/src/modules/shortcut_guide/trace.cpp)
Contains code for telemetry.
This file contains one function with the following signature:
### Supporting Files
```cpp
__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide()
```
#### [`animation.cpp`](/src/modules/shortcut_guide/animation.cpp)
Handles the timing and interpolation of animations. Calculates the current value of an animation based on elapsed time and a specified easing function.
This function checks if the current window is excluded from the Shortcut Guide overlay. It returns `true` if the current window is excluded otherwise it returns `false`.
#### [`d2d_svg.cpp`](/src/modules/shortcut_guide/d2d_svg.cpp)
Provides functionality for loading, resizing, recoloring, rendering, and manipulating SVG images using Direct2D.
#### [`tasklist_positions.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp)
#### [`d2d_text.cpp`](/src/modules/shortcut_guide/d2d_text.cpp)
Handles creation, resizing, alignment, and rendering of text using Direct2D and DirectWrite.
This file contains helper functions to retrieve the positions of the taskbar buttons. It exports the following function:
#### [`d2d_window.cpp`](/src/modules/shortcut_guide/d2d_window.cpp)
Manages a window using Direct2D and Direct3D for rendering. Handles window creation, resizing, rendering, and destruction.
```cpp
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size)
```
#### [`native_event_waiter.cpp`](/src/modules/shortcut_guide/native_event_waiter.cpp)
Waits for a named event and executes a specified action when the event is triggered. Uses a separate thread to handle event waiting and action execution.
This function retrieves the positions of the taskbar buttons for a given monitor. It returns an array of `TasklistButton` structures (max 10), which contain the position and size of each button.
#### [`tasklist_positions.cpp`](/src/modules/shortcut_guide/tasklist_positions.cpp)
Handles retrieving and updating the positions and information of taskbar buttons in Windows.
`monitor` must be the monitor handle of the monitor containing the taskbar instance of which the buttons should be retrieved.
#### [`main.cpp`](/src/modules/shortcut_guide/main.cpp)
The entry point for the PowerToys Shortcut Guide application. Handles initialization, ensures single instance execution, manages parent process termination, creates and displays the overlay window, and runs the main event loop.
`size` will contain the resulting array size.
It determines the positions through Windows `FindWindowEx` function.
For the primary taskbar it searches for:
* A window called "Shell_TrayWnd"
* that contains a window called "ReBarWindow32"
* that contains a window called "MSTaskSwWClass"
* that contains a window called "MSTaskListWClass"
For any secondary taskbar it searches for:
* A window called "Shell_SecondaryTrayWnd"
* that contains a window called "WorkerW"
* that contains a window called "MSTaskListWClass"
It then enumerates all the button elements inside "MSTaskListWClass" while skipping such with a same name (which implies the user does not use combining taskbar buttons)
If this method fails, which it will for newer versions of Windows, it falls back to searching for:
* A window called "Shell_TrayWnd" or "Shell_SecondaryTrayWnd"
* that contains a window called "Windows.UI.Composition.DesktopWindowContentBridge"
* that contains a window called "Windows.UI.Input.InputSite.WindowClass"
* the first child element
It then enumerates all the button elements inside the selected while skipping such with a same name (which implies the user does not use combining taskbar buttons) and such that do not start with "Appid:" (which are not actual taskbar buttons related to apps, but others like the widgets or the search button).
### [`ShortcutGuide.IndexYmlGenerator`](/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/)
This application generates the `index.yml` manifest file.
It is a separate project so that its code can be easier ported to WinGet in the future.
### [`ShortcutGuideModuleInterface`](/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj)
The module interface that handles opening and closing the user interface.
## Features and Limitations
- The overlay displays Windows shortcuts (Windows key combinations)
- The module supports localization, but only for the Windows controls on the left side of the overlay
- Currently the displayed shortcuts (Except the ones from PowerToys) are not localized.
- It's currently rated as a P3 (lower priority) module
## Future Development
A community-contributed version 2 is in development that will support:
- Application-specific shortcuts based on the active application
- Additional shortcuts beyond Windows key combinations
- PowerToys shortcuts
- Implementing with WinGet to get new shortcut manifest files
- Adding localization support for the built-in manifest files

View File

@@ -0,0 +1,318 @@
# WinGet Manifest Keyboard Shortcuts schema
## 1 What this spec is about
This spec provides an extension to the existing [WinGet manifest schema](https://github.com/microsoft/winget-pkgs/blob/master/doc/manifest/README.md) in form of an additional yaml file, that describes keyboard shortcuts the application provides.
These yaml files are saved on a per-user base and so called manifest interpreters can then display these manifests in a human-friendly version.
### 1.1 What this spec is not about
This spec does not provide a way to back up or save user-defined keyboard shortcuts.
## 2 Save location of manifests
### 2.1 WinGet
These files are saved online along with the other manifest files in the [WinGet Package repository](https://github.com/microsoft/winget-pkgs).
### 2.2 Locally
All manifests and one index file are saved locally under `%LocalAppData%/Microsoft/WinGet/KeyboardShortcuts`. All apps are allowed to add their manifest files there. In addition Package Managers (like WinGet) and manifest interpreters (like PowerToys Shortcut Guide) can control and add other manifests themselves.
#### 2.2.1 Downloading manifests
When WinGet or other package managers download a package, they should also download the corresponding keyboard shortcuts manifest file and save it in the local directory, given such a file exists in the WinGet repository.
The downloader is also responsible for updating the local `index.yaml` file, which contains all the information about the different manifest files that are saved in the same directory.
#### 2.2.2 Updating manifests
When a manifest interpreter starts, it should download the latest version of the manifests from the WinGet repository and save them in the local directory. If a manifest interpreter is not able to download the manifests or they do not exist, it should use the locally saved manifests.
The updater is also responsible for updating the local `index.yaml` file, which contains all the information about the different manifest files that are saved in the same directory.
> Note: WinGet must provide a way to update the keyboard shortcuts manifests given a package id.
### 2.3 File names
The file name of a keyboard shortcuts file is the WinGet package identifier, plus the locale of the strings of the file and at last the `.KBSC.yaml` file extension.
For example the package "test.bar" saves its manifest with `en-US` strings in `test.bar.en-US.KBSC.yaml`.
#### 2.3.1 No winget package available
If an application has no corresponding WinGet package its name starts with a plus (`+`) symbol.
### 2.4 Reserved namespaces
Every name starting with `+WindowsNT` is reserved for the Windows OS and its components.
## 3 File syntax
All relevant files are written in [YAML](https://yaml.org/spec).
> Note: A JSON schema will be provided as soon as the spec reaches a further step
### 3.1 Manifest Schema vNext Keyboard Shortcuts File
```
PackageName: # The package unique identifier
WindowFilter: # The filter of window processes to which the shortcuts apply to
BackgroundProcess: # Optionally allows applying WindowFilter to background processes
Shortcuts: # List of sections with keyboard shortcuts
- SectionName: # Name of the category of shortcuts
Properties: # List of shortcuts in the category
- Name: # Name of the shortcut
Description: # Optional description of the shortcut
AdditionalInfo: # Optional additional information about the shortcut
Recommended: # Optionally determines if the shortcut is displayed in a designated recommended area
Shortcut: # An array of shortcuts that need to be pressed
- Win: # Determines if the Windows Key is part of the shortcut
Ctrl: # Determines if the Ctrl Key is part of the shortcut
Shift: # Determines if the Shift Key is part of the shortcut
Alt: # Determines if the Alt Key is part of the shortcut
Keys: # Array of keys that need to be pressed
```
Per Application/Package one or more Keyboard manifests can be declared. Every manifest must have a different locale and the same `PackageName`, `WindowFilter` and `BackgroundProcess` fields.
<details>
<summary><b>PackageName</b> - The package unique identifier</summary>
Package identifier (see 2.1 for more information on the package identifier).
</details>
<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.
</details>
<details>
<summary><b>BackgroundProcess</b> - Optionally allows applying WindowFilter to background processes.</summary>
**Optional field**
Defaults to `False`. Determines if WindowFilter should apply to background processes as well (Rephrased: When the process is running, the shortcuts will apply).
</details>
<details>
<summary><b>Shortcuts</b> - List of sections with keyboard shortcuts</summary>
List of different section (also called categories) of shortcuts.
</details>
<details>
<summary><b>SectionName</b> - Name of the category of shortcuts</summary>
Name of the section of shortcuts.
**Special sections**:
Special sections start with an identifier enclosed between `<` and `>`. This declares the category as a special display. If the interpreter of the manifest file can't understand the content this section should be left out.
</details>
<details>
<summary><b>Properties</b> - List of shortcuts in the category</summary>
</details>
<details>
<summary><b>Name</b> - Name of the shortcut</summary>
Name of the shortcut. This is the name that will be displayed in the interpreter.
</details>
<details>
<summary><b>Description</b> - Optional description of the shortcut</summary>
Optional description of the shortcut. This is the description that will be displayed by the interpreter.
</details>
<details>
<summary><b>AdditionalInfo</b> - Optional additional information about the shortcut</summary>
Array of additional information about the shortcut. This is the additional information that will be displayed by the interpreter and are not part of this manifest.
**Example**:
For example, if the shortcut is only available on a certain Windows version, this information could be added here.
```yaml
AdditionalInfo:
- MinWindowsVersion: "10.0.19041.0"
```
</details>
<details>
<summary><b>Shortcut</b> - An array of shortcuts that need to be pressed</summary>
An array of shortcuts that need to be pressed. This allows defining sequential shortcuts that need to be pressed in order to trigger the action.
</details>
<details>
<summary><b>Win</b> - Determines if the Windows Key is part of the shortcut</summary>
Refers to the left Windows Key on the keyboard.
</details>
<details>
<summary><b>Ctrl</b> - Determines if the Ctrl Key is part of the shortcut</summary>
Refers to the left Ctrl Key on the keyboard.
</details>
<details>
<summary><b>Shift</b> - Determines if the Shift Key is part of the shortcut</summary>
Refers to the left Shift Key on the keyboard.
</details>
<details>
<summary><b>Alt</b> - Determines if the Alt Key is part of the shortcut</summary>
Refers to the left Alt Key on the keyboard.
</details>
<details>
<summary><b>Recommended</b> - Optionally determines if the shortcut is displayed in a designated recommended area</summary>
**Optional field**
Defaults to `False`. Determines if the shortcut should be displayed in a designated recommended area. This is a visual hint for the user that this shortcut is important.
</details>
<details>
<summary><b>Keys</b> - Array of keys that need to be pressed</summary>
A string array of all the keys that need to be pressed. If a number is supplied, it should be read as a [KeyCode](https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes) and displayed accordingly (based on the Keyboard Layout of the user).
**Special keys**:
Special keys are enclosed between `<` and `>` and correspond to a key that should be displayed in a certain way. If the interpreter of the manifest file can't understand the content, the brackets should be left out.
|Name|Description|
|----|-----------|
|`<Office>`| Corresponds to the Office key on some Windows keyboards |
|`<Copilot>`| Corresponds to the Copilot key on some Windows keyboards |
|`<Left>`| Corresponds to the left arrow key |
|`<Right>`| Corresponds to the right arrow key |
|`<Up>`| Corresponds to the up arrow key |
|`<Down>`| Corresponds to the down arrow key |
|`<Enter>`| Corresponds to the Enter key |
|`<Space>`| Corresponds to the Space key |
|`<Tab>`| Corresponds to the Tab key |
|`<Backspace>`| Corresponds to the Backspace key |
|`<Delete>`| Corresponds to the Delete key |
|`<Insert>`| Corresponds to the Insert key |
|`<Home>`| Corresponds to the Home key |
|`<End>`| Corresponds to the End key |
|`<PrtScr>`| Corresponds to the Print Screen key |
|`<Pause>`| Corresponds to the pause key |
|`<PageUp>`| Corresponds to the Page Up key |
|`<PageDown>`| Corresponds to the Page Down key |
|`<Escape>`| Corresponds to the Escape key |
|`<Arrow>`| Corresponds to either the left, right, up or down arrow key |
|`<ArrowLR>`| Corresponds to either the left or right arrow key |
|`<ArrowUD>`| Corresponds to either the up or down arrow key |
|`<Underlined letter>`| Corresponds to any letter that is _underlined_ in the UI |
</details>
#### 3.2.2 Example
```yaml
PackageName: Microsoft.PowerToys
WindowFilter: "*"
BackgroundProcess: True
Shortcuts:
- SectionName: General
Properties:
- Name: Advanced Paste
Shortcut:
- Win: True
Ctrl: False
Alt: False
Shift: False
Keys:
- 86
Description: Open Advanced Paste window
- Name: Advanced Paste
Shortcut:
- Win: True
Ctrl: True
Alt: True
Shift: False
Keys:
- 86
Description: Paste as plain text directly
```
### 3.2 `index.yaml` file
The `index.yaml` file is a file that contains all the information about the different manifest files that are saved in the same directory. This file is only available locally and is not saved in the WinGet repository as it is specific to the user.
```yaml
DefaultShellName: # The package identifier of the default shell used in Windows
Index: # List of all manifest files
- WindowFilter: # The filter of window processes to which the shortcuts apply to
BackgroundProcess: # Optionally allows applying WindowFilter to background processes
Apps: # List of all manifest files for the filter
```
<details>
<summary><b>DefaultShellName</b> - The package identifier of the default shell used in Windows</summary>
This declares the package identifier of the default shell used in Windows. Most commonly it is `+WindowsNT.Shell`. Although not enforced, only the shell declared in the registry key `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell` should be used here.
</details>
<details>
<summary><b>Index</b> - List of all manifest files</summary>
</details>
<details>
<summary><b>WindowFilter</b> - The filter of window processes to which the shortcuts apply to</summary>
See the `WindowFilter` field in the manifest file for more information.
</details>
<details>
<summary><b>BackgroundProcess</b> - Optionally allows applying WindowFilter to background processes</summary>
**Optional field**
See the `BackgroundProcess` field in the manifest file for more information.
</details>
<details>
<summary><b>Apps</b> - List of all the package identifiers applying for the filter</summary>
</details>
#### 3.2.1 Example
```yaml
DefaultShellName: "+WindowsNT.Shell"
Index:
- Filter: "*"
BackgroundProcess: True
Apps: ["+WindowsNT.Shell", "Microsoft.PowerToys"]
- Filter: "explorer.exe"
Apps: ["+WindowsNT.WindowsExplorer"]
- Filter: "taskmgr.exe"
Apps: ["+WindowsNT.TaskManager"]
- Filter: "msedge.exe"
Apps: ["+WindowsNT.Edge"]
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,26 +2,25 @@
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define ShortcutGuideSvgFiles=?>
<?define ShortcutGuideSvgFilesPath=$(var.BinDir)\Assets\ShortcutGuide\?>
<?define ShortcutGuideAssetsFiles=?>
<?define ShortcutGuideAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\?>
<Fragment>
<!-- Shortcut guide files -->
<DirectoryRef Id="BaseApplicationsAssetsFolder">
<Directory Id="ShortcutGuideSvgsInstallFolder" Name="ShortcutGuide" />
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="ShortcutGuideAssetsFolder" Name="ShortcutGuide" />
</DirectoryRef>
<DirectoryRef Id="ShortcutGuideSvgsInstallFolder" FileSource="$(var.ShortcutGuideSvgFilesPath)">
<DirectoryRef Id="ShortcutGuideAssetsFolder" FileSource="$(var.ShortcutGuideAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShortcutGuideSvgFiles_Component_Def-->
<!--ShortcutGuideAssetsFiles_Component_Def-->
</DirectoryRef>
<!-- Shortcut guide -->
<ComponentGroup Id="ShortcutGuideComponentGroup">
<Component Id="RemoveShortcutGuideFolder" Guid="AD1ABC55-B593-4A60-A86A-BA8C0ED493A5" Directory="ShortcutGuideSvgsInstallFolder">
<ComponentGroup Id="ShortcutGuideComponentGroup" >
<Component Id="RemoveShortcutGuideFolder" Guid="AD1ABC55-B593-4A60-A86A-BA8C0ED493A5" Directory="ShortcutGuideAssetsFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveShortcutGuideFolder" Value="" KeyPath="yes" />
<RegistryValue Type="string" Name="RemoveShortcutGuideFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderShortcutGuideSvgsInstallFolder" Directory="ShortcutGuideSvgsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderShortcutGuideAssetsInstallFolder" Directory="ShortcutGuideAssetsFolder" On="uninstall"/>
</Component>
</ComponentGroup>

View File

@@ -176,10 +176,6 @@ Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PS
Generate-FileList -fileDepsJson "" -fileListName LightSwitchFiles -wxsFilePath $PSScriptRoot\LightSwitch.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\LightSwitchService"
Generate-FileComponents -fileListName "LightSwitchFiles" -wxsFilePath $PSScriptRoot\LightSwitch.wxs
#PowerDisplay
Generate-FileList -fileDepsJson "" -fileListName PowerDisplayAssetsFiles -wxsFilePath $PSScriptRoot\PowerDisplay.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\PowerDisplay"
Generate-FileComponents -fileListName "PowerDisplayAssetsFiles" -wxsFilePath $PSScriptRoot\PowerDisplay.wxs
#New+
Generate-FileList -fileDepsJson "" -fileListName NewPlusAssetsFiles -wxsFilePath $PSScriptRoot\NewPlus.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\NewPlus"
Generate-FileComponents -fileListName "NewPlusAssetsFiles" -wxsFilePath $PSScriptRoot\NewPlus.wxs
@@ -303,8 +299,8 @@ Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePat
## Plugins
#ShortcutGuide
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideSvgFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ShortcutGuide\"
Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
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
#Settings
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,61 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <registry.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(RegistryTests)
{
public:
// Note: These tests use HKCU which doesn't require elevation
TEST_METHOD(InstallScope_Registry_CanReadAndWrite)
{
TestHelpers::TestRegistryKey testKey(L"RegistryTest");
Assert::IsTrue(testKey.isValid());
// Write a test value
Assert::IsTrue(testKey.setStringValue(L"TestValue", L"TestData"));
Assert::IsTrue(testKey.setDwordValue(L"TestDword", 42));
}
TEST_METHOD(Registry_ValueChange_StringValue)
{
registry::ValueChange change{ HKEY_CURRENT_USER, L"Software\\PowerToys\\Test", L"TestValue", std::wstring{ L"TestData" } };
Assert::AreEqual(std::wstring(L"Software\\PowerToys\\Test"), change.path);
Assert::IsTrue(change.name.has_value());
Assert::AreEqual(std::wstring(L"TestValue"), *change.name);
Assert::AreEqual(std::wstring(L"TestData"), std::get<std::wstring>(change.value));
}
TEST_METHOD(Registry_ValueChange_DwordValue)
{
registry::ValueChange change{ HKEY_CURRENT_USER, L"Software\\PowerToys\\Test", L"TestDword", static_cast<DWORD>(42) };
Assert::AreEqual(std::wstring(L"Software\\PowerToys\\Test"), change.path);
Assert::IsTrue(change.name.has_value());
Assert::AreEqual(std::wstring(L"TestDword"), *change.name);
Assert::AreEqual(static_cast<DWORD>(42), std::get<DWORD>(change.value));
}
TEST_METHOD(Registry_ChangeSet_AddChanges)
{
registry::ChangeSet changeSet;
changeSet.changes.push_back({ HKEY_CURRENT_USER, L"Software\\PowerToys\\Test", L"Value1", std::wstring{ L"Data1" } });
changeSet.changes.push_back({ HKEY_CURRENT_USER, L"Software\\PowerToys\\Test", L"Value2", static_cast<DWORD>(123) });
Assert::AreEqual(static_cast<size_t>(2), changeSet.changes.size());
}
TEST_METHOD(InstallScope_GetCurrentInstallScope_ReturnsValidValue)
{
auto scope = registry::install_scope::get_current_install_scope();
Assert::IsTrue(scope == registry::install_scope::InstallScope::PerMachine ||
scope == registry::install_scope::InstallScope::PerUser);
}
};
}

View File

@@ -1,144 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <resources.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(ResourcesTests)
{
public:
// get_resource_string tests with current module
TEST_METHOD(GetResourceString_NonExistentId_ReturnsFallback)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto result = get_resource_string(99999, instance, L"fallback");
Assert::AreEqual(std::wstring(L"fallback"), result);
}
TEST_METHOD(GetResourceString_NullInstance_UsesFallback)
{
auto result = get_resource_string(99999, nullptr, L"fallback");
// Should return fallback or empty string
Assert::IsTrue(result == L"fallback" || result.empty());
}
TEST_METHOD(GetResourceString_EmptyFallback_ReturnsEmpty)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto result = get_resource_string(99999, instance, L"");
Assert::IsTrue(result.empty());
}
// get_english_fallback_string tests
TEST_METHOD(GetEnglishFallbackString_NonExistentId_ReturnsEmpty)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto result = get_english_fallback_string(99999, instance);
// Should return empty or the resource if it exists
Assert::IsTrue(true); // Just verify no crash
}
TEST_METHOD(GetEnglishFallbackString_NullInstance_DoesNotCrash)
{
auto result = get_english_fallback_string(99999, nullptr);
Assert::IsTrue(true); // Just verify no crash
}
// get_resource_string_language_override tests
TEST_METHOD(GetResourceStringLanguageOverride_NonExistentId_ReturnsEmpty)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto result = get_resource_string_language_override(99999, instance);
// Should return empty for non-existent resource
Assert::IsTrue(result.empty() || !result.empty()); // Valid either way
}
TEST_METHOD(GetResourceStringLanguageOverride_NullInstance_DoesNotCrash)
{
auto result = get_resource_string_language_override(99999, nullptr);
Assert::IsTrue(true);
}
// Thread safety tests
TEST_METHOD(GetResourceString_ThreadSafe)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount, instance]() {
for (int j = 0; j < 10; ++j)
{
get_resource_string(99999, instance, L"fallback");
successCount++;
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
// Kernel32 resource tests (has known resources)
TEST_METHOD(GetResourceString_Kernel32_DoesNotCrash)
{
HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
if (kernel32)
{
// Kernel32 has resources, but we don't know exact IDs
// Just verify it doesn't crash
get_resource_string(1, kernel32, L"fallback");
get_resource_string(100, kernel32, L"fallback");
get_resource_string(1000, kernel32, L"fallback");
}
Assert::IsTrue(true);
}
// Performance test
TEST_METHOD(GetResourceString_Performance_Acceptable)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; ++i)
{
get_resource_string(99999, instance, L"fallback");
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// 1000 lookups should complete in under 1 second
Assert::IsTrue(duration.count() < 1000);
}
// Edge case tests
TEST_METHOD(GetResourceString_ZeroId_DoesNotCrash)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto result = get_resource_string(0, instance, L"fallback");
Assert::IsTrue(true);
}
TEST_METHOD(GetResourceString_MaxUintId_DoesNotCrash)
{
HINSTANCE instance = GetModuleHandleW(nullptr);
auto result = get_resource_string(UINT_MAX, instance, L"fallback");
Assert::IsTrue(true);
}
};
}

View File

@@ -1,286 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <serialized.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(SerializedTests)
{
public:
// Basic Read tests
TEST_METHOD(Read_DefaultState_ReturnsDefaultValue)
{
Serialized<int> s;
int value = -1;
s.Read([&value](const int& v) {
value = v;
});
Assert::AreEqual(0, value); // Default constructed int is 0
}
TEST_METHOD(Read_StringType_ReturnsEmpty)
{
Serialized<std::string> s;
std::string value = "initial";
s.Read([&value](const std::string& v) {
value = v;
});
Assert::AreEqual(std::string(""), value);
}
// Basic Access tests
TEST_METHOD(Access_ModifyValue_ValueIsModified)
{
Serialized<int> s;
s.Access([](int& v) {
v = 42;
});
int value = 0;
s.Read([&value](const int& v) {
value = v;
});
Assert::AreEqual(42, value);
}
TEST_METHOD(Access_ModifyString_StringIsModified)
{
Serialized<std::string> s;
s.Access([](std::string& v) {
v = "hello";
});
std::string value;
s.Read([&value](const std::string& v) {
value = v;
});
Assert::AreEqual(std::string("hello"), value);
}
TEST_METHOD(Access_MultipleModifications_LastValuePersists)
{
Serialized<int> s;
s.Access([](int& v) { v = 1; });
s.Access([](int& v) { v = 2; });
s.Access([](int& v) { v = 3; });
int value = 0;
s.Read([&value](const int& v) {
value = v;
});
Assert::AreEqual(3, value);
}
// Reset tests
TEST_METHOD(Reset_AfterModification_ReturnsDefault)
{
Serialized<int> s;
s.Access([](int& v) { v = 42; });
s.Reset();
int value = -1;
s.Read([&value](const int& v) {
value = v;
});
Assert::AreEqual(0, value);
}
TEST_METHOD(Reset_String_ReturnsEmpty)
{
Serialized<std::string> s;
s.Access([](std::string& v) { v = "hello"; });
s.Reset();
std::string value = "initial";
s.Read([&value](const std::string& v) {
value = v;
});
Assert::AreEqual(std::string(""), value);
}
// Complex type tests
TEST_METHOD(Serialized_VectorType_Works)
{
Serialized<std::vector<int>> s;
s.Access([](std::vector<int>& v) {
v.push_back(1);
v.push_back(2);
v.push_back(3);
});
size_t size = 0;
int sum = 0;
s.Read([&size, &sum](const std::vector<int>& v) {
size = v.size();
for (int i : v) sum += i;
});
Assert::AreEqual(static_cast<size_t>(3), size);
Assert::AreEqual(6, sum);
}
TEST_METHOD(Serialized_MapType_Works)
{
Serialized<std::map<std::string, int>> s;
s.Access([](std::map<std::string, int>& v) {
v["one"] = 1;
v["two"] = 2;
});
int value = 0;
s.Read([&value](const std::map<std::string, int>& v) {
auto it = v.find("two");
if (it != v.end()) {
value = it->second;
}
});
Assert::AreEqual(2, value);
}
// Thread safety tests
TEST_METHOD(ThreadSafety_ConcurrentReads_NoDataRace)
{
Serialized<int> s;
s.Access([](int& v) { v = 42; });
std::atomic<int> readCount{ 0 };
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&s, &readCount]() {
for (int j = 0; j < 100; ++j)
{
s.Read([&readCount](const int& v) {
if (v == 42) {
readCount++;
}
});
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(1000, readCount.load());
}
TEST_METHOD(ThreadSafety_ConcurrentAccessAndRead_NoDataRace)
{
Serialized<int> s;
std::atomic<bool> done{ false };
std::atomic<int> accessCount{ 0 };
std::atomic<int> readersReady{ 0 };
std::atomic<bool> start{ false };
// Writer thread
std::thread writer([&s, &done, &accessCount, &readersReady, &start]() {
while (readersReady.load() < 5)
{
std::this_thread::yield();
}
start = true;
for (int i = 0; i < 100; ++i)
{
s.Access([i](int& v) {
v = i;
});
accessCount++;
}
done = true;
});
// Reader threads
std::vector<std::thread> readers;
std::atomic<int> readAttempts{ 0 };
for (int i = 0; i < 5; ++i)
{
readers.emplace_back([&s, &done, &readAttempts, &readersReady, &start]() {
readersReady++;
while (!start)
{
std::this_thread::yield();
}
while (!done)
{
s.Read([](const int& v) {
// Just read the value
(void)v;
});
readAttempts++;
}
});
}
writer.join();
for (auto& t : readers)
{
t.join();
}
// Verify all access calls completed
Assert::AreEqual(100, accessCount.load());
// Verify reads happened
Assert::IsTrue(readAttempts > 0);
}
// Struct type test
TEST_METHOD(Serialized_StructType_Works)
{
struct TestStruct
{
int x = 0;
std::string name;
};
Serialized<TestStruct> s;
s.Access([](TestStruct& v) {
v.x = 10;
v.name = "test";
});
int x = 0;
std::string name;
s.Read([&x, &name](const TestStruct& v) {
x = v.x;
name = v.name;
});
Assert::AreEqual(10, x);
Assert::AreEqual(std::string("test"), name);
}
TEST_METHOD(Reset_StructType_ResetsToDefault)
{
struct TestStruct
{
int x = 0;
std::string name;
};
Serialized<TestStruct> s;
s.Access([](TestStruct& v) {
v.x = 10;
v.name = "test";
});
s.Reset();
int x = -1;
std::string name = "not empty";
s.Read([&x, &name](const TestStruct& v) {
x = v.x;
name = v.name;
});
Assert::AreEqual(0, x);
Assert::AreEqual(std::string(""), name);
}
};
}

View File

@@ -1,283 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <string_utils.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(StringUtilsTests)
{
public:
// left_trim tests
TEST_METHOD(LeftTrim_EmptyString_ReturnsEmpty)
{
std::string_view input = "";
auto result = left_trim(input);
Assert::AreEqual(std::string_view(""), result);
}
TEST_METHOD(LeftTrim_NoWhitespace_ReturnsOriginal)
{
std::string_view input = "hello";
auto result = left_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(LeftTrim_LeadingSpaces_TrimsSpaces)
{
std::string_view input = " hello";
auto result = left_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(LeftTrim_LeadingTabs_TrimsTabs)
{
std::string_view input = "\t\thello";
auto result = left_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(LeftTrim_LeadingNewlines_TrimsNewlines)
{
std::string_view input = "\r\n\nhello";
auto result = left_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(LeftTrim_MixedWhitespace_TrimsAll)
{
std::string_view input = " \t\r\nhello";
auto result = left_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(LeftTrim_TrailingWhitespace_PreservesTrailing)
{
std::string_view input = " hello ";
auto result = left_trim(input);
Assert::AreEqual(std::string_view("hello "), result);
}
TEST_METHOD(LeftTrim_OnlyWhitespace_ReturnsEmpty)
{
std::string_view input = " \t\r\n";
auto result = left_trim(input);
Assert::AreEqual(std::string_view(""), result);
}
TEST_METHOD(LeftTrim_CustomChars_TrimsSpecified)
{
std::string_view input = "xxxhello";
auto result = left_trim(input, std::string_view("x"));
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(LeftTrim_WideString_Works)
{
std::wstring_view input = L" hello";
auto result = left_trim(input);
Assert::AreEqual(std::wstring_view(L"hello"), result);
}
// right_trim tests
TEST_METHOD(RightTrim_EmptyString_ReturnsEmpty)
{
std::string_view input = "";
auto result = right_trim(input);
Assert::AreEqual(std::string_view(""), result);
}
TEST_METHOD(RightTrim_NoWhitespace_ReturnsOriginal)
{
std::string_view input = "hello";
auto result = right_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(RightTrim_TrailingSpaces_TrimsSpaces)
{
std::string_view input = "hello ";
auto result = right_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(RightTrim_TrailingTabs_TrimsTabs)
{
std::string_view input = "hello\t\t";
auto result = right_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(RightTrim_TrailingNewlines_TrimsNewlines)
{
std::string_view input = "hello\r\n\n";
auto result = right_trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(RightTrim_LeadingWhitespace_PreservesLeading)
{
std::string_view input = " hello ";
auto result = right_trim(input);
Assert::AreEqual(std::string_view(" hello"), result);
}
TEST_METHOD(RightTrim_OnlyWhitespace_ReturnsEmpty)
{
std::string_view input = " \t\r\n";
auto result = right_trim(input);
Assert::AreEqual(std::string_view(""), result);
}
TEST_METHOD(RightTrim_CustomChars_TrimsSpecified)
{
std::string_view input = "helloxxx";
auto result = right_trim(input, std::string_view("x"));
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(RightTrim_WideString_Works)
{
std::wstring_view input = L"hello ";
auto result = right_trim(input);
Assert::AreEqual(std::wstring_view(L"hello"), result);
}
// trim tests
TEST_METHOD(Trim_EmptyString_ReturnsEmpty)
{
std::string_view input = "";
auto result = trim(input);
Assert::AreEqual(std::string_view(""), result);
}
TEST_METHOD(Trim_NoWhitespace_ReturnsOriginal)
{
std::string_view input = "hello";
auto result = trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(Trim_BothSides_TrimsBoth)
{
std::string_view input = " hello ";
auto result = trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(Trim_MixedWhitespace_TrimsAll)
{
std::string_view input = " \t\r\nhello \t\r\n";
auto result = trim(input);
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(Trim_InternalWhitespace_Preserved)
{
std::string_view input = " hello world ";
auto result = trim(input);
Assert::AreEqual(std::string_view("hello world"), result);
}
TEST_METHOD(Trim_OnlyWhitespace_ReturnsEmpty)
{
std::string_view input = " \t\r\n ";
auto result = trim(input);
Assert::AreEqual(std::string_view(""), result);
}
TEST_METHOD(Trim_CustomChars_TrimsSpecified)
{
std::string_view input = "xxxhelloxxx";
auto result = trim(input, std::string_view("x"));
Assert::AreEqual(std::string_view("hello"), result);
}
TEST_METHOD(Trim_WideString_Works)
{
std::wstring_view input = L" hello ";
auto result = trim(input);
Assert::AreEqual(std::wstring_view(L"hello"), result);
}
// replace_chars tests
TEST_METHOD(ReplaceChars_EmptyString_NoChange)
{
std::string s = "";
replace_chars(s, std::string_view("abc"), 'x');
Assert::AreEqual(std::string(""), s);
}
TEST_METHOD(ReplaceChars_NoMatchingChars_NoChange)
{
std::string s = "hello";
replace_chars(s, std::string_view("xyz"), '_');
Assert::AreEqual(std::string("hello"), s);
}
TEST_METHOD(ReplaceChars_SingleChar_Replaces)
{
std::string s = "hello";
replace_chars(s, std::string_view("l"), '_');
Assert::AreEqual(std::string("he__o"), s);
}
TEST_METHOD(ReplaceChars_MultipleChars_ReplacesAll)
{
std::string s = "hello world";
replace_chars(s, std::string_view("lo"), '_');
Assert::AreEqual(std::string("he___ w_r_d"), s);
}
TEST_METHOD(ReplaceChars_WideString_Works)
{
std::wstring s = L"hello";
replace_chars(s, std::wstring_view(L"l"), L'_');
Assert::AreEqual(std::wstring(L"he__o"), s);
}
// unwide tests
TEST_METHOD(Unwide_EmptyString_ReturnsEmpty)
{
std::wstring input = L"";
auto result = unwide(input);
Assert::AreEqual(std::string(""), result);
}
TEST_METHOD(Unwide_AsciiString_Converts)
{
std::wstring input = L"hello";
auto result = unwide(input);
Assert::AreEqual(std::string("hello"), result);
}
TEST_METHOD(Unwide_WithNumbers_Converts)
{
std::wstring input = L"test123";
auto result = unwide(input);
Assert::AreEqual(std::string("test123"), result);
}
TEST_METHOD(Unwide_WithSpecialChars_Converts)
{
std::wstring input = L"test!@#$%";
auto result = unwide(input);
Assert::AreEqual(std::string("test!@#$%"), result);
}
TEST_METHOD(Unwide_MixedCase_PreservesCase)
{
std::wstring input = L"HeLLo WoRLd";
auto result = unwide(input);
Assert::AreEqual(std::string("HeLLo WoRLd"), result);
}
TEST_METHOD(Unwide_LongString_Works)
{
std::wstring input = L"This is a longer string with multiple words and punctuation!";
auto result = unwide(input);
Assert::AreEqual(std::string("This is a longer string with multiple words and punctuation!"), result);
}
};
}

View File

@@ -1,192 +0,0 @@
#pragma once
#include "pch.h"
#include <string>
#include <filesystem>
#include <fstream>
#include <random>
namespace TestHelpers
{
// RAII helper for creating and cleaning up temporary files
class TempFile
{
public:
TempFile(const std::wstring& content = L"", const std::wstring& extension = L".txt")
{
wchar_t tempPath[MAX_PATH];
GetTempPathW(MAX_PATH, tempPath);
// Generate a unique filename
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(10000, 99999);
m_path = std::wstring(tempPath) + L"test_" + std::to_wstring(dis(gen)) + extension;
if (!content.empty())
{
std::wofstream file(m_path);
file << content;
}
}
~TempFile()
{
if (std::filesystem::exists(m_path))
{
std::filesystem::remove(m_path);
}
}
TempFile(const TempFile&) = delete;
TempFile& operator=(const TempFile&) = delete;
const std::wstring& path() const { return m_path; }
void write(const std::string& content)
{
std::ofstream file(m_path, std::ios::binary);
file << content;
}
void write(const std::wstring& content)
{
std::wofstream file(m_path);
file << content;
}
std::wstring read()
{
std::wifstream file(m_path);
return std::wstring((std::istreambuf_iterator<wchar_t>(file)),
std::istreambuf_iterator<wchar_t>());
}
private:
std::wstring m_path;
};
// RAII helper for creating and cleaning up temporary directories
class TempDirectory
{
public:
TempDirectory()
{
wchar_t tempPath[MAX_PATH];
GetTempPathW(MAX_PATH, tempPath);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(10000, 99999);
m_path = std::wstring(tempPath) + L"testdir_" + std::to_wstring(dis(gen));
std::filesystem::create_directories(m_path);
}
~TempDirectory()
{
if (std::filesystem::exists(m_path))
{
std::filesystem::remove_all(m_path);
}
}
TempDirectory(const TempDirectory&) = delete;
TempDirectory& operator=(const TempDirectory&) = delete;
const std::wstring& path() const { return m_path; }
private:
std::wstring m_path;
};
// Registry test key path - use HKCU for non-elevated tests
inline const std::wstring TestRegistryPath = L"Software\\PowerToys\\UnitTests";
// RAII helper for registry key creation/cleanup
class TestRegistryKey
{
public:
TestRegistryKey(const std::wstring& subKey = L"")
{
m_path = TestRegistryPath;
if (!subKey.empty())
{
m_path += L"\\" + subKey;
}
HKEY key;
if (RegCreateKeyExW(HKEY_CURRENT_USER, m_path.c_str(), 0, nullptr,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr,
&key, nullptr) == ERROR_SUCCESS)
{
RegCloseKey(key);
m_created = true;
}
}
~TestRegistryKey()
{
if (m_created)
{
RegDeleteTreeW(HKEY_CURRENT_USER, m_path.c_str());
}
}
TestRegistryKey(const TestRegistryKey&) = delete;
TestRegistryKey& operator=(const TestRegistryKey&) = delete;
bool isValid() const { return m_created; }
const std::wstring& path() const { return m_path; }
bool setStringValue(const std::wstring& name, const std::wstring& value)
{
HKEY key;
if (RegOpenKeyExW(HKEY_CURRENT_USER, m_path.c_str(), 0, KEY_SET_VALUE, &key) != ERROR_SUCCESS)
{
return false;
}
auto result = RegSetValueExW(key, name.c_str(), 0, REG_SZ,
reinterpret_cast<const BYTE*>(value.c_str()),
static_cast<DWORD>((value.length() + 1) * sizeof(wchar_t)));
RegCloseKey(key);
return result == ERROR_SUCCESS;
}
bool setDwordValue(const std::wstring& name, DWORD value)
{
HKEY key;
if (RegOpenKeyExW(HKEY_CURRENT_USER, m_path.c_str(), 0, KEY_SET_VALUE, &key) != ERROR_SUCCESS)
{
return false;
}
auto result = RegSetValueExW(key, name.c_str(), 0, REG_DWORD,
reinterpret_cast<const BYTE*>(&value), sizeof(DWORD));
RegCloseKey(key);
return result == ERROR_SUCCESS;
}
private:
std::wstring m_path;
bool m_created = false;
};
// Helper to wait for a condition with timeout
template<typename Predicate>
bool WaitFor(Predicate pred, std::chrono::milliseconds timeout = std::chrono::milliseconds(5000))
{
auto start = std::chrono::steady_clock::now();
while (!pred())
{
if (std::chrono::steady_clock::now() - start > timeout)
{
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return true;
}
}

View File

@@ -1,14 +0,0 @@
#include "pch.h"
#include <common/logger/logger.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <spdlog/sinks/null_sink.h>
std::shared_ptr<spdlog::logger> Logger::logger = spdlog::null_logger_mt("Common.Utils.UnitTests");
namespace PTSettingsHelper
{
std::wstring get_root_save_folder_location()
{
return L"";
}
}

View File

@@ -1,336 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <OnThreadExecutor.h>
#include <EventWaiter.h>
#include <EventLocker.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(OnThreadExecutorTests)
{
public:
TEST_METHOD(Constructor_CreatesInstance)
{
OnThreadExecutor executor;
// Should not crash
Assert::IsTrue(true);
}
TEST_METHOD(Submit_SingleTask_Executes)
{
OnThreadExecutor executor;
std::atomic<bool> executed{ false };
auto future = executor.submit(OnThreadExecutor::task_t([&executed]() {
executed = true;
}));
future.wait();
Assert::IsTrue(executed);
}
TEST_METHOD(Submit_MultipleTasks_ExecutesAll)
{
OnThreadExecutor executor;
std::atomic<int> counter{ 0 };
std::vector<std::future<void>> futures;
for (int i = 0; i < 10; ++i)
{
futures.push_back(executor.submit(OnThreadExecutor::task_t([&counter]() {
counter++;
})));
}
for (auto& f : futures)
{
f.wait();
}
Assert::AreEqual(10, counter.load());
}
TEST_METHOD(Submit_TasksExecuteInOrder)
{
OnThreadExecutor executor;
std::vector<int> order;
std::mutex orderMutex;
std::vector<std::future<void>> futures;
for (int i = 0; i < 5; ++i)
{
futures.push_back(executor.submit(OnThreadExecutor::task_t([&order, &orderMutex, i]() {
std::lock_guard lock(orderMutex);
order.push_back(i);
})));
}
for (auto& f : futures)
{
f.wait();
}
Assert::AreEqual(static_cast<size_t>(5), order.size());
for (int i = 0; i < 5; ++i)
{
Assert::AreEqual(i, order[i]);
}
}
TEST_METHOD(Submit_TaskReturnsResult)
{
OnThreadExecutor executor;
std::atomic<int> result{ 0 };
auto future = executor.submit(OnThreadExecutor::task_t([&result]() {
result = 42;
}));
future.wait();
Assert::AreEqual(42, result.load());
}
TEST_METHOD(Cancel_ClearsPendingTasks)
{
OnThreadExecutor executor;
std::atomic<int> counter{ 0 };
// Submit a slow task first
executor.submit(OnThreadExecutor::task_t([&counter]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
counter++;
}));
// Submit more tasks
for (int i = 0; i < 5; ++i)
{
executor.submit(OnThreadExecutor::task_t([&counter]() {
counter++;
}));
}
// Cancel pending tasks
executor.cancel();
// Wait a bit for any running task to complete
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// Not all tasks should have executed
Assert::IsTrue(counter < 6);
}
TEST_METHOD(Destructor_WaitsForCompletion)
{
std::atomic<bool> completed{ false };
std::future<void> future;
{
OnThreadExecutor executor;
future = executor.submit(OnThreadExecutor::task_t([&completed]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
completed = true;
}));
future.wait();
} // Destructor no longer required to wait for completion
Assert::IsTrue(completed);
}
TEST_METHOD(Submit_AfterCancel_StillWorks)
{
OnThreadExecutor executor;
std::atomic<int> counter{ 0 };
executor.submit(OnThreadExecutor::task_t([&counter]() {
counter++;
}));
executor.cancel();
auto future = executor.submit(OnThreadExecutor::task_t([&counter]() {
counter = 42;
}));
future.wait();
Assert::AreEqual(42, counter.load());
}
};
TEST_CLASS(EventWaiterTests)
{
public:
TEST_METHOD(Constructor_CreatesInstance)
{
EventWaiter waiter;
Assert::IsFalse(waiter.is_listening());
}
TEST_METHOD(Start_ValidEvent_ReturnsTrue)
{
EventWaiter waiter;
bool result = waiter.start(L"TestEvent_Start", [](DWORD) {});
Assert::IsTrue(result);
Assert::IsTrue(waiter.is_listening());
waiter.stop();
}
TEST_METHOD(Start_AlreadyListening_ReturnsFalse)
{
EventWaiter waiter;
waiter.start(L"TestEvent_Double1", [](DWORD) {});
bool result = waiter.start(L"TestEvent_Double2", [](DWORD) {});
Assert::IsFalse(result);
waiter.stop();
}
TEST_METHOD(Stop_WhileListening_StopsListening)
{
EventWaiter waiter;
waiter.start(L"TestEvent_Stop", [](DWORD) {});
Assert::IsTrue(waiter.is_listening());
waiter.stop();
Assert::IsFalse(waiter.is_listening());
}
TEST_METHOD(Stop_WhenNotListening_DoesNotCrash)
{
EventWaiter waiter;
waiter.stop(); // Should not crash
Assert::IsFalse(waiter.is_listening());
}
TEST_METHOD(Stop_CalledMultipleTimes_DoesNotCrash)
{
EventWaiter waiter;
waiter.start(L"TestEvent_MultiStop", [](DWORD) {});
waiter.stop();
waiter.stop();
waiter.stop();
Assert::IsFalse(waiter.is_listening());
}
TEST_METHOD(Callback_EventSignaled_CallsCallback)
{
EventWaiter waiter;
std::atomic<bool> called{ false };
std::atomic<DWORD> errorCode{ 0xFFFFFFFF };
// Create a named event we can signal
std::wstring eventName = L"TestEvent_Callback_" + std::to_wstring(GetCurrentProcessId());
HANDLE signalEvent = CreateEventW(nullptr, FALSE, FALSE, eventName.c_str());
Assert::IsNotNull(signalEvent);
waiter.start(eventName, [&called, &errorCode](DWORD err) {
errorCode = err;
called = true;
});
// Signal the event
SetEvent(signalEvent);
// Wait for callback
bool waitResult = TestHelpers::WaitFor([&called]() { return called.load(); }, std::chrono::milliseconds(1000));
waiter.stop();
CloseHandle(signalEvent);
Assert::IsTrue(waitResult);
Assert::AreEqual(static_cast<DWORD>(ERROR_SUCCESS), errorCode.load());
}
TEST_METHOD(Destructor_StopsListening)
{
std::atomic<bool> isListening{ false };
{
EventWaiter waiter;
waiter.start(L"TestEvent_Destructor", [](DWORD) {});
isListening = waiter.is_listening();
}
// After destruction, the waiter should have stopped
Assert::IsTrue(isListening);
}
TEST_METHOD(IsListening_InitialState_ReturnsFalse)
{
EventWaiter waiter;
Assert::IsFalse(waiter.is_listening());
}
TEST_METHOD(IsListening_AfterStart_ReturnsTrue)
{
EventWaiter waiter;
waiter.start(L"TestEvent_IsListening", [](DWORD) {});
Assert::IsTrue(waiter.is_listening());
waiter.stop();
}
TEST_METHOD(IsListening_AfterStop_ReturnsFalse)
{
EventWaiter waiter;
waiter.start(L"TestEvent_AfterStop", [](DWORD) {});
waiter.stop();
Assert::IsFalse(waiter.is_listening());
}
};
TEST_CLASS(EventLockerTests)
{
public:
TEST_METHOD(Get_ValidEventName_ReturnsLocker)
{
std::wstring eventName = L"TestEventLocker_" + std::to_wstring(GetCurrentProcessId());
auto locker = EventLocker::Get(eventName);
Assert::IsTrue(locker.has_value());
}
TEST_METHOD(Get_UniqueNames_CreatesSeparateLockers)
{
auto locker1 = EventLocker::Get(L"TestEventLocker1_" + std::to_wstring(GetCurrentProcessId()));
auto locker2 = EventLocker::Get(L"TestEventLocker2_" + std::to_wstring(GetCurrentProcessId()));
Assert::IsTrue(locker1.has_value());
Assert::IsTrue(locker2.has_value());
}
TEST_METHOD(Destructor_CleansUpHandle)
{
std::wstring eventName = L"TestEventLockerCleanup_" + std::to_wstring(GetCurrentProcessId());
{
auto locker = EventLocker::Get(eventName);
Assert::IsTrue(locker.has_value());
}
// After destruction, the event should be cleaned up
// Creating a new one should succeed
auto newLocker = EventLocker::Get(eventName);
Assert::IsTrue(newLocker.has_value());
}
TEST_METHOD(MoveConstructor_TransfersOwnership)
{
std::wstring eventName = L"TestEventLockerMove_" + std::to_wstring(GetCurrentProcessId());
auto locker1 = EventLocker::Get(eventName);
Assert::IsTrue(locker1.has_value());
EventLocker locker2 = std::move(*locker1);
// Move should transfer ownership without crash
Assert::IsTrue(true);
}
TEST_METHOD(MoveAssignment_TransfersOwnership)
{
std::wstring eventName1 = L"TestEventLockerMoveAssign1_" + std::to_wstring(GetCurrentProcessId());
std::wstring eventName2 = L"TestEventLockerMoveAssign2_" + std::to_wstring(GetCurrentProcessId());
auto locker1 = EventLocker::Get(eventName1);
auto locker2 = EventLocker::Get(eventName2);
Assert::IsTrue(locker1.has_value());
Assert::IsTrue(locker2.has_value());
*locker1 = std::move(*locker2);
// Should not crash
Assert::IsTrue(true);
}
};
}

View File

@@ -1,248 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <timeutil.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(TimeUtilsTests)
{
public:
// to_string tests
TEST_METHOD(ToString_ZeroTime_ReturnsZero)
{
time_t t = 0;
auto result = timeutil::to_string(t);
Assert::AreEqual(std::wstring(L"0"), result);
}
TEST_METHOD(ToString_PositiveTime_ReturnsString)
{
time_t t = 1234567890;
auto result = timeutil::to_string(t);
Assert::AreEqual(std::wstring(L"1234567890"), result);
}
TEST_METHOD(ToString_LargeTime_ReturnsString)
{
time_t t = 1700000000;
auto result = timeutil::to_string(t);
Assert::AreEqual(std::wstring(L"1700000000"), result);
}
// from_string tests
TEST_METHOD(FromString_ZeroString_ReturnsZero)
{
auto result = timeutil::from_string(L"0");
Assert::IsTrue(result.has_value());
Assert::AreEqual(static_cast<time_t>(0), result.value());
}
TEST_METHOD(FromString_ValidNumber_ReturnsTime)
{
auto result = timeutil::from_string(L"1234567890");
Assert::IsTrue(result.has_value());
Assert::AreEqual(static_cast<time_t>(1234567890), result.value());
}
TEST_METHOD(FromString_InvalidString_ReturnsNullopt)
{
auto result = timeutil::from_string(L"invalid");
Assert::IsFalse(result.has_value());
}
TEST_METHOD(FromString_EmptyString_ReturnsNullopt)
{
auto result = timeutil::from_string(L"");
Assert::IsFalse(result.has_value());
}
TEST_METHOD(FromString_MixedAlphaNumeric_ReturnsNullopt)
{
auto result = timeutil::from_string(L"123abc");
Assert::IsFalse(result.has_value());
}
TEST_METHOD(FromString_NegativeNumber_ReturnsNullopt)
{
auto result = timeutil::from_string(L"-1");
Assert::IsFalse(result.has_value());
}
// Roundtrip test
TEST_METHOD(ToStringFromString_Roundtrip_Works)
{
time_t original = 1609459200; // 2021-01-01 00:00:00 UTC
auto str = timeutil::to_string(original);
auto result = timeutil::from_string(str);
Assert::IsTrue(result.has_value());
Assert::AreEqual(original, result.value());
}
// now tests
TEST_METHOD(Now_ReturnsReasonableTime)
{
auto result = timeutil::now();
// Should be after 2020 and before 2100
Assert::IsTrue(result > 1577836800); // 2020-01-01
Assert::IsTrue(result < 4102444800); // 2100-01-01
}
TEST_METHOD(Now_TwoCallsAreCloseInTime)
{
auto first = timeutil::now();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto second = timeutil::now();
// Difference should be less than 2 seconds
Assert::IsTrue(second >= first);
Assert::IsTrue(second - first < 2);
}
// diff::in_seconds tests
TEST_METHOD(DiffInSeconds_SameTime_ReturnsZero)
{
time_t t = 1000000;
auto result = timeutil::diff::in_seconds(t, t);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
TEST_METHOD(DiffInSeconds_OneDifference_ReturnsOne)
{
time_t to = 1000001;
time_t from = 1000000;
auto result = timeutil::diff::in_seconds(to, from);
Assert::AreEqual(static_cast<int64_t>(1), result);
}
TEST_METHOD(DiffInSeconds_60Seconds_Returns60)
{
time_t to = 1000060;
time_t from = 1000000;
auto result = timeutil::diff::in_seconds(to, from);
Assert::AreEqual(static_cast<int64_t>(60), result);
}
TEST_METHOD(DiffInSeconds_NegativeDiff_ReturnsNegative)
{
time_t to = 1000000;
time_t from = 1000060;
auto result = timeutil::diff::in_seconds(to, from);
Assert::AreEqual(static_cast<int64_t>(-60), result);
}
// diff::in_minutes tests
TEST_METHOD(DiffInMinutes_SameTime_ReturnsZero)
{
time_t t = 1000000;
auto result = timeutil::diff::in_minutes(t, t);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
TEST_METHOD(DiffInMinutes_OneMinute_ReturnsOne)
{
time_t to = 1000060;
time_t from = 1000000;
auto result = timeutil::diff::in_minutes(to, from);
Assert::AreEqual(static_cast<int64_t>(1), result);
}
TEST_METHOD(DiffInMinutes_60Minutes_Returns60)
{
time_t to = 1003600;
time_t from = 1000000;
auto result = timeutil::diff::in_minutes(to, from);
Assert::AreEqual(static_cast<int64_t>(60), result);
}
TEST_METHOD(DiffInMinutes_LessThanMinute_ReturnsZero)
{
time_t to = 1000059;
time_t from = 1000000;
auto result = timeutil::diff::in_minutes(to, from);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
// diff::in_hours tests
TEST_METHOD(DiffInHours_SameTime_ReturnsZero)
{
time_t t = 1000000;
auto result = timeutil::diff::in_hours(t, t);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
TEST_METHOD(DiffInHours_OneHour_ReturnsOne)
{
time_t to = 1003600;
time_t from = 1000000;
auto result = timeutil::diff::in_hours(to, from);
Assert::AreEqual(static_cast<int64_t>(1), result);
}
TEST_METHOD(DiffInHours_24Hours_Returns24)
{
time_t to = 1086400;
time_t from = 1000000;
auto result = timeutil::diff::in_hours(to, from);
Assert::AreEqual(static_cast<int64_t>(24), result);
}
TEST_METHOD(DiffInHours_LessThanHour_ReturnsZero)
{
time_t to = 1003599;
time_t from = 1000000;
auto result = timeutil::diff::in_hours(to, from);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
// diff::in_days tests
TEST_METHOD(DiffInDays_SameTime_ReturnsZero)
{
time_t t = 1000000;
auto result = timeutil::diff::in_days(t, t);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
TEST_METHOD(DiffInDays_OneDay_ReturnsOne)
{
time_t to = 1086400;
time_t from = 1000000;
auto result = timeutil::diff::in_days(to, from);
Assert::AreEqual(static_cast<int64_t>(1), result);
}
TEST_METHOD(DiffInDays_7Days_Returns7)
{
time_t to = 1604800;
time_t from = 1000000;
auto result = timeutil::diff::in_days(to, from);
Assert::AreEqual(static_cast<int64_t>(7), result);
}
TEST_METHOD(DiffInDays_LessThanDay_ReturnsZero)
{
time_t to = 1086399;
time_t from = 1000000;
auto result = timeutil::diff::in_days(to, from);
Assert::AreEqual(static_cast<int64_t>(0), result);
}
// format_as_local tests
TEST_METHOD(FormatAsLocal_YearFormat_ReturnsYear)
{
time_t t = 1609459200; // 2021-01-01 00:00:00 UTC
auto result = timeutil::format_as_local("%Y", t);
// Result depends on local timezone, but year should be 2020 or 2021
Assert::IsTrue(result == "2020" || result == "2021");
}
TEST_METHOD(FormatAsLocal_DateFormat_ReturnsDate)
{
time_t t = 0; // 1970-01-01 00:00:00 UTC
auto result = timeutil::format_as_local("%Y-%m-%d", t);
// Result should be a date around 1970-01-01 depending on timezone
Assert::IsTrue(result.length() == 10); // YYYY-MM-DD format
Assert::IsTrue(result.substr(0, 4) == "1969" || result.substr(0, 4) == "1970");
}
};
}

View File

@@ -1,210 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <UnhandledExceptionHandler.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(UnhandledExceptionTests)
{
public:
// exceptionDescription tests
TEST_METHOD(ExceptionDescription_AccessViolation_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_ACCESS_VIOLATION);
Assert::IsTrue(result && *result != '\0');
// Should contain meaningful description
std::string desc{ result };
Assert::IsTrue(desc.find("ACCESS") != std::string::npos ||
desc.find("access") != std::string::npos ||
desc.find("violation") != std::string::npos ||
desc.length() > 0);
}
TEST_METHOD(ExceptionDescription_StackOverflow_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_STACK_OVERFLOW);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_DivideByZero_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_INT_DIVIDE_BY_ZERO);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_IllegalInstruction_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_ILLEGAL_INSTRUCTION);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_ArrayBoundsExceeded_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_Breakpoint_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_BREAKPOINT);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_SingleStep_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_SINGLE_STEP);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_FloatDivideByZero_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_FLT_DIVIDE_BY_ZERO);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_FloatOverflow_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_FLT_OVERFLOW);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_FloatUnderflow_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_FLT_UNDERFLOW);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_FloatInvalidOperation_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_FLT_INVALID_OPERATION);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_PrivilegedInstruction_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_PRIV_INSTRUCTION);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_InPageError_ReturnsDescription)
{
auto result = exceptionDescription(EXCEPTION_IN_PAGE_ERROR);
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_UnknownCode_ReturnsDescription)
{
auto result = exceptionDescription(0x12345678);
// Should return something (possibly "Unknown exception" or similar)
Assert::IsTrue(result && *result != '\0');
}
TEST_METHOD(ExceptionDescription_ZeroCode_ReturnsDescription)
{
auto result = exceptionDescription(0);
// Should handle zero gracefully
Assert::IsTrue(result && *result != '\0');
}
// GetFilenameStart tests (if accessible)
TEST_METHOD(GetFilenameStart_ValidPath_ReturnsFilename)
{
wchar_t path[] = L"C:\\folder\\subfolder\\file.exe";
int start = GetFilenameStart(path);
Assert::IsTrue(start >= 0);
Assert::AreEqual(std::wstring(L"file.exe"), std::wstring(path + start));
}
TEST_METHOD(GetFilenameStart_NoPath_ReturnsOriginal)
{
wchar_t path[] = L"file.exe";
int start = GetFilenameStart(path);
Assert::IsTrue(start >= 0);
Assert::AreEqual(std::wstring(L"file.exe"), std::wstring(path + start));
}
TEST_METHOD(GetFilenameStart_TrailingBackslash_ReturnsEmpty)
{
wchar_t path[] = L"C:\\folder\\";
int start = GetFilenameStart(path);
// Should point to empty string after last backslash
Assert::IsTrue(start >= 0);
}
TEST_METHOD(GetFilenameStart_NullPath_HandlesGracefully)
{
// This might crash or return null depending on implementation
// Just document the behavior
int start = GetFilenameStart(nullptr);
(void)start;
// Result is implementation-defined for null input
Assert::IsTrue(true);
}
// Thread safety tests
TEST_METHOD(ExceptionDescription_ThreadSafe)
{
std::vector<std::thread> threads;
std::atomic<int> successCount{ 0 };
for (int i = 0; i < 10; ++i)
{
threads.emplace_back([&successCount]() {
for (int j = 0; j < 10; ++j)
{
auto desc = exceptionDescription(EXCEPTION_ACCESS_VIOLATION);
if (desc && *desc != '\0')
{
successCount++;
}
}
});
}
for (auto& t : threads)
{
t.join();
}
Assert::AreEqual(100, successCount.load());
}
// All exception codes test
TEST_METHOD(ExceptionDescription_AllCommonCodes_ReturnDescriptions)
{
std::vector<DWORD> codes = {
EXCEPTION_ACCESS_VIOLATION,
EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
EXCEPTION_BREAKPOINT,
EXCEPTION_DATATYPE_MISALIGNMENT,
EXCEPTION_FLT_DENORMAL_OPERAND,
EXCEPTION_FLT_DIVIDE_BY_ZERO,
EXCEPTION_FLT_INEXACT_RESULT,
EXCEPTION_FLT_INVALID_OPERATION,
EXCEPTION_FLT_OVERFLOW,
EXCEPTION_FLT_STACK_CHECK,
EXCEPTION_FLT_UNDERFLOW,
EXCEPTION_ILLEGAL_INSTRUCTION,
EXCEPTION_IN_PAGE_ERROR,
EXCEPTION_INT_DIVIDE_BY_ZERO,
EXCEPTION_INT_OVERFLOW,
EXCEPTION_INVALID_DISPOSITION,
EXCEPTION_NONCONTINUABLE_EXCEPTION,
EXCEPTION_PRIV_INSTRUCTION,
EXCEPTION_SINGLE_STEP,
EXCEPTION_STACK_OVERFLOW
};
for (DWORD code : codes)
{
auto desc = exceptionDescription(code);
Assert::IsTrue(desc && *desc != '\0', (L"Empty description for code: " + std::to_wstring(code)).c_str());
}
}
};
}

View File

@@ -1,36 +0,0 @@
#include <windows.h>
#include "resource.h"
#include "../version/version.h"
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{8B5CFB38-CCBA-40A8-AD7A-89C57B070884}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>UnitTestsCommonUtils</RootNamespace>
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
<ProjectName>Common.Utils.UnitTests</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<PlatformToolset>v143</PlatformToolset>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\tests\UnitTestsCommonUtils\</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>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\deps\spdlog\include;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp23</LanguageStandard>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_HEADER_ONLY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>RuntimeObject.lib;Msi.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="StringUtils.Tests.cpp" />
<ClCompile Include="ColorUtils.Tests.cpp" />
<ClCompile Include="TimeUtils.Tests.cpp" />
<ClCompile Include="WinApiError.Tests.cpp" />
<ClCompile Include="Serialized.Tests.cpp" />
<ClCompile Include="Json.Tests.cpp" />
<ClCompile Include="OsDetect.Tests.cpp" />
<ClCompile Include="Threading.Tests.cpp" />
<ClCompile Include="ProcessPath.Tests.cpp" />
<ClCompile Include="Window.Tests.cpp" />
<ClCompile Include="GameMode.Tests.cpp" />
<ClCompile Include="Gpo.Tests.cpp" />
<ClCompile Include="MsiUtils.Tests.cpp" />
<ClCompile Include="HttpClient.Tests.cpp" />
<ClCompile Include="ComObjectFactory.Tests.cpp" />
<ClCompile Include="AppMutex.Tests.cpp" />
<ClCompile Include="Elevation.Tests.cpp" />
<ClCompile Include="Exec.Tests.cpp" />
<ClCompile Include="ExcludedApps.Tests.cpp" />
<ClCompile Include="HDropIterator.Tests.cpp" />
<ClCompile Include="LoggerHelper.Tests.cpp" />
<ClCompile Include="ModulesRegistry.Tests.cpp" />
<ClCompile Include="MsWindowsSettings.Tests.cpp" />
<ClCompile Include="Package.Tests.cpp" />
<ClCompile Include="ProcessApi.Tests.cpp" />
<ClCompile Include="ProcessWaiter.Tests.cpp" />
<ClCompile Include="Registry.Tests.cpp" />
<ClCompile Include="Resources.Tests.cpp" />
<ClCompile Include="TestStubs.cpp" />
<ClCompile Include="UnhandledException.Tests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="TestHelpers.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="UnitTests-CommonUtils.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\utils\CommonUtils.vcxproj">
<Project>{74485049-C722-400F-ABE5-86AC41736D21}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -1,143 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\Pure Functions">
<UniqueIdentifier>{A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Threading">
<UniqueIdentifier>{B2C3D4E5-F6A7-4B6C-9D0E-1F2A3B4C5D6E}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Process">
<UniqueIdentifier>{C3D4E5F6-A7B8-4C7D-0E1F-2A3B4C5D6E7F}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Registry">
<UniqueIdentifier>{D4E5F6A7-B8C9-4D8E-1F2A-3B4C5D6E7F8A}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Integration">
<UniqueIdentifier>{E5F6A7B8-C9D0-4E9F-2A3B-4C5D6E7F8A9B}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="StringUtils.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="ColorUtils.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="TimeUtils.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="WinApiError.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="Serialized.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="Json.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="ExcludedApps.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="OsDetect.Tests.cpp">
<Filter>Source Files\Pure Functions</Filter>
</ClCompile>
<ClCompile Include="Threading.Tests.cpp">
<Filter>Source Files\Threading</Filter>
</ClCompile>
<ClCompile Include="AppMutex.Tests.cpp">
<Filter>Source Files\Threading</Filter>
</ClCompile>
<ClCompile Include="ProcessWaiter.Tests.cpp">
<Filter>Source Files\Threading</Filter>
</ClCompile>
<ClCompile Include="ProcessPath.Tests.cpp">
<Filter>Source Files\Process</Filter>
</ClCompile>
<ClCompile Include="ProcessApi.Tests.cpp">
<Filter>Source Files\Process</Filter>
</ClCompile>
<ClCompile Include="Window.Tests.cpp">
<Filter>Source Files\Process</Filter>
</ClCompile>
<ClCompile Include="Exec.Tests.cpp">
<Filter>Source Files\Process</Filter>
</ClCompile>
<ClCompile Include="GameMode.Tests.cpp">
<Filter>Source Files\Process</Filter>
</ClCompile>
<ClCompile Include="MsWindowsSettings.Tests.cpp">
<Filter>Source Files\Process</Filter>
</ClCompile>
<ClCompile Include="Registry.Tests.cpp">
<Filter>Source Files\Registry</Filter>
</ClCompile>
<ClCompile Include="Gpo.Tests.cpp">
<Filter>Source Files\Registry</Filter>
</ClCompile>
<ClCompile Include="ModulesRegistry.Tests.cpp">
<Filter>Source Files\Registry</Filter>
</ClCompile>
<ClCompile Include="Elevation.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="Package.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="MsiUtils.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="HttpClient.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="Resources.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="LoggerHelper.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="ComObjectFactory.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="HDropIterator.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
<ClCompile Include="UnhandledException.Tests.cpp">
<Filter>Source Files\Integration</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TestHelpers.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="UnitTests-CommonUtils.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>

View File

@@ -1,130 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <winapi_error.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(WinApiErrorTests)
{
public:
// get_last_error_message tests
TEST_METHOD(GetLastErrorMessage_Success_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_SUCCESS);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_FileNotFound_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_FILE_NOT_FOUND);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_AccessDenied_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_ACCESS_DENIED);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_PathNotFound_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_PATH_NOT_FOUND);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_InvalidHandle_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_INVALID_HANDLE);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_NotEnoughMemory_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_NOT_ENOUGH_MEMORY);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_InvalidParameter_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_INVALID_PARAMETER);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
// get_last_error_or_default tests
TEST_METHOD(GetLastErrorOrDefault_Success_ReturnsMessage)
{
auto result = get_last_error_or_default(ERROR_SUCCESS);
Assert::IsFalse(result.empty());
}
TEST_METHOD(GetLastErrorOrDefault_FileNotFound_ReturnsMessage)
{
auto result = get_last_error_or_default(ERROR_FILE_NOT_FOUND);
Assert::IsFalse(result.empty());
}
TEST_METHOD(GetLastErrorOrDefault_AccessDenied_ReturnsMessage)
{
auto result = get_last_error_or_default(ERROR_ACCESS_DENIED);
Assert::IsFalse(result.empty());
}
TEST_METHOD(GetLastErrorOrDefault_UnknownError_ReturnsEmptyOrMessage)
{
// For an unknown error code, should return empty string or a default message
auto result = get_last_error_or_default(0xFFFFFFFF);
// Either empty or has content, both are valid
Assert::IsTrue(result.empty() || !result.empty());
}
// Comparison tests
TEST_METHOD(BothFunctions_SameError_ProduceSameContent)
{
auto message = get_last_error_message(ERROR_FILE_NOT_FOUND);
auto defaultMessage = get_last_error_or_default(ERROR_FILE_NOT_FOUND);
Assert::IsTrue(message.has_value());
Assert::AreEqual(*message, defaultMessage);
}
TEST_METHOD(BothFunctions_SuccessError_ProduceSameContent)
{
auto message = get_last_error_message(ERROR_SUCCESS);
auto defaultMessage = get_last_error_or_default(ERROR_SUCCESS);
Assert::IsTrue(message.has_value());
Assert::AreEqual(*message, defaultMessage);
}
// Error code specific tests
TEST_METHOD(GetLastErrorMessage_SharingViolation_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_SHARING_VIOLATION);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_FileExists_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_FILE_EXISTS);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
TEST_METHOD(GetLastErrorMessage_DirNotEmpty_ReturnsMessage)
{
auto result = get_last_error_message(ERROR_DIR_NOT_EMPTY);
Assert::IsTrue(result.has_value());
Assert::IsFalse(result->empty());
}
};
}

View File

@@ -1,159 +0,0 @@
#include "pch.h"
#include "TestHelpers.h"
#include <window.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommonUtils
{
TEST_CLASS(WindowTests)
{
public:
// is_system_window tests
TEST_METHOD(IsSystemWindow_DesktopWindow_ReturnsResult)
{
HWND desktop = GetDesktopWindow();
Assert::IsNotNull(desktop);
// Get class name
char className[256] = {};
GetClassNameA(desktop, className, sizeof(className));
bool result = is_system_window(desktop, className);
// Just verify it doesn't crash and returns a boolean
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsSystemWindow_NullHwnd_ReturnsFalse)
{
auto shell = GetShellWindow();
auto desktop = GetDesktopWindow();
bool result = is_system_window(nullptr, "ClassName");
bool expected = (shell == nullptr) || (desktop == nullptr);
Assert::AreEqual(expected, result);
}
TEST_METHOD(IsSystemWindow_InvalidHwnd_ReturnsFalse)
{
bool result = is_system_window(reinterpret_cast<HWND>(0x12345678), "ClassName");
Assert::IsFalse(result);
}
TEST_METHOD(IsSystemWindow_EmptyClassName_DoesNotCrash)
{
HWND desktop = GetDesktopWindow();
bool result = is_system_window(desktop, "");
// Just verify it doesn't crash
Assert::IsTrue(result == true || result == false);
}
TEST_METHOD(IsSystemWindow_NullClassName_DoesNotCrash)
{
HWND desktop = GetDesktopWindow();
bool result = is_system_window(desktop, nullptr);
// Should handle null className gracefully
Assert::IsTrue(result == true || result == false);
}
// GetWindowCreateParam tests
TEST_METHOD(GetWindowCreateParam_ValidLparam_ReturnsValue)
{
struct TestData
{
int value;
};
TestData data{ 42 };
CREATESTRUCT cs{};
cs.lpCreateParams = &data;
auto result = GetWindowCreateParam<TestData*>(reinterpret_cast<LPARAM>(&cs));
Assert::IsNotNull(result);
Assert::AreEqual(42, result->value);
}
// Window data storage tests
TEST_METHOD(WindowData_StoreAndRetrieve_Works)
{
// Create a simple message-only window for testing
WNDCLASSW wc = {};
wc.lpfnWndProc = DefWindowProcW;
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpszClassName = L"TestWindowClass_DataTest";
RegisterClassW(&wc);
HWND hwnd = CreateWindowExW(0, L"TestWindowClass_DataTest", L"Test",
0, 0, 0, 0, 0, HWND_MESSAGE, nullptr,
GetModuleHandleW(nullptr), nullptr);
if (hwnd)
{
int value = 42;
int* testValue = &value;
StoreWindowParam(hwnd, testValue);
auto retrieved = GetWindowParam<int*>(hwnd);
Assert::AreEqual(testValue, retrieved);
DestroyWindow(hwnd);
}
UnregisterClassW(L"TestWindowClass_DataTest", GetModuleHandleW(nullptr));
Assert::IsTrue(true); // Window creation might fail in test environment
}
// run_message_loop tests
TEST_METHOD(RunMessageLoop_UntilIdle_Completes)
{
// Run message loop until idle with a timeout
// This should complete quickly since there are no messages
auto start = std::chrono::steady_clock::now();
run_message_loop(true, 100);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
// Should complete within reasonable time
Assert::IsTrue(elapsed.count() < 500);
}
TEST_METHOD(RunMessageLoop_WithTimeout_RespectsTimeout)
{
auto start = std::chrono::steady_clock::now();
run_message_loop(false, 50);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
// Should take at least the timeout duration
// Allow some tolerance for timing
Assert::IsTrue(elapsed.count() >= 40 && elapsed.count() < 500);
}
TEST_METHOD(RunMessageLoop_ZeroTimeout_CompletesImmediately)
{
auto start = std::chrono::steady_clock::now();
run_message_loop(false, 0);
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start);
// Should complete very quickly
Assert::IsTrue(elapsed.count() < 100);
}
TEST_METHOD(RunMessageLoop_NoTimeout_ProcessesMessages)
{
// Post a quit message before starting the loop
PostQuitMessage(0);
// Should process the quit message and exit
run_message_loop(false, std::nullopt);
Assert::IsTrue(true);
}
};
}

View File

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

View File

@@ -1,5 +0,0 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@@ -1,39 +0,0 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include <Windows.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Data.Json.h>
#include <string>
#include <vector>
#include <optional>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
#include <shared_mutex>
#include <future>
#include <queue>
#include <filesystem>
#include <fstream>
#include <chrono>
#include <ctime>
// 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

View File

@@ -1,13 +0,0 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by UnitTests-CommonUtils.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys UnitTests-CommonUtils"
#define INTERNAL_NAME "UnitTests-CommonUtils"
#define ORIGINAL_FILENAME "UnitTests-CommonUtils.dll"
// Non-localizable
//////////////////////////////

View File

@@ -251,40 +251,4 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::CMDPAL_SHOW_EVENT;
}
hstring Constants::TogglePowerDisplayEvent()
{
return CommonSharedConstants::TOGGLE_POWER_DISPLAY_EVENT;
}
hstring Constants::TerminatePowerDisplayEvent()
{
return CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT;
}
hstring Constants::RefreshPowerDisplayMonitorsEvent()
{
return CommonSharedConstants::REFRESH_POWER_DISPLAY_MONITORS_EVENT;
}
hstring Constants::SettingsUpdatedPowerDisplayEvent()
{
return CommonSharedConstants::SETTINGS_UPDATED_POWER_DISPLAY_EVENT;
}
hstring Constants::PowerDisplaySendSettingsTelemetryEvent()
{
return CommonSharedConstants::POWER_DISPLAY_SEND_SETTINGS_TELEMETRY_EVENT;
}
hstring Constants::HotkeyUpdatedPowerDisplayEvent()
{
return CommonSharedConstants::HOTKEY_UPDATED_POWER_DISPLAY_EVENT;
}
hstring Constants::PowerDisplayToggleMessage()
{
return CommonSharedConstants::POWER_DISPLAY_TOGGLE_MESSAGE;
}
hstring Constants::PowerDisplayApplyProfileMessage()
{
return CommonSharedConstants::POWER_DISPLAY_APPLY_PROFILE_MESSAGE;
}
hstring Constants::PowerDisplayTerminateAppMessage()
{
return CommonSharedConstants::POWER_DISPLAY_TERMINATE_APP_MESSAGE;
}
}

View File

@@ -66,15 +66,6 @@ namespace winrt::PowerToys::Interop::implementation
static hstring WorkspacesHotkeyEvent();
static hstring PowerToysRunnerTerminateSettingsEvent();
static hstring ShowCmdPalEvent();
static hstring TogglePowerDisplayEvent();
static hstring TerminatePowerDisplayEvent();
static hstring RefreshPowerDisplayMonitorsEvent();
static hstring SettingsUpdatedPowerDisplayEvent();
static hstring PowerDisplaySendSettingsTelemetryEvent();
static hstring HotkeyUpdatedPowerDisplayEvent();
static hstring PowerDisplayToggleMessage();
static hstring PowerDisplayApplyProfileMessage();
static hstring PowerDisplayTerminateAppMessage();
};
}

View File

@@ -63,15 +63,6 @@ namespace PowerToys
static String WorkspacesHotkeyEvent();
static String PowerToysRunnerTerminateSettingsEvent();
static String ShowCmdPalEvent();
static String TogglePowerDisplayEvent();
static String TerminatePowerDisplayEvent();
static String RefreshPowerDisplayMonitorsEvent();
static String SettingsUpdatedPowerDisplayEvent();
static String PowerDisplaySendSettingsTelemetryEvent();
static String HotkeyUpdatedPowerDisplayEvent();
static String PowerDisplayToggleMessage();
static String PowerDisplayApplyProfileMessage();
static String PowerDisplayTerminateAppMessage();
}
}
}

View File

@@ -41,7 +41,6 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
@@ -100,6 +99,7 @@
<ClInclude Include="Constants.h">
<DependentUpon>KeyboardListener.idl</DependentUpon>
</ClInclude>
<ClInclude Include="excluded_app.h" />
<ClInclude Include="HotkeyManager.h">
<DependentUpon>HotkeyManager.idl</DependentUpon>
</ClInclude>
@@ -114,6 +114,7 @@
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="shared_constants.h" />
<ClInclude Include="tasklist_positions.h" />
<ClInclude Include="TwoWayPipeMessageIPCManaged.h">
<DependentUpon>TwoWayPipeMessageIPCManaged.idl</DependentUpon>
</ClInclude>
@@ -127,6 +128,7 @@
<ClCompile Include="Constants.cpp">
<DependentUpon>KeyboardListener.idl</DependentUpon>
</ClCompile>
<ClCompile Include="excluded_app.cpp" />
<ClCompile Include="HotkeyManager.cpp">
<DependentUpon>HotkeyManager.idl</DependentUpon>
</ClCompile>
@@ -140,6 +142,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="tasklist_positions.cpp" />
<ClCompile Include="TwoWayPipeMessageIPCManaged.cpp">
<DependentUpon>TwoWayPipeMessageIPCManaged.idl</DependentUpon>
</ClCompile>
@@ -165,6 +168,9 @@
<Midl Include="TwoWayPipeMessageIPCManaged.idl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>

View File

@@ -54,6 +54,12 @@
<ClInclude Include="Constants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="excluded_app.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="tasklist_positions.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="keyboard_layout.cpp">
@@ -83,6 +89,12 @@
<ClCompile Include="Constants.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="excluded_app.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tasklist_positions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="interop.rc">

View File

@@ -0,0 +1,39 @@
#include "pch.h"
#include "excluded_app.h"
#include <../utils/string_utils.h>
extern "C"
{
__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide()
{
PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Shortcut Guide");
auto settingsObject = settings.get_raw_json();
std::wstring apps = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"disabled_apps").GetNamedString(L"value").c_str();
auto excludedUppercase = apps;
CharUpperBuffW(excludedUppercase.data(), static_cast<DWORD>(excludedUppercase.length()));
std::wstring_view view(excludedUppercase);
view = left_trim<wchar_t>(trim<wchar_t>(view));
while (!view.empty())
{
auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
m_excludedApps.emplace_back(view.substr(0, pos));
view.remove_prefix(pos);
view = left_trim<wchar_t>(trim<wchar_t>(view));
}
if (m_excludedApps.empty())
{
return false;
}
if (HWND foregroundApp{ GetForegroundWindow() })
{
auto processPath = get_process_path(foregroundApp);
CharUpperBuffW(processPath.data(), static_cast<DWORD>(processPath.length()));
return check_excluded_app(foregroundApp, processPath, m_excludedApps);
}
return false;
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
extern "C"
{
std::vector<std::wstring> m_excludedApps;
__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide();
}

View File

@@ -12,3 +12,29 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <Windows.h>
#include <Endpointvolume.h>
#include <vector>
#include <UIAutomation.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_3.h>
#include <d2d1_3helper.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <string>
#include <algorithm>
#include <chrono>
#include <mutex>
#include <thread>
#include <functional>
#include <condition_variable>
#include <stdexcept>
#include <tuple>
#include <unordered_set>
#include <filesystem>
#include <common/utils/excluded_apps.h>
#include <common/utils/process_path.h>
#include <../SettingsAPI/settings_objects.h>

View File

@@ -153,23 +153,6 @@ namespace CommonSharedConstants
const wchar_t ZOOMIT_SNIP_EVENT[] = L"Local\\PowerToysZoomIt-SnipEvent-2fd9c211-436d-4f17-a902-2528aaae3e30";
const wchar_t ZOOMIT_RECORD_EVENT[] = L"Local\\PowerToysZoomIt-RecordEvent-74539344-eaad-4711-8e83-23946e424512";
// Path to the events used by PowerDisplay
const wchar_t TOGGLE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-ToggleEvent-5f1a9c3e-7d2b-4e8f-9a6c-3b5d7e9f1a2c";
const wchar_t TERMINATE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a";
const wchar_t REFRESH_POWER_DISPLAY_MONITORS_EVENT[] = L"Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c";
const wchar_t SETTINGS_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e";
const wchar_t POWER_DISPLAY_SEND_SETTINGS_TELEMETRY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsTelemetryEvent-8c4f2a1d-5e3b-7f9c-1a6d-3b8e5f2c9a7d";
const wchar_t HOTKEY_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-HotkeyUpdatedEvent-9d5f3a2b-7e1c-4b8a-6f3d-2a9e5c7b1d4f";
// IPC Messages used in PowerDisplay (Named Pipe communication)
const wchar_t POWER_DISPLAY_TOGGLE_MESSAGE[] = L"Toggle";
const wchar_t POWER_DISPLAY_APPLY_PROFILE_MESSAGE[] = L"ApplyProfile";
const wchar_t POWER_DISPLAY_TERMINATE_APP_MESSAGE[] = L"TerminateApp";
// Path to the events used by LightSwitch to notify PowerDisplay of theme changes
const wchar_t LIGHT_SWITCH_LIGHT_THEME_EVENT[] = L"Local\\PowerToysLightSwitch-LightThemeEvent-50077121-2ffc-4841-9c86-ab1bd3f9baca";
const wchar_t LIGHT_SWITCH_DARK_THEME_EVENT[] = L"Local\\PowerToysLightSwitch-DarkThemeEvent-b3a835c0-eaa2-49b0-b8eb-f793e3df3368";
// used from quick access window
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";
const wchar_t CMDPAL_EXIT_EVENT[] = L"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd";

View File

@@ -0,0 +1,242 @@
#include "pch.h"
#include "tasklist_positions.h"
// Tried my hardest adapting this to C#, but FindWindowW didn't work properly in C#. ~Noraa Junker
extern "C"
{
HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor)
{
POINT pt;
if (!GetCursorPos(&pt))
return nullptr;
// Find the primary taskbar
HWND primaryTaskbar = FindWindowW(L"Shell_TrayWnd", nullptr);
if (primaryTaskbar)
{
MONITORINFO mi = { sizeof(mi) };
if (GetWindowRect(primaryTaskbar, &mi.rcMonitor))
{
HMONITOR primaryMonitor = MonitorFromRect(&mi.rcMonitor, MONITOR_DEFAULTTONEAREST);
if (primaryMonitor == monitor)
return primaryTaskbar;
}
}
// Find the secondary taskbar(s)
HWND secondaryTaskbar = nullptr;
while ((secondaryTaskbar = FindWindowExW(nullptr, secondaryTaskbar, L"Shell_SecondaryTrayWnd", nullptr)) != nullptr)
{
MONITORINFO mi = { sizeof(mi) };
RECT rc;
if (GetWindowRect(secondaryTaskbar, &rc))
{
HMONITOR taskbarMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
if (monitor == taskbarMonitor)
return secondaryTaskbar;
}
}
return nullptr;
}
void update(HMONITOR monitor)
{
// Get HWND of the tasklist for the monitor under the cursor
auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor);
if (!taskbar_hwnd)
return;
wchar_t class_name[64] = {};
GetClassNameW(taskbar_hwnd, class_name, 64);
HWND tasklist_hwnd = nullptr;
if (wcscmp(class_name, L"Shell_TrayWnd") == 0)
{
// Primary taskbar structure
tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"ReBarWindow32", nullptr);
if (!tasklist_hwnd)
return;
tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskSwWClass", nullptr);
if (!tasklist_hwnd)
return;
tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskListWClass", nullptr);
if (!tasklist_hwnd)
return;
}
else if (wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0)
{
// Secondary taskbar structure
HWND worker_hwnd = FindWindowExW(taskbar_hwnd, 0, L"WorkerW", nullptr);
if (!worker_hwnd)
return;
tasklist_hwnd = FindWindowExW(worker_hwnd, 0, L"MSTaskListWClass", nullptr);
if (!tasklist_hwnd)
return;
}
else
{
// Unknown taskbar type
return;
}
if (!automation)
{
winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IUIAutomation,
automation.put_void()));
winrt::check_hresult(automation->CreateTrueCondition(true_condition.put()));
}
element = nullptr;
winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put()));
}
void update_new(HMONITOR monitor)
{
// Get HWND of the tasklist for the monitor under the cursor
auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor);
if (!taskbar_hwnd)
return;
wchar_t class_name[64] = {};
GetClassNameW(taskbar_hwnd, class_name, 64);
HWND tasklist_hwnd = nullptr;
if (wcscmp(class_name, L"Shell_TrayWnd") == 0 || wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0)
{
// Primary taskbar structure
tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"Windows.UI.Composition.DesktopWindowContentBridge", nullptr);
if (!tasklist_hwnd)
return;
tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"Windows.UI.Input.InputSite.WindowClass", nullptr);
if (!tasklist_hwnd)
return;
}
else
{
// Unknown taskbar type
return;
}
if (!automation)
{
winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IUIAutomation,
automation.put_void()));
winrt::check_hresult(automation->CreateTrueCondition(true_condition.put()));
}
winrt::com_ptr<IUIAutomationElement> tempElement;
element = nullptr;
winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, tempElement.put()));
winrt::check_hresult(
tempElement->FindFirst(TreeScope_Children, true_condition.get(), element.put()));
}
bool update_buttons(std::vector<TasklistButton>& buttons)
{
if (!automation || !element)
{
return false;
}
winrt::com_ptr<IUIAutomationElementArray> elements;
if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0)
return false;
if (!elements)
return false;
int count;
if (elements->get_Length(&count) < 0)
return false;
winrt::com_ptr<IUIAutomationElement> child;
std::vector<TasklistButton> found_buttons;
found_buttons.reserve(count);
for (int i = 0; i < count; ++i)
{
child = nullptr;
if (elements->GetElement(i, child.put()) < 0)
return false;
TasklistButton button = {};
if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0)
{
if (var_rect.vt == (VT_R8 | VT_ARRAY))
{
LONG pos;
double value;
pos = 0;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.x = static_cast<long>(value);
pos = 1;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.y = static_cast<long>(value);
pos = 2;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.width = static_cast<long>(value);
pos = 3;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.height = static_cast<long>(value);
}
VariantClear(&var_rect);
}
else
{
return false;
}
if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0)
{
wcsncpy_s(button.name, automation_id, _countof(button.name));
SysFreeString(automation_id);
if (wcsncmp(button.name, L"Appid:", wcslen(L"Appid:")) != 0)
{
continue;
}
}
found_buttons.push_back(button);
}
// assign keynums
buttons.clear();
for (auto& button : found_buttons)
{
if (buttons.empty())
{
button.keynum = 1;
buttons.push_back(std::move(button));
}
else
{
if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row
break;
if (wcsncmp(button.name, buttons.back().name, _countof(button.name)) == 0)
continue; // skip buttons from the same app
button.keynum = buttons.back().keynum + 1;
buttons.push_back(std::move(button));
if (buttons.back().keynum == 10)
break; // no more than 10 buttons
}
}
return true;
}
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size)
{
update(monitor);
static std::vector<TasklistButton> buttons;
update_buttons(buttons);
*size = static_cast<int>(buttons.size());
if (*size == 0)
{
// After a certain Windows update, the old method stopped working, try the new one
update_new(monitor);
update_buttons(buttons);
*size = static_cast<int>(buttons.size());
}
return buttons.data();
}
}

View File

@@ -0,0 +1,23 @@
#pragma once
struct TasklistButton
{
wchar_t name[256];
int x;
int y;
int width;
int height;
int keynum;
};
extern "C"
{
winrt::com_ptr<IUIAutomation> automation;
winrt::com_ptr<IUIAutomationElement> element;
winrt::com_ptr<IUIAutomationCondition> true_condition;
// Helper to get the taskbar HWND for the monitor under the cursor
HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor);
bool update_buttons(std::vector<TasklistButton>& buttons);
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size);
}

View File

@@ -83,7 +83,6 @@ struct LogSettings
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.log";
inline const static std::string zoomItLoggerName = "zoom-it";
inline const static std::string lightSwitchLoggerName = "light-switch";
inline const static std::string powerDisplayLoggerName = "powerdisplay";
inline const static int retention = 30;
std::wstring logLevel;
LogSettings();

View File

@@ -1,135 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="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">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{74485049-C722-400F-ABE5-86AC41736D21}</ProjectGuid>
<RootNamespace>CommonUtils</RootNamespace>
<ProjectName>CommonUtils</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<OutDir>..\..\..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="..\..\..\deps\spdlog.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>..\;..\..\;..\..\..\;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>DbgHelp.lib;Msi.lib;Shlwapi.lib;pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="appMutex.h" />
<ClInclude Include="clean_video_conference.h" />
<ClInclude Include="color.h" />
<ClInclude Include="com_object_factory.h" />
<ClInclude Include="elevation.h" />
<ClInclude Include="EventLocker.h" />
<ClInclude Include="EventWaiter.h" />
<ClInclude Include="excluded_apps.h" />
<ClInclude Include="exec.h" />
<ClInclude Include="game_mode.h" />
<ClInclude Include="gpo.h" />
<ClInclude Include="HDropIterator.h" />
<ClInclude Include="HttpClient.h" />
<ClInclude Include="json.h" />
<ClInclude Include="language_helper.h" />
<ClInclude Include="logger_helper.h" />
<ClInclude Include="modulesRegistry.h" />
<ClInclude Include="MsiUtils.h" />
<ClInclude Include="MsWindowsSettings.h" />
<ClInclude Include="OnThreadExecutor.h" />
<ClInclude Include="os-detect.h" />
<ClInclude Include="package.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="process_path.h" />
<ClInclude Include="processApi.h" />
<ClInclude Include="ProcessWaiter.h" />
<ClInclude Include="registry.h" />
<ClInclude Include="resources.h" />
<ClInclude Include="serialized.h" />
<ClInclude Include="string_utils.h" />
<ClInclude Include="timeutil.h" />
<ClInclude Include="UnhandledExceptionHandler.h" />
<ClInclude Include="winapi_error.h" />
<ClInclude Include="window.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="elevation.cpp" />
<ClCompile Include="exec.cpp" />
<ClCompile Include="gpo.cpp" />
<ClCompile Include="modulesRegistry.cpp" />
<ClCompile Include="MsiUtils.cpp" />
<ClCompile Include="package.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="registry.cpp" />
<ClCompile Include="resources.cpp" />
<ClCompile Include="UnhandledExceptionHandler.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -34,7 +34,6 @@ public:
{
this->eventHandle = e.eventHandle;
e.eventHandle = nullptr;
return *this;
}
~EventLocker()

View File

@@ -1,8 +1,5 @@
#pragma once
#include <Windows.h>
#include "../logger/logger.h"
inline bool GetAnimationsEnabled()
{
BOOL enabled = 0;
@@ -13,4 +10,4 @@ inline bool GetAnimationsEnabled()
Logger::error("SystemParametersInfo SPI_GETCLIENTAREAANIMATION failed.");
}
return enabled;
}
}

View File

@@ -1,92 +0,0 @@
#include "pch.h"
#include "MsiUtils.h"
#include <Msi.h>
#include <pathcch.h>
namespace // Strings in this namespace should not be localized
{
const inline wchar_t POWER_TOYS_UPGRADE_CODE[] = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
const inline wchar_t POWER_TOYS_UPGRADE_CODE_USER[] = L"{D8B559DB-4C98-487A-A33F-50A8EEE42726}";
const inline wchar_t POWERTOYS_EXE_COMPONENT[] = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
}
std::optional<std::wstring> GetMsiPackageInstalledPath(bool perUser)
{
constexpr size_t guid_length = 39;
wchar_t product_ID[guid_length];
std::wstring upgradeCode = (perUser ? POWER_TOYS_UPGRADE_CODE_USER : POWER_TOYS_UPGRADE_CODE);
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(upgradeCode.c_str(), 0, 0, product_ID); !found)
{
return std::nullopt;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(product_ID); !installed)
{
return std::nullopt;
}
DWORD buf_size = MAX_PATH;
wchar_t buf[MAX_PATH];
if (ERROR_SUCCESS == MsiGetProductInfoW(product_ID, INSTALLPROPERTY_INSTALLLOCATION, buf, &buf_size) && buf_size)
{
return buf;
}
DWORD package_path_size = 0;
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size))
{
return std::nullopt;
}
std::wstring package_path(++package_path_size, L'\0');
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size))
{
return std::nullopt;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
wchar_t path[MAX_PATH];
DWORD path_size = MAX_PATH;
MsiGetComponentPathW(product_ID, POWERTOYS_EXE_COMPONENT, path, &path_size);
if (!path_size)
{
return std::nullopt;
}
PathCchRemoveFileSpec(path, path_size);
return path;
}
std::wstring GetMsiPackagePath()
{
std::wstring package_path;
wchar_t GUID_product_string[39];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(POWER_TOYS_UPGRADE_CODE, 0, 0, GUID_product_string); !found)
{
return package_path;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(GUID_product_string); !installed)
{
return package_path;
}
DWORD package_path_size = 0;
if (const bool has_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size); !has_package_path)
{
return package_path;
}
package_path = std::wstring(++package_path_size, L'\0');
if (const bool got_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size); !got_package_path)
{
package_path = {};
return package_path;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
return package_path;
}

View File

@@ -3,10 +3,95 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <pathcch.h>
#include <Msi.h>
#include <optional>
#include <string>
// Implementations in MsiUtils.cpp
std::optional<std::wstring> GetMsiPackageInstalledPath(bool perUser);
std::wstring GetMsiPackagePath();
namespace // Strings in this namespace should not be localized
{
const inline wchar_t POWER_TOYS_UPGRADE_CODE[] = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
const inline wchar_t POWER_TOYS_UPGRADE_CODE_USER[] = L"{D8B559DB-4C98-487A-A33F-50A8EEE42726}";
const inline wchar_t POWERTOYS_EXE_COMPONENT[] = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
}
std::optional<std::wstring> GetMsiPackageInstalledPath(bool perUser)
{
constexpr size_t guid_length = 39;
wchar_t product_ID[guid_length];
std::wstring upgradeCode = (perUser ? POWER_TOYS_UPGRADE_CODE_USER : POWER_TOYS_UPGRADE_CODE);
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(upgradeCode.c_str(), 0, 0, product_ID); !found)
{
return std::nullopt;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(product_ID); !installed)
{
return std::nullopt;
}
DWORD buf_size = MAX_PATH;
wchar_t buf[MAX_PATH];
if (ERROR_SUCCESS == MsiGetProductInfoW(product_ID, INSTALLPROPERTY_INSTALLLOCATION, buf, &buf_size) && buf_size)
{
return buf;
}
DWORD package_path_size = 0;
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size))
{
return std::nullopt;
}
std::wstring package_path(++package_path_size, L'\0');
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size))
{
return std::nullopt;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
wchar_t path[MAX_PATH];
DWORD path_size = MAX_PATH;
MsiGetComponentPathW(product_ID, POWERTOYS_EXE_COMPONENT, path, &path_size);
if (!path_size)
{
return std::nullopt;
}
PathCchRemoveFileSpec(path, path_size);
return path;
}
std::wstring GetMsiPackagePath()
{
std::wstring package_path;
wchar_t GUID_product_string[39];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(POWER_TOYS_UPGRADE_CODE, 0, 0, GUID_product_string); !found)
{
return package_path;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(GUID_product_string); !installed)
{
return package_path;
}
DWORD package_path_size = 0;
if (const bool has_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size); !has_package_path)
{
return package_path;
}
package_path = std::wstring(++package_path_size, L'\0');
if (const bool got_package_path = ERROR_SUCCESS == MsiGetProductInfoW(GUID_product_string, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size); !got_package_path)
{
package_path = {};
return package_path;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
return package_path;
}

View File

@@ -7,19 +7,7 @@ namespace ProcessWaiter
{
void OnProcessTerminate(std::wstring parent_pid, std::function<void(DWORD)> callback)
{
DWORD pid = 0;
try
{
pid = std::stol(parent_pid);
}
catch (...)
{
if (callback)
{
callback(ERROR_INVALID_PARAMETER);
}
return;
}
DWORD pid = std::stol(parent_pid);
std::thread([=]() {
HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (process != nullptr)
@@ -27,26 +15,17 @@ namespace ProcessWaiter
if (WaitForSingleObject(process, INFINITE) == WAIT_OBJECT_0)
{
CloseHandle(process);
if (callback)
{
callback(ERROR_SUCCESS);
}
callback(ERROR_SUCCESS);
}
else
{
CloseHandle(process);
if (callback)
{
callback(GetLastError());
}
callback(GetLastError());
}
}
else
{
if (callback)
{
callback(GetLastError());
}
callback(GetLastError());
}
}).detach();
}

View File

@@ -1,209 +0,0 @@
#include "pch.h"
#include "UnhandledExceptionHandler.h"
#include <DbgHelp.h>
#include <signal.h>
#include <sstream>
#include "winapi_error.h"
#include "../logger/logger.h"
static BOOLEAN processingException = FALSE;
std::wstring GetModuleName(HANDLE process, const STACKFRAME64& stack)
{
static wchar_t modulePath[MAX_PATH]{};
const size_t size = sizeof(modulePath);
memset(&modulePath[0], '\0', size);
DWORD64 moduleBase = SymGetModuleBase64(process, stack.AddrPC.Offset);
if (!moduleBase)
{
Logger::error(L"Failed to get a module. {}", get_last_error_or_default(GetLastError()));
return std::wstring();
}
if (!GetModuleFileNameW(reinterpret_cast<HINSTANCE>(moduleBase), modulePath, MAX_PATH))
{
Logger::error(L"Failed to get a module path. {}", get_last_error_or_default(GetLastError()));
return std::wstring();
}
const int start = GetFilenameStart(modulePath);
return std::wstring(modulePath, start);
}
std::wstring GetName(HANDLE process, const STACKFRAME64& stack)
{
static IMAGEHLP_SYMBOL64* pSymbol = static_cast<IMAGEHLP_SYMBOL64*>(malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH * sizeof(TCHAR)));
if (!pSymbol)
{
return std::wstring();
}
memset(pSymbol, '\0', sizeof(*pSymbol) + MAX_PATH);
pSymbol->MaxNameLength = MAX_PATH;
pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
DWORD64 dw64Displacement = 0;
if (!SymGetSymFromAddr64(process, stack.AddrPC.Offset, &dw64Displacement, pSymbol))
{
Logger::error(L"Failed to get a symbol. {}", get_last_error_or_default(GetLastError()));
return std::wstring();
}
std::string str = pSymbol->Name;
return std::wstring(str.begin(), str.end());
}
std::wstring GetLine(HANDLE process, const STACKFRAME64& stack)
{
static IMAGEHLP_LINE64 line{};
memset(&line, '\0', sizeof(IMAGEHLP_LINE64));
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
line.LineNumber = 0;
DWORD dwDisplacement = 0;
if (!SymGetLineFromAddr64(process, stack.AddrPC.Offset, &dwDisplacement, &line))
{
return std::wstring();
}
std::string fileName(line.FileName);
return L"(" + std::wstring(fileName.begin(), fileName.end()) + L":" + std::to_wstring(line.LineNumber) + L")";
}
void LogStackTrace()
{
CONTEXT context;
try
{
RtlCaptureContext(&context);
}
catch (...)
{
Logger::error(L"Failed to capture context. {}", get_last_error_or_default(GetLastError()));
return;
}
STACKFRAME64 stack;
memset(&stack, 0, sizeof(STACKFRAME64));
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
#ifdef _M_ARM64
stack.AddrPC.Offset = context.Pc;
stack.AddrStack.Offset = context.Sp;
stack.AddrFrame.Offset = context.Fp;
#else
stack.AddrPC.Offset = context.Rip;
stack.AddrStack.Offset = context.Rsp;
stack.AddrFrame.Offset = context.Rbp;
#endif
stack.AddrPC.Mode = AddrModeFlat;
stack.AddrStack.Mode = AddrModeFlat;
stack.AddrFrame.Mode = AddrModeFlat;
BOOL result = false;
std::wstringstream ss;
for (;;)
{
result = StackWalk64(
#ifdef _M_ARM64
IMAGE_FILE_MACHINE_ARM64,
#else
IMAGE_FILE_MACHINE_AMD64,
#endif
process,
thread,
&stack,
&context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL);
if (!result)
{
break;
}
ss << GetModuleName(process, stack) << "!" << GetName(process, stack) << GetLine(process, stack) << std::endl;
}
Logger::error(L"STACK TRACE\r\n{}", ss.str());
Logger::flush();
}
LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info)
{
if (!processingException)
{
bool headerLogged = false;
try
{
const char* exDescription = "Exception code not available";
processingException = true;
if (info != NULL && info->ExceptionRecord != NULL && info->ExceptionRecord->ExceptionCode != NULL)
{
exDescription = exceptionDescription(info->ExceptionRecord->ExceptionCode);
}
headerLogged = true;
Logger::error(exDescription);
LogStackTrace();
}
catch (...)
{
Logger::error("Failed to log stack trace");
Logger::flush();
}
processingException = false;
}
return EXCEPTION_CONTINUE_SEARCH;
}
void AbortHandler(int /*signal_number*/)
{
Logger::error("--- ABORT");
try
{
LogStackTrace();
}
catch (...)
{
Logger::error("Failed to log stack trace on abort");
Logger::flush();
}
}
void InitSymbols()
{
// Preload symbols so they will be available in case of out-of-memory exception
SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
HANDLE process = GetCurrentProcess();
if (!SymInitialize(process, NULL, TRUE))
{
Logger::error(L"Failed to initialize symbol handler. {}", get_last_error_or_default(GetLastError()));
}
}
void InitUnhandledExceptionHandler(void)
{
try
{
InitSymbols();
// Global handler for unhandled exceptions
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
// Handler for abort()
signal(SIGABRT, &AbortHandler);
}
catch (...)
{
Logger::error("Failed to init global unhandled exception handler");
}
}

View File

@@ -2,9 +2,15 @@
#include <Windows.h>
#include <DbgHelp.h>
#include <string>
#include <signal.h>
#include <sstream>
#include <stdio.h>
#include "winapi_error.h"
#include "../logger/logger.h"
static BOOLEAN processingException = FALSE;
// Small inline functions that should stay in the header
static inline const char* exceptionDescription(const DWORD& code)
{
switch (code)
@@ -74,12 +80,201 @@ inline int GetFilenameStart(wchar_t* path)
return found;
}
// Implementations in UnhandledExceptionHandler.cpp
std::wstring GetModuleName(HANDLE process, const STACKFRAME64& stack);
std::wstring GetName(HANDLE process, const STACKFRAME64& stack);
std::wstring GetLine(HANDLE process, const STACKFRAME64& stack);
void LogStackTrace();
LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info);
void AbortHandler(int signal_number);
void InitSymbols();
void InitUnhandledExceptionHandler(void);
inline std::wstring GetModuleName(HANDLE process, const STACKFRAME64& stack)
{
static wchar_t modulePath[MAX_PATH]{};
const size_t size = sizeof(modulePath);
memset(&modulePath[0], '\0', size);
DWORD64 moduleBase = SymGetModuleBase64(process, stack.AddrPC.Offset);
if (!moduleBase)
{
Logger::error(L"Failed to get a module. {}", get_last_error_or_default(GetLastError()));
return std::wstring();
}
if (!GetModuleFileNameW(reinterpret_cast<HINSTANCE>(moduleBase), modulePath, MAX_PATH))
{
Logger::error(L"Failed to get a module path. {}", get_last_error_or_default(GetLastError()));
return std::wstring();
}
const int start = GetFilenameStart(modulePath);
return std::wstring(modulePath, start);
}
inline std::wstring GetName(HANDLE process, const STACKFRAME64& stack)
{
static IMAGEHLP_SYMBOL64* pSymbol = static_cast<IMAGEHLP_SYMBOL64*>(malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH * sizeof(TCHAR)));
if (!pSymbol)
{
return std::wstring();
}
memset(pSymbol, '\0', sizeof(*pSymbol) + MAX_PATH);
pSymbol->MaxNameLength = MAX_PATH;
pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
DWORD64 dw64Displacement = 0;
if (!SymGetSymFromAddr64(process, stack.AddrPC.Offset, &dw64Displacement, pSymbol))
{
Logger::error(L"Failed to get a symbol. {}", get_last_error_or_default(GetLastError()));
return std::wstring();
}
std::string str = pSymbol->Name;
return std::wstring(str.begin(), str.end());
}
inline std::wstring GetLine(HANDLE process, const STACKFRAME64& stack)
{
static IMAGEHLP_LINE64 line{};
memset(&line, '\0', sizeof(IMAGEHLP_LINE64));
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
line.LineNumber = 0;
DWORD dwDisplacement = 0;
if (!SymGetLineFromAddr64(process, stack.AddrPC.Offset, &dwDisplacement, &line))
{
return std::wstring();
}
std::string fileName(line.FileName);
return L"(" + std::wstring(fileName.begin(), fileName.end()) + L":" + std::to_wstring(line.LineNumber) + L")";
}
inline void LogStackTrace()
{
CONTEXT context;
try
{
RtlCaptureContext(&context);
}
catch (...)
{
Logger::error(L"Failed to capture context. {}", get_last_error_or_default(GetLastError()));
return;
}
STACKFRAME64 stack;
memset(&stack, 0, sizeof(STACKFRAME64));
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
#ifdef _M_ARM64
stack.AddrPC.Offset = context.Pc;
stack.AddrStack.Offset = context.Sp;
stack.AddrFrame.Offset = context.Fp;
#else
stack.AddrPC.Offset = context.Rip;
stack.AddrStack.Offset = context.Rsp;
stack.AddrFrame.Offset = context.Rbp;
#endif
stack.AddrPC.Mode = AddrModeFlat;
stack.AddrStack.Mode = AddrModeFlat;
stack.AddrFrame.Mode = AddrModeFlat;
BOOL result = false;
std::wstringstream ss;
for (;;)
{
result = StackWalk64(
#ifdef _M_ARM64
IMAGE_FILE_MACHINE_ARM64,
#else
IMAGE_FILE_MACHINE_AMD64,
#endif
process,
thread,
&stack,
&context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL);
if (!result)
{
break;
}
ss << GetModuleName(process, stack) << "!" << GetName(process, stack) << GetLine(process, stack) << std::endl;
}
Logger::error(L"STACK TRACE\r\n{}", ss.str());
Logger::flush();
}
inline LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info)
{
if (!processingException)
{
bool headerLogged = false;
try
{
const char* exDescription = "Exception code not available";
processingException = true;
if (info != NULL && info->ExceptionRecord != NULL && info->ExceptionRecord->ExceptionCode != NULL)
{
exDescription = exceptionDescription(info->ExceptionRecord->ExceptionCode);
}
headerLogged = true;
Logger::error(exDescription);
LogStackTrace();
}
catch (...)
{
Logger::error("Failed to log stack trace");
Logger::flush();
}
processingException = false;
}
return EXCEPTION_CONTINUE_SEARCH;
}
/* Handler to trap abort() calls */
inline void AbortHandler(int /*signal_number*/)
{
Logger::error("--- ABORT");
try
{
LogStackTrace();
}
catch (...)
{
Logger::error("Failed to log stack trace on abort");
Logger::flush();
}
}
inline void InitSymbols()
{
// Preload symbols so they will be available in case of out-of-memory exception
SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
HANDLE process = GetCurrentProcess();
if (!SymInitialize(process, NULL, TRUE))
{
Logger::error(L"Failed to initialize symbol handler. {}", get_last_error_or_default(GetLastError()));
}
}
inline void InitUnhandledExceptionHandler(void)
{
try
{
InitSymbols();
// Global handler for unhandled exceptions
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
// Handler for abort()
signal(SIGABRT, &AbortHandler);
}
catch (...)
{
Logger::error("Failed to init global unhandled exception handler");
}
}

View File

@@ -31,10 +31,6 @@ public:
HRESULT __stdcall CreateInstance(IUnknown* punkOuter, const IID& riid, void** ppv)
{
if (!ppv)
{
return E_POINTER;
}
*ppv = nullptr;
if (punkOuter)
@@ -59,4 +55,4 @@ public:
private:
std::atomic<long> _refCount;
};
};

View File

@@ -1,491 +0,0 @@
#include "pch.h"
#include "elevation.h"
#include <shldisp.h>
#include <exdisp.h>
#include <comdef.h>
#include <fmt/format.h>
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
#include <common/utils/process_path.h>
#include <common/utils/processApi.h>
namespace
{
std::wstring GetErrorString(HRESULT handle)
{
_com_error err(handle);
return err.ErrorMessage();
}
}
bool FindDesktopFolderView(REFIID riid, void** ppv)
{
CComPtr<IShellWindows> spShellWindows;
auto result = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
if (result != S_OK || spShellWindows == nullptr)
{
Logger::warn(L"Failed to create instance. {}", GetErrorString(result));
return false;
}
CComVariant vtLoc(CSIDL_DESKTOP);
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
result = spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
if (result != S_OK || spdisp == nullptr)
{
Logger::warn(L"Failed to find the window. {}", GetErrorString(result));
return false;
}
CComPtr<IShellBrowser> spBrowser;
result = CComQIPtr<IServiceProvider>(spdisp)->QueryService(SID_STopLevelBrowser,
IID_PPV_ARGS(&spBrowser));
if (result != S_OK || spBrowser == nullptr)
{
Logger::warn(L"Failed to query service. {}", GetErrorString(result));
return false;
}
CComPtr<IShellView> spView;
result = spBrowser->QueryActiveShellView(&spView);
if (result != S_OK || spView == nullptr)
{
Logger::warn(L"Failed to query active shell window. {}", GetErrorString(result));
return false;
}
result = spView->QueryInterface(riid, ppv);
if (result != S_OK || ppv == nullptr || *ppv == nullptr)
{
Logger::warn(L"Failed to query interface. {}", GetErrorString(result));
return false;
}
return true;
}
bool GetDesktopAutomationObject(REFIID riid, void** ppv)
{
CComPtr<IShellView> spsv;
// Desktop may not be available on startup
auto attempts = 5;
for (auto i = 1; i <= attempts; i++)
{
if (FindDesktopFolderView(IID_PPV_ARGS(&spsv)))
{
break;
}
Logger::warn(L"FindDesktopFolderView() failed attempt {}", i);
if (i == attempts)
{
Logger::warn(L"FindDesktopFolderView() max attempts reached");
return false;
}
Sleep(3000);
}
CComPtr<IDispatch> spdispView;
auto result = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
if (result != S_OK)
{
Logger::warn(L"GetItemObject() failed. {}", GetErrorString(result));
return false;
}
result = spdispView->QueryInterface(riid, ppv);
if (result != S_OK)
{
Logger::warn(L"QueryInterface() failed. {}", GetErrorString(result));
return false;
}
return true;
}
bool ShellExecuteFromExplorer(
PCWSTR pszFile,
PCWSTR pszParameters,
PCWSTR workingDir)
{
CComPtr<IShellFolderViewDual> spFolderView;
if (!GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView)))
{
return false;
}
CComPtr<IDispatch> spdispShell;
auto result = spFolderView->get_Application(&spdispShell);
if (result != S_OK)
{
Logger::warn(L"get_Application() failed. {}", GetErrorString(result));
return false;
}
CComQIPtr<IShellDispatch2>(spdispShell)
->ShellExecuteW(CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""),
CComVariant(workingDir),
CComVariant(L""),
CComVariant(SW_SHOWNORMAL));
return true;
}
HANDLE run_as_different_user(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir, const bool showWindow)
{
Logger::info(L"run_elevated with params={}", params);
SHELLEXECUTEINFOW exec_info = { 0 };
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
exec_info.lpVerb = L"runAsUser";
exec_info.lpFile = file.c_str();
exec_info.lpParameters = params.c_str();
exec_info.hwnd = 0;
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = workingDir;
exec_info.hInstApp = 0;
if (showWindow)
{
exec_info.nShow = SW_SHOWDEFAULT;
}
else
{
// might have limited success, but only option with ShellExecuteExW
exec_info.nShow = SW_HIDE;
}
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
}
HANDLE run_elevated(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir, const bool showWindow)
{
Logger::info(L"run_elevated with params={}", params);
SHELLEXECUTEINFOW exec_info = { 0 };
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
exec_info.lpVerb = L"runas";
exec_info.lpFile = file.c_str();
exec_info.lpParameters = params.c_str();
exec_info.hwnd = 0;
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = workingDir;
exec_info.hInstApp = 0;
if (showWindow)
{
exec_info.nShow = SW_SHOWDEFAULT;
}
else
{
// might have limited success, but only option with ShellExecuteExW
exec_info.nShow = SW_HIDE;
}
BOOL result = ShellExecuteExW(&exec_info);
return result ? exec_info.hProcess : nullptr;
}
bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir, const bool showWindow)
{
Logger::info(L"run_non_elevated with params={}", params);
auto executable_args = L"\"" + file + L"\"";
if (!params.empty())
{
executable_args += L" " + params;
}
HWND hwnd = GetShellWindow();
if (!hwnd)
{
if (GetLastError() == ERROR_SUCCESS)
{
Logger::warn(L"GetShellWindow() returned null. Shell window is not available");
}
else
{
Logger::error(L"GetShellWindow() failed. {}", get_last_error_or_default(GetLastError()));
}
return false;
}
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
winrt::handle process{ OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid) };
if (!process)
{
Logger::error(L"OpenProcess() failed. {}", get_last_error_or_default(GetLastError()));
return false;
}
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
auto pproc_buffer = std::make_unique<char[]>(size);
auto pptal = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(pproc_buffer.get());
if (!pptal)
{
Logger::error(L"pptal failed to initialize. {}", get_last_error_or_default(GetLastError()));
return false;
}
if (!InitializeProcThreadAttributeList(pptal, 1, 0, &size))
{
Logger::error(L"InitializeProcThreadAttributeList() failed. {}", get_last_error_or_default(GetLastError()));
return false;
}
HANDLE process_handle = process.get();
if (!UpdateProcThreadAttribute(pptal,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&process_handle,
sizeof(process_handle),
nullptr,
nullptr))
{
Logger::error(L"UpdateProcThreadAttribute() failed. {}", get_last_error_or_default(GetLastError()));
return false;
}
STARTUPINFOEX siex = { 0 };
siex.lpAttributeList = pptal;
siex.StartupInfo.cb = sizeof(siex);
PROCESS_INFORMATION pi = { 0 };
auto dwCreationFlags = EXTENDED_STARTUPINFO_PRESENT;
if (!showWindow)
{
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
siex.StartupInfo.wShowWindow = SW_HIDE;
dwCreationFlags = CREATE_NO_WINDOW;
}
auto succeeded = CreateProcessW(file.c_str(),
&executable_args[0],
nullptr,
nullptr,
FALSE,
dwCreationFlags,
nullptr,
workingDir,
&siex.StartupInfo,
&pi);
if (succeeded)
{
if (pi.hProcess)
{
if (returnPid)
{
*returnPid = GetProcessId(pi.hProcess);
}
CloseHandle(pi.hProcess);
}
if (pi.hThread)
{
CloseHandle(pi.hThread);
}
}
else
{
Logger::error(L"CreateProcessW() failed. {}", get_last_error_or_default(GetLastError()));
}
return succeeded;
}
bool RunNonElevatedEx(const std::wstring& file, const std::wstring& params, const std::wstring& working_dir)
{
bool success = false;
HRESULT co_init = E_FAIL;
try
{
co_init = CoInitialize(nullptr);
success = ShellExecuteFromExplorer(file.c_str(), params.c_str(), working_dir.c_str());
}
catch (...)
{
}
if (SUCCEEDED(co_init))
{
CoUninitialize();
}
return success;
}
std::optional<ProcessInfo> RunNonElevatedFailsafe(const std::wstring& file, const std::wstring& params, const std::wstring& working_dir, DWORD handleAccess)
{
bool launched = RunNonElevatedEx(file, params, working_dir);
if (!launched)
{
Logger::warn(L"RunNonElevatedEx() failed. Trying fallback");
std::wstring action_runner_path = get_module_folderpath() + L"\\PowerToys.ActionRunner.exe";
std::wstring newParams = fmt::format(L"-run-non-elevated -target \"{}\" {}", file, params);
launched = run_non_elevated(action_runner_path, newParams, nullptr, working_dir.c_str());
if (launched)
{
Logger::trace(L"Started {}", file);
}
else
{
Logger::warn(L"Failed to start {}", file);
return std::nullopt;
}
}
auto handles = getProcessHandlesByName(std::filesystem::path{ file }.filename().wstring(), PROCESS_QUERY_INFORMATION | SYNCHRONIZE | handleAccess);
if (handles.empty())
return std::nullopt;
ProcessInfo result;
result.processID = GetProcessId(handles[0].get());
result.processHandle = std::move(handles[0]);
return result;
}
bool run_same_elevation(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir)
{
auto executable_args = L"\"" + file + L"\"";
if (!params.empty())
{
executable_args += L" " + params;
}
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
auto succeeded = CreateProcessW(file.c_str(),
&executable_args[0],
nullptr,
nullptr,
FALSE,
0,
nullptr,
workingDir,
&si,
&pi);
if (succeeded)
{
if (pi.hProcess)
{
if (returnPid)
{
*returnPid = GetProcessId(pi.hProcess);
}
CloseHandle(pi.hProcess);
}
if (pi.hThread)
{
CloseHandle(pi.hThread);
}
}
return succeeded;
}
bool check_user_is_admin()
{
auto freeMemory = [](PSID pSID, PTOKEN_GROUPS pGroupInfo) {
if (pSID)
{
FreeSid(pSID);
}
if (pGroupInfo)
{
GlobalFree(pGroupInfo);
}
};
HANDLE hToken;
DWORD dwSize = 0;
PTOKEN_GROUPS pGroupInfo;
SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
PSID pSID = NULL;
// Open a handle to the access token for the calling process.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
{
return true;
}
// Call GetTokenInformation to get the buffer size.
if (!GetTokenInformation(hToken, TokenGroups, NULL, dwSize, &dwSize))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
return true;
}
}
// Allocate the buffer.
pGroupInfo = static_cast<PTOKEN_GROUPS>(GlobalAlloc(GPTR, dwSize));
// Call GetTokenInformation again to get the group information.
if (!GetTokenInformation(hToken, TokenGroups, pGroupInfo, dwSize, &dwSize))
{
freeMemory(pSID, pGroupInfo);
return true;
}
// Create a SID for the BUILTIN\Administrators group.
if (!AllocateAndInitializeSid(&SIDAuth, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pSID))
{
freeMemory(pSID, pGroupInfo);
return true;
}
// Loop through the group SIDs looking for the administrator SID.
for (DWORD i = 0; i < pGroupInfo->GroupCount; ++i)
{
if (EqualSid(pSID, pGroupInfo->Groups[i].Sid))
{
freeMemory(pSID, pGroupInfo);
return true;
}
}
freeMemory(pSID, pGroupInfo);
return false;
}
bool IsProcessOfWindowElevated(HWND window)
{
DWORD pid = 0;
GetWindowThreadProcessId(window, &pid);
if (!pid)
{
return false;
}
wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
pid) };
wil::unique_handle token;
if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
{
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
{
return elevation.TokenIsElevated != 0;
}
}
return false;
}

View File

@@ -4,25 +4,153 @@
#include <Windows.h>
#include <shellapi.h>
#include <sddl.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
#include <comdef.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <string>
#include <filesystem>
#include <optional>
#pragma warning(push)
#pragma warning(disable : 26471 26492 26493 26497)
#include <wil/resource.h>
#pragma warning(pop)
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
#include <common/utils/process_path.h>
#include <common/utils/processApi.h>
// Forward declarations - implementations in elevation.cpp
bool FindDesktopFolderView(REFIID riid, void** ppv);
bool GetDesktopAutomationObject(REFIID riid, void** ppv);
bool ShellExecuteFromExplorer(
PCWSTR pszFile,
PCWSTR pszParameters = nullptr,
PCWSTR workingDir = L"");
namespace
{
inline std::wstring GetErrorString(HRESULT handle)
{
_com_error err(handle);
return err.ErrorMessage();
}
inline bool FindDesktopFolderView(REFIID riid, void** ppv)
{
CComPtr<IShellWindows> spShellWindows;
auto result = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
if (result != S_OK || spShellWindows == nullptr)
{
Logger::warn(L"Failed to create instance. {}", GetErrorString(result));
return false;
}
CComVariant vtLoc(CSIDL_DESKTOP);
CComVariant vtEmpty;
long lhwnd;
CComPtr<IDispatch> spdisp;
result = spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
if (result != S_OK || spdisp == nullptr)
{
Logger::warn(L"Failed to find the window. {}", GetErrorString(result));
return false;
}
CComPtr<IShellBrowser> spBrowser;
result = CComQIPtr<IServiceProvider>(spdisp)->QueryService(SID_STopLevelBrowser,
IID_PPV_ARGS(&spBrowser));
if (result != S_OK || spBrowser == nullptr)
{
Logger::warn(L"Failed to query service. {}", GetErrorString(result));
return false;
}
CComPtr<IShellView> spView;
result = spBrowser->QueryActiveShellView(&spView);
if (result != S_OK || spView == nullptr)
{
Logger::warn(L"Failed to query active shell window. {}", GetErrorString(result));
return false;
}
result = spView->QueryInterface(riid, ppv);
if (result != S_OK || ppv == nullptr || *ppv == nullptr)
{
Logger::warn(L"Failed to query interface. {}", GetErrorString(result));
return false;
}
return true;
}
inline bool GetDesktopAutomationObject(REFIID riid, void** ppv)
{
CComPtr<IShellView> spsv;
// Desktop may not be available on startup
auto attempts = 5;
for (auto i = 1; i <= attempts; i++)
{
if (FindDesktopFolderView(IID_PPV_ARGS(&spsv)))
{
break;
}
Logger::warn(L"FindDesktopFolderView() failed attempt {}", i);
if (i == attempts)
{
Logger::warn(L"FindDesktopFolderView() max attempts reached");
return false;
}
Sleep(3000);
}
CComPtr<IDispatch> spdispView;
auto result = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
if (result != S_OK)
{
Logger::warn(L"GetItemObject() failed. {}", GetErrorString(result));
return false;
}
result = spdispView->QueryInterface(riid, ppv);
if (result != S_OK)
{
Logger::warn(L"QueryInterface() failed. {}", GetErrorString(result));
return false;
}
return true;
}
inline bool ShellExecuteFromExplorer(
PCWSTR pszFile,
PCWSTR pszParameters = nullptr,
PCWSTR workingDir = L"")
{
CComPtr<IShellFolderViewDual> spFolderView;
if (!GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView)))
{
return false;
}
CComPtr<IDispatch> spdispShell;
auto result = spFolderView->get_Application(&spdispShell);
if (result != S_OK)
{
Logger::warn(L"get_Application() failed. {}", GetErrorString(result));
return false;
}
CComQIPtr<IShellDispatch2>(spdispShell)
->ShellExecuteW(CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""),
CComVariant(workingDir),
CComVariant(L""),
CComVariant(SW_SHOWNORMAL));
return true;
}
}
// Returns true if the current process is running with elevated privileges
inline bool is_process_elevated(const bool use_cached_value = true)
@@ -79,16 +207,191 @@ inline bool drop_elevated_privileges()
return result;
}
// Run command as different user, returns process handle if succeeded
HANDLE run_as_different_user(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir = nullptr, const bool showWindow = true);
// Run command as different user, returns true if succeeded
inline HANDLE run_as_different_user(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir = nullptr, const bool showWindow = true)
{
Logger::info(L"run_elevated with params={}", params);
SHELLEXECUTEINFOW exec_info = { 0 };
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
exec_info.lpVerb = L"runAsUser";
exec_info.lpFile = file.c_str();
exec_info.lpParameters = params.c_str();
exec_info.hwnd = 0;
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = workingDir;
exec_info.hInstApp = 0;
if (showWindow)
{
exec_info.nShow = SW_SHOWDEFAULT;
}
else
{
// might have limited success, but only option with ShellExecuteExW
exec_info.nShow = SW_HIDE;
}
// Run command as elevated user, returns process handle if succeeded
HANDLE run_elevated(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir = nullptr, const bool showWindow = true);
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
}
// Run command as elevated user, returns true if succeeded
inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir = nullptr, const bool showWindow = true)
{
Logger::info(L"run_elevated with params={}", params);
SHELLEXECUTEINFOW exec_info = { 0 };
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
exec_info.lpVerb = L"runas";
exec_info.lpFile = file.c_str();
exec_info.lpParameters = params.c_str();
exec_info.hwnd = 0;
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = workingDir;
exec_info.hInstApp = 0;
if (showWindow)
{
exec_info.nShow = SW_SHOWDEFAULT;
}
else
{
// might have limited success, but only option with ShellExecuteExW
exec_info.nShow = SW_HIDE;
}
BOOL result = ShellExecuteExW(&exec_info);
return result ? exec_info.hProcess : nullptr;
}
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL
bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr, const bool showWindow = true);
inline bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr, const bool showWindow = true)
{
Logger::info(L"run_non_elevated with params={}", params);
auto executable_args = L"\"" + file + L"\"";
if (!params.empty())
{
executable_args += L" " + params;
}
bool RunNonElevatedEx(const std::wstring& file, const std::wstring& params, const std::wstring& working_dir);
HWND hwnd = GetShellWindow();
if (!hwnd)
{
if (GetLastError() == ERROR_SUCCESS)
{
Logger::warn(L"GetShellWindow() returned null. Shell window is not available");
}
else
{
Logger::error(L"GetShellWindow() failed. {}", get_last_error_or_default(GetLastError()));
}
return false;
}
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
winrt::handle process{ OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid) };
if (!process)
{
Logger::error(L"OpenProcess() failed. {}", get_last_error_or_default(GetLastError()));
return false;
}
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
auto pproc_buffer = std::make_unique<char[]>(size);
auto pptal = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(pproc_buffer.get());
if (!pptal)
{
Logger::error(L"pptal failed to initialize. {}", get_last_error_or_default(GetLastError()));
return false;
}
if (!InitializeProcThreadAttributeList(pptal, 1, 0, &size))
{
Logger::error(L"InitializeProcThreadAttributeList() failed. {}", get_last_error_or_default(GetLastError()));
return false;
}
HANDLE process_handle = process.get();
if (!UpdateProcThreadAttribute(pptal,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&process_handle,
sizeof(process_handle),
nullptr,
nullptr))
{
Logger::error(L"UpdateProcThreadAttribute() failed. {}", get_last_error_or_default(GetLastError()));
return false;
}
STARTUPINFOEX siex = { 0 };
siex.lpAttributeList = pptal;
siex.StartupInfo.cb = sizeof(siex);
PROCESS_INFORMATION pi = { 0 };
auto dwCreationFlags = EXTENDED_STARTUPINFO_PRESENT;
if (!showWindow)
{
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
siex.StartupInfo.wShowWindow = SW_HIDE;
dwCreationFlags = CREATE_NO_WINDOW;
}
auto succeeded = CreateProcessW(file.c_str(),
&executable_args[0],
nullptr,
nullptr,
FALSE,
dwCreationFlags,
nullptr,
workingDir,
&siex.StartupInfo,
&pi);
if (succeeded)
{
if (pi.hProcess)
{
if (returnPid)
{
*returnPid = GetProcessId(pi.hProcess);
}
CloseHandle(pi.hProcess);
}
if (pi.hThread)
{
CloseHandle(pi.hThread);
}
}
else
{
Logger::error(L"CreateProcessW() failed. {}", get_last_error_or_default(GetLastError()));
}
return succeeded;
}
inline bool RunNonElevatedEx(const std::wstring& file, const std::wstring& params, const std::wstring& working_dir)
{
bool success = false;
HRESULT co_init = E_FAIL;
try
{
co_init = CoInitialize(nullptr);
success = ShellExecuteFromExplorer(file.c_str(), params.c_str(), working_dir.c_str());
}
catch (...)
{
}
if (SUCCEEDED(co_init))
{
CoUninitialize();
}
return success;
}
struct ProcessInfo
{
@@ -96,14 +399,172 @@ struct ProcessInfo
DWORD processID = {};
};
std::optional<ProcessInfo> RunNonElevatedFailsafe(const std::wstring& file, const std::wstring& params, const std::wstring& working_dir, DWORD handleAccess = 0);
inline std::optional<ProcessInfo> RunNonElevatedFailsafe(const std::wstring& file, const std::wstring& params, const std::wstring& working_dir, DWORD handleAccess = 0)
{
bool launched = RunNonElevatedEx(file, params, working_dir);
if (!launched)
{
Logger::warn(L"RunNonElevatedEx() failed. Trying fallback");
std::wstring action_runner_path = get_module_folderpath() + L"\\PowerToys.ActionRunner.exe";
std::wstring newParams = fmt::format(L"-run-non-elevated -target \"{}\" {}", file, params);
launched = run_non_elevated(action_runner_path, newParams, nullptr, working_dir.c_str());
if (launched)
{
Logger::trace(L"Started {}", file);
}
else
{
Logger::warn(L"Failed to start {}", file);
return std::nullopt;
}
}
auto handles = getProcessHandlesByName(std::filesystem::path{ file }.filename().wstring(), PROCESS_QUERY_INFORMATION | SYNCHRONIZE | handleAccess);
if (handles.empty())
return std::nullopt;
ProcessInfo result;
result.processID = GetProcessId(handles[0].get());
result.processHandle = std::move(handles[0]);
return result;
}
// Run command with the same elevation, returns true if succeeded
bool run_same_elevation(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr);
inline bool run_same_elevation(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr)
{
auto executable_args = L"\"" + file + L"\"";
if (!params.empty())
{
executable_args += L" " + params;
}
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
auto succeeded = CreateProcessW(file.c_str(),
&executable_args[0],
nullptr,
nullptr,
FALSE,
0,
nullptr,
workingDir,
&si,
&pi);
if (succeeded)
{
if (pi.hProcess)
{
if (returnPid)
{
*returnPid = GetProcessId(pi.hProcess);
}
CloseHandle(pi.hProcess);
}
if (pi.hThread)
{
CloseHandle(pi.hThread);
}
}
return succeeded;
}
// Returns true if the current process is running from administrator account
// The function returns true in case of error since we want to return false
// only in case of a positive verification that the user is not an admin.
bool check_user_is_admin();
inline bool check_user_is_admin()
{
auto freeMemory = [](PSID pSID, PTOKEN_GROUPS pGroupInfo) {
if (pSID)
{
FreeSid(pSID);
}
if (pGroupInfo)
{
GlobalFree(pGroupInfo);
}
};
bool IsProcessOfWindowElevated(HWND window);
HANDLE hToken;
DWORD dwSize = 0;
PTOKEN_GROUPS pGroupInfo;
SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
PSID pSID = NULL;
// Open a handle to the access token for the calling process.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
{
return true;
}
// Call GetTokenInformation to get the buffer size.
if (!GetTokenInformation(hToken, TokenGroups, NULL, dwSize, &dwSize))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
return true;
}
}
// Allocate the buffer.
pGroupInfo = static_cast<PTOKEN_GROUPS>(GlobalAlloc(GPTR, dwSize));
// Call GetTokenInformation again to get the group information.
if (!GetTokenInformation(hToken, TokenGroups, pGroupInfo, dwSize, &dwSize))
{
freeMemory(pSID, pGroupInfo);
return true;
}
// Create a SID for the BUILTIN\Administrators group.
if (!AllocateAndInitializeSid(&SIDAuth, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pSID))
{
freeMemory(pSID, pGroupInfo);
return true;
}
// Loop through the group SIDs looking for the administrator SID.
for (DWORD i = 0; i < pGroupInfo->GroupCount; ++i)
{
if (EqualSid(pSID, pGroupInfo->Groups[i].Sid))
{
freeMemory(pSID, pGroupInfo);
return true;
}
}
freeMemory(pSID, pGroupInfo);
return false;
}
inline bool IsProcessOfWindowElevated(HWND window)
{
DWORD pid = 0;
GetWindowThreadProcessId(window, &pid);
if (!pid)
{
return false;
}
wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
pid) };
wil::unique_handle token;
if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
{
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
{
return elevation.TokenIsElevated != 0;
}
}
return false;
}

View File

@@ -1,101 +0,0 @@
#include "pch.h"
#include "exec.h"
#pragma warning(push)
#pragma warning(disable : 26471 26492 26493 26497)
#include <wil/resource.h>
#pragma warning(pop)
#include <array>
std::optional<std::string> exec_and_read_output(const std::wstring_view command, DWORD timeout_ms)
{
SECURITY_ATTRIBUTES saAttr{ sizeof(saAttr) };
saAttr.bInheritHandle = false;
constexpr size_t bufferSize = 4096;
// We must use a named pipe for async I/O
char pipename[MAX_PATH + 1];
if (!GetTempFileNameA(R"(\\.\pipe\)", "tmp", 1, pipename))
{
return std::nullopt;
}
wil::unique_handle readPipe{ CreateNamedPipeA(pipename, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &saAttr) };
saAttr.bInheritHandle = true;
wil::unique_handle writePipe{ CreateFileA(pipename, GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
if (!readPipe || !writePipe)
{
return std::nullopt;
}
PROCESS_INFORMATION piProcInfo{};
STARTUPINFOW siStartInfo{ sizeof(siStartInfo) };
siStartInfo.hStdError = writePipe.get();
siStartInfo.hStdOutput = writePipe.get();
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow = SW_HIDE;
std::wstring cmdLine{ command };
if (!CreateProcessW(nullptr,
cmdLine.data(),
nullptr,
nullptr,
true,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
nullptr,
nullptr,
&siStartInfo,
&piProcInfo))
{
return std::nullopt;
}
// Child process inherited the write end of the pipe, we can close it now
writePipe.reset();
auto closeProcessHandles = wil::scope_exit([&] {
CloseHandle(piProcInfo.hThread);
CloseHandle(piProcInfo.hProcess);
});
std::string childOutput;
bool processExited = false;
for (;;)
{
char buffer[bufferSize];
DWORD gotBytes = 0;
wil::unique_handle IOEvent{ CreateEventW(nullptr, true, false, nullptr) };
OVERLAPPED overlapped{ .hEvent = IOEvent.get() };
ReadFile(readPipe.get(), buffer, sizeof(buffer), nullptr, &overlapped);
const std::array<HANDLE, 2> handlesToWait = { overlapped.hEvent, piProcInfo.hProcess };
switch (WaitForMultipleObjects(1 + !processExited, handlesToWait.data(), false, timeout_ms))
{
case WAIT_OBJECT_0 + 1:
if (!processExited)
{
// When the process exits, we can reduce timeout and read the rest of the output w/o possibly big timeout
timeout_ms = 1000;
processExited = true;
closeProcessHandles.reset();
}
[[fallthrough]];
case WAIT_OBJECT_0:
if (GetOverlappedResultEx(readPipe.get(), &overlapped, &gotBytes, timeout_ms, true))
{
childOutput += std::string_view{ buffer, gotBytes };
break;
}
// Timeout
[[fallthrough]];
default:
goto exit;
}
}
exit:
CancelIo(readPipe.get());
return childOutput;
}

View File

@@ -3,8 +3,106 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// disable warning 26471 - Don't use reinterpret_cast. A cast from void* can use static_cast
// disable warning 26492 - Don't use const_cast to cast away const
// disable warning 26493 - Don't use C-style casts
// Disable 26497 for winrt - This function function-name could be marked constexpr if compile-time evaluation is desired.
#pragma warning(push)
#pragma warning(disable : 26471 26492 26493 26497)
#include <wil/resource.h>
#pragma warning(pop)
#include <optional>
#include <string>
// Implementation in exec.cpp
std::optional<std::string> exec_and_read_output(const std::wstring_view command, DWORD timeout_ms = 30000);
inline std::optional<std::string> exec_and_read_output(const std::wstring_view command, DWORD timeout_ms = 30000)
{
SECURITY_ATTRIBUTES saAttr{ sizeof(saAttr) };
saAttr.bInheritHandle = false;
constexpr size_t bufferSize = 4096;
// We must use a named pipe for async I/O
char pipename[MAX_PATH + 1];
if (!GetTempFileNameA(R"(\\.\pipe\)", "tmp", 1, pipename))
{
return std::nullopt;
}
wil::unique_handle readPipe{ CreateNamedPipeA(pipename, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &saAttr) };
saAttr.bInheritHandle = true;
wil::unique_handle writePipe{ CreateFileA(pipename, GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
if (!readPipe || !writePipe)
{
return std::nullopt;
}
PROCESS_INFORMATION piProcInfo{};
STARTUPINFOW siStartInfo{ sizeof(siStartInfo) };
siStartInfo.hStdError = writePipe.get();
siStartInfo.hStdOutput = writePipe.get();
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow = SW_HIDE;
std::wstring cmdLine{ command };
if (!CreateProcessW(nullptr,
cmdLine.data(),
nullptr,
nullptr,
true,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
nullptr,
nullptr,
&siStartInfo,
&piProcInfo))
{
return std::nullopt;
}
// Child process inherited the write end of the pipe, we can close it now
writePipe.reset();
auto closeProcessHandles = wil::scope_exit([&] {
CloseHandle(piProcInfo.hThread);
CloseHandle(piProcInfo.hProcess);
});
std::string childOutput;
bool processExited = false;
for (;;)
{
char buffer[bufferSize];
DWORD gotBytes = 0;
wil::unique_handle IOEvent{ CreateEventW(nullptr, true, false, nullptr) };
OVERLAPPED overlapped{ .hEvent = IOEvent.get() };
ReadFile(readPipe.get(), buffer, sizeof(buffer), nullptr, &overlapped);
const std::array<HANDLE, 2> handlesToWait = { overlapped.hEvent, piProcInfo.hProcess };
switch (WaitForMultipleObjects(1 + !processExited, handlesToWait.data(), false, timeout_ms))
{
case WAIT_OBJECT_0 + 1:
if (!processExited)
{
// When the process exits, we can reduce timeout and read the rest of the output w/o possibly big timeout
timeout_ms = 1000;
processExited = true;
closeProcessHandles.reset();
}
[[fallthrough]];
case WAIT_OBJECT_0:
if (GetOverlappedResultEx(readPipe.get(), &overlapped, &gotBytes, timeout_ms, true))
{
childOutput += std::string_view{ buffer, gotBytes };
break;
}
// Timeout
[[fallthrough]];
default:
goto exit;
}
}
exit:
CancelIo(readPipe.get());
return childOutput;
}

View File

@@ -1,176 +0,0 @@
#include "pch.h"
#include "gpo.h"
namespace powertoys_gpo
{
std::optional<std::wstring> readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name, const bool is_multi_line_text)
{
// Set value type
DWORD reg_value_type = REG_SZ;
DWORD reg_flags = RRF_RT_REG_SZ;
if (is_multi_line_text)
{
reg_value_type = REG_MULTI_SZ;
reg_flags = RRF_RT_REG_MULTI_SZ;
}
DWORD string_buffer_capacity;
// Request required buffer capacity / string length
if (RegGetValueW(hRootKey, subKey.c_str(), value_name.c_str(), reg_flags, &reg_value_type, NULL, &string_buffer_capacity) != ERROR_SUCCESS)
{
return std::nullopt;
}
else if (string_buffer_capacity == 0)
{
return std::nullopt;
}
// RegGetValueW overshoots sometimes. Use a buffer first to not have characters past the string end.
wchar_t* temp_buffer = new wchar_t[string_buffer_capacity / sizeof(wchar_t) + 1];
// Read string
if (RegGetValueW(hRootKey, subKey.c_str(), value_name.c_str(), reg_flags, &reg_value_type, temp_buffer, &string_buffer_capacity) != ERROR_SUCCESS)
{
delete[] temp_buffer;
return std::nullopt;
}
// Convert buffer to std::wstring
std::wstring string_value = L"";
if (reg_value_type == REG_MULTI_SZ)
{
// If it is REG_MULTI_SZ handle this way
wchar_t* currentString = temp_buffer;
while (*currentString != L'\0')
{
// If first entry then assign the string, else add to the string
string_value = (string_value == L"") ? currentString : (string_value + L"\r\n" + currentString);
currentString += wcslen(currentString) + 1; // Move to the next string
}
}
else
{
// If it is REG_SZ handle this way
string_value = temp_buffer;
}
// delete buffer, return string value
delete[] temp_buffer;
return string_value;
}
gpo_rule_configured_t getConfiguredValue(const std::wstring& registry_value_name)
{
HKEY key{};
DWORD value = 0xFFFFFFFE;
DWORD valueSize = sizeof(value);
bool machine_key_found = true;
if (auto res = RegOpenKeyExW(POLICIES_SCOPE_MACHINE, POLICIES_PATH.c_str(), 0, KEY_READ, &key); res != ERROR_SUCCESS)
{
machine_key_found = false;
}
if (machine_key_found)
{
// If the path was found in the machine, we need to check if the value for the policy exists.
auto res = RegQueryValueExW(key, registry_value_name.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &valueSize);
RegCloseKey(key);
if (res != ERROR_SUCCESS)
{
// Value not found on the path.
machine_key_found = false;
}
}
if (!machine_key_found)
{
// If there's no value found on the machine scope, try to get it from the user scope.
if (auto res = RegOpenKeyExW(POLICIES_SCOPE_USER, POLICIES_PATH.c_str(), 0, KEY_READ, &key); res != ERROR_SUCCESS)
{
if (res == ERROR_FILE_NOT_FOUND)
{
return gpo_rule_configured_not_configured;
}
return gpo_rule_configured_unavailable;
}
auto res = RegQueryValueExW(key, registry_value_name.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &valueSize);
RegCloseKey(key);
if (res != ERROR_SUCCESS)
{
return gpo_rule_configured_not_configured;
}
}
switch (value)
{
case 0:
return gpo_rule_configured_disabled;
case 1:
return gpo_rule_configured_enabled;
default:
return gpo_rule_configured_wrong_value;
}
}
std::optional<std::wstring> getPolicyListValue(const std::wstring& registry_list_path, const std::wstring& registry_list_value_name)
{
// This function returns the value of an entry of a policy list. The user scope is only checked, if the list is not enabled for the machine to not mix the lists.
HKEY key{};
// Try to read from the machine list.
bool machine_list_found = false;
if (RegOpenKeyExW(POLICIES_SCOPE_MACHINE, registry_list_path.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS)
{
machine_list_found = true;
RegCloseKey(key);
// If the path exists in the machine registry, we try to read the value.
auto regValueData = readRegistryStringValue(POLICIES_SCOPE_MACHINE, registry_list_path, registry_list_value_name);
if (regValueData.has_value())
{
// Return the value from the machine list.
return *regValueData;
}
}
// If no list exists for machine, we try to read from the user list.
if (!machine_list_found)
{
if (RegOpenKeyExW(POLICIES_SCOPE_USER, registry_list_path.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS)
{
RegCloseKey(key);
// If the path exists in the user registry, we try to read the value.
auto regValueData = readRegistryStringValue(POLICIES_SCOPE_USER, registry_list_path, registry_list_value_name);
if (regValueData.has_value())
{
// Return the value from the user list.
return *regValueData;
}
}
}
// No list exists for machine and user, or no value was found in the list, or an error ocurred while reading the value.
return std::nullopt;
}
gpo_rule_configured_t getUtilityEnabledValue(const std::wstring& utility_name)
{
auto individual_value = getConfiguredValue(utility_name);
if (individual_value == gpo_rule_configured_disabled || individual_value == gpo_rule_configured_enabled)
{
return individual_value;
}
else
{
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_GLOBAL_ALL_UTILITIES);
}
}
}

View File

@@ -32,7 +32,6 @@ namespace powertoys_gpo
const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker";
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
const std::wstring POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH = L"ConfigureEnabledUtilityLightSwitch";
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_DISPLAY = L"ConfigureEnabledUtilityPowerDisplay";
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
@@ -104,12 +103,179 @@ namespace powertoys_gpo
const std::wstring POLICY_NEW_PLUS_HIDE_TEMPLATE_FILENAME_EXTENSION = L"NewPlusHideTemplateFilenameExtension";
const std::wstring POLICY_NEW_PLUS_REPLACE_VARIABLES = L"NewPlusReplaceVariablesInTemplateFilenames";
// Methods used for reading the registry - declarations
// Implementations are in gpo.cpp
std::optional<std::wstring> readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name, const bool is_multi_line_text = false);
gpo_rule_configured_t getConfiguredValue(const std::wstring& registry_value_name);
std::optional<std::wstring> getPolicyListValue(const std::wstring& registry_list_path, const std::wstring& registry_list_value_name);
gpo_rule_configured_t getUtilityEnabledValue(const std::wstring& utility_name);
// Methods used for reading the registry
#pragma region ReadRegistryMethods
inline std::optional<std::wstring> readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name, const bool is_multi_line_text = false)
{
// Set value type
DWORD reg_value_type = REG_SZ;
DWORD reg_flags = RRF_RT_REG_SZ;
if (is_multi_line_text)
{
reg_value_type = REG_MULTI_SZ;
reg_flags = RRF_RT_REG_MULTI_SZ;
}
DWORD string_buffer_capacity;
// Request required buffer capacity / string length
if (RegGetValueW(hRootKey, subKey.c_str(), value_name.c_str(), reg_flags, &reg_value_type, NULL, &string_buffer_capacity) != ERROR_SUCCESS)
{
return std::nullopt;
}
else if (string_buffer_capacity == 0)
{
return std::nullopt;
}
// RegGetValueW overshoots sometimes. Use a buffer first to not have characters past the string end.
wchar_t* temp_buffer = new wchar_t[string_buffer_capacity / sizeof(wchar_t) + 1];
// Read string
if (RegGetValueW(hRootKey, subKey.c_str(), value_name.c_str(), reg_flags, &reg_value_type, temp_buffer, &string_buffer_capacity) != ERROR_SUCCESS)
{
delete temp_buffer;
return std::nullopt;
}
// Convert buffer to std::wstring
std::wstring string_value = L"";
if (reg_value_type == REG_MULTI_SZ)
{
// If it is REG_MULTI_SZ handle this way
wchar_t* currentString = temp_buffer;
while (*currentString != L'\0')
{
// If first entry then assign the string, else add to the string
string_value = (string_value == L"") ? currentString : (string_value + L"\r\n" + currentString);
currentString += wcslen(currentString) + 1; // Move to the next string
}
}
else
{
// If it is REG_SZ handle this way
string_value = temp_buffer;
}
// delete buffer, return string value
delete temp_buffer;
return string_value;
}
inline gpo_rule_configured_t getConfiguredValue(const std::wstring& registry_value_name)
{
HKEY key{};
DWORD value = 0xFFFFFFFE;
DWORD valueSize = sizeof(value);
bool machine_key_found = true;
if (auto res = RegOpenKeyExW(POLICIES_SCOPE_MACHINE, POLICIES_PATH.c_str(), 0, KEY_READ, &key); res != ERROR_SUCCESS)
{
machine_key_found = false;
}
if (machine_key_found)
{
// If the path was found in the machine, we need to check if the value for the policy exists.
auto res = RegQueryValueExW(key, registry_value_name.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &valueSize);
RegCloseKey(key);
if (res != ERROR_SUCCESS)
{
// Value not found on the path.
machine_key_found = false;
}
}
if (!machine_key_found)
{
// If there's no value found on the machine scope, try to get it from the user scope.
if (auto res = RegOpenKeyExW(POLICIES_SCOPE_USER, POLICIES_PATH.c_str(), 0, KEY_READ, &key); res != ERROR_SUCCESS)
{
if (res == ERROR_FILE_NOT_FOUND)
{
return gpo_rule_configured_not_configured;
}
return gpo_rule_configured_unavailable;
}
auto res = RegQueryValueExW(key, registry_value_name.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&value), &valueSize);
RegCloseKey(key);
if (res != ERROR_SUCCESS)
{
return gpo_rule_configured_not_configured;
}
}
switch (value)
{
case 0:
return gpo_rule_configured_disabled;
case 1:
return gpo_rule_configured_enabled;
default:
return gpo_rule_configured_wrong_value;
}
}
inline std::optional<std::wstring> getPolicyListValue(const std::wstring& registry_list_path, const std::wstring& registry_list_value_name)
{
// This function returns the value of an entry of a policy list. The user scope is only checked, if the list is not enabled for the machine to not mix the lists.
HKEY key{};
// Try to read from the machine list.
bool machine_list_found = false;
if (RegOpenKeyExW(POLICIES_SCOPE_MACHINE, registry_list_path.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS)
{
machine_list_found = true;
RegCloseKey(key);
// If the path exists in the machine registry, we try to read the value.
auto regValueData = readRegistryStringValue(POLICIES_SCOPE_MACHINE, registry_list_path, registry_list_value_name);
if (regValueData.has_value())
{
// Return the value from the machine list.
return *regValueData;
}
}
// If no list exists for machine, we try to read from the user list.
if (!machine_list_found)
{
if (RegOpenKeyExW(POLICIES_SCOPE_USER, registry_list_path.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS)
{
RegCloseKey(key);
// If the path exists in the user registry, we try to read the value.
auto regValueData = readRegistryStringValue(POLICIES_SCOPE_USER, registry_list_path, registry_list_value_name);
if (regValueData.has_value())
{
// Return the value from the user list.
return *regValueData;
}
}
}
// No list exists for machine and user, or no value was found in the list, or an error ocurred while reading the value.
return std::nullopt;
}
inline gpo_rule_configured_t getUtilityEnabledValue(const std::wstring& utility_name)
{
auto individual_value = getConfiguredValue(utility_name);
if (individual_value == gpo_rule_configured_disabled || individual_value == gpo_rule_configured_enabled)
{
return individual_value;
}
else
{
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_GLOBAL_ALL_UTILITIES);
}
}
#pragma endregion ReadRegistryMethods
// Utility enabled state policies
// (Always use 'getUtilityEnabledValue()'.)
@@ -144,11 +310,6 @@ namespace powertoys_gpo
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_LIGHT_SWITCH);
}
inline gpo_rule_configured_t getConfiguredPowerDisplayEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_POWER_DISPLAY);
}
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);

View File

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

View File

@@ -1,83 +0,0 @@
#include "pch.h"
#include "modulesRegistry.h"
#include <common/utils/json.h>
#include <filesystem>
namespace fs = std::filesystem;
namespace NonLocalizable
{
const static wchar_t* MONACO_LANGUAGES_FILE_NAME = L"Assets\\Monaco\\monaco_languages.json";
const static wchar_t* ListID = L"list";
const static wchar_t* ExtensionsID = L"extensions";
}
registry::ChangeSet getMonacoPreviewHandlerChangeSet(const std::wstring installationDir, const bool perUser)
{
using namespace registry::shellex;
// Set up a list of extensions for the preview handler to take over
std::vector<std::wstring> extensions;
// Set up a list of extensions that Monaco support but the preview handler shouldn't take over
std::vector<std::wstring> ExtExclusions;
ExtExclusions.insert(ExtExclusions.end(), NonLocalizable::ExtMarkdown.begin(), NonLocalizable::ExtMarkdown.end());
ExtExclusions.insert(ExtExclusions.end(), NonLocalizable::ExtSVG.begin(), NonLocalizable::ExtSVG.end());
ExtExclusions.insert(ExtExclusions.end(), NonLocalizable::ExtNoNoNo.begin(), NonLocalizable::ExtNoNoNo.end());
bool IsExcluded = false;
std::wstring languagesFilePath = fs::path{ installationDir } / NonLocalizable::MONACO_LANGUAGES_FILE_NAME;
auto json = json::from_file(languagesFilePath);
if (json)
{
try
{
auto list = json->GetNamedArray(NonLocalizable::ListID);
for (uint32_t i = 0; i < list.Size(); ++i)
{
auto entry = list.GetObjectAt(i);
if (entry.HasKey(NonLocalizable::ExtensionsID))
{
auto extensionsList = entry.GetNamedArray(NonLocalizable::ExtensionsID);
for (uint32_t j = 0; j < extensionsList.Size(); ++j)
{
auto extension = extensionsList.GetStringAt(j);
// Ignore extensions in the exclusion list
IsExcluded = false;
for (std::wstring k : ExtExclusions)
{
if (std::wstring{ extension } == k)
{
IsExcluded = true;
break;
}
}
if (IsExcluded)
{
continue;
}
extensions.push_back(std::wstring{ extension });
}
}
}
}
catch (...)
{
}
}
return generatePreviewHandler(PreviewHandlerType::preview,
perUser,
L"{D8034CFA-F34B-41FE-AD45-62FCBB52A6DA}",
get_std_product_version(),
(fs::path{ installationDir } / LR"d(PowerToys.MonacoPreviewHandlerCpp.dll)d").wstring(),
L"MonacoPreviewHandler",
L"Monaco Preview Handler",
extensions);
}

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