Merged powerToys master into Launcher master

This commit is contained in:
Alekhya Reddy 2020-04-08 11:49:32 -07:00
commit 12c44dceb6
373 changed files with 34451 additions and 7212 deletions

5
.gitignore vendored
View File

@ -301,6 +301,8 @@ __pycache__/
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
ImageResizer/tools/**
!ImageResizer/tools/packages.config
# Tabs Studio
*.tss
@ -332,3 +334,6 @@ ASALocalRun/
# Temp build files
src/settings/settings-html/200.html
src/settings/settings-html/404.html
# Temp telemetry files.
src/common/Telemetry/*.etl

View File

@ -33,3 +33,16 @@ steps:
msbuildArgs: ${{ parameters.additionalBuildArguments }}
clean: true
maximumCpuCount: true
- task: VSTest@2
inputs:
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\PreviewPaneUnitTests.dll
**\UnitTests-SvgPreviewHandler.dll
**\UnitTests-PreviewHandlerCommon.dll
**\powerpreviewTest.dll
!**\*TestAdapter.dll
!**\obj\**

View File

@ -50,6 +50,14 @@ build:
- 'modules\fancyzones.dll'
- 'modules\shortcut_guide.dll'
- 'modules\PowerRenameExt.dll'
- 'modules\WindowWalker.exe'
- 'modules\WindowWalker.dll'
- 'modules\ImageResizerExt.dll'
- 'modules\ImageResizer.exe'
- 'modules\powerpreview.dll'
- 'modules\PreviewHandlerCommon.dll'
- 'modules\MarkdownPreviewHandler.dll'
- 'modules\SvgPreviewHandler.dll'
signing_options:
sign_inline: true # This does signing a soon as this command completes
- !!buildcommand

46
NOTICE.md Normal file
View File

@ -0,0 +1,46 @@
# NOTICES AND INFORMATION
Do Not Translate or Localize
This software incorporates material from third parties. Microsoft makes certain
open source code available at http://3rdpartysource.microsoft.com, or you may
send a check or money order for US $5.00, including the product name, the open
source component name, and version number, to:
```
Source Code Compliance Team
Microsoft Corporation
One Microsoft Way
Redmond, WA 98052
USA
```
Notwithstanding any other terms, you may reverse engineer this software to the
extent required to debug changes to any libraries licensed under the GNU Lesser
General Public License.
## ImageResizer
**Source**: https://github.com/bricelam/ImageResizer/
### License
The MIT License (MIT)
Copyright (c) Brice Lambson. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -5,13 +5,28 @@ VisualStudioVersion = 16.0.28803.452
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}"
ProjectSection(ProjectDependencies) = postProject
{217DF501-135C-4E38-BFC8-99D4821032EA} = {217DF501-135C-4E38-BFC8-99D4821032EA}
{0E072714-D127-460B-AFAD-B4C40B412798} = {0E072714-D127-460B-AFAD-B4C40B412798}
{48804216-2A0E-4168-A6D8-9CD068D14227} = {48804216-2A0E-4168-A6D8-9CD068D14227}
{51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2}
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6} = {51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}
{031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {031AC72E-FA28-4AB7-B690-6F7B9C28AA73}
{74485049-C722-400F-ABE5-86AC52D929B3} = {74485049-C722-400F-ABE5-86AC52D929B3}
{0485F45C-EA7A-4BB5-804B-3E8D14699387} = {0485F45C-EA7A-4BB5-804B-3E8D14699387}
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D} = {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481}
{0B593A6C-4143-4337-860E-DB5710FB87DB} = {0B593A6C-4143-4337-860E-DB5710FB87DB}
{E364F67B-BB12-4E91-B639-355866EBCD8B} = {E364F67B-BB12-4E91-B639-355866EBCD8B}
{DA425894-6E13-404F-8DCB-78584EC0557A} = {DA425894-6E13-404F-8DCB-78584EC0557A}
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}
{B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670}
{AF2349B8-E5B6-4004-9502-687C1C7730B1} = {AF2349B8-E5B6-4004-9502-687C1C7730B1}
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB} = {B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB}
{A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}
{07C389E3-6BC8-41CF-923E-307B1265FA2D} = {07C389E3-6BC8-41CF-923E-307B1265FA2D}
EndProjectSection
EndProject
@ -124,6 +139,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowWalker", "src\modules
{74485049-C722-400F-ABE5-86AC52D929B3} = {74485049-C722-400F-ABE5-86AC52D929B3}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "imageresizer", "imageresizer", "{6C7F47CC-2151-44A3-A546-41C70025132C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageResizerUI", "src\modules\imageresizer\ui\ImageResizerUI.csproj", "{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerExt", "src\modules\imageresizer\dll\ImageResizerExt.vcxproj", "{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageResizerUITest", "src\modules\imageresizer\tests\ImageResizerUITest.csproj", "{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "action_runner", "src\action_runner\action_runner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}"
ProjectSection(ProjectDependencies) = postProject
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
@ -171,6 +194,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerLauncher.UI", "src\modules\launcher\PowerLauncher.UI\PowerLauncher.UI.csproj", "{4A3DE70C-684C-410D-B851-C23B6DAEDF16}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E775CC2C-24CB-48D6-9C3A-BE4CCE0DB17A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "win-app-driver", "src\tests\win-app-driver\win-app-driver.csproj", "{880ED251-9E16-4713-9A70-D35FE0C01669}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "previewpane", "previewpane", "{2F305555-C296-497E-AC20-5FA1B237996A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PreviewHandlerCommon", "src\modules\previewpane\Common\PreviewHandlerCommon.csproj", "{AF2349B8-E5B6-4004-9502-687C1C7730B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownPreviewHandler", "src\modules\previewpane\MarkDownPreviewHandler\MarkdownPreviewHandler.csproj", "{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-MarkdownPreviewHandler", "src\modules\previewpane\PreviewPaneUnitTests\UnitTests-MarkdownPreviewHandler.csproj", "{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SvgPreviewHandler", "src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csproj", "{DA425894-6E13-404F-8DCB-78584EC0557A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-SvgPreviewHandler", "src\modules\previewpane\UnitTests-SvgPreviewHandler\UnitTests-SvgPreviewHandler.csproj", "{060D75DA-2D1C-48E6-A4A1-6F0718B64661}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-PreviewHandlerCommon", "src\modules\previewpane\UnitTests-PreviewHandlerCommon\UnitTests-PreviewHandlerCommon.csproj", "{748417CA-F17E-487F-9411-CAFB6D3F4877}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "powerpreview", "src\modules\previewpane\powerpreview\powerpreview.vcxproj", "{217DF501-135C-4E38-BFC8-99D4821032EA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "powerpreviewTest", "src\modules\previewpane\powerpreviewTest\powerpreviewTest.vcxproj", "{47310AB4-9034-4BD1-8D8B-E88AD21A171B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -261,6 +306,18 @@ Global
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}.Debug|x64.Build.0 = Debug|x64
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}.Release|x64.ActiveCfg = Release|x64
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}.Release|x64.Build.0 = Release|x64
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.ActiveCfg = Debug|x64
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.Build.0 = Debug|x64
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.ActiveCfg = Release|x64
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.Build.0 = Release|x64
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.ActiveCfg = Debug|x64
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.Build.0 = Debug|x64
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.ActiveCfg = Release|x64
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.Build.0 = Release|x64
{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.ActiveCfg = Debug|x64
{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.Build.0 = Debug|x64
{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.ActiveCfg = Release|x64
{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.Build.0 = Release|x64
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.ActiveCfg = Debug|x64
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.Build.0 = Debug|x64
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x64.ActiveCfg = Release|x64
@ -323,6 +380,42 @@ Global
{4A3DE70C-684C-410D-B851-C23B6DAEDF16}.Release|x64.ActiveCfg = Release|x64
{4A3DE70C-684C-410D-B851-C23B6DAEDF16}.Release|x64.Build.0 = Release|x64
{4A3DE70C-684C-410D-B851-C23B6DAEDF16}.Release|x64.Deploy.0 = Release|x64
{880ED251-9E16-4713-9A70-D35FE0C01669}.Debug|x64.ActiveCfg = Debug|x64
{880ED251-9E16-4713-9A70-D35FE0C01669}.Debug|x64.Build.0 = Debug|x64
{880ED251-9E16-4713-9A70-D35FE0C01669}.Release|x64.ActiveCfg = Release|x64
{880ED251-9E16-4713-9A70-D35FE0C01669}.Release|x64.Build.0 = Release|x64
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.ActiveCfg = Debug|x64
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.Build.0 = Debug|x64
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.ActiveCfg = Release|x64
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.Build.0 = Release|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.ActiveCfg = Debug|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.Build.0 = Debug|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.ActiveCfg = Release|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.Build.0 = Release|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.ActiveCfg = Debug|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.Build.0 = Debug|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.ActiveCfg = Release|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.Build.0 = Release|x64
{DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x64.ActiveCfg = Debug|x64
{DA425894-6E13-404F-8DCB-78584EC0557A}.Debug|x64.Build.0 = Debug|x64
{DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x64.ActiveCfg = Release|x64
{DA425894-6E13-404F-8DCB-78584EC0557A}.Release|x64.Build.0 = Release|x64
{060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x64.ActiveCfg = Debug|x64
{060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Debug|x64.Build.0 = Debug|x64
{060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x64.ActiveCfg = Release|x64
{060D75DA-2D1C-48E6-A4A1-6F0718B64661}.Release|x64.Build.0 = Release|x64
{748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x64.ActiveCfg = Debug|x64
{748417CA-F17E-487F-9411-CAFB6D3F4877}.Debug|x64.Build.0 = Debug|x64
{748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x64.ActiveCfg = Release|x64
{748417CA-F17E-487F-9411-CAFB6D3F4877}.Release|x64.Build.0 = Release|x64
{217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x64.ActiveCfg = Debug|x64
{217DF501-135C-4E38-BFC8-99D4821032EA}.Debug|x64.Build.0 = Debug|x64
{217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.ActiveCfg = Release|x64
{217DF501-135C-4E38-BFC8-99D4821032EA}.Release|x64.Build.0 = Release|x64
{47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x64.ActiveCfg = Debug|x64
{47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Debug|x64.Build.0 = Debug|x64
{47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x64.ActiveCfg = Release|x64
{47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -351,6 +444,10 @@ Global
{8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB} = {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03}
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6} = {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03}
{6C7F47CC-2151-44A3-A546-41C70025132C} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {6C7F47CC-2151-44A3-A546-41C70025132C}
{0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {6C7F47CC-2151-44A3-A546-41C70025132C}
{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8} = {6C7F47CC-2151-44A3-A546-41C70025132C}
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{DB90F671-D861-46BB-93A3-F1304F5BA1C5} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
@ -367,6 +464,16 @@ Global
{F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{F97E5003-F263-4D4A-A964-0F1F3C82DEF2} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
{4A3DE70C-684C-410D-B851-C23B6DAEDF16} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
{880ED251-9E16-4713-9A70-D35FE0C01669} = {E775CC2C-24CB-48D6-9C3A-BE4CCE0DB17A}
{2F305555-C296-497E-AC20-5FA1B237996A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{AF2349B8-E5B6-4004-9502-687C1C7730B1} = {2F305555-C296-497E-AC20-5FA1B237996A}
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {2F305555-C296-497E-AC20-5FA1B237996A}
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A} = {2F305555-C296-497E-AC20-5FA1B237996A}
{DA425894-6E13-404F-8DCB-78584EC0557A} = {2F305555-C296-497E-AC20-5FA1B237996A}
{060D75DA-2D1C-48E6-A4A1-6F0718B64661} = {2F305555-C296-497E-AC20-5FA1B237996A}
{748417CA-F17E-487F-9411-CAFB6D3F4877} = {2F305555-C296-497E-AC20-5FA1B237996A}
{217DF501-135C-4E38-BFC8-99D4821032EA} = {2F305555-C296-497E-AC20-5FA1B237996A}
{47310AB4-9034-4BD1-8D8B-E88AD21A171B} = {2F305555-C296-497E-AC20-5FA1B237996A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

103
README.md
View File

@ -32,31 +32,22 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
## Build Status
[![Build Status](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=master)](https://dev.azure.com/ms/PowerToys/_build?definitionId=35096)
[![Build Status](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=master)](https://dev.azure.com/ms/PowerToys/_build?definitionId=219)
## Installing and running Microsoft PowerToys
> 👉 **Note:** Microsoft PowerToys requires Windows 10 1803 (build 17134) or later.
> 👉 **Upgrading to 0.15:** You need to reapply your zone layout for FancyZones. Don't worry, your custom zone sets are preserved.
## Installing and running Microsoft PowerToys 0.16
👉 **Note:** Microsoft PowerToys requires Windows 10 1803 (build 17134) or later.
### Via Github with MSI [Recommended]
Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.15.0-x64.msi` to download the PowerToys installer.
Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.16.0-x64.msi` to download the PowerToys installer.
This is our preferred method.
### Other install methods
#### Via GitHub with MSIX - ⚠ Experimental ⚠
##### MSIX / Store Build Update
The experimental version of PowerToys using MSIX is available. It can be installed from the [PowerToys GitHub releases page][github-release-link].
Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-MSIX-0.15.0.zip` to download the PowerToys installer zip. From there, please read the ReadMe and you can double click to install the MSIX file.
##### Known issues with MSIX Build
- For PowerRename, you may need to restart your machine to get this to work for the first time.
- We put in a lot of effort here but currently our plan of record is to make the MSI our only installer option and built-in auto-upgrade. MSIX is a great installer / container tech but there are few spots we are working with the team to improve so we can adopt.
#### Via Chocolatey - ⚠ Unofficial ⚠
@ -74,17 +65,13 @@ To upgrade PowerToys, run the following command from the command line / PowerShe
choco upgrade powertoys
```
### Microsoft Store
On backlog, [Issue #413](https://github.com/microsoft/PowerToys/issues/413)
### Processor support
We currently support the matrix below. Adding MSIX support will make supporting x86 and ARM much easier.
We currently support the matrix below.
| x64 | x86 | ARM |
|:---:|:---:|:---:|
| [Install][github-release-link] | [Issue #602](https://github.com/microsoft/PowerToys/issues/602) | [Issue #490](https://github.com/microsoft/PowerToys/issues/490)|
| [Supported][github-release-link] | [Issue #602](https://github.com/microsoft/PowerToys/issues/602) | [Issue #490](https://github.com/microsoft/PowerToys/issues/490) |
## Current PowerToy Utilities
@ -92,7 +79,7 @@ We currently support the matrix below. Adding MSIX support will make supporting
[FancyZones](/src/modules/fancyzones/) - FancyZones is a window manager that makes it easy to create complex window layouts and quickly position windows into those layouts.
### Shortcut
### Shortcut Guide
[Windows key shortcut guide](/src/modules/shortcut_guide) - The shortcut guide appears when a user holds the Windows key down for more than one second and shows the available shortcuts for the current state of the desktop.
@ -100,36 +87,66 @@ We currently support the matrix below. Adding MSIX support will make supporting
[PowerRename](/src/modules/powerrename) - PowerRename is a Windows Shell Extension for advanced bulk renaming using search and replace or regular expressions. PowerRename allows simple search and replace or more advanced regular expression matching. While you type in the search and replace input fields, the preview area will show what the items will be renamed to. PowerRename then calls into the Windows Explorer file operations engine to perform the rename. This has the benefit of allowing the rename operation to be undone after PowerRename exits.
This code is based on [Chris Davis's SmartRename](https://github.com/chrdavis/SmartRename).
### File Explorer (Preview Panes)
[File Explorer](/src/modules/previewpane) add-ons right now are just limited to Preview Pane additions for File Explorer. Preview Pane is an existing feature in the File Explorer. To enable it, you just click the View tab in the ribbon and then click "Preview Pane".
PowerToys will now enable two types of files to be previewed:
- Markdown files (.md)
- SVG (.svg)
### Image Resizer
[Image Resizer](/src/modules/imageresizer) is a Windows Shell Extension for quickly resizing images. With a simple right click from File Explorer, resize one or many images instantly.
This code is based on [Brice Lambson's Image Resizer](https://github.com/bricelam/ImageResizer).
### Window Walker (Text based alt-tab alternative)
[Window Walker](src/modules/windowwalker/) is an app that lets you search and switch between windows that you have open, all from the comfort of your keyboard. As you are searching for an app, you can use the keyboard up and down arrows to see an Alt-Tab style preview of the windows. In the future, this will be merged into the Launcher project.
This code is based on [Beta Tadele's Window Walker](https://github.com/betsegaw/windowwalker).
### Version 1.0 plan
Our plan for all the [goals and utilities for v1.0 detailed over here in the wiki][v1].
## What's Happening
### February 2020 Update
### March 2020 Update
Our mantra for the 0.15 was infrastructure, quality, stability and work toward getting a way to auto-update PowerToys. While it took a bit longer to get here, we feel it was worth the extra time to fix bugs that really impacted your experience with PowerToys.
Our mantra for the 0.16 was adding in new features along with a continual push for quality and stability. We are working toward getting a way to auto-update PowerToys and have a good plan for this. We want to proactively thank the community for quickly identifying a few bugs inside 0.15 and allowing us to quickly release 0.15.1 and 0.15.2.
Below are just a few of the bullet items from this release.
- We shipped [v0.15][github-release-link]!
- Make you aware there is a new version from within PowerToys
- Removed requirement to always 'run as admin'
- Added almost 300 unit tests to increase stability and prevent regressions.
- Resolved almost 100 issues
- Made .NET Framework parts of the source run faster with NGEN
- Improved for how we store data locally
- Increased FancyZones compatibility with applications
- Initial work for 4 new PowerToys added for 0.16!
- Created the [v1.0 strategy][v1], the [launcher](https://github.com/microsoft/PowerToys/wiki/Launcher), the [keyboard manager](https://github.com/microsoft/PowerToys/wiki/Keyboard-Manager) specs
- Work on cleaning up our issue backlog and labels
- We shipped [v0.16][github-release-link]!
- FancyZone improvement:
- Multi-Monitor improvement: Zone flipping switching now works between monitors!
- Simplified UX: Removed layout hot-swap and flashing feature due to need to improve multi-monitor support
- New Utilities!
- Markdown Preview pane extension
- SVG Preview pane extension
- Image Resizer Window Shell extension
- Window Walker, an alt-tab alternative
- Fixed over 100 issues!
- Testing improvements
- 54 UX Functional tests
- 161 new Unit tests
For 0.16, we have some fun things planned and hopefully will be able to ship pretty quickly. Here are the new utilities we'll enable:
For [0.17](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F3), we are proactively working on:
- An alternative to Alt-Tab PowerToy
- SVG preview pane for support Explorer
- Markdown preview pane support for Explorer
- Image Resizer PowerToy
- Auto-updating
- Win+R replacement (Launcher)
- Keyboard remapping
- Performance improvements with FancyZones
- A testing utility for FancyZones to be sure we can test different window configurations.
Future release work, we are proactively working on:
- Settings v2 / Fix bug #243
## Developer Guidance
@ -151,16 +168,14 @@ PowerToys is still a very fluidic project and the team is actively working out o
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct][oss-conduct-code]. For more information see the [Code of Conduct FAQ][oss-conduct-FAQ] or contact [opencode@microsoft.com][oss-conduct-email] with any additional questions or comments.
This project has adopted the [Microsoft Open Source Code of Conduct][oss-conduct-code].
## Privacy Statement
The application logs basic telemetry. Our Telemetry Data page (Coming Soon) has the trends from the telemetry. Please read the [Microsoft privacy statement][privacyLink] for more information.
[oss-CLA]: https://cla.opensource.microsoft.com
[oss-conduct-code]: https://opensource.microsoft.com/codeofconduct/
[oss-conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
[oss-conduct-email]: mailto:opencode@microsoft.com
[oss-conduct-code]: CODE_OF_CONDUCT.md
[github-release-link]: https://github.com/microsoft/PowerToys/releases/
[v1]: https://github.com/microsoft/PowerToys/wiki/Version-1.0-Strategy
[privacyLink]: http://go.microsoft.com/fwlink/?LinkId=521839

71
doc/devdocs/guidance.md Normal file
View File

@ -0,0 +1,71 @@
# Coding Guidance
## Working With Strings
In order to support localization **YOU SHOULD NOT** have hardcoded UI display strings in your code. Instead, use resource files to consume strings.
### For CPP
Use [`StringTable` resource][String Table] to store the strings and resource header file(`resource.h`) to store Id's linked to the UI display string. Add the strings with Id's referenced from the header file to the resource-definition script file. You can use [Visual Studio Resource Editor][VS Resource Editor] to create and manage resource files.
- `resource.h`:
XXX must be a unique int in the list (mostly the int ID of the last string id plus one):
```cpp
#define IDS_MODULE_DISPLAYNAME XXX
```
- `StringTable` in resource-definition script file `validmodulename.rc`:
```
STRINGTABLE
BEGIN
IDS_MODULE_DISPLAYNAME L"Module Name"
END
```
- Use the `GET_RESOURCE_STRING(UINT resource_id)` method to consume strings in your code.
```cpp
#include <common.h>
extern "C" IMAGE_DOS_HEADER __ImageBase;
std::wstring GET_RESOURCE_STRING(IDS_MODULE_DISPLAYNAME)
```
### For C#
Use [XML resource file(.resx)][Resx Files] to store the UI display strings and [`Resource Manager`][Resource Manager] to consume those strings in the code. You can use [Visual Studio][Resx Files VS] to create and manage XML resources files.
- `Resources.resx`
```xml
<data name="ValidUIDisplayString" xml:space="preserve">
<value>Description to be displayed on UI.</value>
<comment>This text is displayed when XYZ button clicked.</comment>
</data>
```
- Use [`Resource Manager`][Resource Manager] to consume strings in code.
```csharp
System.Resources.ResourceManager manager = new System.Resources.ResourceManager(baseName, assembly);
string validUIDisplayString = manager.GetString("ValidUIDisplayString", resourceCulture);
```
In case of Visual Studio is used to create the resource file. Simply use the `Resources` class in auto-generated `Resources.Designer.cs` file to access the strings which encapsulate the [`Resource Manager`][Resource Manager] logic.
```csharp
string validUIDisplayString = Resources.ValidUIDisplayString;
```
## More On Coding Guidance
Please review these brief docs below relating to our coding standards etc.
* [Coding Style](./style.md)
* [Code Organization](./readme.md)
[VS Resource Editor]: https://docs.microsoft.com/en-us/cpp/windows/resource-editors?view=vs-2019
[String Table]: https://docs.microsoft.com/en-us/windows/win32/menurc/stringtable-resource
[Resx Files VS]: https://docs.microsoft.com/en-us/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resource-files-in-visual-studio
[Resx Files]: https://docs.microsoft.com/en-us/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resources-in-resx-files
[Resource Manager]: https://docs.microsoft.com/en-us/dotnet/api/system.resources.resourcemanager?view=netframework-4.8

View File

@ -10,12 +10,17 @@
## Github Workflow
- Follow the PR template, in particular make sure there is open issue for the new PR.
- When the PR is approved, let the owner of the PR merge it.
- Before starting to work on a fix/feature, make sure there is an open issue to track the work.
- Add the `In progress` label to the issue, if not already present also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set.
- If you are a community contributor, you will not be able to add labels to the issue, in that case just add a comment saying that you started to work on the issue and try to give an estimate for the delivery date.
- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item.
- When opening a PR, follow the PR template.
- When the PR is approved, let the owner of the PR merge it. For community contributions the reviewer that approved the PR can also merge it.
- Use the `Squash and merge` option to merge a PR, if you don't want to squash it because there are logically different commits, use `Rebase and merge`.
- We don't close issues automatically when referenced in a PR, so after the OR is merged:
- mark the issue(s) fixed by the PR with the `resolved` label.
- don't close the issue if it's a bug in the current release since users tend to not search for closed issues, we will close the resolved issues when a new released is published.
- We don't close issues automatically when referenced in a PR, so after the PR is merged:
- mark the issue(s), that the PR solved, with the `Resolution-Fix-Committed` label, remove the `In progress` label and if the issue is assigned to a project, move the item to the `Done` status.
- don't close the issue if it's a bug in the current released version since users tend to not search for closed issues, we will close the resolved issues when a new version is released.
- if it's not a code fix that effects the end user, the issue can be closed (for example a fix in the build or a code refactoring and so on).
## Repository Overview

View File

@ -0,0 +1,50 @@
# PowerToys and running as Administrator
## Too long, Didn't Read 😁
If you're running any application as an administrator (aka elevated) and PowerToys is not, a few things may not work correctly when the elevated applications are in focus or trying to interact with a PowerToys feature like FancyZones.
## Having PowerToys keep functioning properly
We understand users will run applications elevated. We do as well. We have two options for you when this scenario happens:
1. **Recommended:** PowerToys will prompt when we detect a process that is elevated. Go to PowerToys settings inside the General Tab and click "Relaunch as adminstrator".
2. Enable "Always run as administrator" in the PowerToys settings.
## What is "Run as Administrator" / Elevated processes
This is when a process runs with "elevated" privileges. Typically this would be associated with the administrator accounts on a system.
Basically it runs with additional access to the operating system. Most things do not need run elevated. A common scenario would be needing to run certain PowerShell commands or edit the registry.
How do i know my application is "elevated"? If you see this prompt (User Access Control prompt), the application is requesting it:
![alt text][uac]
At times also, elevated terminals for instance, they will typically have the phrase "Administrator" appended to the title bar. Be warned, this isn't always the case it will be appended.
![alt text][elevatedWindow]
## When does PowerToys need this
PowerToys in itself does not. It only needs to be elevated when it has to interact with other applications that are running elevated. If those applications are in focus, PowerToys may not function unless it is elevated as well.
These are the two scenarios we will not work in:
1. Intercepting certain types of keyboard strokes
2. Resizing / Moving windows
### PowerToys affected
1. FancyZones
- Snapping a window into a zone
- Moving the window to a different zone
2. Shortcut guide
- Display shortcut
3. Keyboard remapper
- key to key remapping
- Global level shortcuts remapping
- App-targeted shortcuts remapping
[uac]: ../images/runAsAdmin/uac.png "User access control (UAC)"
[elevatedWindow]: ../images/runAsAdmin/elevatedWindows.png "Run as admin"

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -3,7 +3,7 @@
<Package ID="PowerToys-x64" ProcessorArchitecture="x64">
<Files>
<File DestinationPath="License.rtf" SourcePath="..\..\License.rtf"/>
<File DestinationPath="License.rtf" SourcePath="..\License.rtf"/>
<File DestinationPath="action_runner.exe" SourcePath="..\..\x64\Release\action_runner.exe"/>
<File DestinationPath="PowerToys.exe" SourcePath="..\..\x64\Release\PowerToys.exe"/>
@ -16,7 +16,14 @@
<File DestinationPath="modules\Microsoft.Xaml.Behaviors.dll" SourcePath="..\..\x64\Release\modules\Microsoft.Xaml.Behaviors.dll"/>
<File DestinationPath="modules\PowerRenameExt.dll" SourcePath="..\..\x64\Release\modules\PowerRenameExt.dll"/>
<File DestinationPath="modules\shortcut_guide.dll" SourcePath="..\..\x64\Release\modules\shortcut_guide.dll"/>
<File DestinationPath="modules\PowerRenameUWPUI.exe" SourcePath="..\..\x64\Release\PowerRenameUWPUI.exe"/>
<File DestinationPath="modules\PowerRenameUWPUI.exe" SourcePath="..\..\x64\Release\modules\PowerRenameUWPUI.exe"/>
<File DestinationPath="modules\ImageResizer.exe" SourcePath="..\..\x64\Release\modules\ImageResizer.exe"/>
<File DestinationPath="modules\ImageResizerExt.dll" SourcePath="..\..\x64\Release\modules\ImageResizerExt.dll"/>
<File DestinationPath="modules\GalaSoft.MvvmLight.dll" SourcePath="..\..\x64\Release\modules\GalaSoft.MvvmLight.dll"/>
<File DestinationPath="modules\GalaSoft.MvvmLight.Platform.dll" SourcePath="..\..\x64\Release\modules\GalaSoft.MvvmLight.Platform.dll"/>
<File DestinationPath="modules\GalaSoft.MvvmLight.Extras.dll" SourcePath="..\..\x64\Release\modules\GalaSoft.MvvmLight.Extras.dll"/>
<File DestinationPath="modules\System.Windows.Interactivity.dll" SourcePath="..\..\x64\Release\modules\System.Windows.Interactivity.dll"/>
<File DestinationPath="modules\Newtonsoft.Json.dll" SourcePath="..\..\x64\Release\modules\Newtonsoft.Json.dll"/>
<File DestinationPath="modules\System.Text.Json.dll" SourcePath="..\..\x64\Release\modules\System.Text.Json.dll"/>
<File DestinationPath="modules\System.Memory.dll" SourcePath="..\..\x64\Release\modules\System.Memory.dll"/>
<File DestinationPath="modules\System.Buffers.dll" SourcePath="..\..\x64\Release\modules\System.Buffers.dll"/>
@ -26,6 +33,13 @@
<File DestinationPath="modules\System.ValueTuple.dll" SourcePath="..\..\x64\Release\modules\System.ValueTuple.dll"/>
<File DestinationPath="modules\System.Numerics.Vectors.dll" SourcePath="..\..\x64\Release\modules\System.Numerics.Vectors.dll"/>
<File DestinationPath="modules\Microsoft.Bcl.AsyncInterfaces.dll" SourcePath="..\..\x64\Release\modules\Microsoft.Bcl.AsyncInterfaces.dll"/>
<File DestinationPath="modules\powerpreview.dll" SourcePath="..\..\x64\Release\modules\powerpreview.dll"/>
<File DestinationPath="modules\PreviewHandlerCommon.dll" SourcePath="..\..\x64\Release\modules\PreviewHandlerCommon.dll"/>
<File DestinationPath="modules\SvgPreviewHandler.dll" SourcePath="..\..\x64\Release\modules\SvgPreviewHandler.dll"/>
<File DestinationPath="modules\MarkdownPreviewHandler.dll" SourcePath="..\..\x64\Release\modules\MarkdownPreviewHandler.dll"/>
<File DestinationPath="modules\Markdig.Signed.dll" SourcePath="..\..\x64\Release\modules\Markdig.Signed.dll"/>
<File DestinationPath="modules\HtmlAgilityPack.dll" SourcePath="..\..\x64\Release\modules\HtmlAgilityPack.dll"/>
<File DestinationPath="registry.dat" SourcePath="registry.dat"/>
<File DestinationPath="modules\FancyZonesEditor.exe.config" SourcePath="..\..\x64\Release\modules\FancyZonesEditor.exe.config"/>
@ -34,6 +48,27 @@
<File DestinationPath="svgs\*" SourcePath="..\..\x64\Release\svgs\*"/>
<File DestinationPath="settings-html\**" SourcePath="..\..\x64\Release\settings-html\**"/>
<File DestinationPath="Images\*.png" SourcePath="Images\*.png"/>
<!-- Resource files for ar,bg,ca,cs,de,es,eu-ES,fr,he,hu,it,nb-NO,nl,pl,pt-BR,ru,sk,tr,zh-Hans -->
<File DestinationPath="modules\ar\**" SourcePath="..\..\x64\Release\modules\ar\**"/>
<File DestinationPath="modules\bg\**" SourcePath="..\..\x64\Release\modules\bg\**"/>
<File DestinationPath="modules\ca\**" SourcePath="..\..\x64\Release\modules\ca\**"/>
<File DestinationPath="modules\cs\**" SourcePath="..\..\x64\Release\modules\cs\**"/>
<File DestinationPath="modules\de\**" SourcePath="..\..\x64\Release\modules\de\**"/>
<File DestinationPath="modules\es\**" SourcePath="..\..\x64\Release\modules\es\**"/>
<File DestinationPath="modules\eu-ES\**" SourcePath="..\..\x64\Release\modules\eu-ES\**"/>
<File DestinationPath="modules\fr\**" SourcePath="..\..\x64\Release\modules\fr\**"/>
<File DestinationPath="modules\he\**" SourcePath="..\..\x64\Release\modules\he\**"/>
<File DestinationPath="modules\hu\**" SourcePath="..\..\x64\Release\modules\hu\**"/>
<File DestinationPath="modules\it\**" SourcePath="..\..\x64\Release\modules\it\**"/>
<File DestinationPath="modules\nb-NO\**" SourcePath="..\..\x64\Release\modules\nb-NO\**"/>
<File DestinationPath="modules\nl\**" SourcePath="..\..\x64\Release\modules\nl\**"/>
<File DestinationPath="modules\pl\**" SourcePath="..\..\x64\Release\modules\pl\**"/>
<File DestinationPath="modules\pt-BR\**" SourcePath="..\..\x64\Release\modules\pt-BR\**"/>
<File DestinationPath="modules\ru\**" SourcePath="..\..\x64\Release\modules\ru\**"/>
<File DestinationPath="modules\sk\**" SourcePath="..\..\x64\Release\modules\sk\**"/>
<File DestinationPath="modules\tr\**" SourcePath="..\..\x64\Release\modules\tr\**"/>
<File DestinationPath="modules\zh-Hans\**" SourcePath="..\..\x64\Release\modules\zh-Hans\**"/>
</Files>
</Package>
</PackageFamily>

View File

@ -2,9 +2,12 @@
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop2="http://schemas.microsoft.com/appx/manifest/desktop/windows10/2"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" IgnorableNamespaces="desktop4">
<Identity Name="Microsoft.PowerToys" Version="0.15.2.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" ProcessorArchitecture="x64" />
@ -28,6 +31,12 @@
<Application Id="PowerToys" Executable="PowerToys.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="PowerToys (Experimental)" Description="Windows system utilities to maximize productivity" Square150x150Logo="Images\logo150.png" Square44x44Logo="Images\logo44.png" BackgroundColor="transparent" />
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="powertoys">
<uap:Logo>images\logo.png</uap:Logo>
<uap:DisplayName>Powertoys custom protocol</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
<uap5:Extension Category="windows.startupTask" Executable="PowerToys.exe" EntryPoint="Windows.FullTrustApplication">
<uap5:StartupTask TaskId="PowerToysStartupTaskID" Enabled="true" DisplayName="PowerToys" />
</uap5:Extension>
@ -36,6 +45,9 @@
<com:ExeServer Executable="modules\PowerRenameUWPUI.exe" DisplayName="PowerRenameUWPUI">
<com:Class Id="0440049F-D1DC-4E46-B27B-98393D79486B"/>
</com:ExeServer>
<com:SurrogateServer DisplayName="ImageResizerExt">
<com:Class Id="51B4D7E5-7568-4234-B4BB-47FB3C016A69" Path="modules\ImageResizerExt.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
@ -46,8 +58,35 @@
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="DirectoryPowerRename" Clsid="0440049F-D1DC-4E46-B27B-98393D79486B" />
</desktop5:ItemType>
<desktop4:ItemType Type="*">
<desktop4:Verb Id="ImageResizer" Clsid="51B4D7E5-7568-4234-B4BB-47FB3C016A69" />
</desktop4:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap3:FileTypeAssociation Name="mdpreviewhandler" desktop2:AllowSilentDefaultTakeOver="true">
<uap:SupportedFileTypes>
<uap:FileType>.md</uap:FileType>
</uap:SupportedFileTypes>
<desktop2:DesktopPreviewHandler Clsid="E0907A95-6F9A-4D1B-A97A-7D9D2648881E"/>
</uap3:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.fileTypeAssociation">
<uap3:FileTypeAssociation Name="svgpreviewhandler" desktop2:AllowSilentDefaultTakeOver="true">
<uap:SupportedFileTypes>
<uap:FileType>.svg</uap:FileType>
</uap:SupportedFileTypes>
<desktop2:DesktopPreviewHandler Clsid="74619BDA-A66B-451D-864C-A7726F5FE650"/>
</uap3:FileTypeAssociation>
</uap:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Preview Handler" AppId="E39A92FE-D89A-417B-9B9D-F0B6BD564B36" SystemSurrogate="PreviewHost">
<com:Class Id="74619BDA-A66B-451D-864C-A7726F5FE650" Path="modules\powerpreview.dll" ThreadingModel="Both"/>
<com:Class Id="E0907A95-6F9A-4D1B-A97A-7D9D2648881E" Path="modules\powerpreview.dll" ThreadingModel="Both"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<Extension Category="windows.backgroundTasks" EntryPoint="PowerToysNotifications.BackgroundHandler">
<BackgroundTasks>
<Task Type="general" />

View File

@ -1 +1,13 @@
makeappx build /v /overwrite /f PackagingLayout.xml /id "PowerToys-x64" /op bin\
param (
[bool]$debug = 0
)
$PackagingLayoutFile = "PackagingLayout.xml"
if ($debug) {
(Get-Content $PackagingLayoutFile) `
-replace 'x64\\Release\\', 'x64\Debug\' `
| Out-File -Encoding utf8 "$env:temp\$PackagingLayoutFile"
$PackagingLayoutFile = "$env:temp\$PackagingLayoutFile"
}
makeappx build /v /overwrite /f $PackagingLayoutFile /id "PowerToys-x64" /op bin\

BIN
installer/MSIX/registry.dat Normal file

Binary file not shown.

BIN
installer/MSIX/registry.reg Normal file

Binary file not shown.

View File

@ -15,7 +15,6 @@
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DefineConstants>Debug</DefineConstants>
<OutputPath>$(Platform)\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Platform)\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>

View File

@ -22,7 +22,7 @@
Property="PREVIOUSVERSIONSINSTALLED"
IncludeMinimum="yes" IncludeMaximum="no" />
</Upgrade>
<MediaTemplate EmbedCab="yes" />
<Property Id="WINDOWSBUILDNUMBER" Secure="yes">
@ -32,11 +32,12 @@
<![CDATA[(WINDOWSBUILDNUMBER >= 17134)]]>
</Condition>
<Icon Id="powertoys.ico" SourceFile="$(var.BinX64Dir)\svgs\icon.ico"/>
<Property Id="ARPPRODUCTICON" Value="powertoys.ico" />
<Icon Id="powertoys.exe" SourceFile="$(var.BinX64Dir)\svgs\icon.ico"/>
<Property Id="ARPPRODUCTICON" Value="powertoys.exe" />
<Feature Id="CoreFeature" Title="PowerToys" AllowAdvertise="no" Absent="disallow" TypicalDefault="install"
Description="Contains the Shortcut Guide and Fancy Zones features.">
<ComponentGroupRef Id="CoreComponents" />
<ComponentGroupRef Id="ResourcesComponents" />
</Feature>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<UI>
@ -55,7 +56,7 @@
</UI>
<WixVariable Id="WixUIBannerBmp" Value="$(var.ProjectDir)\Bitmaps\banner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="$(var.ProjectDir)\Bitmaps\dialog.bmp" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.RepoDir)\License.rtf" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.RepoDir)\installer\License.rtf" />
<Property Id="INSTALLSTARTMENUSHORTCUT" Value="1"/>
<Property Id="CREATESCHEDULEDTASK" Value="1"/>
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
@ -66,6 +67,9 @@
<Property Id ="EXISTINGPOWERRENAMEEXTPATH">
<RegistrySearch Id="ExistingExtPath" Root="HKCR" Key="CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}\InprocServer32" Type="raw"/>
</Property>
<Property Id ="EXISTINGIMAGERESIZERPATH">
<RegistrySearch Id="ExistingImageResizerPath" Root="HKCU" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32" Type="raw"/>
</Property>
<InstallExecuteSequence>
<Custom Action="SetRegisterPowerToysSchTaskParam" Before="RegisterPowerToysSchTask" />
@ -144,7 +148,7 @@
BinaryKey="PTCustomActions"
DllEntry="TelemetryLogUninstallFailCA"
/>
<CustomAction Id="TelemetryLogRepairCancel"
Return="ignore"
Impersonate="yes"
@ -161,9 +165,9 @@
<!-- Close 'PowerToys.exe' before uninstall-->
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" />
<!-- Restart explorer.exe if we detect existing powerrenameext.dll installation -->
<!-- Restart explorer.exe if we detect existing PowerRenameExt.dll or ImageResizerExt.dll installation -->
<util:CloseApplication Target="explorer.exe" RebootPrompt="no" TerminateProcess="0">
EXISTINGPOWERRENAMEEXTPATH
EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH
</util:CloseApplication>
<util:CloseApplication CloseMessage="yes" Target="PowerToys.exe" ElevatedCloseMessage="yes" RebootPrompt="no" TerminateProcess="0" />
</Product>
@ -177,7 +181,25 @@
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="PowerToys">
<Directory Id="SvgsInstallFolder" Name="svgs"/>
<Directory Id="ModulesInstallFolder" Name="modules"/>
<Directory Id="ModulesInstallFolder" Name="modules">
<!-- Resource file directories -->
<?foreach Language in ar;bg;ca;cs;de;es;eu-ES;fr;he;hu;it;nb-NO;nl;pl;pt-BR;ru;sk;tr;zh-Hans?>
<!--NB: Ids can't contain hyphens-->
<?if $(var.Language) = eu-ES?>
<?define IdSafeLanguage = eu_ES?>
<?elseif $(var.Language) = nb-NO?>
<?define IdSafeLanguage = nb_NO?>
<?elseif $(var.Language) = pt-BR?>
<?define IdSafeLanguage = pt_BR?>
<?elseif $(var.Language) = zh-Hans?>
<?define IdSafeLanguage = zh_Hans?>
<?else?>
<?define IdSafeLanguage = $(var.Language)?>
<?endif?>
<Directory Id="Resources$(var.IdSafeLanguage)Folder" Name="$(var.Language)" />
<?undef IdSafeLanguage?>
<?endforeach?>
</Directory>
<Directory Id="SettingsHtmlInstallFolder" Name="settings-html">
<Directory Id="SettingsHtmlDistInstallFolder" Name="dist"/>
</Directory>
@ -206,7 +228,7 @@
Description="PowerToys - Windows system utilities to maximize productivity"
Directory="ApplicationProgramsFolder"
WorkingDirectory="INSTALLFOLDER"
Icon="powertoys.ico"
Icon="powertoys.exe"
IconIndex="0"
Advertise="yes">
<ShortcutProperty Key="System.AppUserModel.ID" Value="Microsoft.PowerToysWin32"/>
@ -214,8 +236,20 @@
</Shortcut>
</File>
<RegistryKey Root="HKCR" Key="powertoys" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Value="URL:PowerToys custom internal URI protocol"/>
<RegistryKey Key="DefaultIcon">
<RegistryValue Type="string" Value="PowerToys.exe" />
</RegistryKey>
<RegistryKey Key="shell\open\command">
<RegistryValue Type="string" Value="&quot;[INSTALLFOLDER]PowerToys.exe&quot; &quot;%1&quot;" />
</RegistryKey>
</RegistryKey>
<RemoveFolder Id="DeleteShortcutFolder" Directory="ApplicationProgramsFolder" On="uninstall" />
</Component>
<Component Id="settings_exe" Guid="A5A461A9-7097-4CBA-9D39-3DBBB6B7B80C" Win64="yes">
<File Id="PowerToysSettings.exe" KeyPath="yes" Checksum="yes" />
</Component>
@ -223,7 +257,7 @@
<File Id="Notifications.dll" KeyPath="yes" Checksum="yes" />
</Component>
<Component Id="License_rtf" Guid="3E5AE43B-CFB4-449B-A346-94CAAFF3312E" Win64="yes">
<File Source="$(var.RepoDir)\License.rtf" Id="License.rtf" KeyPath="yes" />
<File Source="$(var.RepoDir)\installer\License.rtf" Id="License.rtf" KeyPath="yes" />
</Component>
</DirectoryRef>
<DirectoryRef Id="SvgsInstallFolder" FileSource="$(var.BinX64Dir)\svgs\">
@ -274,7 +308,167 @@
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey>
<RegistryKey Root="HKCR" Key="AllFileSystemObjects\ShellEx\ContextMenuHandlers\PowerRenameExt">
<RegistryValue Type="string" Value="{0440049F-D1DC-4E46-B27B-98393D79486B}"/>
<RegistryValue Type="string" Value="{0440049F-D1DC-4E46-B27B-98393D79486B}"/>
</RegistryKey>
</Component>
<Component Id="Module_WindowWalker" Guid="0F96981C-5D36-4467-9515-71FB0CE72F6F" Win64="yes">
<File Source="$(var.BinX64Dir)\modules\WindowWalker.exe" />
<File Source="$(var.BinX64Dir)\modules\WindowWalker.dll" />
<File Source="$(var.BinX64Dir)\modules\MaterialDesignColors.dll" />
<File Source="$(var.BinX64Dir)\modules\MaterialDesignThemes.Wpf.dll" />
</Component>
<Component Id="Module_ImageResizer" Guid="96E63289-759C-4A73-A56B-EE7429932F72" Win64="yes">
<File Source="$(var.BinX64Dir)\modules\ImageResizer.exe">
<netfx:NativeImage Id="ImageResizer.exe" Platform="all" Priority="0" />
</File>
<File Source="$(var.BinX64Dir)\modules\GalaSoft.MvvmLight.dll" />
<File Source="$(var.BinX64Dir)\modules\GalaSoft.MvvmLight.Platform.dll" />
<File Source="$(var.BinX64Dir)\modules\GalaSoft.MvvmLight.Extras.dll" />
<File Source="$(var.BinX64Dir)\modules\System.Windows.Interactivity.dll">
<!-- NB: Needed since it's only referenced in XAML. -->
<netfx:NativeImage Id="Interactivity" Platform="all" Priority="0"/>
</File>
<File Source="$(var.BinX64Dir)\modules\Newtonsoft.Json.dll" />
<File Source="$(var.BinX64Dir)\modules\ImageResizerExt.dll" KeyPath="yes" />
</Component>
<Component Id="Module_ImageResizer_Registry" Guid="8B593E2C-2D9B-4EBC-93F7-A2B69707DAC9" Win64="yes">
<RegistryKey Root="HKCU" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32">
<RegistryValue Value="[ModulesInstallFolder]ImageResizerExt.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Apartment" Type="string" />
</RegistryKey>
<!-- Registry Key for the drag and drop handler -->
<RegistryValue Root="HKCU"
Key="Software\Classes\Directory\ShellEx\DragDropHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<!-- Registry Keys for the context menu handler for each of the following image formats: bmp, dib, gif, jfif, jpe, jpeg, jpg, jxr, png, rle, tif, tiff, wdp -->
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.bmp\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.dib\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.gif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.jfif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.jpe\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.jpeg\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.jpg\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.jxr\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.png\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.rle\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.tif\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.tiff\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\SystemFileAssociations\.wdp\ShellEx\ContextMenuHandlers\ImageResizer"
Value="{51B4D7E5-7568-4234-B4BB-47FB3C016A69}"
Type="string" />
</Component>
<Component Id="Module_PowerPreview" Guid="FF1700D5-1B07-4E07-9A62-4D206645EEA9" Win64="yes">
<!-- Component to include PowerPreview Module Source dll's -->
<!-- File to include PowerPreview Module native dll -->
<File Source="$(var.BinX64Dir)\modules\powerpreview.dll" KeyPath="yes" />
<!-- File to include common library used by preview handlers -->
<File Source="$(var.BinX64Dir)\modules\PreviewHandlerCommon.dll" />
<!-- File to include dll for Svg Preview Handler -->
<File Source="$(var.BinX64Dir)\modules\SvgPreviewHandler.dll" />
<!-- Files to include dll's for Markdown Preview Handler and it's dependencies -->
<File Source="$(var.BinX64Dir)\modules\MarkdownPreviewHandler.dll" />
<File Source="$(var.BinX64Dir)\modules\Markdig.Signed.dll" />
<File Source="$(var.BinX64Dir)\modules\HtmlAgilityPack.dll" />
</Component>
<Component Id="Module_PowerPreview_PerUserRegistry" Guid="CD90ADC0-7CD5-4A62-B0AF-23545C1E6DD3" Win64="yes">
<!-- Added a separate component for Per-User registry changes -->
<!-- Registry Key for Class Registration of Svg Preview Handler -->
<RegistryKey Root="HKCU" Key="Software\Classes\CLSID\{ddee2b8a-6807-48a6-bb20-2338174ff779}">
<RegistryValue Type="string" Value="SvgPreviewHandler.SvgPreviewHandler" />
<RegistryValue Type="string" Name="DisplayName" Value="Svg Preview Handler" />
<RegistryValue Type="string" Name="AppID" Value="{CF142243-F059-45AF-8842-DBBE9783DB14}" />
<RegistryValue Type="string" Key="Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Value=""/>
<RegistryValue Type="string" Key="InprocServer32" Value="mscoree.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="Assembly" Value="SvgPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32" Name="Class" Value="SvgPreviewHandler.SvgPreviewHandler" />
<RegistryValue Type="string" Key="InprocServer32" Name="RuntimeVersion" Value="v4.0.30319" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Both" />
<RegistryValue Type="string" Key="InprocServer32" Name="CodeBase" Value="file:///[ModulesInstallFolder]SvgPreviewHandler.dll" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Assembly" Value="SvgPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Class" Value="SvgPreviewHandler.SvgPreviewHandler" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="RuntimeVersion" Value="v4.0.30319" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="CodeBase" Value="file:///[ModulesInstallFolder]SvgPreviewHandler.dll" />
</RegistryKey>
<!-- Registry Key for Class Registration of Markdown Preview Handler -->
<RegistryKey Root="HKCU" Key="Software\Classes\CLSID\{45769bcc-e8fd-42d0-947e-02beef77a1f5}">
<RegistryValue Type="string" Value="MarkdownPreviewHandler.MarkdownPreviewHandler" />
<RegistryValue Type="string" Name="DisplayName" Value="Markdown Preview Handler" />
<RegistryValue Type="string" Name="AppID" Value="{CF142243-F059-45AF-8842-DBBE9783DB14}" />
<RegistryValue Type="string" Key="Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}" Value="" />
<RegistryValue Type="string" Key="InprocServer32" Value="mscoree.dll" />
<RegistryValue Type="string" Key="InprocServer32" Name="Assembly" Value="MarkdownPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32" Name="Class" Value="MarkdownPreviewHandler.MarkdownPreviewHandler" />
<RegistryValue Type="string" Key="InprocServer32" Name="RuntimeVersion" Value="v4.0.30319" />
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Both" />
<RegistryValue Type="string" Key="InprocServer32" Name="CodeBase" Value="file:///[ModulesInstallFolder]MarkdownPreviewHandler.dll" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Assembly" Value="MarkdownPreviewHandler, Version=$(var.Version).0, Culture=neutral" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="Class" Value="MarkdownPreviewHandler.MarkdownPreviewHandler" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="RuntimeVersion" Value="v4.0.30319" />
<RegistryValue Type="string" Key="InprocServer32\$(var.Version).0" Name="CodeBase" Value="file:///[ModulesInstallFolder]MarkdownPreviewHandler.dll" />
</RegistryKey>
<!-- Registry Key for AppID registration -->
<RegistryKey Root="HKCU" Key="Software\Classes\AppID\{CF142243-F059-45AF-8842-DBBE9783DB14}">
<RegistryValue Type="expandable" Name="DllSurrogate" Value="%SystemRoot%\system32\prevhost.exe" />
</RegistryKey>
<!-- Add Svg preview handler to preview handlers list -->
<RegistryKey Root="HKCU" Key="Software\Microsoft\Windows\CurrentVersion\PreviewHandlers">
<RegistryValue Type="string" Name="{ddee2b8a-6807-48a6-bb20-2338174ff779}" Value="Svg Preview Handler" />
</RegistryKey>
<!-- Add Markdown preview handler to preview handlers list -->
<RegistryKey Root="HKCU" Key="Software\Microsoft\Windows\CurrentVersion\PreviewHandlers">
<RegistryValue Type="string" Name="{45769bcc-e8fd-42d0-947e-02beef77a1f5}" Value="Markdown Preview Handler" />
</RegistryKey>
<!-- Add file type association for Svg Preview Handler -->
<RegistryKey Root="HKCU" Key="Software\Classes\.svg\shellex">
<RegistryValue Type="string" Key="{8895b1c6-b41f-4c1c-a562-0d564250836f}" Value="{ddee2b8a-6807-48a6-bb20-2338174ff779}" />
</RegistryKey>
<!-- Add file type association for Markdown Preview Handler -->
<RegistryKey Root="HKCU" Key="Software\Classes\.md\shellex">
<RegistryValue Type="string" Key="{8895b1c6-b41f-4c1c-a562-0d564250836f}" Value="{45769bcc-e8fd-42d0-947e-02beef77a1f5}" />
</RegistryKey>
<!-- Update Key to use IE11 for prevhost.exe -->
<RegistryKey Root="HKCU" Key="Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION">
<RegistryValue Type="integer" Name="prevhost.exe" Value="11000" />
</RegistryKey>
</Component>
</DirectoryRef>
@ -306,7 +500,7 @@
Description="PowerToys - Windows system utilities to maximize productivity"
Target="[!PowerToys.exe]"
WorkingDirectory="INSTALLFOLDER"
Icon="powertoys.ico"
Icon="powertoys.exe"
Directory="DesktopFolder"/>
</Component>
</DirectoryRef>
@ -323,10 +517,38 @@
<ComponentRef Id="Module_FancyZones" />
<ComponentRef Id="DesktopShortcut" />
<ComponentRef Id="Module_PowerRename" />
<ComponentRef Id="Module_ImageResizer" />
<ComponentRef Id="Module_ImageResizer_Registry" />
<ComponentRef Id="Module_PowerPreview" />
<ComponentRef Id="Module_PowerPreview_PerUserRegistry" />
<ComponentRef Id="Module_WindowWalker" />
<ComponentRef Id="settings_exe" />
<ComponentRef Id="settings_html" />
<ComponentRef Id="settings_dark_html" />
<ComponentRef Id="settings_js_bundle" />
</ComponentGroup>
</Fragment>
<Fragment>
<ComponentGroup Id="ResourcesComponents">
<!-- Components for adding resource files -->
<?foreach Language in ar;bg;ca;cs;de;es;eu-ES;fr;he;hu;it;nb-NO;nl;pl;pt-BR;ru;sk;tr;zh-Hans?>
<!--NB: Ids can't contain hyphens-->
<?if $(var.Language) = eu-ES?>
<?define IdSafeLanguage = eu_ES?>
<?elseif $(var.Language) = nb-NO?>
<?define IdSafeLanguage = nb_NO?>
<?elseif $(var.Language) = pt-BR?>
<?define IdSafeLanguage = pt_BR?>
<?elseif $(var.Language) = zh-Hans?>
<?define IdSafeLanguage = zh_Hans?>
<?else?>
<?define IdSafeLanguage = $(var.Language)?>
<?endif?>
<Component Id="Resources_$(var.IdSafeLanguage)_Component" Directory="Resources$(var.IdSafeLanguage)Folder">
<File Id="Resources_ImageResizer_$(var.IdSafeLanguage)_File" Source="$(var.BinX64Dir)\modules\$(var.Language)\ImageResizer.resources.dll" />
</Component>
<?undef IdSafeLanguage?>
<?endforeach?>
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -221,22 +221,25 @@ UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall) {
}
// Run the task with the highest available privileges.
hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_LUA);
pPrincipal->Release();
ExitOnFailure(hr, "Cannot put principal run level: %x", hr);
// ------------------------------------------------------
// Save the task in the PowerToys folder.
hr = pTaskFolder->RegisterTaskDefinition(
_bstr_t(wstrTaskName.c_str()),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(username_domain),
_variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN,
_variant_t(L""),
&pRegisteredTask);
ExitOnFailure(hr, "Error saving the Task : %x", hr);
{
_variant_t SDDL_FULL_ACCESS_FOR_EVERYONE = L"D:(A;;FA;;;WD)";
hr = pTaskFolder->RegisterTaskDefinition(
_bstr_t(wstrTaskName.c_str()),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(username_domain),
_variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN,
SDDL_FULL_ACCESS_FOR_EVERYONE,
&pRegisteredTask);
ExitOnFailure(hr, "Error saving the Task : %x", hr);
}
WcaLog(LOGMSG_STANDARD, "Scheduled task created for the current user.");

View File

@ -20,7 +20,7 @@ For the first-time installation, you'll need to generate a self-signed certifica
**Note:** if you delete the folder, you will have to regenerate the key
#### Elevate `Developer PowerShell for VS` permissions due to unsigned file
`msix_reinstall.ps1` is unsigned, you'll need to elevate your prompt.
`reinstall_msix.ps1` is unsigned, you'll need to elevate your prompt.
1. Open `Developer PowerShell for VS` as admin
2. Run `Set-ExecutionPolicy -executionPolicy Unrestricted`
@ -31,10 +31,10 @@ In order to install the MSIX package without using the Microsoft Store, sideload
1. Make sure you've built the `Release` configuration of `powertoys.sln`
2. Open `Developer PowerShell for VS`
3. Navigate to your repo's `installer\MSIX`
4. Run `.\msix_reinstall.ps1` from the devenv powershell
4. Run `.\reinstall_msix.ps1` from the devenv powershell
### What msix_reinstall.ps1 does
`msix_reinstall.ps1` removes the current PowerToys installation, restarts explorer.exe (to update PowerRename shell extension), builds `PowerToys-x64.msix` package, signs it with a PowerToys_TemporaryKey.pfx, and finally installs it.
### What reinstall_msix.ps1 does
`reinstall_msix.ps1` removes the current PowerToys installation, restarts explorer.exe (to update PowerRename and ImageResizer shell extension), builds `PowerToys-x64.msix` package, signs it with a PowerToys_TemporaryKey.pfx, and finally installs it.
## Cleanup - Removing all .msi/.msix PowerToys installations
```ps

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Version>0.15.3</Version>
<Version>0.16.2</Version>
<DefineConstants>Version=$(Version);</DefineConstants>
</PropertyGroup>
</Project>

View File

@ -73,6 +73,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>../;$(IncludePath)</IncludePath>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
@ -80,6 +81,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>../;$(IncludePath)</IncludePath>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>

View File

@ -433,7 +433,24 @@ namespace UnitTestsCommonLib
compareJsons(expected, actual);
}
TEST_METHOD (SettingsAddStringMultiline)
TEST_METHOD(SettingsAddLargeHeader)
{
const auto value = L"large header sample text ";
Settings settings(nullptr, m_moduleName);
settings.add_header_szLarge(m_defaultSettingsName, m_defaultSettingsDescription, value);
auto expected = m_defaultSettingsJson;
auto expectedProperties = createSettingsProperties(L"header_large");
expectedProperties.SetNamedValue(L"value", json::JsonValue::CreateStringValue(value));
expected.GetNamedObject(L"properties").SetNamedValue(m_defaultSettingsName, expectedProperties);
const auto actual = json::JsonObject::Parse(settings.serialize());
compareJsons(expected, actual);
}
TEST_METHOD(SettingsAddStringMultiline)
{
const auto value = L"Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit,\nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident,\nsunt in culpa qui officia deserunt mollit anim id est laborum.";

View File

@ -48,9 +48,11 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@ -91,10 +93,11 @@
<Link>
<SubSystem>Windows</SubSystem>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>RuntimeObject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>RuntimeObject.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="UnitTestsCommon.cpp" />
<ClCompile Include="UnitTestsVersionHelper.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>

View File

@ -24,6 +24,9 @@
<ClCompile Include="UnitTestsVersionHelper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UnitTestsCommon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">

View File

@ -0,0 +1,57 @@
#include "pch.h"
#include "common.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTestsCommon
{
TEST_CLASS (CommonUtils)
{
std::vector<std::wstring> what_global{
L"TELEGRAM",
L"SUBLIME TEXT",
L"PROGRAM",
L"TEXT",
};
TEST_METHOD (FindAppNameInPathTest1)
{
std::wstring where(L"C:\\USERS\\GUEST\\APPDATA\\ROAMING\\TELEGRAM DESKTOP\\TELEGRAM.EXE");
bool ans = find_app_name_in_path(where, what_global);
Assert::IsTrue(ans);
}
TEST_METHOD (FindAppNameInPathTest2)
{
std::vector<std::wstring> what{
L"NOTEPAD",
};
std::wstring where(L"C:\\PROGRAM FILES\\NOTEPAD++\\NOTEPAD++.EXE");
bool ans = find_app_name_in_path(where, what);
Assert::IsTrue(ans);
}
TEST_METHOD (FindAppNameInPathTest3)
{
std::vector<std::wstring> what{
L"NOTEPAD++.EXE",
};
std::wstring where(L"C:\\PROGRAM FILES\\NOTEPAD++\\NOTEPAD++.EXE");
bool ans = find_app_name_in_path(where, what);
Assert::IsTrue(ans);
}
TEST_METHOD (FindAppNameInPathTest4)
{
std::wstring where(L"C:\\PROGRAM FILES\\SUBLIME TEXT 3\\SUBLIME_TEXT.EXE");
bool ans = find_app_name_in_path(where, what_global);
Assert::IsFalse(ans);
}
TEST_METHOD (FindAppNameInPathTest5)
{
std::vector<std::wstring> what{
L"NOTEPAD.EXE",
};
std::wstring where(L"C:\\PROGRAM FILES\\NOTEPAD++\\NOTEPAD++.EXE");
bool ans = find_app_name_in_path(where, what);
Assert::IsFalse(ans);
}
};
}

View File

@ -50,45 +50,51 @@ namespace UnitTestsVersionHelper
}
TEST_METHOD (whenMajorVersionIsGreaterComparationOperatorShouldReturnProperValue)
{
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper lhs(MAJOR_VERSION_0 + 1, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
Assert::IsTrue(lhs > rhs);
}
TEST_METHOD (whenMajorVersionIsLesserComparationOperatorShouldReturnProperValue)
{
VersionHelper rhs(MAJOR_VERSION_0 + 1, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper rhs(MAJOR_VERSION_0 + 1, MINOR_VERSION_12, REVISION_VERSION_0);
Assert::IsFalse(lhs > rhs);
}
TEST_METHOD (whenMajorVersionIsEqualComparationOperatorShouldCompareMinorVersionValue)
{
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12 - 1, REVISION_VERSION_0);
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12 - 1, REVISION_VERSION_0);
Assert::IsTrue(lhs > rhs);
}
TEST_METHOD (whenMajorVersionIsEqualComparationOperatorShouldCompareMinorVersionValue2)
{
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12 - 1, REVISION_VERSION_0);
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
Assert::IsFalse(lhs > rhs);
}
TEST_METHOD (whenMajorAndMinorVersionIsEqualComparationOperatorShouldCompareRevisionValue)
{
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0 + 1);
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
Assert::IsTrue(lhs > rhs);
}
TEST_METHOD (whenMajorAndMinorVersionIsEqualComparationOperatorShouldCompareRevisionValue2)
{
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0 + 1);
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0 + 1);
Assert::IsFalse(lhs > rhs);
}
TEST_METHOD (whenMajorMinorAndRevisionIsEqualGreaterThanOperatorShouldReturnFalse)
{
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
Assert::IsFalse(lhs > rhs);
}

View File

@ -27,37 +27,3 @@ VersionHelper::VersionHelper(int major, int minor, int revision) :
revision(revision)
{
}
bool VersionHelper::operator>(const VersionHelper& rhs)
{
if (major < rhs.major)
{
return false;
}
else if (major > rhs.major)
{
return true;
}
else
{
if (minor < rhs.minor)
{
return false;
}
else if (minor > rhs.minor)
{
return true;
}
else
{
if (revision < rhs.revision)
{
return false;
}
else
{
return true;
}
}
}
}

View File

@ -1,13 +1,14 @@
#pragma once
#include <string>
#include <compare>
struct VersionHelper
{
VersionHelper(std::string str);
VersionHelper(int major, int minor, int revision);
bool operator>(const VersionHelper& rhs);
auto operator<=>(const VersionHelper&) const = default;
int major;
int minor;

View File

@ -7,6 +7,7 @@
#include "version.h"
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "shlwapi.lib")
namespace localized_strings
{
@ -442,6 +443,7 @@ bool run_elevated(const std::wstring& file, const std::wstring& params)
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = 0;
exec_info.hInstApp = 0;
exec_info.nShow = SW_SHOWDEFAULT;
if (ShellExecuteExW(&exec_info))
{
@ -714,3 +716,18 @@ bool check_user_is_admin()
freeMemory(pSID, pGroupInfo);
return false;
}
bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wstring>& what)
{
for (const auto& row : what)
{
const auto pos = where.rfind(row);
const auto last_slash = where.rfind('\\');
//Check that row occurs in where, and its last occurrence contains in itself the first character after the last backslash.
if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash)
{
return true;
}
}
return false;
}

View File

@ -4,6 +4,7 @@
#include <Windows.h>
#include <string>
#include <memory>
#include <vector>
// Returns RECT with positions of the minmize/maximize buttons of the given window.
// Does not always work, since some apps draw custom toolbars.
@ -77,6 +78,9 @@ bool run_same_elevation(const std::wstring& file, const std::wstring& params);
// Returns true if the current process is running from administrator account
bool check_user_is_admin();
//Returns true when one or more strings from vector found in string
bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wstring>& what);
// Get the executable path or module name for modern apps
std::wstring get_process_path(DWORD pid) noexcept;
// Get the executable path or module name for modern apps

View File

@ -8,12 +8,7 @@
<HeaderLines Include="#define VERSION_MINOR $(Version.Split('.')[1])" />
<HeaderLines Include="#define VERSION_REVISION $(Version.Split('.')[2])" />
</ItemGroup>
<WriteLinesToFile
File="Generated Files\version_gen.h"
Lines="@(HeaderLines)"
Overwrite="true"
Encoding="Unicode"
WriteOnlyWhenDifferent="true" />
<WriteLinesToFile File="Generated Files\version_gen.h" Lines="@(HeaderLines)" Overwrite="true" Encoding="Unicode" WriteOnlyWhenDifferent="true" />
</Target>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
@ -34,6 +29,9 @@
<ProjectName>common</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ImportGroup Label="Shared">
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190716.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190716.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
@ -61,9 +59,11 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
@ -167,7 +167,16 @@
<ClCompile Include="window_helpers.cpp" />
<ClCompile Include="winstore.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190716.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.190716.2\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
</Target>
</Project>

View File

@ -9,9 +9,14 @@ namespace json
{
try
{
std::wifstream file(file_name.data(), std::ios::binary);
using isbi = std::istreambuf_iterator<wchar_t>;
return JsonValue::Parse(std::wstring{ isbi{ file }, isbi{} }).GetObjectW();
std::ifstream file(file_name.data(), std::ios::binary);
if (file.is_open())
{
using isbi = std::istreambuf_iterator<char>;
std::string obj_str{ isbi{ file }, isbi{} };
return JsonValue::Parse(winrt::to_hstring(obj_str)).GetObjectW();
}
return std::nullopt;
}
catch (...)
{
@ -21,6 +26,7 @@ namespace json
void to_file(std::wstring_view file_name, const JsonObject& obj)
{
std::wofstream{ file_name.data(), std::ios::binary } << obj.Stringify().c_str();
std::wstring obj_str{ obj.Stringify().c_str() };
std::ofstream{ file_name.data(), std::ios::binary } << winrt::to_string(obj_str);
}
}

View File

@ -14,6 +14,7 @@
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Windows.Management.Deployment.h>
#include "VersionHelper.h"
@ -23,6 +24,8 @@ namespace
const wchar_t* DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH = L"delete_previous_powertoys_confirm";
const wchar_t* USER_AGENT = L"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)";
const wchar_t* LATEST_RELEASE_ENDPOINT = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
const wchar_t* MSIX_PACKAGE_NAME = L"Microsoft.PowerToys";
const wchar_t* MSIX_PACKAGE_PUBLISHER = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US";
}
namespace localized_strings
@ -110,20 +113,45 @@ std::future<std::optional<new_version_download_info>> check_for_new_github_relea
winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") };
VersionHelper github_version(winrt::to_string(new_version));
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
if (current_version > github_version)
if (github_version > current_version)
{
co_return std::nullopt;
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str() };
}
else
{
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str() };
co_return std::nullopt;
}
}
catch (...)
{
co_return std::nullopt;
}
}
}
std::future<bool> uninstall_previous_msix_version_async()
{
winrt::Windows::Management::Deployment::PackageManager package_manager;
try
{
auto packages = package_manager.FindPackagesForUser({}, MSIX_PACKAGE_NAME, MSIX_PACKAGE_PUBLISHER);
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
for (auto package : packages)
{
VersionHelper msix_version(package.Id().Version().Major, package.Id().Version().Minor, package.Id().Version().Revision);
if (msix_version < current_version)
{
co_await package_manager.RemovePackageAsync(package.Id().FullName());
co_return true;
}
}
}
catch (...)
{
}
co_return false;
}

View File

@ -10,6 +10,8 @@ std::wstring get_msi_package_path();
bool uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation();
std::future<bool> uninstall_previous_msix_version_async();
struct new_version_download_info
{
winrt::Windows::Foundation::Uri release_page_uri;

View File

@ -73,6 +73,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>../../;$(IncludePath)</IncludePath>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
@ -80,6 +81,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>../../;$(IncludePath)</IncludePath>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>

View File

@ -3,7 +3,9 @@
#ifndef PCH_H
#define PCH_H
#pragma warning (disable: 5205)
#include <winrt/base.h>
#pragma warning (default: 5205)
#include <Windows.h>
#include <MsiQuery.h>
#include <Shlwapi.h>

View File

@ -145,10 +145,10 @@ void notifications::register_background_toast_handler()
}
}
void notifications::show_toast(std::wstring_view message)
void notifications::show_toast(std::wstring message, toast_params params)
{
// The toast won't be actually activated in the background, since it doesn't have any buttons
show_toast_with_activations(message, {}, {});
show_toast_with_activations(std::move(message), {}, {}, std::move(params));
}
inline void xml_escape(std::wstring data)
@ -182,13 +182,13 @@ inline void xml_escape(std::wstring data)
data.swap(buffer);
}
void notifications::show_toast_with_activations(std::wstring_view message, std::wstring_view background_handler_id, std::vector<button_t> buttons)
void notifications::show_toast_with_activations(std::wstring message, std::wstring_view background_handler_id, std::vector<action_t> actions, toast_params params)
{
// DO NOT LOCALIZE any string in this function, because they're XML tags and a subject to
// https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-xml-schema
std::wstring toast_xml;
toast_xml.reserve(1024);
toast_xml.reserve(2048);
std::wstring title{ L"PowerToys" };
if (winstore::running_as_packaged())
{
@ -200,28 +200,78 @@ void notifications::show_toast_with_activations(std::wstring_view message, std::
toast_xml += L"</text><text>";
toast_xml += message;
toast_xml += L"</text></binding></visual><actions>";
for (size_t i = 0; i < size(actions); ++i)
{
std::visit(overloaded{
[&](const snooze_button& b) {
const bool has_durations = !b.durations.empty() && size(b.durations) <= 5;
std::wstring selection_id = L"snoozeTime";
selection_id += static_cast<wchar_t>(L'0' + i);
if (has_durations)
{
toast_xml += LR"(<input id=")";
toast_xml += selection_id;
toast_xml += LR"(" type="selection" defaultInput=")";
toast_xml += std::to_wstring(b.durations[0].minutes);
toast_xml += LR"(">)";
for (const auto& duration : b.durations)
{
toast_xml += LR"(<selection id=")";
toast_xml += std::to_wstring(duration.minutes);
toast_xml += LR"(" content=")";
toast_xml += duration.label;
toast_xml += LR"("/>)";
}
toast_xml += LR"(</input>)";
}
},
[](const auto&) {} },
actions[i]);
}
for (size_t i = 0; i < size(buttons); ++i)
for (size_t i = 0; i < size(actions); ++i)
{
std::visit(overloaded{
[&](const link_button& b) {
toast_xml += LR"(<action activationType="protocol" arguments=")";
toast_xml += LR"(<action activationType="protocol" )";
if (b.context_menu)
{
toast_xml += LR"(placement="contextMenu" )";
}
toast_xml += LR"(arguments=")";
toast_xml += b.url;
toast_xml += LR"(" content=")";
toast_xml += b.label;
toast_xml += LR"("/>)";
toast_xml += LR"(" />)";
},
[&](const background_activated_button& b) {
toast_xml += LR"(<action activationType="background" arguments=")";
toast_xml += LR"(<action activationType="background" )";
if (b.context_menu)
{
toast_xml += LR"(placement="contextMenu" )";
}
toast_xml += LR"(arguments=")";
toast_xml += L"button_id=" + std::to_wstring(i); // pass the button ID
toast_xml += L"&amp;handler=";
toast_xml += background_handler_id;
toast_xml += LR"(" content=")";
toast_xml += b.label;
toast_xml += LR"("/>)";
toast_xml += LR"(" />)";
},
},
buttons[i]);
[&](const snooze_button& b) {
const bool has_durations = !b.durations.empty() && size(b.durations) <= 5;
std::wstring selection_id = L"snoozeTime";
selection_id += static_cast<wchar_t>(L'0' + i);
toast_xml += LR"(<action activationType="system" arguments="snooze" )";
if (has_durations)
{
toast_xml += LR"(hint-inputId=")";
toast_xml += selection_id;
toast_xml += '"';
}
toast_xml += LR"( content="" />)";
} },
actions[i]);
}
toast_xml += L"</actions></toast>";
@ -232,5 +282,22 @@ void notifications::show_toast_with_activations(std::wstring_view message, std::
const auto notifier = winstore::running_as_packaged() ? ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() :
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(WIN32_AUMID);
// Set a tag-related params if it has a valid length
if (params.tag.has_value() && params.tag->length() < 64)
{
notification.Tag(*params.tag);
if (!params.resend_if_scheduled)
{
for (const auto& scheduled_toast : notifier.GetScheduledToastNotifications())
{
if (scheduled_toast.Tag() == *params.tag)
{
return;
}
}
}
}
notifier.Show(notification);
}

View File

@ -1,8 +1,10 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include <variant>
#include <optional>
namespace notifications
{
@ -12,19 +14,38 @@ namespace notifications
void run_desktop_app_activator_loop();
struct snooze_duration
{
std::wstring label;
int minutes;
};
struct snooze_button
{
std::vector<snooze_duration> durations;
};
struct link_button
{
std::wstring_view label;
std::wstring_view url;
std::wstring label;
std::wstring url;
bool context_menu = false;
};
struct background_activated_button
{
std::wstring_view label;
std::wstring label;
bool context_menu = false;
};
using button_t = std::variant<link_button, background_activated_button>;
struct toast_params
{
std::optional<std::wstring_view> tag;
bool resend_if_scheduled = true;
};
void show_toast(std::wstring_view plaintext_message);
void show_toast_with_activations(std::wstring_view plaintext_message, std::wstring_view background_handler_id, std::vector<button_t> buttons);
using action_t = std::variant<link_button, background_activated_button, snooze_button>;
void show_toast(std::wstring plaintext_message, toast_params params = {});
void show_toast_with_activations(std::wstring plaintext_message, std::wstring_view background_handler_id, std::vector<action_t> actions, toast_params params = {});
}

View File

@ -0,0 +1,71 @@
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <limits>
#include "../timeutil.h"
namespace
{
const inline wchar_t CANT_DRAG_ELEVATED_DONT_SHOW_AGAIN_REGISTRY_PATH[] = LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\DontShowMeThisDialogAgain\{e16ea82f-6d94-4f30-bb02-d6d911588afd})";
const inline int64_t disable_interval_in_days = 30;
}
inline bool disable_cant_drag_elevated_warning()
{
HKEY key{};
if (RegCreateKeyExW(HKEY_CURRENT_USER,
CANT_DRAG_ELEVATED_DONT_SHOW_AGAIN_REGISTRY_PATH,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&key,
nullptr) != ERROR_SUCCESS)
{
return false;
}
const auto now = timeutil::now();
const size_t buf_size = sizeof(now);
if (RegSetValueExW(key, nullptr, 0, REG_QWORD, reinterpret_cast<const BYTE*>(&now), sizeof(now)) != ERROR_SUCCESS)
{
RegCloseKey(key);
return false;
}
RegCloseKey(key);
return true;
}
inline bool is_cant_drag_elevated_warning_disabled()
{
HKEY key{};
if (RegOpenKeyExW(HKEY_CURRENT_USER,
CANT_DRAG_ELEVATED_DONT_SHOW_AGAIN_REGISTRY_PATH,
0,
KEY_READ,
&key) != ERROR_SUCCESS)
{
return false;
}
std::wstring buffer(std::numeric_limits<time_t>::digits10 + 2, L'\0');
time_t last_disabled_time{};
DWORD time_size = static_cast<DWORD>(sizeof(last_disabled_time));
if (RegGetValueW(
key,
nullptr,
nullptr,
RRF_RT_REG_QWORD,
nullptr,
&last_disabled_time,
&time_size) != ERROR_SUCCESS)
{
RegCloseKey(key);
return false;
}
RegCloseKey(key);
return timeutil::diff::in_days(timeutil::now(), last_disabled_time) < disable_interval_in_days;
return false;
}

View File

@ -73,6 +73,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>Notifications</TargetName>
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>NOTIFICATIONSDLL</TargetName>
@ -81,6 +82,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>Notifications</TargetName>
<LinkIncremental>true</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>NOTIFICATIONSDLL</TargetName>

View File

@ -1,11 +1,10 @@
namespace PowerToysNotifications
{
[version(1)]
[default_interface]
runtimeclass BackgroundHandler : Windows.ApplicationModel.Background.IBackgroundTask
runtimeclass BackgroundHandler
{
BackgroundHandler();
void Run(Windows.ApplicationModel.Background.IBackgroundTaskInstance taskInstance);
}
}

View File

@ -75,9 +75,11 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>notifications</TargetName>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>notifications</TargetName>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>

View File

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

View File

@ -109,6 +109,17 @@ namespace PowerToysSettings
m_json.GetNamedObject(L"properties").SetNamedValue(name, ml_string);
}
void Settings::add_header_szLarge(std::wstring_view name, std::wstring_view description, std::wstring_view value)
{
json::JsonObject string;
string.SetNamedValue(L"display_name", json::value(description));
string.SetNamedValue(L"editor_type", json::value(L"header_large"));
string.SetNamedValue(L"value", json::value(value));
string.SetNamedValue(L"order", json::value(++m_curr_priority));
m_json.GetNamedObject(L"properties").SetNamedValue(name, string);
}
// add_color_picker overloads.
void Settings::add_color_picker(std::wstring_view name, UINT description_resource_id, std::wstring_view value)
{

View File

@ -50,6 +50,7 @@ namespace PowerToysSettings
void add_custom_action(std::wstring_view name, UINT description_resource_id, UINT button_text_resource_id, std::wstring_view value);
void add_custom_action(std::wstring_view name, std::wstring_view description, std::wstring_view button_text, std::wstring_view value);
void add_header_szLarge(std::wstring_view name, std::wstring_view description, std::wstring_view value);
// Serialize the internal json to a string.
std::wstring serialize();
// Serialize the internal json to the input buffer.

View File

@ -1,5 +1,7 @@
#include "window_helpers.h"
#include "pch.h"
#include <wil/Resource.h>
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p)
{
@ -27,4 +29,32 @@ HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p
}
return hwnd;
}
}
bool IsProcessOfWindowElevated(HWND window)
{
DWORD pid = 0;
GetWindowThreadProcessId(window, &pid);
if (!pid)
{
return false;
}
wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
pid) };
wil::unique_handle token;
bool elevated = false;
if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
{
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
{
return elevation.TokenIsElevated != 0;
}
}
return false;
}

View File

@ -1,4 +1,7 @@
#pragma once
#include "common.h"
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p);
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p);
// If HWND is already dead, we assume it wasn't elevated
bool IsProcessOfWindowElevated(HWND window);

View File

@ -46,10 +46,12 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@ -48,10 +48,12 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@ -12,6 +12,10 @@
<!-- Accent and AppTheme setting -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="CanvasZoneBackgroundBrush" Color="#BF333333"/>
<SolidColorBrush x:Key="GridZoneBackgroundBrush" Color="#FF1a1a1a"/>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -55,6 +55,12 @@ namespace FancyZonesEditor
previewChildrenCount++;
}
while (previewChildrenCount > _model.Zones.Count)
{
Preview.Children.RemoveAt(previewChildrenCount - 1);
previewChildrenCount--;
}
for (int i = 0; i < previewChildrenCount; i++)
{
Int32Rect rect = _model.Zones[i];
@ -65,6 +71,7 @@ namespace FancyZonesEditor
Canvas.SetTop(zone, rect.Y);
zone.Height = rect.Height;
zone.Width = rect.Width;
zone.LabelID.Content = i + 1;
}
}
}

View File

@ -16,6 +16,7 @@ namespace FancyZonesEditor
{
InitializeComponent();
_model = EditorOverlay.Current.DataContext as CanvasLayoutModel;
_stashedModel = (CanvasLayoutModel)_model.Clone();
}
private void OnAddZone(object sender, RoutedEventArgs e)
@ -24,7 +25,14 @@ namespace FancyZonesEditor
_offset += 100;
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
_stashedModel.RestoreTo(_model);
}
private int _offset = 100;
private CanvasLayoutModel _model;
private CanvasLayoutModel _stashedModel;
}
}

View File

@ -5,40 +5,113 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
Background="LightGray"
Opacity="0.75"
Background="Transparent"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="Frame">
<Grid.RowDefinitions>
<RowDefinition Height="8"/>
<RowDefinition Height="16"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="16"/>
<RowDefinition Height="8"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Thumb x:Name="NWResize" Cursor="SizeNWSE" Background="Black" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NWResize_DragStarted"/>
<Thumb x:Name="NEResize" Cursor="SizeNESW" Background="Black" Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NEResize_DragStarted"/>
<Thumb x:Name="SWResize" Cursor="SizeNESW" Background="Black" Grid.Row="4" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SWResize_DragStarted"/>
<Thumb x:Name="SEResize" Cursor="SizeNWSE" Background="Black" Grid.Row="4" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SEResize_DragStarted"/>
<Thumb x:Name="NResize" Cursor="SizeNS" Background="Black" Margin="1,0,1,0" Grid.Row="0" Grid.Column="2" DragDelta="UniversalDragDelta" DragStarted="NResize_DragStarted"/>
<Thumb x:Name="SResize" Cursor="SizeNS" Background="Black" Margin="1,0,1,0" Grid.Row="5" Grid.Column="2" DragDelta="UniversalDragDelta" DragStarted="SResize_DragStarted"/>
<Thumb x:Name="WResize" Cursor="SizeWE" Background="Black" Margin="0,1,0,1" Grid.Row="2" Grid.Column="0" Grid.RowSpan="2" DragDelta="UniversalDragDelta" DragStarted="WResize_DragStarted"/>
<Thumb x:Name="EResize" Cursor="SizeWE" Background="Black" Margin="0,1,0,1" Grid.Row="2" Grid.Column="4" Grid.RowSpan="2" DragDelta="UniversalDragDelta" DragStarted="EResize_DragStarted"/>
<DockPanel Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3">
<Button DockPanel.Dock="Right" Padding="8,0" Click="OnClose">
<Image Source="images/ChromeClose.png" Height="24" Width="24" />
</Button>
<Thumb x:Name="Caption" Cursor="SizeAll" Background="DarkGray" DragDelta="UniversalDragDelta" DragStarted="Caption_DragStarted"/>
</DockPanel>
<Rectangle Fill="LightGray" Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3"/>
<Canvas x:Name="Body" />
</Grid>
<UserControl.Resources>
<Style x:Key="CanvasZoneThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border x:Name="ThumbBorder" Opacity="0" BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates" >
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.15">
<VisualTransition.GeneratedEasingFunction>
<ExponentialEase EasingMode="EaseInOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ThumbBorder" Duration="0:0:0.15" Storyboard.TargetProperty="Opacity" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.6"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Opacity" TargetName="contentPresenter" Value="0.4"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Border BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" Background="{StaticResource CanvasZoneBackgroundBrush}" BorderThickness="1">
<Grid x:Name="Frame">
<Grid.RowDefinitions>
<RowDefinition Height="8"/>
<RowDefinition Height="16"/>
<RowDefinition Height="*"/>
<RowDefinition Height="16"/>
<RowDefinition Height="8"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Label Name="LabelID"
Content="ID"
Canvas.Left="10"
Canvas.Bottom="10"
FontSize="64"
FontFamily="Segoe UI Light"
Foreground="White"
Grid.Column="2"
Grid.Row="2"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center" />
<Thumb x:Name="Caption" Cursor="SizeAll" Background="Transparent" BorderThickness="3" Padding="4" Grid.Column="0" Grid.ColumnSpan="5" Grid.Row="0" Grid.RowSpan="5" DragDelta="UniversalDragDelta" DragStarted="Caption_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="NResize" Cursor="SizeNS" BorderThickness="0,3,0,0" Grid.ColumnSpan="5" DragDelta="UniversalDragDelta" DragStarted="NResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="SResize" Cursor="SizeNS" BorderThickness="0,0,0,3" Grid.Row="4" Grid.ColumnSpan="5" DragDelta="UniversalDragDelta" DragStarted="SResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="WResize" Cursor="SizeWE" BorderThickness="3,0,0,0" Grid.RowSpan="5" DragDelta="UniversalDragDelta" DragStarted="WResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="EResize" Cursor="SizeWE" BorderThickness="0,0,3,0" Grid.Column="4" Grid.RowSpan="5" DragDelta="UniversalDragDelta" DragStarted="EResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="NWResize" Cursor="SizeNWSE" BorderThickness="3,3,0,0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NWResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="NEResize" Cursor="SizeNESW" BorderThickness="0,3,3,0" Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="NEResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="SWResize" Cursor="SizeNESW" BorderThickness="3,0,0,3" Grid.Row="3" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SWResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Thumb x:Name="SEResize" Cursor="SizeNWSE" BorderThickness="0,0,3,3" Grid.Row="3" Grid.Column="3" Grid.RowSpan="2" Grid.ColumnSpan="2" DragDelta="UniversalDragDelta" DragStarted="SEResize_DragStarted" Style="{DynamicResource CanvasZoneThumbStyle}"/>
<Button Content="&#xE894;" BorderThickness="0" ToolTip="Delete zone" Background="Transparent" Foreground="White" FontSize="16" Padding="4" Click="OnClose" Grid.Row="2" Grid.Column="2" FontFamily="Segoe MDL2 Assets" HorizontalAlignment="Right" VerticalAlignment="Top" Style="{DynamicResource CloseButtonStyle}"/>
<Canvas x:Name="Body" />
</Grid>
</Border>
</UserControl>

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Text.Json;
using FancyZonesEditor.Utils;
namespace FancyZonesEditor
{
public class DashCaseNamingPolicy : JsonNamingPolicy
{
public static DashCaseNamingPolicy Instance { get; } = new DashCaseNamingPolicy();
public override string ConvertName(string name)
{
return name.UpperCamelCaseToDashCase();
}
}
}

View File

@ -21,14 +21,14 @@ namespace FancyZonesEditor
LayoutModel.SerializeDeletedCustomZoneSets();
_choosing = true;
_backToLayoutPicker = false;
Close();
EditorOverlay.Current.Close();
}
protected void OnClosed(object sender, EventArgs e)
{
if (!_choosing)
if (_backToLayoutPicker)
{
EditorOverlay.Current.ShowLayoutPicker();
}
@ -36,11 +36,10 @@ namespace FancyZonesEditor
protected void OnCancel(object sender, RoutedEventArgs e)
{
_choosing = true;
_backToLayoutPicker = true;
Close();
EditorOverlay.Current.ShowLayoutPicker();
}
private bool _choosing = false;
private bool _backToLayoutPicker = true;
}
}

View File

@ -58,6 +58,7 @@
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<PlatformTarget>x64</PlatformTarget>
@ -109,6 +110,8 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="DashCaseNamingPolicy.cs" />
<Compile Include="StringUtils.cs" />
<Compile Include="Converters\BooleanToBrushConverter.xaml.cs" />
<Compile Include="Converters\BooleanToIntConverter.xaml.cs" />
<Compile Include="CanvasEditor.xaml.cs">

View File

@ -1,32 +1,48 @@
<UserControl x:Class="FancyZonesEditor.GridEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl
x:Class="FancyZonesEditor.GridEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<Style TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="#F2F2F2"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="14" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="#F2F2F2" />
<Setter Property="Width" Value="150" />
</Style>
</UserControl.Resources>
<Grid>
<Canvas x:Name="Preview"/>
<Canvas x:Name="AdornerLayer"/>
<Canvas x:Name="MergePanel" Visibility="Collapsed" MouseUp="MergePanelMouseUp">
<Canvas x:Name="Preview" />
<Canvas x:Name="AdornerLayer" />
<Canvas
x:Name="MergePanel"
MouseUp="MergePanelMouseUp"
Visibility="Collapsed">
<StackPanel x:Name="MergeButtons" Background="Gray" Orientation="Horizontal">
<Button Click="MergeClick" Margin="0" Height="36" Width="134">
<StackPanel
x:Name="MergeButtons"
Background="Gray"
Orientation="Horizontal">
<Button
Width="134"
Height="36"
Margin="0"
Click="MergeClick">
<StackPanel Orientation="Horizontal">
<Image Source="images/Merge.png" Margin="0,0,12,0" Height="16" HorizontalAlignment="Left" />
<TextBlock Text="Merge zones"/>
<Image
Height="16"
Margin="0,0,12,0"
HorizontalAlignment="Left"
Source="images/Merge.png" />
<TextBlock Text="Merge zones" />
</StackPanel>
</Button>
</StackPanel>

View File

@ -18,11 +18,16 @@ namespace FancyZonesEditor
{
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(GridLayoutModel), typeof(GridEditor), new PropertyMetadata(null, OnGridDimensionsChanged));
private static int gridEditorUniqueIdCounter = 0;
private int gridEditorUniqueId;
public GridEditor()
{
InitializeComponent();
Loaded += GridEditor_Loaded;
((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged;
gridEditorUniqueId = ++gridEditorUniqueIdCounter;
}
private void GridEditor_Loaded(object sender, RoutedEventArgs e)
@ -73,7 +78,9 @@ namespace FancyZonesEditor
private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Size actualSize = new Size(ActualWidth, ActualHeight);
if (actualSize.Width > 0)
// Only enter if this is the newest instance
if (actualSize.Width > 0 && gridEditorUniqueId == gridEditorUniqueIdCounter)
{
ArrangeGridRects(actualSize);
}
@ -134,83 +141,50 @@ namespace FancyZonesEditor
private void ExtendRangeToHaveEvenCellEdges()
{
// extend each edge of the [(_startCol, _startRow) - (_endCol, _endRow)] range based on merged cells until you have 4 straight edges with no "straddling cells"
// As long as there is an edge of the 2D range such that some zone crosses its boundary, extend
// that boundary. A single pass is not enough, a while loop is needed. This results in the unique
// smallest rectangle containing the initial range such that no zone is "broken", meaning that
// some part of it is inside the 2D range, and some part is outside.
GridLayoutModel model = Model;
bool possiblyBroken = true;
while (_startRow > 0)
while (possiblyBroken)
{
bool dirty = false;
possiblyBroken = false;
for (int col = _startCol; col <= _endCol; col++)
{
if (model.CellChildMap[_startRow - 1, col] == model.CellChildMap[_startRow, col])
if (_startRow > 0 && model.CellChildMap[_startRow - 1, col] == model.CellChildMap[_startRow, col])
{
_startRow--;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
while (_endRow < model.Rows - 1)
{
bool dirty = false;
for (int col = _startCol; col <= _endCol; col++)
{
if (model.CellChildMap[_endRow + 1, col] == model.CellChildMap[_endRow, col])
if (_endRow < model.Rows - 1 && model.CellChildMap[_endRow + 1, col] == model.CellChildMap[_endRow, col])
{
_endRow++;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
while (_startCol > 0)
{
bool dirty = false;
for (int row = _startRow; row <= _endRow; row++)
{
if (model.CellChildMap[row, _startCol - 1] == model.CellChildMap[row, _startCol])
if (_startCol > 0 && model.CellChildMap[row, _startCol - 1] == model.CellChildMap[row, _startCol])
{
_startCol--;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
while (_endCol < model.Columns - 1)
{
bool dirty = false;
for (int row = _startRow; row <= _endRow; row++)
{
if (model.CellChildMap[row, _endCol + 1] == model.CellChildMap[row, _endCol])
if (_endCol < model.Columns - 1 && model.CellChildMap[row, _endCol + 1] == model.CellChildMap[row, _endCol])
{
_endCol++;
dirty = true;
possiblyBroken = true;
break;
}
}
if (!dirty)
{
break;
}
}
}
@ -528,7 +502,8 @@ namespace FancyZonesEditor
private void OnGridDimensionsChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if ((e.PropertyName == "Rows") || (e.PropertyName == "Columns"))
// Only enter if this is the newest instance
if (((e.PropertyName == "Rows") || (e.PropertyName == "Columns")) && gridEditorUniqueId == gridEditorUniqueIdCounter)
{
OnGridDimensionsChanged();
}
@ -594,6 +569,7 @@ namespace FancyZonesEditor
top = _rowInfo[row].Start;
Canvas.SetLeft(zone, left);
Canvas.SetTop(zone, top);
zone.LabelID.Content = i + 1;
int maxRow = row;
while (((maxRow + 1) < rows) && (model.CellChildMap[maxRow + 1, col] == i))

View File

@ -2,6 +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 System.Windows;
using FancyZonesEditor.Models;
namespace FancyZonesEditor
{
/// <summary>
@ -12,6 +15,16 @@ namespace FancyZonesEditor
public GridEditorWindow()
{
InitializeComponent();
_stashedModel = (GridLayoutModel)(EditorOverlay.Current.DataContext as GridLayoutModel).Clone();
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
GridLayoutModel model = EditorOverlay.Current.DataContext as GridLayoutModel;
_stashedModel.RestoreTo(model);
}
private GridLayoutModel _stashedModel;
}
}

View File

@ -9,10 +9,16 @@
<Thumb.Template>
<ControlTemplate>
<StackPanel x:Name="Body" Grid.Column="0" Width="48" Height="48">
<Ellipse Height="48" Width="48" Fill="#0078D7" />
<Ellipse x:Name="BackgroundEllipse" Height="48" Width="48" Fill="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" />
<Rectangle Height="20" Width="2" Fill="White" Margin="5,-48,0,0"/>
<Rectangle Height="20" Width="2" Fill="White" Margin="-5,-48,0,0"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Opacity" TargetName="BackgroundEllipse" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Thumb.Template>
</Thumb>

View File

@ -1,17 +1,31 @@
<UserControl x:Class="FancyZonesEditor.GridZone"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d"
Background="LightGray"
BorderThickness="1"
BorderBrush="DarkGray"
Opacity="0.5"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="Frame" Visibility="Collapsed">
<UserControl
x:Class="FancyZonesEditor.GridZone"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
Background="{StaticResource GridZoneBackgroundBrush}"
BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}"
BorderThickness="1"
Opacity="0.8"
mc:Ignorable="d">
<Grid x:Name="Frame">
<Canvas x:Name="Body" />
<Label
Name="LabelID"
Grid.Row="3"
Grid.Column="2"
Canvas.Left="10"
Canvas.Bottom="10"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="ID"
FontFamily="Segoe UI Light"
FontSize="64"
Foreground="White" />
<!--<TextBlock Margin="2" Text="Shift Key switches direction&#13;Ctrl Key repeats"/>-->
</Grid>
</UserControl>

View File

@ -45,7 +45,7 @@ namespace FancyZonesEditor
private void OnSelectionChanged()
{
Background = IsSelected ? Brushes.SteelBlue : Brushes.LightGray;
Background = IsSelected ? SystemParameters.WindowGlassBrush : App.Current.Resources["GridZoneBackgroundBrush"] as SolidColorBrush;
}
public bool IsSelected
@ -60,7 +60,7 @@ namespace FancyZonesEditor
OnSelectionChanged();
_splitter = new Rectangle
{
Fill = Brushes.DarkGray,
Fill = SystemParameters.WindowGlassBrush,
};
Body.Children.Add(_splitter);
@ -146,13 +146,13 @@ namespace FancyZonesEditor
protected override void OnMouseEnter(MouseEventArgs e)
{
Frame.Visibility = Visibility.Visible;
_splitter.Fill = SystemParameters.WindowGlassBrush; // Active Accent color
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
Frame.Visibility = Visibility.Collapsed;
_splitter.Fill = Brushes.Transparent;
base.OnMouseLeave(e);
}

View File

@ -113,48 +113,86 @@ namespace FancyZonesEditor.Models
return layout;
}
public void RestoreTo(CanvasLayoutModel other)
{
other.Zones.Clear();
foreach (Int32Rect zone in Zones)
{
other.Zones.Add(zone);
}
}
private struct Zone
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
private struct CanvasLayoutInfo
{
public int RefWidth { get; set; }
public int RefHeight { get; set; }
public Zone[] Zones { get; set; }
}
private struct CanvasLayoutJson
{
public string Uuid { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public CanvasLayoutInfo Info { get; set; }
}
// PersistData
// Implements the LayoutModel.PersistData abstract method
protected override void PersistData()
{
CanvasLayoutInfo layoutInfo = new CanvasLayoutInfo
{
RefWidth = _referenceWidth,
RefHeight = _referenceHeight,
Zones = new Zone[Zones.Count],
};
for (int i = 0; i < Zones.Count; ++i)
{
Zone zone = new Zone
{
X = Zones[i].X,
Y = Zones[i].Y,
Width = Zones[i].Width,
Height = Zones[i].Height,
};
layoutInfo.Zones[i] = zone;
}
CanvasLayoutJson jsonObj = new CanvasLayoutJson
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
Name = Name,
Type = "canvas",
Info = layoutInfo,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create);
using (var writer = new Utf8JsonWriter(outputStream, options: default))
{
writer.WriteStartObject();
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
writer.WriteString("name", Name);
writer.WriteString("type", "canvas");
writer.WriteStartObject("info");
writer.WriteNumber("ref-width", _referenceWidth);
writer.WriteNumber("ref-height", _referenceHeight);
writer.WriteStartArray("zones");
foreach (Int32Rect rect in Zones)
{
writer.WriteStartObject();
writer.WriteNumber("X", rect.X);
writer.WriteNumber("Y", rect.Y);
writer.WriteNumber("width", rect.Width);
writer.WriteNumber("height", rect.Height);
writer.WriteEndObject();
}
writer.WriteEndArray();
// end info object
writer.WriteEndObject();
// end root object
writer.WriteEndObject();
writer.Flush();
}
outputStream.Close();
string jsonString = JsonSerializer.Serialize(jsonObj, options);
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{

View File

@ -131,6 +131,12 @@ namespace FancyZonesEditor.Models
public override LayoutModel Clone()
{
GridLayoutModel layout = new GridLayoutModel(Name);
RestoreTo(layout);
return layout;
}
public void RestoreTo(GridLayoutModel layout)
{
int rows = Rows;
int cols = Columns;
@ -163,69 +169,69 @@ namespace FancyZonesEditor.Models
}
layout.ColumnPercents = colPercents;
}
return layout;
private struct GridLayoutInfo
{
public int Rows { get; set; }
public int Columns { get; set; }
public int[] RowsPercentage { get; set; }
public int[] ColumnsPercentage { get; set; }
public int[][] CellChildMap { get; set; }
}
private struct GridLayoutJson
{
public string Uuid { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public GridLayoutInfo Info { get; set; }
}
// PersistData
// Implements the LayoutModel.PersistData abstract method
protected override void PersistData()
{
GridLayoutInfo layoutInfo = new GridLayoutInfo
{
Rows = Rows,
Columns = Columns,
RowsPercentage = RowPercents,
ColumnsPercentage = ColumnPercents,
CellChildMap = new int[Rows][],
};
for (int row = 0; row < Rows; row++)
{
layoutInfo.CellChildMap[row] = new int[Columns];
for (int col = 0; col < Columns; col++)
{
layoutInfo.CellChildMap[row][col] = CellChildMap[row, col];
}
}
GridLayoutJson jsonObj = new GridLayoutJson
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
Name = Name,
Type = "grid",
Info = layoutInfo,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create);
using (var writer = new Utf8JsonWriter(outputStream, options: default))
{
writer.WriteStartObject();
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
writer.WriteString("name", Name);
writer.WriteString("type", "grid");
writer.WriteStartObject("info");
writer.WriteNumber("rows", Rows);
writer.WriteNumber("columns", Columns);
writer.WriteStartArray("rows-percentage");
for (int row = 0; row < Rows; row++)
{
writer.WriteNumberValue(RowPercents[row]);
}
writer.WriteEndArray();
writer.WriteStartArray("columns-percentage");
for (int col = 0; col < Columns; col++)
{
writer.WriteNumberValue(ColumnPercents[col]);
}
writer.WriteEndArray();
writer.WriteStartArray("cell-child-map");
for (int row = 0; row < Rows; row++)
{
writer.WriteStartArray();
for (int col = 0; col < Columns; col++)
{
writer.WriteNumberValue(CellChildMap[row, col]);
}
writer.WriteEndArray();
}
writer.WriteEndArray();
// end info object
writer.WriteEndObject();
// end root object
writer.WriteEndObject();
writer.Flush();
}
outputStream.Close();
string jsonString = JsonSerializer.Serialize(jsonObj, options);
File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{

View File

@ -135,23 +135,27 @@ namespace FancyZonesEditor.Models
}
}
private struct DeletedCustomZoneSetsWrapper
{
public List<string> DeletedCustomZoneSets { get; set; }
}
public static void SerializeDeletedCustomZoneSets()
{
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
{
DeletedCustomZoneSets = _deletedCustomModels,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create);
var writer = new Utf8JsonWriter(outputStream, options: default);
writer.WriteStartObject();
writer.WriteStartArray("deleted-custom-zone-sets");
foreach (string zoneSet in _deletedCustomModels)
{
writer.WriteStringValue(zoneSet);
}
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
outputStream.Close();
string jsonString = JsonSerializer.Serialize(deletedLayouts, options);
File.WriteAllText(Settings.CustomZoneSetsTmpFile, jsonString);
}
catch (Exception ex)
{
@ -258,51 +262,75 @@ namespace FancyZonesEditor.Models
Apply();
}
private struct ActiveZoneSetWrapper
{
public string Uuid { get; set; }
public string Type { get; set; }
}
private struct AppliedZoneSet
{
public string DeviceId { get; set; }
public ActiveZoneSetWrapper ActiveZoneset { get; set; }
public bool EditorShowSpacing { get; set; }
public int EditorSpacing { get; set; }
public int EditorZoneCount { get; set; }
}
public void Apply()
{
ActiveZoneSetWrapper activeZoneSet = new ActiveZoneSetWrapper
{
Uuid = "{" + Guid.ToString().ToUpper() + "}",
};
switch (Type)
{
case LayoutType.Focus:
activeZoneSet.Type = "focus";
break;
case LayoutType.Rows:
activeZoneSet.Type = "rows";
break;
case LayoutType.Columns:
activeZoneSet.Type = "columns";
break;
case LayoutType.Grid:
activeZoneSet.Type = "grid";
break;
case LayoutType.PriorityGrid:
activeZoneSet.Type = "priority-grid";
break;
case LayoutType.Custom:
activeZoneSet.Type = "custom";
break;
}
Settings settings = ((App)Application.Current).ZoneSettings;
AppliedZoneSet zoneSet = new AppliedZoneSet
{
DeviceId = Settings.UniqueKey,
ActiveZoneset = activeZoneSet,
EditorShowSpacing = settings.ShowSpacing,
EditorSpacing = settings.Spacing,
EditorZoneCount = settings.ZoneCount,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try
{
FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create);
var writer = new Utf8JsonWriter(outputStream, options: default);
writer.WriteStartObject();
writer.WriteString("device-id", Settings.UniqueKey);
writer.WriteStartObject("active-zoneset");
writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}");
switch (Type)
{
case LayoutType.Focus:
writer.WriteString("type", "focus");
break;
case LayoutType.Rows:
writer.WriteString("type", "rows");
break;
case LayoutType.Columns:
writer.WriteString("type", "columns");
break;
case LayoutType.Grid:
writer.WriteString("type", "grid");
break;
case LayoutType.PriorityGrid:
writer.WriteString("type", "priority-grid");
break;
case LayoutType.Custom:
writer.WriteString("type", "custom");
break;
}
writer.WriteEndObject();
Settings settings = ((App)Application.Current).ZoneSettings;
writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing);
writer.WriteNumber("editor-spacing", settings.Spacing);
writer.WriteNumber("editor-zone-count", settings.ZoneCount);
writer.WriteEndObject();
writer.Flush();
outputStream.Close();
string jsonString = JsonSerializer.Serialize(zoneSet, options);
File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString);
}
catch (Exception ex)
{

View File

@ -329,15 +329,15 @@ namespace FancyZonesEditor
_gridModel.ColumnPercents[col] = ((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols);
}
int index = 0;
for (int col = cols - 1; col >= 0; col--)
int index = ZoneCount - 1;
for (int row = rows - 1; row >= 0; row--)
{
for (int row = rows - 1; row >= 0; row--)
for (int col = cols - 1; col >= 0; col--)
{
_gridModel.CellChildMap[row, col] = index++;
if (index == ZoneCount)
_gridModel.CellChildMap[row, col] = index--;
if (index < 0)
{
index--;
index = 0;
}
}
}
@ -407,7 +407,8 @@ namespace FancyZonesEditor
}
inputStream.Close();
} catch (Exception ex)
}
catch (Exception ex)
{
LayoutModel.ShowExceptionMessageBox("Error parsing device info data", ex);
}

View File

@ -0,0 +1,22 @@
// 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.Linq;
namespace FancyZonesEditor.Utils
{
public static class StringUtils
{
public static string UpperCamelCaseToDashCase(this string str)
{
// If it's single letter variable, leave it as it is
if (str.Length == 1)
{
return str;
}
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "-" + x.ToString() : x.ToString())).ToLower();
}
}
}

View File

@ -12,9 +12,13 @@
#include <functional>
#include <common/common.h>
#include <lib\util.h>
#include <common/window_helpers.h>
#include <common/notifications.h>
#include <lib/util.h>
#include <unordered_set>
#include <common/notifications/fancyzones_notifications.h>
enum class DisplayChangeType
{
WorkArea,
@ -24,6 +28,8 @@ enum class DisplayChangeType
Initialization
};
extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace std
{
template<>
@ -83,10 +89,32 @@ public:
IFACEMETHODIMP_(void)
MoveWindowsOnActiveZoneSetChange() noexcept;
IFACEMETHODIMP_(COLORREF)
GetZoneColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
}
IFACEMETHODIMP_(COLORREF)
GetZoneBorderColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings()->zoneBorderColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
const auto nB = (tmp & 0xFF);
return RGB(nR, nG, nB);
}
IFACEMETHODIMP_(COLORREF)
GetZoneHighlightColor() noexcept
{
// Skip the leading # and convert to long
const auto color = m_settings->GetSettings().zoneHightlightColor;
const auto color = m_settings->GetSettings()->zoneHightlightColor;
const auto tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8;
@ -108,7 +136,13 @@ public:
IFACEMETHODIMP_(int)
GetZoneHighlightOpacity() noexcept
{
return m_settings->GetSettings().zoneHighlightOpacity;
return m_settings->GetSettings()->zoneHighlightOpacity;
}
IFACEMETHODIMP_(bool)
isMakeDraggedWindowTransparentActive() noexcept
{
return m_settings->GetSettings()->makeDraggedWindowTransparent;
}
LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept;
@ -146,9 +180,10 @@ private:
};
bool IsInterestingWindow(HWND window) noexcept;
bool IsCursorTypeIndicatingSizeEvent();
void UpdateZoneWindows() noexcept;
void MoveWindowsOnDisplayChange() noexcept;
void UpdateDragState(require_write_lock) noexcept;
void UpdateDragState(HWND window, require_write_lock) noexcept;
void CycleActiveZoneSet(DWORD vkCode) noexcept;
bool OnSnapHotkey(DWORD vkCode) noexcept;
void MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT const& ptScreen, require_write_lock) noexcept;
@ -162,6 +197,10 @@ private:
void OnEditorExitEvent() noexcept;
std::vector<std::pair<HMONITOR, RECT>> GetRawMonitorData() noexcept;
std::vector<HMONITOR> GetMonitorsSorted() noexcept;
bool MoveWindowIntoZoneByDirection(HMONITOR monitor, HWND window, DWORD vkCode, bool cycle);
const HINSTANCE m_hinstance{};
HKEY m_virtualDesktopsRegKey{ nullptr };
@ -217,7 +256,7 @@ FancyZones::Run() noexcept
if (!m_window)
return;
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code());
VirtualDesktopInitialize();
@ -309,7 +348,7 @@ FancyZones::VirtualDesktopInitialize() noexcept
IFACEMETHODIMP_(void)
FancyZones::WindowCreated(HWND window) noexcept
{
if (m_settings->GetSettings().appLastZone_moveWindows && IsInterestingWindow(window))
if (m_settings->GetSettings()->appLastZone_moveWindows && IsInterestingWindow(window))
{
for (const auto& [monitor, zoneWindow] : m_zoneWindowMap)
{
@ -345,17 +384,18 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if (ctrl)
{
if ((info->vkCode >= '0') && (info->vkCode <= '9'))
{
// Win+Ctrl+Number will cycle through ZoneSets
Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
CycleActiveZoneSet(info->vkCode);
return true;
}
// Temporarily disable Win+Ctrl+Number functionality
//if ((info->vkCode >= '0') && (info->vkCode <= '9'))
//{
// // Win+Ctrl+Number will cycle through ZoneSets
// Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
// CycleActiveZoneSet(info->vkCode);
// return true;
//}
}
else if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT))
{
if (m_settings->GetSettings().overrideSnapHotkeys)
if (m_settings->GetSettings()->overrideSnapHotkeys)
{
// Win+Left, Win+Right will cycle through Zones in the active ZoneSet
Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
@ -363,13 +403,14 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
}
}
}
else if (m_inMoveSize && (info->vkCode >= '0') && (info->vkCode <= '9'))
{
// This allows you to cycle through ZoneSets while dragging a window
Trace::FancyZones::OnKeyDown(info->vkCode, win, false /*control*/, true /*inMoveSize*/);
CycleActiveZoneSet(info->vkCode);
return false;
}
// Temporarily disable Win+Ctrl+Number functionality
//else if (m_inMoveSize && (info->vkCode >= '0') && (info->vkCode <= '9'))
//{
// // This allows you to cycle through ZoneSets while dragging a window
// Trace::FancyZones::OnKeyDown(info->vkCode, win, false /*control*/, true /*inMoveSize*/);
// CycleActiveZoneSet(info->vkCode);
// return false;
//}
if (m_dragEnabled && shift)
{
return true;
@ -397,10 +438,7 @@ void FancyZones::ToggleEditor() noexcept
HMONITOR monitor{};
HWND foregroundWindow{};
UINT dpi_x = DPIAware::DEFAULT_DPI;
UINT dpi_y = DPIAware::DEFAULT_DPI;
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings().use_cursorpos_editor_startupscreen;
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen;
POINT currentCursorPos{};
if (use_cursorpos_editor_startupscreen)
{
@ -433,24 +471,14 @@ void FancyZones::ToggleEditor() noexcept
} })
.wait();
if (use_cursorpos_editor_startupscreen)
{
DPIAware::GetScreenDPIForPoint(currentCursorPos, dpi_x, dpi_y);
}
else
{
DPIAware::GetScreenDPIForWindow(foregroundWindow, dpi_x, dpi_y);
}
auto zoneWindow = iter->second;
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
fancyZonesData.CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath());
const auto taskbar_x_offset = MulDiv(mi.rcWork.left - mi.rcMonitor.left, DPIAware::DEFAULT_DPI, dpi_x);
const auto taskbar_y_offset = MulDiv(mi.rcWork.top - mi.rcMonitor.top, DPIAware::DEFAULT_DPI, dpi_y);
// Do not scale window params by the dpi, that will be done in the editor - see LayoutModel.Apply
const auto taskbar_x_offset = mi.rcWork.left - mi.rcMonitor.left;
const auto taskbar_y_offset = mi.rcWork.top - mi.rcMonitor.top;
const auto x = mi.rcMonitor.left + taskbar_x_offset;
const auto y = mi.rcMonitor.top + taskbar_y_offset;
const auto width = mi.rcWork.right - mi.rcWork.left;
@ -474,9 +502,9 @@ void FancyZones::ToggleEditor() noexcept
/*1*/ std::to_wstring(reinterpret_cast<UINT_PTR>(monitor)) + L" " +
/*2*/ editorLocation + L" " +
/*3*/ zoneWindow->WorkAreaKey() + L" " +
/*4*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " +
/*5*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " +
/*6*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath();
/*4*/ L"\"" + ZoneWindowUtils::GetActiveZoneSetTmpPath() + L"\" " +
/*5*/ L"\"" + ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L"\" " +
/*6*/ L"\"" + ZoneWindowUtils::GetCustomZoneSetsTmpPath() + L"\"";
SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@ -484,6 +512,7 @@ void FancyZones::ToggleEditor() noexcept
sei.lpParameters = params.c_str();
sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei);
Trace::FancyZones::EditorLaunched(1);
// Launch the editor on a background thread
// Wait for the editor's process to exit
@ -515,14 +544,14 @@ void FancyZones::SettingsChanged() noexcept
std::shared_lock readLock(m_lock);
// Update the hotkey
UnregisterHotKey(m_window, 1);
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code());
}
// IZoneWindowHost
IFACEMETHODIMP_(void)
FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept
{
if (m_settings->GetSettings().zoneSetChange_moveWindows)
if (m_settings->GetSettings()->zoneSetChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
@ -616,21 +645,21 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange))
{
if (m_settings->GetSettings().displayChange_moveWindows)
if (m_settings->GetSettings()->displayChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
}
else if (changeType == DisplayChangeType::VirtualDesktop)
{
if (m_settings->GetSettings().virtualDesktopChange_moveWindows)
if (m_settings->GetSettings()->virtualDesktopChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
}
else if (changeType == DisplayChangeType::Editor)
{
if (m_settings->GetSettings().zoneSetChange_moveWindows)
if (m_settings->GetSettings()->zoneSetChange_moveWindows)
{
MoveWindowsOnDisplayChange();
}
@ -647,7 +676,9 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept
JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId);
const bool newWorkArea = IsNewWorkArea(m_currentVirtualDesktopId, monitor);
const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newWorkArea;
// "Turning FLASHING_ZONE option off"
//const bool flash = m_settings->GetSettings()->zoneSetChange_flashZones && newWorkArea;
const bool flash = false;
auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash);
if (zoneWindow)
@ -706,17 +737,42 @@ bool FancyZones::IsInterestingWindow(HWND window) noexcept
CharUpperBuffW(filtered.process_path.data(), (DWORD)filtered.process_path.length());
if (m_settings)
{
for (const auto& excluded : m_settings->GetSettings().excludedAppsArray)
const auto& excludedAppsArray = m_settings->GetSettings()->excludedAppsArray;
if (find_app_name_in_path(filtered.process_path, excludedAppsArray))
{
if (filtered.process_path.find(excluded) != std::wstring::npos)
{
return false;
}
return false;
}
}
return true;
}
bool FancyZones::IsCursorTypeIndicatingSizeEvent()
{
CURSORINFO cursorInfo = { 0 };
cursorInfo.cbSize = sizeof(cursorInfo);
if (::GetCursorInfo(&cursorInfo))
{
if (::LoadCursor(NULL, IDC_SIZENS) == cursorInfo.hCursor)
{
return true;
}
if (::LoadCursor(NULL, IDC_SIZEWE) == cursorInfo.hCursor)
{
return true;
}
if (::LoadCursor(NULL, IDC_SIZENESW) == cursorInfo.hCursor)
{
return true;
}
if (::LoadCursor(NULL, IDC_SIZENWSE) == cursorInfo.hCursor)
{
return true;
}
}
return false;
}
void FancyZones::UpdateZoneWindows() noexcept
{
auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL {
@ -774,7 +830,7 @@ void FancyZones::MoveWindowsOnDisplayChange() noexcept
EnumWindows(callback, reinterpret_cast<LPARAM>(this));
}
void FancyZones::UpdateDragState(require_write_lock) noexcept
void FancyZones::UpdateDragState(HWND window, require_write_lock) noexcept
{
const bool shift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
const bool mouseL = GetAsyncKeyState(VK_LBUTTON) & 0x8000;
@ -795,7 +851,7 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
mouse |= mouseR;
}
if (m_settings->GetSettings().shiftDrag)
if (m_settings->GetSettings()->shiftDrag)
{
m_dragEnabled = (shift | mouse);
}
@ -803,6 +859,23 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
{
m_dragEnabled = !(shift | mouse);
}
const bool windowElevated = IsProcessOfWindowElevated(window);
static const bool meElevated = is_process_elevated();
static bool warning_shown = false;
if (windowElevated && !meElevated)
{
m_dragEnabled = false;
if (!warning_shown && !is_cant_drag_elevated_warning_disabled())
{
std::vector<notifications::action_t> actions = {
notifications::link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), L"https://aka.ms/powertoysDetectedElevatedHelp" },
notifications::link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN), L"powertoys://cant_drag_elevated_disable/" }
};
notifications::show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), {}, std::move(actions));
warning_shown = true;
}
}
}
void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept
@ -830,17 +903,43 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
auto window = GetForegroundWindow();
if (IsInterestingWindow(window))
{
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (monitor)
const HMONITOR current = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (current)
{
std::shared_lock readLock(m_lock);
auto iter = m_zoneWindowMap.find(monitor);
if (iter != m_zoneWindowMap.end())
std::vector<HMONITOR> monitorInfo = GetMonitorsSorted();
if (monitorInfo.size() > 1)
{
const auto& zoneWindowPtr = iter->second;
zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode);
return true;
// Multi monitor environment.
auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current);
do
{
if (MoveWindowIntoZoneByDirection(*currMonitorInfo, window, vkCode, false /* cycle through zones */))
{
return true;
}
// We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
if (vkCode == VK_RIGHT)
{
currMonitorInfo = std::next(currMonitorInfo);
if (currMonitorInfo == std::end(monitorInfo))
{
currMonitorInfo = std::begin(monitorInfo);
}
}
else if (vkCode == VK_LEFT)
{
if (currMonitorInfo == std::begin(monitorInfo))
{
currMonitorInfo = std::end(monitorInfo);
}
currMonitorInfo = std::prev(currMonitorInfo);
}
} while (*currMonitorInfo != current);
}
else
{
// Single monitor environment.
return MoveWindowIntoZoneByDirection(current, window, vkCode, true /* cycle through zones */);
}
}
}
@ -849,23 +948,10 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
void FancyZones::MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT const& ptScreen, require_write_lock writeLock) noexcept
{
// Only enter move/size if the cursor is inside the window rect by a certain padding.
// This prevents resize from triggering zones.
RECT windowRect{};
::GetWindowRect(window, &windowRect);
const auto padding_x = 8;
const auto padding_y = 6;
windowRect.top += padding_y;
windowRect.left += padding_x;
windowRect.right -= padding_x;
windowRect.bottom -= padding_y;
if (PtInRect(&windowRect, ptScreen) == FALSE)
if (IsCursorTypeIndicatingSizeEvent())
{
return;
}
m_inMoveSize = true;
auto iter = m_zoneWindowMap.find(monitor);
@ -877,17 +963,37 @@ void FancyZones::MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT cons
m_windowMoveSize = window;
// This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(writeLock);
UpdateDragState(window, writeLock);
if (m_dragEnabled)
{
m_zoneWindowMoveSize = iter->second;
m_zoneWindowMoveSize->MoveSizeEnter(window, m_dragEnabled);
if (m_settings->GetSettings()->showZonesOnAllMonitors)
{
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
// Skip calling ShowZoneWindow for iter->second (m_zoneWindowMoveSize) since it
// was already called in MoveSizeEnter
const bool moveSizeEnterCalled = zoneWindow == m_zoneWindowMoveSize;
if (zoneWindow && !moveSizeEnterCalled)
{
zoneWindow->ShowZoneWindow();
}
}
}
}
else if (m_zoneWindowMoveSize)
{
m_zoneWindowMoveSize->MoveSizeCancel();
m_zoneWindowMoveSize->RestoreOrginalTransparency();
m_zoneWindowMoveSize = nullptr;
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->HideZoneWindow();
}
}
}
}
@ -924,6 +1030,15 @@ void FancyZones::MoveSizeEndInternal(HWND window, POINT const& ptScreen, require
}
}
}
// Also, hide all windows (regardless of settings)
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->HideZoneWindow();
}
}
}
void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen, require_write_lock writeLock) noexcept
@ -931,16 +1046,24 @@ void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen,
if (m_inMoveSize)
{
// This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(writeLock);
UpdateDragState(m_windowMoveSize, writeLock);
if (m_zoneWindowMoveSize)
{
// Update the ZoneWindow already handling move/size
if (!m_dragEnabled)
{
// Drag got disabled, tell it to cancel and clear out m_zoneWindowMoveSize
auto zoneWindow = std::move(m_zoneWindowMoveSize);
zoneWindow->MoveSizeCancel();
// Drag got disabled, tell it to cancel and hide all windows
m_zoneWindowMoveSize = nullptr;
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->RestoreOrginalTransparency();
zoneWindow->HideZoneWindow();
}
}
}
else
{
@ -950,12 +1073,20 @@ void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen,
if (iter->second != m_zoneWindowMoveSize)
{
// The drag has moved to a different monitor.
auto const isDragEnabled = m_zoneWindowMoveSize->IsDragEnabled();
m_zoneWindowMoveSize->MoveSizeCancel();
m_zoneWindowMoveSize->RestoreOrginalTransparency();
if (!m_settings->GetSettings()->showZonesOnAllMonitors)
{
m_zoneWindowMoveSize->HideZoneWindow();
}
m_zoneWindowMoveSize = iter->second;
m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize, isDragEnabled);
m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize, m_zoneWindowMoveSize->IsDragEnabled());
}
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled);
}
m_zoneWindowMoveSize->MoveSizeUpdate(ptScreen, m_dragEnabled);
}
}
}
@ -1075,6 +1206,46 @@ void FancyZones::OnEditorExitEvent() noexcept
JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData();
}
std::vector<HMONITOR> FancyZones::GetMonitorsSorted() noexcept
{
std::shared_lock readLock(m_lock);
auto monitorInfo = GetRawMonitorData();
OrderMonitors(monitorInfo);
std::vector<HMONITOR> output;
std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;
}
std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
{
std::shared_lock readLock(m_lock);
std::vector<std::pair<HMONITOR, RECT>> monitorInfo;
for (const auto& [monitor, window] : m_zoneWindowMap)
{
if (window->ActiveZoneSet() != nullptr)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(monitor, &mi);
monitorInfo.push_back({ monitor, mi.rcMonitor });
}
}
return monitorInfo;
}
bool FancyZones::MoveWindowIntoZoneByDirection(HMONITOR monitor, HWND window, DWORD vkCode, bool cycle)
{
auto iter = m_zoneWindowMap.find(monitor);
if (iter != std::end(m_zoneWindowMap))
{
const auto& zoneWindowPtr = iter->second;
return zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode, cycle);
}
return false;
}
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept
{
if (!settings)
@ -1083,4 +1254,4 @@ winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com
}
return winrt::make_self<FancyZones>(hinstance, settings);
}
}

