Compare commits

..

118 Commits

Author SHA1 Message Date
Dustin L. Howett
b6c7ca9ccd Migrate spelling-0.0.21 changes from main 2022-08-26 23:37:00 +02:00
yuyoyuppe
232f979c30 fix race condition in system internals which happens during concurrent d3d/d2d resource creation 2022-08-27 00:34:46 +02:00
Jaime Bernardo
fe00f720cd Fix core version 2022-08-26 22:29:36 +01:00
Jaime Bernardo
6c4365851f Fix MeasureToolUI version 2022-08-26 22:24:14 +01:00
Jaime Bernardo
b7d1986be5 Fix signing 2022-08-26 22:22:20 +01:00
yuyoyuppe
9178285a9c fix spelling 2022-08-26 23:02:35 +02:00
yuyoyuppe
5df9f04dbd cleanup cursor convertion between coordinate spaces 2022-08-26 22:35:42 +02:00
yuyoyuppe
4daf5714dc fix arm ci 2022-08-26 20:35:28 +02:00
yuyoyuppe
f869768b0b CI fix 2022-08-26 19:26:42 +02:00
yuyoyuppe
8061e1a527 fix ci 2022-08-26 19:16:11 +02:00
yuyoyuppe
61fd8cc8f5 fix 2 2022-08-26 18:57:03 +02:00
yuyoyuppe
9654daa62e fix ci again 2022-08-26 18:51:43 +02:00
yuyoyuppe
c25c7a97b8 fix CI 2022-08-26 18:44:47 +02:00
yuyoyuppe
56a5d783c8 revert taskbar covering 2022-08-26 18:32:29 +02:00
yuyoyuppe
c4f49a0fc9 Merge branch 'main' into measure_tool
# Conflicts:
#	.github/actions/spell-check/expect.txt
#	installer/PowerToysSetup/Product.wxs
#	src/runner/main.cpp
#	src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
#	src/settings-ui/Settings.UI/Views/ShellPage.xaml
2022-08-26 18:21:24 +02:00
yuyoyuppe
1f3dcb3ea6 fix CI 2022-08-26 17:58:18 +02:00
yuyoyuppe
e46e645d3d overlay window covers taskbar 2022-08-26 17:58:12 +02:00
yuyoyuppe
82d265ca80 fix spelling 2022-08-26 10:59:12 +02:00
Andrey Nekrasov
f08900925a make backround convertion success for non continuous mode non-essential 2022-08-26 03:56:37 +02:00
yuyoyuppe
0669b5cbba fix off by 1 2022-08-26 00:11:53 +02:00
yuyoyuppe
562f8a5f37 add comment 2022-08-25 20:02:30 +02:00
yuyoyuppe
7e18881967 dpi from rt for custom renderer 2022-08-25 19:50:48 +02:00
yuyoyuppe
10728e4e66 primary monitor debug also active for the bounds tool 2022-08-25 19:43:48 +02:00
Andrey Nekrasov
5268411ace fix comment 2022-08-25 18:17:48 +02:00
yuyoyuppe
edd80fc01e spelling 2022-08-25 17:33:40 +02:00
yuyoyuppe
f1f95fe151 split global/perScreen measure state and minor improvements 2022-08-25 17:30:59 +02:00
yuyoyuppe
07fb01e178 disable stroboscopic effect 2022-08-25 15:38:15 +02:00
yuyoyuppe
44f7065b9e merge fix 2022-08-25 15:38:06 +02:00
yuyoyuppe
cf67208871 cast a spell... 2022-08-25 15:24:17 +02:00
yuyoyuppe
0dde07cc23 draw captured screen in a non continuous mode 2022-08-25 15:18:57 +02:00
yuyoyuppe
5c71808af1 build fix 2022-08-25 13:31:27 +02:00
yuyoyuppe
b0606dde9b Merge branch 'main' into measure_tool
# Conflicts:
#	installer/PowerToysSetup/Product.wxs
#	src/runner/main.cpp
#	src/settings-ui/Settings.UI.Library/EnabledModules.cs
#	src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
#	tools/BugReportTool/BugReportTool/ProcessesList.cpp
2022-08-25 13:22:02 +02:00
yuyoyuppe
fe3e7e14d9 ContinuousCapture is enabled by default to avoid confusion 2022-08-25 13:11:49 +02:00
yuyoyuppe
461e3c6a21 optimize 2022-08-25 13:10:12 +02:00
yuyoyuppe
8431164bc6 improve sum of all channel diffs method score calc 2022-08-25 12:58:46 +02:00
yuyoyuppe
0ee620988e fix saveasbitmap 2022-08-25 12:38:28 +02:00
yuyoyuppe
12c11908c1 fix texture access 2022-08-25 12:31:27 +02:00
yuyoyuppe
98a870db09 debug texture 2022-08-25 12:19:35 +02:00
yuyoyuppe
d93991a914 clean up x2 2022-08-25 12:04:24 +02:00
yuyoyuppe
798c3eaf6f cleanup 2022-08-25 11:24:20 +02:00
Andrey Nekrasov
4405e4bfb6 fix a real spelling error! 2022-08-25 00:44:44 +02:00
Andrey Nekrasov
56ee3fc558 clarify continuous capture description 2022-08-25 00:42:25 +02:00
Andrey Nekrasov
8c75a71e70 explicitly set texture dims 2022-08-25 00:27:05 +02:00
yuyoyuppe
39fe726690 add PowerToys.MeasureToolUI.exe to bugreport 2022-08-24 20:51:43 +02:00
yuyoyuppe
c3da4a923a increase default pixel tolerance from 5 to 30 2022-08-24 20:39:03 +02:00
Andrey Nekrasov
efe712f969 localization for AutomationPeer 2022-08-24 17:44:23 +02:00
Jaime Bernardo
98eba357e1 Merge branch 'main' into pr19701 2022-08-24 15:49:36 +01:00
yuyoyuppe
b91780b903 appropriate module preview image 2022-08-24 15:31:21 +02:00
yuyoyuppe
614bf7fee3 move license to NOTICE 2022-08-24 15:04:33 +02:00
yuyoyuppe
a69a450899 update OOBE 2022-08-24 14:33:39 +02:00
yuyoyuppe
db2e1d5985 add sse2neon license 2022-08-24 14:27:15 +02:00
Andrey Nekrasov
58f49e1861 pass dpiScale to x renderer 2022-08-24 01:04:37 +02:00
Andrey Nekrasov
de1d18f8d9 Merge branch 'main' into measure_tool
# Conflicts:
#	PowerToys.sln
#	src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
2022-08-24 00:34:52 +02:00
Andrey Nekrasov
45f4a83dab spelling 2022-08-24 00:32:39 +02:00
Andrey Nekrasov
9128ca63c9 implement custom renderer and make × semiopaque 2022-08-24 00:28:20 +02:00
Andrey Nekrasov
78d4f66669 update issue template + refactor text box drawing 2022-08-23 20:45:25 +02:00
yuyoyuppe
48a79819f3 spell-checked 2022-08-23 16:40:30 +02:00
yuyoyuppe
bde578522e move #define DEBUG_OVERLAY in an appropriate space 2022-08-23 16:37:34 +02:00
yuyoyuppe
5d39f44233 allow multiple measurements with bounds tool with shift-click 2022-08-23 16:35:06 +02:00
yuyoyuppe
ddf7b5a7ba use × instead of x 2022-08-23 15:02:57 +02:00
yuyoyuppe
64bf10e74e update assets and try to fix build 2022-08-23 14:03:22 +02:00
yuyoyuppe
d39c129bd0 Merge branch 'main' into measure_tool 2022-08-23 13:26:34 +02:00
yuyoyuppe
bc0e83b59a tool activation telemetry 2022-08-23 13:25:54 +02:00
yuyoyuppe
714626aca8 update installer deps + spelling 2022-08-23 02:43:22 +02:00
yuyoyuppe
a17425b0f7 fix per channel distance calculations 2022-08-23 01:30:56 +02:00
yuyoyuppe
88900a55d6 spelling 2022-08-23 00:00:01 +02:00
yuyoyuppe
87ec6254ad add mouse wheel to adjust pixel tolerance + per channel detection algorithm setting 2022-08-22 23:54:59 +02:00
Andrey Nekrasov
7160eb3a1e dynamic text box size based on text layout metrics 2022-08-22 15:28:30 +02:00
Andrey Nekrasov
5d8856330b fix debug mode 2022-08-21 01:24:06 +02:00
Andrey Nekrasov
3414d6d2c4 spell 2022-08-20 02:30:26 +02:00
Andrey Nekrasov
6b7a0698f5 add shadow effect for textbox 2022-08-20 02:15:03 +02:00
Andrey Nekrasov
89e6849e60 ScreenSize -> Box 2022-08-20 00:59:34 +02:00
Andrey Nekrasov
e18c435bc6 fix powertoys! 2022-08-20 00:14:04 +02:00
yuyoyuppe
b231d89171 spell 2022-08-19 23:24:47 +02:00
yuyoyuppe
ce501489b4 multimonitor support: track cursor position 2022-08-19 23:21:20 +02:00
yuyoyuppe
54ad5ff41f multimonitor: launch tools on all monitors 2022-08-19 20:36:39 +02:00
yuyoyuppe
e116358723 proper thread termination + minor fixes 2022-08-19 17:12:04 +02:00
Andrey Nekrasov
39c5177c43 spelling 2022-08-18 23:36:55 +02:00
Andrey Nekrasov
1ea75e9421 Merge branch 'main' into measure_tool 2022-08-18 23:31:58 +02:00
Andrey Nekrasov
4c26699ad6 prepare for merge 2022-08-18 23:30:31 +02:00
Andrey Nekrasov
123de4fbc0 multimonitor preparation: eliminate global state 2022-08-18 23:22:21 +02:00
Andrey Nekrasov
0ddd3e8395 simplify state structs 2022-08-18 18:18:17 +02:00
Andrey Nekrasov
66870a61ec refactor overlay ui even more 2022-08-18 17:20:53 +02:00
Andrey Nekrasov
aff1020ab0 refactor edge detection & overlay ui 2022-08-18 03:34:44 +02:00
yuyoyuppe
da3626cb51 refactor colors 2022-08-17 22:47:40 +02:00
yuyoyuppe
4332571aa3 always open toolbar on the main display 2022-08-16 23:07:20 +02:00
yuyoyuppe
c6018eb87c toolbar is interactive during measurements 2022-08-16 22:44:36 +02:00
yuyoyuppe
094eeee194 add proj ref 2022-08-16 01:11:32 +02:00
yuyoyuppe
0f7100508f track runner process and exit when it exits 2022-08-16 01:09:59 +02:00
yuyoyuppe
434a9023c3 activation hotkey toggles UI instead of just launching it 2022-08-16 00:49:22 +02:00
yuyoyuppe
7054d4c183 Uncheck active tool's RadioButton when it exits 2022-08-16 00:44:33 +02:00
yuyoyuppe
8a89a567d1 Swap LMB/RMB for interaction 2022-08-15 23:22:38 +02:00
Jaime Bernardo
172d57c69a Add setting for turning cross feet on 2022-08-12 12:15:29 +01:00
Jaime Bernardo
c86903f7c6 Add less precise drawing on continuous warning 2022-08-12 12:04:19 +01:00
Jaime Bernardo
6d66921720 Merge branch 'main' into measure_tool 2022-08-12 11:49:40 +01:00
Jaime Bernardo
d6769f52cf Fix spellchecker 2022-08-12 11:48:00 +01:00
Jaime Bernardo
ac9425ee81 Merge remote-tracking branch 'andrey/measure_tool' into pr19701 2022-08-12 11:44:38 +01:00
Jaime Bernardo
fceeb1b51f Improve measurement accuracy and display 2022-08-12 11:41:54 +01:00
Niels Laute
a47d49d5c2 Icon updates 2022-08-12 10:17:03 +02:00
Niels Laute
a32b7361c8 Updated icons 2022-08-12 10:12:31 +02:00
Jaime Bernardo
9e4dcf9d36 Merge branch 'main' into pr19701 2022-08-11 16:18:06 +01:00
Jaime Bernardo
c218adf3fd remove comment for spell check 2022-08-10 16:40:58 +01:00
Jaime Bernardo
9b9cdabf4e Restore antialiasing to draw the tooltip 2022-08-10 16:39:56 +01:00
Niels Laute
a85fdd7b72 Merge branch 'measure_tool' of https://github.com/yuyoyuppe/PowerToys into measure_tool 2022-08-10 17:18:00 +02:00
Niels Laute
9258353f6c Tooltip updates 2022-08-10 17:17:54 +02:00
Jaime Bernardo
ea850fff75 Use pixel tolerance from settings 2022-08-10 13:51:34 +01:00
Jaime Bernardo
60451a3e19 Remove anti-aliasing, as it's creating artifacts 2022-08-10 13:08:53 +01:00
Jaime Bernardo
183300cb8b Add feet to crosses 2022-08-10 13:07:35 +01:00
Niels Laute
5033fd3db1 UI fixes 2022-08-10 13:36:12 +02:00
Jaime Bernardo
5443c07468 Fix measurement being off by 1 on both ends 2022-08-05 12:08:08 +01:00
Jaime Bernardo
b2bcbcc6af more spellcheck errors 2022-08-03 15:53:55 +01:00
Jaime Bernardo
59c0db2c39 another spellcheck fix 2022-08-03 15:34:07 +01:00
Jaime Bernardo
6564a4f427 fix spellchecker 2022-08-03 15:31:13 +01:00
Jaime Bernardo
cd10bf3ae6 Update vsconfig for needed Windows 10 SDK versions 2022-08-03 15:05:16 +01:00
Jaime Bernardo
02f43b9cbb Fix build errors 2022-08-03 15:04:55 +01:00
Andrey Nekrasov
00dfe5427f [MeasureTool] initial implementation 2022-08-02 03:22:55 +02:00
Andrey Nekrasov
58905ab301 [chore] clean up needless WindowsTargetPlatformVersion overrides from projects 2022-08-01 07:09:54 +02:00
Andrey Nekrasov
a249d0f99b [MeasureTool] initial commit 2022-08-01 07:09:54 +02:00
204 changed files with 2022 additions and 3113 deletions

View File

@@ -46,16 +46,15 @@ body:
- PDF Thumbnail
- G-code Preview
- G-code Thumbnail
- PowerAccent
- PowerRename
- PowerToys Run
- Quick Accent
- Screen ruler
- Shortcut Guide
- STL Thumbnail
- SVG Preview
- SVG Thumbnail
- Settings
- TextExtractor
- Video Conference Mute
- Welcome / PowerToys Tour window
- System tray interaction

View File

@@ -37,7 +37,7 @@ body:
- PDF Thumbnail
- G-code Preview
- G-code Thumbnail
- Quick Accent
- PowerAccent
- PowerRename
- PowerToys Run
- Screen Ruler

View File

@@ -117,7 +117,6 @@
"modules\\PowerAccent\\PowerToys.PowerAccent.dll",
"modules\\PowerAccent\\PowerToys.PowerAccent.exe",
"modules\\PowerAccent\\PowerToys.PowerAccentModuleInterface.dll",
"modules\\PowerAccent\\PowerToys.PowerAccentKeyboardService.dll",
"modules\\PowerRename\\PowerToys.PowerRenameExt.dll",
"modules\\PowerRename\\PowerToys.PowerRename.exe",

View File

@@ -12,9 +12,6 @@ Aaron has helped triaging, discussing, and creating a substantial number of issu
### [@CleanCodeDeveloper](https://github.com/CleanCodeDeveloper)
CleanCodeDeveloper helped do massive amounts of code stability and image resizer work.
### [@damienleroy](https://github.com/damienleroy) - [Damien Leroy](https://www.linkedin.com/in/Damien-Leroy-b2734416a/)
Damien has helped out by developing and contributing the Quick Accent utility.
### [@davidegiacometti](https://github.com/davidegiacometti) - [Davide Giacometti](https://www.linkedin.com/in/davidegiacometti/)
Davide has helped fix multiple bugs, added new features, as well as help us with the ARM64 effort by porting applications to .NET Core.
@@ -62,9 +59,6 @@ Their fork of Wox was the base of PowerToys Run.
Initial base of jjw24's fork, which makes it the base of PowerToys Run.
### [Text-Grab](https://github.com/TheJoeFin/Text-Grab) - Joseph Finney
Joe helped develop and contribute to the Text Extractor utility. It is directly based on his Text Grab application.
## Microsoft community members
We would like to also directly call out some extremely helpful Microsoft employees that have directly contributed to PowerToys. This isn't their day job and was work they did out of passion. We want to say thank you and recognize your work.

View File

@@ -32,7 +32,6 @@
<!-- C++ source compile-specific things for all configurations -->
<PropertyGroup>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
<VcpkgEnabled>false</VcpkgEnabled>
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(ExternalIncludePath)</ExternalIncludePath>
</PropertyGroup>

View File

