diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index 3afbb1f05e..b5195e1344 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -39,6 +39,7 @@ nupkg petabyte resw resx +srt Stereolithography terabyte UYVY @@ -127,6 +128,92 @@ XBUTTONDOWN XBUTTONUP XDOWN +# User32.SYSTEM_METRICS_INDEX.cs + +CLEANBOOT +CMOUSEBUTTONS +CONVERTIBLESLATEMODE +CXBORDER +CXCURSOR +CXDLGFRAME +CXDLGFRAME +CXDOUBLECLK +CXDRAG +CXEDGE +CXFIXEDFRAME +CXFOCUSBORDER +CXFRAME +CXFRAME +CXFULLSCREEN +CXHSCROLL +CXHTHUMB +CXICON +CXICONSPACING +CXMAXIMIZED +CXMAXTRACK +CXMENUCHECK +CXMENUSIZE +CXMIN +CXMINIMIZED +CXMINSPACING +CXMINTRACK +CXPADDEDBORDER +CXSIZE +CXSIZEFRAME +CXSMSIZE +CXVSCROLL +CYBORDER +CYCAPTION +CYCURSOR +CYDLGFRAME +CYDLGFRAME +CYDOUBLECLK +CYDRAG +CYEDGE +CYFIXEDFRAME +CYFOCUSBORDER +CYFRAME +CYFRAME +CYFULLSCREEN +CYHSCROLL +CYICON +CYICONSPACING +CYKANJIWINDOW +CYMAXIMIZED +CYMAXTRACK +CYMENU +CYMENUCHECK +CYMENUSIZE +CYMIN +CYMINIMIZED +CYMINSPACING +CYMINTRACK +CYSIZE +CYSIZEFRAME +CYSMCAPTION +CYSMSIZE +CYVSCROLL +CYVTHUMB +DBCSENABLED +IMMENABLED +MAXIMUMTOUCHES +MEDIACENTER +MENUDROPALIGNMENT +MIDEASTENABLED +MOUSEHORIZONTALWHEELPRESENT +MOUSEPRESENT +MOUSEWHEELPRESENT +PENWINDOWS +REMOTECONTROL +REMOTESESSION +SAMEDISPLAYFORMA +SERVERR +SHOWSOUNDS +SHUTTINGDOWN +SLOWMACHINE +SWAPBUTTON +SYSTEMDOCKED +TABLETPC # MATH diff --git a/.github/actions/spell-check/allow/names.txt b/.github/actions/spell-check/allow/names.txt index c04863a1c6..92faf99ed7 100644 --- a/.github/actions/spell-check/allow/names.txt +++ b/.github/actions/spell-check/allow/names.txt @@ -33,10 +33,12 @@ Advaith alekhyareddy Aleks angularsen +Anirudha arjunbalgovind Ashish Baltazar Bao +Bartosz betadele betsegaw bricelam @@ -51,6 +53,8 @@ crutkas damienleroy davidegiacometti debian +Deibisu +Deibisu Delimarsky Deondre DHowett @@ -62,6 +66,7 @@ gabime Galaxi Garside Gershaft +Giordani Gokce Guo hanselman @@ -70,12 +75,15 @@ Heiko Hemmerlein hlaueriksson Horvalds +Howett htcfreek Huynh Jaswal jefflord Jordi jyuwono +Kairu +Kairu Kamra Kantarci Karthick @@ -92,7 +100,9 @@ martinmoene Melman Mikhayelyan msft +Mykhailo Myrvold +Naro nathancartlidge Nemeth nielslaute @@ -103,9 +113,13 @@ peteblois phoboslab Ponten Pooja +Pylyp +quachpas Quriz randyrants ricardosantos +riri +riri ritchielawrence robmikh Rutkas @@ -119,10 +133,12 @@ Seraphima skttl somil Soref +Sosnowski stefan Szablewski Tadele talynone +Taras TBM tilovell Triet @@ -130,11 +146,9 @@ waaverecords ycv Yuniardi yuyoyuppe +Zeol Zoltan Zykova -Kairu -Deibisu -riri # OTHERS @@ -169,4 +183,3 @@ xamlstyler Xavalon Xbox Youdao - diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 612077fbb0..4ad463621f 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -125,6 +125,7 @@ bootstrapper BOOTSTRAPPERINSTALLFOLDER bostrot BOTTOMALIGN +boxmodel BPBF bpmf bpp @@ -150,6 +151,7 @@ Cangjie CANRENAME CAPTUREBLT CAPTURECHANGED +CARETBLINKING CAtl cch CCHDEVICENAME @@ -252,6 +254,7 @@ CREATESCHEDULEDTASK CREATESTRUCT CREATEWINDOWFAILED CRECT +CRH critsec Crossdevice CRSEL diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index d48cec9abd..c56e5fbb14 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -40,6 +40,9 @@ # tabs in c# \$"\\t +# Hexadecimal character pattern in code +\\x[0-9a-fA-F][0-9a-fA-F] + # windows line breaks in strings \\r\\n diff --git a/.github/workflows/msstore-submissions.yml b/.github/workflows/msstore-submissions.yml index 26d38e70c5..723eda1203 100644 --- a/.github/workflows/msstore-submissions.yml +++ b/.github/workflows/msstore-submissions.yml @@ -16,7 +16,7 @@ jobs: run: | release=$(curl https://api.github.com/repos/Microsoft/PowerToys/releases | jq '[.[]|select(.name | contains("Release"))][0]') assets=$(jq -n "$release" | jq '.assets') - powerToysSetup=$(jq -n "$assets" | jq '[.[]|select(.name | contains("PowerToysSetup"))]') + powerToysSetup=$(jq -n "$assets" | jq '[.[]|select(.name | contains("PowerToysUserSetup"))]') echo ::set-output name=powerToysInstallerX64Url::$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("x64"))][0].browser_download_url') echo ::set-output name=powerToysInstallerArm64Url::$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') diff --git a/.pipelines/ci/templates/run-ui-tests-ci.yml b/.pipelines/ci/templates/run-ui-tests-ci.yml index a45002aebc..8d0b550299 100644 --- a/.pipelines/ci/templates/run-ui-tests-ci.yml +++ b/.pipelines/ci/templates/run-ui-tests-ci.yml @@ -60,7 +60,7 @@ jobs: searchFolder: '$(Pipeline.Workspace)\build-${{ parameters.platform }}-${{ parameters.configuration }}' vstestLocationMethod: 'location' # otherwise fails to find vstest.console.exe #vstestLocation: '$(Agent.ToolsDirectory)\VsTest\**\${{ parameters.platform }}\tools\net462\Common7\IDE\Extensions\TestPlatform' - vstestLocation: '$(Agent.ToolsDirectory)\VsTest\17.10.0-release-24177-07\x64\tools\net462\Common7\IDE\Extensions\TestPlatform' + vstestLocation: '$(Agent.ToolsDirectory)\VsTest\17.10.0\x64\tools\net462\Common7\IDE\Extensions\TestPlatform' uiTests: true rerunFailedTests: true testAssemblyVer2: | diff --git a/COMMUNITY.md b/COMMUNITY.md index 97be115ed8..e2e944b23b 100644 --- a/COMMUNITY.md +++ b/COMMUNITY.md @@ -27,6 +27,9 @@ Heiko has helped triaging, discussing, and creating a substantial number of issu ### [@Jay-o-Way](https://github.com/Jay-o-Way) - Jay Jay has helped triaging, discussing, creating a substantial number of issues and PRs. +### [@jefflord](https://github.com/Jjefflord) - Jeff Lord +Jeff added in multiple new features into Keyboard manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys. + ### [@TheJoeFin](https://github.com/TheJoeFin) - [Joe Finney](https://joefinapps.com) Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor. @@ -34,14 +37,12 @@ Joe has helped triaging, discussing, issues as well as fixing bugs and building Helping keep our spelling correct :) ### [@martinchrzan](https://github.com/martinchrzan/) - Martin Chrzan - Color Picker is from Martin. ### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com) Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility. ### [@riverar](https://github.com/riverar) - [Rafael Rivera](https://withinrafael.com/) - Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/microsoft/PowerToys/issues/1907). He directly provided feedback to the CppWinRT team for bugs from this migration as well. ### [@royvou](https://github.com/royvou) @@ -153,14 +154,25 @@ Other contributors: ## PowerToys core team - [@crutkas](https://github.com/crutkas/) - Clint Rutkas - Lead +- [@ethanfangg](https://github.com/ethanfangg) - Ethan Fang - Lead - [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon - Product Manager -- [@ethanfangg](https://github.com/ethanfangg) - Ethan Fang - Product Manager - [@plante-msft](https://github.com/plante-msft) - Connor Plante - Product Manager - [@nguyen-dows](https://github.com/nguyen-dows) - Christopher Nguyen - Product Manager - [@joadoumie](https://github.com/joadoumie) - Jordi Adoumie - Product Manager - [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev lead +- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev lead +- [@drawbyperpetual](https://github.com/drawbyperpetual) - Anirudha Shankar - Dev - [@donlaci](https://github.com/donlaci) - Laszlo Nemeth - Dev - [@gokcekantarci](https://github.com/gokcekantarci) - Gokce Kantarci - Dev - [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev - [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev + +# Former PowerToys core team members + +- [@indierawk2k2](https://github.com/indierawk2k2) - Mike Harsh - Product Manager +- [@enricogior](https://github.com/enricogior) - Enrico Giordani - Dev Lead +- [@bzoz](https://github.com/bzoz) - Bartosz Sosnowski - Dev +- [@ivan100sic](https://github.com/ivan100sic) - Ivan Stošić - Dev +- [@mykhailopylyp](https://github.com/mykhailopylyp) - Mykhailo Pylyp - Dev +- [@taras-janea](https://github.com/taras-janea) - Taras Sich - Dev - [@yuyoyuppe](https://github.com/yuyoyuppe) - Andrey Nekrasov - Dev diff --git a/Directory.Packages.props b/Directory.Packages.props index 8126639799..0c64c73a53 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,7 +46,7 @@ - + @@ -75,7 +75,7 @@ - + diff --git a/NOTICE.md b/NOTICE.md index 97d24cee97..bdef06557e 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1333,7 +1333,7 @@ EXHIBIT A -Mozilla Public License. - Microsoft.Windows.CsWinRT 2.0.4 - Microsoft.Windows.SDK.BuildTools 10.0.22621.2428 - Microsoft.Windows.SDK.Contracts 10.0.19041.1 -- Microsoft.WindowsAppSDK 1.5.240311000 +- Microsoft.WindowsAppSDK 1.5.240428000 - Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9 - Microsoft.Xaml.Behaviors.Wpf 1.1.39 - ModernWpfUI 0.9.4 @@ -1355,7 +1355,7 @@ EXHIBIT A -Mozilla Public License. - System.Data.SqlClient 4.8.6 - System.Diagnostics.EventLog 8.0.0 - System.Diagnostics.PerformanceCounter 8.0.0 -- System.Drawing.Common 8.0.5 +- System.Drawing.Common 8.0.6 - System.IO.Abstractions 17.2.3 - System.IO.Abstractions.TestingHelpers 17.2.3 - System.Management 8.0.0 diff --git a/PowerToys.sln b/PowerToys.sln index da7f73ebcc..f48208a51f 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -146,11 +146,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E775CC2C-24CB-48D6-9C3A-BE4CCE0DB17A}" - ProjectSection(SolutionItems) = preProject - src\tests\win-app-driver\README.md = src\tests\win-app-driver\README.md - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "previewpane", "previewpane", "{2F305555-C296-497E-AC20-5FA1B237996A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PreviewHandlerCommon", "src\modules\previewpane\Common\PreviewHandlerCommon.csproj", "{AF2349B8-E5B6-4004-9502-687C1C7730B1}" diff --git a/README.md b/README.md index 40784a6157..fa264596bb 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,15 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline | | Current utilities: | | |--------------|--------------------|--------------| -| [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | -| [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | -| [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | -| [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | -| [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [Peek](https://aka.ms/PowerToysOverview_Peek) | -| [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | -| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [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) | +| [Advanced Paste](https://aka.ms/PowerToysOverview_AdvancedPaste) | [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | +| [Command Not Found](https://aka.ms/PowerToysOverview_CmdNotFound) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | +| [Environment Variables](https://aka.ms/PowerToysOverview_EnvironmentVariables) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | +| [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | +| [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | +| [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | +| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | +| [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) | ## Installing and running Microsoft PowerToys @@ -40,19 +41,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user. -[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F54 -[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F53 -[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.80.1/PowerToysUserSetup-0.80.1-x64.exe -[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.80.1/PowerToysUserSetup-0.80.1-arm64.exe -[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.80.1/PowerToysSetup-0.80.1-x64.exe -[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.80.1/PowerToysSetup-0.80.1-arm64.exe +[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.82%22 +[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F54 +[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysUserSetup-0.81.0-x64.exe +[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysUserSetup-0.81.0-arm64.exe +[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysSetup-0.81.0-x64.exe +[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysSetup-0.81.0-arm64.exe | Description | Filename | sha256 hash | |----------------|----------|-------------| -| Per user - x64 | [PowerToysUserSetup-0.80.1-x64.exe][ptUserX64] | 23E35F7B33C6F24237BCA3D5E8EDF9B3BD4802DD656C402B40A4FC82670F8BE3 | -| Per user - ARM64 | [PowerToysUserSetup-0.80.1-arm64.exe][ptUserArm64] | C5EECF0D9D23AB8C14307F91CA28D2CF4DA5932D705F07AE93576C259F74B4D1 | -| Machine wide - x64 | [PowerToysSetup-0.80.1-x64.exe][ptMachineX64] | 62373A08BB8E1C1173D047509F3EA5DCC0BE1845787E07BCDA3F6A09DA2A0C17 | -| Machine wide - ARM64 | [PowerToysSetup-0.80.1-arm64.exe][ptMachineArm64] | 061EF8D1B10D68E69D04F98A2D8E1D8047436174C757770778ED23E01CC3B06C | +| Per user - x64 | [PowerToysUserSetup-0.81.0-x64.exe][ptUserX64] | E62B1EE81954A75355C04E7567B1C9AAD6034AA0C61AD22587F8746D0DC488C8 | +| Per user - ARM64 | [PowerToysUserSetup-0.81.0-arm64.exe][ptUserArm64] | 75330A2DB4F9EF9B548B3B58F8BF3262C8C67E680042639BBBBC87EA244F24E2 | +| Machine wide - x64 | [PowerToysSetup-0.81.0-x64.exe][ptMachineX64] | 29F151B01FE3C94D4FD75F2D6E8F09A6C0F0962385B83A5A733F6717312F639D | +| Machine wide - ARM64 | [PowerToysSetup-0.81.0-arm64.exe][ptMachineArm64] | FCE636220E1FB854771258D9558E07B7532728AD4C722A7920338DEE60DEECF7 | This is our preferred method. @@ -98,136 +99,158 @@ 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.80 - March 2024 Update +### 0.81 - Build 2024 Update -In this release, we focused on stability and improvements. The next release is planned to be released during [Microsoft Build 2024](https://build.microsoft.com/) (late May). +In this release, we focused on new features, stability and improvements. **Highlights** - - New feature: Desired State Configuration support, allowing the use of winget configure for PowerToys. Check the [DSC documentation](https://aka.ms/powertoys-docs-dsc-configure) for more information. - - The Windows App SDK dependency was updated to 1.5.1, fixing many underlying UI issues. - - WebP/WebM files support was added to Peek. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Audio files support was added to Peek. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Automated UI testing for FancyZones Editor was added to CI. + - New utility: Advanced Paste - This is an evolution based on feedback of the Paste As Plain Text utility to do more. It can paste as plain text, markdown, or json directly with the new UX or with a direct keystroke invoke. These are fully locally executed. In addition, it now has an AI powered option as well if you wish with the free form text box. The AI feature is 100% opt-in and requires an Open AI key. This new system will allow us to have more freedom in the future to quickly add in new features like pasting an image directly to a file or handle additional meta data types past just text. + - Thanks [@craigloewen-msft](https://github.com/craigloewen-msft) for the core functionality and [@niels9001](https://github.com/niels9001) for the UI/UX design! + - Command Not Found now uses the PowerShell Gallery release and now supports ARM64. Thanks [@carlos-zamora](https://github.com/carlos-zamora)! + - Fixed most accessibility issues opened after the latest accessibility review. + - Refactored, packaged and released the main Environment Variables Editor, Hosts File Editor and Registry Preview utilities functionality as controls to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome! ### General - - Added a Quick Access entry to access the flyout from PowerToys' tray icon right click menu. Thanks [@pekvasnovsky](https://github.com/pekvasnovsky)! - - Added support for Desired State Configuration in PowerToys, allowing the use of winget configure to configure many settings. + - Fixed crashes on older CPUS by updating .NET to 8.0.4. (This was a hotfix for 0.80) -### Awake +### Advanced Paste - - Fix an issue causing the "Keep screen on" option to disable after Awake deactivated itself. + - New utility: Advanced Paste - This is an evolution based on feedback of the Paste As Plain Text utility to do more. It can paste as plain text, markdown, or json directly with the new UX or with a direct keystroke invoke. These are fully locally executed. In addition, it now has an AI powered option as well if you wish with the free form text box. The AI feature is 100% opt-in and requires an Open AI key. This new system will allow us to have more freedom in the future to quickly add in new features like pasting an image directly to a file or handle additional meta data types past just text. + - Thanks [@craigloewen-msft](https://github.com/craigloewen-msft) for the core functionality and [@niels9001](https://github.com/niels9001) for the UI/UX design! + +### AlwaysOnTop + + - Enable border anti-aliasing. Thanks [@ewancg](https://github.com/ewancg)! ### Color Picker - - Fixed a UI issue causing the color picker modal to hide part of the color bar. Thanks [@TheChilledBuffalo](https://github.com/TheChilledBuffalo)! + - Improved accessibility by making the Settings and Copy to clipboard buttons focusable. + - Improved accessibility by supporting picking a color using the keyboard. ### Command Not Found - - Now tries to find a preview version of PowerShell if no stable version is found. + - Upgraded the Command Not Found to use the new PowerShell Gallery release and support ARM64. Thanks [@carlos-zamora](https://github.com/carlos-zamora)! + +### Environment Variables Editor + + - Refactored, packaged and released the main Environment Variables Editor functionality as a control to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome! ### FancyZones - - Fixed a crash loading the editor when there's a layout with an empty name in the configuration file. - - Refactored layout internal data structures and common code to allow for automated testing. - - The pressing of the shift key is now detected through raw input to fix an issue causing the shift key to be locked for some users. + - Fixed window wrap around behavior when overriding Windows key and arrow shortcuts on single monitor scenarios. Thanks [@DanRosenberry](https://github.com/DanRosenberry)! + - Improved accessibility of the editor by listing the keyboard shortcuts in the Canvas Editor. ### File Explorer add-ons - - Fixed a crash occurring in the Monaco previewer when a file being previewed isn't found by the code behind. - - Fixed an issue in the Markdown previewer adding a leading space to code blocks. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! - - Fixed wrong location and scaling of preview results on screens with different DPIs. - - Added better clean up code to thumbnail handlers to prevent locking files. + - Updated Monaco to 0.47 and added the new sticky scroll setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! + - Added the new font size setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! + - Added support for .srt (subtitle) file previewing in DevFiles viewer. Thanks [@PesBandi](https://github.com/PesBandi)! -### File Locksmith +### Hosts File Editor - - Allow multiple lines to wrap when viewing the modal with selected file paths. Thanks [@sanidhyas3s](https://github.com/sanidhyas3s)! + - Refactored, packaged and released the main Hosts File Editor functionality as a control to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome! + +### Image Resizer + + - Supported narrator announcing the checkboxes in the UI and the sizes combobox. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Improved accessibility by increasing contrast in the text color of combobox items. ### Installer - - Fixed the final directory name of the PowerToys Run VSCode Workspaces plugin in the installation directory to match the plugin name. Thanks [@zetaloop](https://github.com/zetaloop)! - - Used more generic names for the bootstrap steps, so that "Installing PowerToys" is not shown when uninstalling. + - Fixed some install failures when the folders the DSC module is to be installed in isn't accessible by the WiX installer. (This was a hotfix for 0.80) + - Detecting install location for DSC now uses registry instead of WMI to improve performance. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed an error causing the machine scope installer to not install correctly in machines where the documents folder is in a UNC network path. We're still working in a fix for the user scope installer. ### Keyboard Manager - - Fixed an issue that would clear out KBM mappings when certain numpad keys were used as the second key of a chord. - - Added a comment in localization files so that translators won't translate "Text" as "SMS". + - Fixed startup crashes in the editor when the Visual C++ Redistributable wasn't installed. (This was a hotfix for 0.80) + - Fixed an accessibility issue where the first button wasn't focused after adding a new row in the editor. + - Environment Variables are now expanded in arguments of programs started through a shortcut. Thanks [@HydroH](https://github.com/HydroH)! + +### Paste as Plain Text + + - Paste as Plain Text was removed as a separate utility, since its functionality is now part of the Advanced Paste utility. ### Peek - - Added support to .WebP/.WebM files in the image/video previewer. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Added support for audio files. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Fixed an issue causing the open file button in the title bar to be un-clickable. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Fixed an issue when previewing a folder with a dot in the name that caused Peek to try to preview it as a file. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Updated icons, tweaked UI and refactored internal code. Thanks [@Jay-o-Way](https://github.com/Jay-o-Way)! + - Updated Monaco to 0.47 and added the new sticky scroll setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! + - Added the new font size setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! + - Upgrade the SharpCompress dependency to 0.37.2 and fixed archive parsing. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed aliasing in the image viewer. + - Added support for .srt (subtitle) file previewing in DevFiles viewer. Thanks [@PesBandi](https://github.com/PesBandi)! + +### Power Rename + + - Fixed the descriptions that were mixed up in the regex helper (\S and \w). ### PowerToys Run - - Added a setting to the Windows Search plugin to exclude files and patterns from the results. Thanks [@HydroH](https://github.com/HydroH)! - - Fixed an issue showing thumbnails caused by a hash collision between similar images. - - Added the "checkbox and multiline text box" additional property type for plugins and improved multiline text handling. Thanks [@htcfreek](https://github.com/htcfreek)! + - Added support for UNC paths starting with // in the Folder plugin. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed the plugin load failed message to list the failed plugins. Thanks [@belkiss](https://github.com/belkiss)! + - Icons for MSIX packages are now updated when a package update is detected. Thanks [@HydroH](https://github.com/HydroH)! + - Use Mica backdrop instead of Acrylic to fix random crashes caused by the Windows composition being momentarily turned off. + - Improved accessibility in the results list action buttons by improving contrast of hovered/focused buttons. ### Quick Accent - - Added the Schwa character to the Italian character set. Thanks [@damantioworks](https://github.com/damantioworks)! + - Added support for the Esperanto character set. Thanks [@salutontalk](https://github.com/salutontalk) and [@ccmywish](https://github.com/ccmywish)! + - Added the ǽ and ϑ characters. Thanks [@PesBandi](https://github.com/PesBandi)! ### Registry Preview - - Allow alternative valid names for the root keys. Thanks [@e-t-l](https://github.com/e-t-l)! - - Fixed an issue causing many pick file windows to be opened simultaneously. Thanks [@randyrants](https://github.com/randyrants)! - -### Screen Ruler - - - Updated the measure icons for clarity. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker) and [@niels9001](https://github.com/niels9001)! - -### Shortcut Guide - - - Updated the Emoji shortcut that is shown to the new Windows key + period (.) hotkey. + - Refactored, packaged and released the main Registry Preview functionality as a control to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome! ### Text Extractor - - Fixed issues creating the extract layout on certain monitor configurations. - -### Video Conference Mute - - - Added enable/disable telemetry to get usage data. + - Fixed an issue causing the Settings page to not be opened when clicking the Settings button in Text Extractor's overlay. (This was a hotfix for 0.80) ### Settings - - Added locks to some terms (like the name of some utilities) so that they aren't localized. - - Fixed some shortcuts not being shown properly in the Flyout and Dashboard. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Updated image for Color Picker and outdated animations for utilities in OOBE. Thanks [@niels9001](https://github.com/niels9001)! + - Improved UI ordering of the File Explorer add-ons. Thanks [@niels9001](https://github.com/niels9001)! + - Applied fixes to theme overriding and cleaned up unneeded code. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed misspells in references to the Hosts File Editor utility. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Improved accessibility of the Select Folder button in the Settings Backup UI. + - Improved accessibility by improving focus and tab navigation in the ColorPicker page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Added a description to the fallback encoder setting in the Image Resizer page. Thanks [@Kissaki](https://github.com/Kissaki)! + - Refactored and improved performance in the PowerToys Run plugins UI in the Settings page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed a crash when a user cleared the contents of a Number Box in the PowerToys Run plugins additional options. Thanks [@htcfreek](https://github.com/htcfreek)! + - Update the PATH environment variables with the user scope PATH when entering the Command Not Found page to improve PowerShell detection. ### Documentation - - Added FastWeb plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@CCcat8059](https://github.com/CCcat8059)! - - Removed the old security link to MSRC from the create new issue page, since security.md is already linked there. - - Added clarity regarding unofficial plugins to the PowerToys Run thirdPartyRunPlugins.md docs. + - Added the WebSearchShortcut plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@Daydreamer-riri](https://github.com/Daydreamer-riri)! + - Updated COMMUNITY.md with the project managers that are part of the core team. + - Improved the DSC samples. + - Added the 1Password plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@KairuDeibisu](https://github.com/KairuDeibisu)! + - Added the UnicodeInput plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@nathancartlidge](https://github.com/nathancartlidge)! ### Development - - Updated System.Drawing.Common to 8.0.3 to fix CI builds after the .NET 8.0.3 upgrade was released. - - Adjusted the GitHub action names for releasing to winget and Microsoft Store so they're clearer in the UI. - - Upgraded WinAppSDK to 1.5.1, fixing many related issues. - - Consolidate the WebView2 version used by WinUI 2 in the Keyboard Manager Editor. - - Unified the use of Precompiled Headers when building on CI. Thanks [@dfederm](https://github.com/dfederm)! - - Added UI tests for FancyZones Editor in CI. - - Added a GitHub bot to identify possible duplicates when a new issue is created. Thanks [@craigloewen-msft](https://github.com/craigloewen-msft)! - - Updated the WiX installer dependency to 3.14.1 to fix possible security issues. - - Changed the pipelines to use pipeline artifacts instead of build artifacts. Thanks [@dfederm](https://github.com/dfederm)! - - Added the -graph parameter for pipelines. Thanks [@dfederm](https://github.com/dfederm)! - - Tests in the pipelines now run as part of the build step to save on CI time. Thanks [@dfederm](https://github.com/dfederm)! + - Updated System.Drawing.Common to 8.0.5 to fix CI builds after the .NET 8.0.5 upgrade was released. + - Fixed file permissions when doing a build using cache on PR CI. Thanks [@dfederm](https://github.com/dfederm)! + - Removed the Test SDK reference on ARM64 to fix local building for ARM64. Thanks [@dfederm](https://github.com/dfederm)! + - Replaced make_pair with RemapBufferRow in Keyboard Manager internal code. Thanks [@masaru-iritani](https://github.com/masaru-iritani)! + - Added CODEOWNERS file to protect sensitive parts of the repo. Thanks [@htcfreek](https://github.com/htcfreek) for the help in figuring out how to make the spellcheck folder an exception! + - Added comments in code. to make it clear what the error badge in PowerToys Run plugin list in Settings means. Thanks [@Jay-o-Way](https://github.com/Jay-o-Way)! + - Enabled caching by default in the PR CI pipelines. Thanks [@dfederm](https://github.com/dfederm)! + - Disabled caching for PR started from forks, since those were failing. Thanks [@dfederm](https://github.com/dfederm)! + - Removed baseline files for policy checking and turned on the "TSA" process in the release pipelines instead. + - Added caching of nuget packages in the PR CI pipelines. Thanks [@dfederm](https://github.com/dfederm)! + - Updated the release CI pipelines TouchdownBuildTask to v3. + - Moved the release CI pipelines to ESRPv5. + - Added a policy for GitHub Copilot Workspaces for the repo on GitHub. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! -#### What is being planned for version 0.81 +#### What is being planned for version 0.82 -For [v0.81][github-next-release-work], we'll work on the items below: +For [v0.82][github-next-release-work], we'll work on the items below: - Stability / bug fixes - Language selection - New module: File Actions Menu -The next release is planned to be released during Microsoft Build 2024. - ## PowerToys Community The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldn’t be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Month by month, you directly help make PowerToys a better piece of software. diff --git a/doc/devdocs/settingsv2/communication-with-modules.md b/doc/devdocs/settingsv2/communication-with-modules.md index d857e36936..a19190fbe7 100644 --- a/doc/devdocs/settingsv2/communication-with-modules.md +++ b/doc/devdocs/settingsv2/communication-with-modules.md @@ -2,7 +2,7 @@ ## Through runner - The settings process communicates changes in the UI to most modules using the runner through delegates. -- More details on this are mentioned in [`runner-ipc.md`](settingsv2/runner-ipc.md). +- More details on this are mentioned in [`runner-ipc.md`](runner-ipc.md). ## PT Run - Any changes to the UI are saved by the settings process in the `settings.json` file located within the `/Local/Microsoft/PowerToys/Launcher/` folder. @@ -12,4 +12,4 @@ Eg: The maximum number of results drop down updates the maximum number of rows i ## Keyboard Manager - The Settings process and keyboard manager share access to a common `default.json` file which contains information about the remapped keys and shortcuts. - To ensure that there is no contention while both processes try to access the common file, there is a named file mutex. -- The settings process expects the keyboard manager process to create the `default.json` file if it does not exist. It does not create the file in case it is not present. \ No newline at end of file +- The settings process expects the keyboard manager process to create the `default.json` file if it does not exist. It does not create the file in case it is not present. diff --git a/doc/thirdPartyRunPlugins.md b/doc/thirdPartyRunPlugins.md index fa474ff310..5717b3ef43 100644 --- a/doc/thirdPartyRunPlugins.md +++ b/doc/thirdPartyRunPlugins.md @@ -27,7 +27,6 @@ Contact the developers of a plugin directly for assistance with a specific plugi | ------ | ------ | ----------- | | [BrowserSearch](https://github.com/TBM13/BrowserSearch) | [TBM13](https://github.com/TBM13) | Search your browser history | | [GitHub Emoji](https://github.com/hlaueriksson/GEmojiSharp) | [hlaueriksson](https://github.com/hlaueriksson) | Search GitHub Emoji | -| [Guid](https://github.com/skttl/ptrun-guid) | [skttl](https://github.com/skttl) | Guid generator | | [PowerTranslator](https://github.com/N0I0C0K/PowerTranslator) | [N0I0C0K](https://github.com/N0I0C0K) | Text translator based on Youdao | | [Quick Lookup](https://github.com/GTGalaxi/quick-lookup-ptrun) | [gtgalaxi](https://github.com/GTGalaxi) | Search across multiple cyber security tools | | [Input Typer](https://github.com/CoreyHayward/PowerToys-Run-InputTyper) | [CoreyHayward](https://github.com/CoreyHayward) | Type the input as if sent from a keyboard | @@ -36,6 +35,7 @@ Contact the developers of a plugin directly for assistance with a specific plugi | [FastWeb](https://github.com/CCcat8059/FastWeb) | [CCcat](https://github.com/CCcat8059) | Open website in browser | | [WebSearchShortcut](https://github.com/Daydreamer-riri/PowerToys-Run-WebSearchShortcut) | [Riri](https://github.com/Daydreamer-riri) | Select a specific search engine to perform searches. | | [UnicodeInput](https://github.com/nathancartlidge/powertoys-run-unicode) | [nathancartlidge](https://github.com/nathancartlidge) | Copy Unicode characters to the clipboard | +| [PowerHexInspector](https://github.com/NaroZeol/PowerHexInspector) | [NaroZeol](https://github.com/NaroZeol) | Peek other forms of an input number | ## Extending software plugins @@ -44,6 +44,7 @@ Below are community created plugins that target a website or software. They are | Plugin | Author | Description | | ------ | ------ | ----------- | | [Edge Favorite](https://github.com/davidegiacometti/PowerToys-Run-EdgeFavorite) | [davidegiacometti](https://github.com/davidegiacometti) | Open Microsoft Edge favorites | +| [Edge Workspaces](https://github.com/quachpas/PowerToys-Run-EdgeWorkspaces) | [quachpas](https://github.com/quachpas) | Open Microsoft Edge workspaces| | [Everything](https://github.com/lin-ycv/EverythingPowerToys) | [Yu Chieh (Victor) Lin](https://github.com/Lin-ycv) | Get search results from Everything | | [GitKraken](https://github.com/davidegiacometti/PowerToys-Run-GitKraken) | [davidegiacometti](https://github.com/davidegiacometti) | Open GitKraken repositories | | [Visual Studio Recents](https://github.com/davidegiacometti/PowerToys-Run-VisualStudio) | [davidegiacometti](https://github.com/davidegiacometti) | Open Visual Studio recents | diff --git a/installer/PowerToysSetup/Core.wxs b/installer/PowerToysSetup/Core.wxs index 6254bd99ed..81a655b3e1 100644 --- a/installer/PowerToysSetup/Core.wxs +++ b/installer/PowerToysSetup/Core.wxs @@ -8,7 +8,7 @@ - + @@ -46,34 +46,19 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - + + + + @@ -89,7 +74,7 @@ - + @@ -130,23 +115,27 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index aab1ed1520..22940acbb5 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -136,6 +136,11 @@ + + + + + NOT Installed @@ -149,7 +154,10 @@ - + + + + NOT Installed @@ -177,8 +185,12 @@ + + + Installed AND (REMOVE="ALL") + + - NOT Installed @@ -211,6 +223,10 @@ Property="UnApplyModulesRegistryChangeSets" Value="[INSTALLFOLDER]" /> + + @@ -265,6 +281,21 @@ DllEntry="UninstallEmbeddedMSIXCA" /> + + + + + @@ -421,9 +453,6 @@ - - - diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 377a9e7c9d..b71cf33dda 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -139,6 +139,23 @@ LExit: return SUCCEEDED(hr); } +static std::filesystem::path GetUserPowerShellModulesPath() +{ + PWSTR myDocumentsBlockPtr; + + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &myDocumentsBlockPtr))) + { + const std::wstring myDocuments{ myDocumentsBlockPtr }; + CoTaskMemFree(myDocumentsBlockPtr); + return std::filesystem::path(myDocuments) / "PowerShell" / "Modules"; + } + else + { + CoTaskMemFree(myDocumentsBlockPtr); + return {}; + } +} + UINT __stdcall LaunchPowerToysCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; @@ -162,7 +179,7 @@ UINT __stdcall LaunchPowerToysCA(MSIHANDLE hInstall) BOOL isSystemUser = IsLocalSystem(); if (isSystemUser) { - + auto action = [&commandLine](HANDLE userToken) { STARTUPINFO startupInfo{ .cb = sizeof(STARTUPINFO), .wShowWindow = SW_SHOWNORMAL }; PROCESS_INFORMATION processInformation; @@ -317,6 +334,125 @@ LExit: return WcaFinalize(er); } +const wchar_t* DSC_CONFIGURE_PSD1_NAME = L"Microsoft.PowerToys.Configure.psd1"; +const wchar_t* DSC_CONFIGURE_PSM1_NAME = L"Microsoft.PowerToys.Configure.psm1"; + +UINT __stdcall InstallDSCModuleCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + std::wstring installationFolder; + + hr = WcaInitialize(hInstall, "InstallDSCModuleCA"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = getInstallFolder(hInstall, installationFolder); + ExitOnFailure(hr, "Failed to get installFolder."); + + { + const auto baseModulesPath = GetUserPowerShellModulesPath(); + if (baseModulesPath.empty()) + { + hr = E_FAIL; + ExitOnFailure(hr, "Unable to determine Powershell modules path"); + } + + const auto modulesPath = baseModulesPath / L"Microsoft.PowerToys.Configure" / get_product_version(); + + std::error_code errorCode; + fs::create_directories(modulesPath, errorCode); + if (errorCode) + { + hr = E_FAIL; + ExitOnFailure(hr, "Unable to create Powershell modules folder"); + } + + for (const auto* filename : { DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME }) + { + fs::copy_file(fs::path(installationFolder) / "DSCModules" / filename, modulesPath / filename, fs::copy_options::overwrite_existing, errorCode); + + if (errorCode) + { + hr = E_FAIL; + ExitOnFailure(hr, "Unable to copy Powershell modules file"); + } + } + } + +LExit: + if (SUCCEEDED(hr)) + { + er = ERROR_SUCCESS; + Logger::info(L"DSC module was installed!"); + } + else + { + er = ERROR_INSTALL_FAILURE; + Logger::error(L"Couldn't install DSC module!"); + } + + return WcaFinalize(er); +} + +UINT __stdcall UninstallDSCModuleCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "UninstallDSCModuleCA"); + ExitOnFailure(hr, "Failed to initialize"); + + { + const auto baseModulesPath = GetUserPowerShellModulesPath(); + if (baseModulesPath.empty()) + { + hr = E_FAIL; + ExitOnFailure(hr, "Unable to determine Powershell modules path"); + } + + const auto powerToysModulePath = baseModulesPath / L"Microsoft.PowerToys.Configure"; + const auto versionedModulePath = powerToysModulePath / get_product_version(); + + std::error_code errorCode; + + for (const auto* filename : { DSC_CONFIGURE_PSD1_NAME, DSC_CONFIGURE_PSM1_NAME }) + { + fs::remove(versionedModulePath / filename, errorCode); + + if (errorCode) + { + hr = E_FAIL; + ExitOnFailure(hr, "Unable to delete DSC file"); + } + } + + for (const auto* modulePath : { &versionedModulePath, &powerToysModulePath }) + { + fs::remove(*modulePath, errorCode); + + if (errorCode) + { + hr = E_FAIL; + ExitOnFailure(hr, "Unable to delete DSC folder"); + } + } + } + +LExit: + if (SUCCEEDED(hr)) + { + er = ERROR_SUCCESS; + Logger::info(L"DSC module was uninstalled!"); + } + else + { + er = ERROR_INSTALL_FAILURE; + Logger::error(L"Couldn't uninstall DSC module!"); + } + + return WcaFinalize(er); +} + UINT __stdcall InstallEmbeddedMSIXCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; @@ -472,9 +608,19 @@ UINT __stdcall UninstallCommandNotFoundModuleCA(MSIHANDLE hInstall) hr = getInstallFolder(hInstall, installationFolder); ExitOnFailure(hr, "Failed to get installFolder."); +#ifdef _M_ARM64 + command = "powershell.exe"; + command += " "; + command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted"; + command += " -Command "; + command += "\"[Environment]::SetEnvironmentVariable('PATH', [Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('PATH', 'User'), 'Process');"; + command += "pwsh.exe -NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File '" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "'\""; +#else command = "pwsh.exe"; command += " "; command += "-NoProfile -NonInteractive -NoLogo -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + winrt::to_string(installationFolder) + "\\WinUI3Apps\\Assets\\Settings\\Scripts\\DisableModule.ps1" + "\""; +#endif + system(command.c_str()); diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def index a7d2f68f72..f685a0be1d 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -18,10 +18,12 @@ EXPORTS CertifyVirtualCameraDriverCA InstallVirtualCameraDriverCA InstallEmbeddedMSIXCA + InstallDSCModuleCA UnApplyModulesRegistryChangeSetsCA UninstallVirtualCameraDriverCA UnRegisterContextMenuPackagesCA UninstallEmbeddedMSIXCA + UninstallDSCModuleCA UninstallServicesCA UninstallCommandNotFoundModuleCA UpgradeCommandNotFoundModuleCA diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index 8d6c997f0a..f1c4db4d75 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "GPOWrapper.h" #include "GPOWrapper.g.cpp" @@ -172,6 +172,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOnlineAIModelsValue() + { + return static_cast(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredProjectsEnabledValue() { return static_cast(powertoys_gpo::getConfiguredProjectsEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 1c7d65b028..37bad3d4d8 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "GPOWrapper.g.h" #include @@ -49,6 +49,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue(); static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue(); + static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue(); static GpoRuleConfigured GetConfiguredProjectsEnabledValue(); }; } diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 23cb45dc5d..70a149e3a7 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -53,6 +53,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue(); static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue(); + static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue(); static GpoRuleConfigured GetConfiguredProjectsEnabledValue(); } } diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index f9df90a6b3..5b82dbf799 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -71,7 +71,7 @@ namespace powertoys_gpo { // The registry value names for other PowerToys policies. const std::wstring POLICY_ALLOW_EXPERIMENTATION = L"AllowExperimentation"; const std::wstring POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS = L"PowerLauncherAllPluginsEnabledState"; - + const std::wstring POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS = L"AllowPowerToysAdvancedPasteOnlineAIModels"; inline std::optional readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name) { @@ -476,4 +476,9 @@ namespace powertoys_gpo { { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS); } + + inline gpo_rule_configured_t getAllowedAdvancedPasteOnlineAIModelsValue() + { + return getUtilityEnabledValue(POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS); + } } diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 21f767bf32..53c804c8b0 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -1,11 +1,11 @@ - + - + @@ -18,6 +18,7 @@ + @@ -28,6 +29,9 @@ + + + @@ -499,5 +503,15 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index dcbda24e05..c4e611496a 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -1,7 +1,7 @@ - + PowerToys PowerToys @@ -9,6 +9,7 @@ Microsoft PowerToys Installer and Updates PowerToys Run + Advanced Paste Projects PowerToys version 0.64.0 or later @@ -21,6 +22,7 @@ PowerToys version 0.77.0 or later PowerToys version 0.78.0 or later PowerToys version 0.81.0 or later + PowerToys version 0.81.1 or later This policy configures the enabled state for all PowerToys utilities. @@ -125,6 +127,12 @@ If you disable or don't configure this policy, either the user or the policy "Co You can set the enabled state for all plugins not configured by this policy using the policy "Configure enabled state for all plugins". Note: Changes require a restart of PowerToys Run. + + This policy configures the enabled disable state for using Advanced Paste online AI models. + +If you enable or don't configure this policy, the user takes control over the enabled state of the Enable paste with AI Advanced Paste setting. + +If you disable this policy, the user won't be able to enable Enable paste with AI Advanced Paste setting and use Advanced Paste AI prompt nor set up the Open AI key in PowerToys Settings. Configure global utility enabled state Advanced Paste: Configure enabled state @@ -173,6 +181,7 @@ Note: Changes require a restart of PowerToys Run. Configure enabled state for individual plugins QOI file preview: Configure enabled state QOI file thumbnail: Configure enabled state + Advanced Paste: Allow using online AI models diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs index e8f1df2ade..690e3f7a1f 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; using ManagedCommon; @@ -14,6 +16,10 @@ namespace AdvancedPaste.Helpers { internal static class JsonHelper { + // List of supported CSV delimiters and Regex to detect separator property + private static readonly char[] CsvDelimArry = [',', ';', '\t']; + private static readonly Regex CsvSepIdentifierRegex = new Regex(@"^sep=(.)$", RegexOptions.IgnoreCase); + internal static string ToJsonFromXmlOrCsv(DataPackageView clipboardData) { Logger.LogTrace(); @@ -53,11 +59,31 @@ namespace AdvancedPaste.Helpers { var csv = new List(); - foreach (var line in text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) + string[] lines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + + // Detect the csv delimiter and the count of occurrence based on the first two csv lines. + GetCsvDelimiter(lines, out char delim, out int delimCount); + + foreach (var line in lines) { - csv.Add(line.Split(",")); + // If line is separator property line, then skip it + if (CsvSepIdentifierRegex.IsMatch(line)) + { + continue; + } + + // A CSV line is valid, if the delimiter occurs more or equal times in every line compared to the first data line. (More because sometimes the delimiter occurs in a data string.) + if (line.Count(x => x == delim) >= delimCount) + { + csv.Add(line.Split(delim)); + } + else + { + throw new FormatException("Invalid CSV format: Number of delimiters wrong in the current line."); + } } + Logger.LogDebug("Convert from csv."); jsonText = JsonConvert.SerializeObject(csv, Newtonsoft.Json.Formatting.Indented); } } @@ -66,7 +92,79 @@ namespace AdvancedPaste.Helpers Logger.LogError("Failed parsing input as csv", ex); } + // Try convert Plain Text + try + { + if (string.IsNullOrEmpty(jsonText)) + { + var plainText = new List(); + + foreach (var line in text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) + { + plainText.Add(line); + } + + Logger.LogDebug("Convert from plain text."); + jsonText = JsonConvert.SerializeObject(plainText, Newtonsoft.Json.Formatting.Indented); + } + } + catch (Exception ex) + { + Logger.LogError("Failed parsing input as plain text", ex); + } + return string.IsNullOrEmpty(jsonText) ? text : jsonText; } + + private static void GetCsvDelimiter(in string[] csvLines, out char delimiter, out int delimiterCount) + { + delimiter = '\0'; // Unicode "null" character. + delimiterCount = 0; + + if (csvLines.Length > 1) + { + // Try to select the delimiter based on the separator property. + Match matchChar = CsvSepIdentifierRegex.Match(csvLines[0]); + if (matchChar.Success) + { + // We can do matchChar[0] as the match only returns one character. + // We get the count from the second line, as the first one only contains the character definition and not a CSV data line. + char delimChar = matchChar.Groups[1].Value.Trim()[0]; + delimiter = delimChar; + delimiterCount = csvLines[1].Count(x => x == delimChar); + } + } + + if (csvLines.Length > 0 && delimiterCount == 0) + { + // Try to select the correct delimiter based on the first two CSV lines from a list of predefined delimiters. + foreach (char c in CsvDelimArry) + { + int cntFirstLine = csvLines[0].Count(x => x == c); + int cntNextLine = 0; // Default to 0 that the 'second line' check is always true. + + // Additional count if we have more than one line + if (csvLines.Length >= 2) + { + cntNextLine = csvLines[1].Count(x => x == c); + } + + // The delimiter is found if the count is bigger as from the last selected delimiter + // and if the next csv line does not exist or has the same number or more occurrences of the delimiter. + // (We check the next line to prevent false positives.) + if (cntFirstLine > delimiterCount && (cntNextLine == 0 || cntNextLine >= cntFirstLine)) + { + delimiter = c; + delimiterCount = cntFirstLine; + } + } + } + + // If the delimiter count is 0, we can't detect it and it is no valid CSV. + if (delimiterCount == 0) + { + throw new FormatException("Invalid CSV format: Failed to detect the delimiter."); + } + } } } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw b/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw index 618a54332c..8b9df98aff 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw +++ b/src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw @@ -127,7 +127,7 @@ Clipboard data is not text - To custom with AI not enabled + To custom with AI is not enabled Invalid API key or endpoint @@ -225,4 +225,7 @@ OpenAI Terms - \ No newline at end of file + + To custom with AI is disabled by your organization + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs index a3accbe344..1ba2625e79 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs @@ -16,7 +16,6 @@ using CommunityToolkit.Mvvm.Input; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.UI.Dispatching; -using Microsoft.UI.Xaml; using Microsoft.Win32; using Windows.ApplicationModel.DataTransfer; using WinUIEx; @@ -81,32 +80,40 @@ namespace AdvancedPaste.ViewModels { GetClipboardData(); - var openAIKey = AICompletionsHelper.LoadOpenAIKey(); - var currentKey = aiHelper.GetKey(); - bool keyChanged = openAIKey != currentKey; - - if (keyChanged) + if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { - app.GetMainWindow().StartLoading(); - - Task.Run(() => - { - aiHelper.SetOpenAIKey(openAIKey); - }).ContinueWith( - (t) => - { - _dispatcherQueue.TryEnqueue(() => - { - app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled); - OnPropertyChanged(nameof(InputTxtBoxPlaceholderText)); - IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled; - }); - }, - TaskScheduler.Default); + IsCustomAIEnabled = false; + OnPropertyChanged(nameof(InputTxtBoxPlaceholderText)); } else { - IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled; + var openAIKey = AICompletionsHelper.LoadOpenAIKey(); + var currentKey = aiHelper.GetKey(); + bool keyChanged = openAIKey != currentKey; + + if (keyChanged) + { + app.GetMainWindow().StartLoading(); + + Task.Run(() => + { + aiHelper.SetOpenAIKey(openAIKey); + }).ContinueWith( + (t) => + { + _dispatcherQueue.TryEnqueue(() => + { + app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled); + OnPropertyChanged(nameof(InputTxtBoxPlaceholderText)); + IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled; + }); + }, + TaskScheduler.Default); + } + else + { + IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled; + } } ClipboardHistoryEnabled = IsClipboardHistoryEnabled(); @@ -146,7 +153,11 @@ namespace AdvancedPaste.ViewModels { app.GetMainWindow().ClearInputText(); - if (!aiHelper.IsAIEnabled) + if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled"); + } + else if (!aiHelper.IsAIEnabled) { return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured"); } diff --git a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/Strings/en-us/Resources.resw b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/Strings/en-us/Resources.resw index 7ad6654168..8ea986dcb1 100644 --- a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/Strings/en-us/Resources.resw +++ b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/Strings/en-us/Resources.resw @@ -127,7 +127,7 @@ New profile - You can create profiles to quickly apply a set of preconfigured variables + Create profiles to quickly apply a set of preconfigured variables. Profile variables have precedence over User and System variables. Profiles @@ -194,7 +194,7 @@ Add - List of applied variables + Applied variables list shows the current state of the environment, including Profile, User, and System variables. Applied variables @@ -242,7 +242,7 @@ Add variable - Add, remove or edit USER and SYSTEM variables + Add, edit, or remove User and System variables. Edit diff --git a/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs b/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs index 2d03c2ea28..f0a280baa6 100644 --- a/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs +++ b/src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs @@ -47,6 +47,7 @@ namespace Hosts services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Views and ViewModels services.AddSingleton(); diff --git a/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml b/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml index 2537c462ca..c1229b2ec3 100644 --- a/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml +++ b/src/modules/Hosts/Hosts/HostsXAML/MainWindow.xaml @@ -8,7 +8,7 @@ xmlns:winuiex="using:WinUIEx" x:Uid="Window" Width="680" - MinWidth="480" + MinWidth="520" MinHeight="320" mc:Ignorable="d"> diff --git a/src/modules/Hosts/Hosts/Settings/UserSettings.cs b/src/modules/Hosts/Hosts/Settings/UserSettings.cs index 42e2848c18..8a3d9e844f 100644 --- a/src/modules/Hosts/Hosts/Settings/UserSettings.cs +++ b/src/modules/Hosts/Hosts/Settings/UserSettings.cs @@ -5,7 +5,6 @@ using System; using System.IO.Abstractions; using System.Threading; -using HostsUILib.Helpers; using HostsUILib.Settings; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; @@ -45,6 +44,8 @@ namespace Hosts.Settings // Moved from Settings.UI.Library public HostsEncoding Encoding { get; set; } + public event EventHandler LoopbackDuplicatesChanged; + public UserSettings() { _settingsUtils = new SettingsUtils(); @@ -58,8 +59,6 @@ namespace Hosts.Settings _watcher = Helper.GetFileWatcher(HostsModuleName, "settings.json", () => LoadSettingsFromJson()); } - public event EventHandler LoopbackDuplicatesChanged; - private void LoadSettingsFromJson() { lock (_loadingSettingsLock) diff --git a/src/modules/Hosts/HostsUILib/Helpers/DuplicateService.cs b/src/modules/Hosts/HostsUILib/Helpers/DuplicateService.cs new file mode 100644 index 0000000000..f6f89e6dbb --- /dev/null +++ b/src/modules/Hosts/HostsUILib/Helpers/DuplicateService.cs @@ -0,0 +1,165 @@ +// 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.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using HostsUILib.Models; +using HostsUILib.Settings; +using Microsoft.UI.Dispatching; + +namespace HostsUILib.Helpers +{ + public class DuplicateService : IDuplicateService, IDisposable + { + private record struct Check(string Address, string[] Hosts); + + private readonly IUserSettings _userSettings; + private readonly DispatcherQueue _dispatcherQueue; + private readonly Queue _checkQueue; + private readonly ManualResetEvent _checkEvent; + private readonly Thread _queueThread; + + private readonly string[] _loopbackAddresses = + { + "0.0.0.0", + "::", + "::0", + "0:0:0:0:0:0:0:0", + "127.0.0.1", + "::1", + "0:0:0:0:0:0:0:1", + }; + + private ReadOnlyCollection _entries; + private bool _disposed; + + public DuplicateService(IUserSettings userSettings) + { + _userSettings = userSettings; + + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _checkQueue = new Queue(); + _checkEvent = new ManualResetEvent(false); + + _queueThread = new Thread(ProcessQueue); + _queueThread.IsBackground = true; + _queueThread.Start(); + } + + public void Initialize(IList entries) + { + _entries = entries.AsReadOnly(); + + if (_checkQueue.Count > 0) + { + _checkQueue.Clear(); + } + + foreach (var entry in _entries) + { + if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address)) + { + continue; + } + + _checkQueue.Enqueue(new Check(entry.Address, entry.SplittedHosts)); + } + + _checkEvent.Set(); + } + + public void CheckDuplicates(string address, string[] hosts) + { + _checkQueue.Enqueue(new Check(address, hosts)); + _checkEvent.Set(); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void ProcessQueue() + { + while (true) + { + _checkEvent.WaitOne(); + + while (_checkQueue.Count > 0) + { + var check = _checkQueue.Dequeue(); + FindDuplicates(check.Address, check.Hosts); + } + + _checkEvent.Reset(); + } + } + + private void FindDuplicates(string address, string[] hosts) + { + var entries = _entries.Where(e => + string.Equals(e.Address, address, StringComparison.OrdinalIgnoreCase) + || hosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any()); + + foreach (var entry in entries) + { + SetDuplicate(entry); + } + } + + private void SetDuplicate(Entry entry) + { + if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address)) + { + _dispatcherQueue.TryEnqueue(() => + { + entry.Duplicate = false; + }); + + return; + } + + var duplicate = false; + + /* + * Duplicate are based on the following criteria: + * Entries with the same type and at least one host in common + * Entries with the same type and address, except when there is only one entry with less than 9 hosts for that type and address + */ + if (_entries.Any(e => e != entry + && e.Type == entry.Type + && entry.SplittedHosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any())) + { + duplicate = true; + } + else if (_entries.Any(e => e != entry + && e.Type == entry.Type + && string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase))) + { + duplicate = entry.SplittedHosts.Length < Consts.MaxHostsCount + && _entries.Count(e => e.Type == entry.Type + && string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase) + && e.SplittedHosts.Length < Consts.MaxHostsCount) > 1; + } + + _dispatcherQueue.TryEnqueue(() => entry.Duplicate = duplicate); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _checkEvent?.Dispose(); + _disposed = true; + } + } + } + } +} diff --git a/src/modules/Hosts/HostsUILib/Helpers/IDuplicateService.cs b/src/modules/Hosts/HostsUILib/Helpers/IDuplicateService.cs new file mode 100644 index 0000000000..be9f842f99 --- /dev/null +++ b/src/modules/Hosts/HostsUILib/Helpers/IDuplicateService.cs @@ -0,0 +1,16 @@ +// 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.Collections.Generic; +using HostsUILib.Models; + +namespace HostsUILib.Helpers +{ + public interface IDuplicateService + { + void Initialize(IList entries); + + void CheckDuplicates(string address, string[] hosts); + } +} diff --git a/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs b/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs index d97add2e7a..955c1f8f8c 100644 --- a/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs +++ b/src/modules/Hosts/HostsUILib/Helpers/IHostsService.cs @@ -9,7 +9,7 @@ using HostsUILib.Models; namespace HostsUILib.Helpers { - public interface IHostsService : IDisposable + public interface IHostsService { string HostsFilePath { get; } diff --git a/src/modules/Hosts/HostsUILib/HostsMainPage.xaml b/src/modules/Hosts/HostsUILib/HostsMainPage.xaml index 448140f794..f00eef9117 100644 --- a/src/modules/Hosts/HostsUILib/HostsMainPage.xaml +++ b/src/modules/Hosts/HostsUILib/HostsMainPage.xaml @@ -412,23 +412,23 @@ - + - + - + - + - + TextWrapping="NoWrap" /> _dispatcherQueue.TryEnqueue(() => FileChanged = true); _userSettings.LoopbackDuplicatesChanged += (s, e) => ReadHosts(); @@ -111,8 +109,7 @@ namespace HostsUILib.ViewModels { entry.PropertyChanged += Entry_PropertyChanged; _entries.Add(entry); - - FindDuplicates(entry.Address, entry.SplittedHosts); + _duplicateService.CheckDuplicates(entry.Address, entry.SplittedHosts); } public void Update(int index, Entry entry) @@ -126,8 +123,8 @@ namespace HostsUILib.ViewModels existingEntry.Hosts = entry.Hosts; existingEntry.Active = entry.Active; - FindDuplicates(oldAddress, oldHosts); - FindDuplicates(entry.Address, entry.SplittedHosts); + _duplicateService.CheckDuplicates(oldAddress, oldHosts); + _duplicateService.CheckDuplicates(entry.Address, entry.SplittedHosts); } public void DeleteSelected() @@ -135,8 +132,7 @@ namespace HostsUILib.ViewModels var address = Selected.Address; var hosts = Selected.SplittedHosts; _entries.Remove(Selected); - - FindDuplicates(address, hosts); + _duplicateService.CheckDuplicates(address, hosts); } public void UpdateAdditionalLines(string lines) @@ -169,8 +165,7 @@ namespace HostsUILib.ViewModels var address = entry.Address; var hosts = entry.SplittedHosts; _entries.Remove(entry); - - FindDuplicates(address, hosts); + _duplicateService.CheckDuplicates(address, hosts); } } @@ -213,9 +208,7 @@ namespace HostsUILib.ViewModels }); _readingHosts = false; - _tokenSource?.Cancel(); - _tokenSource = new CancellationTokenSource(); - FindDuplicates(_tokenSource.Token); + _duplicateService.Initialize(_entries); }); } @@ -294,12 +287,6 @@ namespace HostsUILib.ViewModels _ = Task.Run(SaveAsync); } - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - private void Entry_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (Filtered && (e.PropertyName == nameof(Entry.Hosts) @@ -326,82 +313,6 @@ namespace HostsUILib.ViewModels _ = Task.Run(SaveAsync); } - private void FindDuplicates(CancellationToken cancellationToken) - { - foreach (var entry in _entries) - { - try - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address)) - { - continue; - } - - SetDuplicate(entry); - } - catch (OperationCanceledException) - { - LoggerInstance.Logger.LogInfo("FindDuplicates cancelled"); - return; - } - } - } - - private void FindDuplicates(string address, IEnumerable hosts) - { - var entries = _entries.Where(e => - string.Equals(e.Address, address, StringComparison.OrdinalIgnoreCase) - || hosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any()); - - foreach (var entry in entries) - { - SetDuplicate(entry); - } - } - - private void SetDuplicate(Entry entry) - { - if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address)) - { - _dispatcherQueue.TryEnqueue(() => - { - entry.Duplicate = false; - }); - - return; - } - - var duplicate = false; - - /* - * Duplicate are based on the following criteria: - * Entries with the same type and at least one host in common - * Entries with the same type and address, except when there is only one entry with less than 9 hosts for that type and address - */ - if (_entries.Any(e => e != entry - && e.Type == entry.Type - && entry.SplittedHosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any())) - { - duplicate = true; - } - else if (_entries.Any(e => e != entry - && e.Type == entry.Type - && string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase))) - { - duplicate = entry.SplittedHosts.Length < Consts.MaxHostsCount - && _entries.Count(e => e.Type == entry.Type - && string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase) - && e.SplittedHosts.Length < Consts.MaxHostsCount) > 1; - } - - _dispatcherQueue.TryEnqueue(() => - { - entry.Duplicate = duplicate; - }); - } - private async Task SaveAsync() { bool error = true; @@ -444,17 +355,5 @@ namespace HostsUILib.ViewModels IsReadOnly = isReadOnly; }); } - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _hostsService?.Dispose(); - _disposed = true; - } - } - } } } diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj index 84114dc2f7..1189b7c08c 100644 --- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj +++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj @@ -1,6 +1,6 @@  - + @@ -141,7 +141,7 @@ - + @@ -152,7 +152,7 @@ - - + + \ No newline at end of file diff --git a/src/modules/MeasureTool/MeasureToolCore/packages.config b/src/modules/MeasureTool/MeasureToolCore/packages.config index b9fac3c784..2b320c8c16 100644 --- a/src/modules/MeasureTool/MeasureToolCore/packages.config +++ b/src/modules/MeasureTool/MeasureToolCore/packages.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/modules/MeasureTool/MeasureToolModuleInterface/dllmain.cpp b/src/modules/MeasureTool/MeasureToolModuleInterface/dllmain.cpp index 62ff786429..cfac5ce640 100644 --- a/src/modules/MeasureTool/MeasureToolModuleInterface/dllmain.cpp +++ b/src/modules/MeasureTool/MeasureToolModuleInterface/dllmain.cpp @@ -82,9 +82,9 @@ private: { Logger::info("MeasureTool is going to use default shortcut"); m_hotkey.win = true; + m_hotkey.ctrl = true; m_hotkey.alt = false; m_hotkey.shift = true; - m_hotkey.ctrl = false; m_hotkey.key = 'M'; } } diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 51b93c37a6..f4ecb031b4 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -65,6 +65,7 @@ protected: bool m_destroyed = false; FindMyMouseActivationMethod m_activationMethod = FIND_MY_MOUSE_DEFAULT_ACTIVATION_METHOD; + bool m_includeWinKey = FIND_MY_MOUSE_DEFAULT_INCLUDE_WIN_KEY; bool m_doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE; int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS; int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM; @@ -146,6 +147,7 @@ private: void OnMouseTimer(); void DetectShake(); + bool KeyboardInputCanActivate(); void StartSonar(); void StopSonar(); @@ -352,7 +354,7 @@ void SuperSonar::OnSonarKeyboardInput(RAWINPUT const& input) break; case SonarState::ControlUp1: - if (pressed) + if (pressed && KeyboardInputCanActivate()) { auto now = GetTickCount64(); auto doubleClickInterval = now - m_lastKeyTime; @@ -438,6 +440,12 @@ void SuperSonar::DetectShake() } +template +bool SuperSonar::KeyboardInputCanActivate() +{ + return !m_includeWinKey || (GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000); +} + template void SuperSonar::OnSonarMouseInput(RAWINPUT const& input) { @@ -762,6 +770,7 @@ public: m_backgroundColor = settings.backgroundColor; m_spotlightColor = settings.spotlightColor; m_activationMethod = settings.activationMethod; + m_includeWinKey = settings.includeWinKey; m_doNotActivateOnGameMode = settings.doNotActivateOnGameMode; m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1; m_finalAlphaNumerator = settings.overlayOpacity; @@ -791,6 +800,7 @@ public: m_backgroundColor = localSettings.backgroundColor; m_spotlightColor = localSettings.spotlightColor; m_activationMethod = localSettings.activationMethod; + m_includeWinKey = localSettings.includeWinKey; m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode; m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1; m_finalAlphaNumerator = localSettings.overlayOpacity; diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h index d55b72a34d..fb52bf11e5 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h @@ -18,6 +18,7 @@ constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS = 100; constexpr int FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS = 500; constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM = 9; constexpr FindMyMouseActivationMethod FIND_MY_MOUSE_DEFAULT_ACTIVATION_METHOD = FindMyMouseActivationMethod::DoubleLeftControlKey; +constexpr bool FIND_MY_MOUSE_DEFAULT_INCLUDE_WIN_KEY = false; constexpr int FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE = 1000; constexpr int FIND_MY_MOUSE_DEFAULT_SHAKE_INTERVAL_MS = 1000; constexpr int FIND_MY_MOUSE_DEFAULT_SHAKE_FACTOR = 400; // 400 percent @@ -25,6 +26,7 @@ constexpr int FIND_MY_MOUSE_DEFAULT_SHAKE_FACTOR = 400; // 400 percent struct FindMyMouseSettings { FindMyMouseActivationMethod activationMethod = FIND_MY_MOUSE_DEFAULT_ACTIVATION_METHOD; + bool includeWinKey = FIND_MY_MOUSE_DEFAULT_INCLUDE_WIN_KEY; bool doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE; winrt::Windows::UI::Color backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR; winrt::Windows::UI::Color spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR; diff --git a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp index d0102ba507..0518f468c2 100644 --- a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp +++ b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp @@ -14,6 +14,7 @@ namespace const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; const wchar_t JSON_KEY_VALUE[] = L"value"; const wchar_t JSON_KEY_ACTIVATION_METHOD[] = L"activation_method"; + const wchar_t JSON_KEY_INCLUDE_WIN_KEY[] = L"include_win_key"; const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode"; const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color"; const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color"; @@ -237,6 +238,15 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings) Logger::warn("Failed to initialize Activation Method from settings. Will use default value"); } try + { + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_INCLUDE_WIN_KEY); + findMyMouseSettings.includeWinKey = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to get 'include windows key with ctrl' setting"); + } + try { auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE); findMyMouseSettings.doNotActivateOnGameMode = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE); diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/DrawingHelperTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/DrawingHelperTests.cs new file mode 100644 index 0000000000..65af07098b --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/DrawingHelperTests.cs @@ -0,0 +1,152 @@ +// 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.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MouseJumpUI.Common.Helpers; +using MouseJumpUI.Common.Imaging; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Styles; +using MouseJumpUI.Helpers; + +namespace MouseJumpUI.UnitTests.Common.Helpers; + +[TestClass] +public static class DrawingHelperTests +{ + [TestClass] + public sealed class GetPreviewLayoutTests + { + public sealed class TestCase + { + public TestCase(PreviewStyle previewStyle, List screens, PointInfo activatedLocation, string desktopImageFilename, string expectedImageFilename) + { + this.PreviewStyle = previewStyle; + this.Screens = screens; + this.ActivatedLocation = activatedLocation; + this.DesktopImageFilename = desktopImageFilename; + this.ExpectedImageFilename = expectedImageFilename; + } + + public PreviewStyle PreviewStyle { get; } + + public List Screens { get; } + + public PointInfo ActivatedLocation { get; } + + public string DesktopImageFilename { get; } + + public string ExpectedImageFilename { get; } + } + + public static IEnumerable GetTestCases() + { + /* 4-grid */ + yield return new object[] + { + new TestCase( + previewStyle: StyleHelper.DefaultPreviewStyle, + screens: new List() + { + new(0, 0, 500, 500), + new(500, 0, 500, 500), + new(500, 500, 500, 500), + new(0, 500, 500, 500), + }, + activatedLocation: new(x: 50, y: 50), + desktopImageFilename: "Common/Helpers/_test-4grid-desktop.png", + expectedImageFilename: "Common/Helpers/_test-4grid-expected.png"), + }; + /* win 11 */ + yield return new object[] + { + new TestCase( + previewStyle: StyleHelper.DefaultPreviewStyle, + screens: new List() + { + new(5120, 349, 1920, 1080), + new(0, 0, 5120, 1440), + }, + activatedLocation: new(x: 50, y: 50), + desktopImageFilename: "Common/Helpers/_test-win11-desktop.png", + expectedImageFilename: "Common/Helpers/_test-win11-expected.png"), + }; + } + + [TestMethod] + [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] + public void RunTestCases(TestCase data) + { + // load the fake desktop image + using var desktopImage = GetPreviewLayoutTests.LoadImageResource(data.DesktopImageFilename); + + // draw the preview image + var previewLayout = LayoutHelper.GetPreviewLayout( + previewStyle: data.PreviewStyle, + screens: data.Screens, + activatedLocation: data.ActivatedLocation); + var imageCopyService = new StaticImageRegionCopyService(desktopImage); + using var actual = DrawingHelper.RenderPreview(previewLayout, imageCopyService); + + // load the expected image + var expected = GetPreviewLayoutTests.LoadImageResource(data.ExpectedImageFilename); + + // compare the images + var screens = System.Windows.Forms.Screen.AllScreens; + AssertImagesEqual(expected, actual); + } + + private static Bitmap LoadImageResource(string filename) + { + var assembly = Assembly.GetExecutingAssembly(); + var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException()); + var resourceName = $"Microsoft.{assemblyName.Name}.{filename.Replace("/", ".")}"; + var resourceNames = assembly.GetManifestResourceNames(); + if (!resourceNames.Contains(resourceName)) + { + throw new InvalidOperationException($"Embedded resource '{resourceName}' does not exist."); + } + + var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException(); + var image = (Bitmap)Image.FromStream(stream); + return image; + } + + /// + /// Naive / brute force image comparison - we can optimise this later :-) + /// + private static void AssertImagesEqual(Bitmap expected, Bitmap actual) + { + Assert.AreEqual( + expected.Width, + actual.Width, + $"expected width: {expected.Width}, actual width: {actual.Width}"); + Assert.AreEqual( + expected.Height, + actual.Height, + $"expected height: {expected.Height}, actual height: {actual.Height}"); + for (var y = 0; y < expected.Height; y++) + { + for (var x = 0; x < expected.Width; x++) + { + var expectedPixel = expected.GetPixel(x, y); + var actualPixel = actual.GetPixel(x, y); + + // allow a small tolerance for rounding differences in gdi + Assert.IsTrue( + (Math.Abs(expectedPixel.A - actualPixel.A) <= 1) && + (Math.Abs(expectedPixel.R - actualPixel.R) <= 1) && + (Math.Abs(expectedPixel.G - actualPixel.G) <= 1) && + (Math.Abs(expectedPixel.B - actualPixel.B) <= 1), + $"images differ at pixel ({x}, {y}) - expected: {expectedPixel}, actual: {actualPixel}"); + } + } + } + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/LayoutHelperTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/LayoutHelperTests.cs new file mode 100644 index 0000000000..f4645c248f --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/LayoutHelperTests.cs @@ -0,0 +1,452 @@ +// 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.Collections.Generic; +using System.Drawing; +using System.Text.Json; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MouseJumpUI.Common.Helpers; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Layout; +using MouseJumpUI.Common.Models.Styles; + +namespace MouseJumpUI.UnitTests.Common.Helpers; + +[TestClass] +public static class LayoutHelperTests +{ + /* + [TestClass] + public sealed class OldLayoutTests + { + + public static IEnumerable GetTestCases() + { + // check we handle rounding errors in scaling the preview form + // that might make the form *larger* than the current screen - + // e.g. a desktop 7168 x 1440 scaled to a screen 1024 x 768 + // with a 5px form padding border: + // + // ((decimal)1014 / 7168) * 7168 = 1014.0000000000000000000000002 + // + // +----------------+ + // | | + // | 1 +-------+ + // | | 0 | + // +----------------+-------+ + layoutConfig = new LayoutConfig( + virtualScreenBounds: new(0, 0, 7168, 1440), + screens: new List + { + new(HMONITOR.Null, false, new(6144, 0, 1024, 768), new(6144, 0, 1024, 768)), + new(HMONITOR.Null, false, new(0, 0, 6144, 1440), new(0, 0, 6144, 1440)), + }, + activatedLocation: new(6656, 384), + activatedScreenIndex: 0, + activatedScreenNumber: 1, + maximumFormSize: new(1600, 1200), + formPadding: new(5, 5, 5, 5), + previewPadding: new(0, 0, 0, 0)); + layoutInfo = new LayoutInfo( + layoutConfig: layoutConfig, + formBounds: new(6144, 277.14732M, 1024, 213.70535M), + previewBounds: new(0, 0, 1014, 203.70535M), + screenBounds: new List + { + new(869.14285M, 0, 144.85714M, 108.642857M), + new(0, 0, 869.142857M, 203.705357M), + }, + activatedScreenBounds: new(6144, 0, 1024, 768)); + yield return new object[] { new TestCase(layoutConfig, layoutInfo) }; + + // check we handle rounding errors in scaling the preview form + // that might make the form a pixel *smaller* than the current screen - + // e.g. a desktop 7168 x 1440 scaled to a screen 1024 x 768 + // with a 5px form padding border: + // + // ((decimal)1280 / 7424) * 7424 = 1279.9999999999999999999999999 + // + // +----------------+ + // | | + // | 1 +-------+ + // | | 0 | + // +----------------+-------+ + layoutConfig = new LayoutConfig( + virtualScreenBounds: new(0, 0, 7424, 1440), + screens: new List + { + new(HMONITOR.Null, false, new(6144, 0, 1280, 768), new(6144, 0, 1280, 768)), + new(HMONITOR.Null, false, new(0, 0, 6144, 1440), new(0, 0, 6144, 1440)), + }, + activatedLocation: new(6784, 384), + activatedScreenIndex: 0, + activatedScreenNumber: 1, + maximumFormSize: new(1600, 1200), + formPadding: new(5, 5, 5, 5), + previewPadding: new(0, 0, 0, 0)); + layoutInfo = new LayoutInfo( + layoutConfig: layoutConfig, + formBounds: new( + 6144, + 255.83189M, // (768 - (((decimal)(1280-10) / 7424 * 1440) + 10)) / 2 + 1280, + 256.33620M // ((decimal)(1280 - 10) / 7424 * 1440) + 10 + ), + previewBounds: new(0, 0, 1270, 246.33620M), + screenBounds: new List + { + new(1051.03448M, 0, 218.96551M, 131.37931M), + new(0, 0M, 1051.03448M, 246.33620M), + }, + activatedScreenBounds: new(6144, 0, 1280, 768)); + yield return new object[] { new TestCase(layoutConfig, layoutInfo) }; + } + } + */ + + [TestClass] + public sealed class GetPreviewLayoutTests + { + public sealed class TestCase + { + public TestCase(PreviewStyle previewStyle, List screens, PointInfo activatedLocation, PreviewLayout expectedResult) + { + this.PreviewStyle = previewStyle; + this.Screens = screens; + this.ActivatedLocation = activatedLocation; + this.ExpectedResult = expectedResult; + } + + public PreviewStyle PreviewStyle { get; } + + public List Screens { get; } + + public PointInfo ActivatedLocation { get; } + + public PreviewLayout ExpectedResult { get; } + } + + public static IEnumerable GetTestCases() + { + // happy path - single screen with 50% scaling, + // *has* a preview borders but *no* screenshot borders + // + // +----------------+ + // | | + // | 0 | + // | | + // +----------------+ + var previewStyle = new PreviewStyle( + canvasSize: new( + width: 524, + height: 396 + ), + canvasStyle: new( + marginStyle: MarginStyle.Empty, + borderStyle: new( + color: SystemColors.Highlight, + all: 5, + depth: 3), + paddingStyle: new( + all: 1), + backgroundStyle: new( + color1: Color.FromArgb(13, 87, 210), // light blue + color2: Color.FromArgb(3, 68, 192) // darker blue + ) + ), + screenStyle: BoxStyle.Empty); + var screens = new List + { + new(0, 0, 1024, 768), + }; + var activatedLocation = new PointInfo(512, 384); + var previewLayout = new PreviewLayout( + virtualScreen: new(0, 0, 1024, 768), + screens: screens, + activatedScreenIndex: 0, + formBounds: new(250, 186, 524, 396), + previewStyle: previewStyle, + previewBounds: new( + outerBounds: new(0, 0, 524, 396), + marginBounds: new(0, 0, 524, 396), + borderBounds: new(0, 0, 524, 396), + paddingBounds: new(5, 5, 514, 386), + contentBounds: new(6, 6, 512, 384) + ), + screenshotBounds: new() + { + new( + outerBounds: new(6, 6, 512, 384), + marginBounds: new(6, 6, 512, 384), + borderBounds: new(6, 6, 512, 384), + paddingBounds: new(6, 6, 512, 384), + contentBounds: new(6, 6, 512, 384) + ), + }); + yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) }; + + // happy path - single screen with 50% scaling, + // *no* preview borders but *has* screenshot borders + // + // +----------------+ + // | | + // | 0 | + // | | + // +----------------+ + previewStyle = new PreviewStyle( + canvasSize: new( + width: 512, + height: 384 + ), + canvasStyle: BoxStyle.Empty, + screenStyle: new( + marginStyle: new( + all: 1), + borderStyle: new( + color: SystemColors.Highlight, + all: 5, + depth: 3), + paddingStyle: PaddingStyle.Empty, + backgroundStyle: new( + color1: Color.FromArgb(13, 87, 210), // light blue + color2: Color.FromArgb(3, 68, 192) // darker blue + ) + )); + screens = new List + { + new(0, 0, 1024, 768), + }; + activatedLocation = new PointInfo(512, 384); + previewLayout = new PreviewLayout( + virtualScreen: new(0, 0, 1024, 768), + screens: screens, + activatedScreenIndex: 0, + formBounds: new(256, 192, 512, 384), + previewStyle: previewStyle, + previewBounds: new( + outerBounds: new(0, 0, 512, 384), + marginBounds: new(0, 0, 512, 384), + borderBounds: new(0, 0, 512, 384), + paddingBounds: new(0, 0, 512, 384), + contentBounds: new(0, 0, 512, 384) + ), + screenshotBounds: new() + { + new( + outerBounds: new(0, 0, 512, 384), + marginBounds: new(0, 0, 512, 384), + borderBounds: new(1, 1, 510, 382), + paddingBounds: new(6, 6, 500, 372), + contentBounds: new(6, 6, 500, 372) + ), + }); + yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) }; + + // primary monitor not topmost / leftmost - if there are screens + // that are further left or higher up than the primary monitor + // they'll have negative coordinates which has caused some + // issues with calculations in the past. this test will make + // sure we handle screens with negative coordinates gracefully + // + // +-------+ + // | 0 +----------------+ + // +-------+ | + // | 1 | + // | | + // +----------------+ + previewStyle = new PreviewStyle( + canvasSize: new( + width: 716, + height: 204 + ), + canvasStyle: new( + marginStyle: MarginStyle.Empty, + borderStyle: new( + color: SystemColors.Highlight, + all: 5, + depth: 3), + paddingStyle: new( + all: 1), + backgroundStyle: new( + color1: Color.FromArgb(13, 87, 210), // light blue + color2: Color.FromArgb(3, 68, 192) // darker blue + ) + ), + screenStyle: new( + marginStyle: new( + all: 1), + borderStyle: new( + color: SystemColors.Highlight, + all: 5, + depth: 3), + paddingStyle: PaddingStyle.Empty, + backgroundStyle: new( + color1: Color.FromArgb(13, 87, 210), // light blue + color2: Color.FromArgb(3, 68, 192) // darker blue + ) + )); + screens = new List + { + new(-1920, -480, 1920, 1080), + new(0, 0, 5120, 1440), + }; + activatedLocation = new(-960, 60); + previewLayout = new PreviewLayout( + virtualScreen: new(-1920, -480, 7040, 1920), + screens: screens, + activatedScreenIndex: 0, + formBounds: new(-1318, -42, 716, 204), + previewStyle: previewStyle, + previewBounds: new( + outerBounds: new(0, 0, 716, 204), + marginBounds: new(0, 0, 716, 204), + borderBounds: new(0, 0, 716, 204), + paddingBounds: new(5, 5, 706, 194), + contentBounds: new(6, 6, 704, 192) + ), + screenshotBounds: new() + { + new( + outerBounds: new(6, 6, 192, 108), + marginBounds: new(6, 6, 192, 108), + borderBounds: new(7, 7, 190, 106), + paddingBounds: new(12, 12, 180, 96), + contentBounds: new(12, 12, 180, 96) + ), + new( + outerBounds: new(198, 54, 512, 144), + marginBounds: new(198, 54, 512, 144), + borderBounds: new(199, 55, 510, 142), + paddingBounds: new(204, 60, 500, 132), + contentBounds: new(204, 60, 500, 132) + ), + }); + yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) }; + } + + [TestMethod] + [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] + public void RunTestCases(TestCase data) + { + // note - even if values are within 0.0001M of each other they could + // still round to different values - e.g. + // (int)1279.999999999999 -> 1279 + // vs + // (int)1280.000000000000 -> 1280 + // so we'll compare the raw values, *and* convert to an int-based + // Rectangle to compare rounded values + var actual = LayoutHelper.GetPreviewLayout(data.PreviewStyle, data.Screens, data.ActivatedLocation); + var expected = data.ExpectedResult; + var options = new JsonSerializerOptions + { + WriteIndented = true, + }; + Assert.AreEqual( + JsonSerializer.Serialize(expected, options), + JsonSerializer.Serialize(actual, options)); + } + } + + [TestClass] + public sealed class GetBoxBoundsFromContentBoundsTests + { + public sealed class TestCase + { + public TestCase(RectangleInfo contentBounds, BoxStyle boxStyle, BoxBounds expectedResult) + { + this.ContentBounds = contentBounds; + this.BoxStyle = boxStyle; + this.ExpectedResult = expectedResult; + } + + public RectangleInfo ContentBounds { get; set; } + + public BoxStyle BoxStyle { get; set; } + + public BoxBounds ExpectedResult { get; set; } + } + + public static IEnumerable GetTestCases() + { + yield return new[] + { + new TestCase( + contentBounds: new(100, 100, 800, 600), + boxStyle: new( + marginStyle: new(3), + borderStyle: new(Color.Red, 5, 0), + paddingStyle: new(7), + backgroundStyle: BackgroundStyle.Empty), + expectedResult: new( + outerBounds: new(85, 85, 830, 630), + marginBounds: new(85, 85, 830, 630), + borderBounds: new(88, 88, 824, 624), + paddingBounds: new(93, 93, 814, 614), + contentBounds: new(100, 100, 800, 600))), + }; + } + + [TestMethod] + [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] + public void RunTestCases(TestCase data) + { + var actual = LayoutHelper.GetBoxBoundsFromContentBounds(data.ContentBounds, data.BoxStyle); + var expected = data.ExpectedResult; + Assert.AreEqual( + JsonSerializer.Serialize(expected), + JsonSerializer.Serialize(actual)); + } + } + + [TestClass] + public sealed class GetBoxBoundsFromOuterBoundsTests + { + public sealed class TestCase + { + public TestCase(RectangleInfo outerBounds, BoxStyle boxStyle, BoxBounds expectedResult) + { + this.OuterBounds = outerBounds; + this.BoxStyle = boxStyle; + this.ExpectedResult = expectedResult; + } + + public RectangleInfo OuterBounds { get; set; } + + public BoxStyle BoxStyle { get; set; } + + public BoxBounds ExpectedResult { get; set; } + } + + public static IEnumerable GetTestCases() + { + yield return new[] + { + new TestCase( + outerBounds: new(85, 85, 830, 630), + boxStyle: new( + marginStyle: new(3), + borderStyle: new(Color.Red, 5, 0), + paddingStyle: new(7), + backgroundStyle: BackgroundStyle.Empty), + expectedResult: new( + outerBounds: new(85, 85, 830, 630), + marginBounds: new(85, 85, 830, 630), + borderBounds: new(88, 88, 824, 624), + paddingBounds: new(93, 93, 814, 614), + contentBounds: new(100, 100, 800, 600))), + }; + } + + [TestMethod] + [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] + public void RunTestCases(TestCase data) + { + var actual = LayoutHelper.GetBoxBoundsFromOuterBounds(data.OuterBounds, data.BoxStyle); + var expected = data.ExpectedResult; + Assert.AreEqual( + JsonSerializer.Serialize(expected), + JsonSerializer.Serialize(actual)); + } + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/MouseHelperTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/MouseHelperTests.cs new file mode 100644 index 0000000000..a61a915137 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/MouseHelperTests.cs @@ -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.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MouseJumpUI.Common.Helpers; +using MouseJumpUI.Common.Models.Drawing; + +namespace MouseJumpUI.UnitTests.Common.Helpers; + +[TestClass] +public static class MouseHelperTests +{ + [TestClass] + public sealed class GetJumpLocationTests + { + public sealed class TestCase + { + public TestCase(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds, PointInfo expectedResult) + { + this.PreviewLocation = previewLocation; + this.PreviewSize = previewSize; + this.DesktopBounds = desktopBounds; + this.ExpectedResult = expectedResult; + } + + public PointInfo PreviewLocation { get; } + + public SizeInfo PreviewSize { get; } + + public RectangleInfo DesktopBounds { get; } + + public PointInfo ExpectedResult { get; } + } + + public static IEnumerable GetTestCases() + { + // screen corners and midpoint with a zero origin + yield return new object[] { new TestCase(new(0, 0), new(160, 120), new(0, 0, 1600, 1200), new(0, 0)) }; + yield return new object[] { new TestCase(new(160, 0), new(160, 120), new(0, 0, 1600, 1200), new(1600, 0)) }; + yield return new object[] { new TestCase(new(0, 120), new(160, 120), new(0, 0, 1600, 1200), new(0, 1200)) }; + yield return new object[] { new TestCase(new(160, 120), new(160, 120), new(0, 0, 1600, 1200), new(1600, 1200)) }; + yield return new object[] { new TestCase(new(80, 60), new(160, 120), new(0, 0, 1600, 1200), new(800, 600)) }; + + // screen corners and midpoint with a positive origin + yield return new object[] { new TestCase(new(0, 0), new(160, 120), new(1000, 1000, 1600, 1200), new(1000, 1000)) }; + yield return new object[] { new TestCase(new(160, 0), new(160, 120), new(1000, 1000, 1600, 1200), new(2600, 1000)) }; + yield return new object[] { new TestCase(new(0, 120), new(160, 120), new(1000, 1000, 1600, 1200), new(1000, 2200)) }; + yield return new object[] { new TestCase(new(160, 120), new(160, 120), new(1000, 1000, 1600, 1200), new(2600, 2200)) }; + yield return new object[] { new TestCase(new(80, 60), new(160, 120), new(1000, 1000, 1600, 1200), new(1800, 1600)) }; + + // screen corners and midpoint with a negative origin + yield return new object[] { new TestCase(new(0, 0), new(160, 120), new(-1000, -1000, 1600, 1200), new(-1000, -1000)) }; + yield return new object[] { new TestCase(new(160, 0), new(160, 120), new(-1000, -1000, 1600, 1200), new(600, -1000)) }; + yield return new object[] { new TestCase(new(0, 120), new(160, 120), new(-1000, -1000, 1600, 1200), new(-1000, 200)) }; + yield return new object[] { new TestCase(new(160, 120), new(160, 120), new(-1000, -1000, 1600, 1200), new(600, 200)) }; + yield return new object[] { new TestCase(new(80, 60), new(160, 120), new(-1000, -1000, 1600, 1200), new(-200, -400)) }; + } + + [TestMethod] + [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] + public void RunTestCases(TestCase data) + { + var actual = MouseHelper.GetJumpLocation( + data.PreviewLocation, + data.PreviewSize, + data.DesktopBounds); + var expected = data.ExpectedResult; + Assert.AreEqual(expected.X, actual.X); + Assert.AreEqual(expected.Y, actual.Y); + } + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-4grid-desktop.png b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-4grid-desktop.png new file mode 100644 index 0000000000..7ad69a7d8d Binary files /dev/null and b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-4grid-desktop.png differ diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-4grid-expected.png b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-4grid-expected.png new file mode 100644 index 0000000000..de74caa55f Binary files /dev/null and b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-4grid-expected.png differ diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-win11-desktop.png b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-win11-desktop.png new file mode 100644 index 0000000000..b874606bd3 Binary files /dev/null and b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-win11-desktop.png differ diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-win11-expected.png b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-win11-expected.png new file mode 100644 index 0000000000..9d4b1612d0 Binary files /dev/null and b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Helpers/_test-win11-expected.png differ diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Models/Drawing/RectangleInfoTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Models/Drawing/RectangleInfoTests.cs similarity index 68% rename from src/modules/MouseUtils/MouseJumpUI.UnitTests/Models/Drawing/RectangleInfoTests.cs rename to src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Models/Drawing/RectangleInfoTests.cs index 24d3bac4e1..48503006fa 100644 --- a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Models/Drawing/RectangleInfoTests.cs +++ b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Models/Drawing/RectangleInfoTests.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MouseJumpUI.Models.Drawing; +using MouseJumpUI.Common.Models.Drawing; -namespace MouseJumpUI.UnitTests.Models.Drawing; +namespace MouseJumpUI.UnitTests.Common.Models.Drawing; [TestClass] public static class RectangleInfoTests @@ -23,30 +23,30 @@ public static class RectangleInfoTests this.ExpectedResult = expectedResult; } - public RectangleInfo Rectangle { get; set; } + public RectangleInfo Rectangle { get; } - public PointInfo Point { get; set; } + public PointInfo Point { get; } - public RectangleInfo ExpectedResult { get; set; } + public RectangleInfo ExpectedResult { get; } } public static IEnumerable GetTestCases() { // zero-sized - yield return new[] { new TestCase(new(0, 0, 0, 0), new(0, 0), new(0, 0, 0, 0)), }; + yield return new object[] { new TestCase(new(0, 0, 0, 0), new(0, 0), new(0, 0, 0, 0)), }; // zero-origin - yield return new[] { new TestCase(new(0, 0, 200, 200), new(100, 100), new(0, 0, 200, 200)), }; - yield return new[] { new TestCase(new(0, 0, 200, 200), new(500, 500), new(400, 400, 200, 200)), }; - yield return new[] { new TestCase(new(0, 0, 800, 600), new(1000, 1000), new(600, 700, 800, 600)), }; + yield return new object[] { new TestCase(new(0, 0, 200, 200), new(100, 100), new(0, 0, 200, 200)), }; + yield return new object[] { new TestCase(new(0, 0, 200, 200), new(500, 500), new(400, 400, 200, 200)), }; + yield return new object[] { new TestCase(new(0, 0, 800, 600), new(1000, 1000), new(600, 700, 800, 600)), }; // non-zero origin - yield return new[] { new TestCase(new(1000, 2000, 200, 200), new(100, 100), new(0, 0, 200, 200)), }; - yield return new[] { new TestCase(new(1000, 2000, 200, 200), new(500, 500), new(400, 400, 200, 200)), }; - yield return new[] { new TestCase(new(1000, 2000, 800, 600), new(1000, 1000), new(600, 700, 800, 600)), }; + yield return new object[] { new TestCase(new(1000, 2000, 200, 200), new(100, 100), new(0, 0, 200, 200)), }; + yield return new object[] { new TestCase(new(1000, 2000, 200, 200), new(500, 500), new(400, 400, 200, 200)), }; + yield return new object[] { new TestCase(new(1000, 2000, 800, 600), new(1000, 1000), new(600, 700, 800, 600)), }; // negative result - yield return new[] { new TestCase(new(0, 0, 1000, 1200), new(300, 300), new(-200, -300, 1000, 1200)), }; + yield return new object[] { new TestCase(new(0, 0, 1000, 1200), new(300, 300), new(-200, -300, 1000, 1200)), }; } [TestMethod] @@ -74,53 +74,53 @@ public static class RectangleInfoTests this.ExpectedResult = expectedResult; } - public RectangleInfo Inner { get; set; } + public RectangleInfo Inner { get; } - public RectangleInfo Outer { get; set; } + public RectangleInfo Outer { get; } - public RectangleInfo ExpectedResult { get; set; } + public RectangleInfo ExpectedResult { get; } } public static IEnumerable GetTestCases() { // already inside - obj fills bounds exactly - yield return new[] + yield return new object[] { new TestCase(new(0, 0, 100, 100), new(0, 0, 100, 100), new(0, 0, 100, 100)), }; // already inside - obj exactly in each corner - yield return new[] + yield return new object[] { new TestCase(new(0, 0, 100, 100), new(0, 0, 200, 200), new(0, 0, 100, 100)), }; - yield return new[] + yield return new object[] { new TestCase(new(100, 0, 100, 100), new(0, 0, 200, 200), new(100, 0, 100, 100)), }; - yield return new[] + yield return new object[] { new TestCase(new(0, 100, 100, 100), new(0, 0, 200, 200), new(0, 100, 100, 100)), }; - yield return new[] + yield return new object[] { new TestCase(new(100, 100, 100, 100), new(0, 0, 200, 200), new(100, 100, 100, 100)), }; // move inside - obj outside each corner - yield return new[] + yield return new object[] { new TestCase(new(-50, -50, 100, 100), new(0, 0, 200, 200), new(0, 0, 100, 100)), }; - yield return new[] + yield return new object[] { new TestCase(new(250, -50, 100, 100), new(0, 0, 200, 200), new(100, 0, 100, 100)), }; - yield return new[] + yield return new object[] { new TestCase(new(-50, 250, 100, 100), new(0, 0, 200, 200), new(0, 100, 100, 100)), }; - yield return new[] + yield return new object[] { new TestCase(new(150, 150, 100, 100), new(0, 0, 200, 200), new(100, 100, 100, 100)), }; diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Models/Drawing/SizeInfoTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Models/Drawing/SizeInfoTests.cs similarity index 58% rename from src/modules/MouseUtils/MouseJumpUI.UnitTests/Models/Drawing/SizeInfoTests.cs rename to src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Models/Drawing/SizeInfoTests.cs index 5e0bd3d4fe..8c0113a9a2 100644 --- a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Models/Drawing/SizeInfoTests.cs +++ b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Common/Models/Drawing/SizeInfoTests.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MouseJumpUI.Models.Drawing; +using MouseJumpUI.Common.Models.Drawing; -namespace MouseJumpUI.UnitTests.Drawing; +namespace MouseJumpUI.UnitTests.Common.Models.Drawing; [TestClass] public static class SizeInfoTests @@ -23,28 +23,28 @@ public static class SizeInfoTests this.ExpectedResult = expectedResult; } - public SizeInfo Obj { get; set; } + public SizeInfo Obj { get; } - public SizeInfo Bounds { get; set; } + public SizeInfo Bounds { get; } - public SizeInfo ExpectedResult { get; set; } + public SizeInfo ExpectedResult { get; } } public static IEnumerable GetTestCases() { // identity tests - yield return new[] { new TestCase(new(512, 384), new(512, 384), new(512, 384)), }; - yield return new[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768)), }; + yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384)), }; + yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768)), }; // general tests - yield return new[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536)), }; - yield return new[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768)), }; + yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536)), }; + yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768)), }; // scale to fit width - yield return new[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536)), }; + yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536)), }; // scale to fit height - yield return new[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536)), }; + yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536)), }; } [TestMethod] @@ -70,28 +70,28 @@ public static class SizeInfoTests this.ExpectedResult = expectedResult; } - public SizeInfo Obj { get; set; } + public SizeInfo Obj { get; } - public SizeInfo Bounds { get; set; } + public SizeInfo Bounds { get; } - public decimal ExpectedResult { get; set; } + public decimal ExpectedResult { get; } } public static IEnumerable GetTestCases() { // identity tests - yield return new[] { new TestCase(new(512, 384), new(512, 384), 1), }; - yield return new[] { new TestCase(new(1024, 768), new(1024, 768), 1), }; + yield return new object[] { new TestCase(new(512, 384), new(512, 384), 1), }; + yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), 1), }; // general tests - yield return new[] { new TestCase(new(512, 384), new(2048, 1536), 4), }; - yield return new[] { new TestCase(new(2048, 1536), new(1024, 768), 0.5M), }; + yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), 4), }; + yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), 0.5M), }; // scale to fit width - yield return new[] { new TestCase(new(512, 384), new(2048, 3072), 4), }; + yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), 4), }; // scale to fit height - yield return new[] { new TestCase(new(512, 384), new(4096, 1536), 4), }; + yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), 4), }; } [TestMethod] diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Helpers/DrawingHelperTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Helpers/DrawingHelperTests.cs deleted file mode 100644 index 7579780ebc..0000000000 --- a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Helpers/DrawingHelperTests.cs +++ /dev/null @@ -1,229 +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.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MouseJumpUI.Helpers; -using MouseJumpUI.Models.Drawing; -using MouseJumpUI.Models.Layout; -using MouseJumpUI.Models.Screen; -using static MouseJumpUI.NativeMethods.Core; - -namespace MouseJumpUI.UnitTests.Helpers; - -[TestClass] -public static class DrawingHelperTests -{ - [TestClass] - public sealed class CalculateLayoutInfoTests - { - public sealed class TestCase - { - public TestCase(LayoutConfig layoutConfig, LayoutInfo expectedResult) - { - this.LayoutConfig = layoutConfig; - this.ExpectedResult = expectedResult; - } - - public LayoutConfig LayoutConfig { get; set; } - - public LayoutInfo ExpectedResult { get; set; } - } - - public static IEnumerable GetTestCases() - { - // happy path - check the preview form is shown - // at the correct size and position on a single screen - // - // +----------------+ - // | | - // | 0 | - // | | - // +----------------+ - var layoutConfig = new LayoutConfig( - virtualScreenBounds: new(0, 0, 5120, 1440), - screens: new List - { - new ScreenInfo(HMONITOR.Null, false, new(0, 0, 5120, 1440), new(0, 0, 5120, 1440)), - }, - activatedLocation: new(5120 / 2, 1440 / 2), - activatedScreenIndex: 0, - activatedScreenNumber: 1, - maximumFormSize: new(1600, 1200), - formPadding: new(5, 5, 5, 5), - previewPadding: new(0, 0, 0, 0)); - var layoutInfo = new LayoutInfo( - layoutConfig: layoutConfig, - formBounds: new(1760, 491.40625M, 1600, 457.1875M), - previewBounds: new(0, 0, 1590, 447.1875M), - screenBounds: new List - { - new(0, 0, 1590, 447.1875M), - }, - activatedScreenBounds: new(0, 0, 5120, 1440)); - yield return new[] { new TestCase(layoutConfig, layoutInfo) }; - - // primary monitor not topmost / leftmost - if there are screens - // that are further left or higher than the primary monitor - // they'll have negative coordinates which has caused some - // issues with calculations in the past. this test will make - // sure we handle negative coordinates gracefully - // - // +-------+ - // | 0 +----------------+ - // +-------+ | - // | 1 | - // | | - // +----------------+ - layoutConfig = new LayoutConfig( - virtualScreenBounds: new(-1920, -472, 7040, 1912), - screens: new List - { - new ScreenInfo(HMONITOR.Null, false, new(-1920, -472, 1920, 1080), new(-1920, -472, 1920, 1080)), - new ScreenInfo(HMONITOR.Null, false, new(0, 0, 5120, 1440), new(0, 0, 5120, 1440)), - }, - activatedLocation: new(-960, -236), - activatedScreenIndex: 0, - activatedScreenNumber: 1, - maximumFormSize: new(1600, 1200), - formPadding: new(5, 5, 5, 5), - previewPadding: new(0, 0, 0, 0)); - layoutInfo = new LayoutInfo( - layoutConfig: layoutConfig, - formBounds: new( - -1760, - -456.91477M, // -236 - (((decimal)(1600-10) / 7040 * 1912) + 10) / 2 - 1600, - 441.829545M // ((decimal)(1600-10) / 7040 * 1912) + 10 - ), - previewBounds: new(0, 0, 1590, 431.829545M), - screenBounds: new List - { - new(0, 0, 433.63636M, 243.92045M), - new(433.63636M, 106.602270M, 1156.36363M, 325.22727M), - }, - activatedScreenBounds: new(-1920, -472, 1920, 1080)); - yield return new[] { new TestCase(layoutConfig, layoutInfo) }; - - // check we handle rounding errors in scaling the preview form - // that might make the form *larger* than the current screen - - // e.g. a desktop 7168 x 1440 scaled to a screen 1024 x 768 - // with a 5px form padding border: - // - // ((decimal)1014 / 7168) * 7168 = 1014.0000000000000000000000002 - // - // +----------------+ - // | | - // | 1 +-------+ - // | | 0 | - // +----------------+-------+ - layoutConfig = new LayoutConfig( - virtualScreenBounds: new(0, 0, 7168, 1440), - screens: new List - { - new ScreenInfo(HMONITOR.Null, false, new(6144, 0, 1024, 768), new(6144, 0, 1024, 768)), - new ScreenInfo(HMONITOR.Null, false, new(0, 0, 6144, 1440), new(0, 0, 6144, 1440)), - }, - activatedLocation: new(6656, 384), - activatedScreenIndex: 0, - activatedScreenNumber: 1, - maximumFormSize: new(1600, 1200), - formPadding: new(5, 5, 5, 5), - previewPadding: new(0, 0, 0, 0)); - layoutInfo = new LayoutInfo( - layoutConfig: layoutConfig, - formBounds: new(6144, 277.14732M, 1024, 213.70535M), - previewBounds: new(0, 0, 1014, 203.70535M), - screenBounds: new List - { - new(869.14285M, 0, 144.85714M, 108.642857M), - new(0, 0, 869.142857M, 203.705357M), - }, - activatedScreenBounds: new(6144, 0, 1024, 768)); - yield return new[] { new TestCase(layoutConfig, layoutInfo) }; - - // check we handle rounding errors in scaling the preview form - // that might make the form a pixel *smaller* than the current screen - - // e.g. a desktop 7168 x 1440 scaled to a screen 1024 x 768 - // with a 5px form padding border: - // - // ((decimal)1280 / 7424) * 7424 = 1279.9999999999999999999999999 - // - // +----------------+ - // | | - // | 1 +-------+ - // | | 0 | - // +----------------+-------+ - layoutConfig = new LayoutConfig( - virtualScreenBounds: new(0, 0, 7424, 1440), - screens: new List - { - new ScreenInfo(HMONITOR.Null, false, new(6144, 0, 1280, 768), new(6144, 0, 1280, 768)), - new ScreenInfo(HMONITOR.Null, false, new(0, 0, 6144, 1440), new(0, 0, 6144, 1440)), - }, - activatedLocation: new(6784, 384), - activatedScreenIndex: 0, - activatedScreenNumber: 1, - maximumFormSize: new(1600, 1200), - formPadding: new(5, 5, 5, 5), - previewPadding: new(0, 0, 0, 0)); - layoutInfo = new LayoutInfo( - layoutConfig: layoutConfig, - formBounds: new( - 6144, - 255.83189M, // (768 - (((decimal)(1280-10) / 7424 * 1440) + 10)) / 2 - 1280, - 256.33620M // ((decimal)(1280 - 10) / 7424 * 1440) + 10 - ), - previewBounds: new(0, 0, 1270, 246.33620M), - screenBounds: new List - { - new(1051.03448M, 0, 218.96551M, 131.37931M), - new(0, 0M, 1051.03448M, 246.33620M), - }, - activatedScreenBounds: new(6144, 0, 1280, 768)); - yield return new[] { new TestCase(layoutConfig, layoutInfo) }; - } - - [TestMethod] - [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] - public void RunTestCases(TestCase data) - { - // note - even if values are within 0.0001M of each other they could - // still round to different values - e.g. - // (int)1279.999999999999 -> 1279 - // vs - // (int)1280.000000000000 -> 1280 - // so we'll compare the raw values, *and* convert to an int-based - // Rectangle to compare rounded values - var actual = LayoutHelper.CalculateLayoutInfo(data.LayoutConfig); - var expected = data.ExpectedResult; - Assert.AreEqual(expected.FormBounds.X, actual.FormBounds.X, 0.00001M, "FormBounds.X"); - Assert.AreEqual(expected.FormBounds.Y, actual.FormBounds.Y, 0.00001M, "FormBounds.Y"); - Assert.AreEqual(expected.FormBounds.Width, actual.FormBounds.Width, 0.00001M, "FormBounds.Width"); - Assert.AreEqual(expected.FormBounds.Height, actual.FormBounds.Height, 0.00001M, "FormBounds.Height"); - Assert.AreEqual(expected.FormBounds.ToRectangle(), actual.FormBounds.ToRectangle(), "FormBounds.ToRectangle"); - Assert.AreEqual(expected.PreviewBounds.X, actual.PreviewBounds.X, 0.00001M, "PreviewBounds.X"); - Assert.AreEqual(expected.PreviewBounds.Y, actual.PreviewBounds.Y, 0.00001M, "PreviewBounds.Y"); - Assert.AreEqual(expected.PreviewBounds.Width, actual.PreviewBounds.Width, 0.00001M, "PreviewBounds.Width"); - Assert.AreEqual(expected.PreviewBounds.Height, actual.PreviewBounds.Height, 0.00001M, "PreviewBounds.Height"); - Assert.AreEqual(expected.PreviewBounds.ToRectangle(), actual.PreviewBounds.ToRectangle(), "PreviewBounds.ToRectangle"); - Assert.AreEqual(expected.ScreenBounds.Count, actual.ScreenBounds.Count, "ScreenBounds.Count"); - for (var i = 0; i < expected.ScreenBounds.Count; i++) - { - Assert.AreEqual(expected.ScreenBounds[i].X, actual.ScreenBounds[i].X, 0.00001M, $"ScreenBounds[{i}].X"); - Assert.AreEqual(expected.ScreenBounds[i].Y, actual.ScreenBounds[i].Y, 0.00001M, $"ScreenBounds[{i}].Y"); - Assert.AreEqual(expected.ScreenBounds[i].Width, actual.ScreenBounds[i].Width, 0.00001M, $"ScreenBounds[{i}].Width"); - Assert.AreEqual(expected.ScreenBounds[i].Height, actual.ScreenBounds[i].Height, 0.00001M, $"ScreenBounds[{i}].Height"); - Assert.AreEqual(expected.ScreenBounds[i].ToRectangle(), actual.ScreenBounds[i].ToRectangle(), "ActivatedScreen.ToRectangle"); - } - - Assert.AreEqual(expected.ActivatedScreenBounds.X, actual.ActivatedScreenBounds.X, "ActivatedScreen.X"); - Assert.AreEqual(expected.ActivatedScreenBounds.Y, actual.ActivatedScreenBounds.Y, "ActivatedScreen.Y"); - Assert.AreEqual(expected.ActivatedScreenBounds.Width, actual.ActivatedScreenBounds.Width, "ActivatedScreen.Width"); - Assert.AreEqual(expected.ActivatedScreenBounds.Height, actual.ActivatedScreenBounds.Height, "ActivatedScreen.Height"); - Assert.AreEqual(expected.ActivatedScreenBounds.ToRectangle(), actual.ActivatedScreenBounds.ToRectangle(), "ActivatedScreen.ToRectangle"); - } - } -} diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Helpers/MouseHelperTests.cs b/src/modules/MouseUtils/MouseJumpUI.UnitTests/Helpers/MouseHelperTests.cs deleted file mode 100644 index e0267d4787..0000000000 --- a/src/modules/MouseUtils/MouseJumpUI.UnitTests/Helpers/MouseHelperTests.cs +++ /dev/null @@ -1,74 +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.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MouseJumpUI.Helpers; -using MouseJumpUI.Models.Drawing; - -namespace MouseJumpUI.UnitTests.Helpers; - -[TestClass] -public static class MouseHelperTests -{ - [TestClass] - public sealed class GetJumpLocationTests - { - public sealed class TestCase - { - public TestCase(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds, PointInfo expectedResult) - { - this.PreviewLocation = previewLocation; - this.PreviewSize = previewSize; - this.DesktopBounds = desktopBounds; - this.ExpectedResult = expectedResult; - } - - public PointInfo PreviewLocation { get; set; } - - public SizeInfo PreviewSize { get; set; } - - public RectangleInfo DesktopBounds { get; set; } - - public PointInfo ExpectedResult { get; set; } - } - - public static IEnumerable GetTestCases() - { - // screen corners and midpoint with a zero origin - yield return new[] { new TestCase(new(0, 0), new(160, 120), new(0, 0, 1600, 1200), new(0, 0)) }; - yield return new[] { new TestCase(new(160, 0), new(160, 120), new(0, 0, 1600, 1200), new(1600, 0)) }; - yield return new[] { new TestCase(new(0, 120), new(160, 120), new(0, 0, 1600, 1200), new(0, 1200)) }; - yield return new[] { new TestCase(new(160, 120), new(160, 120), new(0, 0, 1600, 1200), new(1600, 1200)) }; - yield return new[] { new TestCase(new(80, 60), new(160, 120), new(0, 0, 1600, 1200), new(800, 600)) }; - - // screen corners and midpoint with a positive origin - yield return new[] { new TestCase(new(0, 0), new(160, 120), new(1000, 1000, 1600, 1200), new(1000, 1000)) }; - yield return new[] { new TestCase(new(160, 0), new(160, 120), new(1000, 1000, 1600, 1200), new(2600, 1000)) }; - yield return new[] { new TestCase(new(0, 120), new(160, 120), new(1000, 1000, 1600, 1200), new(1000, 2200)) }; - yield return new[] { new TestCase(new(160, 120), new(160, 120), new(1000, 1000, 1600, 1200), new(2600, 2200)) }; - yield return new[] { new TestCase(new(80, 60), new(160, 120), new(1000, 1000, 1600, 1200), new(1800, 1600)) }; - - // screen corners and midpoint with a negative origin - yield return new[] { new TestCase(new(0, 0), new(160, 120), new(-1000, -1000, 1600, 1200), new(-1000, -1000)) }; - yield return new[] { new TestCase(new(160, 0), new(160, 120), new(-1000, -1000, 1600, 1200), new(600, -1000)) }; - yield return new[] { new TestCase(new(0, 120), new(160, 120), new(-1000, -1000, 1600, 1200), new(-1000, 200)) }; - yield return new[] { new TestCase(new(160, 120), new(160, 120), new(-1000, -1000, 1600, 1200), new(600, 200)) }; - yield return new[] { new TestCase(new(80, 60), new(160, 120), new(-1000, -1000, 1600, 1200), new(-200, -400)) }; - } - - [TestMethod] - [DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)] - public void RunTestCases(TestCase data) - { - var actual = MouseHelper.GetJumpLocation( - data.PreviewLocation, - data.PreviewSize, - data.DesktopBounds); - var expected = data.ExpectedResult; - Assert.AreEqual(expected.X, actual.X); - Assert.AreEqual(expected.Y, actual.Y); - } - } -} diff --git a/src/modules/MouseUtils/MouseJumpUI.UnitTests/MouseJumpUI.UnitTests.csproj b/src/modules/MouseUtils/MouseJumpUI.UnitTests/MouseJumpUI.UnitTests.csproj index 66b91f1443..a2fab9dc7e 100644 --- a/src/modules/MouseUtils/MouseJumpUI.UnitTests/MouseJumpUI.UnitTests.csproj +++ b/src/modules/MouseUtils/MouseJumpUI.UnitTests/MouseJumpUI.UnitTests.csproj @@ -28,6 +28,13 @@ + + + + + + + diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/DrawingHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/DrawingHelper.cs new file mode 100644 index 0000000000..9537fca82e --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/DrawingHelper.cs @@ -0,0 +1,248 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Linq; +using MouseJumpUI.Common.Imaging; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Layout; +using MouseJumpUI.Common.Models.Styles; + +namespace MouseJumpUI.Common.Helpers; + +internal static class DrawingHelper +{ + public static Bitmap RenderPreview( + PreviewLayout previewLayout, + IImageRegionCopyService imageCopyService, + Action? previewImageCreatedCallback = null, + Action? previewImageUpdatedCallback = null) + { + var stopwatch = Stopwatch.StartNew(); + + // initialize the preview image + var previewBounds = previewLayout.PreviewBounds.OuterBounds.ToRectangle(); + var previewImage = new Bitmap(previewBounds.Width, previewBounds.Height, PixelFormat.Format32bppPArgb); + var previewGraphics = Graphics.FromImage(previewImage); + previewImageCreatedCallback?.Invoke(previewImage); + + DrawingHelper.DrawRaisedBorder(previewGraphics, previewLayout.PreviewStyle.CanvasStyle, previewLayout.PreviewBounds); + DrawingHelper.DrawBackgroundFill( + previewGraphics, + previewLayout.PreviewStyle.CanvasStyle, + previewLayout.PreviewBounds, + []); + + // sort the source and target screen areas into the order we want to + // draw them, putting the activated screen first (we need to capture + // and draw the activated screen before we show the form because + // otherwise we'll capture the form as part of the screenshot!) + var sourceScreens = new List { previewLayout.Screens[previewLayout.ActivatedScreenIndex] } + .Concat(previewLayout.Screens.Where((_, idx) => idx != previewLayout.ActivatedScreenIndex)) + .ToList(); + var targetScreens = new List { previewLayout.ScreenshotBounds[previewLayout.ActivatedScreenIndex] } + .Concat(previewLayout.ScreenshotBounds.Where((_, idx) => idx != previewLayout.ActivatedScreenIndex)) + .ToList(); + + // draw all the screenshot bezels + foreach (var screenshotBounds in previewLayout.ScreenshotBounds) + { + DrawingHelper.DrawRaisedBorder( + previewGraphics, previewLayout.PreviewStyle.ScreenStyle, screenshotBounds); + } + + var refreshRequired = false; + var placeholdersDrawn = false; + for (var i = 0; i < sourceScreens.Count; i++) + { + imageCopyService.CopyImageRegion(previewGraphics, sourceScreens[i], targetScreens[i].ContentBounds); + refreshRequired = true; + + // show the placeholder images and show the form if it looks like it might take + // a while to capture the remaining screenshot images (but only if there are any) + if (stopwatch.ElapsedMilliseconds > 250) + { + // draw placeholder backgrounds for any undrawn screens + if (!placeholdersDrawn) + { + DrawingHelper.DrawScreenPlaceholders( + previewGraphics, + previewLayout.PreviewStyle.ScreenStyle, + targetScreens.GetRange(i + 1, targetScreens.Count - i - 1)); + placeholdersDrawn = true; + } + + previewImageUpdatedCallback?.Invoke(); + refreshRequired = false; + } + } + + if (refreshRequired) + { + previewImageUpdatedCallback?.Invoke(); + } + + stopwatch.Stop(); + + return previewImage; + } + + /// + /// Draws a border shape with an optional raised 3d highlight and shadow effect. + /// + private static void DrawRaisedBorder( + Graphics graphics, BoxStyle boxStyle, BoxBounds boxBounds) + { + var borderStyle = boxStyle.BorderStyle; + if ((borderStyle.Horizontal == 0) || (borderStyle.Vertical == 0)) + { + return; + } + + // draw the main box border + using var borderBrush = new SolidBrush(borderStyle.Color); + var borderRegion = new Region(boxBounds.BorderBounds.ToRectangle()); + borderRegion.Exclude(boxBounds.PaddingBounds.ToRectangle()); + graphics.FillRegion(borderBrush, borderRegion); + + // draw the highlight and shadow + var bounds = boxBounds.BorderBounds.ToRectangle(); + using var highlight = new Pen(Color.FromArgb(0x44, 0xFF, 0xFF, 0xFF)); + using var shadow = new Pen(Color.FromArgb(0x44, 0x00, 0x00, 0x00)); + + var outer = ( + Left: bounds.Left, + Top: bounds.Top, + Right: bounds.Right - 1, + Bottom: bounds.Bottom - 1 + ); + var inner = ( + Left: bounds.Left + (int)borderStyle.Left - 1, + Top: bounds.Top + (int)borderStyle.Top - 1, + Right: bounds.Right - (int)borderStyle.Right, + Bottom: bounds.Bottom - (int)borderStyle.Bottom + ); + + for (var i = 0; i < borderStyle.Depth; i++) + { + // left edge + if (borderStyle.Left >= i * 2) + { + graphics.DrawLine(highlight, outer.Left, outer.Top, outer.Left, outer.Bottom); + graphics.DrawLine(shadow, inner.Left, inner.Top, inner.Left, inner.Bottom); + } + + // top edge + if (borderStyle.Top >= i * 2) + { + graphics.DrawLine(highlight, outer.Left, outer.Top, outer.Right, outer.Top); + graphics.DrawLine(shadow, inner.Left, inner.Top, inner.Right, inner.Top); + } + + // right edge + if (borderStyle.Right >= i * 2) + { + graphics.DrawLine(highlight, inner.Right, inner.Top, inner.Right, inner.Bottom); + graphics.DrawLine(shadow, outer.Right, outer.Top, outer.Right, outer.Bottom); + } + + // bottom edge + if (borderStyle.Bottom >= i * 2) + { + graphics.DrawLine(highlight, inner.Left, inner.Bottom, inner.Right, inner.Bottom); + graphics.DrawLine(shadow, outer.Left, outer.Bottom, outer.Right, outer.Bottom); + } + + // shrink the outer border for the next iteration + outer = ( + outer.Left + 1, + outer.Top + 1, + outer.Right - 1, + outer.Bottom - 1 + ); + + // enlarge the inner border for the next iteration + inner = ( + inner.Left - 1, + inner.Top - 1, + inner.Right + 1, + inner.Bottom + 1 + ); + } + } + + /// + /// Draws a gradient-filled background shape. + /// + private static void DrawBackgroundFill( + Graphics graphics, BoxStyle boxStyle, BoxBounds boxBounds, IEnumerable excludeBounds) + { + var backgroundBounds = boxBounds.PaddingBounds; + + using var backgroundBrush = DrawingHelper.GetBackgroundStyleBrush(boxStyle.BackgroundStyle, backgroundBounds); + if (backgroundBrush == null) + { + return; + } + + // it's faster to build a region with the screen areas excluded + // and fill that than it is to fill the entire bounding rectangle + var backgroundRegion = new Region(backgroundBounds.ToRectangle()); + foreach (var exclude in excludeBounds) + { + backgroundRegion.Exclude(exclude.ToRectangle()); + } + + graphics.FillRegion(backgroundBrush, backgroundRegion); + } + + /// + /// Draws placeholder background images for the specified screens on the preview. + /// + private static void DrawScreenPlaceholders( + Graphics graphics, BoxStyle screenStyle, IList screenBounds) + { + if (screenBounds.Count == 0) + { + return; + } + + if (screenStyle?.BackgroundStyle?.Color1 == null) + { + return; + } + + using var brush = new SolidBrush(screenStyle.BackgroundStyle.Color1.Value); + graphics.FillRectangles(brush, screenBounds.Select(bounds => bounds.PaddingBounds.ToRectangle()).ToArray()); + } + + private static Brush? GetBackgroundStyleBrush(BackgroundStyle backgroundStyle, RectangleInfo backgroundBounds) + { + var backgroundBrush = backgroundStyle switch + { + { Color1: not null, Color2: not null } => + /* draw a gradient fill if both colors are specified */ + new LinearGradientBrush( + backgroundBounds.ToRectangle(), + backgroundStyle.Color1.Value, + backgroundStyle.Color2.Value, + LinearGradientMode.ForwardDiagonal), + { Color1: not null } => + /* draw a solid fill if only one color is specified */ + new SolidBrush( + backgroundStyle.Color1.Value), + { Color2: not null } => + /* draw a solid fill if only one color is specified */ + new SolidBrush( + backgroundStyle.Color2.Value), + _ => (Brush?)null, + }; + return backgroundBrush; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/LayoutHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/LayoutHelper.cs new file mode 100644 index 0000000000..e5a57ee567 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/LayoutHelper.cs @@ -0,0 +1,159 @@ +// 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.Collections.Generic; +using System.Linq; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Layout; +using MouseJumpUI.Common.Models.Styles; + +namespace MouseJumpUI.Common.Helpers; + +internal static class LayoutHelper +{ + public static PreviewLayout GetPreviewLayout( + PreviewStyle previewStyle, List screens, PointInfo activatedLocation) + { + ArgumentNullException.ThrowIfNull(previewStyle); + ArgumentNullException.ThrowIfNull(screens); + + if (screens.Count == 0) + { + throw new ArgumentException("Value must contain at least one item.", nameof(screens)); + } + + var builder = new PreviewLayout.Builder(); + builder.Screens = screens.ToList(); + + // calculate the bounding rectangle for the virtual screen + builder.VirtualScreen = LayoutHelper.GetCombinedScreenBounds(builder.Screens); + + // find the screen that contains the activated location - this is the + // one we'll show the preview form on + var activatedScreen = builder.Screens.Single( + screen => screen.Contains(activatedLocation)); + builder.ActivatedScreenIndex = builder.Screens.IndexOf(activatedScreen); + + // work out the maximum allowed size of the preview form: + // * can't be bigger than the activated screen + // * can't be bigger than the configured canvas size + var maxPreviewSize = activatedScreen.Size + .Intersect(previewStyle.CanvasSize); + + // the "content area" (i.e. drawing area) for screenshots is inside the + // preview border and inside the preview padding (if any) + var maxContentSize = maxPreviewSize + .Shrink(previewStyle.CanvasStyle.MarginStyle) + .Shrink(previewStyle.CanvasStyle.BorderStyle) + .Shrink(previewStyle.CanvasStyle.PaddingStyle); + + // scale the virtual screen to fit inside the content area + var screenScalingRatio = builder.VirtualScreen.Size + .ScaleToFitRatio(maxContentSize); + + // work out the actual size of the "content area" by scaling the virtual screen + // to fit inside the maximum content area while maintaining its aspect ration. + // we'll also offset it to allow for any margins, borders and padding + var contentBounds = builder.VirtualScreen.Size + .Scale(screenScalingRatio) + .Floor() + .PlaceAt(0, 0) + .Offset(previewStyle.CanvasStyle.MarginStyle.Left, previewStyle.CanvasStyle.MarginStyle.Top) + .Offset(previewStyle.CanvasStyle.BorderStyle.Left, previewStyle.CanvasStyle.BorderStyle.Top) + .Offset(previewStyle.CanvasStyle.PaddingStyle.Left, previewStyle.CanvasStyle.PaddingStyle.Top); + + // now we know the actual size of the content area we can work outwards to + // get the size of the background bounds including margins, borders and padding + builder.PreviewStyle = previewStyle; + builder.PreviewBounds = LayoutHelper.GetBoxBoundsFromContentBounds( + contentBounds, + previewStyle.CanvasStyle); + + // ... and then the size and position of the preview form on the activated screen + // * center the form to the activated position, but nudge it back + // inside the visible area of the activated screen if it falls outside + var formBounds = builder.PreviewBounds.OuterBounds + .Center(activatedLocation) + .Clamp(activatedScreen); + builder.FormBounds = formBounds; + + // now calculate the positions of each of the screenshot images on the preview + builder.ScreenshotBounds = builder.Screens + .Select( + screen => LayoutHelper.GetBoxBoundsFromOuterBounds( + screen + .Offset(builder.VirtualScreen.Location.ToSize().Invert()) + .Scale(screenScalingRatio) + .Offset(builder.PreviewBounds.ContentBounds.Location.ToSize()) + .Truncate(), + previewStyle.ScreenStyle)) + .ToList(); + + return builder.Build(); + } + + internal static RectangleInfo GetCombinedScreenBounds(List screens) + { + return screens.Skip(1).Aggregate( + seed: screens.First(), + (bounds, screen) => bounds.Union(screen)); + } + + /// + /// Calculates the bounds of the various areas of a box, given the content bounds and the box style. + /// Starts with the content bounds and works outward, enlarging the content bounds by the padding, border, and margin sizes to calculate the outer bounds of the box. + /// + /// The content bounds of the box. + /// The style of the box, which includes the sizes of the margin, border, and padding areas. + /// A object that represents the bounds of the different areas of the box. + /// Thrown when or is null. + /// Thrown when any of the styles in is null. + internal static BoxBounds GetBoxBoundsFromContentBounds( + RectangleInfo contentBounds, + BoxStyle boxStyle) + { + ArgumentNullException.ThrowIfNull(contentBounds); + ArgumentNullException.ThrowIfNull(boxStyle); + if (boxStyle.PaddingStyle == null || boxStyle.BorderStyle == null || boxStyle.MarginStyle == null) + { + throw new ArgumentException(null, nameof(boxStyle)); + } + + var paddingBounds = contentBounds.Enlarge(boxStyle.PaddingStyle); + var borderBounds = paddingBounds.Enlarge(boxStyle.BorderStyle); + var marginBounds = borderBounds.Enlarge(boxStyle.MarginStyle); + var outerBounds = marginBounds; + return new( + outerBounds, marginBounds, borderBounds, paddingBounds, contentBounds); + } + + /// + /// Calculates the bounds of the various areas of a box, given the outer bounds and the box style. + /// This method starts with the outer bounds and works inward, shrinking the outer bounds by the margin, border, and padding sizes to calculate the content bounds of the box. + /// + /// The outer bounds of the box. + /// The style of the box, which includes the sizes of the margin, border, and padding areas. + /// A object that represents the bounds of the different areas of the box. + /// Thrown when or is null. + /// Thrown when any of the styles in is null. + internal static BoxBounds GetBoxBoundsFromOuterBounds( + RectangleInfo outerBounds, + BoxStyle boxStyle) + { + ArgumentNullException.ThrowIfNull(outerBounds); + ArgumentNullException.ThrowIfNull(boxStyle); + if (outerBounds == null || boxStyle.MarginStyle == null || boxStyle.BorderStyle == null || boxStyle.PaddingStyle == null) + { + throw new ArgumentException(null, nameof(boxStyle)); + } + + var marginBounds = outerBounds; + var borderBounds = marginBounds.Shrink(boxStyle.MarginStyle); + var paddingBounds = borderBounds.Shrink(boxStyle.BorderStyle); + var contentBounds = paddingBounds.Shrink(boxStyle.PaddingStyle); + return new( + outerBounds, marginBounds, borderBounds, paddingBounds, contentBounds); + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/MouseHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/MouseHelper.cs similarity index 74% rename from src/modules/MouseUtils/MouseJumpUI/Helpers/MouseHelper.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/Helpers/MouseHelper.cs index 78eb9a496d..f06be27646 100644 --- a/src/modules/MouseUtils/MouseJumpUI/Helpers/MouseHelper.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/MouseHelper.cs @@ -4,12 +4,12 @@ using System.ComponentModel; using System.Runtime.InteropServices; -using System.Windows.Forms; -using MouseJumpUI.Models.Drawing; -using MouseJumpUI.NativeMethods; -using static MouseJumpUI.NativeMethods.Core; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.NativeMethods; +using static MouseJumpUI.Common.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.User32; -namespace MouseJumpUI.Helpers; +namespace MouseJumpUI.Common.Helpers; internal static class MouseHelper { @@ -22,7 +22,7 @@ internal static class MouseHelper /// or even negative if the primary monitor is not the at the top-left of the /// entire desktop rectangle, so results may contain negative coordinates. /// - public static PointInfo GetJumpLocation(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds) + internal static PointInfo GetJumpLocation(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds) { return previewLocation .Scale(previewSize.ScaleToFitRatio(desktopBounds.Size)) @@ -32,7 +32,7 @@ internal static class MouseHelper /// /// Get the current position of the cursor. /// - public static PointInfo GetCursorPosition() + internal static PointInfo GetCursorPosition() { var lpPoint = new LPPOINT(new POINT(0, 0)); var result = User32.GetCursorPos(lpPoint); @@ -55,7 +55,7 @@ internal static class MouseHelper /// /// See https://github.com/mikeclayton/FancyMouse/pull/3 /// - public static void SetCursorPosition(PointInfo location) + internal static void SetCursorPosition(PointInfo location) { // set the new cursor position *twice* - the cursor sometimes end up in // the wrong place if we try to cross the dead space between non-aligned @@ -73,15 +73,21 @@ internal static class MouseHelper // // setting the position a second time seems to fix this and moves the // cursor to the expected location (b) - var point = location.ToPoint(); + var target = location.ToPoint(); for (var i = 0; i < 2; i++) { - var result = User32.SetCursorPos(point.X, point.Y); + var result = User32.SetCursorPos(target.X, target.Y); if (!result) { throw new Win32Exception( Marshal.GetLastWin32Error()); } + + var current = MouseHelper.GetCursorPosition(); + if ((current.X == target.X) || (current.Y == target.Y)) + { + break; + } } // temporary workaround for issue #1273 @@ -95,25 +101,25 @@ internal static class MouseHelper /// See https://github.com/microsoft/PowerToys/issues/24523 /// https://github.com/microsoft/PowerToys/pull/24527 /// - public static void SimulateMouseMovementEvent(PointInfo location) + internal static void SimulateMouseMovementEvent(PointInfo location) { var inputs = new User32.INPUT[] { new( - type: User32.INPUT_TYPE.INPUT_MOUSE, - data: new User32.INPUT.DUMMYUNIONNAME( - mi: new User32.MOUSEINPUT( + type: INPUT_TYPE.INPUT_MOUSE, + data: new INPUT.DUMMYUNIONNAME( + mi: new MOUSEINPUT( dx: (int)MouseHelper.CalculateAbsoluteCoordinateX(location.X), dy: (int)MouseHelper.CalculateAbsoluteCoordinateY(location.Y), mouseData: 0, - dwFlags: User32.MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | User32.MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE, + dwFlags: MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE, time: 0, dwExtraInfo: ULONG_PTR.Null))), }; var result = User32.SendInput( - (uint)inputs.Length, - new User32.LPINPUT(inputs), - User32.INPUT.Size * inputs.Length); + (UINT)inputs.Length, + new LPINPUT(inputs), + INPUT.Size * inputs.Length); if (result != inputs.Length) { throw new Win32Exception( @@ -125,13 +131,13 @@ internal static class MouseHelper { // If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535. // see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput - return (x * 65535) / User32.GetSystemMetrics(User32.SYSTEM_METRICS_INDEX.SM_CXSCREEN); + return (x * 65535) / User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN); } - internal static decimal CalculateAbsoluteCoordinateY(decimal y) + private static decimal CalculateAbsoluteCoordinateY(decimal y) { // If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535. // see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput - return (y * 65535) / User32.GetSystemMetrics(User32.SYSTEM_METRICS_INDEX.SM_CYSCREEN); + return (y * 65535) / User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN); } } diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/ScreenHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/ScreenHelper.cs similarity index 73% rename from src/modules/MouseUtils/MouseJumpUI/Helpers/ScreenHelper.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/Helpers/ScreenHelper.cs index 4a61e6a6b3..a280b59a75 100644 --- a/src/modules/MouseUtils/MouseJumpUI/Helpers/ScreenHelper.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Helpers/ScreenHelper.cs @@ -5,13 +5,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using MouseJumpUI.Models.Drawing; -using MouseJumpUI.Models.Screen; -using MouseJumpUI.NativeMethods; -using static MouseJumpUI.NativeMethods.Core; -using static MouseJumpUI.NativeMethods.User32; +using System.Linq; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.NativeMethods; +using static MouseJumpUI.Common.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.User32; -namespace MouseJumpUI.Helpers; +namespace MouseJumpUI.Common.Helpers; internal static class ScreenHelper { @@ -28,22 +28,21 @@ internal static class ScreenHelper User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN)); } - public static IEnumerable GetAllScreens() + internal static IEnumerable GetAllScreens() { // enumerate the monitors attached to the system var hMonitors = new List(); - var result = User32.EnumDisplayMonitors( - HDC.Null, - LPCRECT.Null, - (unnamedParam1, unnamedParam2, unnamedParam3, unnamedParam4) => + var callback = new User32.MONITORENUMPROC( + (hMonitor, hdcMonitor, lprcMonitor, dwData) => { - hMonitors.Add(unnamedParam1); + hMonitors.Add(hMonitor); return true; - }, - LPARAM.Null); + }); + var result = User32.EnumDisplayMonitors(HDC.Null, LPCRECT.Null, callback, LPARAM.Null); if (!result) { throw new Win32Exception( + result.Value, $"{nameof(User32.EnumDisplayMonitors)} failed with return code {result.Value}"); } @@ -51,11 +50,12 @@ internal static class ScreenHelper foreach (var hMonitor in hMonitors) { var monitorInfoPtr = new LPMONITORINFO( - new MONITORINFO((uint)MONITORINFO.Size, RECT.Empty, RECT.Empty, 0)); + new MONITORINFO((DWORD)MONITORINFO.Size, RECT.Empty, RECT.Empty, 0)); result = User32.GetMonitorInfoW(hMonitor, monitorInfoPtr); if (!result) { throw new Win32Exception( + result.Value, $"{nameof(User32.GetMonitorInfoW)} failed with return code {result.Value}"); } @@ -78,9 +78,11 @@ internal static class ScreenHelper } } - public static HMONITOR MonitorFromPoint( + internal static ScreenInfo GetScreenFromPoint( + List screens, PointInfo pt) { + // get the monitor handle from the point var hMonitor = User32.MonitorFromPoint( new((int)pt.X, (int)pt.Y), User32.MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); @@ -89,6 +91,9 @@ internal static class ScreenHelper throw new InvalidOperationException($"no monitor found for point {pt}"); } - return hMonitor; + // find the screen with the given monitor handle + var screen = screens + .Single(item => item.Handle == hMonitor); + return screen; } } diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/DesktopImageRegionCopyService.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/DesktopImageRegionCopyService.cs new file mode 100644 index 0000000000..7b3653d837 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/DesktopImageRegionCopyService.cs @@ -0,0 +1,123 @@ +// 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.Drawing; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.NativeMethods; +using static MouseJumpUI.Common.NativeMethods.Core; + +namespace MouseJumpUI.Common.Imaging; + +/// +/// Implements an IImageRegionCopyService that uses the current desktop window as the copy source. +/// This is used during the main application runtime to generate preview images of the desktop. +/// +internal sealed class DesktopImageRegionCopyService : IImageRegionCopyService +{ + /// + /// Copies the source region from the current desktop window + /// to the target region on the specified Graphics object. + /// + public void CopyImageRegion( + Graphics targetGraphics, + RectangleInfo sourceBounds, + RectangleInfo targetBounds) + { + var stopwatch = Stopwatch.StartNew(); + var (desktopHwnd, desktopHdc) = DesktopImageRegionCopyService.GetDesktopDeviceContext(); + var previewHdc = DesktopImageRegionCopyService.GetGraphicsDeviceContext( + targetGraphics, Gdi32.STRETCH_BLT_MODE.STRETCH_HALFTONE); + stopwatch.Stop(); + + var source = sourceBounds.ToRectangle(); + var target = targetBounds.ToRectangle(); + var result = Gdi32.StretchBlt( + previewHdc, + target.X, + target.Y, + target.Width, + target.Height, + desktopHdc, + source.X, + source.Y, + source.Width, + source.Height, + Gdi32.ROP_CODE.SRCCOPY); + if (!result) + { + throw new InvalidOperationException( + $"{nameof(Gdi32.StretchBlt)} returned {result.Value}"); + } + + // we need to release the graphics device context handle before anything + // else tries to use the Graphics object otherwise it'll give an error + // from GDI saying "Object is currently in use elsewhere" + DesktopImageRegionCopyService.FreeGraphicsDeviceContext(targetGraphics, ref previewHdc); + + DesktopImageRegionCopyService.FreeDesktopDeviceContext(ref desktopHwnd, ref desktopHdc); + } + + private static (HWND DesktopHwnd, HDC DesktopHdc) GetDesktopDeviceContext() + { + var desktopHwnd = User32.GetDesktopWindow(); + var desktopHdc = User32.GetWindowDC(desktopHwnd); + if (desktopHdc.IsNull) + { + throw new InvalidOperationException( + $"{nameof(User32.GetWindowDC)} returned null"); + } + + return (desktopHwnd, desktopHdc); + } + + private static void FreeDesktopDeviceContext(ref HWND desktopHwnd, ref HDC desktopHdc) + { + if (!desktopHwnd.IsNull && !desktopHdc.IsNull) + { + var result = User32.ReleaseDC(desktopHwnd, desktopHdc); + if (result == 0) + { + throw new InvalidOperationException( + $"{nameof(User32.ReleaseDC)} returned {result}"); + } + } + + desktopHwnd = HWND.Null; + desktopHdc = HDC.Null; + } + + /// + /// Checks if the target device context handle exists, and creates a new one from the + /// specified Graphics object if not. + /// + private static HDC GetGraphicsDeviceContext(Graphics graphics, Gdi32.STRETCH_BLT_MODE mode) + { + var graphicsHdc = (HDC)graphics.GetHdc(); + + var result = Gdi32.SetStretchBltMode(graphicsHdc, mode); + if (result == 0) + { + throw new InvalidOperationException( + $"{nameof(Gdi32.SetStretchBltMode)} returned {result}"); + } + + return graphicsHdc; + } + + /// + /// Free the specified device context handle if it exists. + /// + private static void FreeGraphicsDeviceContext(Graphics graphics, ref HDC graphicsHdc) + { + if (graphicsHdc.IsNull) + { + return; + } + + graphics.ReleaseHdc(graphicsHdc.Value); + graphicsHdc = HDC.Null; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/IImageRegionCopyService.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/IImageRegionCopyService.cs new file mode 100644 index 0000000000..24c33766d5 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/IImageRegionCopyService.cs @@ -0,0 +1,24 @@ +// 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.Drawing; +using MouseJumpUI.Common.Models.Drawing; + +namespace MouseJumpUI.Common.Imaging; + +internal interface IImageRegionCopyService +{ + /// + /// Copies the source region from the provider's source image (e.g. the interactive desktop, + /// a static image, etc) to the target region on the specified Graphics object. + /// + /// + /// Implementations of this interface are used to capture regions of the interactive desktop + /// during runtime, or to capture regions of a static reference image during unit tests. + /// + void CopyImageRegion( + Graphics targetGraphics, + RectangleInfo sourceBounds, + RectangleInfo targetBounds); +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/StaticImageRegionCopyService.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/StaticImageRegionCopyService.cs new file mode 100644 index 0000000000..a7557c52f3 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Imaging/StaticImageRegionCopyService.cs @@ -0,0 +1,42 @@ +// 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.Drawing; +using MouseJumpUI.Common.Models.Drawing; + +namespace MouseJumpUI.Common.Imaging; + +/// +/// Implements an IImageRegionCopyService that uses the specified image as the copy source. +/// This is used for testing the DrawingHelper rather than as part of the main application. +/// +internal sealed class StaticImageRegionCopyService : IImageRegionCopyService +{ + public StaticImageRegionCopyService(Image sourceImage) + { + this.SourceImage = sourceImage ?? throw new ArgumentNullException(nameof(sourceImage)); + } + + private Image SourceImage + { + get; + } + + /// + /// Copies the source region from the static source image + /// to the target region on the specified Graphics object. + /// + public void CopyImageRegion( + Graphics targetGraphics, + RectangleInfo sourceBounds, + RectangleInfo targetBounds) + { + targetGraphics.DrawImage( + image: this.SourceImage, + destRect: targetBounds.ToRectangle(), + srcRect: sourceBounds.ToRectangle(), + srcUnit: GraphicsUnit.Pixel); + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/BoxBounds.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/BoxBounds.cs new file mode 100644 index 0000000000..8b04ab9d28 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/BoxBounds.cs @@ -0,0 +1,75 @@ +// 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 MouseJumpUI.Common.Models.Drawing; + +public sealed class BoxBounds +{ + /* + + see https://www.w3schools.com/css/css_boxmodel.asp + + +--------------[bounds]---------------+ + |▒▒▒▒▒▒▒▒▒▒▒▒▒▒[margin]▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒| + |▒▒▓▓▓▓▓▓▓▓▓▓▓▓[border]▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒| + |▒▒▓▓░░░░░░░░░░[padding]░░░░░░░░░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░ [content] ░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▒▒| + |▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒| + |▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒| + +-------------------------------------+ + + */ + + internal BoxBounds( + RectangleInfo outerBounds, + RectangleInfo marginBounds, + RectangleInfo borderBounds, + RectangleInfo paddingBounds, + RectangleInfo contentBounds) + { + this.OuterBounds = outerBounds ?? throw new ArgumentNullException(nameof(outerBounds)); + this.MarginBounds = marginBounds ?? throw new ArgumentNullException(nameof(marginBounds)); + this.BorderBounds = borderBounds ?? throw new ArgumentNullException(nameof(borderBounds)); + this.PaddingBounds = paddingBounds ?? throw new ArgumentNullException(nameof(paddingBounds)); + this.ContentBounds = contentBounds ?? throw new ArgumentNullException(nameof(contentBounds)); + } + + /// + /// Gets the outer bounds of this layout box. + /// + public RectangleInfo OuterBounds + { + get; + } + + public RectangleInfo MarginBounds + { + get; + } + + public RectangleInfo BorderBounds + { + get; + } + + public RectangleInfo PaddingBounds + { + get; + } + + /// + /// Gets the bounds of the content area for this layout box. + /// + public RectangleInfo ContentBounds + { + get; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/PointInfo.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/PointInfo.cs new file mode 100644 index 0000000000..41ba60eec4 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/PointInfo.cs @@ -0,0 +1,80 @@ +// 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.Drawing; + +namespace MouseJumpUI.Common.Models.Drawing; + +/// +/// Immutable version of a System.Drawing.Point object with some extra utility methods. +/// +public sealed class PointInfo +{ + public PointInfo(decimal x, decimal y) + { + this.X = x; + this.Y = y; + } + + public PointInfo(Point point) + : this(point.X, point.Y) + { + } + + public decimal X + { + get; + } + + public decimal Y + { + get; + } + + /// + /// Moves this PointInfo inside the specified RectangleInfo. + /// + public PointInfo Clamp(RectangleInfo outer) + { + return new( + x: Math.Clamp(this.X, outer.X, outer.Right), + y: Math.Clamp(this.Y, outer.Y, outer.Bottom)); + } + + public PointInfo Scale(decimal scalingFactor) => new(this.X * scalingFactor, this.Y * scalingFactor); + + public PointInfo Offset(PointInfo amount) => new(this.X + amount.X, this.Y + amount.Y); + + public Point ToPoint() => new((int)this.X, (int)this.Y); + + public SizeInfo ToSize() + { + return new((int)this.X, (int)this.Y); + } + + /// + /// Stretches the point to the same proportional position in targetBounds as + /// it currently is in sourceBounds + /// + public PointInfo Stretch(RectangleInfo source, RectangleInfo target) + { + return new PointInfo( + x: ((this.X - source.X) / source.Width * target.Width) + target.X, + y: ((this.Y - source.Y) / source.Height * target.Height) + target.Y); + } + + public PointInfo Truncate() => + new( + (int)this.X, + (int)this.Y); + + public override string ToString() + { + return "{" + + $"{nameof(this.X)}={this.X}," + + $"{nameof(this.Y)}={this.Y}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/RectangleInfo.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/RectangleInfo.cs new file mode 100644 index 0000000000..8118138db4 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/RectangleInfo.cs @@ -0,0 +1,300 @@ +// 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.Drawing; +using System.Text.Json.Serialization; +using MouseJumpUI.Common.Models.Styles; +using BorderStyle = MouseJumpUI.Common.Models.Styles.BorderStyle; + +namespace MouseJumpUI.Common.Models.Drawing; + +/// +/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods. +/// +public sealed class RectangleInfo +{ + public static readonly RectangleInfo Empty = new(0, 0, 0, 0); + + public RectangleInfo(decimal x, decimal y, decimal width, decimal height) + { + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } + + public RectangleInfo(Rectangle rectangle) + : this(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height) + { + } + + public RectangleInfo(Point location, SizeInfo size) + : this(location.X, location.Y, size.Width, size.Height) + { + } + + public RectangleInfo(SizeInfo size) + : this(0, 0, size.Width, size.Height) + { + } + + public decimal X + { + get; + } + + public decimal Y + { + get; + } + + public decimal Width + { + get; + } + + public decimal Height + { + get; + } + + [JsonIgnore] + public decimal Left => + this.X; + + [JsonIgnore] + public decimal Top => + this.Y; + + [JsonIgnore] + public decimal Right => + this.X + this.Width; + + [JsonIgnore] + public decimal Bottom => + this.Y + this.Height; + + [JsonIgnore] + public decimal Area => + this.Width * this.Height; + + [JsonIgnore] + public PointInfo Location => + new(this.X, this.Y); + + [JsonIgnore] + public PointInfo Midpoint => + new( + x: this.X + (this.Width / 2), + y: this.Y + (this.Height / 2)); + + [JsonIgnore] + public SizeInfo Size => new(this.Width, this.Height); + + /// + /// Centers the rectangle around a specified point. + /// + /// The around which the rectangle will be centered. + /// A new that is centered around the specified point. + public RectangleInfo Center(PointInfo point) => + new( + x: point.X - (this.Width / 2), + y: point.Y - (this.Height / 2), + width: this.Width, + height: this.Height); + + /// + /// Returns a new that is moved within the bounds of the specified outer rectangle. + /// If the current rectangle is larger than the outer rectangle, an exception is thrown. + /// + /// The outer within which to confine this rectangle. + /// A new that is the result of moving this rectangle within the bounds of the outer rectangle. + /// Thrown when the current rectangle is larger than the outer rectangle. + public RectangleInfo Clamp(RectangleInfo outer) + { + if ((this.Width > outer.Width) || (this.Height > outer.Height)) + { + throw new ArgumentException($"Value cannot be larger than {nameof(outer)}."); + } + + return new( + x: Math.Clamp(this.X, outer.X, outer.Right - this.Width), + y: Math.Clamp(this.Y, outer.Y, outer.Bottom - this.Height), + width: this.Width, + height: this.Height); + } + + /// + /// Adapted from https://github.com/dotnet/runtime + /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs + /// + public bool Contains(decimal x, decimal y) => + this.X <= x && x < this.X + this.Width && this.Y <= y && y < this.Y + this.Height; + + /// + /// Adapted from https://github.com/dotnet/runtime + /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs + /// + public bool Contains(PointInfo pt) => + this.Contains(pt.X, pt.Y); + + /// + /// Adapted from https://github.com/dotnet/runtime + /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs + /// + public bool Contains(RectangleInfo rect) => + (this.X <= rect.X) && (rect.X + rect.Width <= this.X + this.Width) && + (this.Y <= rect.Y) && (rect.Y + rect.Height <= this.Y + this.Height); + + /// + /// Returns a new that is larger than the current rectangle. + /// The dimensions of the new rectangle are calculated by enlarging the current rectangle's dimensions by the size of the border. + /// + /// The that specifies the amount to enlarge the rectangle on each side. + /// A new that is larger than the current rectangle by the specified border amounts. + public RectangleInfo Enlarge(BorderStyle border) => + new( + this.X - border.Left, + this.Y - border.Top, + this.Width + border.Horizontal, + this.Height + border.Vertical); + + /// + /// Returns a new that is larger than the current rectangle. + /// The dimensions of the new rectangle are calculated by enlarging the current rectangle's dimensions by the size of the margin. + /// + /// The that specifies the amount to enlarge the rectangle on each side. + /// A new that is larger than the current rectangle by the specified margin amounts. + public RectangleInfo Enlarge(MarginStyle margin) => + new( + this.X - margin.Left, + this.Y - margin.Top, + this.Width + margin.Horizontal, + this.Height + margin.Vertical); + + /// + /// Returns a new that is larger than the current rectangle. + /// The dimensions of the new rectangle are calculated by enlarging the current rectangle's dimensions by the size of the padding. + /// + /// The that specifies the amount to enlarge the rectangle on each side. + /// A new that is larger than the current rectangle by the specified padding amounts. + public RectangleInfo Enlarge(PaddingStyle padding) => + new( + this.X - padding.Left, + this.Y - padding.Top, + this.Width + padding.Horizontal, + this.Height + padding.Vertical); + + /// + /// Returns a new that is offset by the specified amount. + /// + /// The representing the amount to offset in both the X and Y directions. + /// A new that is offset by the specified amount. + public RectangleInfo Offset(SizeInfo amount) => + this.Offset(amount.Width, amount.Height); + + /// + /// Returns a new that is offset by the specified X and Y distances. + /// + /// The distance to offset the rectangle along the X-axis. + /// The distance to offset the rectangle along the Y-axis. + /// A new that is offset by the specified X and Y distances. + public RectangleInfo Offset(decimal dx, decimal dy) => + new(this.X + dx, this.Y + dy, this.Width, this.Height); + + /// + /// Returns a new that is a scaled version of the current rectangle. + /// The dimensions of the new rectangle are calculated by multiplying the current rectangle's dimensions by the scaling factor. + /// + /// The factor by which to scale the rectangle's dimensions. + /// A new that is a scaled version of the current rectangle. + public RectangleInfo Scale(decimal scalingFactor) => + new( + this.X * scalingFactor, + this.Y * scalingFactor, + this.Width * scalingFactor, + this.Height * scalingFactor); + + /// + /// Returns a new that is smaller than the current rectangle. + /// The dimensions of the new rectangle are calculated by shrinking the current rectangle's dimensions by the size of the border. + /// + /// The that specifies the amount to shrink the rectangle on each side. + /// A new that is smaller than the current rectangle by the specified border amounts. + public RectangleInfo Shrink(BorderStyle border) => + new( + this.X + border.Left, + this.Y + border.Top, + this.Width - border.Horizontal, + this.Height - border.Vertical); + + /// + /// Returns a new that is smaller than the current rectangle. + /// The dimensions of the new rectangle are calculated by shrinking the current rectangle's dimensions by the size of the margin. + /// + /// The that specifies the amount to shrink the rectangle on each side. + /// A new that is smaller than the current rectangle by the specified margin amounts. + public RectangleInfo Shrink(MarginStyle margin) => + new( + this.X + margin.Left, + this.Y + margin.Top, + this.Width - margin.Horizontal, + this.Height - margin.Vertical); + + /// + /// Returns a new that is smaller than the current rectangle. + /// The dimensions of the new rectangle are calculated by shrinking the current rectangle's dimensions by the size of the padding. + /// + /// The that specifies the amount to shrink the rectangle on each side. + /// A new that is smaller than the current rectangle by the specified padding amounts. + public RectangleInfo Shrink(PaddingStyle padding) => + new( + this.X + padding.Left, + this.Y + padding.Top, + this.Width - padding.Horizontal, + this.Height - padding.Vertical); + + /// + /// Returns a new where the X, Y, Width, and Height properties of the current rectangle are truncated to integers. + /// + /// A new with the X, Y, Width, and Height properties of the current rectangle truncated to integers. + public RectangleInfo Truncate() => + new( + (int)this.X, + (int)this.Y, + (int)this.Width, + (int)this.Height); + + /// + /// Adapted from https://github.com/dotnet/runtime + /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs + /// + public RectangleInfo Union(RectangleInfo rect) + { + var x1 = Math.Min(this.X, rect.X); + var x2 = Math.Max(this.X + this.Width, rect.X + rect.Width); + var y1 = Math.Min(this.Y, rect.Y); + var y2 = Math.Max(this.Y + this.Height, rect.Y + rect.Height); + + return new RectangleInfo(x1, y1, x2 - x1, y2 - y1); + } + + public Rectangle ToRectangle() => + new( + (int)this.X, + (int)this.Y, + (int)this.Width, + (int)this.Height); + + public override string ToString() + { + return "{" + + $"{nameof(this.Left)}={this.Left}," + + $"{nameof(this.Top)}={this.Top}," + + $"{nameof(this.Width)}={this.Width}," + + $"{nameof(this.Height)}={this.Height}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/ScreenInfo.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/ScreenInfo.cs new file mode 100644 index 0000000000..66990a5e6e --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/ScreenInfo.cs @@ -0,0 +1,43 @@ +// 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 static MouseJumpUI.Common.NativeMethods.Core; + +namespace MouseJumpUI.Common.Models.Drawing; + +/// +/// Immutable version of a System.Windows.Forms.Screen object so we don't need to +/// take a dependency on WinForms just for screen info. +/// +internal sealed class ScreenInfo +{ + internal ScreenInfo(HMONITOR handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea) + { + this.Handle = handle; + this.Primary = primary; + this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea)); + this.WorkingArea = workingArea ?? throw new ArgumentNullException(nameof(workingArea)); + } + + public int Handle + { + get; + } + + public bool Primary + { + get; + } + + public RectangleInfo DisplayArea + { + get; + } + + public RectangleInfo WorkingArea + { + get; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/SizeInfo.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/SizeInfo.cs new file mode 100644 index 0000000000..a3e2954834 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Drawing/SizeInfo.cs @@ -0,0 +1,156 @@ +// 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.Drawing; +using MouseJumpUI.Common.Models.Styles; +using BorderStyle = MouseJumpUI.Common.Models.Styles.BorderStyle; + +namespace MouseJumpUI.Common.Models.Drawing; + +/// +/// Immutable version of a System.Drawing.Size object with some extra utility methods. +/// +public sealed class SizeInfo +{ + public SizeInfo(decimal width, decimal height) + { + this.Width = width; + this.Height = height; + } + + public SizeInfo(Size size) + : this(size.Width, size.Height) + { + } + + public decimal Width + { + get; + } + + public decimal Height + { + get; + } + + public SizeInfo Enlarge(BorderStyle border) => + new( + this.Width + border.Horizontal, + this.Height + border.Vertical); + + public SizeInfo Enlarge(PaddingStyle padding) => + new( + this.Width + padding.Horizontal, + this.Height + padding.Vertical); + + /// + /// Calculates the intersection of this size with another size, resulting in a size that represents + /// the overlapping dimensions. Both sizes must be non-negative. + /// + /// The size to intersect with this instance. + /// A new instance representing the intersection of the two sizes. + /// Thrown when either this size or the specified size has negative dimensions. + public SizeInfo Intersect(SizeInfo size) + { + if ((this.Width < 0) || (this.Height < 0) || (size.Width < 0) || (size.Height < 0)) + { + throw new ArgumentException("Sizes must be non-negative"); + } + + return new( + Math.Min(this.Width, size.Width), + Math.Min(this.Height, size.Height)); + } + + /// + /// Creates a new instance with the width and height negated, effectively inverting its dimensions. + /// + /// A new instance with inverted dimensions. + public SizeInfo Invert() => + new(-this.Width, -this.Height); + + public SizeInfo Scale(decimal scalingFactor) => new( + this.Width * scalingFactor, + this.Height * scalingFactor); + + public SizeInfo Shrink(BorderStyle border) => + new(this.Width - border.Horizontal, this.Height - border.Vertical); + + public SizeInfo Shrink(MarginStyle margin) => + new(this.Width - margin.Horizontal, this.Height - margin.Vertical); + + public SizeInfo Shrink(PaddingStyle padding) => + new(this.Width - padding.Horizontal, this.Height - padding.Vertical); + + /// + /// Creates a new instance representing a rectangle with this size, + /// positioned at the specified coordinates. + /// + /// The x-coordinate of the upper-left corner of the rectangle. + /// The y-coordinate of the upper-left corner of the rectangle. + /// A new instance representing the positioned rectangle. + public RectangleInfo PlaceAt(decimal x, decimal y) => + new(x, y, this.Width, this.Height); + + /// + /// Scales this size to fit within the bounds of another size, while maintaining the aspect ratio. + /// + /// The size to fit this size into. + /// A new instance representing the scaled size. + public SizeInfo ScaleToFit(SizeInfo bounds) + { + var widthRatio = bounds.Width / this.Width; + var heightRatio = bounds.Height / this.Height; + return widthRatio.CompareTo(heightRatio) switch + { + < 0 => new(bounds.Width, this.Height * widthRatio), + 0 => bounds, + > 0 => new(this.Width * heightRatio, bounds.Height), + }; + } + + /// + /// Rounds down the width and height of this size to the nearest whole number. + /// + /// A new instance with floored dimensions. + public SizeInfo Floor() + { + return new SizeInfo( + Math.Floor(this.Width), + Math.Floor(this.Height)); + } + + /// + /// Calculates the scaling ratio needed to fit this size within the bounds of another size without distorting the aspect ratio. + /// + /// The size to fit this size into. + /// The scaling ratio as a decimal. + /// Thrown if the width or height of the bounds is zero. + public decimal ScaleToFitRatio(SizeInfo bounds) + { + if (bounds.Width == 0 || bounds.Height == 0) + { + throw new ArgumentException($"{nameof(bounds.Width)} or {nameof(bounds.Height)} cannot be zero", nameof(bounds)); + } + + var widthRatio = bounds.Width / this.Width; + var heightRatio = bounds.Height / this.Height; + var scalingRatio = Math.Min(widthRatio, heightRatio); + + return scalingRatio; + } + + public Size ToSize() => new((int)this.Width, (int)this.Height); + + public Point ToPoint() => new((int)this.Width, (int)this.Height); + + public override string ToString() + { + return "{" + + $"{nameof(this.Width)}={this.Width}," + + $"{nameof(this.Height)}={this.Height}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Layout/PreviewLayout.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Layout/PreviewLayout.cs new file mode 100644 index 0000000000..9cf95b6eea --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Layout/PreviewLayout.cs @@ -0,0 +1,133 @@ +// 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.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Styles; + +namespace MouseJumpUI.Common.Models.Layout; + +public sealed class PreviewLayout +{ + public sealed class Builder + { + public Builder() + { + this.Screens = new(); + this.ScreenshotBounds = new(); + } + + public PreviewStyle? PreviewStyle + { + get; + set; + } + + public RectangleInfo? VirtualScreen + { + get; + set; + } + + public List Screens + { + get; + set; + } + + public int ActivatedScreenIndex + { + get; + set; + } + + public RectangleInfo? FormBounds + { + get; + set; + } + + public BoxBounds? PreviewBounds + { + get; + set; + } + + public List ScreenshotBounds + { + get; + set; + } + + public PreviewLayout Build() + { + return new PreviewLayout( + previewStyle: this.PreviewStyle ?? throw new InvalidOperationException($"{nameof(this.PreviewStyle)} must be initialized before calling {nameof(this.Build)}."), + virtualScreen: this.VirtualScreen ?? throw new InvalidOperationException($"{nameof(this.VirtualScreen)} must be initialized before calling {nameof(this.Build)}."), + screens: this.Screens ?? throw new InvalidOperationException($"{nameof(this.Screens)} must be initialized before calling {nameof(this.Build)}."), + activatedScreenIndex: this.ActivatedScreenIndex, + formBounds: this.FormBounds ?? throw new InvalidOperationException($"{nameof(this.FormBounds)} must be initialized before calling {nameof(this.Build)}."), + previewBounds: this.PreviewBounds ?? throw new InvalidOperationException($"{nameof(this.PreviewBounds)} must be initialized before calling {nameof(this.Build)}."), + screenshotBounds: this.ScreenshotBounds ?? throw new InvalidOperationException($"{nameof(this.ScreenshotBounds)} must be initialized before calling {nameof(this.Build)}.")); + } + } + + public PreviewLayout( + PreviewStyle previewStyle, + RectangleInfo virtualScreen, + List screens, + int activatedScreenIndex, + RectangleInfo formBounds, + BoxBounds previewBounds, + List screenshotBounds) + { + this.PreviewStyle = previewStyle ?? throw new ArgumentNullException(nameof(previewStyle)); + this.VirtualScreen = virtualScreen ?? throw new ArgumentNullException(nameof(virtualScreen)); + this.Screens = (screens ?? throw new ArgumentNullException(nameof(screens))) + .ToList().AsReadOnly(); + this.ActivatedScreenIndex = activatedScreenIndex; + this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds)); + this.PreviewBounds = previewBounds ?? throw new ArgumentNullException(nameof(previewBounds)); + this.ScreenshotBounds = (screenshotBounds ?? throw new ArgumentNullException(nameof(screenshotBounds))) + .ToList().AsReadOnly(); + } + + public PreviewStyle PreviewStyle + { + get; + } + + public RectangleInfo VirtualScreen + { + get; + } + + public ReadOnlyCollection Screens + { + get; + } + + public int ActivatedScreenIndex + { + get; + } + + public RectangleInfo FormBounds + { + get; + } + + public BoxBounds PreviewBounds + { + get; + } + + public ReadOnlyCollection ScreenshotBounds + { + get; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BackgroundStyle.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BackgroundStyle.cs new file mode 100644 index 0000000000..332a55fcbe --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BackgroundStyle.cs @@ -0,0 +1,44 @@ +// 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.Drawing; + +namespace MouseJumpUI.Common.Models.Styles; + +/// +/// Represents the background fill style for a drawing object. +/// +public sealed class BackgroundStyle +{ + public static readonly BackgroundStyle Empty = new( + Color.Transparent, + Color.Transparent + ); + + public BackgroundStyle( + Color? color1, + Color? color2) + { + this.Color1 = color1; + this.Color2 = color2; + } + + public Color? Color1 + { + get; + } + + public Color? Color2 + { + get; + } + + public override string ToString() + { + return "{" + + $"{nameof(this.Color1)}={this.Color1}," + + $"{nameof(this.Color2)}={this.Color2}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BorderStyle.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BorderStyle.cs new file mode 100644 index 0000000000..3c5b870e60 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BorderStyle.cs @@ -0,0 +1,79 @@ +// 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.Drawing; + +namespace MouseJumpUI.Common.Models.Styles; + +/// +/// Represents the border style for a drawing object. +/// +public sealed class BorderStyle +{ + public static readonly BorderStyle Empty = new(Color.Transparent, 0, 0); + + public BorderStyle(Color color, decimal all, decimal depth) + : this(color, all, all, all, all, depth) + { + } + + public BorderStyle(Color color, decimal left, decimal top, decimal right, decimal bottom, decimal depth) + { + this.Color = color; + this.Left = left; + this.Top = top; + this.Right = right; + this.Bottom = bottom; + this.Depth = depth; + } + + public Color Color + { + get; + } + + public decimal Left + { + get; + } + + public decimal Top + { + get; + } + + public decimal Right + { + get; + } + + public decimal Bottom + { + get; + } + + /// + /// Gets the "depth" of the 3d highlight and shadow effect on the border. + /// + public decimal Depth + { + get; + } + + public decimal Horizontal => this.Left + this.Right; + + public decimal Vertical => this.Top + this.Bottom; + + public override string ToString() + { + return "{" + + $"{nameof(this.Color)}={this.Color}," + + $"{nameof(this.Left)}={this.Left}," + + $"{nameof(this.Top)}={this.Top}," + + $"{nameof(this.Right)}={this.Right}," + + $"{nameof(this.Bottom)}={this.Bottom}," + + $"{nameof(this.Depth)}={this.Depth}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BoxStyle.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BoxStyle.cs new file mode 100644 index 0000000000..dac908b05c --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/BoxStyle.cs @@ -0,0 +1,79 @@ +// 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 MouseJumpUI.Common.Models.Styles; + +/// +/// Represents the styles to apply to a simple box-layout based drawing object. +/// +public sealed class BoxStyle +{ + /* + + see https://www.w3schools.com/css/css_boxmodel.asp + + +--------------[bounds]---------------+ + |▒▒▒▒▒▒▒▒▒▒▒▒▒▒[margin]▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒| + |▒▒▓▓▓▓▓▓▓▓▓▓▓▓[border]▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒| + |▒▒▓▓░░░░░░░░░░[padding]░░░░░░░░░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░ [content] ░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░ ░░▓▓▒▒| + |▒▒▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▒▒| + |▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒| + |▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒| + +-------------------------------------+ + + */ + + public static readonly BoxStyle Empty = new(MarginStyle.Empty, BorderStyle.Empty, PaddingStyle.Empty, BackgroundStyle.Empty); + + public BoxStyle( + MarginStyle marginStyle, + BorderStyle borderStyle, + PaddingStyle paddingStyle, + BackgroundStyle backgroundStyle) + { + this.MarginStyle = marginStyle ?? throw new ArgumentNullException(nameof(marginStyle)); + this.BorderStyle = borderStyle ?? throw new ArgumentNullException(nameof(borderStyle)); + this.PaddingStyle = paddingStyle ?? throw new ArgumentNullException(nameof(paddingStyle)); + this.BackgroundStyle = backgroundStyle ?? throw new ArgumentNullException(nameof(backgroundStyle)); + } + + /// + /// Gets the margin style for this layout box. + /// + public MarginStyle MarginStyle + { + get; + } + + /// + /// Gets the border style for this layout box. + /// + public BorderStyle BorderStyle + { + get; + } + + /// + /// Gets the padding style for this layout box. + /// + public PaddingStyle PaddingStyle + { + get; + } + + /// + /// Gets the background fill style for the content area of this layout box. + /// + public BackgroundStyle BackgroundStyle + { + get; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/MarginStyle.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/MarginStyle.cs new file mode 100644 index 0000000000..6dce63f57b --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/MarginStyle.cs @@ -0,0 +1,60 @@ +// 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 MouseJumpUI.Common.Models.Styles; + +/// +/// Represents the margin style for a drawing object. +/// +public sealed class MarginStyle +{ + public static readonly MarginStyle Empty = new(0); + + public MarginStyle(decimal all) + : this(all, all, all, all) + { + } + + public MarginStyle(decimal left, decimal top, decimal right, decimal bottom) + { + this.Left = left; + this.Top = top; + this.Right = right; + this.Bottom = bottom; + } + + public decimal Left + { + get; + } + + public decimal Top + { + get; + } + + public decimal Right + { + get; + } + + public decimal Bottom + { + get; + } + + public decimal Horizontal => this.Left + this.Right; + + public decimal Vertical => this.Top + this.Bottom; + + public override string ToString() + { + return "{" + + $"{nameof(this.Left)}={this.Left}," + + $"{nameof(this.Top)}={this.Top}," + + $"{nameof(this.Right)}={this.Right}," + + $"{nameof(this.Bottom)}={this.Bottom}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/PaddingStyle.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/PaddingStyle.cs new file mode 100644 index 0000000000..6ac7bc6b11 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/PaddingStyle.cs @@ -0,0 +1,60 @@ +// 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 MouseJumpUI.Common.Models.Styles; + +/// +/// Represents the margin style for a drawing object. +/// +public sealed class PaddingStyle +{ + public static readonly PaddingStyle Empty = new(0); + + public PaddingStyle(decimal all) + : this(all, all, all, all) + { + } + + public PaddingStyle(decimal left, decimal top, decimal right, decimal bottom) + { + this.Left = left; + this.Top = top; + this.Right = right; + this.Bottom = bottom; + } + + public decimal Left + { + get; + } + + public decimal Top + { + get; + } + + public decimal Right + { + get; + } + + public decimal Bottom + { + get; + } + + public decimal Horizontal => this.Left + this.Right; + + public decimal Vertical => this.Top + this.Bottom; + + public override string ToString() + { + return "{" + + $"{nameof(this.Left)}={this.Left}," + + $"{nameof(this.Top)}={this.Top}," + + $"{nameof(this.Right)}={this.Right}," + + $"{nameof(this.Bottom)}={this.Bottom}" + + "}"; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/PreviewStyle.cs b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/PreviewStyle.cs new file mode 100644 index 0000000000..10443a4a0e --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Common/Models/Styles/PreviewStyle.cs @@ -0,0 +1,36 @@ +// 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 MouseJumpUI.Common.Models.Drawing; + +namespace MouseJumpUI.Common.Models.Styles; + +public sealed class PreviewStyle +{ + public PreviewStyle( + SizeInfo canvasSize, + BoxStyle canvasStyle, + BoxStyle screenStyle) + { + this.CanvasSize = canvasSize ?? throw new ArgumentNullException(nameof(canvasSize)); + this.CanvasStyle = canvasStyle ?? throw new ArgumentNullException(nameof(canvasStyle)); + this.ScreenStyle = screenStyle ?? throw new ArgumentNullException(nameof(screenStyle)); + } + + public SizeInfo CanvasSize + { + get; + } + + public BoxStyle CanvasStyle + { + get; + } + + public BoxStyle ScreenStyle + { + get; + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/BOOL.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/BOOL.cs similarity index 96% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/BOOL.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/BOOL.cs index 851cb77a46..ec8849d952 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/BOOL.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/BOOL.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/CRECT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/CRECT.cs similarity index 84% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/CRECT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/CRECT.cs index a44da78727..3b02b3bf5e 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/CRECT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/CRECT.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -25,6 +25,15 @@ internal static partial class Core public readonly LONG right; public readonly LONG bottom; + public CRECT( + int left, int top, int right, int bottom) + { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + public CRECT( LONG left, LONG top, LONG right, LONG bottom) { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/DWORD.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/DWORD.cs similarity index 75% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/DWORD.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/DWORD.cs index 9009b63c86..900b5bfb77 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/DWORD.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/DWORD.cs @@ -2,7 +2,9 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +using System.Runtime.InteropServices; + +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -23,10 +25,17 @@ internal static partial class Core this.Value = value; } + public static int Size => + Marshal.SizeOf(typeof(DWORD)); + public static implicit operator uint(DWORD value) => value.Value; public static implicit operator DWORD(uint value) => new(value); + public static explicit operator int(DWORD value) => (int)value.Value; + + public static explicit operator DWORD(int value) => new((uint)value); + public override string ToString() { return $"{this.GetType().Name}({this.Value})"; diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HANDLE.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HANDLE.cs similarity index 90% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HANDLE.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HANDLE.cs index 1df2624ed5..c89132e0be 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HANDLE.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HANDLE.cs @@ -4,7 +4,7 @@ using System; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -31,7 +31,7 @@ internal static partial class Core public static implicit operator IntPtr(HANDLE value) => value.Value; - public static implicit operator HANDLE(IntPtr value) => new(value); + public static explicit operator HANDLE(IntPtr value) => new(value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HDC.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HDC.cs similarity index 82% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HDC.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HDC.cs index b02a811c06..0996030b01 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HDC.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HDC.cs @@ -4,7 +4,7 @@ using System; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -29,6 +29,10 @@ internal static partial class Core public bool IsNull => this.Value == HDC.Null.Value; + public static implicit operator IntPtr(HDC value) => value.Value; + + public static explicit operator HDC(IntPtr value) => new(value); + public override string ToString() { return $"{this.GetType().Name}({this.Value})"; diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HMONITOR.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HMONITOR.cs similarity index 84% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HMONITOR.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HMONITOR.cs index 047c654f6f..5baa77bfc5 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HMONITOR.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HMONITOR.cs @@ -4,7 +4,7 @@ using System; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -31,15 +31,15 @@ internal static partial class Core public static implicit operator int(HMONITOR value) => value.Value.ToInt32(); - public static implicit operator HMONITOR(int value) => new(value); + public static explicit operator HMONITOR(int value) => new(value); public static implicit operator IntPtr(HMONITOR value) => value.Value; - public static implicit operator HMONITOR(IntPtr value) => new(value); + public static explicit operator HMONITOR(IntPtr value) => new(value); public static implicit operator HANDLE(HMONITOR value) => new(value.Value); - public static implicit operator HMONITOR(HANDLE value) => new(value.Value); + public static explicit operator HMONITOR(HANDLE value) => new(value.Value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HWND.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HWND.cs similarity index 75% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HWND.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HWND.cs index e12f2846f3..e421a1ca91 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/HWND.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/HWND.cs @@ -3,8 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -20,6 +21,9 @@ internal static partial class Core { public static readonly HWND Null = new(IntPtr.Zero); + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Name and value taken from Win32Api")] + public static readonly HWND HWND_MESSAGE = new(-3); + public readonly IntPtr Value; public HWND(IntPtr value) diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LONG.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LONG.cs similarity index 95% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LONG.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LONG.cs index 2d2dd82283..7fe4b6cb99 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LONG.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LONG.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPARAM.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPARAM.cs similarity index 84% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPARAM.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPARAM.cs index 6a227f5e21..f214e53a6f 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPARAM.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPARAM.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -26,9 +26,11 @@ internal static partial class Core this.Value = value; } + public bool IsNull => this.Value == LPARAM.Null.Value; + public static implicit operator IntPtr(LPARAM value) => value.Value; - public static implicit operator LPARAM(IntPtr value) => new(value); + public static explicit operator LPARAM(IntPtr value) => new(value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPCRECT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPCRECT.cs similarity index 86% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPCRECT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPCRECT.cs index d19abd9c39..c23dbbfb85 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPCRECT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPCRECT.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -25,6 +25,8 @@ internal static partial class Core this.Value = LPCRECT.ToPtr(value); } + public bool IsNull => this.Value == LPCRECT.Null.Value; + private static IntPtr ToPtr(CRECT value) { var ptr = Marshal.AllocHGlobal(CRECT.Size); @@ -39,7 +41,7 @@ internal static partial class Core public static implicit operator IntPtr(LPCRECT value) => value.Value; - public static implicit operator LPCRECT(IntPtr value) => new(value); + public static explicit operator LPCRECT(IntPtr value) => new(value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPPOINT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPPOINT.cs similarity index 87% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPPOINT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPPOINT.cs index db3423decd..c68d8b336b 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPPOINT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPPOINT.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -25,6 +25,8 @@ internal static partial class Core this.Value = LPPOINT.ToPtr(value); } + public bool IsNull => this.Value == LPPOINT.Null.Value; + private static IntPtr ToPtr(POINT value) { var ptr = Marshal.AllocHGlobal(POINT.Size); @@ -44,7 +46,7 @@ internal static partial class Core public static implicit operator IntPtr(LPPOINT value) => value.Value; - public static implicit operator LPPOINT(IntPtr value) => new(value); + public static explicit operator LPPOINT(IntPtr value) => new(value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPRECT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPRECT.cs similarity index 86% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPRECT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPRECT.cs index 087e22abe3..66f22da181 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/LPRECT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/LPRECT.cs @@ -4,7 +4,7 @@ using System; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -24,6 +24,8 @@ internal static partial class Core this.Value = LPRECT.ToPtr(value); } + public bool IsNull => this.Value == LPRECT.Null.Value; + private static IntPtr ToPtr(RECT value) { var ptr = Marshal.AllocHGlobal(RECT.Size); @@ -38,7 +40,7 @@ internal static partial class Core public static implicit operator IntPtr(LPRECT value) => value.Value; - public static implicit operator LPRECT(IntPtr value) => new(value); + public static explicit operator LPRECT(IntPtr value) => new(value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/POINT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/POINT.cs similarity index 88% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/POINT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/POINT.cs index d4513d6740..6077174ce6 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/POINT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/POINT.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -28,6 +28,14 @@ internal static partial class Core /// public readonly LONG y; + public POINT( + int x, + int y) + { + this.x = x; + this.y = y; + } + public POINT( LONG x, LONG y) diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/RECT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/RECT.cs similarity index 84% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/RECT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/RECT.cs index ff107f6ad8..084616cc77 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/RECT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/RECT.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -25,6 +25,15 @@ internal static partial class Core public readonly LONG right; public readonly LONG bottom; + public RECT( + int left, int top, int right, int bottom) + { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + public RECT( LONG left, LONG top, LONG right, LONG bottom) { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/UINT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/UINT.cs similarity index 82% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/UINT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/UINT.cs index e135212ec9..d2ce9a2e51 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/UINT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/UINT.cs @@ -1,7 +1,8 @@ // 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 MouseJumpUI.NativeMethods; + +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -26,6 +27,10 @@ internal static partial class Core public static implicit operator UINT(uint value) => new(value); + public static explicit operator int(UINT value) => (int)value.Value; + + public static explicit operator UINT(int value) => new((uint)value); + public override string ToString() { return $"{this.GetType().Name}({this.Value})"; diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/ULONG_PTR.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/ULONG_PTR.cs similarity index 91% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/ULONG_PTR.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/ULONG_PTR.cs index 76d99d88be..ed26092cb1 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/ULONG_PTR.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/ULONG_PTR.cs @@ -4,7 +4,7 @@ using System; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { @@ -34,7 +34,7 @@ internal static partial class Core public static implicit operator UIntPtr(ULONG_PTR value) => value.Value; - public static implicit operator ULONG_PTR(UIntPtr value) => new(value); + public static explicit operator ULONG_PTR(UIntPtr value) => new(value); public override string ToString() { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/WORD.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/WORD.cs similarity index 95% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/WORD.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/WORD.cs index 642d0b0514..987b532f29 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Core/WORD.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Core/WORD.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Core { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.ROP_CODE.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.ROP_CODE.cs similarity index 96% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.ROP_CODE.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.ROP_CODE.cs index 994eeb7165..1cb5261967 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.ROP_CODE.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.ROP_CODE.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Gdi32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.STRETCH_BLT_MODE.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.STRETCH_BLT_MODE.cs similarity index 94% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.STRETCH_BLT_MODE.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.STRETCH_BLT_MODE.cs index 575e092982..f1417941d3 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.STRETCH_BLT_MODE.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.STRETCH_BLT_MODE.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Gdi32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.SetStretchBltMode.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.SetStretchBltMode.cs similarity index 90% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.SetStretchBltMode.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.SetStretchBltMode.cs index 410af308d4..014efd3f5b 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.SetStretchBltMode.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.SetStretchBltMode.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Gdi32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.StretchBlt.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.StretchBlt.cs similarity index 92% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.StretchBlt.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.StretchBlt.cs index 345391d666..97cb7a941b 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.StretchBlt.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Gdi32/Graphics/Gdi/Gdi32.StretchBlt.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class Gdi32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Libraries.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Libraries.cs similarity index 87% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/Libraries.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Libraries.cs index 59947db8fd..e648f9b01b 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/Libraries.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/Libraries.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static class Libraries { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.EnumDisplayMonitors.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.EnumDisplayMonitors.cs similarity index 93% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.EnumDisplayMonitors.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.EnumDisplayMonitors.cs index 6d5a0687bb..9002973536 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.EnumDisplayMonitors.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.EnumDisplayMonitors.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.GetMonitorInfoW.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.GetMonitorInfoW.cs similarity index 89% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.GetMonitorInfoW.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.GetMonitorInfoW.cs index ad55845cf9..be50c5c8ea 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.GetMonitorInfoW.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.GetMonitorInfoW.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.GetWindowDC.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.GetWindowDC.cs similarity index 93% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.GetWindowDC.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.GetWindowDC.cs index ea0b79db9b..33afc19c59 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.GetWindowDC.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.GetWindowDC.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.LPMONITORINFO.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.LPMONITORINFO.cs similarity index 96% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.LPMONITORINFO.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.LPMONITORINFO.cs index f44a2c7972..3896962d71 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.LPMONITORINFO.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.LPMONITORINFO.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITORENUMPROC .cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITORENUMPROC .cs similarity index 87% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITORENUMPROC .cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITORENUMPROC .cs index 0e366c8163..169bf3e567 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITORENUMPROC .cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITORENUMPROC .cs @@ -2,9 +2,9 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITORINFO.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITORINFO.cs similarity index 90% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITORINFO.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITORINFO.cs index 24fb270938..750222cd43 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITORINFO.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITORINFO.cs @@ -4,9 +4,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { @@ -34,6 +34,6 @@ internal static partial class User32 } public static int Size => - Marshal.SizeOf(typeof(INPUT)); + Marshal.SizeOf(typeof(MONITORINFO)); } } diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_FROM_FLAGS.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_FROM_FLAGS.cs similarity index 95% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_FROM_FLAGS.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_FROM_FLAGS.cs index 5d5fc3b035..c566193874 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_FROM_FLAGS.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_FROM_FLAGS.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; [SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")] internal static partial class User32 diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_INFO_FLAGS.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_INFO_FLAGS.cs similarity index 94% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_INFO_FLAGS.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_INFO_FLAGS.cs index f20e6a2f58..8bfa14eb55 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_INFO_FLAGS.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MONITOR_INFO_FLAGS.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; [SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")] internal static partial class User32 diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MonitorFromPoint .cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MonitorFromPoint .cs similarity index 90% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MonitorFromPoint .cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MonitorFromPoint .cs index 93878544e5..86ed2c2efc 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.MonitorFromPoint .cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.MonitorFromPoint .cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.ReleaseDC.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.ReleaseDC.cs similarity index 91% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.ReleaseDC.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.ReleaseDC.cs index a6f6caa355..f7c7db68ed 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/Graphics/Gdi/User32.ReleaseDC.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/Graphics/Gdi/User32.ReleaseDC.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.HARDWAREINPUT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.HARDWAREINPUT.cs similarity index 92% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.HARDWAREINPUT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.HARDWAREINPUT.cs index 254b60eecd..2e98098916 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.HARDWAREINPUT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.HARDWAREINPUT.cs @@ -4,9 +4,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT.cs similarity index 94% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT.cs index 1a4bcf8546..247b7eb4d5 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT.cs @@ -4,9 +4,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT_TYPE.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT_TYPE.cs similarity index 94% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT_TYPE.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT_TYPE.cs index 89e8f60218..b065fdfc43 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT_TYPE.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.INPUT_TYPE.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; [SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")] internal static partial class User32 diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.KEYBDINPUT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.KEYBDINPUT.cs similarity index 93% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.KEYBDINPUT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.KEYBDINPUT.cs index a5c9bc782b..f7d472e329 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.KEYBDINPUT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.KEYBDINPUT.cs @@ -4,9 +4,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.LPINPUT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.LPINPUT.cs similarity index 94% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.LPINPUT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.LPINPUT.cs index ff689a5931..e803fe3d85 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.LPINPUT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.LPINPUT.cs @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { @@ -52,7 +52,7 @@ internal static partial class User32 var size = INPUT.Size; foreach (var value in values) { - Marshal.StructureToPtr(value, ptr, true); + Marshal.StructureToPtr(value, ptr, false); ptr += size; } diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSEINPUT.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSEINPUT.cs similarity index 93% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSEINPUT.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSEINPUT.cs index 80f9bbe2fc..dc2ee4707e 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSEINPUT.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSEINPUT.cs @@ -4,9 +4,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSE_EVENT_FLAGS.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSE_EVENT_FLAGS.cs similarity index 96% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSE_EVENT_FLAGS.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSE_EVENT_FLAGS.cs index b6f3cba549..6bfe6d3bbe 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSE_EVENT_FLAGS.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.MOUSE_EVENT_FLAGS.cs @@ -5,7 +5,7 @@ using System; using System.Diagnostics.CodeAnalysis; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; [SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")] internal static partial class User32 diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.SendInput.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.SendInput.cs similarity index 91% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.SendInput.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.SendInput.cs index b7bfba63bf..d1dd0b620a 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.SendInput.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/Input/KeyboardAndMouse/User32.SendInput.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetCursorPos.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetCursorPos.cs similarity index 89% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetCursorPos.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetCursorPos.cs index f6cbfa75bc..e2f9021c15 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetCursorPos.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetCursorPos.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetDesktopWindow.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetDesktopWindow.cs similarity index 89% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetDesktopWindow.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetDesktopWindow.cs index 4d16b95e15..7172fa0788 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetDesktopWindow.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetDesktopWindow.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetSystemMetrics.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetSystemMetrics.cs similarity index 95% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetSystemMetrics.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetSystemMetrics.cs index 7791c22963..0865d5af3d 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetSystemMetrics.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.GetSystemMetrics.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs similarity index 98% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs index b2ca6b3b68..5891c53551 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; [SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")] internal static partial class User32 diff --git a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SetCursorPos.cs b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SetCursorPos.cs similarity index 91% rename from src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SetCursorPos.cs rename to src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SetCursorPos.cs index b548148925..ee400474e3 100644 --- a/src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SetCursorPos.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SetCursorPos.cs @@ -3,9 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Runtime.InteropServices; -using static MouseJumpUI.NativeMethods.Core; +using static MouseJumpUI.Common.NativeMethods.Core; -namespace MouseJumpUI.NativeMethods; +namespace MouseJumpUI.Common.NativeMethods; internal static partial class User32 { diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/DrawingHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Helpers/DrawingHelper.cs deleted file mode 100644 index 225ed68b2d..0000000000 --- a/src/modules/MouseUtils/MouseJumpUI/Helpers/DrawingHelper.cs +++ /dev/null @@ -1,184 +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.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Linq; -using MouseJumpUI.Models.Drawing; -using MouseJumpUI.NativeMethods; -using static MouseJumpUI.NativeMethods.Core; - -namespace MouseJumpUI.Helpers; - -internal static class DrawingHelper -{ - /// - /// Draw the gradient-filled preview background. - /// - public static void DrawPreviewBackground( - Graphics previewGraphics, RectangleInfo previewBounds, IEnumerable screenBounds) - { - using var backgroundBrush = new LinearGradientBrush( - previewBounds.Location.ToPoint(), - previewBounds.Size.ToPoint(), - Color.FromArgb(13, 87, 210), // light blue - Color.FromArgb(3, 68, 192)); // darker blue - - // it's faster to build a region with the screen areas excluded - // and fill that than it is to fill the entire bounding rectangle - var backgroundRegion = new Region(previewBounds.ToRectangle()); - foreach (var screen in screenBounds) - { - backgroundRegion.Exclude(screen.ToRectangle()); - } - - previewGraphics.FillRegion(backgroundBrush, backgroundRegion); - } - - public static void EnsureDesktopDeviceContext(ref HWND desktopHwnd, ref HDC desktopHdc) - { - if (desktopHwnd.IsNull) - { - desktopHwnd = User32.GetDesktopWindow(); - } - - if (desktopHdc.IsNull) - { - desktopHdc = User32.GetWindowDC(desktopHwnd); - if (desktopHdc.IsNull) - { - throw new InvalidOperationException( - $"{nameof(User32.GetWindowDC)} returned null"); - } - } - } - - public static void FreeDesktopDeviceContext(ref HWND desktopHwnd, ref HDC desktopHdc) - { - if (!desktopHwnd.IsNull && !desktopHdc.IsNull) - { - var result = User32.ReleaseDC(desktopHwnd, desktopHdc); - if (result == 0) - { - throw new InvalidOperationException( - $"{nameof(User32.ReleaseDC)} returned {result}"); - } - } - - desktopHwnd = HWND.Null; - desktopHdc = HDC.Null; - } - - /// - /// Checks if the device context handle exists, and creates a new one from the - /// specified Graphics object if not. - /// - public static void EnsurePreviewDeviceContext(Graphics previewGraphics, ref HDC previewHdc) - { - if (previewHdc.IsNull) - { - previewHdc = new HDC(previewGraphics.GetHdc()); - var result = Gdi32.SetStretchBltMode(previewHdc, Gdi32.STRETCH_BLT_MODE.STRETCH_HALFTONE); - - if (result == 0) - { - throw new InvalidOperationException( - $"{nameof(Gdi32.SetStretchBltMode)} returned {result}"); - } - } - } - - /// - /// Free the specified device context handle if it exists. - /// - public static void FreePreviewDeviceContext(Graphics previewGraphics, ref HDC previewHdc) - { - if ((previewGraphics is not null) && !previewHdc.IsNull) - { - previewGraphics.ReleaseHdc(previewHdc.Value); - previewHdc = HDC.Null; - } - } - - /// - /// Draw placeholder images for any non-activated screens on the preview. - /// Will release the specified device context handle if it needs to draw anything. - /// - public static void DrawPreviewScreenPlaceholders( - Graphics previewGraphics, IEnumerable screenBounds) - { - // we can exclude the activated screen because we've already draw - // the screen capture image for that one on the preview - if (screenBounds.Any()) - { - var brush = Brushes.Black; - previewGraphics.FillRectangles(brush, screenBounds.Select(screen => screen.ToRectangle()).ToArray()); - } - } - - /// - /// Draws a screen capture from the specified desktop handle onto the target device context. - /// - public static void DrawPreviewScreen( - HDC sourceHdc, - HDC targetHdc, - RectangleInfo sourceBounds, - RectangleInfo targetBounds) - { - var source = sourceBounds.ToRectangle(); - var target = targetBounds.ToRectangle(); - var result = Gdi32.StretchBlt( - targetHdc, - target.X, - target.Y, - target.Width, - target.Height, - sourceHdc, - source.X, - source.Y, - source.Width, - source.Height, - Gdi32.ROP_CODE.SRCCOPY); - if (!result) - { - throw new InvalidOperationException( - $"{nameof(Gdi32.StretchBlt)} returned {result.Value}"); - } - } - - /// - /// Draws screen captures from the specified desktop handle onto the target device context. - /// - public static void DrawPreviewScreens( - HDC sourceHdc, - HDC targetHdc, - IList sourceBounds, - IList targetBounds) - { - for (var i = 0; i < sourceBounds.Count; i++) - { - var source = sourceBounds[i].ToRectangle(); - var target = targetBounds[i].ToRectangle(); - var result = Gdi32.StretchBlt( - targetHdc, - target.X, - target.Y, - target.Width, - target.Height, - sourceHdc, - source.X, - source.Y, - source.Width, - source.Height, - Gdi32.ROP_CODE.SRCCOPY); - if (!result) - { - throw new InvalidOperationException( - $"{nameof(Gdi32.StretchBlt)} returned {result.Value}"); - } - } - } -} diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/LayoutHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Helpers/LayoutHelper.cs deleted file mode 100644 index 025bba1d6e..0000000000 --- a/src/modules/MouseUtils/MouseJumpUI/Helpers/LayoutHelper.cs +++ /dev/null @@ -1,89 +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.Drawing; -using System.Linq; -using System.Windows.Forms; -using MouseJumpUI.Models.Drawing; -using MouseJumpUI.Models.Layout; - -namespace MouseJumpUI.Helpers; - -internal static class LayoutHelper -{ - public static LayoutInfo CalculateLayoutInfo( - LayoutConfig layoutConfig) - { - ArgumentNullException.ThrowIfNull(layoutConfig); - - var builder = new LayoutInfo.Builder - { - LayoutConfig = layoutConfig, - }; - - builder.ActivatedScreenBounds = layoutConfig.Screens[layoutConfig.ActivatedScreenIndex].Bounds; - - // work out the maximum *constrained* form size - // * can't be bigger than the activated screen - // * can't be bigger than the max form size - var maxFormSize = builder.ActivatedScreenBounds.Size - .Intersect(layoutConfig.MaximumFormSize); - - // the drawing area for screen images is inside the - // form border and inside the preview border - var maxDrawingSize = maxFormSize - .Shrink(layoutConfig.FormPadding) - .Shrink(layoutConfig.PreviewPadding); - - // scale the virtual screen to fit inside the drawing bounds - var scalingRatio = layoutConfig.VirtualScreenBounds.Size - .ScaleToFitRatio(maxDrawingSize); - - // position the drawing bounds inside the preview border - var drawingBounds = layoutConfig.VirtualScreenBounds.Size - .ScaleToFit(maxDrawingSize) - .PlaceAt(layoutConfig.PreviewPadding.Left, layoutConfig.PreviewPadding.Top); - - // now we know the size of the drawing area we can work out the preview size - builder.PreviewBounds = drawingBounds.Enlarge(layoutConfig.PreviewPadding); - - // ... and the form size - // * center the form to the activated position, but nudge it back - // inside the visible area of the activated screen if it falls outside - builder.FormBounds = builder.PreviewBounds - .Enlarge(layoutConfig.FormPadding) - .Center(layoutConfig.ActivatedLocation) - .Clamp(builder.ActivatedScreenBounds); - - // now calculate the positions of each of the screen images on the preview - builder.ScreenBounds = layoutConfig.Screens - .Select( - screen => screen.Bounds - .Offset(layoutConfig.VirtualScreenBounds.Location.ToSize().Negate()) - .Scale(scalingRatio) - .Offset(layoutConfig.PreviewPadding.Left, layoutConfig.PreviewPadding.Top)) - .ToList(); - - return builder.Build(); - } - - /// - /// Resize and position the specified form. - /// - public static void PositionForm( - Form form, RectangleInfo formBounds) - { - // note - do this in two steps rather than "this.Bounds = formBounds" as there - // appears to be an issue in WinForms with dpi scaling even when using PerMonitorV2, - // where the form scaling uses either the *primary* screen scaling or the *previous* - // screen's scaling when the form is moved to a different screen. i've got no idea - // *why*, but the exact sequence of calls below seems to be a workaround... - // see https://github.com/mikeclayton/FancyMouse/issues/2 - var bounds = formBounds.ToRectangle(); - form.Location = bounds.Location; - _ = form.PointToScreen(Point.Empty); - form.Size = bounds.Size; - } -} diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/StyleHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Helpers/StyleHelper.cs new file mode 100644 index 0000000000..bc9ff705d0 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Helpers/StyleHelper.cs @@ -0,0 +1,103 @@ +// 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.Drawing; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Styles; + +namespace MouseJumpUI.Helpers; + +internal static class StyleHelper +{ + /// + /// Default v2 preview style + /// + public static readonly PreviewStyle DefaultPreviewStyle = new( + canvasSize: new( + width: 1600, + height: 1200 + ), + canvasStyle: new( + marginStyle: MarginStyle.Empty, + borderStyle: new( + color: SystemColors.Highlight, + all: 6, + depth: 0 + ), + paddingStyle: new( + all: 4 + ), + backgroundStyle: new( + color1: Color.FromArgb(0xFF, 0x0D, 0x57, 0xD2), + color2: Color.FromArgb(0xFF, 0x03, 0x44, 0xC0) + ) + ), + screenStyle: new( + marginStyle: new( + all: 4 + ), + borderStyle: new( + color: Color.FromArgb(0xFF, 0x22, 0x22, 0x22), + all: 12, + depth: 4 + ), + paddingStyle: PaddingStyle.Empty, + backgroundStyle: new( + color1: Color.MidnightBlue, + color2: Color.MidnightBlue + ) + ) + ); + + /// + /// Legacy preview style + /// + public static readonly PreviewStyle LegacyPreviewStyle = new( + canvasSize: new( + width: 1600, + height: 1200 + ), + canvasStyle: new( + marginStyle: MarginStyle.Empty, + borderStyle: new( + color: SystemColors.Highlight, + all: 6, + depth: 0 + ), + paddingStyle: new( + all: 0 + ), + backgroundStyle: new( + color1: Color.FromArgb(0xFF, 0x0D, 0x57, 0xD2), + color2: Color.FromArgb(0xFF, 0x03, 0x44, 0xC0) + ) + ), + screenStyle: new( + marginStyle: new( + all: 0 + ), + borderStyle: new( + color: Color.FromArgb(0xFF, 0x22, 0x22, 0x22), + all: 0, + depth: 0 + ), + paddingStyle: PaddingStyle.Empty, + backgroundStyle: new( + color1: Color.MidnightBlue, + color2: Color.MidnightBlue + ) + ) + ); + + public static PreviewStyle WithCanvasSize(this PreviewStyle previewStyle, SizeInfo canvasSize) + { + ArgumentNullException.ThrowIfNull(previewStyle); + ArgumentNullException.ThrowIfNull(canvasSize); + return new PreviewStyle( + canvasSize: canvasSize, + canvasStyle: previewStyle.CanvasStyle, + screenStyle: previewStyle.ScreenStyle); + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/MainForm.Designer.cs b/src/modules/MouseUtils/MouseJumpUI/MainForm.Designer.cs index 8a8fa9110a..6300590e73 100644 --- a/src/modules/MouseUtils/MouseJumpUI/MainForm.Designer.cs +++ b/src/modules/MouseUtils/MouseJumpUI/MainForm.Designer.cs @@ -49,7 +49,6 @@ partial class MainForm panel1.Dock = DockStyle.Fill; panel1.Location = new System.Drawing.Point(0, 0); panel1.Name = "panel1"; - panel1.Padding = new Padding(5); panel1.Size = new System.Drawing.Size(800, 450); panel1.TabIndex = 1; // @@ -59,7 +58,7 @@ partial class MainForm Thumbnail.Dock = DockStyle.Fill; Thumbnail.Location = new System.Drawing.Point(5, 5); Thumbnail.Name = "Thumbnail"; - Thumbnail.Size = new System.Drawing.Size(790, 440); + Thumbnail.Size = new System.Drawing.Size(800, 450); Thumbnail.SizeMode = PictureBoxSizeMode.Normal; Thumbnail.TabIndex = 1; Thumbnail.TabStop = false; diff --git a/src/modules/MouseUtils/MouseJumpUI/MainForm.cs b/src/modules/MouseUtils/MouseJumpUI/MainForm.cs index 2b055aa862..546571d1ab 100644 --- a/src/modules/MouseUtils/MouseJumpUI/MainForm.cs +++ b/src/modules/MouseUtils/MouseJumpUI/MainForm.cs @@ -5,15 +5,14 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Drawing.Imaging; using System.Linq; using System.Windows.Forms; using ManagedCommon; -using Microsoft.PowerToys.Settings.UI.Library; +using MouseJumpUI.Common.Helpers; +using MouseJumpUI.Common.Imaging; +using MouseJumpUI.Common.Models.Drawing; +using MouseJumpUI.Common.Models.Layout; using MouseJumpUI.Helpers; -using MouseJumpUI.Models.Drawing; -using MouseJumpUI.Models.Layout; -using static MouseJumpUI.NativeMethods.Core; namespace MouseJumpUI; @@ -25,6 +24,12 @@ internal partial class MainForm : Form this.SettingsHelper = settingsHelper ?? throw new ArgumentNullException(nameof(settingsHelper)); } + private PreviewLayout? PreviewLayout + { + get; + set; + } + public SettingsHelper SettingsHelper { get; @@ -42,61 +47,68 @@ internal partial class MainForm : Form return; } - // map screens to their screen number in "System > Display" - var screens = ScreenHelper.GetAllScreens() - .Select((screen, index) => new { Screen = screen, Index = index, Number = index + 1 }) - .ToList(); + var screens = ScreenHelper.GetAllScreens().ToList(); + if (screens.Count == 0) + { + return; + } var currentLocation = MouseHelper.GetCursorPosition(); - var currentScreenHandle = ScreenHelper.MonitorFromPoint(currentLocation); - var currentScreen = screens - .Single(item => item.Screen.Handle == currentScreenHandle.Value); - var targetScreenNumber = default(int?); + var currentScreen = ScreenHelper.GetScreenFromPoint(screens, currentLocation); + var currentScreenIndex = screens.IndexOf(currentScreen); + var targetScreen = default(ScreenInfo?); - if (((e.KeyCode >= Keys.D1) && (e.KeyCode <= Keys.D9)) - || ((e.KeyCode >= Keys.NumPad1) && (e.KeyCode <= Keys.NumPad9))) + switch (e.KeyCode) { - // number keys 1-9 or numpad keys 1-9 - move to the numbered screen - var screenNumber = e.KeyCode - Keys.D0; - if (screenNumber <= screens.Count) - { - targetScreenNumber = screenNumber; - } - } - else if (e.KeyCode == Keys.P) - { - // "P" - move to the primary screen - targetScreenNumber = screens.Single(item => item.Screen.Primary).Number; - } - else if (e.KeyCode == Keys.Left) - { - // move to the previous screen - targetScreenNumber = currentScreen.Number == 1 - ? screens.Count - : currentScreen.Number - 1; - } - else if (e.KeyCode == Keys.Right) - { - // move to the next screen - targetScreenNumber = currentScreen.Number == screens.Count - ? 1 - : currentScreen.Number + 1; - } - else if (e.KeyCode == Keys.Home) - { - // move to the first screen - targetScreenNumber = 1; - } - else if (e.KeyCode == Keys.End) - { - // move to the last screen - targetScreenNumber = screens.Count; + case >= Keys.D1 and <= Keys.D9: + { + // number keys 1-9 - move to the numbered screen + var screenNumber = e.KeyCode - Keys.D0; + /* note - screen *numbers* are 1-based, screen *indexes* are 0-based */ + targetScreen = (screenNumber <= screens.Count) + ? targetScreen = screens[screenNumber - 1] + : null; + break; + } + + case >= Keys.NumPad1 and <= Keys.NumPad9: + { + // numpad keys 1-9 - move to the numbered screen + var screenNumber = e.KeyCode - Keys.NumPad0; + /* note - screen *numbers* are 1-based, screen *indexes* are 0-based */ + targetScreen = (screenNumber <= screens.Count) + ? targetScreen = screens[screenNumber - 1] + : null; + break; + } + + case Keys.P: + // "P" - move to the primary screen + targetScreen = screens.Single(screen => screen.Primary); + break; + case Keys.Left: + // move to the previous screen, looping back to the end if needed + var prevIndex = (currentScreenIndex - 1 + screens.Count) % screens.Count; + targetScreen = screens[prevIndex]; + break; + case Keys.Right: + // move to the next screen, looping round to the start if needed + var nextIndex = (currentScreenIndex + 1) % screens.Count; + targetScreen = screens[nextIndex]; + break; + case Keys.Home: + // move to the first screen + targetScreen = screens.First(); + break; + case Keys.End: + // move to the last screen + targetScreen = screens.Last(); + break; } - if (targetScreenNumber.HasValue) + if (targetScreen is not null) { - MouseHelper.SetCursorPosition( - screens[targetScreenNumber.Value - 1].Screen.Bounds.Midpoint); + MouseHelper.SetCursorPosition(targetScreen.DisplayArea.Midpoint); this.OnDeactivate(EventArgs.Empty); } } @@ -118,15 +130,42 @@ internal partial class MainForm : Form if (mouseEventArgs.Button == MouseButtons.Left) { - // plain click - move mouse pointer - var virtualScreen = ScreenHelper.GetVirtualScreen(); - var scaledLocation = MouseHelper.GetJumpLocation( - new PointInfo(mouseEventArgs.X, mouseEventArgs.Y), - new SizeInfo(this.Thumbnail.Size), - virtualScreen); - Logger.LogInfo($"scaled location = {scaledLocation}"); - MouseHelper.SetCursorPosition(scaledLocation); - Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent()); + if (this.PreviewLayout is null) + { + // there's no layout data so we can't work out what screen was clicked + return; + } + + // work out which screenshot was clicked + var clickedScreenshot = this.PreviewLayout.ScreenshotBounds + .FirstOrDefault( + box => box.BorderBounds.Contains(mouseEventArgs.X, mouseEventArgs.Y)); + if (clickedScreenshot is null) + { + return; + } + + // scale up the click onto the physical screen - the aspect ratio of the screenshot + // might be distorted compared to the physical screen due to the borders around the + // screenshot, so we need to work out the target location on the physical screen first + var clickedScreen = + this.PreviewLayout.Screens[this.PreviewLayout.ScreenshotBounds.IndexOf(clickedScreenshot)]; + var clickedLocation = new PointInfo(mouseEventArgs.Location) + .Stretch( + source: clickedScreenshot.ContentBounds, + target: clickedScreen) + .Clamp( + new( + x: clickedScreen.X + 1, + y: clickedScreen.Y + 1, + width: clickedScreen.Width - 1, + height: clickedScreen.Height - 1 + )) + .Truncate(); + + // move mouse pointer + Logger.LogInfo($"clicked location = {clickedLocation}"); + MouseHelper.SetCursorPosition(clickedLocation); } this.OnDeactivate(EventArgs.Empty); @@ -138,162 +177,34 @@ internal partial class MainForm : Form this.Visible = false; var stopwatch = Stopwatch.StartNew(); - var layoutInfo = MainForm.GetLayoutInfo(this); - LayoutHelper.PositionForm(this, layoutInfo.FormBounds); - MainForm.RenderPreview(this, layoutInfo); + + var appSettings = this.SettingsHelper.CurrentSettings ?? throw new InvalidOperationException(); + var screens = ScreenHelper.GetAllScreens().Select(screen => screen.DisplayArea).ToList(); + var activatedLocation = MouseHelper.GetCursorPosition(); + this.PreviewLayout = LayoutHelper.GetPreviewLayout( + previewStyle: StyleHelper.LegacyPreviewStyle.WithCanvasSize( + new( + appSettings.Properties.ThumbnailSize.Width, + appSettings.Properties.ThumbnailSize.Height + )), + screens: screens, + activatedLocation: activatedLocation); + + this.PositionForm(this.PreviewLayout.FormBounds); + + var imageCopyService = new DesktopImageRegionCopyService(); + DrawingHelper.RenderPreview( + this.PreviewLayout, + imageCopyService, + this.OnPreviewImageCreated, + this.OnPreviewImageUpdated); + stopwatch.Stop(); // we have to activate the form to make sure the deactivate event fires - Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpShowEvent()); this.Activate(); } - private static LayoutInfo GetLayoutInfo(MainForm form) - { - // map screens to their screen number in "System > Display" - var screens = ScreenHelper.GetAllScreens() - .Select((screen, index) => new { Screen = screen, Index = index, Number = index + 1 }) - .ToList(); - foreach (var screen in screens) - { - Logger.LogInfo(string.Join( - '\n', - $"screen[{screen.Number}]", - $"\tprimary = {screen.Screen.Primary}", - $"\tdisplay area = {screen.Screen.DisplayArea}", - $"\tworking area = {screen.Screen.WorkingArea}")); - } - - // collect together some values that we need for calculating layout - var activatedLocation = MouseHelper.GetCursorPosition(); - var activatedScreenHandle = ScreenHelper.MonitorFromPoint(activatedLocation); - var activatedScreenIndex = screens - .Single(item => item.Screen.Handle == activatedScreenHandle.Value) - .Index; - - // avoid a race condition - cache the current settings in case they change - var currentSettings = form.SettingsHelper.CurrentSettings; - - var layoutConfig = new LayoutConfig( - virtualScreenBounds: ScreenHelper.GetVirtualScreen(), - screens: screens.Select(item => item.Screen).ToList(), - activatedLocation: activatedLocation, - activatedScreenIndex: activatedScreenIndex, - activatedScreenNumber: activatedScreenIndex + 1, - maximumFormSize: new( - currentSettings.Properties.ThumbnailSize.Width, - currentSettings.Properties.ThumbnailSize.Height), - /* - don't read the panel padding values because they are affected by dpi scaling - and can give wrong values when moving between monitors with different dpi scaling - */ - formPadding: new(5, 5, 5, 5), - previewPadding: new(0)); - Logger.LogInfo(string.Join( - '\n', - $"Layout config", - $"-------------", - $"virtual screen = {layoutConfig.VirtualScreenBounds}", - $"activated location = {layoutConfig.ActivatedLocation}", - $"activated screen index = {layoutConfig.ActivatedScreenIndex}", - $"activated screen number = {layoutConfig.ActivatedScreenNumber}", - $"maximum form size = {layoutConfig.MaximumFormSize}", - $"form padding = {layoutConfig.FormPadding}", - $"preview padding = {layoutConfig.PreviewPadding}")); - - // calculate the layout coordinates for everything - var layoutInfo = LayoutHelper.CalculateLayoutInfo(layoutConfig); - Logger.LogInfo(string.Join( - '\n', - $"Layout info", - $"-----------", - $"form bounds = {layoutInfo.FormBounds}", - $"preview bounds = {layoutInfo.PreviewBounds}", - $"activated screen = {layoutInfo.ActivatedScreenBounds}")); - - return layoutInfo; - } - - private static void RenderPreview( - MainForm form, LayoutInfo layoutInfo) - { - var layoutConfig = layoutInfo.LayoutConfig; - - var stopwatch = Stopwatch.StartNew(); - - // initialize the preview image - var preview = new Bitmap( - (int)layoutInfo.PreviewBounds.Width, - (int)layoutInfo.PreviewBounds.Height, - PixelFormat.Format32bppArgb); - form.Thumbnail.Image = preview; - - using var previewGraphics = Graphics.FromImage(preview); - - DrawingHelper.DrawPreviewBackground(previewGraphics, layoutInfo.PreviewBounds, layoutInfo.ScreenBounds); - - var desktopHwnd = HWND.Null; - var desktopHdc = HDC.Null; - var previewHdc = HDC.Null; - try - { - // sort the source and target screen areas, putting the activated screen first - // (we need to capture and draw the activated screen before we show the form - // because otherwise we'll capture the form as part of the screenshot!) - var sourceScreens = layoutConfig.Screens - .Where((_, idx) => idx == layoutConfig.ActivatedScreenIndex) - .Union(layoutConfig.Screens.Where((_, idx) => idx != layoutConfig.ActivatedScreenIndex)) - .Select(screen => screen.Bounds) - .ToList(); - var targetScreens = layoutInfo.ScreenBounds - .Where((_, idx) => idx == layoutConfig.ActivatedScreenIndex) - .Union(layoutInfo.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreenIndex)) - .ToList(); - - DrawingHelper.EnsureDesktopDeviceContext(ref desktopHwnd, ref desktopHdc); - DrawingHelper.EnsurePreviewDeviceContext(previewGraphics, ref previewHdc); - - var placeholdersDrawn = false; - for (var i = 0; i < sourceScreens.Count; i++) - { - DrawingHelper.DrawPreviewScreen( - desktopHdc, previewHdc, sourceScreens[i], targetScreens[i]); - - // show the placeholder images and show the form if it looks like it might take - // a while to capture the remaining screenshot images (but only if there are any) - if ((i < (sourceScreens.Count - 1)) && (stopwatch.ElapsedMilliseconds > 250)) - { - // we need to release the device context handle before we draw the placeholders - // using the Graphics object otherwise we'll get an error from GDI saying - // "Object is currently in use elsewhere" - DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc); - - if (!placeholdersDrawn) - { - // draw placeholders for any undrawn screens - DrawingHelper.DrawPreviewScreenPlaceholders( - previewGraphics, - targetScreens.Where((_, idx) => idx > i)); - placeholdersDrawn = true; - } - - MainForm.RefreshPreview(form); - - // we've still got more screens to draw so open the device context again - DrawingHelper.EnsurePreviewDeviceContext(previewGraphics, ref previewHdc); - } - } - } - finally - { - DrawingHelper.FreeDesktopDeviceContext(ref desktopHwnd, ref desktopHdc); - DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc); - } - - MainForm.RefreshPreview(form); - stopwatch.Stop(); - } - private void ClearPreview() { if (this.Thumbnail.Image is null) @@ -310,17 +221,40 @@ internal partial class MainForm : Form GC.Collect(); } - private static void RefreshPreview(MainForm form) + /// + /// Resize and position the specified form. + /// + private void PositionForm(RectangleInfo bounds) { - if (!form.Visible) + // note - do this in two steps rather than "this.Bounds = formBounds" as there + // appears to be an issue in WinForms with dpi scaling even when using PerMonitorV2, + // where the form scaling uses either the *primary* screen scaling or the *previous* + // screen's scaling when the form is moved to a different screen. i've got no idea + // *why*, but the exact sequence of calls below seems to be a workaround... + // see https://github.com/mikeclayton/FancyMouse/issues/2 + var rect = bounds.ToRectangle(); + this.Location = rect.Location; + _ = this.PointToScreen(Point.Empty); + this.Size = rect.Size; + } + + private void OnPreviewImageCreated(Bitmap preview) + { + this.ClearPreview(); + this.Thumbnail.Image = preview; + } + + private void OnPreviewImageUpdated() + { + if (!this.Visible) { // we seem to need to turn off topmost and then re-enable it again - // when we show the form, otherwise it doesn't get shown topmost... - form.TopMost = false; - form.TopMost = true; - form.Show(); + // when we show the form, otherwise it doesn't always get shown topmost... + this.TopMost = false; + this.TopMost = true; + this.Show(); } - form.Thumbnail.Refresh(); + this.Thumbnail.Refresh(); } } diff --git a/src/modules/MouseUtils/MouseJumpUI/MainForm.resx b/src/modules/MouseUtils/MouseJumpUI/MainForm.resx index e33d2ec3e8..4df95b5495 100644 --- a/src/modules/MouseUtils/MouseJumpUI/MainForm.resx +++ b/src/modules/MouseUtils/MouseJumpUI/MainForm.resx @@ -1,4 +1,64 @@ - + + + diff --git a/src/modules/MouseWithoutBorders/App/Form/frmScreen.Designer.cs b/src/modules/MouseWithoutBorders/App/Form/frmScreen.Designer.cs index 7588122f0b..8b8b2376c6 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmScreen.Designer.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmScreen.Designer.cs @@ -54,7 +54,6 @@ this.dUCTDOToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.tRUONG2DToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.NotifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.imgListIcon = new System.Windows.Forms.ImageList(this.components); this.picLogonLogo = new System.Windows.Forms.PictureBox(); this.MainMenu.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.picLogonLogo)).BeginInit(); @@ -228,12 +227,6 @@ this.NotifyIcon.Text = "Microsoft® Visual Studio® 2010"; this.NotifyIcon.MouseDown += new System.Windows.Forms.MouseEventHandler(this.NotifyIcon_MouseDown); // - // imgListIcon - // - this.imgListIcon.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imgListIcon.ImageStream"))); - this.imgListIcon.TransparentColor = System.Drawing.Color.Transparent; - this.imgListIcon.Images.SetKeyName(0, "Logo.ico"); - // // picLogonLogo // this.picLogonLogo.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; @@ -286,7 +279,6 @@ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2; private System.Windows.Forms.ToolStripMenuItem dUCTDOToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem tRUONG2DToolStripMenuItem; - private System.Windows.Forms.ImageList imgListIcon; private System.Windows.Forms.ToolStripMenuItem menuSendScreenCapture; private System.Windows.Forms.ToolStripMenuItem menuSend2Myself; private System.Windows.Forms.ToolStripMenuItem menuGetScreenCapture; diff --git a/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs b/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs index 8decdebe35..d0d541126b 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs +++ b/src/modules/MouseWithoutBorders/App/Form/frmScreen.cs @@ -21,7 +21,7 @@ using Microsoft.PowerToys.Telemetry; // 2023- Included in PowerToys. // using MouseWithoutBorders.Class; -using MouseWithoutBorders.Form; +using MouseWithoutBorders.Properties; using Timer = System.Windows.Forms.Timer; [module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.frmScreen.#ShowMouseWithoutBordersUiOnWinLogonDesktop(System.Boolean)", Justification = "Dotnet port with style preservation")] @@ -617,7 +617,7 @@ namespace MouseWithoutBorders { Graphics g; Pen p; - Bitmap bm = new(imgListIcon.Images[0]); + Bitmap bm = Images.notify_default; /* if (curIcon == Common.ICON_ONE) diff --git a/src/modules/MouseWithoutBorders/App/Form/frmScreen.resx b/src/modules/MouseWithoutBorders/App/Form/frmScreen.resx index 076454b8c3..fa54f646ac 100644 --- a/src/modules/MouseWithoutBorders/App/Form/frmScreen.resx +++ b/src/modules/MouseWithoutBorders/App/Form/frmScreen.resx @@ -196,48 +196,6 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAA//8AAP//AAD//wAA7/8AAMfnAADjzwAA8Z8AAAAAAAABAAAA848AAOfHAADJJwAA+T8AAP// AAD//wAA//8AAA== - - - - 233, 17 - - - - AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w - LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 - ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAABW - BwAAAk1TRnQBSQFMAwEBAAHEAQABxAEAASABAAEgAQAE/wEZAQAI/wFCAU0BNgcAATYDAAEoAwABgAMA - ASADAAEBAQABGAYAATD/AP8A/wD/AP8A/wD/AP8A/wAwAAESAY0B9jAAARIBjQH2/wBIAAESAY0B9gEU - AYsB9wESAY0B9ioAARIBjQH2AREBiwH3ARIBjQH2/wBCAAESAY0B9gEEAYcB9wEYAZAB9gESAYsB9wES - AY0B9iQAARIBjQH2AQkBiQH3ARkBkAH2AQwBhwH4ARIBjQH2/wBCAAFBAZsB9AEUAY4B9gEaAZEB9gEU - AYsB9wESAY0B9h4AARIBjQH2AQoBiQH3ARwBkQH2ARQBjgH2AUABmwH0/wBIAAE0AZYB9QEUAY4B9gEZ - AZAB9gEeAY8B9gESAY0B9hgAARIBjQH2ARIBjQH2ARsBkQH2ARQBjgH2ATQBlgH1/wBOAAE+AZoB9AEW - AY4B9gEZAZAB9gEOAYkB9wESAY0B9hIAARIBjQH2AQQBiAH3ARwBkQH2ARYBjgH2AT4BmgH0/wBUAAE6 - AZkB9AEWAY8B9gEZAZAB9gEgAY8B9gESAY0B9gwAARIBjQH2ARMBjQH2ARwBkQH2ARYBjwH2AToBmQH0 - /wBaAAEiAY8B9gEcAZEB9gEZAZAB9gEWAYwB9wESAY0B9gYAARIBjQH2AQoBigH3ARwBkQH2ARwBkQH2 - ASIBjwH2/wA/AAFRAZ4B8wEDAYcB9wEKAYkB9wEKAYkB9wEKAYkB9wEKAYkB9wEKAYkB9wEKAYkB9wEK - AYkB9wENAYsB9wEcAZEB9gEeAZIB9gEdAZEB9gEZAZAB9gEYAYwB9gESAY0B9gESAY0B9gELAYsB9wEc - AZEB9gEeAZIB9gEeAZIB9gEcAZEB9gENAYsB9wEKAYkB9wEKAYkB9wEKAYkB9wEKAYkB9wEKAYkB9wEK - AYkB9wEKAYkB9wEAAYUB+AESAY0B9v8AIQABWgGiAfMBDwGNAfYBFgGPAfYBFgGPAfYBFgGPAfYBFgGP - AfYBFgGPAfYBFgGPAfYBFgGPAfYBFwGPAfYBHQGRAfYBHgGSAfYBHgGSAfYBHQGRAfYBEQGNAfYBOwGY - AfQBNgGXAfQBFAGOAfYBHQGRAfYBHgGSAfYBHgGSAfYBHAGRAfYBFgGPAfYBFgGPAfYBFgGPAfYBFgGP - AfYBFgGPAfYBFgGPAfYBFgGPAfYBFgGPAfYBCwGLAfcBEgGNAfb/ACEAAX4BswHvAUkBowHyAU4BpAHy - AU4BpAHyAU4BpAHyAU4BpAHyAU4BpAHyAU4BpAHyAU8BpQHyAVcBqAHxASABkQH2AR0BkQH2AR4BkgH2 - ARIBjQH2AToBmwH0BgABLAGUAfYBFQGPAfYBHgGSAfYBHAGRAfYBKwGUAfUBVwGpAfEBTwGkAfIBTgGk - AfIBTgGkAfIBTgGkAfIBTgGkAfIBTgGkAfIBTgGkAfIBRgGhAfMBEgGNAfb/ADwAAR0BkQH2ARkBjgH2 - AR0BkQH2ARIBjQH2ATgBmgH0DAABKQGTAfUBFQGPAfYBGwGRAfYBIwGRAfYBEgGNAfb/AFQAAR0BkQH2 - AR4BjwH2ARoBkQH2ARIBjQH2AT0BnAH0EgABLwGWAfUBFQGPAfYBGAGQAfYBKAGUAfYBEgGNAfb/AE4A - AR0BkQH2ARsBjgH2ARkBkAH2ARMBjQH2ATYBmQH0GAABKAGSAfUBFgGPAfYBFgGPAfYBJwGTAfYBEgGN - Afb/AEgAAR0BkQH2AR4BkAH2ARgBkAH2ARQBjgH2ATEBmAH1HgABJQGSAfYBFgGPAfYBFgGPAfYBKgGU - AfUBEgGNAfb/AEIAAR0BkQH2ARQBiwH3ARkBkAH2ARMBjgH2ATEBlwH1BgAGBAwABgQGAAElAZIB9gEW - AY8B9gEWAY8B9gEfAY8B9wESAY0B9v8APwABEgGNAfYBHwGQAfYBDgGMAfYBMwGYAfQGAAwEBgAMBAYA - ASYBkgH1AQ8BjAH2AScBlAH1ARIBjQH2/wBCAAESAY0B9gE0AZgB9QkAAwQB/wH4AfAGBAYAAwQB/wH4 - AfAGBAkAATABlgH1ARIBjQH2/wBUAAwEBgAMBP8AZgAGBAwABgT/AP8A/wD/AP8A/wD/AP8A/wD/AE4A - AUIBTQE+BwABPgMAASgDAAGAAwABIAMAAQEBAAEBBgABAhYAA/8BAAT/DAAE/wwABP8MAAT/DAAE/wwA - BP8MAAH+Av8BfwwAAfwBfwH+AT8MAAH4AT8B/AEfDAAB/AEfAfgBPwwAAf4BDwHwAX8MAAH/AQcB4AH/ - DAAB/wGDAcEB/wwAAf8BwQGDAf8tAAEBAYANAAH/AYMBwQH/DAAB/wEHAeAB/wwAAf4BDwHwAX8MAAH8 - AR8B+AE/DAAB+AEzAcwBHwwAAfgBYQGGAR8MAAH8AeEBhwE/DAAB/wHhAYcB/wwAAf8B8wHPAf8MAAT/ - DAAE/wwABP8MAAT/DAAE/wwABP8MAAs= diff --git a/src/modules/MouseWithoutBorders/App/Icon/notify_default.bmp b/src/modules/MouseWithoutBorders/App/Icon/notify_default.bmp new file mode 100644 index 0000000000..f0ce85ee2a Binary files /dev/null and b/src/modules/MouseWithoutBorders/App/Icon/notify_default.bmp differ diff --git a/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj b/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj index 9e922c28ad..af242f40a8 100644 --- a/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj +++ b/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj @@ -103,6 +103,11 @@ UserControl + + True + True + Images.resx + @@ -243,6 +248,12 @@ + + + ResXFileCodeGenerator + Images.Designer.cs + + True diff --git a/src/modules/MouseWithoutBorders/App/Properties/Images.Designer.cs b/src/modules/MouseWithoutBorders/App/Properties/Images.Designer.cs index 1625d54a5c..07eabb49a5 100644 --- a/src/modules/MouseWithoutBorders/App/Properties/Images.Designer.cs +++ b/src/modules/MouseWithoutBorders/App/Properties/Images.Designer.cs @@ -1,4 +1,4 @@ - +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -6,7 +6,7 @@ // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // - +//------------------------------------------------------------------------------ namespace MouseWithoutBorders.Properties { using System; @@ -600,6 +600,16 @@ namespace MouseWithoutBorders.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap notify_default { + get { + object obj = ResourceManager.GetObject("notify_default", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/src/modules/MouseWithoutBorders/App/Properties/Images.resx b/src/modules/MouseWithoutBorders/App/Properties/Images.resx index 91dad667f0..07184ecae0 100644 --- a/src/modules/MouseWithoutBorders/App/Properties/Images.resx +++ b/src/modules/MouseWithoutBorders/App/Properties/Images.resx @@ -112,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -355,4 +355,7 @@ ..\Icon\two_row_button_unchecked.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Icon\notify_default.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs index 172e9230a4..905e4e6fb9 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.Designer.cs @@ -88,7 +88,7 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter.Properties { } /// - /// Looks up a localized string similar to Provides unit conversion (e.g. 10 ft in m).. + /// Looks up a localized string similar to Provides unit conversion (e.g. 10 ft to m). /// public static string plugin_description { get { diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx index 22e07ab650..0dfaceba8f 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Properties/Resources.resx @@ -127,7 +127,7 @@ Copy {0} to clipboard - Provides unit conversion (e.g. 10 ft in m). + Provides unit conversion (e.g. 10 ft to m) Unit Converter diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs index dd24288924..9f7d4d920f 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs @@ -70,7 +70,7 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.Properties { } /// - /// Looks up a localized string similar to Opens workspaces, remote machines (SSH or Codespaces) and containers, previously opened in VS Code.. + /// Looks up a localized string similar to Opens workspaces, remote machines (SSH or Codespaces) and containers, previously opened in VS Code. The VS Code instance needs to be in PATH. /// internal static string PluginDescription { get { diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx index ca9d176229..01778a0beb 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx @@ -125,7 +125,7 @@ Used to indicate the location where something is - Opens workspaces, remote machines (SSH or Codespaces) and containers, previously opened in VS Code. The VS Code instance needs to be in PATH. + Opens workspaces, remote machines (SSH or Codespaces) and containers, previously opened in VS Code. The VS Code instance needs to be in PATH VS Code is the name of a product. PATH is the name of an environment variable. Don't translate it. diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs index 38ed677a3e..9cb1595c27 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.Designer.cs @@ -88,7 +88,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.Properties { } /// - /// Looks up a localized string similar to Calculates hashes and generate values.. + /// Looks up a localized string similar to Calculates hashes and generate values. /// public static string plugin_description { get { diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx index 98d4a99b05..d7ae6bd97c 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Properties/Resources.resx @@ -127,7 +127,7 @@ Value Generator Error - Calculates hashes and generate values. + Calculates hashes and generate values Value Generator diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.Designer.cs index df09aada0b..39d6c51687 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.Designer.cs @@ -70,7 +70,7 @@ namespace Community.PowerToys.Run.Plugin.WebSearch.Properties { } /// - /// Looks up a localized string similar to Searches the web with your default search engine.. + /// Looks up a localized string similar to Searches the web with your default search engine. /// public static string plugin_description { get { diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.resx index de27cbb170..8fe0ce02ee 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Properties/Resources.resx @@ -121,7 +121,7 @@ the default browser - Searches the web with your default search engine. + Searches the web with your default search engine Don't include in global results on queries that are URIs diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.Designer.cs index 23679b9dd1..49fba811e0 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.Designer.cs @@ -124,7 +124,7 @@ namespace Microsoft.Plugin.Folder.Properties { } /// - /// Looks up a localized string similar to Navigates folders starting from a drive letter 'C:\' or from the user home '~'.. + /// Looks up a localized string similar to Navigates folders starting from a drive letter 'C:\' or from the user home '~'. /// public static string wox_plugin_folder_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.resx index f1e7342702..4e83c3de11 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Properties/Resources.resx @@ -121,7 +121,7 @@ Folder - Navigates folders starting from a drive letter 'C:\' or from the user home '~'. + Navigates folders starting from a drive letter 'C:\' or from the user home '~' Copy path (Ctrl+C) diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.Designer.cs index 89d1827311..322cc0bdd4 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.Designer.cs @@ -187,7 +187,7 @@ namespace Microsoft.Plugin.Indexer.Properties { } /// - /// Looks up a localized string similar to Searches files and folders.. + /// Looks up a localized string similar to Searches files and folders. /// public static string Microsoft_plugin_indexer_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.resx index 5eb1a3d401..6ab653c1b2 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Properties/Resources.resx @@ -151,7 +151,7 @@ Fail to open folder at - Searches files and folders. + Searches files and folders Windows Search diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs index 5c0d6ea10d..30396e537a 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs @@ -130,9 +130,12 @@ namespace Microsoft.Plugin.Program public void UpdateUWPIconPath(Theme theme) { - foreach (UWPApplication app in _packageRepository) + if (_packageRepository != null) { - app.UpdateLogoPath(theme); + foreach (UWPApplication app in _packageRepository) + { + app.UpdateLogoPath(theme); + } } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.Designer.cs index cea5c273fd..7a92f4a64d 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.Designer.cs @@ -178,7 +178,7 @@ namespace Microsoft.Plugin.Program.Properties { } /// - /// Looks up a localized string similar to Searches programs.. + /// Looks up a localized string similar to Searches programs. /// public static string wox_plugin_program_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.resx index 47a518699e..cde47f270f 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Properties/Resources.resx @@ -130,7 +130,7 @@ Program - Searches programs. + Searches programs Application diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs index 3505b1576e..5eb3028781 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs @@ -178,7 +178,7 @@ namespace Microsoft.Plugin.Shell.Properties { } /// - /// Looks up a localized string similar to Executes commands (e.g. 'ping', 'cmd').. + /// Looks up a localized string similar to Executes commands (e.g. 'ping', 'cmd'). /// public static string wox_plugin_cmd_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx index dc72f33069..24838722a8 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx @@ -121,7 +121,7 @@ Shell - Executes commands (e.g. 'ping', 'cmd'). + Executes commands (e.g. 'ping', 'cmd') this command has been executed {0} times diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs index 0d073767a1..90499c0ebf 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.Plugin.Uri.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -97,7 +97,7 @@ namespace Microsoft.Plugin.Uri.Properties { } /// - /// Looks up a localized string similar to Opens URLs and UNC network shares.. + /// Looks up a localized string similar to Opens URLs and UNC network shares. /// public static string Microsoft_plugin_uri_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx index 19c0a39031..39222c4c45 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx @@ -130,7 +130,7 @@ Failed to open URI - Opens URLs and UNC network shares. + Opens URLs and UNC network shares URI Handler diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ResultHelper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ResultHelper.cs index 827ea910ab..17a9ef4e19 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ResultHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/ResultHelper.cs @@ -92,6 +92,11 @@ namespace Microsoft.Plugin.WindowWalker.Components subtitleText += $" ({window.Process.ProcessID})"; } + if (!window.Process.IsResponding) + { + subtitleText += $" [{Resources.wox_plugin_windowwalker_NotResponding}]"; + } + if (WindowWalkerSettings.Instance.SubtitleShowDesktopName && Main.VirtualDesktopHelperInstance.GetDesktopCount() > 1) { subtitleText += $" - {Resources.wox_plugin_windowwalker_Desktop}: {window.Desktop.Name}"; @@ -148,7 +153,8 @@ namespace Microsoft.Plugin.WindowWalker.Components $"Desktop number: {window.Desktop.Number}\n" + $"Desktop is visible: {window.Desktop.IsVisible}\n" + $"Desktop position: {window.Desktop.Position}\n" + - $"Is AllDesktops view: {window.Desktop.IsAllDesktopsView}"; + $"Is AllDesktops view: {window.Desktop.IsAllDesktopsView}\n" + + $"Responding: {window.Process.IsResponding}"; return new ToolTipData(window.Title, text); } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs index 28ca9e062a..6d467598c4 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; +using System.Threading; using System.Threading.Tasks; using Wox.Plugin.Common.VirtualDesktop.Helper; using Wox.Plugin.Common.Win32; @@ -234,12 +235,21 @@ namespace Microsoft.Plugin.WindowWalker.Components NativeMethods.FlashWindow(Hwnd, true); } + /// + /// Helper function to close the window + /// + internal void CloseThisWindowHelper() + { + _ = NativeMethods.SendMessageTimeout(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE, 0, 0x0000, 5000, out _); + } + /// /// Closes the window /// internal void CloseThisWindow() { - _ = NativeMethods.SendMessage(Hwnd, Win32Constants.WM_SYSCOMMAND, Win32Constants.SC_CLOSE); + Thread thread = new(new ThreadStart(CloseThisWindowHelper)); + thread.Start(); } /// diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs index b7bf00e962..ade997f93b 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/WindowProcess.cs @@ -33,6 +33,30 @@ namespace Microsoft.Plugin.WindowWalker.Components get; private set; } + /// + /// Gets a value indicating whether the process is responding or not + /// + internal bool IsResponding + { + get + { + try + { + return Process.GetProcessById((int)ProcessID).Responding; + } + catch (InvalidOperationException) + { + // Thrown when process not exist. + return true; + } + catch (NotSupportedException) + { + // Thrown when process is not running locally. + return true; + } + } + } + /// /// Gets the id of the thread /// diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.Designer.cs index 024368b158..a5c8f20f5e 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.Designer.cs @@ -79,7 +79,7 @@ namespace Microsoft.Plugin.WindowWalker.Properties { } /// - /// Looks up a localized string similar to Folder windows doesn't run in separate processes. (Click to open Explorer properties.). + /// Looks up a localized string similar to Folder windows do not run in separate processes. (Click to open Explorer properties.). /// public static string wox_plugin_windowwalker_ExplorerInfoSubTitle { get { @@ -141,6 +141,15 @@ namespace Microsoft.Plugin.WindowWalker.Properties { } } + /// + /// Looks up a localized string similar to Not Responding. + /// + public static string wox_plugin_windowwalker_NotResponding { + get { + return ResourceManager.GetString("wox_plugin_windowwalker_NotResponding", resourceCulture); + } + } + /// /// Looks up a localized string similar to No.. /// @@ -151,7 +160,7 @@ namespace Microsoft.Plugin.WindowWalker.Properties { } /// - /// Looks up a localized string similar to Switches between open windows.. + /// Looks up a localized string similar to Switches between open windows. /// public static string wox_plugin_windowwalker_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.resx index 6b0499343a..90c63aff6d 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Properties/Resources.resx @@ -121,7 +121,7 @@ Window Walker - Switches between open windows. + Switches between open windows Running @@ -202,4 +202,7 @@ This information is only shown in subtitle and tool tip, if you have at least two desktops. - + + Not Responding + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.Designer.cs index d772c9c367..b48a3f417e 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.Designer.cs @@ -169,7 +169,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.Properties { } /// - /// Looks up a localized string similar to Calculates mathematical equations (e.g. 5*3-2).. + /// Looks up a localized string similar to Calculates mathematical equations (e.g. 5*3-2). /// public static string wox_plugin_calculator_plugin_description { get { @@ -187,7 +187,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.Properties { } /// - /// Looks up a localized string similar to Replace input appending '='. + /// Looks up a localized string similar to Replace input if query ends with '='. /// public static string wox_plugin_calculator_replace_input { get { @@ -196,7 +196,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator.Properties { } /// - /// Looks up a localized string similar to When using direct activation, appending '=' to the expression will replace the input with the calculated result (e.g. =5*3-2=).. + /// Looks up a localized string similar to When using direct activation, appending '=' to the expression will replace the input with the calculated result (e.g. '=5*3-2=' will change the query to '=13').. /// public static string wox_plugin_calculator_replace_input_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.resx index 538b135c94..727603ab04 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Properties/Resources.resx @@ -121,7 +121,7 @@ Calculator - Calculates mathematical equations (e.g. 5*3-2). + Calculates mathematical equations (e.g. 5*3-2) Calculation result is not a valid number (NaN) diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.Designer.cs index 9f56f4ee43..efbdaf92f0 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.Designer.cs @@ -61,7 +61,7 @@ namespace Microsoft.PowerToys.Run.Plugin.History.Properties { } /// - /// Looks up a localized string similar to Accesses previously selected results.. + /// Looks up a localized string similar to Accesses previously selected results. /// public static string wox_plugin_history_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.resx index 5bf6726694..1a9bfe4dea 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Properties/Resources.resx @@ -121,7 +121,7 @@ History - Accesses previously selected results. + Accesses previously selected results Failed to process the input diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.Designer.cs index 464ef14e3f..f8a9265376 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.Designer.cs @@ -61,7 +61,7 @@ namespace Microsoft.PowerToys.Run.Plugin.OneNote.Properties { } /// - /// Looks up a localized string similar to Searches your local OneNote notebooks. This plugin requires the OneNote desktop app which is included in Microsoft Office.. + /// Looks up a localized string similar to Searches your local OneNote notebooks. This plugin requires the OneNote desktop app which is included in Microsoft Office. /// internal static string PluginDescription { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.resx index b4ebe604c1..0419897f3d 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Properties/Resources.resx @@ -121,6 +121,6 @@ OneNote - Searches your local OneNote notebooks. This plugin requires the OneNote desktop app which is included in Microsoft Office. + Searches your local OneNote notebooks. This plugin requires the OneNote desktop app which is included in Microsoft Office \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.Designer.cs index 2e4bc8f0dc..5c33feea53 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.Designer.cs @@ -133,7 +133,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Properties { } /// - /// Looks up a localized string similar to Opens PowerToys utilities and settings.. + /// Looks up a localized string similar to Opens PowerToys utilities and settings. /// internal static string Plugin_Description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.resx index f45c7813bc..9a6bdf335f 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Properties/Resources.resx @@ -152,7 +152,7 @@ "Hosts File Editor" is the name of the utility - Opens PowerToys utilities and settings. + Opens PowerToys utilities and settings PowerToys diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs index 27a74cc215..e14c717f02 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Registry.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -133,7 +133,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Registry.Properties { } /// - /// Looks up a localized string similar to Navigates inside the Windows Registry.. + /// Looks up a localized string similar to Navigates inside the Windows Registry. /// internal static string PluginDescription { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx index fb5892c916..e79b49ccd6 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Properties/Resources.resx @@ -121,7 +121,7 @@ Registry Plugin - Navigates inside the Windows Registry. + Navigates inside the Windows Registry "this built into Windows the OS. translate accordingly, https://learn.microsoft.com/troubleshoot/windows-server/performance/windows-registry-advanced-users is an example of it translated in German" diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.Designer.cs index 6380120ff3..7db377ab1c 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.Designer.cs @@ -106,7 +106,7 @@ namespace Microsoft.PowerToys.Run.Plugin.Service.Properties { } /// - /// Looks up a localized string similar to Manages Windows services.. + /// Looks up a localized string similar to Manages Windows services. /// internal static string wox_plugin_service_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.resx index f5a899fd13..3a2854f9e3 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Properties/Resources.resx @@ -133,7 +133,7 @@ Pausing - Manages Windows services. + Manages Windows services Service diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs index 21edf832d4..5d07afd41f 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs @@ -367,7 +367,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Properties { } /// - /// Looks up a localized string similar to Executes system commands (e.g. 'shutdown', 'lock', 'sleep').. + /// Looks up a localized string similar to Executes system commands (e.g. 'shutdown', 'lock', 'sleep'). /// internal static string Microsoft_plugin_sys_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx index c762257889..eeaa8a423b 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx @@ -233,7 +233,7 @@ Physical address (MAC) - Executes system commands (e.g. 'shutdown', 'lock', 'sleep'). + Executes system commands (e.g. 'shutdown', 'lock', 'sleep') This should align to the actions in Windows relating to shutting down, signing out, locking, sleeping, restarting, emptying the recycle bin, and hibernating your computer. diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs index 69689b7980..ae496cd95c 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs @@ -295,7 +295,7 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate.Properties { } /// - /// Looks up a localized string similar to Provides time and date values for the system time or a custom time stamp (Examples: '{0}', '{1}', '{2}', '{3}'). + /// Looks up a localized string similar to Provides time and date values for the system time or a custom time stamp (e.g.'{0}', '{1}', '{2}', '{3}'). /// internal static string Microsoft_plugin_timedate_plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx index acdb4aa3f4..c78b2bb108 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx @@ -202,7 +202,7 @@ 'UTC' means here 'Universal Time Convention' - Provides time and date values for the system time or a custom time stamp (Examples: '{0}', '{1}', '{2}', '{3}') + Provides time and date values for the system time or a custom time stamp (e.g.'{0}', '{1}', '{2}', '{3}') Do not translate the placeholders like '{0}' because it will be replaced in code. diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs index 61df500f17..e1bcfb7c1b 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs @@ -88,7 +88,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Properties { } /// - /// Looks up a localized string similar to Opens Windows Terminal profiles.. + /// Looks up a localized string similar to Opens Windows Terminal profiles. /// internal static string plugin_description { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx index 14fd94486e..2ed41dc811 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx @@ -129,7 +129,7 @@ Quake is a well-known computer game. Don't localize. See https://en.wikipedia.org/wiki/Quake_(video_game) - Opens Windows Terminal profiles. + Opens Windows Terminal profiles Windows Terminal diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml b/src/modules/launcher/PowerLauncher/MainWindow.xaml index 384560c7c9..9ae9a70cf3 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml @@ -45,6 +45,11 @@ Grid.Row="0" Padding="12,4,12,3"> + + + + + diff --git a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj index 90c91ea27d..f2e9674823 100644 --- a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj +++ b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj @@ -7,7 +7,7 @@ 10.0.19041.0 10.0.19041.0 true - true + False PowerLauncher.App Assets\PowerLauncher\RunResource.ico app.manifest diff --git a/src/modules/launcher/PowerLauncher/app.manifest b/src/modules/launcher/PowerLauncher/app.manifest index 52d1c39327..fb9b15e291 100644 --- a/src/modules/launcher/PowerLauncher/app.manifest +++ b/src/modules/launcher/PowerLauncher/app.manifest @@ -49,13 +49,13 @@ 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. --> - diff --git a/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs b/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs index 140a6c37b8..0c2c76e8f0 100644 --- a/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs +++ b/src/modules/launcher/Wox.Plugin/Common/Win32/NativeMethods.cs @@ -81,6 +81,9 @@ namespace Wox.Plugin.Common.Win32 [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int msg, int wParam); + [DllImport("user32.dll")] + public static extern int SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam, int fuFlags, int uTimeout, out int lpdwResult); + [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); diff --git a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs index 2c3931c218..763ba94532 100644 --- a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs +++ b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs @@ -3,12 +3,14 @@ // See the LICENSE file in the project root for more information. using System; +using System.Runtime.InteropServices; using Peek.Common.Models; using Peek.UI.Extensions; using SHDocVw; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.Shell; +using Windows.Win32.UI.WindowsAndMessaging; using IServiceProvider = Peek.Common.Models.IServiceProvider; namespace Peek.UI.Helpers @@ -27,7 +29,12 @@ namespace Peek.UI.Helpers private static IShellItemArray? GetItemsInternal(HWND foregroundWindowHandle, bool onlySelectedFiles) { - if (foregroundWindowHandle.IsDesktopWindow()) + // If the caret is visible, we assume the user is typing and we don't want to interfere with that + if (CaretVisible(foregroundWindowHandle)) + { + return null; + } + else if (foregroundWindowHandle.IsDesktopWindow()) { return GetItemsFromDesktop(foregroundWindowHandle, onlySelectedFiles); } @@ -104,5 +111,21 @@ namespace Peek.UI.Helpers return null; } + + /// + /// Returns whether the caret is visible in the specified window. + /// + private static bool CaretVisible(HWND hwnd) + { + GUITHREADINFO guiThreadInfo = new() { cbSize = (uint)Marshal.SizeOf() }; + + // Get information for the foreground thread + if (PInvoke.GetGUIThreadInfo(0, ref guiThreadInfo)) + { + return guiThreadInfo.hwndActive == hwnd && (guiThreadInfo.flags & GUITHREADINFO_FLAGS.GUI_CARETBLINKING) != 0; + } + + return false; + } } } diff --git a/src/modules/peek/Peek.UI/NativeMethods.txt b/src/modules/peek/Peek.UI/NativeMethods.txt index e65e72c12f..102eaa1ebd 100644 --- a/src/modules/peek/Peek.UI/NativeMethods.txt +++ b/src/modules/peek/Peek.UI/NativeMethods.txt @@ -19,4 +19,5 @@ GetWindowRect SET_WINDOW_POS_FLAGS SetWindowPos GetWindowPlacement -SetWindowPlacement \ No newline at end of file +SetWindowPlacement +GetGUIThreadInfo \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.Core/Languages.cs b/src/modules/poweraccent/PowerAccent.Core/Languages.cs index ccac28aa5c..bf799fb7ea 100644 --- a/src/modules/poweraccent/PowerAccent.Core/Languages.cs +++ b/src/modules/poweraccent/PowerAccent.Core/Languages.cs @@ -11,6 +11,7 @@ namespace PowerAccent.Core { ALL, CA, + CRH, CUR, CY, CZ, @@ -27,6 +28,7 @@ namespace PowerAccent.Core HE, HU, IS, + IPA, IT, KU, LT, @@ -54,6 +56,7 @@ namespace PowerAccent.Core { Language.ALL => GetDefaultLetterKeyALL(letter), // ALL Language.CA => GetDefaultLetterKeyCA(letter), // Catalan + Language.CRH => GetDefaultLetterKeyCRH(letter), // Crimean Tatar Language.CUR => GetDefaultLetterKeyCUR(letter), // Currency Language.CY => GetDefaultLetterKeyCY(letter), // Welsh Language.CZ => GetDefaultLetterKeyCZ(letter), // Czech @@ -70,6 +73,7 @@ namespace PowerAccent.Core Language.HE => GetDefaultLetterKeyHE(letter), // Hebrew Language.HU => GetDefaultLetterKeyHU(letter), // Hungarian Language.IS => GetDefaultLetterKeyIS(letter), // Iceland + Language.IPA => GetDefaultLetterKeyIPA(letter), // IPA (International phonetic alphabet) Language.IT => GetDefaultLetterKeyIT(letter), // Italian Language.KU => GetDefaultLetterKeyKU(letter), // Kurdish Language.LT => GetDefaultLetterKeyLT(letter), // Lithuanian @@ -100,6 +104,7 @@ namespace PowerAccent.Core if (!_allLanguagesCache.TryGetValue(letter, out string[] cachedValue)) { cachedValue = GetDefaultLetterKeyCA(letter) + .Union(GetDefaultLetterKeyCRH(letter)) .Union(GetDefaultLetterKeyCUR(letter)) .Union(GetDefaultLetterKeyCY(letter)) .Union(GetDefaultLetterKeyCZ(letter)) @@ -116,6 +121,7 @@ namespace PowerAccent.Core .Union(GetDefaultLetterKeyHE(letter)) .Union(GetDefaultLetterKeyHU(letter)) .Union(GetDefaultLetterKeyIS(letter)) + .Union(GetDefaultLetterKeyIPA(letter)) .Union(GetDefaultLetterKeyIT(letter)) .Union(GetDefaultLetterKeyKU(letter)) .Union(GetDefaultLetterKeyLT(letter)) @@ -168,7 +174,7 @@ namespace PowerAccent.Core LetterKey.VK_K => new[] { "ķ", "ǩ" }, LetterKey.VK_L => new[] { "ļ", "₺" }, // ₺ is in VK_T for other languages, but not VK_L, so we add it here. LetterKey.VK_M => new[] { "ṁ" }, - LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ℕ" }, + LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ℕ", "№" }, LetterKey.VK_O => new[] { "ȯ", "∅" }, LetterKey.VK_P => new[] { "ṗ", "℗", "∏", "¶" }, LetterKey.VK_Q => new[] { "ℚ" }, @@ -182,7 +188,7 @@ namespace PowerAccent.Core LetterKey.VK_Y => new[] { "ẏ", "ꝡ" }, LetterKey.VK_Z => new[] { "ʒ", "ǯ", "ℤ" }, LetterKey.VK_COMMA => new[] { "∙", "₋", "⁻", "–", "√" }, // – is in VK_MINUS for other languages, but not VK_COMMA, so we add it here. - LetterKey.VK_PERIOD => new[] { "…", "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0308", "\u030C" }, + LetterKey.VK_PERIOD => new[] { "…", "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0308", "\u030B", "\u030C" }, LetterKey.VK_MINUS => new[] { "~", "‐", "‑", "‒", "—", "―", "⁓", "−", "⸺", "⸻", "∓" }, LetterKey.VK_SLASH_ => new[] { "÷", "√" }, LetterKey.VK_DIVIDE_ => new[] { "÷", "√" }, @@ -192,6 +198,26 @@ namespace PowerAccent.Core }; } + // Crimean Tatar + private static string[] GetDefaultLetterKeyCRH(LetterKey letter) + { + return letter switch + { + LetterKey.VK_A => new[] { "â" }, + LetterKey.VK_C => new[] { "ç" }, + LetterKey.VK_E => new[] { "€" }, + LetterKey.VK_G => new[] { "ğ" }, + LetterKey.VK_H => new[] { "₴" }, + LetterKey.VK_I => new[] { "ı", "İ" }, + LetterKey.VK_N => new[] { "ñ" }, + LetterKey.VK_O => new[] { "ö" }, + LetterKey.VK_S => new[] { "ş" }, + LetterKey.VK_T => new[] { "₺" }, + LetterKey.VK_U => new[] { "ü" }, + _ => Array.Empty(), + }; + } + // Currencies (source: https://www.eurochange.co.uk/travel-money/world-currency-abbreviations-symbols-and-codes-travel-money) private static string[] GetDefaultLetterKeyCUR(LetterKey letter) { @@ -769,5 +795,33 @@ namespace PowerAccent.Core _ => Array.Empty(), }; } + + // IPA (International Phonetic Alphabet) + private static string[] GetDefaultLetterKeyIPA(LetterKey letter) + { + return letter switch + { + LetterKey.VK_A => new[] { "ɐ", "ɑ", "ɒ" }, + LetterKey.VK_B => new[] { "ʙ" }, + LetterKey.VK_E => new[] { "ɘ", "ɵ", "ə", "ɛ", "ɜ", "ɞ" }, + LetterKey.VK_F => new[] { "ɟ", "ɸ" }, + LetterKey.VK_G => new[] { "ɢ", "ɣ" }, + LetterKey.VK_H => new[] { "ɦ", "ʜ" }, + LetterKey.VK_I => new[] { "ɨ", "ɪ" }, + LetterKey.VK_J => new[] { "ʝ" }, + LetterKey.VK_L => new[] { "ɬ", "ɮ", "ꞎ", "ɭ", "ʎ", "ʟ", "ɺ" }, + LetterKey.VK_N => new[] { "ɳ", "ɲ", "ŋ", "ɴ" }, + LetterKey.VK_O => new[] { "ɤ", "ɔ", "ɶ" }, + LetterKey.VK_R => new[] { "ʁ", "ɹ", "ɻ", "ɾ", "ɽ", "ʀ" }, + LetterKey.VK_S => new[] { "ʃ", "ʂ", "ɕ" }, + LetterKey.VK_U => new[] { "ʉ", "ʊ" }, + LetterKey.VK_V => new[] { "ʋ", "ⱱ", "ʌ" }, + LetterKey.VK_W => new[] { "ɰ", "ɯ" }, + LetterKey.VK_Y => new[] { "ʏ" }, + LetterKey.VK_Z => new[] { "ʒ", "ʐ", "ʑ" }, + LetterKey.VK_COMMA => new[] { "ʡ", "ʔ", "ʕ", "ʢ" }, + _ => Array.Empty(), + }; + } } } diff --git a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs index ee89f62158..50b07b8a81 100644 --- a/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs +++ b/src/modules/poweraccent/PowerAccent.Core/PowerAccent.cs @@ -150,7 +150,7 @@ public class PowerAccent : IDisposable { var unicode = unicodeList.First(); var charUnicodeNumber = unicode.CodePoint.ToString("X4", CultureInfo.InvariantCulture); - description.AppendFormat(CultureInfo.InvariantCulture, "(U+{0}) {1} ", charUnicodeNumber, unicode.Name); + description.AppendFormat(CultureInfo.InvariantCulture, "(U+{0}) {1}", charUnicodeNumber, unicode.Name); return description.ToString(); } diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj index 92cd4dd82e..aabed1cff8 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj @@ -1,6 +1,6 @@  - + @@ -205,7 +205,7 @@ - + @@ -218,8 +218,8 @@ - - + + diff --git a/src/modules/powerrename/PowerRenameUILib/packages.config b/src/modules/powerrename/PowerRenameUILib/packages.config index c069173ead..329104b361 100644 --- a/src/modules/powerrename/PowerRenameUILib/packages.config +++ b/src/modules/powerrename/PowerRenameUILib/packages.config @@ -5,5 +5,5 @@ - + \ No newline at end of file diff --git a/src/modules/powerrename/lib/Helpers.cpp b/src/modules/powerrename/lib/Helpers.cpp index 73991c8e9b..040167d6ef 100644 --- a/src/modules/powerrename/lib/Helpers.cpp +++ b/src/modules/powerrename/lib/Helpers.cpp @@ -46,6 +46,11 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour { std::locale::global(std::locale("")); HRESULT hr = E_INVALIDARG; + + auto contractionOrSingleQuotedWordCheck = [](std::wstring stem, size_t i) { + return !i || stem[i - 1] != '\'' || (i == 1 || iswpunct(stem[i - 2]) || iswspace(stem[i - 2])); + }; + if (source && flags) { if (flags & Uppercase) @@ -156,7 +161,7 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour for (size_t i = 0; i < stemLength; i++) { - if (!i || iswspace(stem[i - 1]) || iswpunct(stem[i - 1])) + if (!i || iswspace(stem[i - 1]) || (iswpunct(stem[i - 1]) && contractionOrSingleQuotedWordCheck(stem, i))) { if (iswspace(stem[i]) || iswpunct(stem[i])) { @@ -167,7 +172,10 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour { wordLength++; } - if (isFirstWord || i + wordLength == stemLength || std::find(exceptions.begin(), exceptions.end(), stem.substr(i, wordLength)) == exceptions.end()) + + auto subStr = stem.substr(i, wordLength); + std::transform(subStr.begin(), subStr.end(), subStr.begin(), ::towlower); + if (isFirstWord || i + wordLength == stemLength || std::find(exceptions.begin(), exceptions.end(), subStr) == exceptions.end()) { stem[i] = towupper(stem[i]); isFirstWord = false; @@ -205,13 +213,16 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour for (size_t i = 0; i < stemLength; i++) { - if (!i || iswspace(stem[i - 1]) || iswpunct(stem[i - 1])) + if (!i || iswspace(stem[i - 1]) || (iswpunct(stem[i - 1]) && contractionOrSingleQuotedWordCheck(stem, i))) { if (iswspace(stem[i]) || iswpunct(stem[i])) { continue; } - stem[i] = towupper(stem[i]); + else + { + stem[i] = towupper(stem[i]); + } } else { diff --git a/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp b/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp index c3a57dfc8b..c6145ff0a7 100644 --- a/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp +++ b/src/modules/powerrename/unittests/PowerRenameManagerTests.cpp @@ -91,11 +91,15 @@ namespace PowerRenameManagerTests Assert::IsTrue(replaceSuccess); + std::vector shouldRename = { L"not ", L"" }; + // Verify the rename occurred for (int i = 0; i < numPairs; i++) { - Assert::IsTrue(testFileHelper.PathExists(renamePairs[i].originalName) == !renamePairs[i].shouldRename); - Assert::IsTrue(testFileHelper.PathExists(renamePairs[i].newName) == renamePairs[i].shouldRename); + Assert::IsTrue(testFileHelper.PathExistsCaseSensitive(renamePairs[i].originalName) == !renamePairs[i].shouldRename, + (std::wstring(L"The path: [" + renamePairs[i].originalName + L"] should ") + shouldRename[!renamePairs[i].shouldRename] + L"exist.").c_str()); + Assert::IsTrue(testFileHelper.PathExistsCaseSensitive(renamePairs[i].newName) == renamePairs[i].shouldRename, + (std::wstring(L"The path: [" + renamePairs[i].newName + L"] should ") + shouldRename[renamePairs[i].shouldRename] + L"exist.").c_str()); } Assert::IsTrue(mgr->Shutdown() == S_OK); @@ -250,7 +254,19 @@ namespace PowerRenameManagerTests TEST_METHOD (VerifyTitlecaseTransform) { rename_pairs renamePairs[] = { - { L"foo and the to", L"Bar and the To", false, true, 0 }, + { L"foo And The To", L"Bar and the To", false, true, 0 }, + { L"foo And The To.txt", L"Bar and the To.txt", true, true, 0 }, + { L"Test", L"Test_norename", false, false, 0 } + }; + + RenameHelper(renamePairs, ARRAYSIZE(renamePairs), L"foo", L"bar", SYSTEMTIME{ 2020, 7, 3, 22, 15, 6, 42, 453 }, DEFAULT_FLAGS | Titlecase); + } + + TEST_METHOD (VerifyTitlecaseWithApostropheTransform) + { + rename_pairs renamePairs[] = { + { L"the foo i'll and i've you're dogs' the i'd it's i'm don't to y'all", L"The Bar I'll and I've You're Dogs' the I'd It's I'm Don't to Y'all", false, true, 0 }, + { L"'the 'foo' 'i'll' and i've you're dogs' the 'i'd' it's i'm don't to y'all.txt", L"'The 'Bar' 'I'll' and I've You're Dogs' the 'I'd' It's I'm Don't to Y'all.txt", true, true, 0 }, { L"Test", L"Test_norename", false, false, 0 } }; @@ -267,10 +283,22 @@ namespace PowerRenameManagerTests RenameHelper(renamePairs, ARRAYSIZE(renamePairs), L"foo", L"bar", SYSTEMTIME{ 2020, 7, 3, 22, 15, 6, 42, 453 }, DEFAULT_FLAGS | Capitalized); } + TEST_METHOD (VerifyCapitalizedWithApostropheTransform) + { + rename_pairs renamePairs[] = { + { L"foo i'll and i've you're dogs' the i'd it's i'm don't to y'all", L"Bar I'll And I've You're Dogs' The I'd It's I'm Don't To Y'all", false, true, 0 }, + { L"'foo i'll 'and' i've you're dogs' the i'd it's i'm don't to y'all.txt", L"'Bar I'll 'And' I've You're Dogs' The I'd It's I'm Don't To Y'all.txt", true, true, 0 }, + { L"Test", L"Test_norename", false, false, 0 } + }; + + RenameHelper(renamePairs, ARRAYSIZE(renamePairs), L"foo", L"bar", SYSTEMTIME{ 2020, 7, 3, 22, 15, 6, 42, 453 }, DEFAULT_FLAGS | Capitalized); + } + TEST_METHOD (VerifyNameOnlyTransform) { rename_pairs renamePairs[] = { - { L"foo.txt", L"BAR.txt", false, true, 0 }, + { L"foo.foo", L"BAR.foo", true, true, 0 }, + { L"foo.txt", L"BAR.TXT", false, true, 0 }, { L"TEST", L"TEST_norename", false, false, 1 } }; diff --git a/src/modules/powerrename/unittests/TestFileHelper.cpp b/src/modules/powerrename/unittests/TestFileHelper.cpp index 6d55ae0659..e7b2a1a3ea 100644 --- a/src/modules/powerrename/unittests/TestFileHelper.cpp +++ b/src/modules/powerrename/unittests/TestFileHelper.cpp @@ -47,6 +47,19 @@ bool CTestFileHelper::PathExists(_In_ const std::wstring path) return fs::exists(fullPath); } +bool CTestFileHelper::PathExistsCaseSensitive(_In_ const std::wstring path) +{ + fs::path tempDirPath = fs::path(_tempDirectory); + for (const auto& entry : fs::directory_iterator(tempDirPath)) + { + if (entry.path().filename().wstring() == path) + { + return true; + } + } + return false; +} + bool CTestFileHelper::_CreateTempDirectory() { // Initialize to the temp directory diff --git a/src/modules/powerrename/unittests/TestFileHelper.h b/src/modules/powerrename/unittests/TestFileHelper.h index 0ccfe1f641..8f6f12c96b 100644 --- a/src/modules/powerrename/unittests/TestFileHelper.h +++ b/src/modules/powerrename/unittests/TestFileHelper.h @@ -14,6 +14,7 @@ public: bool AddFolder(_In_ const std::wstring path); const std::filesystem::path GetTempDirectory() { return _tempDirectory; } bool PathExists(_In_ const std::wstring path); + bool PathExistsCaseSensitive(_In_ const std::wstring path); std::filesystem::path GetFullPath(_In_ const std::wstring path); private: diff --git a/src/modules/previewpane/GcodePreviewHandler/Program.cs b/src/modules/previewpane/GcodePreviewHandler/Program.cs index e00c668a51..0b0696fd01 100644 --- a/src/modules/previewpane/GcodePreviewHandler/Program.cs +++ b/src/modules/previewpane/GcodePreviewHandler/Program.cs @@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode if (args.Length == 6) { string filePath = args[0]; - int hwnd = Convert.ToInt32(args[1], 16); + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); int left = Convert.ToInt32(args[2], 10); int right = Convert.ToInt32(args[3], 10); @@ -36,7 +36,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode Rectangle s = new Rectangle(left, top, right - left, bottom - top); _previewHandlerControl = new GcodePreviewHandlerControl(); - _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.SetWindow(hwnd, s); _previewHandlerControl.DoPreview(filePath); NativeEventWaiter.WaitForEventLoop( diff --git a/src/modules/previewpane/MarkdownPreviewHandler/Program.cs b/src/modules/previewpane/MarkdownPreviewHandler/Program.cs index c746df97b1..8b6b66f974 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/Program.cs +++ b/src/modules/previewpane/MarkdownPreviewHandler/Program.cs @@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown if (args.Length == 6) { string filePath = args[0]; - int hwnd = Convert.ToInt32(args[1], 16); + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); int left = Convert.ToInt32(args[2], 10); int right = Convert.ToInt32(args[3], 10); @@ -36,7 +36,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown Rectangle s = new Rectangle(left, top, right - left, bottom - top); _previewHandlerControl = new MarkdownPreviewHandlerControl(); - _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.SetWindow(hwnd, s); _previewHandlerControl.DoPreview(filePath); NativeEventWaiter.WaitForEventLoop( diff --git a/src/modules/previewpane/MonacoPreviewHandler/Program.cs b/src/modules/previewpane/MonacoPreviewHandler/Program.cs index 50e1d58957..7821d25658 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/Program.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/Program.cs @@ -30,7 +30,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco if (args.Length == 6) { string filePath = args[0]; - int hwnd = Convert.ToInt32(args[1], 16); + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); int left = Convert.ToInt32(args[2], 10); int right = Convert.ToInt32(args[3], 10); @@ -39,7 +39,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco Rectangle s = new Rectangle(left, top, right - left, bottom - top); _previewHandlerControl = new MonacoPreviewHandlerControl(); - _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.SetWindow(hwnd, s); _previewHandlerControl.DoPreview(filePath); NativeEventWaiter.WaitForEventLoop( diff --git a/src/modules/previewpane/PdfPreviewHandler/Program.cs b/src/modules/previewpane/PdfPreviewHandler/Program.cs index 929be0dd15..d9427dc215 100644 --- a/src/modules/previewpane/PdfPreviewHandler/Program.cs +++ b/src/modules/previewpane/PdfPreviewHandler/Program.cs @@ -26,7 +26,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf if (args.Length == 6) { string filePath = args[0]; - int hwnd = Convert.ToInt32(args[1], 16); + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); int left = Convert.ToInt32(args[2], 10); int right = Convert.ToInt32(args[3], 10); @@ -35,7 +35,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf Rectangle s = new Rectangle(left, top, right - left, bottom - top); _previewHandlerControl = new PdfPreviewHandlerControl(); - _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.SetWindow(hwnd, s); _previewHandlerControl.DoPreview(filePath); NativeEventWaiter.WaitForEventLoop( diff --git a/src/modules/previewpane/QoiPreviewHandler/Program.cs b/src/modules/previewpane/QoiPreviewHandler/Program.cs index 91d1916881..5a9f5bfa2b 100644 --- a/src/modules/previewpane/QoiPreviewHandler/Program.cs +++ b/src/modules/previewpane/QoiPreviewHandler/Program.cs @@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Qoi if (args.Length == 6) { string filePath = args[0]; - int hwnd = Convert.ToInt32(args[1], 16); + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); int left = Convert.ToInt32(args[2], 10); int right = Convert.ToInt32(args[3], 10); @@ -36,7 +36,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Qoi Rectangle s = new Rectangle(left, top, right - left, bottom - top); _previewHandlerControl = new QoiPreviewHandlerControl(); - _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.SetWindow(hwnd, s); _previewHandlerControl.DoPreview(filePath); NativeEventWaiter.WaitForEventLoop( diff --git a/src/modules/previewpane/SvgPreviewHandler/Program.cs b/src/modules/previewpane/SvgPreviewHandler/Program.cs index f59654a0ef..4b506ded1a 100644 --- a/src/modules/previewpane/SvgPreviewHandler/Program.cs +++ b/src/modules/previewpane/SvgPreviewHandler/Program.cs @@ -27,7 +27,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg if (args.Length == 6) { string filePath = args[0]; - int hwnd = Convert.ToInt32(args[1], 16); + IntPtr hwnd = IntPtr.Parse(args[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture); int left = Convert.ToInt32(args[2], 10); int right = Convert.ToInt32(args[3], 10); @@ -36,7 +36,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg Rectangle s = new Rectangle(left, top, right - left, bottom - top); _previewHandlerControl = new SvgPreviewControl(); - _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.SetWindow(hwnd, s); _previewHandlerControl.DoPreview(filePath); NativeEventWaiter.WaitForEventLoop( diff --git a/src/runner/centralized_kb_hook.cpp b/src/runner/centralized_kb_hook.cpp index fcb96a09a7..72b473bd08 100644 --- a/src/runner/centralized_kb_hook.cpp +++ b/src/runner/centralized_kb_hook.cpp @@ -151,6 +151,11 @@ namespace CentralizedKeyboardHook .key = static_cast(keyPressInfo.vkCode) }; + if (hotkey == Hotkey{}) + { + return CallNextHookEx(hHook, nCode, wParam, lParam); + } + std::function action; { // Hold the lock for the shortest possible duration diff --git a/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs b/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs index b5a8e5555b..64ca0457aa 100644 --- a/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs +++ b/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs @@ -15,6 +15,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("activation_method")] public IntProperty ActivationMethod { get; set; } + [JsonPropertyName("include_win_key")] + public BoolProperty IncludeWinKey { get; set; } + [JsonPropertyName("activation_shortcut")] public HotkeySettings ActivationShortcut { get; set; } @@ -54,6 +57,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library public FindMyMouseProperties() { ActivationMethod = new IntProperty(0); + IncludeWinKey = new BoolProperty(false); ActivationShortcut = DefaultActivationShortcut; DoNotActivateOnGameMode = new BoolProperty(true); BackgroundColor = new StringProperty("#000000"); diff --git a/src/settings-ui/Settings.UI.Library/MeasureToolProperties.cs b/src/settings-ui/Settings.UI.Library/MeasureToolProperties.cs index 609f027dbe..97a0c3219a 100644 --- a/src/settings-ui/Settings.UI.Library/MeasureToolProperties.cs +++ b/src/settings-ui/Settings.UI.Library/MeasureToolProperties.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library public class MeasureToolProperties { [CmdConfigureIgnore] - public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, false, true, 0x4D); + public HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, true, false, true, 0x4D); public MeasureToolProperties() { diff --git a/src/settings-ui/Settings.UI/Converters/FindMyMouseActivationIntToVisibilityConverter.cs b/src/settings-ui/Settings.UI/Converters/IndexBitFieldToVisibilityConverter.cs similarity index 58% rename from src/settings-ui/Settings.UI/Converters/FindMyMouseActivationIntToVisibilityConverter.cs rename to src/settings-ui/Settings.UI/Converters/IndexBitFieldToVisibilityConverter.cs index 080de07713..9d6950af83 100644 --- a/src/settings-ui/Settings.UI/Converters/FindMyMouseActivationIntToVisibilityConverter.cs +++ b/src/settings-ui/Settings.UI/Converters/IndexBitFieldToVisibilityConverter.cs @@ -3,20 +3,20 @@ // See the LICENSE file in the project root for more information. using System; -using System.Globalization; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Data; namespace Microsoft.PowerToys.Settings.UI.Converters { - public sealed class FindMyMouseActivationIntToVisibilityConverter : IValueConverter + public sealed class IndexBitFieldToVisibilityConverter : IValueConverter { + // Receives a hexadecimal bit mask as a parameter. Will check the value against that bitmask. public object Convert(object value, Type targetType, object parameter, string language) { - var selectedActivation = (int)value; - var expectedActivation = int.Parse(parameter as string, CultureInfo.InvariantCulture); + var currentIndexBit = 1 << (int)value; + var selectedIndicesBitField = System.Convert.ToUInt32(parameter as string, 16); - return selectedActivation == expectedActivation ? Visibility.Visible : Visibility.Collapsed; + return (selectedIndicesBitField & currentIndexBit) == 0 ? Visibility.Collapsed : Visibility.Visible; } public object ConvertBack(object value, Type targetType, object parameter, string language) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml index dc43ff3303..08fabdff9c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPaste.xaml @@ -45,7 +45,7 @@ Severity="Informational" /> - + @@ -73,12 +73,27 @@ IsEnabled="{x:Bind ViewModel.IsOpenAIEnabled, Mode=OneWay}"> + - + + @@ -152,7 +167,7 @@ - + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml index 10a7ed6ad2..5dcec727c7 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/MouseUtilsPage.xaml @@ -12,7 +12,7 @@ AutomationProperties.LandmarkType="Main" mc:Ignorable="d"> - + - + + + + + + - + + - + + + Visibility="{x:Bind ViewModel.FindMyMouseActivationMethod, Converter={StaticResource IndexBitFieldToVisibilityConverter}, Mode=OneWay, ConverterParameter=0x8}"> + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerAccentPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerAccentPage.xaml index c7409d2c97..b57ffc8792 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerAccentPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerAccentPage.xaml @@ -54,6 +54,7 @@ + @@ -70,6 +71,7 @@ + diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 5b222ae3f5..223abb2a63 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -2677,6 +2677,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Shake mouse Mouse is the hardware peripheral. + + Only activate while holding the Windows key + Specifies that the 'Find My Mouse' will only activate if the Windows key is held down while pressing the Ctrl key twice. + Prevents module activation when an excluded application is the foreground application @@ -3476,6 +3480,9 @@ Activate by holding the key for the character you want to add an accent to, then Catalan + + Crimean Tatar + Currency @@ -3517,6 +3524,10 @@ Activate by holding the key for the character you want to add an accent to, then Esperanto + + IPA + International Phonetic Alphabet + Lithuanian @@ -3771,7 +3782,7 @@ Activate by holding the key for the character you want to add an accent to, then Product name: Navigation view item name for Advanced Paste - Advanced Paste is an AI powered tool to put your clipboard content into any format you need + Advanced Paste is a tool to put your clipboard content into any format you need. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Advanced Paste @@ -3795,7 +3806,7 @@ Activate by holding the key for the character you want to add an accent to, then Preview the output of the custom format before pasting - Advanced Paste is An AI powered tool to put your clipboard content into any format you need, focused towards developer workflows. Note: this will replace the formatted text in your clipboard with the selected format. + Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format. Advanced Paste @@ -4185,6 +4196,9 @@ Activate by holding the key for the character you want to add an accent to, then • Login into your + + Configure OpenAI key + OpenAI API keys overview diff --git a/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs index bf9e8136ec..a4762c5e98 100644 --- a/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs @@ -32,6 +32,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private GpoRuleConfigured _enabledGpoRuleConfiguration; private bool _enabledStateIsGPOConfigured; + private GpoRuleConfigured _onlineAIModelsGpoRuleConfiguration; + private bool _onlineAIModelsDisallowedByGPO; private bool _isEnabled; private Func SendConfigMSG { get; } @@ -80,6 +82,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { _isEnabled = GeneralSettingsConfig.Enabled.AdvancedPaste; } + + _onlineAIModelsGpoRuleConfiguration = GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue(); + if (_onlineAIModelsGpoRuleConfiguration == GpoRuleConfigured.Disabled) + { + _onlineAIModelsDisallowedByGPO = true; + + // disable AI if it was enabled + DisableAI(); + } } public bool IsEnabled @@ -97,6 +108,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { _isEnabled = value; OnPropertyChanged(nameof(IsEnabled)); + OnPropertyChanged(nameof(ShowOnlineAIModelsGpoConfiguredInfoBar)); + OnPropertyChanged(nameof(ShowClipboardHistoryIsGpoConfiguredInfoBar)); // Set the status of AdvancedPaste in the general settings GeneralSettingsConfig.Enabled.AdvancedPaste = value; @@ -124,13 +137,23 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels return cred is not null; } - public bool IsOpenAIEnabled => OpenAIKeyExists(); + public bool IsOpenAIEnabled => OpenAIKeyExists() && !IsOnlineAIModelsDisallowedByGPO; public bool IsEnabledGpoConfigured { get => _enabledStateIsGPOConfigured; } + public bool IsOnlineAIModelsDisallowedByGPO + { + get => _onlineAIModelsDisallowedByGPO || _enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled; + } + + public bool ShowOnlineAIModelsGpoConfiguredInfoBar + { + get => _onlineAIModelsDisallowedByGPO && _isEnabled; + } + private bool IsClipboardHistoryEnabled() { string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\"; @@ -145,6 +168,27 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + private bool IsClipboardHistoryDisabledByGPO() + { + string registryKey = @"HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\System\"; + try + { + object allowClipboardHistory = Registry.GetValue(registryKey, "AllowClipboardHistory", null); + if (allowClipboardHistory != null) + { + return (int)allowClipboardHistory == 0; + } + else + { + return false; + } + } + catch (Exception) + { + return false; + } + } + private void SetClipboardHistoryEnabled(bool value) { string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\"; @@ -169,6 +213,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool ClipboardHistoryDisabledByGPO + { + get => IsClipboardHistoryDisabledByGPO(); + } + + public bool ShowClipboardHistoryIsGpoConfiguredInfoBar + { + get => IsClipboardHistoryDisabledByGPO() && _isEnabled; + } + public HotkeySettings AdvancedPasteUIShortcut { get => _advancedPasteSettings.Properties.AdvancedPasteUIShortcut; @@ -285,6 +339,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { InitializeEnabledValue(); OnPropertyChanged(nameof(IsEnabled)); + OnPropertyChanged(nameof(ShowOnlineAIModelsGpoConfiguredInfoBar)); + OnPropertyChanged(nameof(ShowClipboardHistoryIsGpoConfiguredInfoBar)); } protected virtual void Dispose(bool disposing) @@ -308,18 +364,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels internal void DisableAI() { - PasswordVault vault = new PasswordVault(); - PasswordCredential cred = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); - vault.Remove(cred); - OnPropertyChanged(nameof(IsOpenAIEnabled)); + try + { + PasswordVault vault = new PasswordVault(); + PasswordCredential cred = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); + vault.Remove(cred); + OnPropertyChanged(nameof(IsOpenAIEnabled)); + } + catch (Exception) + { + } } internal void EnableAI(string password) { - PasswordVault vault = new PasswordVault(); - PasswordCredential cred = new PasswordCredential("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey", password); - vault.Add(cred); - OnPropertyChanged(nameof(IsOpenAIEnabled)); + try + { + PasswordVault vault = new PasswordVault(); + PasswordCredential cred = new PasswordCredential("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey", password); + vault.Add(cred); + OnPropertyChanged(nameof(IsOpenAIEnabled)); + } + catch (Exception) + { + } } } } diff --git a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs index 05d8184a46..2ad31341c2 100644 --- a/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/MouseUtilsViewModel.cs @@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels FindMyMouseSettingsConfig = findMyMouseSettingsRepository.SettingsConfig; _findMyMouseActivationMethod = FindMyMouseSettingsConfig.Properties.ActivationMethod.Value < 4 ? FindMyMouseSettingsConfig.Properties.ActivationMethod.Value : 0; + _findMyMouseIncludeWinKey = FindMyMouseSettingsConfig.Properties.IncludeWinKey.Value; _findMyMouseDoNotActivateOnGameMode = FindMyMouseSettingsConfig.Properties.DoNotActivateOnGameMode.Value; string backgroundColor = FindMyMouseSettingsConfig.Properties.BackgroundColor.Value; @@ -210,6 +211,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool FindMyMouseIncludeWinKey + { + get + { + return _findMyMouseIncludeWinKey; + } + + set + { + if (_findMyMouseIncludeWinKey != value) + { + _findMyMouseIncludeWinKey = value; + FindMyMouseSettingsConfig.Properties.IncludeWinKey.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + public HotkeySettings FindMyMouseActivationShortcut { get @@ -973,6 +992,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _findMyMouseEnabledStateIsGPOConfigured; private bool _isFindMyMouseEnabled; private int _findMyMouseActivationMethod; + private bool _findMyMouseIncludeWinKey; private bool _findMyMouseDoNotActivateOnGameMode; private string _findMyMouseBackgroundColor; private string _findMyMouseSpotlightColor; diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerAccentViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerAccentViewModel.cs index bfb67bf494..6c4d1a7951 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerAccentViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerAccentViewModel.cs @@ -25,6 +25,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { "ALL", "CA", + "CRH", "CUR", "HR", "CZ", @@ -41,6 +42,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels "HE", "HU", "IS", + "IPA", "IT", "KU", "LT", diff --git a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp index 6eef4860c2..6677b865b0 100644 --- a/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp +++ b/tools/BugReportTool/BugReportTool/ReportGPOValues.cpp @@ -68,4 +68,5 @@ void ReportGPOValues(const std::filesystem::path& tmpDir) report << "getAllowExperimentationValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowExperimentationValue()) << std::endl; report << "getConfiguredQoiPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredQoiPreviewEnabledValue()) << std::endl; report << "getConfiguredQoiThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue()) << std::endl; + report << "getAllowedAdvancedPasteOnlineAIModelsValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue()) << std::endl; }