View File

@ -1,106 +1,118 @@
#pragma once
interface IZoneWindow;
interface IFancyZonesSettings;
interface IZoneSet;
interface __declspec(uuid("{50D3F0F5-736E-4186-BDF4-3D6BEE150C3A}")) IFancyZones : public IUnknown
{
/**
* Start and initialize FancyZones.
*/
IFACEMETHOD_(void, Run)() = 0;
/**
* Stop FancyZones and do the clean up.
*/
IFACEMETHOD_(void, Destroy)() = 0;
};
/**
* Core FancyZones functionality.
*/
interface __declspec(uuid("{2CB37E8F-87E6-4AEC-B4B2-E0FDC873343F}")) IFancyZonesCallback : public IUnknown
{
/**
* @returns Boolean indicating whether a move/size operation is currently active.
*/
IFACEMETHOD_(bool, InMoveSize)() = 0;
/**
* A window is being moved or resized. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param window Handle of window being moved or resized.
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeStart)(HWND window, HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* A window has changed location, shape, or size. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeUpdate)(HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* The movement or resizing of a window has finished. Assign window to the zone if it
* is dropped within zone borders.
*
* @param window Handle of window being moved or resized.
* @param ptScreen Cursor coordinates where window is droped.
*/
IFACEMETHOD_(void, MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0;
/**
* Inform FancyZones that user has switched between virtual desktops.
*/
IFACEMETHOD_(void, VirtualDesktopChanged)() = 0;
/**
* Inform FancyZones that new window is created. FancyZones will try to assign it to the
* zone insde active zone layout (if information about last zone, in which window was located
* before being closed, is available).
*
* @param window Handle of newly created window.
*/
IFACEMETHOD_(void, WindowCreated)(HWND window) = 0;
/**
* Process keyboard event.
*
* @param info Information about low level keyboard event.
* @returns Boolean indicating if this event should be passed on further to other applications
* in event chain, or should it be suppressed.
*/
IFACEMETHOD_(bool, OnKeyDown)(PKBDLLHOOKSTRUCT info) = 0;
/**
* Toggle FancyZones editor application.
*/
IFACEMETHOD_(void, ToggleEditor)() = 0;
/**
* Callback triggered when user changes FancyZones settings.
*/
IFACEMETHOD_(void, SettingsChanged)() = 0;
};
/**
* Helper functions used by each ZoneWindow (representing work area).
*/
interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindowHost : public IUnknown
{
/**
* Assign window to appropriate zone inside new zone layout.
*/
IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0;
/**
* @returns Color used to highlight zone while giving zone layout hints.
*/
IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0;
/**
* @returns ZoneWindow (representing work area) currently being processed.
*/
IFACEMETHOD_(IZoneWindow*, GetParentZoneWindow) (HMONITOR monitor) = 0;
/**
* @returns Integer in range [0, 100] indicating opacity of highlited zone (while giving zone layout hints).
*/
IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0;
};
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept;
#pragma once
interface IZoneWindow;
interface IFancyZonesSettings;
interface IZoneSet;
interface __declspec(uuid("{50D3F0F5-736E-4186-BDF4-3D6BEE150C3A}")) IFancyZones : public IUnknown
{
/**
* Start and initialize FancyZones.
*/
IFACEMETHOD_(void, Run)() = 0;
/**
* Stop FancyZones and do the clean up.
*/
IFACEMETHOD_(void, Destroy)() = 0;
};
/**
* Core FancyZones functionality.
*/
interface __declspec(uuid("{2CB37E8F-87E6-4AEC-B4B2-E0FDC873343F}")) IFancyZonesCallback : public IUnknown
{
/**
* @returns Boolean indicating whether a move/size operation is currently active.
*/
IFACEMETHOD_(bool, InMoveSize)() = 0;
/**
* A window is being moved or resized. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param window Handle of window being moved or resized.
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeStart)(HWND window, HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* A window has changed location, shape, or size. Track down window position and give zone layout
* hints if dragging functionality is enabled.
*
* @param monitor Handle of monitor on which windows is moving / resizing.
* @param ptScreen Cursor coordinates.
*/
IFACEMETHOD_(void, MoveSizeUpdate)(HMONITOR monitor, POINT const& ptScreen) = 0;
/**
* The movement or resizing of a window has finished. Assign window to the zone if it
* is dropped within zone borders.
*
* @param window Handle of window being moved or resized.
* @param ptScreen Cursor coordinates where window is droped.
*/
IFACEMETHOD_(void, MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0;
/**
* Inform FancyZones that user has switched between virtual desktops.
*/
IFACEMETHOD_(void, VirtualDesktopChanged)() = 0;
/**
* Inform FancyZones that new window is created. FancyZones will try to assign it to the
* zone insde active zone layout (if information about last zone, in which window was located
* before being closed, is available).
*
* @param window Handle of newly created window.
*/
IFACEMETHOD_(void, WindowCreated)(HWND window) = 0;
/**
* Process keyboard event.
*
* @param info Information about low level keyboard event.
* @returns Boolean indicating if this event should be passed on further to other applications
* in event chain, or should it be suppressed.
*/
IFACEMETHOD_(bool, OnKeyDown)(PKBDLLHOOKSTRUCT info) = 0;
/**
* Toggle FancyZones editor application.
*/
IFACEMETHOD_(void, ToggleEditor)() = 0;
/**
* Callback triggered when user changes FancyZones settings.
*/
IFACEMETHOD_(void, SettingsChanged)() = 0;
};
/**
* Helper functions used by each ZoneWindow (representing work area).
*/
interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindowHost : public IUnknown
{
/**
* Assign window to appropriate zone inside new zone layout.
*/
IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0;
/**
* @returns Basic zone color.
*/
IFACEMETHOD_(COLORREF, GetZoneColor)() = 0;
/**
* @returns Zone border color.
*/
IFACEMETHOD_(COLORREF, GetZoneBorderColor)() = 0;
/**
* @returns Color used to highlight zone while giving zone layout hints.
*/
IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0;
/**
* @returns ZoneWindow (representing work area) currently being processed.
*/
IFACEMETHOD_(IZoneWindow*, GetParentZoneWindow) (HMONITOR monitor) = 0;
/**
* @returns Integer in range [0, 100] indicating opacity of highlited zone (while giving zone layout hints).
*/
IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0;
/**
* @returns Bool indicating if dragged window should be transparrent
*/
IFACEMETHOD_(bool, isMakeDraggedWindowTransparentActive) () = 0;
};
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept;

View File

@ -46,9 +46,13 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@ -10,6 +10,7 @@
#include <filesystem>
#include <fstream>
#include <regex>
#include <sstream>
namespace
{
@ -46,6 +47,84 @@ namespace
namespace JSONHelpers
{
bool isValidGuid(const std::wstring& str)
{
GUID id;
return SUCCEEDED_LOG(CLSIDFromString(str.c_str(), &id));
}
bool isValidDeviceId(const std::wstring& str)
{
std::wstring monitorName;
std::wstring temp;
std::vector<std::wstring> parts;
std::wstringstream wss(str);
/*
Important fix for device info that contains a '_' in the name:
1. first search for '#'
2. Then split the remaining string by '_'
*/
// Step 1: parse the name until the #, then to the '_'
if (str.find(L'#') != std::string::npos)
{
std::getline(wss, temp, L'#');
monitorName = temp;
if (!std::getline(wss, temp, L'_'))
{
return false;
}
monitorName += L"#" + temp;
parts.push_back(monitorName);
}
// Step 2: parse the rest of the id
while (std::getline(wss, temp, L'_'))
{
parts.push_back(temp);
}
if (parts.size() != 4)
{
return false;
}
/*
Refer to ZoneWindowUtils::GenerateUniqueId parts contain:
1. monitor id [string]
2. width of device [int]
3. height of device [int]
4. virtual desktop id (GUID) [string]
*/
try
{
//check if resolution contain only digits
for (const auto& c : parts[1])
{
std::stoi(std::wstring(&c));
}
for (const auto& c : parts[2])
{
std::stoi(std::wstring(&c));
}
}
catch (const std::exception&)
{
return false;
}
if (!isValidGuid(parts[3]) || parts[0].empty())
{
return false;
}
return true;
}
json::JsonArray NumVecToJsonArray(const std::vector<int>& vec)
{
json::JsonArray arr;
@ -525,9 +604,6 @@ namespace JSONHelpers
if (!std::filesystem::exists(jsonFilePath))
{
TmpMigrateAppliedZoneSetsFromRegistry();
// Custom zone sets have to be migrated after applied zone sets!
MigrateCustomZoneSetsFromRegistry();
SaveFancyZonesData();
@ -560,56 +636,6 @@ namespace JSONHelpers
json::to_file(jsonFilePath, root);
}
void FancyZonesData::TmpMigrateAppliedZoneSetsFromRegistry()
{
std::wregex ex(L"^[0-9]{3,4}_[0-9]{3,4}$");
std::scoped_lock lock{ dataLock };
wchar_t key[256];
StringCchPrintf(key, ARRAYSIZE(key), L"%s", RegistryHelpers::REG_SETTINGS);
HKEY hkey;
if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS)
{
wchar_t resolutionKey[256]{};
DWORD resolutionKeyLength = ARRAYSIZE(resolutionKey);
DWORD i = 0;
while (RegEnumKeyW(hkey, i++, resolutionKey, resolutionKeyLength) == ERROR_SUCCESS)
{
std::wstring resolution{ resolutionKey };
wchar_t appliedZoneSetskey[256];
StringCchPrintf(appliedZoneSetskey, ARRAYSIZE(appliedZoneSetskey), L"%s\\%s", RegistryHelpers::REG_SETTINGS, resolutionKey);
HKEY appliedZoneSetsHkey;
if (std::regex_match(resolution, ex) && RegOpenKeyExW(HKEY_CURRENT_USER, appliedZoneSetskey, 0, KEY_ALL_ACCESS, &appliedZoneSetsHkey) == ERROR_SUCCESS)
{
ZoneSetPersistedDataOLD data;
DWORD dataSize = sizeof(data);
wchar_t value[256]{};
DWORD valueLength = ARRAYSIZE(value);
DWORD i = 0;
while (RegEnumValueW(appliedZoneSetsHkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast<BYTE*>(&data), &dataSize) == ERROR_SUCCESS)
{
ZoneSetData appliedZoneSetData;
appliedZoneSetData.type = TypeFromLayoutId(data.LayoutId);
if (appliedZoneSetData.type != ZoneSetLayoutType::Custom)
{
appliedZoneSetData.uuid = std::wstring{ value };
}
else
{
// uuid is changed later to actual uuid when migrating custom zone sets
appliedZoneSetData.uuid = std::to_wstring(data.LayoutId);
}
appliedZoneSetsMap[value] = appliedZoneSetData;
dataSize = sizeof(data);
valueLength = ARRAYSIZE(value);
}
}
resolutionKeyLength = ARRAYSIZE(resolutionKey);
}
}
}
void FancyZonesData::MigrateCustomZoneSetsFromRegistry()
{
std::scoped_lock lock{ dataLock };
@ -630,29 +656,19 @@ namespace JSONHelpers
zoneSetData.type = static_cast<CustomLayoutType>(data[2]);
// int version = data[0] * 256 + data[1]; - Not used anymore
std::wstring uuid = std::to_wstring(data[3] * 256 + data[4]);
auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair<std::wstring, ZoneSetData> zoneSetMap) {
return zoneSetMap.second.uuid.compare(uuid) == 0;
});
GUID guid;
auto result = CoCreateGuid(&guid);
if (result != S_OK)
{
continue;
}
wil::unique_cotaskmem_string guidString;
if (!SUCCEEDED_LOG(StringFromCLSID(guid, &guidString)))
{
continue;
}
if (it != appliedZoneSetsMap.end())
{
it->second.uuid = uuid = it->first;
}
else
{
GUID guid;
auto result = CoCreateGuid(&guid);
if (result != S_OK)
{
return;
}
wil::unique_cotaskmem_string guidString;
if (SUCCEEDED_LOG(StringFromCLSID(guid, &guidString)))
{
it->second.uuid = uuid = guidString.get();
}
}
std::wstring uuid = guidString.get();
switch (zoneSetData.type)
{
@ -660,14 +676,14 @@ namespace JSONHelpers
int j = 5;
GridLayoutInfo zoneSetInfo(GridLayoutInfo::Minimal{ .rows = data[j++], .columns = data[j++] });
for (int row = 0; row < zoneSetInfo.rows(); row++)
for (int row = 0; row < zoneSetInfo.rows(); row++, j+=2)
{
zoneSetInfo.rowsPercents()[row] = data[j++] * 256 + data[j++];
zoneSetInfo.rowsPercents()[row] = data[j] * 256 + data[j+1];
}
for (int col = 0; col < zoneSetInfo.columns(); col++)
for (int col = 0; col < zoneSetInfo.columns(); col++, j+=2)
{
zoneSetInfo.columnsPercents()[col] = data[j++] * 256 + data[j++];
zoneSetInfo.columnsPercents()[col] = data[j] * 256 + data[j+1];
}
for (int row = 0; row < zoneSetInfo.rows(); row++)
@ -733,10 +749,14 @@ namespace JSONHelpers
try
{
ZoneSetData zoneSetData;
zoneSetData.uuid = zoneSet.GetNamedString(L"uuid");
zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") });
if (!isValidGuid(zoneSetData.uuid))
{
return std::nullopt;
}
return zoneSetData;
}
catch (const winrt::hresult_error&)
@ -768,6 +788,11 @@ namespace JSONHelpers
result.data.deviceId = zoneSet.GetNamedString(L"device-id");
result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid");
if (!isValidGuid(result.data.zoneSetUuid) || !isValidDeviceId(result.data.deviceId))
{
return std::nullopt;
}
return result;
}
catch (const winrt::hresult_error&)
@ -796,6 +821,10 @@ namespace JSONHelpers
DeviceInfoJSON result;
result.deviceId = device.GetNamedString(L"device-id");
if (!isValidDeviceId(result.deviceId))
{
return std::nullopt;
}
if (auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); zoneSet.has_value())
{
@ -988,6 +1017,11 @@ namespace JSONHelpers
CustomZoneSetJSON result;
result.uuid = customZoneSet.GetNamedString(L"uuid");
if (!isValidGuid(result.uuid))
{
return std::nullopt;
}
result.data.name = customZoneSet.GetNamedString(L"name");
json::JsonObject infoJson = customZoneSet.GetNamedObject(L"info");

View File

@ -16,6 +16,11 @@ namespace JSONHelpers
{
constexpr int MAX_ZONE_COUNT = 50;
#if defined(UNIT_TESTS)
bool isValidGuid(const std::wstring& str);
bool isValidDeviceId(const std::wstring& str);
#endif
enum class ZoneSetLayoutType : int
{
Blank = -1,
@ -202,7 +207,6 @@ namespace JSONHelpers
#if defined(UNIT_TESTS)
inline void clear_data()
{
appliedZoneSetsMap.clear();
appZoneHistoryMap.clear();
deviceInfoMap.clear();
customZoneSetsMap.clear();
@ -249,10 +253,8 @@ namespace JSONHelpers
void SaveFancyZonesData() const;
private:
void TmpMigrateAppliedZoneSetsFromRegistry();
void MigrateCustomZoneSetsFromRegistry();
std::unordered_map<std::wstring, ZoneSetData> appliedZoneSetsMap{};
std::unordered_map<std::wstring, AppZoneHistoryData> appZoneHistoryMap{};
std::unordered_map<std::wstring, DeviceInfoData> deviceInfoMap{};
std::unordered_map<std::wstring, CustomZoneSetData> customZoneSetsMap{};

View File

@ -19,7 +19,7 @@ public:
IFACEMETHODIMP_(bool) GetConfig(_Out_ PWSTR buffer, _Out_ int *buffer_sizeg) noexcept;
IFACEMETHODIMP_(void) SetConfig(PCWSTR config) noexcept;
IFACEMETHODIMP_(void) CallCustomAction(PCWSTR action) noexcept;
IFACEMETHODIMP_(Settings) GetSettings() noexcept { return m_settings; }
IFACEMETHODIMP_(const Settings*) GetSettings() const noexcept { return &m_settings; }
private:
void LoadSettings(PCWSTR config, bool fromFile) noexcept;
@ -36,17 +36,22 @@ private:
PCWSTR name;
bool* value;
int resourceId;
} m_configBools[8] = {
} m_configBools[9 /* 10 */] = { // "Turning FLASHING_ZONE option off"
{ L"fancyzones_shiftDrag", &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG },
{ L"fancyzones_overrideSnapHotkeys", &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS },
{ L"fancyzones_zoneSetChange_flashZones", &m_settings.zoneSetChange_flashZones, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES },
// "Turning FLASHING_ZONE option off"
//{ L"fancyzones_zoneSetChange_flashZones", &m_settings.zoneSetChange_flashZones, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES },
{ L"fancyzones_displayChange_moveWindows", &m_settings.displayChange_moveWindows, IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS },
{ L"fancyzones_zoneSetChange_moveWindows", &m_settings.zoneSetChange_moveWindows, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS },
{ L"fancyzones_virtualDesktopChange_moveWindows", &m_settings.virtualDesktopChange_moveWindows, IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS },
{ L"fancyzones_appLastZone_moveWindows", &m_settings.appLastZone_moveWindows, IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS },
{ L"use_cursorpos_editor_startupscreen", &m_settings.use_cursorpos_editor_startupscreen, IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN },
{ L"fancyzones_show_on_all_monitors", &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS},
{ L"fancyzones_makeDraggedWindowTransparent", &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT},
};
const std::wstring m_zoneColorName = L"fancyzones_zoneColor";
const std::wstring m_zoneBorderColorName = L"fancyzones_zoneBorderColor";
const std::wstring m_zoneHiglightName = L"fancyzones_zoneHighlightColor";
const std::wstring m_editorHotkeyName = L"fancyzones_editor_hotkey";
const std::wstring m_excludedAppsName = L"fancyzones_excluded_apps";
@ -78,8 +83,12 @@ IFACEMETHODIMP_(bool) FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ in
settings.add_bool_toogle(setting.name, setting.resourceId, *setting.value);
}
settings.add_int_spinner(m_zoneHighlightOpacity, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1);
settings.add_color_picker(m_zoneHiglightName, IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, m_settings.zoneHightlightColor);
settings.add_color_picker(m_zoneColorName, IDS_SETTING_DESCRIPTION_ZONECOLOR, m_settings.zoneColor);
settings.add_color_picker(m_zoneBorderColorName, IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, m_settings.zoneBorderColor);
settings.add_int_spinner(m_zoneHighlightOpacity, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1);
settings.add_multiline_string(m_excludedAppsName, IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, m_settings.excludedApps);
return settings.serialize_to_buffer(buffer, buffer_size);
@ -124,6 +133,16 @@ void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept try
}
}
if (auto val = values.get_string_value(m_zoneColorName))
{
m_settings.zoneColor = std::move(*val);
}
if (auto val = values.get_string_value(m_zoneBorderColorName))
{
m_settings.zoneBorderColor = std::move(*val);
}
if (auto val = values.get_string_value(m_zoneHiglightName))
{
m_settings.zoneHightlightColor = std::move(*val);
@ -173,6 +192,8 @@ void FancyZonesSettings::SaveSettings() noexcept try
values.add_property(setting.name, *setting.value);
}
values.add_property(m_zoneColorName, m_settings.zoneColor);
values.add_property(m_zoneBorderColorName, m_settings.zoneBorderColor);
values.add_property(m_zoneHiglightName, m_settings.zoneHightlightColor);
values.add_property(m_zoneHighlightOpacity, m_settings.zoneHighlightOpacity);
values.add_property(m_editorHotkeyName, m_settings.editorHotkey.get_json());

View File

@ -14,8 +14,12 @@ struct Settings
bool overrideSnapHotkeys = false;
bool appLastZone_moveWindows = false;
bool use_cursorpos_editor_startupscreen = true;
std::wstring zoneHightlightColor = L"#0078D7";
int zoneHighlightOpacity = 90;
bool showZonesOnAllMonitors = false;
bool makeDraggedWindowTransparent = true;
std::wstring zoneColor = L"#F5FCFF";
std::wstring zoneBorderColor = L"#FFFFFF";
std::wstring zoneHightlightColor = L"#008CFF";
int zoneHighlightOpacity = 50;
PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3);
std::wstring excludedApps = L"";
std::vector<std::wstring> excludedAppsArray;
@ -28,7 +32,7 @@ interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZones
IFACEMETHOD_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0;
IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0;
IFACEMETHOD_(void, CallCustomAction)(PCWSTR action) = 0;
IFACEMETHOD_(Settings, GetSettings)() = 0;
IFACEMETHOD_(const Settings*, GetSettings)() const = 0;
};
winrt::com_ptr<IFancyZonesSettings> MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR config) noexcept;