@@ -447,8 +447,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MeasureToolModuleInterface"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeasureToolUI", "src\modules\MeasureTool\MeasureToolUI\MeasureToolUI.csproj", "{515554D1-D004-4F7F-A107-2211FC0F6B2C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerAccentKeyboardService", "src\modules\poweraccent\PowerAccentKeyboardService\PowerAccentKeyboardService.vcxproj", "{C97D9A5D-206C-454E-997E-009E227D7F02}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -1792,18 +1790,6 @@ Global
{515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x86.ActiveCfg = Release|x86
{515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x86.Build.0 = Release|x86
{515554D1-D004-4F7F-A107-2211FC0F6B2C}.Release|x86.Deploy.0 = Release|x86
{C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|ARM64.ActiveCfg = Debug|ARM64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|ARM64.Build.0 = Debug|ARM64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x64.ActiveCfg = Debug|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x64.Build.0 = Debug|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x86.ActiveCfg = Debug|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Debug|x86.Build.0 = Debug|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|ARM64.ActiveCfg = Release|ARM64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|ARM64.Build.0 = Release|ARM64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.ActiveCfg = Release|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.Build.0 = Release|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x86.ActiveCfg = Release|x64
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1955,7 +1941,6 @@ Global
{54A93AF7-60C7-4F6C-99D2-FBB1F75F853A} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{92C39820-9F84-4529-BC7D-22AAE514D63B} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{515554D1-D004-4F7F-A107-2211FC0F6B2C} = {7AC943C9-52E8-44CF-9083-744D8049667B}
{C97D9A5D-206C-454E-997E-009E227D7F02} = {0F14491C-6369-4C45-AAA8-135814E66E6B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

131
README.md
View File

@@ -20,8 +20,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) |
| [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) |
| [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) |
| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) |
| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) |
## Installing and running Microsoft PowerToys
@@ -29,7 +28,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
- Windows 11 or Windows 10 version 2004 (code name 20H1 / build number 19041) or newer.
- Our installer will install the following items:
- [.NET 6.0.8 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/6.0#runtime-desktop-6.0.8) or a newer 6.0.x runtime.
- [.NET 6.0.7 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/6.0#runtime-desktop-6.0.7) or a newer 6.0.x runtime.
- [Microsoft Edge WebView2 Runtime](https://go.microsoft.com/fwlink/p/?LinkId=2124703) bootstrapper. This will install the latest version.
- [Microsoft Visual C++ Redistributable](https://docs.microsoft.com/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022) installer. This will install one of the latest versions available.
@@ -37,8 +36,8 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
[Microsoft PowerToys GitHub releases page][github-release-link], click on `Assets` at the bottom to show the files available in the release. Please use the appropriate the PowerToys installer that matches your machine's architecture. For most people, it is `x64`.
- **For x64 processors (most common):** [PowerToysSetup-0.62.0-x64.exe](https://github.com/microsoft/PowerToys/releases/download/v0.62.0/PowerToysSetup-0.62.0-x64.exe)
- **For ARM64 processors:** [PowerToysSetup-0.62.0-arm64.exe](https://github.com/microsoft/PowerToys/releases/download/v0.62.0/PowerToysSetup-0.62.0-arm64.exe)
- **For x64 processors (most common):** [PowerToysSetup-0.61.1-x64.exe](https://github.com/microsoft/PowerToys/releases/download/v0.61.1/PowerToysSetup-0.61.1-x64.exe)
- **For ARM64 processors:** [PowerToysSetup-0.61.1-arm64.exe](https://github.com/microsoft/PowerToys/releases/download/v0.61.1/PowerToysSetup-0.61.1-arm64.exe)
This is our preferred method.
@@ -73,112 +72,94 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.62 - August 2022 Update
### 0.61 - July 2022 Update
In this release, we focused on releasing three new PowerToys.
This is a lighter release, with a shorter development cycle and focused on stability and improvements.
**Highlights**
- New utility: Screen Ruler is a quick and easy way to measure pixels on your screen.
- New utility: Quick Accent is an easy way to write letters with accents. Thanks [@damienleroy](https://github.com/damienleroy)!
- New utility: Text Extractor works like Snipping Tool, but copies the text out of the selected region using OCR and puts it on the clipboard. Thanks [@TheJoeFin](https://github.com/TheJoeFin)!
- PowerToy Run ships with a new Plugin letting you search in past query results. Thanks [@jefflord](https://github.com/jefflord)!
- Quality of life improvements for Always on Top, FancyZones and PowerToys Run.
### Known issues
- The Text Extractor utility [fails to recognize text in some cases on ARM64 devices running Windows 10](https://github.com/microsoft/PowerToys/issues/20278).
- After installing PowerToys, [the new Windows 11 context menu entries for PowerRename and Image Resizer might not appear before a system restart](https://github.com/microsoft/PowerToys/issues/19124).
- There are reports of users who are [unable to open the Settings window](https://github.com/microsoft/PowerToys/issues/18015). This is being caused by incompatibilities with some applications (RTSS RivaTuner Statistics Server and MSI AfterBurner are known examples of this). If you're affected by this, please check the linked issue to verify if any of the presented solutions works for you.
### General
- Added a new utility: Screen Ruler.
- Added a new utility: Quick Accent. Thanks [@damienleroy](https://github.com/damienleroy)!
- Added a new utility: Text Extractor. Thanks [@TheJoeFin](https://github.com/TheJoeFin)!
- Upgraded the Windows App SDK runtimes to 1.1.4.
- The new Windows 11 context menu entries are now correctly added to Windows 11 dev channel insider builds. (This was a hotfix for 0.60)
- The old context menu entries are shown alongside the new Windows 11 context menu entries to be compatible with software that overrides the Windows 11 context menu behavior. (This was a hotfix for 0.60)
- Consolidated C# language version across the solution. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Removed deprecated Segoe icon glyph codes and replaced them with the correct ones. Thanks [@niels9001](https://github.com/niels9001) and [@Jay-o-Way](https://github.com/Jay-o-Way)!
- Fixed an issue that caused a random accent key to be pressed on certain keyboard layouts when enabling some modules.
### Always on Top
- Fixed a bug causing the border to linger when closing an Outlook popup window.
### Color Picker
- Fixed the HSB color format to correctly track HSV instead of HSL.
- Fixed an issue where the zoom factor wasn't reset when reopening the zoom window. Thanks [@FWest98](https://github.com/FWest98)!
- Fixed border flickering when activating. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed a bug causing Always on Top to activate and hang when exiting PowerToys. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed black edges appearing on rounded corners.
- Fixed a bug that was causing 100% CPU consumption.
### FancyZones
- Removed the button to open Settings from the FancyZones Editor, as it was opening behind the overlay.
- Changed the Highlight distance control to a slider in the FancyZones Editor, to address accessibility issues with screen readers.
- Fixed an issue where the FancyZones Editor would duplicate or edit the wrong layout.
- Fixed an issue that caused canvas layout width/height to be changed without even opening the layout in FancyZones Editor.
- Fixed a bug that caused layouts to not be applied correctly when many monitors reported having the same serial number. (This was a hotfix for 0.60)
- Fixed a bug that caused layouts to not be applied correctly on some virtual monitor setups (This was a hotfix for 0.60)
- A "Rows" default layout is now applied to vertical monitors, instead of a "Columns" layout. Thanks [@augustkarlstedt](https://github.com/augustkarlstedt)!
### Image Resizer
- Screen reader now announces the size name instead of the class name.
### File explorer add-ons
- Quality of life improvements to Developer Files preview, including a progress bar while loading, performance improvements, an improved dark mode, and logs. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)!
- Fixed possible WebView related vulnerabilities in the SVG and Markdown handlers.
- Fixed some race conditions in Developer Files preview causing the loading bar to hang.
- Added localization support to the Developer Files preview messages.
- It's now possible to configure default color for Stl Thumbnails. Thanks [@pedrolamas](https://github.com/pedrolamas)!
- Added an option to format JSON and XML files before rendering. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed an issue when creating thumbnails for SVG files created using Inkscape.
### Keyboard Manager
- Adjusted wording on the editor when keys are orphaned.
### Mouse utility
- Fixed a bug that caused the current Find My Mouse spotlight to hang when activated in the top left corner of the screen. (This was a hotfix for 0.60)
### PowerRename
- Fixed an issue that was generating a silent crash when the context menu was triggered when not selecting any file or folder. (This was a hotfix for 0.61)
- Improved performance when loading a big number of files.
- Fixed a specific case in which PowerRename tried to rename a file to an empty string.
- The UI now shows when a file can't be renamed due to its name being too long or containing invalid characters.
- The PowerRename window reacts to current dpi when created.
### PowerToys Run
- Added a fix to the VSCodeWorkspaces plugin to better support portable installations. Thanks [@bkmeneguello](https://github.com/bkmeneguello)!
- The Folder plugin now expands `%HOMEPATH%` correctly.
- Fixed a case where a previous result was being activated when searching for new results. Added a setting to better control input throttling. Thanks [@jefflord](https://github.com/jefflord)!
- Added support for port numbers in the URI plugin. Thanks [@KohGeek](https://github.com/KohGeek)!
- Fixed query errors when the search delay option was turned off.
- New History plugin to search for old search results. Thanks [@jefflord](https://github.com/jefflord)!
- Changed the default TimeDate activation keyword to `)`, as queries starting by `(` are expected as Calculator global queries, and added information in Settings so users know that some activation keywords may conflict with normal usage of some plugins when trying to do a global query. Thanks [@htcfreek](https://github.com/htcfreek)!
- The Unit Converter plugin updated its UnitsNet dependency and now supports plural units. Thanks [@FWest98](https://github.com/FWest98)!
- Improved the validation logic in the Calculator plugin. Thanks [@htcfreek](https://github.com/htcfreek)!
### Runner
- Improved: Clean up old install folders and logs at startup. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed a typo in the WindowWalker plugin UI. Thanks [@rohanrdy](https://github.com/rohanrdy)!
- Improved performance by saving the search history files only on exit. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- PowerToys Run no longer shows results for some plugins when querying for empty spaces in a global query. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Added support for showing localized names for some win32 programs in the programs plugin. Thanks [@htcfreek](https://github.com/htcfreek)!
- The program plugin will now consider settings changed directly in ProgramPluginSettings.json. Thanks [@bezgumption](https://github.com/bezgumption)!
### Settings
- Image and phrasing adjustments.
- Icon and image updates for the new utilities. Thanks [@niels9001](https://github.com/niels9001)!
### Shortcut Guide
- Fixed the Narrator shortcut to include the newly added Control key.
- PowerToys Run settings page properly greys out the score adjustment setting when a plugin is not global. Thanks [@jefflord](https://github.com/jefflord)!
- PowerToys Run plugins score adjustment field accepts only numeric characters. Thanks [@jefflord](https://github.com/jefflord)!
- Will not run if started directly from its executable, as it was before the WinUI 3 upgrade.
- Fixed a typo in a PowerToys Run settings page description. Thanks [@eltociear](https://github.com/eltociear)!
### Installer
- Fixed a regression that was causing the PowerToys shortcut to be deleted on update. (This was a hotfix for 0.61)
- Updated the .NET dependency to 6.0.8.
### Documentation
- Fixed wrong links to installers in README. Thanks [@unuing](https://github.com/unuing)!
- Removed the dead code to make a msix installer.
- Updated the .NET dependency to 6.0.7.
- Won't create a new PowerToys shortcut on update if it's been removed manually by the user.
### Development
- Removed FXCop leftovers. Thanks [@CleanCodeDeveloper](https://github.com/CleanCodeDeveloper)!
- Added version number to missing binaries and added a CI script to verify that all binaries have their version numbers set correctly.
- Updated a dependency to fix building on Visual Studio 17.3 C++ tools.
- Fixed and reactivated the CI unit tests for FancyZones.
- Cleaned up and removed dead code from PowerRename code base.
- Added a script for verifying the solution targets match the expected CPU architectures. Thanks [@snickler](https://github.com/snickler)!
- Obsolete package Castle.Core was removed. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Language typos were corrected across the PowerToys assets. Thanks [@pea-sys](https://github.com/pea-sys), [@eltociear](https://github.com/eltociear) and [@obairka](https://github.com/obairka)!
- Updated the Windows Store Package submission script to show less UI while installing PowerToys. (This was a hotfix for 0.60)
- Added more functionality to the Monitor Report Tool.
- The release CI now includes the version number in the symbols artifacts.
- GitHub should now show .vsconfig as a JSON file. Thanks [@osfanbuff63](https://github.com/osfanbuff63)!
- Centralized the configurations for NetAnalyzers and StyleCop. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Check-spelling has been upgraded to version 0.0.20. Thanks [@jsoref](https://github.com/jsoref)!
#### What is being planned for v0.63
#### What is being planned for v0.62
For [v0.63][github-next-release-work], we'll work on below:
For [v0.62][github-next-release-work], we'll work on below:
- Environment Variables Editor PowerToy
- GPO policies for PowerToys
- Screen Measure PowerToy
- Stability / bug fixes
## PowerToys Community
@@ -206,5 +187,5 @@ The application logs basic telemetry. Our Telemetry Data page (Coming Soon) has
[usingPowerToys-docs-link]: https://aka.ms/powertoys-docs
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F36
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F35
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F35
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F34

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -6,9 +6,9 @@
<?define BinDir="$(var.RepoDir)$(var.Platform)\$(var.Configuration)\" ?>
<?define PowerToysPlatform="x64"?>
<?define Dotnet6DownloadUrl="https://download.visualstudio.microsoft.com/download/pr/b4a17a47-2fe8-498d-b817-30ad2e23f413/00020402af25ba40990c6cc3db5cb270/windowsdesktop-runtime-6.0.8-win-x64.exe"?>
<?define Dotnet6PayloadSize="57909296"?>
<?define Dotnet6PayloadHash="ABA98AAA3DB700D41EB067280F86F35B7DDEA550"?>
<?define Dotnet6DownloadUrl="https://download.visualstudio.microsoft.com/download/pr/dc0e0e83-0115-4518-8b6a-590ed594f38a/65b63e41f6a80decb37fa3c5af79a53d/windowsdesktop-runtime-6.0.7-win-x64.exe"?>
<?define Dotnet6PayloadSize="57708544"?>
<?define Dotnet6PayloadHash="16DDD34B1E1AEA0D89D3B0A8786026436CD17234"?>
<?define VCRedistDownloadUrl="https://download.visualstudio.microsoft.com/download/pr/ed95ef9e-da02-4735-9064-bd1f7f69b6ed/CE6593A1520591E7DEA2B93FD03116E3FC3B3821A0525322B0A430FAA6B3C0B4/VC_redist.x64.exe"?>
<?define VCRedistPayloadSize="25234792"?>
@@ -20,9 +20,9 @@
<?define BinDir="$(var.RepoDir)ARM64\$(var.Configuration)\" ?>
<?define PowerToysPlatform="ARM64"?>
<?define Dotnet6DownloadUrl="https://download.visualstudio.microsoft.com/download/pr/17737b16-dbb0-45f8-9684-16cce46f0835/14475e8380422840249513d58c70d8da/windowsdesktop-runtime-6.0.8-win-arm64.exe"?>
<?define Dotnet6PayloadSize="51735240"?>
<?define Dotnet6PayloadHash="AE097FD933EEF88A1F8D800961A1584CAF9DA37F"?>
<?define Dotnet6DownloadUrl="https://download.visualstudio.microsoft.com/download/pr/f33cf7ce-bf03-428c-8aa7-e32ef6d7ddc6/e61dc60fce686844c41ec2901ad5b01e/windowsdesktop-runtime-6.0.7-win-arm64.exe"?>
<?define Dotnet6PayloadSize="51539968"?>
<?define Dotnet6PayloadHash="F98232B9B572848B8425462F9458E92710BBF55F"?>
<?define VCRedistDownloadUrl="https://download.visualstudio.microsoft.com/download/pr/ed95ef9e-da02-4735-9064-bd1f7f69b6ed/8E126191012691AE22A0D5A89FAC01B59BABC7B680E5D9B65828935FD366E375/VC_redist.arm64.exe"?>
<?define VCRedistPayloadSize="11500416"?>
@@ -49,7 +49,7 @@
SuppressRepair="yes" />
</BootstrapperApplicationRef>
<util:FileSearch Variable="HasDotnet608" Path="$(var.PlatformProgramFiles)dotnet\shared\Microsoft.WindowsDesktop.App\6.0.8\System.Xaml.dll" Result="exists" />
<util:FileSearch Variable="HasDotnet607" Path="$(var.PlatformProgramFiles)dotnet\shared\Microsoft.WindowsDesktop.App\6.0.7\System.Xaml.dll" Result="exists" />
<util:RegistrySearch Variable="HasWebView2PerMachine" Root="HKLM" Key="SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Result="exists" />
<util:RegistrySearch Variable="HasWebView2PerUser" Root="HKCU" Key="Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Result="exists" />
@@ -73,20 +73,10 @@
<Chain>
<ExePackage
Name="terminate_powertoys.cmd"
Cache="no"
Compressed="yes"
Id="TerminatePowerToys"
SourceFile="terminate_powertoys.cmd"
Permanent="yes"
PerMachine="yes"
Vital="no">
</ExePackage>
<ExePackage
Name="windowsdesktop-runtime-6.0.8-win-$(var.PowerToysPlatform).exe"
Name="windowsdesktop-runtime-6.0.7-win-$(var.PowerToysPlatform).exe"
Compressed="no"
Id="DotnetRuntime6"
DetectCondition="HasDotnet608"
DetectCondition="HasDotnet607"
DownloadUrl="$(var.Dotnet6DownloadUrl)"
InstallCommand="/install /quiet /norestart"
RepairCommand="/repair /passive /norestart"
@@ -95,10 +85,10 @@
UninstallCommand="/uninstall /quiet /norestart">
<ExitCode Value="1638" Behavior="success"/>
<RemotePayload
Description="Microsoft Windows Desktop Runtime - 6.0.8 ($(var.PowerToysPlatform))"
ProductName="Microsoft Windows Desktop Runtime - 6.0.8 ($(var.PowerToysPlatform))"
Description="Microsoft Windows Desktop Runtime - 6.0.7 ($(var.PowerToysPlatform))"
ProductName="Microsoft Windows Desktop Runtime - 6.0.7 ($(var.PowerToysPlatform))"
Size="$(var.Dotnet6PayloadSize)"
Version="6.0.8.31518"
Version="6.0.7.31422"
Hash="$(var.Dotnet6PayloadHash)" />
</ExePackage>
<ExePackage

View File

@@ -119,13 +119,12 @@
<?define ImageResizerSparsePackageAssets=LargeTile.png;SmallTile.png;SplashScreen.png;Square150x150Logo.png;Square44x44Logo.png;storelogo.png;Wide310x150Logo.png?>
<?define MeasureToolFiles=CoreMessagingXP.dll;dcompi.dll;dwmcorei.dll;DwmSceneI.dll;DWriteCore.dll;marshal.dll;Microsoft.DirectManipulation.dll;Microsoft.InputStateManager.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Internal.FrameworkUdk.dll;Microsoft.UI.Composition.OSSupport.dll;Microsoft.UI.Input.dll;Microsoft.UI.Windowing.Core.dll;Microsoft.UI.Xaml.Controls.dll;Microsoft.UI.Xaml.Controls.pri;Microsoft.ui.xaml.dll;Microsoft.UI.Xaml.Internal.dll;Microsoft.UI.Xaml.Phone.dll;Microsoft.ui.xaml.resources.19h1.dll;Microsoft.ui.xaml.resources.common.dll;Microsoft.Web.WebView2.Core.dll;Microsoft.Windows.ApplicationModel.Resources.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.AppNotifications.Projection.dll;Microsoft.Windows.PushNotifications.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WindowsAppRuntime.dll;Microsoft.WindowsAppRuntime.Insights.Resource.dll;Microsoft.WindowsAppRuntime.Release.Net.dll;Microsoft.WinUI.dll;MRM.dll;PowerToys.ManagedCommon.dll;PowerToys.Interop.dll;PowerToys.ManagedTelemetry.dll;PowerToys.MeasureToolCore.dll;PowerToys.MeasureToolUI.deps.json;PowerToys.MeasureToolUI.dll;PowerToys.MeasureToolUI.exe;PowerToys.MeasureToolUI.runtimeconfig.json;PushNotificationsLongRunningTask.ProxyStub.dll;resources.pri;System.CodeDom.dll;System.Management.dll;WindowsAppRuntime.png;WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll;WinRT.Runtime.dll;WinUIEdit.dll;WinUIEx.dll;wuceffectsi.dll?>
<?define MeasureToolFiles=CoreMessagingXP.dll;dcompi.dll;dwmcorei.dll;DwmSceneI.dll;DWriteCore.dll;marshal.dll;Microsoft.DirectManipulation.dll;Microsoft.InputStateManager.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Internal.FrameworkUdk.dll;Microsoft.UI.Composition.OSSupport.dll;Microsoft.UI.Input.dll;Microsoft.UI.Windowing.Core.dll;Microsoft.UI.Xaml.Controls.dll;Microsoft.UI.Xaml.Controls.pri;Microsoft.ui.xaml.dll;Microsoft.UI.Xaml.Internal.dll;Microsoft.UI.Xaml.Phone.dll;Microsoft.ui.xaml.resources.19h1.dll;Microsoft.ui.xaml.resources.common.dll;Microsoft.Web.WebView2.Core.dll;Microsoft.Windows.ApplicationModel.Resources.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.AppNotifications.Projection.dll;Microsoft.Windows.PushNotifications.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WindowsAppRuntime.dll;Microsoft.WindowsAppRuntime.Insights.Resource.dll;Microsoft.WindowsAppRuntime.Release.Net.dll;Microsoft.WinUI.dll;MRM.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.MeasureToolCore.dll;PowerToys.MeasureToolUI.deps.json;PowerToys.MeasureToolUI.dll;PowerToys.MeasureToolUI.exe;PowerToys.MeasureToolUI.runtimeconfig.json;PushNotificationsLongRunningTask.ProxyStub.dll;resources.pri;System.CodeDom.dll;System.Management.dll;WindowsAppRuntime.png;WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll;WinRT.Runtime.dll;WinUIEdit.dll;WinUIEx.dll;wuceffectsi.dll?>
<?define PowerRenameMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
<?define MeasureToolMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
<?define PowerAccentFiles=ControlzEx.dll;GongSolutions.WPF.DragDrop.dll;Ijwhost.dll;MahApps.Metro.dll;Microsoft.Xaml.Behaviors.dll;PowerAccent.Core.dll;PowerAccent.deps.json;PowerAccent.dll;PowerAccent.exe;PowerAccent.runtimeconfig.json;PowerToys.PowerAccentModuleInterface.dll;PowerToys.Interop.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.PowerAccent.deps.json;PowerToys.PowerAccent.dll;PowerToys.PowerAccent.exe;PowerToys.PowerAccent.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;System.IO.Abstractions.dll;System.Management.dll;System.Text.Json.dll;Vanara.Core.dll;Vanara.PInvoke.Gdi32.dll;Vanara.PInvoke.Kernel32.dll;Vanara.PInvoke.Shared.dll;Vanara.PInvoke.User32.dll;PowerToys.PowerAccentKeyboardService.dll;Microsoft.Windows.SDK.NET.dll;WinRT.Runtime.dll?>
<?define PowerAccentFiles=ControlzEx.dll;GongSolutions.WPF.DragDrop.dll;Ijwhost.dll;MahApps.Metro.dll;Microsoft.Xaml.Behaviors.dll;PowerAccent.Core.dll;PowerAccent.deps.json;PowerAccent.dll;PowerAccent.exe;PowerAccent.runtimeconfig.json;PowerToys.PowerAccentModuleInterface.dll;PowerToys.Interop.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.PowerAccent.deps.json;PowerToys.PowerAccent.dll;PowerToys.PowerAccent.exe;PowerToys.PowerAccent.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;System.IO.Abstractions.dll;System.Management.dll;System.Text.Json.dll;Vanara.Core.dll;Vanara.PInvoke.Gdi32.dll;Vanara.PInvoke.Kernel32.dll;Vanara.PInvoke.Shared.dll;Vanara.PInvoke.User32.dll?>
<Product Id="*"
Name="PowerToys (Preview)"
@@ -468,9 +467,6 @@
<!-- MeasureTool -->
<Directory Id="MeasureToolInstallFolder" Name="$(var.MeasureToolProjectName)">
<Directory Id="MeasureToolMicrosoftUIXamlInstallFolder" Name="Microsoft.UI.Xaml">
<Directory Id="MeasureToolMicrosoftUIXamlAssetsInstallFolder" Name="Assets" />
</Directory>
</Directory>
<!-- Launcher -->
@@ -801,13 +797,13 @@
</Component>
</DirectoryRef>
<DirectoryRef Id="PowerRenameMicrosoftUIXamlAssetsInstallFolder" FileSource="$(var.BinDir)modules\$(var.PowerRenameProjectName)\Microsoft.UI.Xaml\Assets">
<?foreach File in $(var.PowerRenameMicrosoftUIXamlAssetsInstallFiles)?>
<Component Id="PowerRenameMicrosoftUIXamlAssets_$(var.File)" Win64="yes">
<File Id="PowerRenameMicrosoftUIXamlAssetsFile_$(var.File)" Source="$(var.BinDir)modules\$(var.PowerRenameProjectName)\Microsoft.UI.Xaml\Assets\$(var.File)" />
</Component>
<?endforeach?>
</DirectoryRef>
<DirectoryRef Id="PowerRenameMicrosoftUIXamlAssetsInstallFolder" FileSource="$(var.BinDir)modules\$(var.PowerRenameProjectName)\Microsoft.UI.Xaml\Assets">
<?foreach File in $(var.PowerRenameMicrosoftUIXamlAssetsInstallFiles)?>
<Component Id="PowerRenameMicrosoftUIXamlAssets_$(var.File)" Win64="yes">
<File Id="PowerRenameMicrosoftUIXamlAssetsFile_$(var.File)" Source="$(var.BinDir)modules\$(var.PowerRenameProjectName)\Microsoft.UI.Xaml\Assets\$(var.File)" />
</Component>
<?endforeach?>
</DirectoryRef>
<DirectoryRef Id="PowerRenameAssetsFolder" FileSource="$(var.BinDir)modules\$(var.PowerRenameProjectName)">
<!-- !Warning! Make sure to change Component Guid if you update the file list -->
@@ -980,21 +976,13 @@
</Component>
<?foreach File in $(var.MeasureToolFiles)?>
<Component Id="MT_$(var.File)" Win64="yes">
<File Id="MT_$(var.File)" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\$(var.File)" />
<Component Id="MEASURE_TOOL_$(var.File)" Win64="yes">
<File Id="MEASURE_TOOL_$(var.File)" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\$(var.File)" />
</Component>
<?endforeach?>
</DirectoryRef>
<DirectoryRef Id="MeasureToolMicrosoftUIXamlAssetsInstallFolder" FileSource="$(var.BinDir)modules\$(var.MeasureToolProjectName)\Microsoft.UI.Xaml\Assets">
<?foreach File in $(var.MeasureToolMicrosoftUIXamlAssetsInstallFiles)?>
<Component Id="MeasureToolMicrosoftUIXamlAssets_$(var.File)" Win64="yes">
<File Id="MeasureToolMicrosoftUIXamlAssetsFile_$(var.File)" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\Microsoft.UI.Xaml\Assets\$(var.File)" />
</Component>
<?endforeach?>
</DirectoryRef>
<!-- SettingsV2 components -->
<DirectoryRef Id="SettingsV2InstallFolder" FileSource="$(var.BinDir)Settings\">
<?foreach File in $(var.SettingsV2Files)?>
@@ -1139,10 +1127,7 @@
<ComponentRef Id="Module_AlwaysOnTopInterface"/>
<ComponentRef Id="Module_MeasureToolInterface"/>
<?foreach File in $(var.MeasureToolFiles)?>
<ComponentRef Id="MT_$(var.File)" />
<?endforeach?>
<?foreach File in $(var.MeasureToolMicrosoftUIXamlAssetsInstallFiles)?>
<ComponentRef Id="MeasureToolMicrosoftUIXamlAssets_$(var.File)" />
<ComponentRef Id="MEASURE_TOOL_$(var.File)" />
<?endforeach?>
<?foreach File in $(var.SettingsV2Files)?>
@@ -1491,7 +1476,7 @@
<!-- Localization languages shipped with WinAppSDK. We should ship these as well. -->
<?define WinAppSDKLocLanguageList = af-ZA;ar-SA;az-Latn-AZ;bg-BG;bs-Latn-BA;ca-ES;cs-CZ;cy-GB;da-DK;de-DE;el-GR;en-GB;en-us;es-ES;es-MX;et-EE;eu-ES;fa-IR;fi-FI;fr-CA;fr-FR;gl-ES;he-IL;hi-IN;hr-HR;hu-HU;id-ID;is-IS;it-IT;ja-JP;ka-GE;kk-KZ;ko-KR;lt-LT;lv-LV;ms-MY;nb-NO;nl-NL;nn-NO;pl-PL;pt-BR;pt-PT;ro-RO;ru-RU;sk-SK;sl-SI;sq-AL;sr-Cyrl-RS;sr-Latn-RS;sv-SE;th-TH;tr-TR;uk-UA;vi-VN;zh-CN;zh-TW?>
<Fragment>
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder?>
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<?foreach Language in $(var.WinAppSDKLocLanguageList)?>
<?if $(var.Language) = af-ZA?>
@@ -1803,13 +1788,6 @@
<File Id="PowerRename_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.PowerRenameProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
<File Id="PowerRename_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.PowerRenameProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
</Component>
<Component
Id="MeasureTool_WinAppSDKLoc_$(var.IdSafeLanguage)_Component"
Directory="WinAppSDKLoc$(var.IdSafeLanguage)MeasureToolInstallFolder"
Guid="$(var.CompGUIDPrefix)03">
<File Id="MeasureTool_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
<File Id="MeasureTool_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
</Component>
<?undef IdSafeLanguage?>
<?undef CompGUIDPrefix?>
<?endforeach?>

View File

@@ -1,12 +0,0 @@
@echo off
setlocal ENABLEDELAYEDEXPANSION
@REM We loop here until taskkill cannot find a PowerToys process. We can't use /F flag, because it
@REM doesn't give application an opportunity to cleanup. Thus we send WM_CLOSE which is being caught
@REM by multiple windows running a msg loop in PowerToys.exe process, which we close one by one.
for /l %%x in (1, 1, 100) do (
taskkill /IM PowerToys.exe 1>NUL 2>NUL
if !ERRORLEVEL! NEQ 0 goto quit
)
:quit

View File

@@ -982,7 +982,7 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
}
}
}
catch (std::exception& e)
catch (std::exception e)
{
std::string errorMessage{ "Exception thrown while trying to unregister sparse packages: " };
errorMessage += e.what();

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using ControlzEx.Theming;
namespace Common.UI

View File

@@ -18,13 +18,13 @@ struct LogSettings
inline const static std::string launcherLoggerName = "launcher";
inline const static std::wstring launcherLogPath = L"LogsModuleInterface\\launcher-log.txt";
inline const static std::wstring awakeLogPath = L"Logs\\awake-log.txt";
inline const static std::wstring powerAccentLogPath = L"quick-accent-log.txt";
inline const static std::wstring powerAccentLogPath = L"poweraccent.log";
inline const static std::string fancyZonesLoggerName = "fancyzones";
inline const static std::wstring fancyZonesLogPath = L"fancyzones-log.txt";
inline const static std::wstring fancyZonesOldLogPath = L"FancyZonesLogs\\"; // needed to clean up old logs
inline const static std::string shortcutGuideLoggerName = "shortcut-guide";
inline const static std::wstring shortcutGuideLogPath = L"ShortcutGuideLogs\\shortcut-guide-log.txt";
inline const static std::wstring powerOcrLogPath = L"Logs\\text-extractor-log.txt";
inline const static std::wstring powerOcrLogPath = L"Logs\\power-ocr-log.txt";
inline const static std::string keyboardManagerLoggerName = "keyboard-manager";
inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt";
inline const static std::string findMyMouseLoggerName = "find-my-mouse";
@@ -33,7 +33,7 @@ struct LogSettings
inline const static std::string imageResizerLoggerName = "imageresizer";
inline const static std::string powerRenameLoggerName = "powerrename";
inline const static std::string alwaysOnTopLoggerName = "always-on-top";
inline const static std::string powerOcrLoggerName = "TextExtractor";
inline const static std::string powerOcrLoggerName = "power-ocr";
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt";
inline const static int retention = 30;
std::wstring logLevel;

View File

@@ -156,7 +156,7 @@ inline void LogStackTrace()
Logger::error(L"Failed to capture context. {}", get_last_error_or_default(GetLastError()));
return;
}
STACKFRAME64 stack;
memset(&stack, 0, sizeof(STACKFRAME64));
@@ -238,14 +238,14 @@ inline LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info)
}
/* Handler to trap abort() calls */
inline void AbortHandler(int /*signal_number*/)
inline void AbortHandler(int signal_number)
{
Logger::error("--- ABORT");
try
{
LogStackTrace();
}
catch (...)
catch(...)
{
Logger::error("Failed to log stack trace on abort");
Logger::flush();
@@ -271,9 +271,9 @@ inline void InitUnhandledExceptionHandler(void)
// Global handler for unhandled exceptions
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
// Handler for abort()
signal(SIGABRT, &AbortHandler);
signal(SIGABRT, &AbortHandler);
}
catch (...)
catch(...)
{
Logger::error("Failed to init global unhandled exception handler");
}

View File

@@ -102,7 +102,7 @@ namespace package {
return true;
}
catch (std::exception& e)
catch (std::exception e)
{
Logger::error("Exception thrown while trying to register package: {}", e.what());

View File

@@ -9,8 +9,6 @@
#endif
#include <cassert>
#include <limits>
#include <d3d11.h>
//#define DEBUG_TEXTURE
@@ -127,43 +125,3 @@ struct BGRATextureView
void SaveAsBitmap(const char* filename) const;
#endif
};
class MappedTextureView
{
winrt::com_ptr<ID3D11DeviceContext> context;
winrt::com_ptr<ID3D11Texture2D> texture;
public:
BGRATextureView view;
MappedTextureView(winrt::com_ptr<ID3D11Texture2D> _texture,
winrt::com_ptr<ID3D11DeviceContext> _context,
const size_t textureWidth,
const size_t textureHeight) :
texture{ std::move(_texture) }, context{ std::move(_context) }
{
D3D11_TEXTURE2D_DESC desc;
texture->GetDesc(&desc);
D3D11_MAPPED_SUBRESOURCE resource = {};
winrt::check_hresult(context->Map(texture.get(), D3D11CalcSubresource(0, 0, 0), D3D11_MAP_READ, 0, &resource));
view.pixels = static_cast<const uint32_t*>(resource.pData);
view.pitch = resource.RowPitch / 4;
view.width = textureWidth;
view.height = textureHeight;
}
MappedTextureView(MappedTextureView&&) = default;
MappedTextureView& operator=(MappedTextureView&&) = default;
inline winrt::com_ptr<ID3D11Texture2D> GetTexture() const
{
return texture;
}
~MappedTextureView()
{
if (context && texture)
context->Unmap(texture.get(), D3D11CalcSubresource(0, 0, 0));
}
};

View File

@@ -5,72 +5,6 @@
#include <common/utils/window.h>
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
namespace
{
void ToggleCursor(const bool show)
{
if (show)
{
for (; ShowCursor(show) < 0;)
;
}
else
{
for (; ShowCursor(show) >= 0;)
;
}
}
void HandleCursorMove(HWND window, BoundsToolState* toolState, const POINT cursorPos, const DWORD touchID = 0)
{
if (!toolState->perScreen[window].currentBounds || (toolState->perScreen[window].currentBounds->touchID != touchID))
return;
toolState->perScreen[window].currentBounds->currentPos =
D2D_POINT_2F{ .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
}
void HandleCursorDown(HWND window, BoundsToolState* toolState, const POINT cursorPos, const DWORD touchID = 0)
{
ToggleCursor(false);
RECT windowRect;
if (GetWindowRect(window, &windowRect))
ClipCursor(&windowRect);
const D2D_POINT_2F newBoundsStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
toolState->perScreen[window].currentBounds = CursorDrag{
.startPos = newBoundsStart,
.currentPos = newBoundsStart,
.touchID = touchID
};
}
void HandleCursorUp(HWND window, BoundsToolState* toolState, const POINT cursorPos)
{
ToggleCursor(true);
ClipCursor(nullptr);
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
});
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress && toolState->perScreen[window].currentBounds)
{
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) =
std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentBounds->startPos.x);
std::tie(rect.top, rect.bottom) =
std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentBounds->startPos.y);
toolState->perScreen[window].measurements.push_back(Measurement{ rect });
}
toolState->perScreen[window].currentBounds = std::nullopt;
}
}
LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
switch (message)
@@ -91,122 +25,54 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
break;
case WM_LBUTTONDOWN:
{
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
const POINT cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
HandleCursorDown(window,
toolState,
convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
D2D_POINT_2F newRegionStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
toolState->perScreen[window].currentRegionStart = newRegionStart;
break;
}
case WM_CURSOR_LEFT_MONITOR:
{
ToggleCursor(true);
ClipCursor(nullptr);
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
toolState->perScreen[window].currentBounds = std::nullopt;
toolState->perScreen[window].currentRegionStart = std::nullopt;
break;
}
case WM_TOUCH:
{
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
std::array<TOUCHINPUT, 8> inputs;
const size_t nInputs = std::min(static_cast<size_t>(LOWORD(wparam)), inputs.size());
const auto inputHandle = std::bit_cast<HTOUCHINPUT>(lparam);
GetTouchInputInfo(inputHandle, static_cast<UINT>(nInputs), inputs.data(), sizeof(TOUCHINPUT));
for (UINT i = 0; i < nInputs; ++i)
{
const auto& input = inputs[i];
if (const bool down = (input.dwFlags & TOUCHEVENTF_DOWN) && (input.dwFlags & TOUCHEVENTF_PRIMARY); down)
{
HandleCursorDown(
window,
toolState,
POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) },
input.dwID);
continue;
}
if (const bool up = input.dwFlags & TOUCHEVENTF_UP; up)
{
HandleCursorUp(
window,
toolState,
POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) });
continue;
}
if (const bool move = input.dwFlags & TOUCHEVENTF_MOVE; move)
{
HandleCursorMove(window,
toolState,
POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) },
input.dwID);
continue;
}
}
CloseTouchInputHandle(inputHandle);
break;
}
case WM_MOUSEMOVE:
{
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
HandleCursorMove(window,
toolState,
convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
break;
}
case WM_LBUTTONUP:
{
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
if (!toolState || !toolState->perScreen[window].currentRegionStart)
break;
HandleCursorUp(window,
toolState,
convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
});
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress)
{
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) = std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentRegionStart->x);
std::tie(rect.top, rect.bottom) = std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentRegionStart->y);
toolState->perScreen[window].measurements.push_back(rect);
}
toolState->perScreen[window].currentRegionStart = std::nullopt;
break;
}
case WM_RBUTTONUP:
{
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
ToggleCursor(true);
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
if (toolState->perScreen[window].currentBounds)
toolState->perScreen[window].currentBounds = std::nullopt;
if (toolState->perScreen[window].currentRegionStart)
toolState->perScreen[window].currentRegionStart = std::nullopt;
else
{
if (toolState->perScreen[window].measurements.empty())
@@ -223,42 +89,45 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
namespace
{
void DrawMeasurement(const Measurement& measurement,
void DrawMeasurement(const D2D1_RECT_F rect,
const bool alignTextBoxToCenter,
const CommonState& commonState,
HWND window,
const D2DState& d2dState,
std::optional<D2D_POINT_2F> textBoxCenter)
const D2DState& d2dState)
{
const bool screenQuadrantAware = textBoxCenter.has_value();
d2dState.ToggleAliasedLinesMode(true);
d2dState.dxgiWindowState.rt->DrawRectangle(measurement.rect, d2dState.solidBrushes[Brush::line].get());
d2dState.ToggleAliasedLinesMode(false);
const bool screenQuadrantAware = !alignTextBoxToCenter;
const auto prevMode = d2dState.rt->GetAntialiasMode();
d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
d2dState.rt->DrawRectangle(rect, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->SetAntialiasMode(prevMode);
OverlayBoxText text;
const auto [crossSymbolPos, measureStringBufLen] =
measurement.Print(text.buffer.data(),
text.buffer.size(),
true,
true,
commonState.units);
const auto width = std::abs(rect.right - rect.left + 1);
const auto height = std::abs(rect.top - rect.bottom + 1);
const uint32_t textLen = swprintf_s(text.buffer.data(),
text.buffer.size(),
L"%.0f × %.0f",
width,
height);
std::optional<size_t> crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
v = text;
});
D2D_POINT_2F textBoxPos;
if (textBoxCenter)
textBoxPos = *textBoxCenter;
else
float cornerX = rect.right;
float cornerY = rect.bottom;
if (alignTextBoxToCenter)
{
textBoxPos.x = measurement.rect.left + measurement.Width(Measurement::Unit::Pixel) / 2;
textBoxPos.y = measurement.rect.top + measurement.Height(Measurement::Unit::Pixel) / 2;
cornerX = rect.left + width / 2;
cornerY = rect.top + height / 2;
}
d2dState.DrawTextBox(text.buffer.data(),
measureStringBufLen,
textLen,
crossSymbolPos,
textBoxPos,
cornerX,
cornerY,
screenQuadrantAware,
window);
}
@@ -273,17 +142,20 @@ void DrawBoundsToolTick(const CommonState& commonState,
if (it == end(toolState.perScreen))
return;
d2dState.dxgiWindowState.rt->Clear();
d2dState.rt->Clear();
const auto& perScreen = it->second;
for (const auto& measure : perScreen.measurements)
DrawMeasurement(measure, commonState, window, d2dState, {});
DrawMeasurement(measure, true, commonState, window, d2dState);
if (perScreen.currentBounds.has_value())
{
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) = std::minmax(perScreen.currentBounds->startPos.x, perScreen.currentBounds->currentPos.x);
std::tie(rect.top, rect.bottom) = std::minmax(perScreen.currentBounds->startPos.y, perScreen.currentBounds->currentPos.y);
DrawMeasurement(Measurement{ rect }, commonState, window, d2dState, perScreen.currentBounds->currentPos);
}
if (!perScreen.currentRegionStart.has_value())
return;
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
const D2D1_RECT_F rect{ .left = perScreen.currentRegionStart->x,
.top = perScreen.currentRegionStart->y,
.right = static_cast<float>(cursorPos.x),
.bottom = static_cast<float>(cursorPos.y) };
DrawMeasurement(rect, false, commonState, window, d2dState);
}

View File

@@ -6,9 +6,21 @@
namespace convert
{
// Converts a given point from multi-monitor coordinate system to the one relative to HWND
inline POINT FromSystemToWindow(HWND window, POINT p)
inline POINT FromSystemToRelative(HWND window, POINT p)
{
ScreenToClient(window, &p);
return p;
}
// Converts a given point from multi-monitor coordinate system to the one relative to HWND and also ready
// to be used in Direct2D calls with AA mode set to aliased
inline POINT FromSystemToRelativeForDirect2D(HWND window, POINT p)
{
ScreenToClient(window, &p);
// Submitting DrawLine calls to Direct2D with thickness == 1.f and AA mode set to aliased causes
// them to be drawn offset by [1,1] toward upper-left corner, so we must to compensate for that.
++p.x;
++p.y;
return p;
}
}

View File

@@ -2,7 +2,6 @@
#include "constants.h"
#include "D2DState.h"
#include "DxgiAPI.h"
#include <common/Display/dpi_aware.h>
#include <ToolState.h>
@@ -20,28 +19,44 @@ namespace
}
}
D2DState::D2DState(const DxgiAPI* dxgi,
HWND window,
std::vector<D2D1::ColorF> solidBrushesColors)
D2DState::D2DState(const HWND overlayWindow, std::vector<D2D1::ColorF> solidBrushesColors)
{
dxgiAPI = dxgi;
std::lock_guard guard{ gpuAccessLock };
RECT clientRect = {};
winrt::check_bool(GetClientRect(overlayWindow, &clientRect));
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &d2dFactory));
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), writeFactory.put_unknown()));
// We should always use DPIAware::DEFAULT_DPI, since it's the correct thing to do in DPI-Aware mode
auto renderTargetProperties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
DPIAware::DEFAULT_DPI,
DPIAware::DEFAULT_DPI,
D2D1_RENDER_TARGET_USAGE_NONE,
D2D1_FEATURE_LEVEL_DEFAULT);
auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(overlayWindow, renderTargetSize);
winrt::check_hresult(d2dFactory->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, &rt));
winrt::check_hresult(rt->CreateCompatibleRenderTarget(&bitmapRt));
unsigned dpi = DPIAware::DEFAULT_DPI;
DPIAware::GetScreenDPIForWindow(window, dpi);
DPIAware::GetScreenDPIForWindow(overlayWindow, dpi);
dpiScale = dpi / static_cast<float>(DPIAware::DEFAULT_DPI);
dxgiWindowState = dxgiAPI->CreateD2D1RenderTarget(window);
winrt::check_hresult(dxgiWindowState.rt->CreateCompatibleRenderTarget(bitmapRt.put()));
winrt::check_hresult(dxgiAPI->writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
nullptr,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
consts::FONT_SIZE * dpiScale,
L"en-US",
textFormat.put()));
winrt::check_hresult(writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
nullptr,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
consts::FONT_SIZE * dpiScale,
L"en-US",
&textFormat));
winrt::check_hresult(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
winrt::check_hresult(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
winrt::check_hresult(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));
@@ -49,61 +64,57 @@ D2DState::D2DState(const DxgiAPI* dxgi,
solidBrushes.resize(solidBrushesColors.size());
for (size_t i = 0; i < solidBrushes.size(); ++i)
{
winrt::check_hresult(dxgiWindowState.rt->CreateSolidColorBrush(solidBrushesColors[i], solidBrushes[i].put()));
winrt::check_hresult(rt->CreateSolidColorBrush(solidBrushesColors[i], &solidBrushes[i]));
}
const auto deviceContext = dxgiWindowState.rt.as<ID2D1DeviceContext>();
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, shadowEffect.put()));
const auto deviceContext = rt.query<ID2D1DeviceContext>();
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, &shadowEffect));
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, consts::SHADOW_RADIUS));
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, D2D1::ColorF(0.f, 0.f, 0.f, consts::SHADOW_OPACITY)));
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, affineTransformEffect.put()));
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, &affineTransformEffect));
affineTransformEffect->SetInputEffect(0, shadowEffect.get());
textRenderer = winrt::make_self<PerGlyphOpacityTextRender>(dxgi->d2dFactory2, dxgiWindowState.rt, solidBrushes[Brush::foreground]);
textRenderer = winrt::make_self<PerGlyphOpacityTextRender>(d2dFactory, rt, solidBrushes[Brush::foreground]);
}
void D2DState::DrawTextBox(const wchar_t* text,
const size_t textLen,
const uint32_t textLen,
const std::optional<size_t> halfOpaqueSymbolPos,
const D2D_POINT_2F center,
const float centerX,
const float centerY,
const bool screenQuadrantAware,
const HWND window) const
{
wil::com_ptr<IDWriteTextLayout> textLayout;
winrt::check_hresult(
dxgiAPI->writeFactory->CreateTextLayout(text,
static_cast<uint32_t>(textLen),
textFormat.get(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
&textLayout));
winrt::check_hresult(writeFactory->CreateTextLayout(text,
textLen,
textFormat.get(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
&textLayout));
DWRITE_TEXT_METRICS textMetrics = {};
winrt::check_hresult(textLayout->GetMetrics(&textMetrics));
// Assumes text doesn't contain new lines
const float lineHeight = textMetrics.height;
textMetrics.width += lineHeight;
textMetrics.height += lineHeight * .5f;
textMetrics.width *= consts::TEXT_BOX_MARGIN_COEFF;
textMetrics.height *= consts::TEXT_BOX_MARGIN_COEFF;
winrt::check_hresult(textLayout->SetMaxWidth(textMetrics.width));
winrt::check_hresult(textLayout->SetMaxHeight(textMetrics.height));
D2D1_RECT_F textRect{ .left = center.x - textMetrics.width / 2.f,
.top = center.y - textMetrics.height / 2.f,
.right = center.x + textMetrics.width / 2.f,
.bottom = center.y + textMetrics.height / 2.f };
const float SHADOW_OFFSET = consts::SHADOW_OFFSET * dpiScale;
D2D1_RECT_F textRect{ .left = centerX - textMetrics.width / 2.f,
.top = centerY - textMetrics.height / 2.f,
.right = centerX + textMetrics.width / 2.f,
.bottom = centerY + textMetrics.height / 2.f };
if (screenQuadrantAware)
{
bool cursorInLeftScreenHalf = false;
bool cursorInTopScreenHalf = false;
DetermineScreenQuadrant(window,
static_cast<long>(center.x),
static_cast<long>(center.y),
static_cast<long>(centerX),
static_cast<long>(centerY),
cursorInLeftScreenHalf,
cursorInTopScreenHalf);
float textQuadrantOffsetX = textMetrics.width / 2.f + SHADOW_OFFSET;
float textQuadrantOffsetY = textMetrics.height / 2.f + SHADOW_OFFSET;
float textQuadrantOffsetX = textMetrics.width * dpiScale;
float textQuadrantOffsetY = textMetrics.height * dpiScale;
if (!cursorInLeftScreenHalf)
textQuadrantOffsetX *= -1.f;
if (!cursorInTopScreenHalf)
@@ -130,14 +141,15 @@ void D2DState::DrawTextBox(const wchar_t* text,
bitmapRt->GetBitmap(&rtBitmap);
shadowEffect->SetInput(0, rtBitmap.get());
const auto shadowMatrix = D2D1::Matrix3x2F::Translation(SHADOW_OFFSET, SHADOW_OFFSET);
const auto shadowMatrix = D2D1::Matrix3x2F::Translation(consts::SHADOW_OFFSET * dpiScale,
consts::SHADOW_OFFSET * dpiScale);
winrt::check_hresult(affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX,
shadowMatrix));
auto deviceContext = dxgiWindowState.rt.as<ID2D1DeviceContext>();
auto deviceContext = rt.query<ID2D1DeviceContext>();
deviceContext->DrawImage(affineTransformEffect.get(), D2D1_INTERPOLATION_MODE_LINEAR);
// Draw text box border rectangle
dxgiWindowState.rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
const float TEXT_BOX_PADDING = 1.f * dpiScale;
textBoxRect.rect.bottom -= TEXT_BOX_PADDING;
textBoxRect.rect.top += TEXT_BOX_PADDING;
@@ -145,7 +157,7 @@ void D2DState::DrawTextBox(const wchar_t* text,
textBoxRect.rect.right -= TEXT_BOX_PADDING;
// Draw text & its box
dxgiWindowState.rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
if (halfOpaqueSymbolPos.has_value())
{
@@ -156,19 +168,3 @@ void D2DState::DrawTextBox(const wchar_t* text,
}
winrt::check_hresult(textLayout->Draw(nullptr, textRenderer.get(), textRect.left, textRect.top));
}
void D2DState::ToggleAliasedLinesMode(const bool enabled) const
{
if (enabled)
{
// Draw lines in the middle of a pixel to avoid bleeding, since [0,0] pixel is
// a rectangle filled from (0,0) to (1,1) and the lines use thickness = 1.
dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Translation(.5f, .5f));
dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
}
else
{
dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Identity());
dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
}
}

View File

@@ -3,9 +3,10 @@
#include <optional>
#include <vector>
#include <d2d1_3.h>
#include <wil/com.h>
#include <windef.h>
#include "DxgiAPI.h"
#include "PerGlyphOpacityTextRender.h"
enum Brush : size_t
@@ -18,26 +19,24 @@ enum Brush : size_t
struct D2DState
{
const DxgiAPI* dxgiAPI = nullptr;
DxgiWindowState dxgiWindowState;
winrt::com_ptr<ID2D1BitmapRenderTarget> bitmapRt;
winrt::com_ptr<IDWriteTextFormat> textFormat;
winrt::com_ptr<PerGlyphOpacityTextRender> textRenderer;
std::vector<winrt::com_ptr<ID2D1SolidColorBrush>> solidBrushes;
winrt::com_ptr<ID2D1Effect> shadowEffect;
winrt::com_ptr<ID2D1Effect> affineTransformEffect;
wil::com_ptr<ID2D1Factory> d2dFactory;
wil::com_ptr<IDWriteFactory> writeFactory;
wil::com_ptr<ID2D1HwndRenderTarget> rt;
wil::com_ptr<ID2D1BitmapRenderTarget> bitmapRt;
wil::com_ptr<IDWriteTextFormat> textFormat;
winrt::com_ptr<PerGlyphOpacityTextRender> textRenderer;
std::vector<wil::com_ptr<ID2D1SolidColorBrush>> solidBrushes;
wil::com_ptr<ID2D1Effect> shadowEffect;
wil::com_ptr<ID2D1Effect> affineTransformEffect;
float dpiScale = 1.f;
D2DState(const DxgiAPI*,
HWND window,
std::vector<D2D1::ColorF> solidBrushesColors);
D2DState(const HWND window, std::vector<D2D1::ColorF> solidBrushesColors);
void DrawTextBox(const wchar_t* text,
const size_t textLen,
const uint32_t textLen,
const std::optional<size_t> halfOpaqueSymbolPos,
const D2D_POINT_2F center,
const float centerX,
const float centerY,
const bool screenQuadrantAware,
const HWND window) const;
void ToggleAliasedLinesMode(const bool enabled) const;
};

View File

@@ -1,145 +0,0 @@
#include "pch.h"
#include "DxgiAPI.h"
#include <common/Display/dpi_aware.h>
//#define DEBUG_DEVICES
#define SEPARATE_D3D_FOR_CAPTURE
namespace
{
DxgiAPI::D3D CreateD3D()
{
DxgiAPI::D3D d3d;
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(DEBUG_DEVICES)
flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
HRESULT hr =
D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3d.d3dDevice.put(),
nullptr,
nullptr);
if (hr == DXGI_ERROR_UNSUPPORTED)
{
hr = D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_WARP,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3d.d3dDevice.put(),
nullptr,
nullptr);
}
winrt::check_hresult(hr);
d3d.dxgiDevice = d3d.d3dDevice.as<IDXGIDevice>();
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(d3d.dxgiDevice.get(), d3d.d3dDeviceInspectable.put()));
winrt::com_ptr<IDXGIAdapter> adapter;
winrt::check_hresult(d3d.dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), d3d.dxgiFactory2.put_void()));
d3d.d3dDevice->GetImmediateContext(d3d.d3dContext.put());
winrt::check_bool(d3d.d3dContext);
auto contextMultithread = d3d.d3dContext.as<ID3D11Multithread>();
contextMultithread->SetMultithreadProtected(true);
return d3d;
}
}
DxgiAPI::DxgiAPI()
{
const D2D1_FACTORY_OPTIONS d2dFactoryOptions = {
#if defined(DEBUG_DEVICES)
D2D1_DEBUG_LEVEL_INFORMATION
#else
D2D1_DEBUG_LEVEL_NONE
#endif
};
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, d2dFactoryOptions, d2dFactory2.put()));
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
winrt::guid_of<IDWriteFactory>(),
reinterpret_cast<IUnknown**>(writeFactory.put())));
auto d3d = CreateD3D();
d3dDevice = d3d.d3dDevice;
dxgiDevice = d3d.dxgiDevice;
d3dDeviceInspectable = d3d.d3dDeviceInspectable;
dxgiFactory2 = d3d.dxgiFactory2;
d3dContext = d3d.d3dContext;
#if defined(SEPARATE_D3D_FOR_CAPTURE)
auto d3dFC = CreateD3D();
d3dForCapture = d3dFC;
#else
d3dForCapture = d3d;
#endif
winrt::check_hresult(d2dFactory2->CreateDevice(dxgiDevice.get(), d2dDevice1.put()));
winrt::check_hresult(DCompositionCreateDevice(
dxgiDevice.get(),
winrt::guid_of<IDCompositionDevice>(),
compositionDevice.put_void()));
}
DxgiWindowState DxgiAPI::CreateD2D1RenderTarget(HWND window) const
{
RECT rect = {};
winrt::check_bool(GetClientRect(window, &rect));
const DXGI_SWAP_CHAIN_DESC1 desc = {
.Width = static_cast<UINT>(rect.right - rect.left),
.Height = static_cast<UINT>(rect.bottom - rect.top),
.Format = static_cast<DXGI_FORMAT>(winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized),
.SampleDesc = { .Count = 1, .Quality = 0 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.Scaling = DXGI_SCALING_STRETCH,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
};
DxgiWindowState state;
winrt::com_ptr<ID2D1DeviceContext> rt;
d2dDevice1->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, rt.put());
state.rt = rt;
winrt::check_hresult(dxgiFactory2->CreateSwapChainForComposition(d3dDevice.get(),
&desc,
nullptr,
state.swapChain.put()));
winrt::com_ptr<IDXGISurface> surface;
winrt::check_hresult(state.swapChain->GetBuffer(0, winrt::guid_of<IDXGISurface>(), surface.put_void()));
const D2D1_BITMAP_PROPERTIES1 properties = {
.pixelFormat = { .format = DXGI_FORMAT_B8G8R8A8_UNORM, .alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED },
.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW
};
winrt::com_ptr<ID2D1Bitmap1> bitmap;
winrt::check_hresult(rt->CreateBitmapFromDxgiSurface(surface.get(),
properties,
bitmap.put()));
rt->SetTarget(bitmap.get());
winrt::check_hresult(compositionDevice->CreateTargetForHwnd(window,
true,
state.compositionTarget.put()));
winrt::com_ptr<IDCompositionVisual> visual;
winrt::check_hresult(compositionDevice->CreateVisual(visual.put()));
winrt::check_hresult(visual->SetContent(state.swapChain.get()));
winrt::check_hresult(state.compositionTarget->SetRoot(visual.get()));
winrt::check_hresult(compositionDevice->Commit());
return state;
}

