Compare commits
118 Commits
temp/test-
...
temp/test-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6c7ca9ccd | ||
|
|
232f979c30 | ||
|
|
fe00f720cd | ||
|
|
6c4365851f | ||
|
|
b7d1986be5 | ||
|
|
9178285a9c | ||
|
|
5df9f04dbd | ||
|
|
4daf5714dc | ||
|
|
f869768b0b | ||
|
|
8061e1a527 | ||
|
|
61fd8cc8f5 | ||
|
|
9654daa62e | ||
|
|
c25c7a97b8 | ||
|
|
56a5d783c8 | ||
|
|
c4f49a0fc9 | ||
|
|
1f3dcb3ea6 | ||
|
|
e46e645d3d | ||
|
|
82d265ca80 | ||
|
|
f08900925a | ||
|
|
0669b5cbba | ||
|
|
562f8a5f37 | ||
|
|
7e18881967 | ||
|
|
10728e4e66 | ||
|
|
5268411ace | ||
|
|
edd80fc01e | ||
|
|
f1f95fe151 | ||
|
|
07fb01e178 | ||
|
|
44f7065b9e | ||
|
|
cf67208871 | ||
|
|
0dde07cc23 | ||
|
|
5c71808af1 | ||
|
|
b0606dde9b | ||
|
|
fe3e7e14d9 | ||
|
|
461e3c6a21 | ||
|
|
8431164bc6 | ||
|
|
0ee620988e | ||
|
|
12c11908c1 | ||
|
|
98a870db09 | ||
|
|
d93991a914 | ||
|
|
798c3eaf6f | ||
|
|
4405e4bfb6 | ||
|
|
56ee3fc558 | ||
|
|
8c75a71e70 | ||
|
|
39fe726690 | ||
|
|
c3da4a923a | ||
|
|
efe712f969 | ||
|
|
98eba357e1 | ||
|
|
b91780b903 | ||
|
|
614bf7fee3 | ||
|
|
a69a450899 | ||
|
|
db2e1d5985 | ||
|
|
58f49e1861 | ||
|
|
de1d18f8d9 | ||
|
|
45f4a83dab | ||
|
|
9128ca63c9 | ||
|
|
78d4f66669 | ||
|
|
48a79819f3 | ||
|
|
bde578522e | ||
|
|
5d39f44233 | ||
|
|
ddf7b5a7ba | ||
|
|
64bf10e74e | ||
|
|
d39c129bd0 | ||
|
|
bc0e83b59a | ||
|
|
714626aca8 | ||
|
|
a17425b0f7 | ||
|
|
88900a55d6 | ||
|
|
87ec6254ad | ||
|
|
7160eb3a1e | ||
|
|
5d8856330b | ||
|
|
3414d6d2c4 | ||
|
|
6b7a0698f5 | ||
|
|
89e6849e60 | ||
|
|
e18c435bc6 | ||
|
|
b231d89171 | ||
|
|
ce501489b4 | ||
|
|
54ad5ff41f | ||
|
|
e116358723 | ||
|
|
39c5177c43 | ||
|
|
1ea75e9421 | ||
|
|
4c26699ad6 | ||
|
|
123de4fbc0 | ||
|
|
0ddd3e8395 | ||
|
|
66870a61ec | ||
|
|
aff1020ab0 | ||
|
|
da3626cb51 | ||
|
|
4332571aa3 | ||
|
|
c6018eb87c | ||
|
|
094eeee194 | ||
|
|
0f7100508f | ||
|
|
434a9023c3 | ||
|
|
7054d4c183 | ||
|
|
8a89a567d1 | ||
|
|
172d57c69a | ||
|
|
c86903f7c6 | ||
|
|
6d66921720 | ||
|
|
d6769f52cf | ||
|
|
ac9425ee81 | ||
|
|
fceeb1b51f | ||
|
|
a47d49d5c2 | ||
|
|
a32b7361c8 | ||
|
|
9e4dcf9d36 | ||
|
|
c218adf3fd | ||
|
|
9b9cdabf4e | ||
|
|
a85fdd7b72 | ||
|
|
9258353f6c | ||
|
|
ea850fff75 | ||
|
|
60451a3e19 | ||
|
|
183300cb8b | ||
|
|
5033fd3db1 | ||
|
|
5443c07468 | ||
|
|
b2bcbcc6af | ||
|
|
59c0db2c39 | ||
|
|
6564a4f427 | ||
|
|
cd10bf3ae6 | ||
|
|
02f43b9cbb | ||
|
|
00dfe5427f | ||
|
|
58905ab301 | ||
|
|
a249d0f99b |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/translation_issue.yml
vendored
@@ -37,7 +37,7 @@ body:
|
||||
- PDF Thumbnail
|
||||
- G-code Preview
|
||||
- G-code Thumbnail
|
||||
- Quick Accent
|
||||
- PowerAccent
|
||||
- PowerRename
|
||||
- PowerToys Run
|
||||
- Screen Ruler
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 28 KiB |
@@ -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
|
||||
|
||||
@@ -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?>
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace PowerToys
|
||||
delegate void ToolSessionCompleted();
|
||||
|
||||
[default_interface]
|
||||
runtimeclass Core : Windows.Foundation.IClosable
|
||||
runtimeclass Core
|
||||
{
|
||||
Core();
|
||||
void SetToolCompletionEvent(event ToolSessionCompleted completionTrigger);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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 (...)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,21 +261,13 @@
|
||||
Click="BoundsTool_Click"
|
||||
Content=""
|
||||
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="" />
|
||||
<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="" />
|
||||
<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=""
|
||||
ToolTipService.ToolTip="Close">
|
||||
<Button.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked"/>
|
||||
</Button.KeyboardAccelerators>
|
||||
</Button>
|
||||
ToolTipService.ToolTip="Close" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 78 KiB |
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
|
||||
17
src/modules/awake/Awake/Core/Models/ExecutionState.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
20
src/modules/awake/Awake/Core/Models/SystemPowerState.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
26
src/modules/awake/Awake/Core/NativeConstants.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
74
src/modules/awake/Awake/Core/NativeMethods.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||