View File

@ -66,9 +66,9 @@ void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
{
return;
}
// Take care of 1px border
RECT zoneRect = m_zoneRect;
RECT newWindowRect = m_zoneRect;
RECT windowRect{};
::GetWindowRect(window, &windowRect);
@ -82,38 +82,54 @@ void Zone::SizeWindowToZone(HWND window, HWND zoneWindow) noexcept
const auto left_margin = frameRect.left - windowRect.left;
const auto right_margin = frameRect.right - windowRect.right;
const auto bottom_margin = frameRect.bottom - windowRect.bottom;
zoneRect.left -= left_margin;
zoneRect.right -= right_margin;
zoneRect.bottom -= bottom_margin;
newWindowRect.left -= left_margin;
newWindowRect.right -= right_margin;
newWindowRect.bottom -= bottom_margin;
}
// Map to screen coords
MapWindowRect(zoneWindow, nullptr, &zoneRect);
MapWindowRect(zoneWindow, nullptr, &newWindowRect);
MONITORINFO mi{sizeof(mi)};
MONITORINFO mi{ sizeof(mi) };
if (GetMonitorInfoW(MonitorFromWindow(zoneWindow, MONITOR_DEFAULTTONEAREST), &mi))
{
const auto taskbar_left_size = std::abs(mi.rcMonitor.left - mi.rcWork.left);
const auto taskbar_top_size = std::abs(mi.rcMonitor.top - mi.rcWork.top);
OffsetRect(&zoneRect, -taskbar_left_size, -taskbar_top_size);
OffsetRect(&newWindowRect, -taskbar_left_size, -taskbar_top_size);
if (accountForUnawareness)
{
zoneRect.left = max(mi.rcMonitor.left, zoneRect.left);
zoneRect.right = min(mi.rcMonitor.right - taskbar_left_size, zoneRect.right);
zoneRect.top = max(mi.rcMonitor.top, zoneRect.top);
zoneRect.bottom = min(mi.rcMonitor.bottom - taskbar_top_size, zoneRect.bottom);
newWindowRect.left = max(mi.rcMonitor.left, newWindowRect.left);
newWindowRect.right = min(mi.rcMonitor.right - taskbar_left_size, newWindowRect.right);
newWindowRect.top = max(mi.rcMonitor.top, newWindowRect.top);
newWindowRect.bottom = min(mi.rcMonitor.bottom - taskbar_top_size, newWindowRect.bottom);
}
}
if ((::GetWindowLong(window, GWL_STYLE) & WS_SIZEBOX) == 0)
{
newWindowRect.right = newWindowRect.left + (windowRect.right - windowRect.left);
newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top);
}
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
placement.rcNormalPosition = zoneRect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
//wait if SW_SHOWMINIMIZED would be removed from window (Issue #1685)
for (int i = 0; i < 5 && (placement.showCmd & SW_SHOWMINIMIZED) != 0; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
::GetWindowPlacement(window, &placement);
}
// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
if ((placement.showCmd & SW_SHOWMINIMIZED) == 0)
{
placement.showCmd = SW_RESTORE | SW_SHOWNA;
}
placement.rcNormalPosition = newWindowRect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
::SetWindowPlacement(window, &placement);
// Do it again, allowing Windows to resize the window and set correct scaling
// This fixes Issue #365