View File

@@ -1,49 +0,0 @@
#pragma once
#include <d2d1_3.h>
#include <d3d11_4.h>
#include <dcomp.h>
#include <dxgi1_3.h>
#include <inspectable.h>
#include <winrt/base.h>
struct DxgiWindowState
{
winrt::com_ptr<ID2D1RenderTarget> rt;
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::com_ptr<IDCompositionTarget> compositionTarget;
};
struct DxgiAPI final
{
struct D3D
{
winrt::com_ptr<ID3D11Device> d3dDevice;
winrt::com_ptr<IDXGIDevice> dxgiDevice;
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
winrt::com_ptr<IDXGIFactory2> dxgiFactory2;
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
};
winrt::com_ptr<ID2D1Factory2> d2dFactory2;
winrt::com_ptr<IDWriteFactory> writeFactory;
winrt::com_ptr<ID3D11Device> d3dDevice;
winrt::com_ptr<IDXGIDevice> dxgiDevice;
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
winrt::com_ptr<IDXGIFactory2> dxgiFactory2;
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
D3D d3dForCapture;
winrt::com_ptr<ID2D1Device1> d2dDevice1;
winrt::com_ptr<IDCompositionDevice> compositionDevice;
DxgiAPI();
enum class Uninitialized
{
};
explicit inline DxgiAPI(Uninitialized) {}
DxgiWindowState CreateD2D1RenderTarget(HWND window) const;
};

View File

@@ -2,18 +2,36 @@
#include "constants.h"
#include "EdgeDetection.h"
template<bool PerChannel,
bool ContinuousCapture,
bool IsX,
bool Increment>
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
{
using namespace consts;
long xOffset = 0;
long yOffset = 0;
// For continuous capture, we'll be a bit off center from the cursor so the cross we draw won't interfere with the measurement.
if constexpr (ContinuousCapture)
{
if constexpr (IsX)
{
xOffset = Increment ? CURSOR_OFFSET_AMOUNT_X : -CURSOR_OFFSET_AMOUNT_X;
yOffset = 1;
}
else
{
xOffset = 1;
yOffset = Increment ? CURSOR_OFFSET_AMOUNT_Y : -CURSOR_OFFSET_AMOUNT_Y;
}
}
const size_t maxDim = IsX ? texture.width : texture.height;
long x = std::clamp<long>(centerPoint.x, 1, static_cast<long>(texture.width - 2));
long y = std::clamp<long>(centerPoint.y, 1, static_cast<long>(texture.height - 2));
long x = std::clamp<long>(centerPoint.x + xOffset, 1, static_cast<long>(texture.width - 2));
long y = std::clamp<long>(centerPoint.y + yOffset, 1, static_cast<long>(texture.height - 2));
const uint32_t startPixel = texture.GetPixel(x, y);
while (true)
@@ -57,21 +75,25 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
return Increment ? static_cast<long>(IsX ? texture.width : texture.height) - 1 : 0;
}
template<bool PerChannel>
template<bool PerChannel, bool ContinuousCapture>
inline RECT DetectEdgesInternal(const BGRATextureView& texture,
const POINT centerPoint,
const uint8_t tolerance)
{
return RECT{ .left = FindEdge<PerChannel,
ContinuousCapture,
true,
false>(texture, centerPoint, tolerance),
.top = FindEdge<PerChannel,
ContinuousCapture,
false,
false>(texture, centerPoint, tolerance),
.right = FindEdge<PerChannel,
ContinuousCapture,
true,
true>(texture, centerPoint, tolerance),
.bottom = FindEdge<PerChannel,
ContinuousCapture,
false,
true>(texture, centerPoint, tolerance) };
}
@@ -79,9 +101,12 @@ inline RECT DetectEdgesInternal(const BGRATextureView& texture,
RECT DetectEdges(const BGRATextureView& texture,
const POINT centerPoint,
const bool perChannel,
const uint8_t tolerance)
const uint8_t tolerance,
const bool continuousCapture)
{
auto function = perChannel ? &DetectEdgesInternal<true> : DetectEdgesInternal<false>;
auto function = perChannel ? &DetectEdgesInternal<true, false> : DetectEdgesInternal<false, false>;
if (continuousCapture)
function = perChannel ? &DetectEdgesInternal<true, true> : &DetectEdgesInternal<false, true>;
return function(texture, centerPoint, tolerance);
}

View File

@@ -5,4 +5,5 @@
RECT DetectEdges(const BGRATextureView& texture,
const POINT centerPoint,
const bool perChannel,
const uint8_t tolerance);
const uint8_t tolerance,
const bool continuousCapture);

View File