View File

@ -130,8 +130,8 @@ public:
GetZones() noexcept { return m_zones; }
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept;
IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept;
IFACEMETHODIMP_(bool)
@ -226,7 +226,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
if (index >= int(m_zones.size()))
{
index = 0;
return;
}
while (auto zoneDrop = ZoneFromWindow(window))
@ -240,12 +240,12 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
}
}
IFACEMETHODIMP_(void)
ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept
IFACEMETHODIMP_(bool)
ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode, bool cycle) noexcept
{
if (m_zones.empty())
{
return;
return false;
}
winrt::com_ptr<IZone> oldZone = nullptr;
@ -262,6 +262,11 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
{
if (iter == m_zones.begin())
{
if (!cycle)
{
oldZone->RemoveWindowFromZone(window, false);
return false;
}
iter = m_zones.end();
}
iter--;
@ -271,6 +276,11 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
iter++;
if (iter == m_zones.end())
{
if (!cycle)
{
oldZone->RemoveWindowFromZone(window, false);
return false;
}
iter = m_zones.begin();
}
}
@ -283,7 +293,9 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
oldZone->RemoveWindowFromZone(window, false);
}
newZone->AddWindowToZone(window, windowZone, true);
return true;
}
return false;
}
IFACEMETHODIMP_(void)
@ -560,7 +572,7 @@ bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo grid
// Note: The expressions below are carefully written to
// make the sum of all zones' sizes exactly total{Width|Height}
long long totalPercents = 0;
int totalPercents = 0;
for (int row = 0; row < gridLayoutInfo.rows(); row++)
{
rowInfo[row].Start = totalPercents * totalHeight / C_MULTIPLIER + (row + 1) * spacing;

View File

@ -57,8 +57,12 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area.
* @param vkCode Pressed arrow key.
* @param cycle Whether we should move window to the first zone if we reached last zone in layout.
*
* @returns Boolean which is always true if cycle argument is set, otherwise indicating if there is more
* zones left in the zone layout in which window can move.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode) = 0;
IFACEMETHOD_(bool, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) = 0;
/**
* Assign window to the zone based on cursor coordinates.
*
@ -75,7 +79,8 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet :
* @param monitorInfo Information about monitor on which zone layout is applied.
* @param zoneCount Number of zones inside zone layout.
* @param spacing Spacing between zones in pixels.
* @returns Boolean if calculation was successful.
*
* @returns Boolean indicating if calculation was successful.
*/
IFACEMETHOD_(bool, CalculateZones)(MONITORINFO monitorInfo, int zoneCount, int spacing) = 0;
};

View File

@ -10,6 +10,8 @@
#include <ShellScalingApi.h>
#include <mutex>
#include <gdiplus.h>
namespace ZoneWindowUtils
{
const std::wstring& GetActiveZoneSetTmpPath()
@ -92,146 +94,70 @@ namespace ZoneWindowDrawUtils
int thickness{};
};
bool IsOccluded(const std::vector<winrt::com_ptr<IZone>>& zones, POINT pt, size_t index) noexcept
{
size_t i = 1;
for (auto iter = zones.begin(); iter != zones.end(); iter++)
{
if (winrt::com_ptr<IZone> zone = iter->try_as<IZone>())
{
if (i < index)
{
if (PtInRect(&zone->GetZoneRect(), pt))
{
return true;
}
}
}
i++;
}
return false;
}
void DrawBackdrop(wil::unique_hdc& hdc, RECT const& clientRect) noexcept
{
FillRectARGB(hdc, &clientRect, 0, RGB(0, 0, 0), false);
}
void DrawIndex(wil::unique_hdc& hdc, POINT offset, size_t index, int padding, int size, bool flipX, bool flipY, COLORREF colorFill)
void DrawIndex(wil::unique_hdc& hdc, Rect rect, size_t index)
{
RECT rect = { offset.x, offset.y, offset.x + size, offset.y + size };
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
RECT useRect = rect;
if (flipX)
{
if (x == 0)
useRect.left += (size + padding + size + padding);
else if (x == 2)
useRect.left -= (size + padding + size + padding);
useRect.right = useRect.left + size;
}
Gdiplus::Graphics g(hdc.get());
if (flipY)
{
if (y == 0)
useRect.top += (size + padding + size + padding);
else if (y == 2)
useRect.top -= (size + padding + size + padding);
useRect.bottom = useRect.top + size;
}
Gdiplus::FontFamily fontFamily(L"Segoe ui");
Gdiplus::Font font(&fontFamily, 80, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
Gdiplus::SolidBrush solidBrush(Gdiplus::Color(255, 0, 0, 0));
FillRectARGB(hdc, &useRect, 200, RGB(50, 50, 50), true);
std::wstring text = std::to_wstring(index);
RECT inside = useRect;
InflateRect(&inside, -2, -2);
g.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
Gdiplus::StringFormat stringFormat = new Gdiplus::StringFormat();
stringFormat.SetAlignment(Gdiplus::StringAlignmentCenter);
stringFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter);
FillRectARGB(hdc, &inside, 100, colorFill, true);
Gdiplus::RectF gdiRect(static_cast<Gdiplus::REAL>(rect.left()),
static_cast<Gdiplus::REAL>(rect.top()),
static_cast<Gdiplus::REAL>(rect.width()),
static_cast<Gdiplus::REAL>(rect.height()));
rect.left += (size + padding);
rect.right = rect.left + size;
if (--index == 0)
{
return;
}
}
rect.left = offset.x;
rect.right = rect.left + size;
rect.top += (size + padding);
rect.bottom = rect.top + size;
}
g.DrawString(text.c_str(), -1, &font, gdiRect, &stringFormat, &solidBrush);
}
void DrawZone(wil::unique_hdc& hdc, ColorSetting const& colorSetting, winrt::com_ptr<IZone> zone, const std::vector<winrt::com_ptr<IZone>>& zones, bool flashMode) noexcept
{
RECT zoneRect = zone->GetZoneRect();
if (colorSetting.borderAlpha > 0)
{
FillRectARGB(hdc, &zoneRect, colorSetting.borderAlpha, colorSetting.border, false);
InflateRect(&zoneRect, colorSetting.thickness, colorSetting.thickness);
}
FillRectARGB(hdc, &zoneRect, colorSetting.fillAlpha, colorSetting.fill, false);
if (flashMode)
{
return;
}
COLORREF const colorFill = RGB(255, 255, 255);
Gdiplus::Graphics g(hdc.get());
Gdiplus::Color fillColor(colorSetting.fillAlpha, GetRValue(colorSetting.fill), GetGValue(colorSetting.fill), GetBValue(colorSetting.fill));
Gdiplus::Color borderColor(colorSetting.borderAlpha, GetRValue(colorSetting.border), GetGValue(colorSetting.border), GetBValue(colorSetting.border));
size_t const index = zone->Id();
int const padding = 5;
int const size = 10;
POINT offset = { zoneRect.left + padding, zoneRect.top + padding };
if (!IsOccluded(zones, offset, index))
{
DrawIndex(hdc, offset, index, padding, size, false, false, colorFill); // top left
return;
}
Gdiplus::Rect rectangle(zoneRect.left, zoneRect.top, zoneRect.right - zoneRect.left - 1, zoneRect.bottom - zoneRect.top - 1);
offset.x = zoneRect.right - ((padding + size) * 3);
if (!IsOccluded(zones, offset, index))
{
DrawIndex(hdc, offset, index, padding, size, true, false, colorFill); // top right
return;
}
Gdiplus::Pen pen(borderColor, static_cast<Gdiplus::REAL>(colorSetting.thickness));
g.FillRectangle(new Gdiplus::SolidBrush(fillColor), rectangle);
g.DrawRectangle(&pen, rectangle);
offset.y = zoneRect.bottom - ((padding + size) * 3);
if (!IsOccluded(zones, offset, index))
if (!flashMode)
{
DrawIndex(hdc, offset, index, padding, size, true, true, colorFill); // bottom right
return;
DrawIndex(hdc, zoneRect, zone->Id());
}
offset.x = zoneRect.left + padding;
DrawIndex(hdc, offset, index, padding, size, false, true, colorFill); // bottom left
}
void DrawActiveZoneSet(wil::unique_hdc& hdc, COLORREF highlightColor, int highlightOpacity, const std::vector<winrt::com_ptr<IZone>>& zones, const winrt::com_ptr<IZone>& highlightZone, bool flashMode, bool drawHints) noexcept
void DrawActiveZoneSet(wil::unique_hdc& hdc,
COLORREF zoneColor,
COLORREF zoneBorderColor,
COLORREF highlightColor,
int zoneOpacity,
const std::vector<winrt::com_ptr<IZone>>& zones,
const winrt::com_ptr<IZone>& highlightZone,
bool flashMode,
bool drawHints) noexcept
{
static constexpr std::array<COLORREF, 9> colors{
RGB(75, 75, 85),
RGB(150, 150, 160),
RGB(100, 100, 110),
RGB(125, 125, 135),
RGB(225, 225, 235),
RGB(25, 25, 35),
RGB(200, 200, 210),
RGB(50, 50, 60),
RGB(175, 175, 185),
};
// { fillAlpha, fill, borderAlpha, border, thickness }
ColorSetting const colorHints{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 };
ColorSetting colorViewer{ OpacitySettingToAlpha(zoneOpacity), 0, 255, RGB(40, 50, 60), -2 };
ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 };
ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
// ColorSetting { fillAlpha, fill, borderAlpha, border, thickness }
ColorSetting const colorHints{ 225, RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 };
ColorSetting colorViewer{ OpacitySettingToAlpha(highlightOpacity), 0, 255, RGB(40, 50, 60), -2 };
ColorSetting colorHighlight{ OpacitySettingToAlpha(highlightOpacity), 0, 255, 0, -2 };
ColorSetting const colorFlash{ 200, RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
const size_t maxColorIndex = min(size(zones) - 1, size(colors) - 1);
size_t colorIndex = maxColorIndex;
for (auto iter = zones.begin(); iter != zones.end(); iter++)
{
winrt::com_ptr<IZone> zone = iter->try_as<IZone>();
@ -251,20 +177,17 @@ namespace ZoneWindowDrawUtils
DrawZone(hdc, colorHints, zone, zones, flashMode);
}
{
colorViewer.fill = colors[colorIndex];
colorViewer.fill = zoneColor;
colorViewer.border = zoneBorderColor;
DrawZone(hdc, colorViewer, zone, zones, flashMode);
}
}
colorIndex = colorIndex != 0 ? colorIndex - 1 : maxColorIndex;
}
if (highlightZone)
{
colorHighlight.fill = highlightColor;
colorHighlight.border = RGB(
max(0, GetRValue(colorHighlight.fill) - 25),
max(0, GetGValue(colorHighlight.fill) - 25),
max(0, GetBValue(colorHighlight.fill) - 25));
colorHighlight.border = zoneBorderColor;
DrawZone(hdc, colorHighlight, highlightZone, zones, flashMode);
}
}
@ -274,18 +197,21 @@ struct ZoneWindow : public winrt::implements<ZoneWindow, IZoneWindow>
{
public:
ZoneWindow(HINSTANCE hinstance);
~ZoneWindow();
bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones);
IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept;
IFACEMETHODIMP MoveSizeCancel() noexcept;
IFACEMETHODIMP_(void)
RestoreOrginalTransparency() noexcept;
IFACEMETHODIMP_(bool)
IsDragEnabled() noexcept { return m_dragEnabled; }
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, int index) noexcept;
IFACEMETHODIMP_(void)
MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept;
IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void)
CycleActiveZoneSet(DWORD vkCode) noexcept;
IFACEMETHODIMP_(std::wstring)
@ -296,13 +222,15 @@ public:
SaveWindowProcessToZoneIndex(HWND window) noexcept;
IFACEMETHODIMP_(IZoneSet*)
ActiveZoneSet() noexcept { return m_activeZoneSet.get(); }
IFACEMETHODIMP_(void)
ShowZoneWindow() noexcept;
IFACEMETHODIMP_(void)
HideZoneWindow() noexcept;
protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
private:
void ShowZoneWindow() noexcept;
void HideZoneWindow() noexcept;
void LoadSettings() noexcept;
void InitializeZoneSets(MONITORINFO const& mi) noexcept;
void CalculateZoneSet() noexcept;
@ -330,6 +258,14 @@ private:
size_t m_keyCycle{};
static const UINT m_showAnimationDuration = 200; // ms
static const UINT m_flashDuration = 700; // ms
HWND draggedWindow = nullptr;
long draggedWindowExstyle = 0;
COLORREF draggedWindowCrKey = RGB(0, 0, 0);
DWORD draggedWindowDwFlags = 0;
BYTE draggedWindowInitialAlpha = 0;
ULONG_PTR gdiplusToken;
};
ZoneWindow::ZoneWindow(HINSTANCE hinstance)
@ -341,6 +277,16 @@ ZoneWindow::ZoneWindow(HINSTANCE hinstance)
wcex.lpszClassName = L"SuperFancyZones_ZoneWindow";
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
RegisterClassExW(&wcex);
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
}
ZoneWindow::~ZoneWindow()
{
RestoreOrginalTransparency();
Gdiplus::GdiplusShutdown(gdiplusToken);
}
bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones)
@ -398,6 +344,22 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window, bool dragEnabled) noexcept
return E_INVALIDARG;
}
if (m_host->isMakeDraggedWindowTransparentActive())
{
RestoreOrginalTransparency();
draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE);
draggedWindow = window;
SetWindowLong(window,
GWL_EXSTYLE,
draggedWindowExstyle | WS_EX_LAYERED);
GetLayeredWindowAttributes(window, &draggedWindowCrKey, &draggedWindowInitialAlpha, &draggedWindowDwFlags);
SetLayeredWindowAttributes(window, 0, (255 * 50) / 100, LWA_ALPHA);
}
m_dragEnabled = dragEnabled;
m_windowMoveSize = window;
m_drawHints = true;
@ -435,6 +397,8 @@ IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnable
IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept
{
RestoreOrginalTransparency();
if (m_windowMoveSize != window)
{
return E_INVALIDARG;
@ -455,10 +419,15 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexc
return S_OK;
}
IFACEMETHODIMP ZoneWindow::MoveSizeCancel() noexcept
IFACEMETHODIMP_(void)
ZoneWindow::RestoreOrginalTransparency() noexcept
{
HideZoneWindow();
return S_OK;
if (m_host->isMakeDraggedWindowTransparentActive() && draggedWindow != nullptr)
{
SetLayeredWindowAttributes(draggedWindow, draggedWindowCrKey, draggedWindowInitialAlpha, draggedWindowDwFlags);
SetWindowLong(draggedWindow, GWL_EXSTYLE, draggedWindowExstyle);
draggedWindow = nullptr;
}
}
IFACEMETHODIMP_(void)
@ -470,14 +439,18 @@ ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept
}
}
IFACEMETHODIMP_(void)
ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept
IFACEMETHODIMP_(bool)
ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept
{
if (m_activeZoneSet)
{
m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode);
SaveWindowProcessToZoneIndex(window);
if (m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode, cycle))
{
SaveWindowProcessToZoneIndex(window);
return true;
}
}
return false;
}
IFACEMETHODIMP_(void)
@ -514,8 +487,8 @@ ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept
}
}
#pragma region private
void ZoneWindow::ShowZoneWindow() noexcept
IFACEMETHODIMP_(void)
ZoneWindow::ShowZoneWindow() noexcept
{
if (m_window)
{
@ -536,7 +509,8 @@ void ZoneWindow::ShowZoneWindow() noexcept
}
}
void ZoneWindow::HideZoneWindow() noexcept
IFACEMETHODIMP_(void)
ZoneWindow::HideZoneWindow() noexcept
{
if (m_window)
{
@ -548,6 +522,8 @@ void ZoneWindow::HideZoneWindow() noexcept
}
}
#pragma region private
void ZoneWindow::LoadSettings() noexcept
{
JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId);
@ -630,7 +606,8 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
switch (message)
{
case WM_NCDESTROY: {
case WM_NCDESTROY:
{
::DefWindowProc(m_window.get(), message, wparam, lparam);
SetWindowLongPtr(m_window.get(), GWLP_USERDATA, 0);
}
@ -640,7 +617,8 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
return 1;
case WM_PRINTCLIENT:
case WM_PAINT: {
case WM_PAINT:
{
PAINTSTRUCT ps;
wil::unique_hdc hdc{ reinterpret_cast<HDC>(wparam) };
if (!hdc)
@ -677,9 +655,18 @@ void ZoneWindow::OnPaint(wil::unique_hdc& hdc) noexcept
if (bufferedPaint)
{
ZoneWindowDrawUtils::DrawBackdrop(hdcMem, clientRect);
if (m_activeZoneSet && m_host)
{
ZoneWindowDrawUtils::DrawActiveZoneSet(hdcMem, m_host->GetZoneHighlightColor(), m_host->GetZoneHighlightOpacity(), m_activeZoneSet->GetZones(), m_highlightZone, m_flashMode, m_drawHints);
ZoneWindowDrawUtils::DrawActiveZoneSet(hdcMem,
m_host->GetZoneColor(),
m_host->GetZoneBorderColor(),
m_host->GetZoneHighlightColor(),
m_host->GetZoneHighlightOpacity(),
m_activeZoneSet->GetZones(),
m_highlightZone,
m_flashMode,
m_drawHints);
}
EndBufferedPaint(bufferedPaint, TRUE);
@ -757,6 +744,12 @@ void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::Inp
void ZoneWindow::FlashZones() noexcept
{
// "Turning FLASHING_ZONE option off"
if (true)
{
return;
}
m_flashMode = true;
ShowWindow(m_window.get(), SW_SHOWNA);

View File

@ -41,10 +41,6 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* @param ptScreen Cursor coordinates where window is droped.
*/
IFACEMETHOD(MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0;
/**
* Abort tracking down of window position and giving zone layout hints (if dragging functionality is enabled).
*/
IFACEMETHOD(MoveSizeCancel)() = 0;
/**
* @returns Boolean indicating is giving hints about active zone layout enabled. Hints are
* given while dragging window while holding SHIFT key.
@ -62,14 +58,22 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
*
* @param window Handle of window which should be assigned to zone.
* @param vkCode Pressed arrow key.
* @param cycle Whether we should move window to the first zone if we reached last zone in layout.
*
* @returns Boolean which is always true if cycle argument is set, otherwise indicating if there is more
* zones left in the zone layout in which window can move.
*/
IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, DWORD vkCode) = 0;
IFACEMETHOD_(bool, MoveWindowIntoZoneByDirection)(HWND window, DWORD vkCode, bool cycle) = 0;
/**
* Cycle through active zone layouts (giving hints about each layout).
*
* @param vkCode Pressed key representing layout index.
*/
IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0;
/**
* Restore orginal transaprency of dragged window.
*/
IFACEMETHOD_(void, RestoreOrginalTransparency) () = 0;
/**
* Save information about zone in which window was assigned, when closing the window.
* Used once we open same window again to assign it to its previous zone.
@ -89,6 +93,8 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow
* @returns Active zone layout for this work area.
*/
IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0;
IFACEMETHOD_(void, ShowZoneWindow)() = 0;
IFACEMETHOD_(void, HideZoneWindow)() = 0;
};
winrt::com_ptr<IZoneWindow> MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor,

View File

@ -1,59 +1,66 @@
// Microsoft Visual C++ generated resource script.
⼀⼀ഀ<EFBFBD>
⌀椀渀挀氀甀搀攀 ∀爀攀猀漀甀爀挀攀⸀栀∀ഀ<EFBFBD>
⌀椀渀挀氀甀搀攀 ∀⸀⸀⼀⸀⸀⼀⸀⸀⼀挀漀洀洀漀渀⼀瘀攀爀猀椀漀渀⸀栀∀ഀ<EFBFBD>
<EFBFBD>
匀吀刀䤀一䜀吀䄀䈀䰀䔀ഀ<EFBFBD>
䈀䔀䜀䤀一ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一                                    ∀䌀爀攀愀琀攀 眀椀渀搀漀眀 氀愀礀漀甀琀猀 琀漀 栀攀氀瀀 洀愀欀攀 洀甀氀琀椀ⴀ琀愀猀欀椀渀最 攀愀猀礀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开匀䠀䤀䘀吀䐀刀䄀䜀                          ∀伀渀㨀 䠀漀氀搀 匀栀椀昀琀 欀攀礀 漀爀 愀渀礀 渀漀渀ⴀ瀀爀椀洀愀爀礀 洀漀甀猀攀 戀甀琀琀漀渀 琀漀 攀渀愀戀氀攀 稀漀渀攀猀 眀栀椀氀攀 搀爀愀最最椀渀最∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开伀嘀䔀刀刀䤀䐀䔀开匀一䄀倀开䠀伀吀䬀䔀夀匀              ∀伀瘀攀爀爀椀搀攀 圀椀渀搀漀眀猀 匀渀愀瀀 栀漀琀欀攀礀猀 眀椀渀⬀愀爀爀漀眀⤀ 琀漀 洀漀瘀攀 眀椀渀搀漀眀猀 戀攀琀眀攀攀渀 稀漀渀攀猀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开䐀䤀匀倀䰀䄀夀䌀䠀䄀一䜀䔀开䴀伀嘀䔀圀䤀一䐀伀圀匀          ∀䬀攀攀瀀 眀椀渀搀漀眀猀 椀渀 琀栀攀椀爀 稀漀渀攀猀 眀栀攀渀 琀栀攀 猀挀爀攀攀渀 爀攀猀漀氀甀琀椀漀渀 挀栀愀渀最攀猀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开娀伀一䔀匀䔀吀䌀䠀䄀一䜀䔀开䴀伀嘀䔀圀䤀一䐀伀圀匀          ∀䐀甀爀椀渀最 稀漀渀攀 氀愀礀漀甀琀 挀栀愀渀最攀猀Ⰰ 眀椀渀搀漀眀猀 愀猀猀椀最渀攀搀 琀漀  稀漀渀攀 眀椀氀氀 洀愀琀挀栀 渀攀眀 猀椀稀攀⼀瀀漀猀椀琀椀漀渀猀∀  <EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开嘀䤀刀吀唀䄀䰀䐀䔀匀䬀吀伀倀䌀䠀䄀一䜀䔀开䴀伀嘀䔀圀䤀一䐀伀圀匀   ∀䬀攀攀瀀 眀椀渀搀漀眀猀 瀀椀渀渀攀搀 琀漀 洀甀氀琀椀瀀氀攀 搀攀猀欀琀漀瀀猀 椀渀 琀栀攀 猀愀洀攀 稀漀渀攀 眀栀攀渀 琀栀攀 愀挀琀椀瘀攀 搀攀猀欀琀漀瀀 挀栀愀渀最攀猀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开娀伀一䔀匀䔀吀䌀䠀䄀一䜀䔀开䘀䰀䄀匀䠀娀伀一䔀匀           ∀䘀氀愀猀栀 稀漀渀攀猀 眀栀攀渀 琀栀攀 愀挀琀椀瘀攀 䘀愀渀挀礀娀漀渀攀猀 氀愀礀漀甀琀 挀栀愀渀最攀猀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开娀伀一䔀䠀䤀䜀䠀䰀䤀䜀䠀吀䌀伀䰀伀刀                 ∀娀漀渀攀 䠀椀最栀氀椀最栀琀 䌀漀氀漀爀 䐀攀昀愀甀氀琀   㜀㠀䐀㜀⤀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开唀匀䔀开䌀唀刀匀伀刀倀伀匀开䔀䐀䤀吀伀刀开匀吀䄀刀吀唀倀匀䌀刀䔀䔀一 ∀䘀漀氀氀漀眀 洀漀甀猀攀 挀甀爀猀漀爀 椀渀猀琀攀愀搀 漀昀 昀漀挀甀猀 眀栀攀渀 氀愀甀渀挀栀椀渀最 攀搀椀琀漀爀 椀渀  洀甀氀琀椀 猀挀爀攀攀渀 攀渀瘀椀爀漀渀洀攀渀琀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䐀䔀匀䌀刀䤀倀吀䤀伀一开䄀倀倀䰀䄀匀吀娀伀一䔀开䴀伀嘀䔀圀䤀一䐀伀圀匀            ∀䴀漀瘀攀 渀攀眀氀礀 挀爀攀愀琀攀搀 眀椀渀搀漀眀猀 琀漀 琀栀攀椀爀 氀愀猀琀 欀渀漀眀渀 稀漀渀攀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䰀䄀唀一䌀䠀开䔀䐀䤀吀伀刀开䰀䄀䈀䔀䰀                            ∀娀漀渀攀 挀漀渀昀椀最甀爀愀琀椀漀渀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䰀䄀唀一䌀䠀开䔀䐀䤀吀伀刀开䈀唀吀吀伀一                           ∀䔀搀椀琀 稀漀渀攀猀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䰀䄀唀一䌀䠀开䔀䐀䤀吀伀刀开䐀䔀匀䌀刀䤀倀吀䤀伀一                      ∀吀漀 氀愀甀渀挀栀 琀栀攀 稀漀渀攀 攀搀椀琀漀爀Ⰰ 猀攀氀攀挀琀 琀栀攀 䔀搀椀琀 稀漀渀攀猀 戀甀琀琀漀渀 戀攀氀漀眀 漀爀 瀀爀攀猀猀 琀栀攀 稀漀渀攀 攀搀椀琀漀爀 栀漀琀欀攀礀 愀渀礀琀椀洀攀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䰀䄀唀一䌀䠀开䔀䐀䤀吀伀刀开䠀伀吀䬀䔀夀开䰀䄀䈀䔀䰀                     ∀䌀漀渀昀椀最甀爀攀 琀栀攀 稀漀渀攀 攀搀椀琀漀爀 栀漀琀欀攀礀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀开䔀堀䌀䰀䌀唀䐀䔀䐀开䄀倀倀匀开䐀䔀匀䌀刀䤀倀吀䤀伀一                     ∀吀漀 攀砀挀氀甀搀攀 愀渀 愀瀀瀀氀椀挀愀琀椀漀渀 昀爀漀洀 猀渀愀瀀瀀椀渀最 琀漀 稀漀渀攀猀 愀搀搀 椀琀猀 渀愀洀攀 栀攀爀攀 漀渀攀 瀀攀爀 氀椀渀攀⤀⸀ 䔀砀挀氀甀搀攀搀 愀瀀瀀猀 眀椀氀氀 爀攀愀挀琀 琀漀 琀栀攀 圀椀渀搀漀眀猀 匀渀愀瀀 爀攀最愀爀搀氀攀猀猀 漀昀 愀氀氀 漀琀栀攀爀 猀攀琀琀椀渀最猀⸀∀ഀ<EFBFBD>
    䤀䐀匀开匀䔀吀吀䤀一䜀匀开䠀䤀䜀䠀䰀䤀䜀䠀吀开伀倀䄀䌀䤀吀夀                             ∀娀漀渀攀 伀瀀愀挀椀琀礀∀ <EFBFBD>
    䤀䐀匀开䘀䄀一䌀夀娀伀一䔀匀                                             䰀∀䘀愀渀挀礀娀漀渀攀猀∀ <EFBFBD>
䔀一䐀ഀ<EFBFBD>
<EFBFBD>
 嘀䔀刀匀䤀伀一䤀一䘀伀ഀ<EFBFBD>
 䘀䤀䰀䔀嘀䔀刀匀䤀伀一 䘀䤀䰀䔀开嘀䔀刀匀䤀伀一ഀ<EFBFBD>
 倀刀伀䐀唀䌀吀嘀䔀刀匀䤀伀一 倀刀伀䐀唀䌀吀开嘀䔀刀匀䤀伀一ഀ<EFBFBD>
 䘀䤀䰀䔀䘀䰀䄀䜀匀䴀䄀匀䬀  砀㌀昀䰀ഀ<EFBFBD>
⌀椀昀搀攀昀 开䐀䔀䈀唀䜀ഀ<EFBFBD>
 䘀䤀䰀䔀䘀䰀䄀䜀匀  砀㄀䰀ഀ<EFBFBD>
⌀攀氀猀攀ഀ<EFBFBD>
 䘀䤀䰀䔀䘀䰀䄀䜀匀   䰀ഀ<EFBFBD>
⌀攀渀搀椀昀ഀ<EFBFBD>
 䘀䤀䰀䔀伀匀  砀㐀   㐀䰀ഀ<EFBFBD>
 䘀䤀䰀䔀吀夀倀䔀  砀㈀䰀ഀ<EFBFBD>
 䘀䤀䰀䔀匀唀䈀吀夀倀䔀   䰀ഀ<EFBFBD>
䈀䔀䜀䤀一ഀ<EFBFBD>
    䈀䰀伀䌀䬀 ∀匀琀爀椀渀最䘀椀氀攀䤀渀昀漀∀ഀ<EFBFBD>
    䈀䔀䜀䤀一ഀ<EFBFBD>
        䈀䰀伀䌀䬀    㐀戀 ∀ഀ<EFBFBD>
        䈀䔀䜀䤀一ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀䌀漀洀瀀愀渀礀一愀洀攀∀Ⰰ 䌀伀䴀倀䄀一夀开一䄀䴀䔀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀䘀椀氀攀䐀攀猀挀爀椀瀀琀椀漀渀∀Ⰰ ∀䘀愀渀挀礀娀漀渀攀猀 倀漀眀攀爀吀漀礀 䴀漀搀甀氀攀∀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀䘀椀氀攀嘀攀爀猀椀漀渀∀Ⰰ 䘀䤀䰀䔀开嘀䔀刀匀䤀伀一开匀吀刀䤀一䜀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀䤀渀琀攀爀渀愀氀一愀洀攀∀Ⰰ ∀䘀愀渀挀礀娀漀渀攀猀 倀漀眀攀爀吀漀礀∀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀䰀攀最愀氀䌀漀瀀礀爀椀最栀琀∀Ⰰ 䌀伀倀夀刀䤀䜀䠀吀开一伀吀䔀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀伀爀椀最椀渀愀氀䘀椀氀攀渀愀洀攀∀Ⰰ ∀昀愀渀挀礀稀漀渀攀猀开瀀漀眀攀爀琀漀礀⸀搀氀氀∀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀倀爀漀搀甀挀琀一愀洀攀∀Ⰰ ∀䘀愀渀挀礀娀漀渀攀猀 倀漀眀攀爀吀漀礀∀ഀ<EFBFBD>
            嘀䄀䰀唀䔀 ∀倀爀漀搀甀挀琀嘀攀爀猀椀漀渀∀Ⰰ 倀刀伀䐀唀䌀吀开嘀䔀刀匀䤀伀一开匀吀刀䤀一䜀ഀ<EFBFBD>
        䔀一䐀ഀ<EFBFBD>
    䔀一䐀ഀ<EFBFBD>
    䈀䰀伀䌀䬀 ∀嘀愀爀䘀椀氀攀䤀渀昀漀∀ഀ<EFBFBD>
    䈀䔀䜀䤀一ഀ<EFBFBD>
        嘀䄀䰀唀䔀 ∀吀爀愀渀猀氀愀琀椀漀渀∀Ⰰ  砀㐀 㤀Ⰰ ㄀㈀  <EFBFBD>
    䔀一䐀ഀ<EFBFBD>
䔀一䐀ഀ<EFBFBD>
<EFBFBD>
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#include "../../../common/version.h"
STRINGTABLE
BEGIN
IDS_SETTING_DESCRIPTION "Create window layouts to help make multi-tasking easy"
IDS_SETTING_DESCRIPTION_SHIFTDRAG "On: Hold Shift key or any non-primary mouse button to enable zones while dragging"
IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS "Override Windows Snap hotkeys (win+arrow) to move windows between zones"
IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS "Keep windows in their zones when the screen resolution changes"
IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS "During zone layout changes, windows assigned to a zone will match new size/positions"
IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS "Keep windows pinned to multiple desktops in the same zone when the active desktop changes"
IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES "Flash zones when the active FancyZones layout changes"
IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS "Show zones on all monitors while dragging a window"
IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT "Make dragged window transparent"
IDS_SETTING_DESCRIPTION_ZONECOLOR "Zone inactive color (Default #F5FCFF)"
IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR "Zone border color (Default #FFFFFF)"
IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR "Zone highlight color (Default #008CFF)"
IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN "Follow mouse cursor instead of focus when launching editor in a multi screen environment"
IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS "Move newly created windows to their last known zone"
IDS_SETTING_LAUNCH_EDITOR_LABEL "Zone configuration"
IDS_SETTING_LAUNCH_EDITOR_BUTTON "Edit zones"
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION "To launch the zone editor, select the Edit zones button below or press the zone editor hotkey anytime"
IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL "Configure the zone editor hotkey"
IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION "To exclude an application from snapping to zones add its name here (one per line). Excluded apps will react to the Windows Snap regardless of all other settings."
IDS_SETTINGS_HIGHLIGHT_OPACITY "Zone opacity (%)"
IDS_FANCYZONES L"FancyZones"
IDS_CANT_DRAG_ELEVATED L"We've detected an application running with administrator privileges. This blocks some functionality in PowerToys. Visit our wiki page to learn more."
IDS_CANT_DRAG_ELEVATED_LEARN_MORE L"Learn more"
IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN L"Don't show again"
END
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", "FancyZones PowerToy Module"
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", "FancyZones PowerToy"
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", "fancyzones_powertoy.dll"
VALUE "ProductName", "FancyZones PowerToy"
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@ -1,17 +1,24 @@
#define IDS_SETTING_DESCRIPTION_SHIFTDRAG 101
#define IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS 102
#define IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS 103
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS 104
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES 105
#define IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS 106
#define IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR 107
#define IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS 108
#define IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN 109
#define IDS_SETTING_DESCRIPTION 110
#define IDS_SETTING_LAUNCH_EDITOR_LABEL 111
#define IDS_SETTING_LAUNCH_EDITOR_BUTTON 112
#define IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION 113
#define IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL 114
#define IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION 115
#define IDS_SETTINGS_HIGHLIGHT_OPACITY 116
#define IDS_FANCYZONES 117
#define IDS_SETTING_DESCRIPTION_SHIFTDRAG 101
#define IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS 102
#define IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS 103
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS 104
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES 105
#define IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS 106
#define IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS 107
#define IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT 108
#define IDS_SETTING_DESCRIPTION_ZONECOLOR 109
#define IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR 110
#define IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR 111
#define IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS 112
#define IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN 113
#define IDS_SETTING_DESCRIPTION 114
#define IDS_SETTING_LAUNCH_EDITOR_LABEL 115
#define IDS_SETTING_LAUNCH_EDITOR_BUTTON 116
#define IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION 117
#define IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL 118
#define IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION 119
#define IDS_SETTINGS_HIGHLIGHT_OPACITY 120
#define IDS_FANCYZONES 121
#define IDS_CANT_DRAG_ELEVATED 122
#define IDS_CANT_DRAG_ELEVATED_LEARN_MORE 123
#define IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN 124

View File

@ -147,6 +147,16 @@ void Trace::FancyZones::DataChanged() noexcept
TraceLoggingWideString(activeZoneSetInfo.c_str(), "ActiveZoneSetsList"));
}
void Trace::FancyZones::EditorLaunched(int value) noexcept
{
TraceLoggingWrite(
g_hProvider,
"FancyZones_EditorLaunch",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingInt32(value, "Value"));
}
void Trace::SettingsChanged(const Settings& settings) noexcept
{
const auto& editorHotkey = settings.editorHotkey;
@ -170,6 +180,10 @@ void Trace::SettingsChanged(const Settings& settings) noexcept
TraceLoggingBoolean(settings.overrideSnapHotkeys, "OverrideSnapHotKeys"),
TraceLoggingBoolean(settings.appLastZone_moveWindows, "MoveWindowsToLastZoneOnAppOpening"),
TraceLoggingBoolean(settings.use_cursorpos_editor_startupscreen, "UseCursorPosOnEditorStartup"),
TraceLoggingBoolean(settings.showZonesOnAllMonitors, "ShowZonesOnAllMonitors"),
TraceLoggingBoolean(settings.makeDraggedWindowTransparent, "MakeDraggedWindowTransparent"),
TraceLoggingWideString(settings.zoneColor.c_str(), "ZoneColor"),
TraceLoggingWideString(settings.zoneBorderColor.c_str(), "ZoneBorderColor"),
TraceLoggingWideString(settings.zoneHightlightColor.c_str(), "ZoneHighlightColor"),
TraceLoggingInt32(settings.zoneHighlightOpacity, "ZoneHighlightOpacity"),
TraceLoggingWideString(hotkeyStr.c_str(), "Hotkey"),

View File

@ -15,6 +15,7 @@ public:
static void EnableFancyZones(bool enabled) noexcept;
static void OnKeyDown(DWORD vkCode, bool win, bool control, bool inMoveSize) noexcept;
static void DataChanged() noexcept;
static void EditorLaunched(int value) noexcept;
};
static void SettingsChanged(const Settings& settings) noexcept;

View File

@ -25,3 +25,86 @@ UINT GetDpiForMonitor(HMONITOR monitor) noexcept
return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
}
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
const size_t nMonitors = monitorInfo.size();
// blocking[i][j] - whether monitor i blocks monitor j in the ordering, i.e. monitor i should go before monitor j
std::vector<std::vector<bool>> blocking(nMonitors, std::vector<bool>(nMonitors, false));
// blockingCount[j] - the number of monitors which block monitor j
std::vector<size_t> blockingCount(nMonitors, 0);
for (size_t i = 0; i < nMonitors; i++)
{
RECT rectI = monitorInfo[i].second;
for (size_t j = 0; j < nMonitors; j++)
{
RECT rectJ = monitorInfo[j].second;
blocking[i][j] = rectI.top < rectJ.bottom && rectI.left < rectJ.right && i != j;
if (blocking[i][j])
{
blockingCount[j]++;
}
}
}
// used[i] - whether the sorting algorithm has used monitor i so far
std::vector<bool> used(nMonitors, false);
// the sorted sequence of monitors
std::vector<std::pair<HMONITOR, RECT>> sortedMonitorInfo;
for (size_t iteration = 0; iteration < nMonitors; iteration++)
{
// Indices of candidates to become the next monitor in the sequence
std::vector<size_t> candidates;
// First, find indices of all unblocked monitors
for (size_t i = 0; i < nMonitors; i++)
{
if (blockingCount[i] == 0 && !used[i])
{
candidates.push_back(i);
}
}
// In the unlikely event that there are no unblocked monitors, declare all unused monitors as candidates.
if (candidates.empty())
{
for (size_t i = 0; i < nMonitors; i++)
{
if (!used[i])
{
candidates.push_back(i);
}
}
}
// Pick the lexicographically smallest monitor as the next one
size_t smallest = candidates[0];
for (size_t j = 1; j < candidates.size(); j++)
{
size_t current = candidates[j];
// Compare (top, left) lexicographically
if (std::tie(monitorInfo[current].second.top, monitorInfo[current].second.left)
< std::tie(monitorInfo[smallest].second.top, monitorInfo[smallest].second.left))
{
smallest = current;
}
}
used[smallest] = true;
sortedMonitorInfo.push_back(monitorInfo[smallest]);
for (size_t i = 0; i < nMonitors; i++)
{
if (blocking[smallest][i])
{
blockingCount[i]--;
}
}
}
monitorInfo = std::move(sortedMonitorInfo);
}

View File

@ -1,14 +1,18 @@
#pragma once
#include "gdiplus.h"
struct Rect
{
Rect() {}
Rect(RECT rect) : m_rect(rect)
Rect(RECT rect) :
m_rect(rect)
{
}
Rect(RECT rect, UINT dpi) : m_rect(rect)
Rect(RECT rect, UINT dpi) :
m_rect(rect)
{
m_rect.right = m_rect.left + MulDiv(m_rect.right - m_rect.left, dpi, 96);
m_rect.bottom = m_rect.top + MulDiv(m_rect.bottom - m_rect.top, dpi, 96);
@ -38,7 +42,7 @@ inline void MakeWindowTransparent(HWND window)
}
}
inline void InitRGB(_Out_ RGBQUAD *quad, BYTE alpha, COLORREF color)
inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color)
{
ZeroMemory(quad, sizeof(*quad));
quad->rgbReserved = alpha;
@ -47,7 +51,7 @@ inline void InitRGB(_Out_ RGBQUAD *quad, BYTE alpha, COLORREF color)
quad->rgbBlue = GetBValue(color) * alpha / 255;
}
inline void FillRectARGB(wil::unique_hdc& hdc, RECT const *prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
inline void FillRectARGB(wil::unique_hdc& hdc, RECT const* prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
{
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
@ -60,63 +64,30 @@ inline void FillRectARGB(wil::unique_hdc& hdc, RECT const *prcFill, BYTE alpha,
RECT fillRect;
CopyRect(&fillRect, prcFill);
if ((alpha == 255) || !blendAlpha)
{
// Opaque or the caller does not want to blend the alpha
RGBQUAD bitmapBits;
InitRGB(&bitmapBits, alpha, color);
StretchDIBits(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
0, 0, 1, 1, &bitmapBits, &bi, DIB_RGB_COLORS, SRCCOPY);
}
else
{
if (wil::unique_hdc hdcSrc{ CreateCompatibleDC(hdc.get()) })
{
void* pBitmapBits;
if (wil::unique_hbitmap bitmapSource{ CreateDIBSection(hdcSrc.get(), &bi, DIB_RGB_COLORS, &pBitmapBits, nullptr, 0) })
{
InitRGB(reinterpret_cast<RGBQUAD *>(pBitmapBits), alpha, color);
wil::unique_select_object bitmapOld{ SelectObject(hdcSrc.get(), bitmapSource.get()) };
BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
GdiAlphaBlend(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
hdcSrc.get(), 0, 0, 1, 1, bf);
}
}
}
}
inline void FrameRectARGB(wil::unique_hdc& hdc, const RECT &rc, BYTE bAlpha, COLORREF clr, int thickness)
{
RECT sides[] = {
{ rc.left, rc.top, (rc.left + thickness), rc.bottom },
{ (rc.right - thickness), rc.top, rc.right, rc.bottom },
{ (rc.left + thickness), rc.top, (rc.right - thickness), (rc.top + thickness) },
{ (rc.left + thickness), (rc.bottom - thickness), (rc.right - thickness), rc.bottom }
};
for (UINT i = 0; i < ARRAYSIZE(sides); i++)
{
FillRectARGB(hdc, &(sides[i]), bAlpha, clr, false);
}
RGBQUAD bitmapBits;
InitRGB(&bitmapBits, alpha, color);
StretchDIBits(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
0,
0,
1,
1,
&bitmapBits,
&bi,
DIB_RGB_COLORS,
SRCCOPY);
}
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
// Example output: DELA026#5&10a58c63&0&UID16777488
const std::wstring defaultDeviceId = L"FallbackDevice";
if (!deviceId)
{
@ -140,10 +111,10 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
}
}
inline unsigned char OpacitySettingToAlpha(int opacity)
inline BYTE OpacitySettingToAlpha(int opacity)
{
// convert percentage to a 0-255 alpha value
return static_cast<unsigned char>(opacity * 2.55);
return static_cast<BYTE>(opacity * 2.55);
}
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);

View File

@ -12,123 +12,43 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests
{
TEST_CLASS(FancyZonesUnitTests)
TEST_CLASS (FancyZonesUnitTests)
{
HINSTANCE m_hInst;
winrt::com_ptr<IFancyZonesSettings> m_settings;
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
Assert::IsTrue(m_settings != nullptr);
}
TEST_METHOD(Create)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD(CreateWithEmptyHinstance)
{
auto actual = MakeFancyZones({}, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD(CreateWithNullHinstance)
{
auto actual = MakeFancyZones(nullptr, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD(CreateWithNullSettings)
{
auto actual = MakeFancyZones(m_hInst, nullptr);
Assert::IsNull(actual.get());
}
TEST_METHOD(Run)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
std::vector<std::thread> threads;
std::atomic<int> counter = 0;
const int expectedCount = 10;
auto runFunc = [&]() {
actual->Run();
counter++;
};
for (int i = 0; i < expectedCount; i++)
{
threads.push_back(std::thread(runFunc));
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests");
Assert::IsTrue(m_settings != nullptr);
}
for (auto& thread : threads)
TEST_METHOD (Create)
{
thread.join();
auto actual = MakeFancyZones(m_hInst, m_settings);
Assert::IsNotNull(actual.get());
}
TEST_METHOD (CreateWithEmptyHinstance)
{
auto actual = MakeFancyZones({}, m_settings);
Assert::IsNotNull(actual.get());
}
Assert::AreEqual(expectedCount, counter.load());
}
TEST_METHOD(Destroy)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
std::vector<std::thread> threads;
std::atomic<int> counter = 0;
const int expectedCount = 10;
auto destroyFunc = [&]() {
actual->Destroy();
counter++;
};
for (int i = 0; i < expectedCount; i++)
TEST_METHOD (CreateWithNullHinstance)
{
threads.push_back(std::thread(destroyFunc));
auto actual = MakeFancyZones(nullptr, m_settings);
Assert::IsNotNull(actual.get());
}
for (auto& thread : threads)
TEST_METHOD (CreateWithNullSettings)
{
thread.join();
auto actual = MakeFancyZones(m_hInst, nullptr);
Assert::IsNull(actual.get());
}
Assert::AreEqual(expectedCount, counter.load());
}
TEST_METHOD(RunDestroy)
{
auto actual = MakeFancyZones(m_hInst, m_settings);
std::vector<std::thread> threads;
std::atomic<int> counter = 0;
const int expectedCount = 20;
auto func = [&]() {
auto idHash = std::hash<std::thread::id>()(std::this_thread::get_id());
bool run = (idHash % 2 == 0);
run ? actual->Run() : actual->Destroy();
counter++;
};
for (int i = 0; i < expectedCount; i++)
{
threads.push_back(std::thread(func));
}
for (auto& thread : threads)
{
thread.join();
}
Assert::AreEqual(expectedCount, counter.load());
}
};
TEST_CLASS(FancyZonesIZoneWindowHostUnitTests)
TEST_CLASS (FancyZonesIZoneWindowHostUnitTests)
{
HINSTANCE m_hInst{};
std::wstring m_settingsLocation = L"FancyZonesUnitTests";
@ -148,7 +68,11 @@ namespace FancyZonesUnitTests
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
ptSettings.add_bool_toogle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors);
ptSettings.add_bool_toogle(L"fancyzones_makeDraggedWindowTransparent", IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT, settings.makeDraggedWindowTransparent);
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
ptSettings.add_color_picker(L"fancyzones_zoneColor", IDS_SETTING_DESCRIPTION_ZONECOLOR, settings.zoneColor);
ptSettings.add_color_picker(L"fancyzones_zoneBorderColor", IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, settings.zoneBorderColor);
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
@ -156,92 +80,185 @@ namespace FancyZonesUnitTests
}
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
m_zoneWindowHost = fancyZones.as<IZoneWindowHost>();
Assert::IsTrue(m_zoneWindowHost != nullptr);
}
m_zoneWindowHost = fancyZones.as<IZoneWindowHost>();
Assert::IsTrue(m_zoneWindowHost != nullptr);
}
TEST_METHOD_CLEANUP(Cleanup)
{
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD_CLEANUP(Cleanup)
{
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD(GetZoneHighlightColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
TEST_METHOD (GetZoneColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneColor = L"#abafee",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#FAFAFA",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightColor();
Assert::AreEqual(expected, actual);
}
const auto actual = m_zoneWindowHost->GetZoneColor();
Assert::AreEqual(expected, actual);
}
TEST_METHOD(GetZoneHighlightOpacity)
{
const auto expected = 88;
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = expected,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
TEST_METHOD (GetZoneBorderColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"#abafee",
.zoneHightlightColor = L"#FAFAFA",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity();
Assert::AreEqual(expected, actual);
}
const auto actual = m_zoneWindowHost->GetZoneBorderColor();
Assert::AreEqual(expected, actual);
}
TEST_METHOD(GetCurrentMonitorZoneSetEmpty)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor());
Assert::IsNull(actual);
}
TEST_METHOD (GetZoneHighlightColor)
{
const auto expected = RGB(171, 175, 238);
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
TEST_METHOD(GetCurrentMonitorZoneSetNullMonitor)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr);
Assert::IsNull(actual);
}
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightColor();
Assert::AreEqual(expected, actual);
}
TEST_METHOD (GetZoneHighlightOpacity)
{
const auto expected = 88;
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = expected,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity();
Assert::AreEqual(expected, actual);
}
TEST_METHOD (IsMakeDraggenWindowTransparentActive)
{
const auto expected = true;
const Settings settings{
.shiftDrag = true,
.displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"FAFAFA",
.zoneHightlightColor = L"#abafee",
.zoneHighlightOpacity = expected,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
Assert::AreEqual(expected, m_zoneWindowHost->isMakeDraggedWindowTransparentActive());
}
TEST_METHOD (GetCurrentMonitorZoneSetEmpty)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor());
Assert::IsNull(actual);
}
TEST_METHOD (GetCurrentMonitorZoneSetNullMonitor)
{
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr);
Assert::IsNull(actual);
}
};
TEST_CLASS(FancyZonesIFancyZonesCallbackUnitTests)
TEST_CLASS (FancyZonesIFancyZonesCallbackUnitTests)
{
HINSTANCE m_hInst{};
std::wstring m_settingsLocation = L"FancyZonesUnitTests";
@ -263,7 +280,11 @@ namespace FancyZonesUnitTests
ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows);
ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
ptSettings.add_bool_toogle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors);
ptSettings.add_bool_toogle(L"fancyzones_makeDraggedWindowTransparent", IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT, settings.makeDraggedWindowTransparent);
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
ptSettings.add_color_picker(L"fancyzones_zoneColor", IDS_SETTING_DESCRIPTION_ZONECOLOR, settings.zoneColor);
ptSettings.add_color_picker(L"fancyzones_zoneBorderColor", IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, settings.zoneBorderColor);
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor);
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps);
@ -283,157 +304,158 @@ namespace FancyZonesUnitTests
}
TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
m_fzCallback = fancyZones.as<IFancyZonesCallback>();
Assert::IsTrue(m_fzCallback != nullptr);
m_fancyZonesData.clear_data();
}
TEST_METHOD_CLEANUP(Cleanup)
{
sendKeyboardInput(VK_SHIFT, true);
sendKeyboardInput(VK_LWIN, true);
sendKeyboardInput(VK_CONTROL, true);
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD(OnKeyDownNothingPressed)
{
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr);
m_fzCallback = fancyZones.as<IFancyZonesCallback>();
Assert::IsTrue(m_fzCallback != nullptr);
m_fancyZonesData.clear_data();
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
TEST_METHOD_CLEANUP(Cleanup)
{
sendKeyboardInput(VK_SHIFT, true);
sendKeyboardInput(VK_LWIN, true);
sendKeyboardInput(VK_CONTROL, true);
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder);
}
TEST_METHOD(OnKeyDownShiftPressed)
{
sendKeyboardInput(VK_SHIFT);
TEST_METHOD (OnKeyDownNothingPressed)
{
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD (OnKeyDownShiftPressed)
{
sendKeyboardInput(VK_SHIFT);
TEST_METHOD(OnKeyDownWinPressed)
{
sendKeyboardInput(VK_LWIN);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD (OnKeyDownWinPressed)
{
sendKeyboardInput(VK_LWIN);
TEST_METHOD(OnKeyDownWinShiftPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_SHIFT);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD (OnKeyDownWinShiftPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_SHIFT);
TEST_METHOD(OnKeyDownWinCtrlPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_CONTROL);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
const Settings settings{
.overrideSnapHotkeys = false,
};
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
/*
TEST_METHOD (OnKeyDownWinCtrlPressed)
{
sendKeyboardInput(VK_LWIN);
sendKeyboardInput(VK_CONTROL);
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
}
const Settings settings{
.overrideSnapHotkeys = false,
};
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str());
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
for (DWORD code = '0'; code <= '9'; code++)
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{
tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
*/
};
}

File diff suppressed because it is too large Load Diff

View File

@ -49,9 +49,13 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>

View File

@ -1,32 +1,235 @@
#include "pch.h"
#include "Util.h"
#include "lib\util.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests
{
TEST_CLASS(UtilUnitTests){
public:
TEST_METHOD(TestParseDeviceId){
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488"));
void TestMonitorSetPermutations(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
auto monitorInfoPermutation = monitorInfo;
do {
auto monitorInfoCopy = monitorInfoPermutation;
OrderMonitors(monitorInfoCopy);
CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
} while (std::next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
}
void TestMonitorSetPermutationsOffsets(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
for (int offsetX = -3000; offsetX <= 3000; offsetX += 1000)
{
for (int offsetY = -3000; offsetY <= 3000; offsetY += 1000)
{
auto monitorInfoCopy = monitorInfo;
for (auto& [monitor, rect] : monitorInfoCopy)
{
rect.left += offsetX;
rect.right += offsetX;
rect.top += offsetY;
rect.bottom += offsetY;
}
TestMonitorSetPermutations(monitorInfoCopy);
}
}
}
TEST_CLASS(UtilUnitTests)
{
TEST_METHOD(TestParseDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488"));
}
TEST_METHOD(TestParseInvalidDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"AnInvalidDeviceId";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"FallbackDevice"));
}
TEST_METHOD(TestMonitorOrdering01)
{
// Three horizontally arranged monitors, bottom aligned, with increasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 200, .right = 1600, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 100, .right = 3300, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 3300, .top = 0, .right = 5100, .bottom = 1100} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering02)
{
// Three horizontally arranged monitors, bottom aligned, with equal sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3200, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 3200, .top = 0, .right = 4800, .bottom = 900} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering03)
{
// Three horizontally arranged monitors, bottom aligned, with decreasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1800, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 1800, .top = 100, .right = 3500, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 3500, .top = 200, .right = 5100, .bottom = 1100} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering04)
{
// Three horizontally arranged monitors, top aligned, with increasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3300, .bottom = 1000} },
{Mocks::Monitor(), RECT{.left = 3300, .top = 0, .right = 5100, .bottom = 1100} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering05)
{
// Three horizontally arranged monitors, top aligned, with equal sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1600, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 1600, .top = 0, .right = 3200, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 3200, .top = 0, .right = 4800, .bottom = 900} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering06)
{
// Three horizontally arranged monitors, top aligned, with decreasing sizes
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 1800, .bottom = 1100} },
{Mocks::Monitor(), RECT{.left = 1800, .top = 0, .right = 3500, .bottom = 1000} },
{Mocks::Monitor(), RECT{.left = 3500, .top = 0, .right = 5100, .bottom = 900} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering07)
{
// Three vertically arranged monitors, center aligned, with equal sizes, except the middle monitor is a bit wider
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 100, .top = 0, .right = 1700, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 0, .top = 900, .right = 1800, .bottom = 1800} },
{Mocks::Monitor(), RECT{.left = 100, .top = 1800, .right = 1700, .bottom = 2700} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering08)
{
// ------------------
// | || || |
// | || || |
// ------------------
// | || |
// | || |
// ------------------
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 600, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 600, .top = 0, .right = 1200, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 1200, .top = 0, .right = 1800, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 0, .top = 400, .right = 900, .bottom = 800} },
{Mocks::Monitor(), RECT{.left = 900, .top = 400, .right = 1800, .bottom = 800} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering09)
{
// Regular 3x3 grid
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 400, .bottom = 300} },
{Mocks::Monitor(), RECT{.left = 400, .top = 0, .right = 800, .bottom = 300} },
{Mocks::Monitor(), RECT{.left = 800, .top = 0, .right = 1200, .bottom = 300} },
{Mocks::Monitor(), RECT{.left = 0, .top = 300, .right = 400, .bottom = 600} },
{Mocks::Monitor(), RECT{.left = 400, .top = 300, .right = 800, .bottom = 600} },
{Mocks::Monitor(), RECT{.left = 800, .top = 300, .right = 1200, .bottom = 600} },
{Mocks::Monitor(), RECT{.left = 0, .top = 600, .right = 400, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 400, .top = 600, .right = 800, .bottom = 900} },
{Mocks::Monitor(), RECT{.left = 800, .top = 600, .right = 1200, .bottom = 900} },
};
// Reduce running time by testing only rotations
for (int i = 0; i < 9; i++)
{
auto monitorInfoCopy = monitorInfo;
std::rotate(monitorInfoCopy.begin(), monitorInfoCopy.begin() + i, monitorInfoCopy.end());
OrderMonitors(monitorInfoCopy);
CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
}
}
TEST_METHOD(TestMonitorOrdering10)
{
// ------------------
// | || |
// | || |
// ------------------
// | || || |
// | || || |
// ------------------
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 0, .top = 0, .right = 900, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 900, .top = 0, .right = 1800, .bottom = 400} },
{Mocks::Monitor(), RECT{.left = 0, .top = 400, .right = 600, .bottom = 800} },
{Mocks::Monitor(), RECT{.left = 600, .top = 400, .right = 1200, .bottom = 800} },
{Mocks::Monitor(), RECT{.left = 1200, .top = 400, .right = 1800, .bottom = 800} },
};
TestMonitorSetPermutationsOffsets(monitorInfo);
}
TEST_METHOD(TestMonitorOrdering11)
{
// Random values, some monitors overlap, don't check order, just ensure it doesn't crash and it's the same every time
std::vector<std::pair<HMONITOR, RECT>> monitorInfo = {
{Mocks::Monitor(), RECT{.left = 410, .top = 630, .right = 988, .bottom = 631} },
{Mocks::Monitor(), RECT{.left = 302, .top = 189, .right = 550, .bottom = 714} },
{Mocks::Monitor(), RECT{.left = 158, .top = 115, .right = 657, .bottom = 499} },
{Mocks::Monitor(), RECT{.left = 341, .top = 340, .right = 723, .bottom = 655} },
{Mocks::Monitor(), RECT{.left = 433, .top = 393, .right = 846, .bottom = 544} },
};
auto monitorInfoPermutation = monitorInfo;
auto firstTime = monitorInfo;
OrderMonitors(firstTime);
do {
auto monitorInfoCopy = monitorInfoPermutation;
OrderMonitors(monitorInfoCopy);
CustomAssert::AreEqual(firstTime, monitorInfoCopy);
} while (next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
}
};
}
TEST_METHOD(TestParseInvalidDeviceId)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
PCWSTR input = L"AnInvalidDeviceId";
wchar_t output[256]{};
ParseDeviceId(input, output, ARRAYSIZE(output));
Assert::AreEqual(0, wcscmp(output, L"FallbackDevice"));
}
}
;
}

View File

@ -146,4 +146,28 @@ namespace Mocks
m_conditionVar.notify_one();
}
}
}
std::wstring Helpers::GuidToString(const GUID& guid)
{
OLECHAR* guidString;
if (StringFromCLSID(guid, &guidString) == S_OK)
{
std::wstring guidStr{ guidString };
CoTaskMemFree(guidString);
return guidStr;
}
return L"";
}
std::wstring Helpers::CreateGuidString()
{
GUID guid;
if (CoCreateGuid(&guid) == S_OK)
{
return GuidToString(guid);
}
return L"";
}

View File

@ -19,6 +19,15 @@ namespace CustomAssert
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2);
}
static void AreEqual(const std::vector<std::pair<HMONITOR, RECT>>& a1, const std::vector<std::pair<HMONITOR, RECT>>& a2)
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1.size() == a2.size());
for (size_t i = 0; i < a1.size(); i++)
{
Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1[i].first == a2[i].first);
}
}
}
namespace Mocks
@ -43,3 +52,9 @@ namespace Mocks
HWND WindowCreate(HINSTANCE hInst);
}
namespace Helpers
{
std::wstring GuidToString(const GUID& guid);
std::wstring CreateGuidString();
}

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