@@ -13,38 +13,56 @@ namespace
inline std::pair<D2D_POINT_2F, D2D_POINT_2F> ComputeCrossFeetLine(D2D_POINT_2F center, const bool horizontal)
{
D2D_POINT_2F start = center, end = center;
// Computing in this way to achieve pixel-perfect axial symmetry of aliased D2D lines
// Computing in this way to achieve pixel-perfect axial symmetry of aliased D2D lines
if (horizontal)
{
start.x -= consts::FEET_HALF_LENGTH;
end.x += consts::FEET_HALF_LENGTH + 1.f;
start.x -= consts::FEET_HALF_LENGTH + 1.f;
end.x += consts::FEET_HALF_LENGTH;
start.y += 1.f;
end.y += 1.f;
}
else
{
start.y -= consts::FEET_HALF_LENGTH;
end.y += consts::FEET_HALF_LENGTH + 1.f;
start.y -= consts::FEET_HALF_LENGTH + 1.f;
end.y += consts::FEET_HALF_LENGTH;
start.x += 1.f;
end.x += 1.f;
}
return { start, end };
}
}
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(winrt::com_ptr<ID2D1RenderTarget> rt,
const MappedTextureView* capturedScreenTexture)
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(wil::com_ptr<ID2D1HwndRenderTarget> rt,
winrt::com_ptr<ID3D11Texture2D> texture)
{
capturedScreenTexture->view.pixels;
std::lock_guard guard{ gpuAccessLock };
auto dxgiSurface = texture.try_as<IDXGISurface>();
if (!dxgiSurface)
return nullptr;
DXGI_MAPPED_RECT bitmap2Dmap = {};
HRESULT hr = dxgiSurface->Map(&bitmap2Dmap, DXGI_MAP_READ);
if (FAILED(hr))
{
return nullptr;
}
D2D1_BITMAP_PROPERTIES props = { .pixelFormat = rt->GetPixelFormat() };
rt->GetDpi(&props.dpiX, &props.dpiY);
const auto sizeF = rt->GetSize();
winrt::com_ptr<ID2D1Bitmap> bitmap;
auto hr = rt->CreateBitmap(D2D1::SizeU(static_cast<uint32_t>(capturedScreenTexture->view.width),
static_cast<uint32_t>(capturedScreenTexture->view.height)),
capturedScreenTexture->view.pixels,
static_cast<uint32_t>(capturedScreenTexture->view.pitch * 4),
props,
bitmap.put());
if (FAILED(hr))
if (FAILED(rt->CreateBitmap(D2D1::SizeU(static_cast<uint32_t>(sizeF.width),
static_cast<uint32_t>(sizeF.height)),
bitmap2Dmap.pBits,
bitmap2Dmap.Pitch,
props,
bitmap.put())))
return nullptr;
if (FAILED(dxgiSurface->Unmap()))
return nullptr;
return bitmap;
@@ -54,7 +72,6 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
{
switch (message)
{
case WM_MOUSELEAVE:
case WM_CURSOR_LEFT_MONITOR:
{
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
@@ -73,7 +90,7 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
StoreWindowParam(window, state);
#if !defined(DEBUG_OVERLAY)
for (; ShowCursor(false) >= 0;)
for (; ShowCursor(false) > 0;)
;
#endif
break;
@@ -96,7 +113,6 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
SetClipBoardToText(text.buffer);
}); });
}
PostMessageW(window, WM_CLOSE, {}, {});
break;
case WM_MOUSEWHEEL:
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
@@ -123,27 +139,20 @@ void DrawMeasureToolTick(const CommonState& commonState,
bool drawFeetOnCross = {};
bool drawHorizontalCrossLine = true;
bool drawVerticalCrossLine = true;
Measurement measuredEdges{};
RECT measuredEdges{};
MeasureToolState::Mode mode = {};
winrt::com_ptr<ID2D1Bitmap> backgroundBitmap;
const MappedTextureView* backgroundTextureToConvert = nullptr;
winrt::com_ptr<ID3D11Texture2D> backgroundTextureToConvert;
bool gotMeasurement = false;
toolState.Read([&](const MeasureToolState& state) {
continuousCapture = state.global.continuousCapture;
drawFeetOnCross = state.global.drawFeetOnCross;
mode = state.global.mode;
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
{
const auto& perScreen = it->second;
if (!perScreen.measuredEdges)
{
return;
}
gotMeasurement = true;
measuredEdges = *perScreen.measuredEdges;
measuredEdges = perScreen.measuredEdges;
if (continuousCapture)
return;
@@ -158,10 +167,6 @@ void DrawMeasureToolTick(const CommonState& commonState,
}
}
});
if (!gotMeasurement)
return;
switch (mode)
{
case MeasureToolState::Mode::Cross:
@@ -180,7 +185,7 @@ void DrawMeasureToolTick(const CommonState& commonState,
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
{
backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.dxgiWindowState.rt, backgroundTextureToConvert);
backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.rt, backgroundTextureToConvert);
if (backgroundBitmap)
{
toolState.Access([&](MeasureToolState& state) {
@@ -191,26 +196,37 @@ void DrawMeasureToolTick(const CommonState& commonState,
}
if (continuousCapture || !backgroundBitmap)
d2dState.dxgiWindowState.rt->Clear();
d2dState.rt->Clear();
const float hMeasure = measuredEdges.Width(Measurement::Unit::Pixel);
const float vMeasure = measuredEdges.Height(Measurement::Unit::Pixel);
// Add 1px to each dim, since the range we obtain from measuredEdges is inclusive.
const float hMeasure = static_cast<float>(measuredEdges.right - measuredEdges.left + 1);
const float vMeasure = static_cast<float>(measuredEdges.bottom - measuredEdges.top + 1);
// Prevent drawing until we get the first capture
const bool hasMeasure = (measuredEdges.right != measuredEdges.left) && (measuredEdges.bottom != measuredEdges.top);
if (!hasMeasure)
{
return;
}
if (!continuousCapture && backgroundBitmap)
{
d2dState.dxgiWindowState.rt->DrawBitmap(backgroundBitmap.get());
d2dState.rt->DrawBitmap(backgroundBitmap.get());
}
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
const auto previousAliasingMode = d2dState.rt->GetAntialiasMode();
// Anti-aliasing is creating artifacts. Aliasing is for drawing straight lines.
d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
d2dState.ToggleAliasedLinesMode(true);
if (drawHorizontalCrossLine)
{
const D2D_POINT_2F hLineStart{ .x = measuredEdges.rect.left, .y = static_cast<float>(cursorPos.y) };
const D2D_POINT_2F hLineStart{ .x = static_cast<float>(measuredEdges.left), .y = static_cast<float>(cursorPos.y) };
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
if (drawFeetOnCross && !continuousCapture)
{
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
@@ -218,37 +234,57 @@ void DrawMeasureToolTick(const CommonState& commonState,
hLineEnd.x -= 1.f;
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
}
}
if (drawVerticalCrossLine)
{
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = measuredEdges.rect.top };
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(measuredEdges.top) };
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
if (drawFeetOnCross && !continuousCapture)
{
vLineEnd.y -= 1.f;
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
d2dState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
}
}
d2dState.ToggleAliasedLinesMode(false);
// After drawing the lines, restore anti aliasing to draw the measurement tooltip.
d2dState.rt->SetAntialiasMode(previousAliasingMode);
uint32_t measureStringBufLen = 0;
OverlayBoxText text;
const auto [crossSymbolPos, measureStringBufLen] =
measuredEdges.Print(text.buffer.data(),
text.buffer.size(),
drawHorizontalCrossLine,
drawVerticalCrossLine,
commonState.units);
std::optional<size_t> crossSymbolPos;
switch (mode)
{
case MeasureToolState::Mode::Cross:
measureStringBufLen = swprintf_s(text.buffer.data(),
text.buffer.size(),
L"%.0f × %.0f",
hMeasure,
vMeasure);
crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
break;
case MeasureToolState::Mode::Vertical:
measureStringBufLen = swprintf_s(text.buffer.data(),
text.buffer.size(),
L"%.0f",
vMeasure);
break;
case MeasureToolState::Mode::Horizontal:
measureStringBufLen = swprintf_s(text.buffer.data(),
text.buffer.size(),
L"%.0f",
hMeasure);
break;
}
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
v = text;
@@ -257,7 +293,8 @@ void DrawMeasureToolTick(const CommonState& commonState,
d2dState.DrawTextBox(text.buffer.data(),
measureStringBufLen,
crossSymbolPos,
D2D_POINT_2F{ static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y) },
static_cast<float>(cursorPos.x),
static_cast<float>(cursorPos.y),
true,
window);
}

View File

@@ -1,91 +0,0 @@
#include "pch.h"
#include "Measurement.h"
Measurement::Measurement(RECT winRect)
{
rect.left = static_cast<float>(winRect.left);
rect.right = static_cast<float>(winRect.right);
rect.top = static_cast<float>(winRect.top);
rect.bottom = static_cast<float>(winRect.bottom);
}
Measurement::Measurement(D2D1_RECT_F d2dRect) :
rect{ d2dRect }
{
}
namespace
{
inline float Convert(const float pixels, const Measurement::Unit units)
{
switch (units)
{
case Measurement::Unit::Pixel:
return pixels;
case Measurement::Unit::Inch:
return pixels / 96.f;
case Measurement::Unit::Centimetre:
return pixels / 96.f * 2.54f;
default:
return pixels;
}
}
}
inline float Measurement::Width(const Unit units) const
{
return Convert(rect.right - rect.left + 1.f, units);
}
inline float Measurement::Height(const Unit units) const
{
return Convert(rect.bottom - rect.top + 1.f, units);
}
Measurement::PrintResult Measurement::Print(wchar_t* buf,
const size_t bufSize,
const bool printWidth,
const bool printHeight,
const Unit units) const
{
PrintResult result;
if (printWidth)
{
result.strLen += swprintf_s(buf,
bufSize,
L"%g",
Width(units));
if (printHeight)
{
result.crossSymbolPos = result.strLen + 1;
result.strLen += swprintf_s(buf + result.strLen,
bufSize - result.strLen,
L" \x00D7 ");
}
}
if (printHeight)
{
result.strLen += swprintf_s(buf + result.strLen,
bufSize - result.strLen,
L"%g",
Height(units));
}
switch (units)
{
case Measurement::Unit::Inch:
result.strLen += swprintf_s(buf + result.strLen,
bufSize - result.strLen,
L" in");
break;
case Measurement::Unit::Centimetre:
result.strLen += swprintf_s(buf + result.strLen,
bufSize - result.strLen,
L" cm");
break;
}
return result;
}

View File

@@ -1,38 +0,0 @@
#pragma once
#include <dcommon.h>
#include <windef.h>
struct Measurement
{
enum Unit
{
Pixel,
Inch,
Centimetre
};
D2D1_RECT_F rect = {}; // corners are inclusive
Measurement() = default;
Measurement(const Measurement&) = default;
Measurement& operator=(const Measurement&) = default;
explicit Measurement(D2D1_RECT_F d2dRect);
explicit Measurement(RECT winRect);
float Width(const Unit units) const;
float Height(const Unit units) const;
struct PrintResult
{
std::optional<size_t> crossSymbolPos;
size_t strLen = {};
};
PrintResult Print(wchar_t* buf,
const size_t bufSize,
const bool printWidth,
const bool printHeight,
const Unit units) const;
};

View File

@@ -1,7 +1,6 @@
#include "pch.h"
#include "BoundsToolOverlayUI.h"
#include "constants.h"
#include "MeasureToolOverlayUI.h"
#include "OverlayUI.h"
@@ -23,17 +22,16 @@ void CreateOverlayWindowClasses()
wcex.lpfnWndProc = MeasureToolWndProc;
wcex.lpszClassName = NonLocalizable::MeasureToolOverlayWindowName;
wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
RegisterClassExW(&wcex);
wcex.lpfnWndProc = BoundsToolWndProc;
wcex.lpszClassName = NonLocalizable::BoundsToolOverlayWindowName;
wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
RegisterClassExW(&wcex);
}
HWND CreateOverlayUIWindow(const CommonState& commonState,
const MonitorInfo& monitor,
const bool excludeFromCapture,
const wchar_t* windowClass,
void* extraParam)
{
@@ -41,40 +39,20 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
std::call_once(windowClassesCreatedFlag, CreateOverlayWindowClasses);
const auto screenArea = monitor.GetScreenSize(true);
DWORD windowStyle = WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW;
#if !defined(DEBUG_OVERLAY)
windowStyle |= WS_EX_TOPMOST;
#endif
HWND window{
CreateWindowExW(windowStyle,
windowClass,
L"PowerToys.MeasureToolOverlay",
WS_POPUP | CS_HREDRAW | CS_VREDRAW,
screenArea.left(),
screenArea.top(),
screenArea.width(),
screenArea.height(),
HWND_DESKTOP,
nullptr,
GetModuleHandleW(nullptr),
extraParam)
};
HWND window{ CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
windowClass,
L"PowerToys.MeasureToolOverlay",
WS_POPUP,
screenArea.left(),
screenArea.top(),
screenArea.width(),
screenArea.height(),
HWND_DESKTOP,
nullptr,
GetModuleHandleW(nullptr),
extraParam) };
winrt::check_bool(window);
// Exclude overlay window from displaying in WIN+TAB preview, since WS_EX_TOOLWINDOW windows are displayed simultaneously on all virtual desktops.
// We can't remove WS_EX_TOOLWINDOW/WS_EX_NOACTIVATE flag, since we want to exclude the window from taskbar
BOOL val = TRUE;
DwmSetWindowAttribute(window, DWMWA_EXCLUDED_FROM_PEEK, &val, sizeof(val));
// We want to receive input events as soon as possible to prevent issues with touch input
RegisterTouchWindow(window, TWF_WANTPALM);
ShowWindow(window, SW_SHOWNORMAL);
UpdateWindow(window);
if (excludeFromCapture)
{
SetWindowDisplayAffinity(window, WDA_EXCLUDEFROMCAPTURE);
}
#if !defined(DEBUG_OVERLAY)
SetWindowPos(window, HWND_TOPMOST, {}, {}, {}, {}, SWP_NOMOVE | SWP_NOSIZE);
#else
@@ -109,13 +87,13 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineColor)
{
D2D1::ColorF foreground = D2D1::ColorF::Black;
D2D1::ColorF background = D2D1::ColorF(0.96f, 0.96f, 0.96f, .93f);
D2D1::ColorF background = D2D1::ColorF(0.96f, 0.96f, 0.96f, 1.0f);
D2D1::ColorF border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f);
if (WindowsColors::is_dark_mode())
{
foreground = D2D1::ColorF::White;
background = D2D1::ColorF(0.17f, 0.17f, 0.17f, .93f);
background = D2D1::ColorF(0.17f, 0.17f, 0.17f, 1.0f);
border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f);
}
@@ -124,60 +102,56 @@ std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineCo
void OverlayUIState::RunUILoop()
{
bool cursorOnScreen = false;
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto cursor = _commonState.cursorPosSystemSpace;
const bool cursorOnScreen = _monitorArea.inside(cursor);
const bool cursorOverToolbar = _commonState.toolbarBoundingBox.inside(cursor);
auto& dxgi = _d2dState.dxgiWindowState;
if (_monitorArea.inside(cursor) != cursorOnScreen)
if (cursorOnScreen != _cursorOnScreen)
{
cursorOnScreen = !cursorOnScreen;
_cursorOnScreen = cursorOnScreen;
if (!cursorOnScreen)
{
if (_clearOnCursorLeavingScreen)
{
_d2dState.rt->BeginDraw();
_d2dState.rt->Clear();
_d2dState.rt->EndDraw();
}
PostMessageW(_window, WM_CURSOR_LEFT_MONITOR, {}, {});
}
}
run_message_loop(true, 1);
dxgi.rt->BeginDraw();
dxgi.rt->Clear();
if (!cursorOverToolbar)
_tickFunc();
dxgi.rt->EndDraw();
dxgi.swapChain->Present(0, 0);
if (cursorOnScreen)
{
const auto frameTime = std::chrono::high_resolution_clock::now() - now;
if (frameTime < consts::TARGET_FRAME_DURATION)
_d2dState.rt->BeginDraw();
if (!cursorOverToolbar)
_tickFunc();
else
_d2dState.rt->Clear();
{
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION - frameTime);
// TODO: use latch to wait until all threads are created their corresponding d2d textures
// in the non-continuous mode
// std::lock_guard guard{ gpuAccessLock };
_d2dState.rt->EndDraw();
}
}
else
{
// Don't consume resources while nothing could be updated
std::this_thread::sleep_for(std::chrono::milliseconds{ 200 });
}
run_message_loop(true, 1);
}
DestroyWindow(_window);
}
template<typename StateT, typename TickFuncT>
OverlayUIState::OverlayUIState(const DxgiAPI* dxgiAPI,
StateT& toolState,
OverlayUIState::OverlayUIState(StateT& toolState,
TickFuncT tickFunc,
const CommonState& commonState,
HWND window) :
_window{ window },
_commonState{ commonState },
_d2dState{ dxgiAPI, window, AppendCommonOverlayUIColors(commonState.lineColor) },
_d2dState{ window, AppendCommonOverlayUIColors(commonState.lineColor) },
_tickFunc{ [this, tickFunc, &toolState] {
tickFunc(_commonState, toolState, _window, _d2dState);
} }
@@ -199,30 +173,25 @@ OverlayUIState::~OverlayUIState()
// Returning unique_ptr, since we need to pin ui state in memory
template<typename ToolT, typename TickFuncT>
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(const DxgiAPI* dxgi,
ToolT& toolState,
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& toolState,
TickFuncT tickFunc,
CommonState& commonState,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor,
const bool excludeFromCapture)
const bool clearOnCursorLeavingScreen)
{
wil::shared_event uiCreatedEvent(wil::EventOptions::ManualReset);
std::unique_ptr<OverlayUIState> uiState;
std::thread threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
OverlayUIState* state = nullptr;
{
auto sinalUICreatedEvent = wil::scope_exit([&] { uiCreatedEvent.SetEvent(); });
const HWND window = CreateOverlayUIWindow(commonState, monitor, excludeFromCapture, toolWindowClassName, windowParam);
uiState = std::unique_ptr<OverlayUIState>{ new OverlayUIState{ dxgi, toolState, tickFunc, commonState, window } };
uiState->_monitorArea = monitor.GetScreenSize(true);
// we must create window + d2d state in the same thread, then store thread handle in uiState, thus
// lifetime is ok here, since we join the thread in destructor
state = uiState.get();
}
auto threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
const HWND window = CreateOverlayUIWindow(commonState, monitor, toolWindowClassName, windowParam);
uiState = std::unique_ptr<OverlayUIState>{ new OverlayUIState{ toolState, tickFunc, commonState, window } };
uiState->_monitorArea = monitor.GetScreenSize(true);
uiState->_clearOnCursorLeavingScreen = clearOnCursorLeavingScreen;
// we must create window + d2d state in the same thread, then store thread handle in uiState, thus
// lifetime is ok here, since we join the thread in destructor
auto* state = uiState.get();
uiCreatedEvent.SetEvent();
state->RunUILoop();
@@ -231,40 +200,28 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(const Dxgi
});
uiCreatedEvent.wait();
if (uiState)
uiState->_uiThread = std::move(threadHandle);
else if (threadHandle.joinable())
threadHandle.join();
uiState->_uiThread = std::move(threadHandle);
return uiState;
}
std::unique_ptr<OverlayUIState> OverlayUIState::Create(const DxgiAPI* dxgi,
Serialized<MeasureToolState>& toolState,
std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolState>& toolState,
CommonState& commonState,
const MonitorInfo& monitor)
{
bool excludeFromCapture = false;
toolState.Read([&](const MeasureToolState& s) {
excludeFromCapture = s.global.continuousCapture;
});
return OverlayUIState::CreateInternal(dxgi,
toolState,
return OverlayUIState::CreateInternal(toolState,
DrawMeasureToolTick,
commonState,
NonLocalizable::MeasureToolOverlayWindowName,
&toolState,
monitor,
excludeFromCapture);
true);
}
std::unique_ptr<OverlayUIState> OverlayUIState::Create(const DxgiAPI* dxgi,
BoundsToolState& toolState,
std::unique_ptr<OverlayUIState> OverlayUIState::Create(BoundsToolState& toolState,
CommonState& commonState,
const MonitorInfo& monitor)
{
return OverlayUIState::CreateInternal(dxgi,
toolState,
return OverlayUIState::CreateInternal(toolState,
DrawBoundsToolTick,
commonState,
NonLocalizable::BoundsToolOverlayWindowName,

View File

@@ -1,8 +1,6 @@
#pragma once
#include "DxgiAPI.h"
#include "D2DState.h"
#include "ToolState.h"
#include <common/display/monitors.h>
@@ -11,8 +9,7 @@
class OverlayUIState final
{
template<typename StateT, typename TickFuncT>
OverlayUIState(const DxgiAPI* dxgiAPI,
StateT& toolState,
OverlayUIState(StateT& toolState,
TickFuncT tickFunc,
const CommonState& commonState,
HWND window);
@@ -23,27 +20,26 @@ class OverlayUIState final
D2DState _d2dState;
std::function<void()> _tickFunc;
std::thread _uiThread;
bool _cursorOnScreen = true;
bool _clearOnCursorLeavingScreen = false;
template<typename ToolT, typename TickFuncT>
static std::unique_ptr<OverlayUIState> CreateInternal(const DxgiAPI* dxgi,
ToolT& toolState,
static std::unique_ptr<OverlayUIState> CreateInternal(ToolT& toolState,
TickFuncT tickFunc,
CommonState& commonState,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor,
const bool excludeFromCapture);
const bool clearOnCursorLeavingScreen);
public:
OverlayUIState(OverlayUIState&&) noexcept = default;
~OverlayUIState();
static std::unique_ptr<OverlayUIState> Create(const DxgiAPI* dxgi,
BoundsToolState& toolState,
static std::unique_ptr<OverlayUIState> Create(BoundsToolState& toolState,
CommonState& commonState,
const MonitorInfo& monitor);
static std::unique_ptr<OverlayUIState> Create(const DxgiAPI* dxgi,
Serialized<MeasureToolState>& toolState,
static std::unique_ptr<OverlayUIState> Create(Serialized<MeasureToolState>& toolState,
CommonState& commonState,
const MonitorInfo& monitor);
inline HWND overlayWindowHandle() const

View File

@@ -3,12 +3,12 @@
#include "PerGlyphOpacityTextRender.h"
PerGlyphOpacityTextRender::PerGlyphOpacityTextRender(
winrt::com_ptr<ID2D1Factory> pD2DFactory,
winrt::com_ptr<ID2D1RenderTarget> rt,
winrt::com_ptr<ID2D1SolidColorBrush> baseBrush) :
_pD2DFactory{ pD2DFactory.get() },
_rt{ rt.get() },
_baseBrush{ baseBrush.get() }
wil::com_ptr<ID2D1Factory> pD2DFactory,
wil::com_ptr<ID2D1HwndRenderTarget> rt,
wil::com_ptr<ID2D1SolidColorBrush> baseBrush) :
_pD2DFactory{ pD2DFactory },
_rt{ rt },
_baseBrush{ baseBrush }
{
}
@@ -18,22 +18,20 @@ HRESULT __stdcall PerGlyphOpacityTextRender::DrawGlyphRun(void* /*clientDrawingC
DWRITE_MEASURING_MODE measuringMode,
_In_ const DWRITE_GLYPH_RUN* glyphRun,
_In_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/,
IUnknown* clientDrawingEffect_) noexcept
IUnknown* clientDrawingEffect) noexcept
{
HRESULT hr = S_OK;
if (!clientDrawingEffect_)
if (!clientDrawingEffect)
{
_rt->DrawGlyphRun(D2D1_POINT_2F{ .x = baselineOriginX, .y = baselineOriginY }, glyphRun, _baseBrush, measuringMode);
_rt->DrawGlyphRun(D2D1_POINT_2F{ .x = baselineOriginX, .y = baselineOriginY }, glyphRun, _baseBrush.get(), measuringMode);
return hr;
}
wil::com_ptr<IUnknown> clientDrawingEffect{ clientDrawingEffect_ };
// Create the path geometry.
wil::com_ptr<ID2D1PathGeometry> pathGeometry;
hr = _pD2DFactory->CreatePathGeometry(&pathGeometry);
// Write to the path geometry using the geometry sink.
wil::com_ptr<ID2D1GeometrySink> pSink;
ID2D1GeometrySink* pSink = nullptr;
if (SUCCEEDED(hr))
{
hr = pathGeometry->Open(&pSink);
@@ -51,10 +49,11 @@ HRESULT __stdcall PerGlyphOpacityTextRender::DrawGlyphRun(void* /*clientDrawingC
glyphRun->glyphCount,
glyphRun->isSideways,
glyphRun->bidiLevel % 2,
pSink.get());
pSink);
}
if (pSink)
// Close the geometry sink
if (SUCCEEDED(hr))
{
hr = pSink->Close();
}
@@ -71,15 +70,21 @@ HRESULT __stdcall PerGlyphOpacityTextRender::DrawGlyphRun(void* /*clientDrawingC
}
float prevOpacity = _baseBrush->GetOpacity();
auto opacityEffect = clientDrawingEffect.try_query<IDrawingEffect>();
if (opacityEffect)
_baseBrush->SetOpacity(static_cast<OpacityEffect*>(opacityEffect.get())->alpha);
OpacityEffect* opacityEffect = nullptr;
if (SUCCEEDED(hr))
{
hr = clientDrawingEffect->QueryInterface(__uuidof(IDrawingEffect), reinterpret_cast<void**>(&opacityEffect));
}
if (SUCCEEDED(hr))
{
_rt->DrawGeometry(pTransformedGeometry.get(), _baseBrush);
_rt->FillGeometry(pTransformedGeometry.get(), _baseBrush);
_baseBrush->SetOpacity(opacityEffect->alpha);
}
if (SUCCEEDED(hr))
{
_rt->DrawGeometry(pTransformedGeometry.get(), _baseBrush.get());
_rt->FillGeometry(pTransformedGeometry.get(), _baseBrush.get());
_baseBrush->SetOpacity(prevOpacity);
}

View File

@@ -16,14 +16,14 @@ struct OpacityEffect : winrt::implements<OpacityEffect, IDrawingEffect>
struct PerGlyphOpacityTextRender : winrt::implements<PerGlyphOpacityTextRender, IDWriteTextRenderer>
{
ID2D1Factory* _pD2DFactory = nullptr;
ID2D1RenderTarget* _rt = nullptr;
ID2D1SolidColorBrush* _baseBrush = nullptr;
wil::com_ptr<ID2D1Factory> _pD2DFactory;
wil::com_ptr<ID2D1HwndRenderTarget> _rt;
wil::com_ptr<ID2D1SolidColorBrush> _baseBrush;
PerGlyphOpacityTextRender(
winrt::com_ptr<ID2D1Factory> pD2DFactory,
winrt::com_ptr<ID2D1RenderTarget> rt,
winrt::com_ptr<ID2D1SolidColorBrush> baseBrush);
wil::com_ptr<ID2D1Factory> pD2DFactory,
wil::com_ptr<ID2D1HwndRenderTarget> rt,
wil::com_ptr<ID2D1SolidColorBrush> baseBrush);
HRESULT __stdcall DrawGlyphRun(void* clientDrawingContext,
FLOAT baselineOriginX,

View File

@@ -3,7 +3,6 @@
#include <common/display/dpi_aware.h>
#include <common/display/monitors.h>
#include <common/utils/logger_helper.h>
#include <common/utils/UnhandledExceptionHandler.h>
#include <common/logger/logger.h>
#include "../MeasureToolModuleInterface/trace.h"
@@ -15,6 +14,8 @@
//#define DEBUG_PRIMARY_MONITOR_ONLY
std::recursive_mutex gpuAccessLock;
namespace winrt::PowerToys::MeasureToolCore::implementation
{
void Core::MouseCaptureThread()
@@ -30,36 +31,20 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
}
Core::Core() :
_stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset },
_mouseCaptureThread{ [this] { MouseCaptureThread(); } }
_mouseCaptureThread{ [this] { MouseCaptureThread(); } },
_stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset }
{
Trace::RegisterProvider();
LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
}
Core::~Core()
{
Close();
}
_stopMouseCaptureThreadSignal.SetEvent();
_mouseCaptureThread.join();
void Core::Close()
{
ResetState();
// avoid triggering d2d debug layer leak on shutdown
dxgiAPI = DxgiAPI{ DxgiAPI::Uninitialized{} };
#if 0
winrt::com_ptr<IDXGIDebug> dxgiDebug;
winrt::check_hresult(DXGIGetDebugInterface1({},
winrt::guid_of<IDXGIDebug>(),
dxgiDebug.put_void()));
dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL);
#endif
if (!_stopMouseCaptureThreadSignal.is_signaled())
_stopMouseCaptureThreadSignal.SetEvent();
if (_mouseCaptureThread.joinable())
_mouseCaptureThread.join();
Trace::UnregisterProvider();
}
void Core::ResetState()
@@ -82,7 +67,6 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
_settings = Settings::LoadFromFile();
_commonState.units = _settings.units;
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
@@ -94,17 +78,12 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
ResetState();
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
std::vector<MonitorInfo> monitors = { MonitorInfo::GetPrimaryMonitor() };
const auto& monitorInfo = monitors[0];
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
#else
const auto monitors = MonitorInfo::GetMonitors(true);
for (const auto& monitorInfo : monitors)
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
#endif
{
auto overlayUI = OverlayUIState::Create(&dxgiAPI,
_boundsToolState,
_commonState,
monitorInfo);
auto overlayUI = OverlayUIState::Create(_boundsToolState, _commonState, monitorInfo);
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
if (!overlayUI)
continue;
@@ -131,35 +110,22 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
});
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
std::vector<MonitorInfo> monitors = { MonitorInfo::GetPrimaryMonitor() };
const auto& monitorInfo = monitors[0];
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
#else
const auto monitors = MonitorInfo::GetMonitors(true);
for (const auto& monitorInfo : monitors)
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
#endif
{
auto overlayUI = OverlayUIState::Create(&dxgiAPI,
_measureToolState,
_commonState,
monitorInfo);
auto overlayUI = OverlayUIState::Create(_measureToolState, _commonState, monitorInfo);
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
if (!overlayUI)
return;
continue;
#endif
_screenCaptureThreads.emplace_back(StartCapturingThread(_commonState,
_measureToolState,
overlayUI->overlayWindowHandle(),
monitorInfo));
_overlayUIStates.push_back(std::move(overlayUI));
}
for (size_t i = 0; i < monitors.size(); ++i)
{
auto thread = StartCapturingThread(
&dxgiAPI,
_commonState,
_measureToolState,
_overlayUIStates[i]->overlayWindowHandle(),
monitors[i]);
_screenCaptureThreads.emplace_back(std::move(thread));
}
Trace::MeasureToolActivated();
}

View File

@@ -6,31 +6,13 @@
#include "Settings.h"
#include <common/utils/serialized.h>
#include "ScreenCapturing.h"
struct PowerToysMisc
{
PowerToysMisc()
{
Trace::RegisterProvider();
LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
InitUnhandledExceptionHandler();
}
~PowerToysMisc()
{
Trace::UnregisterProvider();
}
};
namespace winrt::PowerToys::MeasureToolCore::implementation
{
struct Core : PowerToysMisc, CoreT<Core>
struct Core : CoreT<Core>
{
Core();
~Core();
void Close();
void StartBoundsTool();
void StartMeasureTool(const bool horizontal, const bool vertical);
void SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger);
@@ -39,11 +21,9 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
float GetDPIScaleForWindow(uint64_t windowHandle);
void MouseCaptureThread();
DxgiAPI dxgiAPI;
wil::shared_event _stopMouseCaptureThreadSignal;
std::thread _mouseCaptureThread;
std::vector<std::thread> _screenCaptureThreads;
wil::shared_event _stopMouseCaptureThreadSignal;
std::vector<std::unique_ptr<OverlayUIState>> _overlayUIStates;
Serialized<MeasureToolState> _measureToolState;

View File

@@ -11,7 +11,7 @@ namespace PowerToys
delegate void ToolSessionCompleted();
[default_interface]
runtimeclass Core : Windows.Foundation.IClosable
runtimeclass Core
{
Core();
void SetToolCompletionEvent(event ToolSessionCompleted completionTrigger);

View File

@@ -55,7 +55,7 @@
<SubSystem>Windows</SubSystem>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
<ModuleDefinitionFile>MeasureTool.def</ModuleDefinitionFile>
<AdditionalDependencies>Dbghelp.lib;Shell32.lib;Shcore.lib;dcomp.lib;DXGI.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Shell32.lib;Shcore.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
@@ -74,11 +74,8 @@
<ClInclude Include="..\MeasureToolModuleInterface\trace.h" />
<ClInclude Include="BoundsToolOverlayUI.h" />
<ClInclude Include="Clipboard.h" />
<ClInclude Include="CoordinateSystemConversion.h" />
<ClInclude Include="constants.h" />
<ClInclude Include="D2DState.h" />
<ClInclude Include="DxgiAPI.h" />
<ClInclude Include="Measurement.h" />
<ClInclude Include="MeasureToolOverlayUI.h" />
<ClInclude Include="PerGlyphOpacityTextRender.h" />
<ClInclude Include="resource.h" />
@@ -99,9 +96,7 @@
<ClCompile Include="BoundsToolOverlayUI.cpp" />
<ClCompile Include="Clipboard.cpp" />
<ClCompile Include="D2DState.cpp" />
<ClCompile Include="DxgiAPI.cpp" />
<ClCompile Include="EdgeDetection.cpp" />
<ClCompile Include="Measurement.cpp" />
<ClCompile Include="MeasureToolOverlayUI.cpp" />
<ClCompile Include="OverlayUI.cpp" />
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />

View File

@@ -17,8 +17,6 @@
<ClCompile Include="..\MeasureToolModuleInterface\trace.cpp" />
<ClCompile Include="Clipboard.cpp" />
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
<ClCompile Include="Measurement.cpp" />
<ClCompile Include="DxgiAPI.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -36,9 +34,6 @@
<ClInclude Include="Clipboard.h" />
<ClInclude Include="PerGlyphOpacityTextRender.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="CoordinateSystemConversion.h" />
<ClInclude Include="Measurement.h" />
<ClInclude Include="DxgiAPI.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="Assets">

View File

@@ -10,47 +10,70 @@
//#define DEBUG_EDGES
namespace
class MappedTextureView
{
winrt::GraphicsCaptureItem CreateCaptureItemForMonitor(HMONITOR monitor)
winrt::com_ptr<ID3D11DeviceContext> context;
winrt::com_ptr<ID3D11Texture2D> texture;
public:
BGRATextureView view;
MappedTextureView(winrt::com_ptr<ID3D11Texture2D> _texture,
winrt::com_ptr<ID3D11DeviceContext> _context,
const size_t textureWidth,
const size_t textureHeight) :
texture{ std::move(_texture) }, context{ std::move(_context) }
{
auto captureInterop = winrt::get_activation_factory<
winrt::GraphicsCaptureItem,
IGraphicsCaptureItemInterop>();
D3D11_TEXTURE2D_DESC desc;
texture->GetDesc(&desc);
winrt::GraphicsCaptureItem item = nullptr;
D3D11_MAPPED_SUBRESOURCE resource = {};
winrt::check_hresult(context->Map(texture.get(), D3D11CalcSubresource(0, 0, 0), D3D11_MAP_READ, 0, &resource));
winrt::check_hresult(captureInterop->CreateForMonitor(
monitor,
winrt::guid_of<winrt::GraphicsCaptureItem>(),
winrt::put_abi(item)));
return item;
view.pixels = static_cast<const uint32_t*>(resource.pData);
view.pitch = resource.RowPitch / 4;
view.width = textureWidth;
view.height = textureHeight;
}
}
MappedTextureView(MappedTextureView&&) = default;
MappedTextureView& operator=(MappedTextureView&&) = default;
inline winrt::com_ptr<ID3D11Texture2D> GetTexture() const
{
return texture;
}
~MappedTextureView()
{
if (context && texture)
context->Unmap(texture.get(), D3D11CalcSubresource(0, 0, 0));
}
};
class D3DCaptureState final
{
DxgiAPI* dxgiAPI = nullptr;
winrt::com_ptr<ID3D11Device> d3dDevice;
winrt::IDirect3DDevice device;
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::com_ptr<ID3D11DeviceContext> context;
winrt::SizeInt32 frameSize;
HMONITOR monitor = {};
winrt::DirectXPixelFormat pixelFormat;
winrt::Direct3D11CaptureFramePool framePool = nullptr;
winrt::GraphicsCaptureSession session = nullptr;
winrt::DirectXPixelFormat pixelFormat;
winrt::Direct3D11CaptureFramePool framePool;
winrt::GraphicsCaptureSession session;
std::function<void(MappedTextureView)> frameCallback;
Box monitorArea;
bool continuousCapture = false;
bool captureOutsideOfMonitor = false;
D3DCaptureState(DxgiAPI* dxgiAPI,
winrt::com_ptr<IDXGISwapChain1> swapChain,
winrt::DirectXPixelFormat pixelFormat,
MonitorInfo monitorInfo,
const bool continuousCapture);
D3DCaptureState(winrt::com_ptr<ID3D11Device> d3dDevice,
winrt::IDirect3DDevice _device,
winrt::com_ptr<IDXGISwapChain1> _swapChain,
winrt::com_ptr<ID3D11DeviceContext> _context,
const winrt::GraphicsCaptureItem& item,
winrt::DirectXPixelFormat _pixelFormat,
Box monitorArea,
const bool captureOutsideOfMonitor);
winrt::com_ptr<ID3D11Texture2D> CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& texture);
@@ -58,13 +81,13 @@ class D3DCaptureState final
void StartSessionInPreferredMode();
std::mutex frameArrivedMutex;
std::mutex destructorMutex;
public:
static std::unique_ptr<D3DCaptureState> Create(DxgiAPI* dxgiAPI,
MonitorInfo monitorInfo,
static std::unique_ptr<D3DCaptureState> Create(winrt::GraphicsCaptureItem item,
const winrt::DirectXPixelFormat pixelFormat,
const bool continuousCapture);
Box monitorSize,
const bool captureOutsideOfMonitor);
~D3DCaptureState();
@@ -74,19 +97,26 @@ public:
void StopCapture();
};
D3DCaptureState::D3DCaptureState(DxgiAPI* dxgiAPI,
D3DCaptureState::D3DCaptureState(winrt::com_ptr<ID3D11Device> _d3dDevice,
winrt::IDirect3DDevice _device,
winrt::com_ptr<IDXGISwapChain1> _swapChain,
winrt::DirectXPixelFormat pixelFormat_,
MonitorInfo monitorInfo,
const bool continuousCapture_) :
dxgiAPI{ dxgiAPI },
device{ dxgiAPI->d3dForCapture.d3dDeviceInspectable.as<winrt::IDirect3DDevice>() },
winrt::com_ptr<ID3D11DeviceContext> _context,
const winrt::GraphicsCaptureItem& item,
winrt::DirectXPixelFormat _pixelFormat,
Box _monitorArea,
const bool _captureOutsideOfMonitor) :
d3dDevice{ std::move(_d3dDevice) },
device{ std::move(_device) },
swapChain{ std::move(_swapChain) },
pixelFormat{ std::move(pixelFormat_) },
monitor{ monitorInfo.GetHandle() },
monitorArea{ monitorInfo.GetScreenSize(true) },
continuousCapture{ continuousCapture_ }
context{ std::move(_context) },
frameSize{ item.Size() },
pixelFormat{ std::move(_pixelFormat) },
framePool{ winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 2, item.Size()) },
session{ framePool.CreateCaptureSession(item) },
monitorArea{ _monitorArea },
captureOutsideOfMonitor{ _captureOutsideOfMonitor }
{
framePool.FrameArrived({ this, &D3DCaptureState::OnFrameArrived });
}
winrt::com_ptr<ID3D11Texture2D> D3DCaptureState::CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& frameTexture)
@@ -99,8 +129,8 @@ winrt::com_ptr<ID3D11Texture2D> D3DCaptureState::CopyFrameToCPU(const winrt::com
desc.BindFlags = 0;
winrt::com_ptr<ID3D11Texture2D> cpuTexture;
winrt::check_hresult(dxgiAPI->d3dForCapture.d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
dxgiAPI->d3dForCapture.d3dContext->CopyResource(cpuTexture.get(), frameTexture.get());
winrt::check_hresult(d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
context->CopyResource(cpuTexture.get(), frameTexture.get());
return cpuTexture;
}
@@ -117,25 +147,15 @@ auto GetDXGIInterfaceFromObject(winrt::IInspectable const& object)
void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& sender, const winrt::IInspectable&)
{
// Prevent calling a callback on a partially destroyed state
std::lock_guard callbackLock{ frameArrivedMutex };
std::unique_lock callbackLock{ destructorMutex };
bool resized = false;
POINT cursorPos = {};
GetCursorPos(&cursorPos);
winrt::Direct3D11CaptureFrame frame = nullptr;
try
{
frame = sender.TryGetNextFrame();
}
catch (...)
{
}
if (!frame)
return;
if (monitorArea.inside(cursorPos) || !continuousCapture)
auto frame = sender.TryGetNextFrame();
winrt::check_bool(frame);
if (monitorArea.inside(cursorPos) || captureOutsideOfMonitor)
{
winrt::com_ptr<ID3D11Texture2D> texture;
{
@@ -151,14 +171,9 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
}
winrt::check_hresult(swapChain->GetBuffer(0, winrt::guid_of<ID3D11Texture2D>(), texture.put_void()));
auto surface = frame.Surface();
auto gpuTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(surface);
auto gpuTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
texture = CopyFrameToCPU(gpuTexture);
surface.Close();
MappedTextureView textureView{ texture,
dxgiAPI->d3dForCapture.d3dContext,
static_cast<size_t>(frameSize.Width),
static_cast<size_t>(frameSize.Height) };
MappedTextureView textureView{ texture, context, static_cast<size_t>(frameSize.Width), static_cast<size_t>(frameSize.Height) };
frameCallback(std::move(textureView));
}
@@ -166,60 +181,103 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
frame.Close();
DXGI_PRESENT_PARAMETERS presentParameters = {};
swapChain->Present1(1, 0, &presentParameters);
if (resized)
{
framePool.Recreate(device, pixelFormat, 2, frameSize);
}
}
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(DxgiAPI* dxgiAPI,
MonitorInfo monitorInfo,
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(winrt::GraphicsCaptureItem item,
const winrt::DirectXPixelFormat pixelFormat,
const bool continuousCapture)
Box monitorArea,
const bool captureOutsideOfMonitor)
{
const auto dims = monitorInfo.GetScreenSize(true);
std::lock_guard guard{ gpuAccessLock };
winrt::com_ptr<ID3D11Device> d3dDevice;
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifndef NDEBUG
flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
HRESULT hr =
D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3dDevice.put(),
nullptr,
nullptr);
if (hr == DXGI_ERROR_UNSUPPORTED)
{
hr = D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_WARP,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3dDevice.put(),
nullptr,
nullptr);
}
winrt::check_hresult(hr);
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), d3dDeviceInspectable.put()));
const DXGI_SWAP_CHAIN_DESC1 desc = {
.Width = static_cast<uint32_t>(dims.width()),
.Height = static_cast<uint32_t>(dims.height()),
.Width = static_cast<uint32_t>(item.Size().Width),
.Height = static_cast<uint32_t>(item.Size().Height),
.Format = static_cast<DXGI_FORMAT>(pixelFormat),
.SampleDesc = { .Count = 1, .Quality = 0 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.Scaling = DXGI_SCALING_STRETCH,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
};
winrt::com_ptr<IDXGIAdapter> adapter;
winrt::check_hresult(dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
winrt::com_ptr<IDXGIFactory2> factory;
winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), factory.put_void()));
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::check_hresult(dxgiAPI->d3dForCapture.dxgiFactory2->CreateSwapChainForComposition(dxgiAPI->d3dForCapture.d3dDevice.get(),
&desc,
nullptr,
swapChain.put()));
winrt::check_hresult(factory->CreateSwapChainForComposition(d3dDevice.get(), &desc, nullptr, swapChain.put()));
winrt::com_ptr<ID3D11DeviceContext> context;
d3dDevice->GetImmediateContext(context.put());
winrt::check_bool(context);
// We must create the object in a heap, since we need to pin it in memory to receive callbacks
auto statePtr = new D3DCaptureState{ dxgiAPI,
auto statePtr = new D3DCaptureState{ d3dDevice,
d3dDeviceInspectable.as<winrt::IDirect3DDevice>(),
std::move(swapChain),
std::move(context),
item,
pixelFormat,
std::move(monitorInfo),
continuousCapture };
monitorArea,
captureOutsideOfMonitor };
return std::unique_ptr<D3DCaptureState>{ statePtr };
}
D3DCaptureState::~D3DCaptureState()
{
std::unique_lock callbackLock{ frameArrivedMutex };
std::unique_lock callbackLock{ destructorMutex };
StopCapture();
framePool.Close();
device.Close();
}
void D3DCaptureState::StartSessionInPreferredMode()
{
auto item = CreateCaptureItemForMonitor(monitor);
frameSize = item.Size();
framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 2, item.Size());
session = framePool.CreateCaptureSession(item);
framePool.FrameArrived({ this, &D3DCaptureState::OnFrameArrived });
// Try disable border if possible (available on Windows ver >= 20348)
if (auto session3 = session.try_as<winrt::IGraphicsCaptureSession3>())
{
@@ -242,42 +300,34 @@ MappedTextureView D3DCaptureState::CaptureSingleFrame()
wil::shared_event frameArrivedEvent(wil::EventOptions::ManualReset);
frameCallback = [frameArrivedEvent, &result, this](MappedTextureView tex) {
if (frameArrivedEvent.is_signaled())
if (result)
return;
StopCapture();
result.emplace(std::move(tex));
frameArrivedEvent.SetEvent();
};
std::lock_guard guard{ gpuAccessLock };
StartSessionInPreferredMode();
frameArrivedEvent.wait();
assert(result.has_value());
return std::move(*result);
}
void D3DCaptureState::StopCapture()
{
try
{
if (session)
session.Close();
if (framePool)
framePool.Close();
}
catch (...)
{
// RPC call might fail here
}
session.Close();
}
void UpdateCaptureState(const CommonState& commonState,
Serialized<MeasureToolState>& state,
HWND window,
const MappedTextureView& textureView)
const MappedTextureView& textureView,
const bool continuousCapture)
{
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
const auto cursorPos = convert::FromSystemToRelative(window, commonState.cursorPosSystemSpace);
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
uint8_t pixelTolerance = {};
@@ -297,7 +347,8 @@ void UpdateCaptureState(const CommonState& commonState,
const RECT bounds = DetectEdges(textureView.view,
cursorPos,
perColorChannelEdgeDetection,
pixelTolerance);
pixelTolerance,
continuousCapture);
#if defined(DEBUG_EDGES)
char buffer[256];
@@ -314,61 +365,55 @@ void UpdateCaptureState(const CommonState& commonState,
OutputDebugStringA(buffer);
#endif
state.Access([&](MeasureToolState& state) {
state.perScreen[window].measuredEdges = Measurement{ bounds };
state.perScreen[window].measuredEdges = bounds;
});
}
std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
const CommonState& commonState,
std::thread StartCapturingThread(const CommonState& commonState,
Serialized<MeasureToolState>& state,
HWND window,
MonitorInfo monitor)
MonitorInfo targetMonitor)
{
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, monitor, window, dxgiAPI] {
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, targetMonitor, window] {
auto captureInterop = winrt::get_activation_factory<
winrt::GraphicsCaptureItem,
IGraphicsCaptureItemInterop>();
winrt::GraphicsCaptureItem item = nullptr;
winrt::check_hresult(captureInterop->CreateForMonitor(
targetMonitor.GetHandle(),
winrt::guid_of<winrt::GraphicsCaptureItem>(),
winrt::put_abi(item)));
bool continuousCapture = {};
state.Read([&](const MeasureToolState& state) {
continuousCapture = state.global.continuousCapture;
});
auto captureState = D3DCaptureState::Create(dxgiAPI,
monitor,
const auto monitorArea = targetMonitor.GetScreenSize(true);
auto captureState = D3DCaptureState::Create(item,
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
continuousCapture);
const auto monitorArea = monitor.GetScreenSize(true);
bool mouseOnMonitor = false;
monitorArea,
!continuousCapture);
if (continuousCapture)
{
captureState->StartCapture([&, window](MappedTextureView textureView) {
UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
});
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
{
if (mouseOnMonitor == monitorArea.inside(commonState.cursorPosSystemSpace))
{
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
continue;
}
mouseOnMonitor = !mouseOnMonitor;
if (mouseOnMonitor)
{
captureState->StartCapture([&, window](MappedTextureView textureView) {
UpdateCaptureState(commonState, state, window, textureView);
});
}
else
{
state.Access([&](MeasureToolState& state) {
state.perScreen[window].measuredEdges = {};
});
captureState->StopCapture();
}
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
}
captureState->StopCapture();
}
else
{
const auto textureView = captureState->CaptureSingleFrame();
state.Access([&](MeasureToolState& s) {
s.perScreen[window].capturedScreenTexture = &textureView;
s.perScreen[window].capturedScreenTexture = textureView.GetTexture();
});
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
@@ -384,15 +429,7 @@ std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
auto path = std::filesystem::temp_directory_path() / buf;
textureView.view.SaveAsBitmap(path.string().c_str());
#endif
UpdateCaptureState(commonState, state, window, textureView);
mouseOnMonitor = true;
}
else if (mouseOnMonitor)
{
state.Access([&](MeasureToolState& state) {
state.perScreen[window].measuredEdges = {};
});
mouseOnMonitor = false;
UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
}
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - now);
@@ -402,7 +439,5 @@ std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
}
}
}
captureState->StopCapture();
});
}

View File

@@ -1,12 +1,10 @@
#pragma once
#include "DxgiAPI.h"
#include "ToolState.h"
#include <common/utils/serialized.h>
std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
const CommonState& commonState,
std::thread StartCapturingThread(const CommonState& commonState,
Serialized<MeasureToolState>& state,
HWND targetWindow,
MonitorInfo targetMonitor);

View File

@@ -15,7 +15,6 @@ namespace
const wchar_t JSON_KEY_PIXEL_TOLERANCE[] = L"PixelTolerance";
const wchar_t JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION[] = L"PerColorChannelEdgeDetection";
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
const wchar_t JSON_KEY_UNITS_OF_MEASURE[] = L"UnitsOfMeasure";
}
Settings Settings::LoadFromFile()
@@ -66,14 +65,6 @@ Settings Settings::LoadFromFile()
catch (...)
{
}
try
{
result.units = static_cast<Measurement::Unit>(props.GetNamedObject(JSON_KEY_UNITS_OF_MEASURE).GetNamedNumber(JSON_KEY_VALUE));
}
catch (...)
{
}
}
catch (...)
{

View File

@@ -3,16 +3,13 @@
#include <array>
#include <cinttypes>
#include "Measurement.h"
struct Settings
{
uint8_t pixelTolerance = 30;
bool continuousCapture = false;
bool continuousCapture = true;
bool drawFeetOnCross = true;
bool perColorChannelEdgeDetection = false;
std::array<uint8_t, 3> lineColor = {255, 69, 0};
Measurement::Unit units = Measurement::Unit::Pixel;
static Settings LoadFromFile();
};

View File

@@ -15,12 +15,10 @@
#include <common/utils/serialized.h>
//#define DEBUG_OVERLAY
#include "BGRATextureView.h"
#include "Measurement.h"
struct OverlayBoxText
{
std::array<wchar_t, 128> buffer = {};
std::array<wchar_t, 32> buffer = {};
};
struct CommonState
@@ -29,29 +27,18 @@ struct CommonState
D2D1::ColorF lineColor = D2D1::ColorF::OrangeRed;
Box toolbarBoundingBox;
Measurement::Unit units = Measurement::Unit::Pixel;
mutable Serialized<OverlayBoxText> overlayBoxText;
POINT cursorPosSystemSpace = {}; // updated atomically
std::atomic_bool closeOnOtherMonitors = false;
};
struct CursorDrag
{
D2D_POINT_2F startPos = {};
D2D_POINT_2F currentPos = {};
DWORD touchID = 0; // indicate whether the drag belongs to a touch input sequence
};
struct BoundsToolState
{
struct PerScreen
{
std::optional<CursorDrag> currentBounds;
std::vector<Measurement> measurements;
std::optional<D2D_POINT_2F> currentRegionStart;
std::vector<D2D1_RECT_F> measurements;
};
// TODO: refactor so we don't need unordered_map
std::unordered_map<HWND, PerScreen> perScreen;
CommonState* commonState = nullptr; // required for WndProc
@@ -69,7 +56,7 @@ struct MeasureToolState
struct Global
{
uint8_t pixelTolerance = 30;
bool continuousCapture = false;
bool continuousCapture = true;
bool drawFeetOnCross = true;
bool perColorChannelEdgeDetection = false;
Mode mode = Mode::Cross;
@@ -79,10 +66,10 @@ struct MeasureToolState
{
bool cursorInLeftScreenHalf = false;
bool cursorInTopScreenHalf = false;
std::optional<Measurement> measuredEdges;
RECT measuredEdges = {};
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
// directly from a capturing thread.
const MappedTextureView* capturedScreenTexture = nullptr;
winrt::com_ptr<ID3D11Texture2D> capturedScreenTexture;
// After the drawing thread finds its capturedScreenTexture, it converts it to
// a Direct2D compatible bitmap and caches it here
winrt::com_ptr<ID2D1Bitmap> capturedScreenBitmap;
@@ -91,3 +78,6 @@ struct MeasureToolState
CommonState* commonState = nullptr; // required for WndProc
};
// Concurrently accessing Direct2D and Direct3D APIs make the driver go boom
extern std::recursive_mutex gpuAccessLock;

View File

@@ -4,11 +4,12 @@
namespace consts
{
constexpr inline size_t TARGET_FRAME_RATE = 90;
constexpr inline auto TARGET_FRAME_DURATION = std::chrono::microseconds{ 1000000 } / TARGET_FRAME_RATE;
constexpr inline size_t TARGET_FRAME_RATE = 120;
constexpr inline auto TARGET_FRAME_DURATION = std::chrono::milliseconds{ 1000 } / TARGET_FRAME_RATE;
constexpr inline float FONT_SIZE = 14.f;
constexpr inline float TEXT_BOX_CORNER_RADIUS = 4.f;
constexpr inline float TEXT_BOX_MARGIN_COEFF = 1.25f;
constexpr inline float FEET_HALF_LENGTH = 2.f;
constexpr inline float SHADOW_OPACITY = .4f;
constexpr inline float SHADOW_RADIUS = 6.f;

View File

@@ -11,7 +11,6 @@
#include <d3d11.h>
#include <d3d11_4.h>
#include <dxgi1_6.h>
#include <dxgidebug.h>
#include <d2d1_3.h>
#include <dwrite.h>
#include <dwmapi.h>

View File

@@ -128,7 +128,7 @@ private:
parse_hotkey(settings);
}
catch (std::exception&)
catch (std::exception ex)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
@@ -208,7 +208,7 @@ public:
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception&)
catch (std::exception ex)
{
// Improper JSON.
}

View File

@@ -4,8 +4,6 @@
using System;
using ManagedCommon;
using MeasureToolUI.Helpers;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
namespace MeasureToolUI
@@ -37,27 +35,14 @@ namespace MeasureToolUI
{
if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid))
{
var dispatcher = DispatcherQueue.GetForCurrentThread();
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
{
dispatcher.TryEnqueue(App.Current.Exit);
Environment.Exit(0);
});
}
}
PowerToys.MeasureToolCore.Core core = null;
try
{
core = new PowerToys.MeasureToolCore.Core();
}
catch (Exception ex)
{
Logger.LogError($"MeasureToolCore failed to initialize: {ex}");
App.Current.Exit();
return;
}
_window = new MainWindow(core);
_window = new MainWindow();
_window.Activate();
}

View File

@@ -1,79 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using interop;
using Microsoft.VisualBasic;
using Windows.Storage;
namespace MeasureToolUI.Helpers
{
public static class Logger
{
private static readonly string ApplicationLogPath = Path.Combine(interop.Constants.AppDataPath(), "Measure Tool\\MeasureToolUI\\Logs");
static Logger()
{
if (!Directory.Exists(ApplicationLogPath))
{
Directory.CreateDirectory(ApplicationLogPath);
}
// Using InvariantCulture since this is used for a log file name
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
}
public static void LogError(string message)
{
Log(message, "ERROR");
}
public static void LogError(string message, Exception ex)
{
Log(
message + Environment.NewLine +
ex?.Message + Environment.NewLine +
"Inner exception: " + Environment.NewLine +
ex?.InnerException?.Message + Environment.NewLine +
"Stack trace: " + Environment.NewLine +
ex?.StackTrace,
"ERROR");
}
public static void LogWarning(string message)
{
Log(message, "WARNING");
}
public static void LogInfo(string message)
{
Log(message, "INFO");
}
private static void Log(string message, string type)
{
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
Trace.Indent();
Trace.WriteLine(GetCallerInfo());
Trace.WriteLine(message);
Trace.Unindent();
}
private static string GetCallerInfo()
{
StackTrace stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(3)?.GetMethod();
var className = methodName?.DeclaringType.Name;
return "[Method]: " + methodName?.Name + " [Class]: " + className;
}
}
}

View File

@@ -261,21 +261,13 @@
Click="BoundsTool_Click"
Content="&#xEF20;"
Style="{StaticResource ToggleButtonRadioButtonStyle}"
KeyboardAcceleratorPlacementMode="Auto"
ToolTipService.ToolTip="{x:Bind p:Resources.Bounds}">
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number1" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
ToolTipService.ToolTip="{x:Bind p:Resources.Bounds}" />
<ToggleButton
AutomationProperties.Name="{x:Bind p:Resources.Spacing}"
Click="MeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.Spacing}">
<FontIcon Margin="1,0,0,0" Glyph="&#xE948;" />
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number2" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<ToggleButton
@@ -284,9 +276,6 @@
Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.HorizontalSpacing}">
<FontIcon Margin="1,0,0,0" Glyph="&#xE949;" />
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number3" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<ToggleButton
AutomationProperties.Name="{x:Bind p:Resources.VerticalSpacing}"
@@ -298,19 +287,12 @@
<RotateTransform Angle="90" />
</FontIcon.RenderTransform>
</FontIcon>
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number4" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<AppBarSeparator Height="36" />
<Button
Click="ClosePanelTool_Click"
Content="&#xE8BB;"
ToolTipService.ToolTip="Close">
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked"/>
</Button.KeyboardAccelerators>
</Button>
ToolTipService.ToolTip="Close" />
</StackPanel>
</Grid>
</winuiex:WindowEx>

View File

@@ -6,11 +6,8 @@ using System;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Windows.Graphics;
using WinUIEx;
@@ -21,12 +18,12 @@ namespace MeasureToolUI
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : WindowEx, IDisposable
public sealed partial class MainWindow : WindowEx
{
private const int WindowWidth = 216;
private const int WindowHeight = 50;
private PowerToys.MeasureToolCore.Core _coreLogic;
private PowerToys.MeasureToolCore.Core _coreLogic = new PowerToys.MeasureToolCore.Core();
private AppWindow _appWindow;
private PointInt32 _initialPosition;
@@ -37,14 +34,14 @@ namespace MeasureToolUI
this.SetWindowSize(WindowWidth, WindowHeight);
}
public MainWindow(PowerToys.MeasureToolCore.Core core)
public MainWindow()
{
InitializeComponent();
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
_appWindow = AppWindow.GetFromWindowId(windowId);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
var presenter = _appWindow.Presenter as OverlappedPresenter;
presenter.IsAlwaysOnTop = true;
this.SetIsAlwaysOnTop(true);
@@ -54,8 +51,6 @@ namespace MeasureToolUI
this.SetIsMaximizable(false);
IsTitleBarVisible = false;
_coreLogic = core;
Closed += MainWindow_Closed;
DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
float dpiScale = _coreLogic.GetDPIScaleForWindow((int)hwnd);
@@ -66,14 +61,8 @@ namespace MeasureToolUI
_initialPosition.Y,
_initialPosition.X + (int)(dpiScale * WindowWidth),
_initialPosition.Y + (int)(dpiScale * WindowHeight));
OnPositionChanged(_initialPosition);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
private void MainWindow_Closed(object sender, WindowEventArgs args)
{
_coreLogic?.Dispose();
_coreLogic = null;
OnPositionChanged(_initialPosition);
}
private void UpdateToolUsageCompletionEvent(object sender)
@@ -148,29 +137,5 @@ namespace MeasureToolUI
_coreLogic.ResetState();
this.Close();
}
public void Dispose()
{
_coreLogic?.Dispose();
}
private void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
if (args.Element is ToggleButton toggle)
{
var peer = new ToggleButtonAutomationPeer(toggle);
peer.Toggle();
args.Handled = true;
}
else if (args.Element is Button button)
{
var peer = new ButtonAutomationPeer(button);
if (peer.GetPattern(PatternInterface.Invoke) is IInvokeProvider provider)
{
provider.Invoke();
args.Handled = true;
}
}
}
}
}

View File

@@ -79,7 +79,6 @@
<Folder Include="Assets\Icons\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj" />
</ItemGroup>

View File

@@ -13,6 +13,5 @@ internal static class NativeMethods
internal static readonly IntPtr HWND_TOPMOST = new System.IntPtr(-1);
internal const uint SWP_NOSIZE = 0x0001;
internal const uint SWP_NOMOVE = 0x0002;
internal const uint SWP_NOACTIVATE = 0x0010;
internal const uint SWP_SHOWWINDOW = 0x0040;
}

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using ManagedCommon;
@@ -22,13 +23,6 @@ public partial class App : Application, IDisposable
private Mutex? _instanceMutex;
private int _powerToysRunnerPid;
private CancellationTokenSource NativeThreadCTS { get; set; }
public App()
{
NativeThreadCTS = new CancellationTokenSource();
}
public void Dispose()
{
GC.SuppressFinalize(this);
@@ -41,9 +35,9 @@ public partial class App : Application, IDisposable
_instanceMutex = new Mutex(true, @"Local\PowerToys_PowerOCR_InstanceMutex", out bool createdNew);
if (!createdNew)
{
Logger.LogWarning("Another running TextExtractor instance was detected. Exiting TextExtractor");
Logger.LogWarning("Another running PowerOCR instance was detected. Exiting PowerOCR");
_instanceMutex = null;
Shutdown();
Environment.Exit(0);
return;
}
@@ -52,25 +46,24 @@ public partial class App : Application, IDisposable
try
{
_ = int.TryParse(e.Args[0], out _powerToysRunnerPid);
Logger.LogInfo($"TextExtractor started from the PowerToys Runner. Runner pid={_powerToysRunnerPid}");
Logger.LogInfo($"PowerOCR started from the PowerToys Runner. Runner pid={_powerToysRunnerPid}");
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
{
Logger.LogInfo("PowerToys Runner exited. Exiting TextExtractor");
NativeThreadCTS.Cancel();
Application.Current.Dispatcher.Invoke(() => Shutdown());
Logger.LogInfo("PowerToys Runner exited. Exiting PowerOCR");
Environment.Exit(0);
});
var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker());
eventMonitor = new EventMonitor(Application.Current.Dispatcher, NativeThreadCTS.Token);
eventMonitor = new EventMonitor();
}
catch (Exception ex)
{
Logger.LogError($"TextExtractor got an exception on start: {ex}");
Debug.WriteLine(ex.Message);
}
}
else
{
Logger.LogInfo($"TextExtractor started detached from PowerToys Runner.");
Logger.LogInfo($"PowerOCR started detached from PowerToys Runner.");
_powerToysRunnerPid = -1;
var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker());
keyboardMonitor = new KeyboardMonitor(userSettings);

View File

@@ -137,24 +137,9 @@ internal class ImageMethods
if (singlePoint == null)
{
if (isCJKLang == false)
foreach (OcrLine line in ocrResult.Lines)
{
foreach (OcrLine line in ocrResult.Lines)
{
text.AppendLine(line.Text);
}
}
else
{
foreach (OcrLine ocrLine in ocrResult.Lines)
{
foreach (OcrWord ocrWord in ocrLine.Words)
{
_ = text.Append(ocrWord.Text);
}
text.Append(Environment.NewLine);
}
text.AppendLine(line.Text);
}
}
else

View File

@@ -14,7 +14,7 @@ namespace PowerOCR.Helpers
public static class Logger
{
private static readonly IFileSystem _fileSystem = new FileSystem();
private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "TextExtractor\\Logs");
private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "PowerOCR\\Logs");
static Logger()
{

View File

@@ -4,27 +4,23 @@
using System;
using System.Threading;
using System.Windows;
using Dispatcher = System.Windows.Threading.Dispatcher;
namespace Common.UI
namespace PowerOCR.Helpers
{
public static class NativeEventWaiter
{
public static void WaitForEventLoop(string eventName, Action callback, Dispatcher dispatcher, CancellationToken cancel)
public static void WaitForEventLoop(string eventName, Action callback)
{
new Thread(() =>
{
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, eventName);
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
while (true)
{
if (WaitHandle.WaitAny(new WaitHandle[] { cancel.WaitHandle, eventHandle }) == 1)
if (eventHandle.WaitOne())
{
dispatcher.BeginInvoke(callback);
}
else
{
return;
Logger.LogInfo($"Successfully waited for {eventName}");
Application.Current.Dispatcher.Invoke(callback);
}
}
}).Start();

View File

@@ -77,7 +77,7 @@ public static class WindowUtilities
allFullscreenGrab.Add(overlay);
}
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRInvokedEvent());
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRLaunchOverlayEvent());
}
internal static bool IsOCROverlayCreated()

View File

@@ -4,7 +4,6 @@
using System;
using System.Windows.Interop;
using Common.UI;
using interop;
using PowerOCR.Helpers;
using PowerOCR.Utilities;
@@ -17,9 +16,9 @@ namespace PowerOCR.Keyboard
/// </summary>
internal class EventMonitor
{
public EventMonitor(System.Windows.Threading.Dispatcher dispatcher, System.Threading.CancellationToken exitToken)
public EventMonitor()
{
NativeEventWaiter.WaitForEventLoop(Constants.ShowPowerOCRSharedEvent(), StartOCRSession, dispatcher, exitToken);
NativeEventWaiter.WaitForEventLoop(Constants.ShowPowerOCRSharedEvent(), StartOCRSession);
}
public void StartOCRSession()

View File

@@ -5,11 +5,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PowerOCR"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TextExtractor"
Title="PowerOCR"
Width="800"
Height="450"
ShowActivated="False"
ShowInTaskbar="False"
AllowsTransparency="True"
Background="Transparent"
Loaded="Window_Loaded"

View File

@@ -8,7 +8,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.PowerToys.Telemetry;
using PowerOCR.Helpers;
using PowerOCR.Utilities;
@@ -43,8 +42,6 @@ public partial class OCROverlay : Window
private double xShiftDelta;
private double yShiftDelta;
private const double ActiveOpacity = 0.4;
public OCROverlay()
{
InitializeComponent();
@@ -58,7 +55,7 @@ public partial class OCROverlay : Window
KeyUp += MainWindow_KeyUp;
BackgroundImage.Source = ImageMethods.GetWindowBoundsImage(this);
BackgroundBrush.Opacity = ActiveOpacity;
BackgroundBrush.Opacity = 0.4;
}
private void Window_Unloaded(object sender, RoutedEventArgs e)
@@ -87,6 +84,9 @@ public partial class OCROverlay : Window
switch (e.Key)
{
case Key.LeftShift:
isShiftDown = false;
clickedPoint = new Point(clickedPoint.X + xShiftDelta, clickedPoint.Y + yShiftDelta);
break;
case Key.RightShift:
isShiftDown = false;
clickedPoint = new Point(clickedPoint.X + xShiftDelta, clickedPoint.Y + yShiftDelta);
@@ -102,7 +102,6 @@ public partial class OCROverlay : Window
{
case Key.Escape:
WindowUtilities.CloseAllOCROverlays();
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCancelledEvent());
break;
default:
break;
@@ -111,7 +110,7 @@ public partial class OCROverlay : Window
private void RegionClickCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
if (e.RightButton == MouseButtonState.Pressed)
{
return;
}
@@ -148,7 +147,6 @@ public partial class OCROverlay : Window
if (scr.Bounds.Contains(formsPoint))
{
CurrentScreen = scr;
break;
}
}
}
@@ -192,7 +190,7 @@ public partial class OCROverlay : Window
clippingGeometry.Rect = new Rect(
new Point(leftValue, topValue),
new Size(selectBorder.Width, selectBorder.Height));
new Size(selectBorder.Width - 2, selectBorder.Height - 2));
Canvas.SetLeft(selectBorder, leftValue - 1);
Canvas.SetTop(selectBorder, topValue - 1);
return;
@@ -237,6 +235,11 @@ public partial class OCROverlay : Window
movingPoint.X = Math.Round(movingPoint.X);
movingPoint.Y = Math.Round(movingPoint.Y);
if (mPt == movingPoint)
{
Debug.WriteLine("Probably on Screen 1");
}
double xDimScaled = Canvas.GetLeft(selectBorder) * m.M11;
double yDimScaled = Canvas.GetTop(selectBorder) * m.M22;
@@ -259,6 +262,7 @@ public partial class OCROverlay : Window
if (regionScaled.Width < 3 || regionScaled.Height < 3)
{
BackgroundBrush.Opacity = 0;
grabbedText = await ImageMethods.GetClickedWord(this, new Point(xDimScaled, yDimScaled));
}
else
@@ -268,23 +272,13 @@ public partial class OCROverlay : Window
if (string.IsNullOrWhiteSpace(grabbedText) == false)
{
try
{
Clipboard.SetText(grabbedText);
}
catch (Exception ex)
{
Logger.LogError($"Clipboard.SetText exception: {ex}");
}
Clipboard.SetText(grabbedText);
WindowUtilities.CloseAllOCROverlays();
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCaptureEvent());
}
}
private void CancelMenuItem_Click(object sender, RoutedEventArgs e)
{
WindowUtilities.CloseAllOCROverlays();
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRCancelledEvent());
}
}

View File

@@ -23,7 +23,6 @@
<ProjectTypeGuids>{2150E333-8FDC-42A3-9474-1A3956D46DE8}</ProjectTypeGuids>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
@@ -47,4 +46,5 @@
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -17,7 +17,7 @@ namespace PowerOCR.Settings
public class UserSettings : IUserSettings
{
private readonly ISettingsUtils _settingsUtils;
private const string PowerOcrModuleName = "TextExtractor";
private const string PowerOcrModuleName = "PowerOCR";
private const string DefaultActivationShortcut = "Win + Shift + O";
private const int MaxNumberOfRetry = 5;
private const int SettingsReadOnChangeDelayInMs = 300;
@@ -56,7 +56,7 @@ namespace PowerOCR.Settings
if (!_settingsUtils.SettingsExists(PowerOcrModuleName))
{
Logger.LogInfo("TextExtractor settings.json was missing, creating a new one");
Logger.LogInfo("PowerOCR settings.json was missing, creating a new one");
var defaultPowerOcrSettings = new PowerOcrSettings();
defaultPowerOcrSettings.Save(_settingsUtils);
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace PowerOCR.Telemetry
{
[EventData]
public class PowerOCRCancelledEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace PowerOCR.Telemetry
{
[EventData]
public class PowerOCRCaptureEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -9,7 +9,7 @@ using Microsoft.PowerToys.Telemetry.Events;
namespace PowerOCR.Telemetry
{
[EventData]
public class PowerOCRInvokedEvent : EventBase, IEvent
public class PowerOCRLaunchOverlayEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -1,84 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<application>
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

View File

@@ -4,5 +4,5 @@
namespace PowerOcrConstants
{
// Name of the powertoy module.
inline const std::wstring ModuleKey = L"TextExtractor";
inline const std::wstring ModuleKey = L"PowerOCR";
}

View File

@@ -117,10 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="TextExtractor_Name" xml:space="preserve">
<value>Text Extractor</value>
<data name="PowerOCR_Name" xml:space="preserve">
<value>PowerOCR</value>
</data>
<data name="TextExtractor_Settings_Desc" xml:space="preserve">
<data name="PowerOCR_Settings_Desc" xml:space="preserve">
<value>This feature requires Windows 10 version 1903 or higher</value>
</data>
</root>

View File

@@ -21,7 +21,6 @@ BOOL APIENTRY DllMain(HMODULE hModule,
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
@@ -84,17 +83,17 @@ private:
}
catch (...)
{
Logger::error("Failed to initialize TextExtractor start shortcut");
Logger::error("Failed to initialize PowerOCR start shortcut");
}
}
else
{
Logger::info("TextExtractor settings are empty");
Logger::info("PowerOCR settings are empty");
}
if (!m_hotkey.key)
{
Logger::info("TextExtractor is going to use default shortcut");
Logger::info("PowerOCR is going to use default shortcut");
m_hotkey.win = true;
m_hotkey.alt = false;
m_hotkey.shift = true;
@@ -110,7 +109,7 @@ private:
void launch_process()
{
Logger::trace(L"Starting TextExtractor process");
Logger::trace(L"Starting PowerOCR process");
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
@@ -123,11 +122,11 @@ private:
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei))
{
Logger::trace("Successfully started the TextExtractor process");
Logger::trace("Successfully started the PowerOCR process");
}
else
{
Logger::error( L"TextExtractor failed to start. {}", get_last_error_or_default(GetLastError()));
Logger::error( L"PowerOCR failed to start. {}", get_last_error_or_default(GetLastError()));
}
m_hProcess = sei.hProcess;
@@ -144,7 +143,7 @@ private:
parse_hotkey(settings);
}
catch (std::exception&)
catch (std::exception ex)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
@@ -154,9 +153,9 @@ private:
public:
PowerOCR()
{
app_name = GET_RESOURCE_STRING(IDS_TEXTEXTRACTOR_NAME);
app_name = GET_RESOURCE_STRING(IDS_POWEROCR_NAME);
app_key = PowerOcrConstants::ModuleKey;
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "TextExtractor");
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "PowerOCR");
m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_POWEROCR_SHARED_EVENT);
init_settings();
}
@@ -172,7 +171,7 @@ public:
// Destroy the powertoy and free memory
virtual void destroy() override
{
Logger::trace("TextExtractor::destroy()");
Logger::trace("PowerOCR::destroy()");
delete this;
}
@@ -194,9 +193,9 @@ public:
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(GET_RESOURCE_STRING(IDS_TEXTEXTRACTOR_SETTINGS_DESC));
settings.set_description(GET_RESOURCE_STRING(IDS_POWEROCR_SETTINGS_DESC));
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_TextExtractor");
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_PowerOCR");
return settings.serialize_to_buffer(buffer, buffer_size);
}
@@ -220,7 +219,7 @@ public:
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception&)
catch (std::exception ex)
{
// Improper JSON.
}
@@ -228,7 +227,7 @@ public:
virtual void enable()
{
Logger::trace("TextExtractor::enable()");
Logger::trace("PowerOCR::enable()");
ResetEvent(m_hInvokeEvent);
launch_process();
m_enabled = true;
@@ -237,7 +236,7 @@ public:
virtual void disable()
{
Logger::trace("TextExtractor::disable()");
Logger::trace("PowerOCR::disable()");
if (m_enabled)
{
ResetEvent(m_hInvokeEvent);
@@ -252,7 +251,7 @@ public:
{
if (m_enabled)
{
Logger::trace(L"TextExtractor hotkey pressed");
Logger::trace(L"PowerOCR hotkey pressed");
if (!is_process_running())
{
launch_process();

View File

@@ -5,7 +5,7 @@
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys TextExtractor"
#define FILE_DESCRIPTION "PowerToys PowerOCR"
#define INTERNAL_NAME "PowerToys.PowerOCR"
#define ORIGINAL_FILENAME "PowerToys.PowerOCR.dll"

View File

@@ -66,7 +66,7 @@ public:
ParseSettings(values);
}
catch (std::exception& ex)
catch (std::exception ex)
{
Logger::error("Failed to parse settings. {}", ex.what());
}
@@ -261,7 +261,7 @@ private:
ParseSettings(settings);
}
catch (std::exception& ex)
catch (std::exception ex)
{
Logger::error("Failed to init settings. {}", ex.what());
}

View File

@@ -317,7 +317,7 @@ void AlwaysOnTop::SubscribeToEvents()
EVENT_SYSTEM_MINIMIZESTART,
EVENT_SYSTEM_MINIMIZEEND,
EVENT_SYSTEM_MOVESIZEEND,
EVENT_SYSTEM_FOREGROUND,
EVENT_OBJECT_NAMECHANGE,
EVENT_OBJECT_DESTROY
};
@@ -479,9 +479,14 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
}
}
break;
case EVENT_SYSTEM_FOREGROUND:
case EVENT_OBJECT_NAMECHANGE:
{
RefreshBorders();
// The accessibility name of the desktop window changes whenever the user
// switches virtual desktops.
if (data->hwnd == GetDesktopWindow())
{
VirtualDesktopSwitchedHandle();
}
}
break;
default:
@@ -489,23 +494,17 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
}
}
void AlwaysOnTop::RefreshBorders()
void AlwaysOnTop::VirtualDesktopSwitchedHandle()
{
for (const auto& [window, border] : m_topmostWindows)
{
if (m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window))
{
if (!border)
{
AssignBorder(window);
}
AssignBorder(window);
}
else
{
if (border)
{
m_topmostWindows[window] = nullptr;
}
m_topmostWindows[window] = nullptr;
}
}
}

View File

@@ -65,6 +65,8 @@ private:
void UnpinAll();
void CleanUp();
void VirtualDesktopSwitchedHandle();
bool IsTracked(HWND window) const noexcept;
bool IsTopmost(HWND window) const noexcept;
bool IsPinned(HWND window) const noexcept;
@@ -72,7 +74,6 @@ private:
bool PinTopmostWindow(HWND window) const noexcept;
bool UnpinTopmostWindow(HWND window) const noexcept;
bool AssignBorder(HWND window);
void RefreshBorders();
virtual void SettingsUpdate(SettingId type) override;

View File

@@ -127,9 +127,6 @@ bool WindowBorder::Init(HINSTANCE hinstance)
, windowRect.bottom - windowRect.top
, SWP_NOMOVE | SWP_NOSIZE);
BOOL val = TRUE;
DwmSetWindowAttribute(m_window, DWMWA_EXCLUDED_FROM_PEEK, &val, sizeof(val));
m_frameDrawer = FrameDrawer::Create(m_window);
if (!m_frameDrawer)
{

View File

@@ -94,7 +94,7 @@ public:
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception&)
catch (std::exception ex)
{
// Improper JSON.
}
@@ -273,7 +273,7 @@ private:
parse_hotkey(settings);
}
catch (std::exception&)
catch (std::exception ex)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.

View File

@@ -43,9 +43,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20071.2" />
<PackageReference Include="System.Reactive" Version="5.0.0" />

View File

@@ -7,20 +7,17 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Awake.Core.Models;
using Microsoft.Win32;
using NLog;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.FileSystem;
using Windows.Win32.System.Console;
using Windows.Win32.System.Power;
namespace Awake.Core
{
public delegate bool ConsoleEventHandler(ControlType ctrlType);
/// <summary>
/// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts
/// of the codebase.
@@ -28,6 +25,9 @@ namespace Awake.Core
public class APIHelper
{
private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
private const int StdOutputHandle = -11;
private const uint GenericWrite = 0x40000000;
private const uint GenericRead = 0x80000000;
private static readonly Logger _log;
private static CancellationTokenSource _tokenSource;
@@ -43,21 +43,21 @@ namespace Awake.Core
_tokenSource = new CancellationTokenSource();
}
internal static void SetConsoleControlHandler(PHANDLER_ROUTINE handler, bool addHandler)
public static void SetConsoleControlHandler(ConsoleEventHandler handler, bool addHandler)
{
PInvoke.SetConsoleCtrlHandler(handler, addHandler);
NativeMethods.SetConsoleCtrlHandler(handler, addHandler);
}
public static void AllocateConsole()
{
_log.Debug("Bootstrapping the console allocation routine.");
PInvoke.AllocConsole();
NativeMethods.AllocConsole();
_log.Debug($"Console allocation result: {Marshal.GetLastWin32Error()}");
var outputFilePointer = PInvoke.CreateFile("CONOUT$", FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE, FILE_SHARE_MODE.FILE_SHARE_WRITE, null, FILE_CREATION_DISPOSITION.CREATE_NEW, 0, null);
var outputFilePointer = NativeMethods.CreateFile("CONOUT$", GenericRead | GenericWrite, FileShare.Write, IntPtr.Zero, FileMode.OpenOrCreate, 0, IntPtr.Zero);
_log.Debug($"CONOUT creation result: {Marshal.GetLastWin32Error()}");
PInvoke.SetStdHandle(Windows.Win32.System.Console.STD_HANDLE.STD_OUTPUT_HANDLE, outputFilePointer);
NativeMethods.SetStdHandle(StdOutputHandle, outputFilePointer);
_log.Debug($"SetStdHandle result: {Marshal.GetLastWin32Error()}");
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true });
@@ -70,11 +70,11 @@ namespace Awake.Core
/// </summary>
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
/// <returns>true if successful, false if failed</returns>
private static bool SetAwakeState(EXECUTION_STATE state)
private static bool SetAwakeState(ExecutionState state)
{
try
{
var stateResult = PInvoke.SetThreadExecutionState(state);
var stateResult = NativeMethods.SetThreadExecutionState(state);
return stateResult != 0;
}
catch
@@ -160,18 +160,18 @@ namespace Awake.Core
bool success;
if (keepDisplayOn)
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS);
}
else
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS);
}
try
{
if (success)
{
_log.Info($"Initiated indefinite keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
_log.Info($"Initiated indefinite keep awake in background thread: {NativeMethods.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
WaitHandle.WaitAny(new[] { _threadToken.WaitHandle });
@@ -186,35 +186,28 @@ namespace Awake.Core
catch (OperationCanceledException ex)
{
// Task was clearly cancelled.
_log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}");
_log.Info($"Background thread termination: {NativeMethods.GetCurrentThreadId()}. Message: {ex.Message}");
return success;
}
}
internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false)
internal static void CompleteExit(int exitCode, bool force = false)
{
SetNoKeepAwake();
APIHelper.SetNoKeepAwake();
TrayHelper.ClearTray();
HWND windowHandle = GetHiddenWindow();
// Because we are running a message loop for the tray, we can't just use Environment.Exit,
// but have to make sure that we properly send the termination message.
IntPtr windowHandle = APIHelper.GetHiddenWindow();
if (windowHandle != HWND.Null)
if (windowHandle != IntPtr.Zero)
{
PInvoke.SendMessage(windowHandle, PInvoke.WM_CLOSE, 0, 0);
NativeMethods.SendMessage(windowHandle, NativeConstants.WM_CLOSE, 0, string.Empty);
}
if (force)
{
PInvoke.PostQuitMessage(0);
}
try
{
exitSignal?.Set();
PInvoke.DestroyWindow(windowHandle);
}
catch (Exception ex)
{
_log.Info($"Exit signal error ${ex}");
Environment.Exit(exitCode);
}
}
@@ -228,18 +221,18 @@ namespace Awake.Core
{
if (keepDisplayOn)
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS);
}
else
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS);
}
if (success)
{
_log.Info($"Initiated temporary keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
_log.Info($"Initiated temporary keep awake in background thread: {NativeMethods.GetCurrentThreadId()}. Screen on: {keepDisplayOn}");
_timedLoopTimer = new System.Timers.Timer((seconds * 1000) + 1);
_timedLoopTimer = new System.Timers.Timer(seconds * 1000);
_timedLoopTimer.Elapsed += (s, e) =>
{
_tokenSource.Cancel();
@@ -269,7 +262,7 @@ namespace Awake.Core
catch (OperationCanceledException ex)
{
// Task was clearly cancelled.
_log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}");
_log.Info($"Background thread termination: {NativeMethods.GetCurrentThreadId()}. Message: {ex.Message}");
return success;
}
}
@@ -301,20 +294,15 @@ namespace Awake.Core
}
[SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Function returns DWORD value that identifies the current thread, but we do not need it.")]
internal static IEnumerable<HWND> EnumerateWindowsForProcess(int processId)
public static IEnumerable<IntPtr> EnumerateWindowsForProcess(int processId)
{
var handles = new List<HWND>();
var hCurrentWnd = HWND.Null;
var handles = new List<IntPtr>();
IntPtr hCurrentWnd = IntPtr.Zero;
do
{
hCurrentWnd = PInvoke.FindWindowEx(HWND.Null, hCurrentWnd, null as string, null);
uint targetProcessId = 0;
unsafe
{
PInvoke.GetWindowThreadProcessId(hCurrentWnd, &targetProcessId);
}
hCurrentWnd = NativeMethods.FindWindowEx(IntPtr.Zero, hCurrentWnd, null, null);
NativeMethods.GetWindowThreadProcessId(hCurrentWnd, out uint targetProcessId);
if (targetProcessId == processId)
{
handles.Add(hCurrentWnd);
@@ -326,30 +314,23 @@ namespace Awake.Core
}
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")]
internal static HWND GetHiddenWindow()
public static IntPtr GetHiddenWindow()
{
IEnumerable<HWND> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
IEnumerable<IntPtr> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x");
string targetClass = $"{InternalConstants.TrayWindowId}{domain}";
unsafe
foreach (var handle in windowHandles)
{
var classNameLen = 256;
Span<char> className = stackalloc char[classNameLen];
foreach (var handle in windowHandles)
StringBuilder className = new (256);
int classQueryResult = NativeMethods.GetClassName(handle, className, className.Capacity);
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase))
{
fixed (char* ptr = className)
{
int classQueryResult = PInvoke.GetClassName(handle, ptr, classNameLen);
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase))
{
return handle;
}
}
return handle;
}
}
return HWND.Null;
return IntPtr.Zero;
}
public static Dictionary<string, int> GetDefaultTrayOptions()

View File

@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Awake.Core.Models
{
[Flags]
public enum ExecutionState : uint
{
ES_AWAYMODE_REQUIRED = 0x00000040,
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001,
}
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
namespace Awake.Core.Models
{
public struct SystemPowerCapabilities
{
[MarshalAs(UnmanagedType.U1)]
public bool PowerButtonPresent;
[MarshalAs(UnmanagedType.U1)]
public bool SleepButtonPresent;
[MarshalAs(UnmanagedType.U1)]
public bool LidPresent;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS1;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS2;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS3;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS4;
[MarshalAs(UnmanagedType.U1)]
public bool SystemS5;
[MarshalAs(UnmanagedType.U1)]
public bool HiberFilePresent;
[MarshalAs(UnmanagedType.U1)]
public bool FullWake;
[MarshalAs(UnmanagedType.U1)]
public bool VideoDimPresent;
[MarshalAs(UnmanagedType.U1)]
public bool ApmPresent;
[MarshalAs(UnmanagedType.U1)]
public bool UpsPresent;
[MarshalAs(UnmanagedType.U1)]
public bool ThermalControl;
[MarshalAs(UnmanagedType.U1)]
public bool ProcessorThrottle;
public byte ProcessorMinThrottle;
public byte ProcessorMaxThrottle;
[MarshalAs(UnmanagedType.U1)]
public bool FastSystemS4;
[MarshalAs(UnmanagedType.U1)]
public bool Hiberboot;
[MarshalAs(UnmanagedType.U1)]
public bool WakeAlarmPresent;
[MarshalAs(UnmanagedType.U1)]
public bool AoAc;
[MarshalAs(UnmanagedType.U1)]
public bool DiskSpinDown;
public byte HiberFileType;
[MarshalAs(UnmanagedType.U1)]
public bool AoAcConnectivitySupported;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
private readonly byte[] spare3;
[MarshalAs(UnmanagedType.U1)]
public bool SystemBatteriesPresent;
[MarshalAs(UnmanagedType.U1)]
public bool BatteriesAreShortTerm;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public BatteryReportingScale[] BatteryScale;
public SystemPowerState AcOnLineWake;
public SystemPowerState SoftLidWake;
public SystemPowerState RtcWake;
public SystemPowerState MinDeviceWakeState;
public SystemPowerState DefaultLowLatencyWake;
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Awake.Core.Models
{
// Maps to the OS power state.
// See documentation: https://docs.microsoft.com/windows/win32/power/system-power-states
public enum SystemPowerState
{
PowerSystemUnspecified = 0,
PowerSystemWorking = 1,
PowerSystemSleeping1 = 2,
PowerSystemSleeping2 = 3,
PowerSystemSleeping3 = 4,
PowerSystemHibernate = 5,
PowerSystemShutdown = 6,
PowerSystemMaximum = 7,
}
}

View File

@@ -2,16 +2,14 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.Win32;
namespace Awake.Core.Models
{
internal enum TrayCommands : uint
{
TC_DISPLAY_SETTING = PInvoke.WM_USER + 1,
TC_MODE_PASSIVE = PInvoke.WM_USER + 2,
TC_MODE_INDEFINITE = PInvoke.WM_USER + 3,
TC_EXIT = PInvoke.WM_USER + 4,
TC_TIME = PInvoke.WM_USER + 5,
TC_DISPLAY_SETTING = NativeConstants.WM_USER + 1,
TC_MODE_PASSIVE = NativeConstants.WM_USER + 2,
TC_MODE_INDEFINITE = NativeConstants.WM_USER + 3,
TC_EXIT = NativeConstants.WM_USER + 4,
TC_TIME = NativeConstants.WM_USER + 5,
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable SA1310 // Field names should not contain underscore
namespace Awake.Core
{
internal class NativeConstants
{
internal const uint WM_COMMAND = 0x111;
internal const uint WM_USER = 0x400;
internal const uint WM_GETTEXT = 0x000D;
internal const uint WM_CLOSE = 0x0010;
// Popup menu constants.
internal const uint MF_BYPOSITION = 1024;
internal const uint MF_STRING = 0;
internal const uint MF_MENUBREAK = 0x00000040;
internal const uint MF_SEPARATOR = 0x00000800;
internal const uint MF_POPUP = 0x00000010;
internal const uint MF_UNCHECKED = 0x00000000;
internal const uint MF_CHECKED = 0x00000008;
internal const uint MF_OWNERDRAW = 0x00000100;
}
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Awake.Core.Models;
namespace Awake.Core
{
internal static class NativeMethods
{
internal delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("Powrprof.dll", SetLastError = true)]
internal static extern bool GetPwrCapabilities(out SystemPowerCapabilities lpSystemPowerCapabilities);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler handler, bool add);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern uint GetCurrentThreadId();
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPWStr)] string filename,
[MarshalAs(UnmanagedType.U4)] uint access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr CreatePopupMenu();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool InsertMenu(IntPtr hMenu, uint uPosition, uint uFlags, uint uIDNewItem, string lpNewItem);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string? className, string? windowTitle);
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, nuint wParam, string lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool DestroyMenu(IntPtr hMenu);
}
}

View File

@@ -7,16 +7,11 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Awake.Core.Models;
using Microsoft.PowerToys.Settings.UI.Library;
using NLog;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
@@ -27,18 +22,21 @@ namespace Awake.Core
{
private static readonly Logger _log;
private static DestroyMenuSafeHandle TrayMenu { get; set; }
private static IntPtr _trayMenu;
private static NotifyIcon TrayIcon { get; set; }
private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; }
private static NotifyIcon? _trayIcon;
private static NotifyIcon TrayIcon { get => _trayIcon; set => _trayIcon = value; }
static TrayHelper()
{
_log = LogManager.GetCurrentClassLogger();
TrayMenu = new DestroyMenuSafeHandle();
TrayIcon = new NotifyIcon();
}
public static void InitializeTray(string text, Icon icon, ManualResetEvent? exitSignal, ContextMenuStrip? contextMenu = null)
public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null)
{
Task.Factory.StartNew(
(tray) =>
@@ -51,7 +49,7 @@ namespace Awake.Core
((NotifyIcon?)tray).ContextMenuStrip = contextMenu;
((NotifyIcon?)tray).Visible = true;
((NotifyIcon?)tray).MouseClick += TrayClickHandler;
Application.AddMessageFilter(new TrayMessageFilter(exitSignal));
Application.AddMessageFilter(new TrayMessageFilter());
Application.Run();
_log.Info("Tray setup complete.");
}
@@ -76,15 +74,21 @@ namespace Awake.Core
/// <param name="e">MouseEventArgs instance containing mouse click event information.</param>
private static void TrayClickHandler(object? sender, MouseEventArgs e)
{
HWND windowHandle = APIHelper.GetHiddenWindow();
IntPtr windowHandle = APIHelper.GetHiddenWindow();
if (windowHandle != HWND.Null)
if (windowHandle != IntPtr.Zero)
{
PInvoke.SetForegroundWindow(windowHandle);
PInvoke.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, null);
NativeMethods.SetForegroundWindow(windowHandle);
NativeMethods.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, IntPtr.Zero);
}
}
public static void ClearTray()
{
TrayIcon.Icon = null;
TrayIcon.Dispose();
}
internal static void SetTray(string text, AwakeSettings settings)
{
SetTray(
@@ -97,15 +101,20 @@ namespace Awake.Core
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:Single line comments should begin with single space", Justification = "For debugging purposes - will remove later.")]
public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts)
{
TrayMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu());
if (!TrayMenu.IsInvalid)
if (TrayMenu != IntPtr.Zero)
{
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit");
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_SEPARATOR, 0, string.Empty);
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (keepDisplayOn ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on");
var destructionStatus = NativeMethods.DestroyMenu(TrayMenu);
if (destructionStatus != true)
{
_log.Error("Failed to destroy tray menu and free up memory.");
}
}
TrayMenu = NativeMethods.CreatePopupMenu();
NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit");
NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_SEPARATOR, 0, string.Empty);
NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING | (keepDisplayOn ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on");
// In case there are no tray shortcuts defined for the application default to a
// reasonable initial set.
if (trayTimeShortcuts.Count == 0)
@@ -114,18 +123,18 @@ namespace Awake.Core
}
// TODO: Make sure that this loads from JSON instead of being hard-coded.
var awakeTimeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false);
var awakeTimeMenu = NativeMethods.CreatePopupMenu();
for (int i = 0; i < trayTimeShortcuts.Count; i++)
{
PInvoke.InsertMenu(awakeTimeMenu, (uint)i, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key);
NativeMethods.InsertMenu(awakeTimeMenu, (uint)i, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key);
}
var modeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false);
PInvoke.InsertMenu(modeMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.PASSIVE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)");
PInvoke.InsertMenu(modeMenu, 1, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.INDEFINITE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely");
var modeMenu = NativeMethods.CreatePopupMenu();
NativeMethods.InsertMenu(modeMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING | (mode == AwakeMode.PASSIVE ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)");
NativeMethods.InsertMenu(modeMenu, 1, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING | (mode == AwakeMode.INDEFINITE ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely");
PInvoke.InsertMenu(modeMenu, 2, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP | (mode == AwakeMode.TIMED ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)awakeTimeMenu.DangerousGetHandle(), "Keep awake temporarily");
PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP, (uint)modeMenu.DangerousGetHandle(), "Mode");
NativeMethods.InsertMenu(modeMenu, 2, NativeConstants.MF_BYPOSITION | NativeConstants.MF_POPUP | (mode == AwakeMode.TIMED ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)awakeTimeMenu, "Keep awake temporarily");
NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_POPUP, (uint)modeMenu, "Mode");
TrayIcon.Text = text;
}

View File

@@ -6,11 +6,9 @@ using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Windows.Forms;
using Awake.Core.Models;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.Win32;
#pragma warning disable CS8603 // Possible null reference return.
@@ -22,11 +20,8 @@ namespace Awake.Core
private static SettingsUtils ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
private static ManualResetEvent? _exitSignal;
public TrayMessageFilter(ManualResetEvent? exitSignal)
public TrayMessageFilter()
{
_exitSignal = exitSignal;
ModuleSettings = new SettingsUtils();
}
@@ -36,12 +31,12 @@ namespace Awake.Core
switch (m.Msg)
{
case (int)PInvoke.WM_COMMAND:
case (int)NativeConstants.WM_COMMAND:
var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF;
switch (targetCommandIndex)
{
case (long)TrayCommands.TC_EXIT:
ExitCommandHandler(_exitSignal);
ExitCommandHandler();
break;
case (long)TrayCommands.TC_DISPLAY_SETTING:
DisplaySettingCommandHandler(InternalConstants.AppName);
@@ -73,9 +68,9 @@ namespace Awake.Core
return false;
}
private static void ExitCommandHandler(ManualResetEvent? exitSignal)
private static void ExitCommandHandler()
{
APIHelper.CompleteExit(0, exitSignal, true);
APIHelper.CompleteExit(0, true);
}
private static void DisplaySettingCommandHandler(string moduleName)

View File

@@ -1,23 +0,0 @@
AllocConsole
CreateFile
CreatePopupMenu
DestroyMenu
DestroyWindow
FindWindowEx
GetClassName
GetCurrentThreadId
GetPwrCapabilities
GetWindowThreadProcessId
HMENU
InsertMenu
PostQuitMessage
SendMessage
SetConsoleCtrlHandler
SetForegroundWindow
SetStdHandle
SetThreadExecutionState
TrackPopupMenuEx
WM_CLOSE
WM_COMMAND
WM_GETTEXT
WM_USER

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics;
@@ -16,15 +17,11 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Awake.Core;
using Awake.Core.Models;
using interop;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry.Events;
using NLog;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Console;
using Windows.Win32.System.Power;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
@@ -50,8 +47,8 @@ namespace Awake
private static Logger? _log;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private static PHANDLER_ROUTINE _handler;
private static SYSTEM_POWER_CAPABILITIES _powerCapabilities;
private static ConsoleEventHandler _handler;
private static SystemPowerCapabilities _powerCapabilities;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private static ManualResetEvent _exitSignal = new ManualResetEvent(false);
@@ -66,7 +63,7 @@ namespace Awake
if (!instantiated)
{
Exit(InternalConstants.AppName + " is already running! Exiting the application.", 1, _exitSignal, true);
Exit(InternalConstants.AppName + " is already running! Exiting the application.", 1, true);
}
_settingsUtils = new SettingsUtils();
@@ -85,12 +82,12 @@ namespace Awake
// To make it easier to diagnose future issues, let's get the
// system power capabilities and aggregate them in the log.
PInvoke.GetPwrCapabilities(out _powerCapabilities);
NativeMethods.GetPwrCapabilities(out _powerCapabilities);
_log.Info(JsonSerializer.Serialize(_powerCapabilities));
_log.Info("Parsing parameters...");
var configOption = new Option<bool>(
Option<bool>? configOption = new (
aliases: new[] { "--use-pt-config", "-c" },
getDefaultValue: () => false,
description: $"Specifies whether {InternalConstants.AppName} will be using the PowerToys configuration file for managing the state.")
@@ -103,7 +100,7 @@ namespace Awake
configOption.Required = false;
var displayOption = new Option<bool>(
Option<bool>? displayOption = new (
aliases: new[] { "--display-on", "-d" },
getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.")
@@ -116,7 +113,7 @@ namespace Awake
displayOption.Required = false;
var timeOption = new Option<uint>(
Option<uint>? timeOption = new (
aliases: new[] { "--time-limit", "-t" },
getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.")
@@ -129,7 +126,7 @@ namespace Awake
timeOption.Required = false;
var pidOption = new Option<int>(
Option<int>? pidOption = new (
aliases: new[] { "--pid", "-p" },
getDefaultValue: () => 0,
description: $"Bind the execution of {InternalConstants.AppName} to another process.")
@@ -159,23 +156,23 @@ namespace Awake
return rootCommand.InvokeAsync(args).Result;
}
private static BOOL ExitHandler(uint ctrlType)
private static bool ExitHandler(ControlType ctrlType)
{
_log.Info($"Exited through handler with control type: {ctrlType}");
Exit("Exiting from the internal termination handler.", Environment.ExitCode, _exitSignal);
Exit("Exiting from the internal termination handler.", Environment.ExitCode);
return false;
}
private static void Exit(string message, int exitCode, ManualResetEvent exitSignal, bool force = false)
private static void Exit(string message, int exitCode, bool force = false)
{
_log.Info(message);
APIHelper.CompleteExit(exitCode, exitSignal, force);
APIHelper.CompleteExit(exitCode, force);
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid)
{
_handler += ExitHandler;
_handler += new ConsoleEventHandler(ExitHandler);
APIHelper.SetConsoleControlHandler(_handler, true);
if (pid == 0)
@@ -195,15 +192,16 @@ namespace Awake
// and instead watch for changes in the file.
try
{
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, Constants.AwakeExitEvent());
new Thread(() =>
{
if (WaitHandle.WaitAny(new WaitHandle[] { _exitSignal, eventHandle }) == 1)
EventWaitHandle? eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AwakeExitEvent());
if (eventHandle.WaitOne())
{
Exit("Received a signal to end the process. Making sure we quit...", 0, _exitSignal, true);
Exit("Received a signal to end the process. Making sure we quit...", 0, true);
}
}).Start();
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico"), _exitSignal);
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico"));
string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName);
_log.Info($"Reading configuration file: {settingsPath}");
@@ -265,7 +263,7 @@ namespace Awake
RunnerHelper.WaitForPowerToysRunner(pid, () =>
{
_log.Info($"Triggered PID-based exit handler for PID {pid}.");
Exit("Terminating from process binding hook.", 0, _exitSignal, true);
Exit("Terminating from process binding hook.", 0, true);
});
}

View File

@@ -144,7 +144,7 @@ private:
parse_hotkey(settings);
}
catch (std::exception&)
catch (std::exception ex)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
@@ -221,7 +221,7 @@ public:
// Otherwise call a custom function to process the settings before saving them to disk:
// save_settings();
}
catch (std::exception&)
catch (std::exception ex)
{
// Improper JSON.
}

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