Merged powerToys master into Launcher master

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

5
.gitignore vendored
View File

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

View File

@@ -33,3 +33,16 @@ steps:
msbuildArgs: ${{ parameters.additionalBuildArguments }} msbuildArgs: ${{ parameters.additionalBuildArguments }}
clean: true clean: true
maximumCpuCount: 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\fancyzones.dll'
- 'modules\shortcut_guide.dll' - 'modules\shortcut_guide.dll'
- 'modules\PowerRenameExt.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: signing_options:
sign_inline: true # This does signing a soon as this command completes sign_inline: true # This does signing a soon as this command completes
- !!buildcommand - !!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 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}"
ProjectSection(ProjectDependencies) = postProject 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} {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} {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} {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} {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} {B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB} = {B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB}
{A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E} {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} {07C389E3-6BC8-41CF-923E-307B1265FA2D} = {07C389E3-6BC8-41CF-923E-307B1265FA2D}
EndProjectSection EndProjectSection
EndProject EndProject
@@ -124,6 +139,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowWalker", "src\modules
{74485049-C722-400F-ABE5-86AC52D929B3} = {74485049-C722-400F-ABE5-86AC52D929B3} {74485049-C722-400F-ABE5-86AC52D929B3} = {74485049-C722-400F-ABE5-86AC52D929B3}
EndProjectSection EndProjectSection
EndProject 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}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "action_runner", "src\action_runner\action_runner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E} {17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
@@ -171,6 +194,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerLauncher.UI", "src\modules\launcher\PowerLauncher.UI\PowerLauncher.UI.csproj", "{4A3DE70C-684C-410D-B851-C23B6DAEDF16}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerLauncher.UI", "src\modules\launcher\PowerLauncher.UI\PowerLauncher.UI.csproj", "{4A3DE70C-684C-410D-B851-C23B6DAEDF16}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 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}.Debug|x64.Build.0 = Debug|x64
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}.Release|x64.ActiveCfg = Release|x64 {51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}.Release|x64.ActiveCfg = Release|x64
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6}.Release|x64.Build.0 = 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.ActiveCfg = Debug|x64
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.Build.0 = Debug|x64 {D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Debug|x64.Build.0 = Debug|x64
{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}.Release|x64.ActiveCfg = Release|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.ActiveCfg = Release|x64
{4A3DE70C-684C-410D-B851-C23B6DAEDF16}.Release|x64.Build.0 = Release|x64 {4A3DE70C-684C-410D-B851-C23B6DAEDF16}.Release|x64.Build.0 = Release|x64
{4A3DE70C-684C-410D-B851-C23B6DAEDF16}.Release|x64.Deploy.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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -351,6 +444,10 @@ Global
{8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB} = {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03} {B9BDF8BE-FED7-49B5-A7AE-DD4D1CA2D9EB} = {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03}
{51D3BD1F-07A8-48EB-B2A0-0A249CD4E1A6} = {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} {17DA04DF-E393-4397-9CF0-84DABE11032E} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{DB90F671-D861-46BB-93A3-F1304F5BA1C5} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68} {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} {F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{F97E5003-F263-4D4A-A964-0F1F3C82DEF2} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68} {F97E5003-F263-4D4A-A964-0F1F3C82DEF2} = {C140A3EF-6DBF-4084-9D4C-4EB5A99FEE68}
{4A3DE70C-684C-410D-B851-C23B6DAEDF16} = {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} 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
[![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 ## Installing and running Microsoft PowerToys 0.16
👉 **Note:** Microsoft PowerToys requires Windows 10 1803 (build 17134) or later.
> 👉 **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.
### Via Github with MSI [Recommended] ### 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. This is our preferred method.
### Other install methods ### 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]. - 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.
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.
#### Via Chocolatey - ⚠ Unofficial ⚠ #### Via Chocolatey - ⚠ Unofficial ⚠
@@ -74,17 +65,13 @@ To upgrade PowerToys, run the following command from the command line / PowerShe
choco upgrade powertoys choco upgrade powertoys
``` ```
### Microsoft Store
On backlog, [Issue #413](https://github.com/microsoft/PowerToys/issues/413)
### Processor support ### 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 | | 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 ## 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. [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. [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. [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 ### Version 1.0 plan
Our plan for all the [goals and utilities for v1.0 detailed over here in the wiki][v1]. Our plan for all the [goals and utilities for v1.0 detailed over here in the wiki][v1].
## What's Happening ## 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. Below are just a few of the bullet items from this release.
- We shipped [v0.15][github-release-link]! - We shipped [v0.16][github-release-link]!
- Make you aware there is a new version from within PowerToys - FancyZone improvement:
- Removed requirement to always 'run as admin' - Multi-Monitor improvement: Zone flipping switching now works between monitors!
- Added almost 300 unit tests to increase stability and prevent regressions. - Simplified UX: Removed layout hot-swap and flashing feature due to need to improve multi-monitor support
- Resolved almost 100 issues - New Utilities!
- Made .NET Framework parts of the source run faster with NGEN - Markdown Preview pane extension
- Improved for how we store data locally - SVG Preview pane extension
- Increased FancyZones compatibility with applications - Image Resizer Window Shell extension
- Initial work for 4 new PowerToys added for 0.16! - Window Walker, an alt-tab alternative
- 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 - Fixed over 100 issues!
- Work on cleaning up our issue backlog and labels - 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 - Auto-updating
- SVG preview pane for support Explorer - Win+R replacement (Launcher)
- Markdown preview pane support for Explorer - Keyboard remapping
- Image Resizer PowerToy - 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 ## Developer Guidance
@@ -151,16 +168,14 @@ PowerToys is still a very fluidic project and the team is actively working out o
## Code of Conduct ## 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 ## 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. 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-CLA]: https://cla.opensource.microsoft.com
[oss-conduct-code]: https://opensource.microsoft.com/codeofconduct/ [oss-conduct-code]: CODE_OF_CONDUCT.md
[oss-conduct-FAQ]: https://opensource.microsoft.com/codeofconduct/faq/
[oss-conduct-email]: mailto:opencode@microsoft.com
[github-release-link]: https://github.com/microsoft/PowerToys/releases/ [github-release-link]: https://github.com/microsoft/PowerToys/releases/
[v1]: https://github.com/microsoft/PowerToys/wiki/Version-1.0-Strategy [v1]: https://github.com/microsoft/PowerToys/wiki/Version-1.0-Strategy
[privacyLink]: http://go.microsoft.com/fwlink/?LinkId=521839 [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 ## Github Workflow
- Follow the PR template, in particular make sure there is open issue for the new PR. - Before starting to work on a fix/feature, make sure there is an open issue to track the work.
- When the PR is approved, let the owner of the PR merge it. - 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`. - 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: - We don't close issues automatically when referenced in a PR, so after the PR is merged:
- mark the issue(s) fixed by the PR with the `resolved` label. - 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 release since users tend to not search for closed issues, we will close the resolved issues when a new released is published. - 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 ## 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"> <Package ID="PowerToys-x64" ProcessorArchitecture="x64">
<Files> <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="action_runner.exe" SourcePath="..\..\x64\Release\action_runner.exe"/>
<File DestinationPath="PowerToys.exe" SourcePath="..\..\x64\Release\PowerToys.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\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\PowerRenameExt.dll" SourcePath="..\..\x64\Release\modules\PowerRenameExt.dll"/>
<File DestinationPath="modules\shortcut_guide.dll" SourcePath="..\..\x64\Release\modules\shortcut_guide.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.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.Memory.dll" SourcePath="..\..\x64\Release\modules\System.Memory.dll"/>
<File DestinationPath="modules\System.Buffers.dll" SourcePath="..\..\x64\Release\modules\System.Buffers.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.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\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\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"/> <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="svgs\*" SourcePath="..\..\x64\Release\svgs\*"/>
<File DestinationPath="settings-html\**" SourcePath="..\..\x64\Release\settings-html\**"/> <File DestinationPath="settings-html\**" SourcePath="..\..\x64\Release\settings-html\**"/>
<File DestinationPath="Images\*.png" SourcePath="Images\*.png"/> <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> </Files>
</Package> </Package>
</PackageFamily> </PackageFamily>

View File

@@ -2,9 +2,12 @@
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/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:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" 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:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5" IgnorableNamespaces="desktop4"> 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" /> <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"> <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" /> <uap:VisualElements DisplayName="PowerToys (Experimental)" Description="Windows system utilities to maximize productivity" Square150x150Logo="Images\logo150.png" Square44x44Logo="Images\logo44.png" BackgroundColor="transparent" />
<Extensions> <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:Extension Category="windows.startupTask" Executable="PowerToys.exe" EntryPoint="Windows.FullTrustApplication">
<uap5:StartupTask TaskId="PowerToysStartupTaskID" Enabled="true" DisplayName="PowerToys" /> <uap5:StartupTask TaskId="PowerToysStartupTaskID" Enabled="true" DisplayName="PowerToys" />
</uap5:Extension> </uap5:Extension>
@@ -36,6 +45,9 @@
<com:ExeServer Executable="modules\PowerRenameUWPUI.exe" DisplayName="PowerRenameUWPUI"> <com:ExeServer Executable="modules\PowerRenameUWPUI.exe" DisplayName="PowerRenameUWPUI">
<com:Class Id="0440049F-D1DC-4E46-B27B-98393D79486B"/> <com:Class Id="0440049F-D1DC-4E46-B27B-98393D79486B"/>
</com:ExeServer> </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:ComServer>
</com:Extension> </com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus"> <desktop4:Extension Category="windows.fileExplorerContextMenus">
@@ -46,8 +58,35 @@
<desktop5:ItemType Type="Directory"> <desktop5:ItemType Type="Directory">
<desktop5:Verb Id="DirectoryPowerRename" Clsid="0440049F-D1DC-4E46-B27B-98393D79486B" /> <desktop5:Verb Id="DirectoryPowerRename" Clsid="0440049F-D1DC-4E46-B27B-98393D79486B" />
</desktop5:ItemType> </desktop5:ItemType>
<desktop4:ItemType Type="*">
<desktop4:Verb Id="ImageResizer" Clsid="51B4D7E5-7568-4234-B4BB-47FB3C016A69" />
</desktop4:ItemType>
</desktop4:FileExplorerContextMenus> </desktop4:FileExplorerContextMenus>
</desktop4:Extension> </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"> <Extension Category="windows.backgroundTasks" EntryPoint="PowerToysNotifications.BackgroundHandler">
<BackgroundTasks> <BackgroundTasks>
<Task Type="general" /> <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> </NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DefineConstants>Debug</DefineConstants>
<OutputPath>$(Platform)\$(Configuration)\</OutputPath> <OutputPath>$(Platform)\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Platform)\$(Configuration)\</IntermediateOutputPath> <IntermediateOutputPath>obj\$(Platform)\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup> </PropertyGroup>

View File

@@ -22,7 +22,7 @@
Property="PREVIOUSVERSIONSINSTALLED" Property="PREVIOUSVERSIONSINSTALLED"
IncludeMinimum="yes" IncludeMaximum="no" /> IncludeMinimum="yes" IncludeMaximum="no" />
</Upgrade> </Upgrade>
<MediaTemplate EmbedCab="yes" /> <MediaTemplate EmbedCab="yes" />
<Property Id="WINDOWSBUILDNUMBER" Secure="yes"> <Property Id="WINDOWSBUILDNUMBER" Secure="yes">
@@ -32,11 +32,12 @@
<![CDATA[(WINDOWSBUILDNUMBER >= 17134)]]> <![CDATA[(WINDOWSBUILDNUMBER >= 17134)]]>
</Condition> </Condition>
<Icon Id="powertoys.ico" SourceFile="$(var.BinX64Dir)\svgs\icon.ico"/> <Icon Id="powertoys.exe" SourceFile="$(var.BinX64Dir)\svgs\icon.ico"/>
<Property Id="ARPPRODUCTICON" Value="powertoys.ico" /> <Property Id="ARPPRODUCTICON" Value="powertoys.exe" />
<Feature Id="CoreFeature" Title="PowerToys" AllowAdvertise="no" Absent="disallow" TypicalDefault="install" <Feature Id="CoreFeature" Title="PowerToys" AllowAdvertise="no" Absent="disallow" TypicalDefault="install"
Description="Contains the Shortcut Guide and Fancy Zones features."> Description="Contains the Shortcut Guide and Fancy Zones features.">
<ComponentGroupRef Id="CoreComponents" /> <ComponentGroupRef Id="CoreComponents" />
<ComponentGroupRef Id="ResourcesComponents" />
</Feature> </Feature>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" /> <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<UI> <UI>
@@ -55,7 +56,7 @@
</UI> </UI>
<WixVariable Id="WixUIBannerBmp" Value="$(var.ProjectDir)\Bitmaps\banner.bmp" /> <WixVariable Id="WixUIBannerBmp" Value="$(var.ProjectDir)\Bitmaps\banner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="$(var.ProjectDir)\Bitmaps\dialog.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="INSTALLSTARTMENUSHORTCUT" Value="1"/>
<Property Id="CREATESCHEDULEDTASK" Value="1"/> <Property Id="CREATESCHEDULEDTASK" Value="1"/>
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/> <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
@@ -66,6 +67,9 @@
<Property Id ="EXISTINGPOWERRENAMEEXTPATH"> <Property Id ="EXISTINGPOWERRENAMEEXTPATH">
<RegistrySearch Id="ExistingExtPath" Root="HKCR" Key="CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}\InprocServer32" Type="raw"/> <RegistrySearch Id="ExistingExtPath" Root="HKCR" Key="CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}\InprocServer32" Type="raw"/>
</Property> </Property>
<Property Id ="EXISTINGIMAGERESIZERPATH">
<RegistrySearch Id="ExistingImageResizerPath" Root="HKCU" Key="Software\Classes\CLSID\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\InprocServer32" Type="raw"/>
</Property>
<InstallExecuteSequence> <InstallExecuteSequence>
<Custom Action="SetRegisterPowerToysSchTaskParam" Before="RegisterPowerToysSchTask" /> <Custom Action="SetRegisterPowerToysSchTaskParam" Before="RegisterPowerToysSchTask" />
@@ -144,7 +148,7 @@
BinaryKey="PTCustomActions" BinaryKey="PTCustomActions"
DllEntry="TelemetryLogUninstallFailCA" DllEntry="TelemetryLogUninstallFailCA"
/> />
<CustomAction Id="TelemetryLogRepairCancel" <CustomAction Id="TelemetryLogRepairCancel"
Return="ignore" Return="ignore"
Impersonate="yes" Impersonate="yes"
@@ -161,9 +165,9 @@
<!-- Close 'PowerToys.exe' before uninstall--> <!-- Close 'PowerToys.exe' before uninstall-->
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" /> <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"> <util:CloseApplication Target="explorer.exe" RebootPrompt="no" TerminateProcess="0">
EXISTINGPOWERRENAMEEXTPATH EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH
</util:CloseApplication> </util:CloseApplication>
<util:CloseApplication CloseMessage="yes" Target="PowerToys.exe" ElevatedCloseMessage="yes" RebootPrompt="no" TerminateProcess="0" /> <util:CloseApplication CloseMessage="yes" Target="PowerToys.exe" ElevatedCloseMessage="yes" RebootPrompt="no" TerminateProcess="0" />
</Product> </Product>
@@ -177,7 +181,25 @@
<Directory Id="ProgramFiles64Folder"> <Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="PowerToys"> <Directory Id="INSTALLFOLDER" Name="PowerToys">
<Directory Id="SvgsInstallFolder" Name="svgs"/> <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="SettingsHtmlInstallFolder" Name="settings-html">
<Directory Id="SettingsHtmlDistInstallFolder" Name="dist"/> <Directory Id="SettingsHtmlDistInstallFolder" Name="dist"/>
</Directory> </Directory>
@@ -206,7 +228,7 @@
Description="PowerToys - Windows system utilities to maximize productivity" Description="PowerToys - Windows system utilities to maximize productivity"
Directory="ApplicationProgramsFolder" Directory="ApplicationProgramsFolder"
WorkingDirectory="INSTALLFOLDER" WorkingDirectory="INSTALLFOLDER"
Icon="powertoys.ico" Icon="powertoys.exe"
IconIndex="0" IconIndex="0"
Advertise="yes"> Advertise="yes">
<ShortcutProperty Key="System.AppUserModel.ID" Value="Microsoft.PowerToysWin32"/> <ShortcutProperty Key="System.AppUserModel.ID" Value="Microsoft.PowerToysWin32"/>
@@ -214,8 +236,20 @@
</Shortcut> </Shortcut>
</File> </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" /> <RemoveFolder Id="DeleteShortcutFolder" Directory="ApplicationProgramsFolder" On="uninstall" />
</Component> </Component>
<Component Id="settings_exe" Guid="A5A461A9-7097-4CBA-9D39-3DBBB6B7B80C" Win64="yes"> <Component Id="settings_exe" Guid="A5A461A9-7097-4CBA-9D39-3DBBB6B7B80C" Win64="yes">
<File Id="PowerToysSettings.exe" KeyPath="yes" Checksum="yes" /> <File Id="PowerToysSettings.exe" KeyPath="yes" Checksum="yes" />
</Component> </Component>
@@ -223,7 +257,7 @@
<File Id="Notifications.dll" KeyPath="yes" Checksum="yes" /> <File Id="Notifications.dll" KeyPath="yes" Checksum="yes" />
</Component> </Component>
<Component Id="License_rtf" Guid="3E5AE43B-CFB4-449B-A346-94CAAFF3312E" Win64="yes"> <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> </Component>
</DirectoryRef> </DirectoryRef>
<DirectoryRef Id="SvgsInstallFolder" FileSource="$(var.BinX64Dir)\svgs\"> <DirectoryRef Id="SvgsInstallFolder" FileSource="$(var.BinX64Dir)\svgs\">
@@ -274,7 +308,167 @@
<RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" /> <RegistryValue Type="string" Key="InprocServer32" Name="ThreadingModel" Value="Apartment" />
</RegistryKey> </RegistryKey>
<RegistryKey Root="HKCR" Key="AllFileSystemObjects\ShellEx\ContextMenuHandlers\PowerRenameExt"> <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> </RegistryKey>
</Component> </Component>
</DirectoryRef> </DirectoryRef>
@@ -306,7 +500,7 @@
Description="PowerToys - Windows system utilities to maximize productivity" Description="PowerToys - Windows system utilities to maximize productivity"
Target="[!PowerToys.exe]" Target="[!PowerToys.exe]"
WorkingDirectory="INSTALLFOLDER" WorkingDirectory="INSTALLFOLDER"
Icon="powertoys.ico" Icon="powertoys.exe"
Directory="DesktopFolder"/> Directory="DesktopFolder"/>
</Component> </Component>
</DirectoryRef> </DirectoryRef>
@@ -323,10 +517,38 @@
<ComponentRef Id="Module_FancyZones" /> <ComponentRef Id="Module_FancyZones" />
<ComponentRef Id="DesktopShortcut" /> <ComponentRef Id="DesktopShortcut" />
<ComponentRef Id="Module_PowerRename" /> <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_exe" />
<ComponentRef Id="settings_html" /> <ComponentRef Id="settings_html" />
<ComponentRef Id="settings_dark_html" /> <ComponentRef Id="settings_dark_html" />
<ComponentRef Id="settings_js_bundle" /> <ComponentRef Id="settings_js_bundle" />
</ComponentGroup> </ComponentGroup>
</Fragment> </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> </Wix>

View File

@@ -221,22 +221,25 @@ UINT __stdcall CreateScheduledTaskCA(MSIHANDLE hInstall) {
} }
// Run the task with the highest available privileges. // Run the task with the highest available privileges.
hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_LUA);
pPrincipal->Release(); pPrincipal->Release();
ExitOnFailure(hr, "Cannot put principal run level: %x", hr); ExitOnFailure(hr, "Cannot put principal run level: %x", hr);
// ------------------------------------------------------ // ------------------------------------------------------
// Save the task in the PowerToys folder. // Save the task in the PowerToys folder.
hr = pTaskFolder->RegisterTaskDefinition( {
_bstr_t(wstrTaskName.c_str()), _variant_t SDDL_FULL_ACCESS_FOR_EVERYONE = L"D:(A;;FA;;;WD)";
pTask, hr = pTaskFolder->RegisterTaskDefinition(
TASK_CREATE_OR_UPDATE, _bstr_t(wstrTaskName.c_str()),
_variant_t(username_domain), pTask,
_variant_t(), TASK_CREATE_OR_UPDATE,
TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(username_domain),
_variant_t(L""), _variant_t(),
&pRegisteredTask); TASK_LOGON_INTERACTIVE_TOKEN,
ExitOnFailure(hr, "Error saving the Task : %x", hr); SDDL_FULL_ACCESS_FOR_EVERYONE,
&pRegisteredTask);
ExitOnFailure(hr, "Error saving the Task : %x", hr);
}
WcaLog(LOGMSG_STANDARD, "Scheduled task created for the current user."); 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 **Note:** if you delete the folder, you will have to regenerate the key
#### Elevate `Developer PowerShell for VS` permissions due to unsigned file #### 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 1. Open `Developer PowerShell for VS` as admin
2. Run `Set-ExecutionPolicy -executionPolicy Unrestricted` 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` 1. Make sure you've built the `Release` configuration of `powertoys.sln`
2. Open `Developer PowerShell for VS` 2. Open `Developer PowerShell for VS`
3. Navigate to your repo's `installer\MSIX` 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 ### What reinstall_msix.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. `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 ## Cleanup - Removing all .msi/.msix PowerToys installations
```ps ```ps

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,9 @@
<ClCompile Include="UnitTestsVersionHelper.cpp"> <ClCompile Include="UnitTestsVersionHelper.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="UnitTestsCommon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="pch.h"> <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) 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 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); Assert::IsTrue(lhs > rhs);
} }
TEST_METHOD (whenMajorVersionIsLesserComparationOperatorShouldReturnProperValue) 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 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); Assert::IsFalse(lhs > rhs);
} }
TEST_METHOD (whenMajorVersionIsEqualComparationOperatorShouldCompareMinorVersionValue) 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 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); Assert::IsTrue(lhs > rhs);
} }
TEST_METHOD (whenMajorVersionIsEqualComparationOperatorShouldCompareMinorVersionValue2) 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 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); Assert::IsFalse(lhs > rhs);
} }
TEST_METHOD (whenMajorAndMinorVersionIsEqualComparationOperatorShouldCompareRevisionValue) 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 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); Assert::IsTrue(lhs > rhs);
} }
TEST_METHOD (whenMajorAndMinorVersionIsEqualComparationOperatorShouldCompareRevisionValue2) 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 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); Assert::IsFalse(lhs > rhs);
} }

View File

@@ -27,37 +27,3 @@ VersionHelper::VersionHelper(int major, int minor, int revision) :
revision(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 #pragma once
#include <string> #include <string>
#include <compare>
struct VersionHelper struct VersionHelper
{ {
VersionHelper(std::string str); VersionHelper(std::string str);
VersionHelper(int major, int minor, int revision); VersionHelper(int major, int minor, int revision);
bool operator>(const VersionHelper& rhs); auto operator<=>(const VersionHelper&) const = default;
int major; int major;
int minor; int minor;

View File

@@ -7,6 +7,7 @@
#include "version.h" #include "version.h"
#pragma comment(lib, "advapi32.lib") #pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "shlwapi.lib")
namespace localized_strings 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.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = 0; exec_info.lpDirectory = 0;
exec_info.hInstApp = 0; exec_info.hInstApp = 0;
exec_info.nShow = SW_SHOWDEFAULT;
if (ShellExecuteExW(&exec_info)) if (ShellExecuteExW(&exec_info))
{ {
@@ -714,3 +716,18 @@ bool check_user_is_admin()
freeMemory(pSID, pGroupInfo); freeMemory(pSID, pGroupInfo);
return false; 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 <Windows.h>
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector>
// Returns RECT with positions of the minmize/maximize buttons of the given window. // Returns RECT with positions of the minmize/maximize buttons of the given window.
// Does not always work, since some apps draw custom toolbars. // 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 // Returns true if the current process is running from administrator account
bool check_user_is_admin(); 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 // Get the executable path or module name for modern apps
std::wstring get_process_path(DWORD pid) noexcept; std::wstring get_process_path(DWORD pid) noexcept;
// Get the executable path or module name for modern apps // 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_MINOR $(Version.Split('.')[1])" />
<HeaderLines Include="#define VERSION_REVISION $(Version.Split('.')[2])" /> <HeaderLines Include="#define VERSION_REVISION $(Version.Split('.')[2])" />
</ItemGroup> </ItemGroup>
<WriteLinesToFile <WriteLinesToFile File="Generated Files\version_gen.h" Lines="@(HeaderLines)" Overwrite="true" Encoding="Unicode" WriteOnlyWhenDifferent="true" />
File="Generated Files\version_gen.h"
Lines="@(HeaderLines)"
Overwrite="true"
Encoding="Unicode"
WriteOnlyWhenDifferent="true" />
</Target> </Target>
<ItemGroup Label="ProjectConfigurations"> <ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64"> <ProjectConfiguration Include="Debug|x64">
@@ -34,6 +29,9 @@
<ProjectName>common</ProjectName> <ProjectName>common</ProjectName>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <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"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType> <ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
@@ -61,9 +59,11 @@
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental> <LinkIncremental>true</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental> <LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>
@@ -167,7 +167,16 @@
<ClCompile Include="window_helpers.cpp" /> <ClCompile Include="window_helpers.cpp" />
<ClCompile Include="winstore.cpp" /> <ClCompile Include="winstore.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </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> </Project>

View File

@@ -9,9 +9,14 @@ namespace json
{ {
try try
{ {
std::wifstream file(file_name.data(), std::ios::binary); std::ifstream file(file_name.data(), std::ios::binary);
using isbi = std::istreambuf_iterator<wchar_t>; if (file.is_open())
return JsonValue::Parse(std::wstring{ isbi{ file }, isbi{} }).GetObjectW(); {
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 (...) catch (...)
{ {
@@ -21,6 +26,7 @@ namespace json
void to_file(std::wstring_view file_name, const JsonObject& obj) 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.h>
#include <winrt/Windows.Web.Http.Headers.h> #include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Windows.Management.Deployment.h>
#include "VersionHelper.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* 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* 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* 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 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") }; winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") };
VersionHelper github_version(winrt::to_string(new_version)); VersionHelper github_version(winrt::to_string(new_version));
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION); 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 else
{ {
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str() }; co_return std::nullopt;
} }
} }
catch (...) catch (...)
{ {
co_return std::nullopt; 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 uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation(); bool offer_msi_uninstallation();
std::future<bool> uninstall_previous_msix_version_async();
struct new_version_download_info struct new_version_download_info
{ {
winrt::Windows::Foundation::Uri release_page_uri; winrt::Windows::Foundation::Uri release_page_uri;

View File

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

View File

@@ -3,7 +3,9 @@
#ifndef PCH_H #ifndef PCH_H
#define PCH_H #define PCH_H
#pragma warning (disable: 5205)
#include <winrt/base.h> #include <winrt/base.h>
#pragma warning (default: 5205)
#include <Windows.h> #include <Windows.h>
#include <MsiQuery.h> #include <MsiQuery.h>
#include <Shlwapi.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 // 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) inline void xml_escape(std::wstring data)
@@ -182,13 +182,13 @@ inline void xml_escape(std::wstring data)
data.swap(buffer); 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 // 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 // https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-xml-schema
std::wstring toast_xml; std::wstring toast_xml;
toast_xml.reserve(1024); toast_xml.reserve(2048);
std::wstring title{ L"PowerToys" }; std::wstring title{ L"PowerToys" };
if (winstore::running_as_packaged()) 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 += L"</text><text>";
toast_xml += message; toast_xml += message;
toast_xml += L"</text></binding></visual><actions>"; 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{ std::visit(overloaded{
[&](const link_button& b) { [&](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 += b.url;
toast_xml += LR"(" content=")"; toast_xml += LR"(" content=")";
toast_xml += b.label; toast_xml += b.label;
toast_xml += LR"("/>)"; toast_xml += LR"(" />)";
}, },
[&](const background_activated_button& b) { [&](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"button_id=" + std::to_wstring(i); // pass the button ID
toast_xml += L"&amp;handler="; toast_xml += L"&amp;handler=";
toast_xml += background_handler_id; toast_xml += background_handler_id;
toast_xml += LR"(" content=")"; toast_xml += LR"(" content=")";
toast_xml += b.label; toast_xml += b.label;
toast_xml += LR"("/>)"; toast_xml += LR"(" />)";
}, },
}, [&](const snooze_button& b) {
buttons[i]); 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>"; 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() : const auto notifier = winstore::running_as_packaged() ? ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() :
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(WIN32_AUMID); 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); notifier.Show(notification);
} }

View File

@@ -1,8 +1,10 @@
#pragma once #pragma once
#include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <variant> #include <variant>
#include <optional>
namespace notifications namespace notifications
{ {
@@ -12,19 +14,38 @@ namespace notifications
void run_desktop_app_activator_loop(); 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 struct link_button
{ {
std::wstring_view label; std::wstring label;
std::wstring_view url; std::wstring url;
bool context_menu = false;
}; };
struct background_activated_button 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); using action_t = std::variant<link_button, background_activated_button, snooze_button>;
void show_toast_with_activations(std::wstring_view plaintext_message, std::wstring_view background_handler_id, std::vector<button_t> buttons);
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'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>Notifications</TargetName> <TargetName>Notifications</TargetName>
<LinkIncremental>false</LinkIncremental> <LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>NOTIFICATIONSDLL</TargetName> <TargetName>NOTIFICATIONSDLL</TargetName>
@@ -81,6 +82,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>Notifications</TargetName> <TargetName>Notifications</TargetName>
<LinkIncremental>true</LinkIncremental> <LinkIncremental>true</LinkIncremental>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>NOTIFICATIONSDLL</TargetName> <TargetName>NOTIFICATIONSDLL</TargetName>

View File

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

View File

@@ -75,9 +75,11 @@
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>notifications</TargetName> <TargetName>notifications</TargetName>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>notifications</TargetName> <TargetName>notifications</TargetName>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup> <ItemDefinitionGroup>
<ClCompile> <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); 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. // add_color_picker overloads.
void Settings::add_color_picker(std::wstring_view name, UINT description_resource_id, std::wstring_view value) 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, 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_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. // Serialize the internal json to a string.
std::wstring serialize(); std::wstring serialize();
// Serialize the internal json to the input buffer. // Serialize the internal json to the input buffer.

View File

@@ -1,5 +1,7 @@
#include "window_helpers.h" #include "window_helpers.h"
#include "pch.h" #include "pch.h"
#include <wil/Resource.h>
HWND CreateMsgWindow(_In_ HINSTANCE hInst, _In_ WNDPROC pfnWndProc, _In_ void* p) 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; 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 #pragma once
#include "common.h" #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'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental> <LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir> <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental> <LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir> <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,40 +5,113 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor" xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d" mc:Ignorable="d"
Background="LightGray" Background="Transparent"
Opacity="0.75"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="Frame">
<Grid.RowDefinitions> <UserControl.Resources>
<RowDefinition Height="8"/> <Style x:Key="CanvasZoneThumbStyle" TargetType="{x:Type Thumb}">
<RowDefinition Height="16"/> <Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<RowDefinition Height="24"/> <Setter Property="Background" Value="Transparent"/>
<RowDefinition Height="*"/> <Setter Property="Template">
<RowDefinition Height="16"/> <Setter.Value>
<RowDefinition Height="8"/> <ControlTemplate TargetType="{x:Type Thumb}">
</Grid.RowDefinitions> <Border x:Name="ThumbBorder" Opacity="0" BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions> <VisualStateManager.VisualStateGroups>
<ColumnDefinition Width="8"/> <VisualStateGroup x:Name="CommonStates" >
<ColumnDefinition Width="16"/> <VisualStateGroup.Transitions>
<ColumnDefinition Width="*"/> <VisualTransition GeneratedDuration="0:0:0.15">
<ColumnDefinition Width="16"/> <VisualTransition.GeneratedEasingFunction>
<ColumnDefinition Width="8"/> <ExponentialEase EasingMode="EaseInOut"/>
</Grid.ColumnDefinitions> </VisualTransition.GeneratedEasingFunction>
<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"/> </VisualTransition>
<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"/> </VisualStateGroup.Transitions>
<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"/> <VisualState x:Name="Normal" />
<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"/> <VisualState x:Name="MouseOver">
<Thumb x:Name="NResize" Cursor="SizeNS" Background="Black" Margin="1,0,1,0" Grid.Row="0" Grid.Column="2" DragDelta="UniversalDragDelta" DragStarted="NResize_DragStarted"/> <Storyboard>
<Thumb x:Name="SResize" Cursor="SizeNS" Background="Black" Margin="1,0,1,0" Grid.Row="5" Grid.Column="2" DragDelta="UniversalDragDelta" DragStarted="SResize_DragStarted"/> <DoubleAnimation Storyboard.TargetName="ThumbBorder" Duration="0:0:0.15" Storyboard.TargetProperty="Opacity" To="1"/>
<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"/> </Storyboard>
<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"/> </VisualState>
<DockPanel Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3"> </VisualStateGroup>
<Button DockPanel.Dock="Right" Padding="8,0" Click="OnClose"> </VisualStateManager.VisualStateGroups>
<Image Source="images/ChromeClose.png" Height="24" Width="24" /> </Border>
</Button> </ControlTemplate>
<Thumb x:Name="Caption" Cursor="SizeAll" Background="DarkGray" DragDelta="UniversalDragDelta" DragStarted="Caption_DragStarted"/> </Setter.Value>
</DockPanel> </Setter>
<Rectangle Fill="LightGray" Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3"/> </Style>
<Canvas x:Name="Body" />
</Grid> <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> </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(); LayoutModel.SerializeDeletedCustomZoneSets();
_choosing = true; _backToLayoutPicker = false;
Close(); Close();
EditorOverlay.Current.Close(); EditorOverlay.Current.Close();
} }
protected void OnClosed(object sender, EventArgs e) protected void OnClosed(object sender, EventArgs e)
{ {
if (!_choosing) if (_backToLayoutPicker)
{ {
EditorOverlay.Current.ShowLayoutPicker(); EditorOverlay.Current.ShowLayoutPicker();
} }
@@ -36,11 +36,10 @@ namespace FancyZonesEditor
protected void OnCancel(object sender, RoutedEventArgs e) protected void OnCancel(object sender, RoutedEventArgs e)
{ {
_choosing = true; _backToLayoutPicker = true;
Close(); Close();
EditorOverlay.Current.ShowLayoutPicker();
} }
private bool _choosing = false; private bool _backToLayoutPicker = true;
} }
} }

View File

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

View File

@@ -1,32 +1,48 @@
<UserControl x:Class="FancyZonesEditor.GridEditor" <UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" x:Class="FancyZonesEditor.GridEditor"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor" xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<Style TargetType="Button"> <Style TargetType="Button">
<Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontWeight" Value="SemiBold" /> <Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="Black"/> <Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="14"/> <Setter Property="FontSize" Value="14" />
<Setter Property="BorderThickness" Value="0"/> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="#F2F2F2"/> <Setter Property="Background" Value="#F2F2F2" />
<Setter Property="Width" Value="150"/> <Setter Property="Width" Value="150" />
</Style> </Style>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Canvas x:Name="Preview"/> <Canvas x:Name="Preview" />
<Canvas x:Name="AdornerLayer"/> <Canvas x:Name="AdornerLayer" />
<Canvas x:Name="MergePanel" Visibility="Collapsed" MouseUp="MergePanelMouseUp"> <Canvas
x:Name="MergePanel"
MouseUp="MergePanelMouseUp"
Visibility="Collapsed">
<StackPanel x:Name="MergeButtons" Background="Gray" Orientation="Horizontal"> <StackPanel
<Button Click="MergeClick" Margin="0" Height="36" Width="134"> x:Name="MergeButtons"
Background="Gray"
Orientation="Horizontal">
<Button
Width="134"
Height="36"
Margin="0"
Click="MergeClick">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Image Source="images/Merge.png" Margin="0,0,12,0" Height="16" HorizontalAlignment="Left" /> <Image
<TextBlock Text="Merge zones"/> Height="16"
Margin="0,0,12,0"
HorizontalAlignment="Left"
Source="images/Merge.png" />
<TextBlock Text="Merge zones" />
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel> </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)); 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() public GridEditor()
{ {
InitializeComponent(); InitializeComponent();
Loaded += GridEditor_Loaded; Loaded += GridEditor_Loaded;
((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged; ((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged;
gridEditorUniqueId = ++gridEditorUniqueIdCounter;
} }
private void GridEditor_Loaded(object sender, RoutedEventArgs e) private void GridEditor_Loaded(object sender, RoutedEventArgs e)
@@ -73,7 +78,9 @@ namespace FancyZonesEditor
private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{ {
Size actualSize = new Size(ActualWidth, ActualHeight); 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); ArrangeGridRects(actualSize);
} }
@@ -134,83 +141,50 @@ namespace FancyZonesEditor
private void ExtendRangeToHaveEvenCellEdges() 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; GridLayoutModel model = Model;
bool possiblyBroken = true;
while (_startRow > 0) while (possiblyBroken)
{ {
bool dirty = false; possiblyBroken = false;
for (int col = _startCol; col <= _endCol; col++) 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--; _startRow--;
dirty = true; possiblyBroken = true;
break; break;
} }
}
if (!dirty) if (_endRow < model.Rows - 1 && model.CellChildMap[_endRow + 1, col] == model.CellChildMap[_endRow, col])
{
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])
{ {
_endRow++; _endRow++;
dirty = true; possiblyBroken = true;
break; break;
} }
} }
if (!dirty)
{
break;
}
}
while (_startCol > 0)
{
bool dirty = false;
for (int row = _startRow; row <= _endRow; row++) 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--; _startCol--;
dirty = true; possiblyBroken = true;
break; break;
} }
}
if (!dirty) if (_endCol < model.Columns - 1 && model.CellChildMap[row, _endCol + 1] == model.CellChildMap[row, _endCol])
{
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])
{ {
_endCol++; _endCol++;
dirty = true; possiblyBroken = true;
break; break;
} }
} }
if (!dirty)
{
break;
}
} }
} }
@@ -528,7 +502,8 @@ namespace FancyZonesEditor
private void OnGridDimensionsChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 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(); OnGridDimensionsChanged();
} }
@@ -594,6 +569,7 @@ namespace FancyZonesEditor
top = _rowInfo[row].Start; top = _rowInfo[row].Start;
Canvas.SetLeft(zone, left); Canvas.SetLeft(zone, left);
Canvas.SetTop(zone, top); Canvas.SetTop(zone, top);
zone.LabelID.Content = i + 1;
int maxRow = row; int maxRow = row;
while (((maxRow + 1) < rows) && (model.CellChildMap[maxRow + 1, col] == i)) 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. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Windows;
using FancyZonesEditor.Models;
namespace FancyZonesEditor namespace FancyZonesEditor
{ {
/// <summary> /// <summary>
@@ -12,6 +15,16 @@ namespace FancyZonesEditor
public GridEditorWindow() public GridEditorWindow()
{ {
InitializeComponent(); 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> <Thumb.Template>
<ControlTemplate> <ControlTemplate>
<StackPanel x:Name="Body" Grid.Column="0" Width="48" Height="48"> <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"/>
<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> </StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Opacity" TargetName="BackgroundEllipse" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate> </ControlTemplate>
</Thumb.Template> </Thumb.Template>
</Thumb> </Thumb>

View File

@@ -1,17 +1,31 @@
<UserControl x:Class="FancyZonesEditor.GridZone" <UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" x:Class="FancyZonesEditor.GridZone"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FancyZonesEditor" xmlns:local="clr-namespace:FancyZonesEditor"
mc:Ignorable="d" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="LightGray" d:DesignHeight="450"
BorderThickness="1" d:DesignWidth="800"
BorderBrush="DarkGray" Background="{StaticResource GridZoneBackgroundBrush}"
Opacity="0.5" BorderBrush="{Binding Source={x:Static SystemParameters.WindowGlassBrush}}"
d:DesignHeight="450" d:DesignWidth="800"> BorderThickness="1"
<Grid x:Name="Frame" Visibility="Collapsed"> Opacity="0.8"
mc:Ignorable="d">
<Grid x:Name="Frame">
<Canvas x:Name="Body" /> <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"/>--> <!--<TextBlock Margin="2" Text="Shift Key switches direction&#13;Ctrl Key repeats"/>-->
</Grid> </Grid>
</UserControl> </UserControl>

View File

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

View File

@@ -113,48 +113,86 @@ namespace FancyZonesEditor.Models
return layout; 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 // PersistData
// Implements the LayoutModel.PersistData abstract method // Implements the LayoutModel.PersistData abstract method
protected override void PersistData() 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 try
{ {
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); string jsonString = JsonSerializer.Serialize(jsonObj, options);
using (var writer = new Utf8JsonWriter(outputStream, options: default)) File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
{
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();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -131,6 +131,12 @@ namespace FancyZonesEditor.Models
public override LayoutModel Clone() public override LayoutModel Clone()
{ {
GridLayoutModel layout = new GridLayoutModel(Name); GridLayoutModel layout = new GridLayoutModel(Name);
RestoreTo(layout);
return layout;
}
public void RestoreTo(GridLayoutModel layout)
{
int rows = Rows; int rows = Rows;
int cols = Columns; int cols = Columns;
@@ -163,69 +169,69 @@ namespace FancyZonesEditor.Models
} }
layout.ColumnPercents = colPercents; 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 // PersistData
// Implements the LayoutModel.PersistData abstract method // Implements the LayoutModel.PersistData abstract method
protected override void PersistData() 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 try
{ {
FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); string jsonString = JsonSerializer.Serialize(jsonObj, options);
using (var writer = new Utf8JsonWriter(outputStream, options: default)) File.WriteAllText(Settings.AppliedZoneSetTmpFile, jsonString);
{
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();
} }
catch (Exception ex) 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() public static void SerializeDeletedCustomZoneSets()
{ {
DeletedCustomZoneSetsWrapper deletedLayouts = new DeletedCustomZoneSetsWrapper
{
DeletedCustomZoneSets = _deletedCustomModels,
};
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = new DashCaseNamingPolicy(),
};
try try
{ {
FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create); string jsonString = JsonSerializer.Serialize(deletedLayouts, options);
var writer = new Utf8JsonWriter(outputStream, options: default); File.WriteAllText(Settings.CustomZoneSetsTmpFile, jsonString);
writer.WriteStartObject();
writer.WriteStartArray("deleted-custom-zone-sets");
foreach (string zoneSet in _deletedCustomModels)
{
writer.WriteStringValue(zoneSet);
}
writer.WriteEndArray();
writer.WriteEndObject();
writer.Flush();
outputStream.Close();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -258,51 +262,75 @@ namespace FancyZonesEditor.Models
Apply(); 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() 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 try
{ {
FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create); string jsonString = JsonSerializer.Serialize(zoneSet, options);
var writer = new Utf8JsonWriter(outputStream, options: default); File.WriteAllText(Settings.ActiveZoneSetTmpFile, jsonString);
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();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -329,15 +329,15 @@ namespace FancyZonesEditor
_gridModel.ColumnPercents[col] = ((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols); _gridModel.ColumnPercents[col] = ((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols);
} }
int index = 0; int index = ZoneCount - 1;
for (int col = cols - 1; col >= 0; col--) 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++; _gridModel.CellChildMap[row, col] = index--;
if (index == ZoneCount) if (index < 0)
{ {
index--; index = 0;
} }
} }
} }
@@ -407,7 +407,8 @@ namespace FancyZonesEditor
} }
inputStream.Close(); inputStream.Close();
} catch (Exception ex) }
catch (Exception ex)
{ {
LayoutModel.ShowExceptionMessageBox("Error parsing device info data", 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 <functional>
#include <common/common.h> #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 <unordered_set>
#include <common/notifications/fancyzones_notifications.h>
enum class DisplayChangeType enum class DisplayChangeType
{ {
WorkArea, WorkArea,
@@ -24,6 +28,8 @@ enum class DisplayChangeType
Initialization Initialization
}; };
extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace std namespace std
{ {
template<> template<>
@@ -83,10 +89,32 @@ public:
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
MoveWindowsOnActiveZoneSetChange() noexcept; MoveWindowsOnActiveZoneSetChange() noexcept;
IFACEMETHODIMP_(COLORREF) 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 GetZoneHighlightColor() noexcept
{ {
// Skip the leading # and convert to long // 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 tmp = std::stol(color.substr(1), nullptr, 16);
const auto nR = (tmp & 0xFF0000) >> 16; const auto nR = (tmp & 0xFF0000) >> 16;
const auto nG = (tmp & 0xFF00) >> 8; const auto nG = (tmp & 0xFF00) >> 8;
@@ -108,7 +136,13 @@ public:
IFACEMETHODIMP_(int) IFACEMETHODIMP_(int)
GetZoneHighlightOpacity() noexcept 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; LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept;
@@ -146,9 +180,10 @@ private:
}; };
bool IsInterestingWindow(HWND window) noexcept; bool IsInterestingWindow(HWND window) noexcept;
bool IsCursorTypeIndicatingSizeEvent();
void UpdateZoneWindows() noexcept; void UpdateZoneWindows() noexcept;
void MoveWindowsOnDisplayChange() noexcept; void MoveWindowsOnDisplayChange() noexcept;
void UpdateDragState(require_write_lock) noexcept; void UpdateDragState(HWND window, require_write_lock) noexcept;
void CycleActiveZoneSet(DWORD vkCode) noexcept; void CycleActiveZoneSet(DWORD vkCode) noexcept;
bool OnSnapHotkey(DWORD vkCode) noexcept; bool OnSnapHotkey(DWORD vkCode) noexcept;
void MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT const& ptScreen, require_write_lock) noexcept; void MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT const& ptScreen, require_write_lock) noexcept;
@@ -162,6 +197,10 @@ private:
void OnEditorExitEvent() noexcept; 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{}; const HINSTANCE m_hinstance{};
HKEY m_virtualDesktopsRegKey{ nullptr }; HKEY m_virtualDesktopsRegKey{ nullptr };
@@ -217,7 +256,7 @@ FancyZones::Run() noexcept
if (!m_window) if (!m_window)
return; 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(); VirtualDesktopInitialize();
@@ -309,7 +348,7 @@ FancyZones::VirtualDesktopInitialize() noexcept
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
FancyZones::WindowCreated(HWND window) noexcept 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) for (const auto& [monitor, zoneWindow] : m_zoneWindowMap)
{ {
@@ -345,17 +384,18 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000; bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
if (ctrl) if (ctrl)
{ {
if ((info->vkCode >= '0') && (info->vkCode <= '9')) // 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*/); // // Win+Ctrl+Number will cycle through ZoneSets
CycleActiveZoneSet(info->vkCode); // Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/);
return true; // CycleActiveZoneSet(info->vkCode);
} // return true;
//}
} }
else if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT)) 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 // Win+Left, Win+Right will cycle through Zones in the active ZoneSet
Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/); 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')) // 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*/); // // This allows you to cycle through ZoneSets while dragging a window
CycleActiveZoneSet(info->vkCode); // Trace::FancyZones::OnKeyDown(info->vkCode, win, false /*control*/, true /*inMoveSize*/);
return false; // CycleActiveZoneSet(info->vkCode);
} // return false;
//}
if (m_dragEnabled && shift) if (m_dragEnabled && shift)
{ {
return true; return true;
@@ -397,10 +438,7 @@ void FancyZones::ToggleEditor() noexcept
HMONITOR monitor{}; HMONITOR monitor{};
HWND foregroundWindow{}; HWND foregroundWindow{};
UINT dpi_x = DPIAware::DEFAULT_DPI; const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen;
UINT dpi_y = DPIAware::DEFAULT_DPI;
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings().use_cursorpos_editor_startupscreen;
POINT currentCursorPos{}; POINT currentCursorPos{};
if (use_cursorpos_editor_startupscreen) if (use_cursorpos_editor_startupscreen)
{ {
@@ -433,24 +471,14 @@ void FancyZones::ToggleEditor() noexcept
} }) } })
.wait(); .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; auto zoneWindow = iter->second;
const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance();
fancyZonesData.CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); 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 // 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 x = mi.rcMonitor.left + taskbar_x_offset;
const auto y = mi.rcMonitor.top + taskbar_y_offset; const auto y = mi.rcMonitor.top + taskbar_y_offset;
const auto width = mi.rcWork.right - mi.rcWork.left; 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" " + /*1*/ std::to_wstring(reinterpret_cast<UINT_PTR>(monitor)) + L" " +
/*2*/ editorLocation + L" " + /*2*/ editorLocation + L" " +
/*3*/ zoneWindow->WorkAreaKey() + L" " + /*3*/ zoneWindow->WorkAreaKey() + L" " +
/*4*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " + /*4*/ L"\"" + ZoneWindowUtils::GetActiveZoneSetTmpPath() + L"\" " +
/*5*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " + /*5*/ L"\"" + ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L"\" " +
/*6*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath(); /*6*/ L"\"" + ZoneWindowUtils::GetCustomZoneSetsTmpPath() + L"\"";
SHELLEXECUTEINFO sei{ sizeof(sei) }; SHELLEXECUTEINFO sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@@ -484,6 +512,7 @@ void FancyZones::ToggleEditor() noexcept
sei.lpParameters = params.c_str(); sei.lpParameters = params.c_str();
sei.nShow = SW_SHOWNORMAL; sei.nShow = SW_SHOWNORMAL;
ShellExecuteEx(&sei); ShellExecuteEx(&sei);
Trace::FancyZones::EditorLaunched(1);
// Launch the editor on a background thread // Launch the editor on a background thread
// Wait for the editor's process to exit // Wait for the editor's process to exit
@@ -515,14 +544,14 @@ void FancyZones::SettingsChanged() noexcept
std::shared_lock readLock(m_lock); std::shared_lock readLock(m_lock);
// Update the hotkey // Update the hotkey
UnregisterHotKey(m_window, 1); 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 // IZoneWindowHost
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept
{ {
if (m_settings->GetSettings().zoneSetChange_moveWindows) if (m_settings->GetSettings()->zoneSetChange_moveWindows)
{ {
MoveWindowsOnDisplayChange(); MoveWindowsOnDisplayChange();
} }
@@ -616,21 +645,21 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange))
{ {
if (m_settings->GetSettings().displayChange_moveWindows) if (m_settings->GetSettings()->displayChange_moveWindows)
{ {
MoveWindowsOnDisplayChange(); MoveWindowsOnDisplayChange();
} }
} }
else if (changeType == DisplayChangeType::VirtualDesktop) else if (changeType == DisplayChangeType::VirtualDesktop)
{ {
if (m_settings->GetSettings().virtualDesktopChange_moveWindows) if (m_settings->GetSettings()->virtualDesktopChange_moveWindows)
{ {
MoveWindowsOnDisplayChange(); MoveWindowsOnDisplayChange();
} }
} }
else if (changeType == DisplayChangeType::Editor) else if (changeType == DisplayChangeType::Editor)
{ {
if (m_settings->GetSettings().zoneSetChange_moveWindows) if (m_settings->GetSettings()->zoneSetChange_moveWindows)
{ {
MoveWindowsOnDisplayChange(); MoveWindowsOnDisplayChange();
} }
@@ -647,7 +676,9 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept
JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId); JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId);
const bool newWorkArea = IsNewWorkArea(m_currentVirtualDesktopId, monitor); 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); auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash);
if (zoneWindow) if (zoneWindow)
@@ -706,17 +737,42 @@ bool FancyZones::IsInterestingWindow(HWND window) noexcept
CharUpperBuffW(filtered.process_path.data(), (DWORD)filtered.process_path.length()); CharUpperBuffW(filtered.process_path.data(), (DWORD)filtered.process_path.length());
if (m_settings) 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; 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 void FancyZones::UpdateZoneWindows() noexcept
{ {
auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL { auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL {
@@ -774,7 +830,7 @@ void FancyZones::MoveWindowsOnDisplayChange() noexcept
EnumWindows(callback, reinterpret_cast<LPARAM>(this)); 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 shift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
const bool mouseL = GetAsyncKeyState(VK_LBUTTON) & 0x8000; const bool mouseL = GetAsyncKeyState(VK_LBUTTON) & 0x8000;
@@ -795,7 +851,7 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
mouse |= mouseR; mouse |= mouseR;
} }
if (m_settings->GetSettings().shiftDrag) if (m_settings->GetSettings()->shiftDrag)
{ {
m_dragEnabled = (shift | mouse); m_dragEnabled = (shift | mouse);
} }
@@ -803,6 +859,23 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept
{ {
m_dragEnabled = !(shift | mouse); 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 void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept
@@ -830,17 +903,43 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
auto window = GetForegroundWindow(); auto window = GetForegroundWindow();
if (IsInterestingWindow(window)) if (IsInterestingWindow(window))
{ {
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); const HMONITOR current = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (monitor) if (current)
{ {
std::shared_lock readLock(m_lock); std::vector<HMONITOR> monitorInfo = GetMonitorsSorted();
if (monitorInfo.size() > 1)
auto iter = m_zoneWindowMap.find(monitor);
if (iter != m_zoneWindowMap.end())
{ {
const auto& zoneWindowPtr = iter->second; // Multi monitor environment.
zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode); auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current);
return true; 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 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. if (IsCursorTypeIndicatingSizeEvent())
// 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)
{ {
return; return;
} }
m_inMoveSize = true; m_inMoveSize = true;
auto iter = m_zoneWindowMap.find(monitor); auto iter = m_zoneWindowMap.find(monitor);
@@ -877,17 +963,37 @@ void FancyZones::MoveSizeStartInternal(HWND window, HMONITOR monitor, POINT cons
m_windowMoveSize = window; m_windowMoveSize = window;
// This updates m_dragEnabled depending on if the shift key is being held down. // This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(writeLock); UpdateDragState(window, writeLock);
if (m_dragEnabled) if (m_dragEnabled)
{ {
m_zoneWindowMoveSize = iter->second; m_zoneWindowMoveSize = iter->second;
m_zoneWindowMoveSize->MoveSizeEnter(window, m_dragEnabled); 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) else if (m_zoneWindowMoveSize)
{ {
m_zoneWindowMoveSize->MoveSizeCancel(); m_zoneWindowMoveSize->RestoreOrginalTransparency();
m_zoneWindowMoveSize = nullptr; 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 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) if (m_inMoveSize)
{ {
// This updates m_dragEnabled depending on if the shift key is being held down. // This updates m_dragEnabled depending on if the shift key is being held down.
UpdateDragState(writeLock); UpdateDragState(m_windowMoveSize, writeLock);
if (m_zoneWindowMoveSize) if (m_zoneWindowMoveSize)
{ {
// Update the ZoneWindow already handling move/size // Update the ZoneWindow already handling move/size
if (!m_dragEnabled) if (!m_dragEnabled)
{ {
// Drag got disabled, tell it to cancel and clear out m_zoneWindowMoveSize // Drag got disabled, tell it to cancel and hide all windows
auto zoneWindow = std::move(m_zoneWindowMoveSize); m_zoneWindowMoveSize = nullptr;
zoneWindow->MoveSizeCancel();
for (auto [keyMonitor, zoneWindow] : m_zoneWindowMap)
{
if (zoneWindow)
{
zoneWindow->RestoreOrginalTransparency();
zoneWindow->HideZoneWindow();
}
}
} }
else else
{ {
@@ -950,12 +1073,20 @@ void FancyZones::MoveSizeUpdateInternal(HMONITOR monitor, POINT const& ptScreen,
if (iter->second != m_zoneWindowMoveSize) if (iter->second != m_zoneWindowMoveSize)
{ {
// The drag has moved to a different monitor. // The drag has moved to a different monitor.
auto const isDragEnabled = m_zoneWindowMoveSize->IsDragEnabled(); m_zoneWindowMoveSize->RestoreOrginalTransparency();
m_zoneWindowMoveSize->MoveSizeCancel();
if (!m_settings->GetSettings()->showZonesOnAllMonitors)
{
m_zoneWindowMoveSize->HideZoneWindow();
}
m_zoneWindowMoveSize = iter->second; 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(); 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 winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings) noexcept
{ {
if (!settings) if (!settings)
@@ -1083,4 +1254,4 @@ winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com
} }
return winrt::make_self<FancyZones>(hinstance, settings); return winrt::make_self<FancyZones>(hinstance, settings);
} }

View File

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

View File

@@ -10,6 +10,7 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <regex> #include <regex>
#include <sstream>
namespace namespace
{ {
@@ -46,6 +47,84 @@ namespace
namespace JSONHelpers 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 NumVecToJsonArray(const std::vector<int>& vec)
{ {
json::JsonArray arr; json::JsonArray arr;
@@ -525,9 +604,6 @@ namespace JSONHelpers
if (!std::filesystem::exists(jsonFilePath)) if (!std::filesystem::exists(jsonFilePath))
{ {
TmpMigrateAppliedZoneSetsFromRegistry();
// Custom zone sets have to be migrated after applied zone sets!
MigrateCustomZoneSetsFromRegistry(); MigrateCustomZoneSetsFromRegistry();
SaveFancyZonesData(); SaveFancyZonesData();
@@ -560,56 +636,6 @@ namespace JSONHelpers
json::to_file(jsonFilePath, root); 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() void FancyZonesData::MigrateCustomZoneSetsFromRegistry()
{ {
std::scoped_lock lock{ dataLock }; std::scoped_lock lock{ dataLock };
@@ -630,29 +656,19 @@ namespace JSONHelpers
zoneSetData.type = static_cast<CustomLayoutType>(data[2]); zoneSetData.type = static_cast<CustomLayoutType>(data[2]);
// int version = data[0] * 256 + data[1]; - Not used anymore // int version = data[0] * 256 + data[1]; - Not used anymore
std::wstring uuid = std::to_wstring(data[3] * 256 + data[4]); GUID guid;
auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair<std::wstring, ZoneSetData> zoneSetMap) { auto result = CoCreateGuid(&guid);
return zoneSetMap.second.uuid.compare(uuid) == 0; if (result != S_OK)
}); {
continue;
}
wil::unique_cotaskmem_string guidString;
if (!SUCCEEDED_LOG(StringFromCLSID(guid, &guidString)))
{
continue;
}
if (it != appliedZoneSetsMap.end()) std::wstring uuid = guidString.get();
{
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();
}
}
switch (zoneSetData.type) switch (zoneSetData.type)
{ {
@@ -660,14 +676,14 @@ namespace JSONHelpers
int j = 5; int j = 5;
GridLayoutInfo zoneSetInfo(GridLayoutInfo::Minimal{ .rows = data[j++], .columns = data[j++] }); 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++) for (int row = 0; row < zoneSetInfo.rows(); row++)
@@ -733,10 +749,14 @@ namespace JSONHelpers
try try
{ {
ZoneSetData zoneSetData; ZoneSetData zoneSetData;
zoneSetData.uuid = zoneSet.GetNamedString(L"uuid"); zoneSetData.uuid = zoneSet.GetNamedString(L"uuid");
zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") }); zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") });
if (!isValidGuid(zoneSetData.uuid))
{
return std::nullopt;
}
return zoneSetData; return zoneSetData;
} }
catch (const winrt::hresult_error&) catch (const winrt::hresult_error&)
@@ -768,6 +788,11 @@ namespace JSONHelpers
result.data.deviceId = zoneSet.GetNamedString(L"device-id"); result.data.deviceId = zoneSet.GetNamedString(L"device-id");
result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid"); result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid");
if (!isValidGuid(result.data.zoneSetUuid) || !isValidDeviceId(result.data.deviceId))
{
return std::nullopt;
}
return result; return result;
} }
catch (const winrt::hresult_error&) catch (const winrt::hresult_error&)
@@ -796,6 +821,10 @@ namespace JSONHelpers
DeviceInfoJSON result; DeviceInfoJSON result;
result.deviceId = device.GetNamedString(L"device-id"); 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()) if (auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); zoneSet.has_value())
{ {
@@ -988,6 +1017,11 @@ namespace JSONHelpers
CustomZoneSetJSON result; CustomZoneSetJSON result;
result.uuid = customZoneSet.GetNamedString(L"uuid"); result.uuid = customZoneSet.GetNamedString(L"uuid");
if (!isValidGuid(result.uuid))
{
return std::nullopt;
}
result.data.name = customZoneSet.GetNamedString(L"name"); result.data.name = customZoneSet.GetNamedString(L"name");
json::JsonObject infoJson = customZoneSet.GetNamedObject(L"info"); json::JsonObject infoJson = customZoneSet.GetNamedObject(L"info");

View File

@@ -16,6 +16,11 @@ namespace JSONHelpers
{ {
constexpr int MAX_ZONE_COUNT = 50; 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 enum class ZoneSetLayoutType : int
{ {
Blank = -1, Blank = -1,
@@ -202,7 +207,6 @@ namespace JSONHelpers
#if defined(UNIT_TESTS) #if defined(UNIT_TESTS)
inline void clear_data() inline void clear_data()
{ {
appliedZoneSetsMap.clear();
appZoneHistoryMap.clear(); appZoneHistoryMap.clear();
deviceInfoMap.clear(); deviceInfoMap.clear();
customZoneSetsMap.clear(); customZoneSetsMap.clear();
@@ -249,10 +253,8 @@ namespace JSONHelpers
void SaveFancyZonesData() const; void SaveFancyZonesData() const;
private: private:
void TmpMigrateAppliedZoneSetsFromRegistry();
void MigrateCustomZoneSetsFromRegistry(); void MigrateCustomZoneSetsFromRegistry();
std::unordered_map<std::wstring, ZoneSetData> appliedZoneSetsMap{};
std::unordered_map<std::wstring, AppZoneHistoryData> appZoneHistoryMap{}; std::unordered_map<std::wstring, AppZoneHistoryData> appZoneHistoryMap{};
std::unordered_map<std::wstring, DeviceInfoData> deviceInfoMap{}; std::unordered_map<std::wstring, DeviceInfoData> deviceInfoMap{};
std::unordered_map<std::wstring, CustomZoneSetData> customZoneSetsMap{}; 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_(bool) GetConfig(_Out_ PWSTR buffer, _Out_ int *buffer_sizeg) noexcept;
IFACEMETHODIMP_(void) SetConfig(PCWSTR config) noexcept; IFACEMETHODIMP_(void) SetConfig(PCWSTR config) noexcept;
IFACEMETHODIMP_(void) CallCustomAction(PCWSTR action) noexcept; IFACEMETHODIMP_(void) CallCustomAction(PCWSTR action) noexcept;
IFACEMETHODIMP_(Settings) GetSettings() noexcept { return m_settings; } IFACEMETHODIMP_(const Settings*) GetSettings() const noexcept { return &m_settings; }
private: private:
void LoadSettings(PCWSTR config, bool fromFile) noexcept; void LoadSettings(PCWSTR config, bool fromFile) noexcept;
@@ -36,17 +36,22 @@ private:
PCWSTR name; PCWSTR name;
bool* value; bool* value;
int resourceId; 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_shiftDrag", &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG },
{ L"fancyzones_overrideSnapHotkeys", &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS }, { 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_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_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_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"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"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_zoneHiglightName = L"fancyzones_zoneHighlightColor";
const std::wstring m_editorHotkeyName = L"fancyzones_editor_hotkey"; const std::wstring m_editorHotkeyName = L"fancyzones_editor_hotkey";
const std::wstring m_excludedAppsName = L"fancyzones_excluded_apps"; 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_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_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); settings.add_multiline_string(m_excludedAppsName, IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, m_settings.excludedApps);
return settings.serialize_to_buffer(buffer, buffer_size); 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)) if (auto val = values.get_string_value(m_zoneHiglightName))
{ {
m_settings.zoneHightlightColor = std::move(*val); 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(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_zoneHiglightName, m_settings.zoneHightlightColor);
values.add_property(m_zoneHighlightOpacity, m_settings.zoneHighlightOpacity); values.add_property(m_zoneHighlightOpacity, m_settings.zoneHighlightOpacity);
values.add_property(m_editorHotkeyName, m_settings.editorHotkey.get_json()); values.add_property(m_editorHotkeyName, m_settings.editorHotkey.get_json());

View File

@@ -14,8 +14,12 @@ struct Settings
bool overrideSnapHotkeys = false; bool overrideSnapHotkeys = false;
bool appLastZone_moveWindows = false; bool appLastZone_moveWindows = false;
bool use_cursorpos_editor_startupscreen = true; bool use_cursorpos_editor_startupscreen = true;
std::wstring zoneHightlightColor = L"#0078D7"; bool showZonesOnAllMonitors = false;
int zoneHighlightOpacity = 90; 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); PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3);
std::wstring excludedApps = L""; std::wstring excludedApps = L"";
std::vector<std::wstring> excludedAppsArray; 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_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0;
IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0; IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0;
IFACEMETHOD_(void, CallCustomAction)(PCWSTR action) = 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; 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; return;
} }
// Take care of 1px border // Take care of 1px border
RECT zoneRect = m_zoneRect; RECT newWindowRect = m_zoneRect;
RECT windowRect{}; RECT windowRect{};
::GetWindowRect(window, &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 left_margin = frameRect.left - windowRect.left;
const auto right_margin = frameRect.right - windowRect.right; const auto right_margin = frameRect.right - windowRect.right;
const auto bottom_margin = frameRect.bottom - windowRect.bottom; const auto bottom_margin = frameRect.bottom - windowRect.bottom;
zoneRect.left -= left_margin; newWindowRect.left -= left_margin;
zoneRect.right -= right_margin; newWindowRect.right -= right_margin;
zoneRect.bottom -= bottom_margin; newWindowRect.bottom -= bottom_margin;
} }
// Map to screen coords // 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)) if (GetMonitorInfoW(MonitorFromWindow(zoneWindow, MONITOR_DEFAULTTONEAREST), &mi))
{ {
const auto taskbar_left_size = std::abs(mi.rcMonitor.left - mi.rcWork.left); 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); 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) if (accountForUnawareness)
{ {
zoneRect.left = max(mi.rcMonitor.left, zoneRect.left); newWindowRect.left = max(mi.rcMonitor.left, newWindowRect.left);
zoneRect.right = min(mi.rcMonitor.right - taskbar_left_size, zoneRect.right); newWindowRect.right = min(mi.rcMonitor.right - taskbar_left_size, newWindowRect.right);
zoneRect.top = max(mi.rcMonitor.top, zoneRect.top); newWindowRect.top = max(mi.rcMonitor.top, newWindowRect.top);
zoneRect.bottom = min(mi.rcMonitor.bottom - taskbar_top_size, zoneRect.bottom); 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{}; WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &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. // Do not restore minimized windows. We change their placement though so they restore to the correct zone.
if ((placement.showCmd & SW_SHOWMINIMIZED) == 0) if ((placement.showCmd & SW_SHOWMINIMIZED) == 0)
{ {
placement.showCmd = SW_RESTORE | SW_SHOWNA; placement.showCmd = SW_RESTORE | SW_SHOWNA;
} }
placement.rcNormalPosition = newWindowRect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
::SetWindowPlacement(window, &placement); ::SetWindowPlacement(window, &placement);
// Do it again, allowing Windows to resize the window and set correct scaling // Do it again, allowing Windows to resize the window and set correct scaling
// This fixes Issue #365 // This fixes Issue #365

View File

@@ -130,8 +130,8 @@ public:
GetZones() noexcept { return m_zones; } GetZones() noexcept { return m_zones; }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept; MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept; MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept; MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept;
IFACEMETHODIMP_(bool) IFACEMETHODIMP_(bool)
@@ -226,7 +226,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
if (index >= int(m_zones.size())) if (index >= int(m_zones.size()))
{ {
index = 0; return;
} }
while (auto zoneDrop = ZoneFromWindow(window)) while (auto zoneDrop = ZoneFromWindow(window))
@@ -240,12 +240,12 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex
} }
} }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(bool)
ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode, bool cycle) noexcept
{ {
if (m_zones.empty()) if (m_zones.empty())
{ {
return; return false;
} }
winrt::com_ptr<IZone> oldZone = nullptr; winrt::com_ptr<IZone> oldZone = nullptr;
@@ -262,6 +262,11 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
{ {
if (iter == m_zones.begin()) if (iter == m_zones.begin())
{ {
if (!cycle)
{
oldZone->RemoveWindowFromZone(window, false);
return false;
}
iter = m_zones.end(); iter = m_zones.end();
} }
iter--; iter--;
@@ -271,6 +276,11 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
iter++; iter++;
if (iter == m_zones.end()) if (iter == m_zones.end())
{ {
if (!cycle)
{
oldZone->RemoveWindowFromZone(window, false);
return false;
}
iter = m_zones.begin(); iter = m_zones.begin();
} }
} }
@@ -283,7 +293,9 @@ ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCod
oldZone->RemoveWindowFromZone(window, false); oldZone->RemoveWindowFromZone(window, false);
} }
newZone->AddWindowToZone(window, windowZone, true); newZone->AddWindowToZone(window, windowZone, true);
return true;
} }
return false;
} }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
@@ -560,7 +572,7 @@ bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo grid
// Note: The expressions below are carefully written to // Note: The expressions below are carefully written to
// make the sum of all zones' sizes exactly total{Width|Height} // 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++) for (int row = 0; row < gridLayoutInfo.rows(); row++)
{ {
rowInfo[row].Start = totalPercents * totalHeight / C_MULTIPLIER + (row + 1) * spacing; 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 * @param zoneWindow The m_window of a ZoneWindow, it's a hidden window representing the
* current monitor desktop work area. * current monitor desktop work area.
* @param vkCode Pressed arrow key. * @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. * 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 monitorInfo Information about monitor on which zone layout is applied.
* @param zoneCount Number of zones inside zone layout. * @param zoneCount Number of zones inside zone layout.
* @param spacing Spacing between zones in pixels. * @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; IFACEMETHOD_(bool, CalculateZones)(MONITORINFO monitorInfo, int zoneCount, int spacing) = 0;
}; };

View File

@@ -10,6 +10,8 @@
#include <ShellScalingApi.h> #include <ShellScalingApi.h>
#include <mutex> #include <mutex>
#include <gdiplus.h>
namespace ZoneWindowUtils namespace ZoneWindowUtils
{ {
const std::wstring& GetActiveZoneSetTmpPath() const std::wstring& GetActiveZoneSetTmpPath()
@@ -92,146 +94,70 @@ namespace ZoneWindowDrawUtils
int thickness{}; 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 void DrawBackdrop(wil::unique_hdc& hdc, RECT const& clientRect) noexcept
{ {
FillRectARGB(hdc, &clientRect, 0, RGB(0, 0, 0), false); 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 }; Gdiplus::Graphics g(hdc.get());
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;
}
if (flipY) Gdiplus::FontFamily fontFamily(L"Segoe ui");
{ Gdiplus::Font font(&fontFamily, 80, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
if (y == 0) Gdiplus::SolidBrush solidBrush(Gdiplus::Color(255, 0, 0, 0));
useRect.top += (size + padding + size + padding);
else if (y == 2)
useRect.top -= (size + padding + size + padding);
useRect.bottom = useRect.top + size;
}
FillRectARGB(hdc, &useRect, 200, RGB(50, 50, 50), true); std::wstring text = std::to_wstring(index);
RECT inside = useRect; g.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
InflateRect(&inside, -2, -2); 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); g.DrawString(text.c_str(), -1, &font, gdiRect, &stringFormat, &solidBrush);
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;
}
} }
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 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(); 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) Gdiplus::Graphics g(hdc.get());
{ Gdiplus::Color fillColor(colorSetting.fillAlpha, GetRValue(colorSetting.fill), GetGValue(colorSetting.fill), GetBValue(colorSetting.fill));
return; Gdiplus::Color borderColor(colorSetting.borderAlpha, GetRValue(colorSetting.border), GetGValue(colorSetting.border), GetBValue(colorSetting.border));
}
COLORREF const colorFill = RGB(255, 255, 255);
size_t const index = zone->Id(); Gdiplus::Rect rectangle(zoneRect.left, zoneRect.top, zoneRect.right - zoneRect.left - 1, zoneRect.bottom - zoneRect.top - 1);
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;
}
offset.x = zoneRect.right - ((padding + size) * 3); Gdiplus::Pen pen(borderColor, static_cast<Gdiplus::REAL>(colorSetting.thickness));
if (!IsOccluded(zones, offset, index)) g.FillRectangle(new Gdiplus::SolidBrush(fillColor), rectangle);
{ g.DrawRectangle(&pen, rectangle);
DrawIndex(hdc, offset, index, padding, size, true, false, colorFill); // top right
return;
}
offset.y = zoneRect.bottom - ((padding + size) * 3); if (!flashMode)
if (!IsOccluded(zones, offset, index))
{ {
DrawIndex(hdc, offset, index, padding, size, true, true, colorFill); // bottom right DrawIndex(hdc, zoneRect, zone->Id());
return;
} }
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{ // { fillAlpha, fill, borderAlpha, border, thickness }
RGB(75, 75, 85), ColorSetting const colorHints{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 };
RGB(150, 150, 160), ColorSetting colorViewer{ OpacitySettingToAlpha(zoneOpacity), 0, 255, RGB(40, 50, 60), -2 };
RGB(100, 100, 110), ColorSetting colorHighlight{ OpacitySettingToAlpha(zoneOpacity), 0, 255, 0, -2 };
RGB(125, 125, 135), ColorSetting const colorFlash{ OpacitySettingToAlpha(zoneOpacity), RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 };
RGB(225, 225, 235),
RGB(25, 25, 35),
RGB(200, 200, 210),
RGB(50, 50, 60),
RGB(175, 175, 185),
};
// 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++) for (auto iter = zones.begin(); iter != zones.end(); iter++)
{ {
winrt::com_ptr<IZone> zone = iter->try_as<IZone>(); winrt::com_ptr<IZone> zone = iter->try_as<IZone>();
@@ -251,20 +177,17 @@ namespace ZoneWindowDrawUtils
DrawZone(hdc, colorHints, zone, zones, flashMode); DrawZone(hdc, colorHints, zone, zones, flashMode);
} }
{ {
colorViewer.fill = colors[colorIndex]; colorViewer.fill = zoneColor;
colorViewer.border = zoneBorderColor;
DrawZone(hdc, colorViewer, zone, zones, flashMode); DrawZone(hdc, colorViewer, zone, zones, flashMode);
} }
} }
colorIndex = colorIndex != 0 ? colorIndex - 1 : maxColorIndex;
} }
if (highlightZone) if (highlightZone)
{ {
colorHighlight.fill = highlightColor; colorHighlight.fill = highlightColor;
colorHighlight.border = RGB( colorHighlight.border = zoneBorderColor;
max(0, GetRValue(colorHighlight.fill) - 25),
max(0, GetGValue(colorHighlight.fill) - 25),
max(0, GetBValue(colorHighlight.fill) - 25));
DrawZone(hdc, colorHighlight, highlightZone, zones, flashMode); DrawZone(hdc, colorHighlight, highlightZone, zones, flashMode);
} }
} }
@@ -274,18 +197,21 @@ struct ZoneWindow : public winrt::implements<ZoneWindow, IZoneWindow>
{ {
public: public:
ZoneWindow(HINSTANCE hinstance); ZoneWindow(HINSTANCE hinstance);
~ZoneWindow();
bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones); bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones);
IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept; IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept; IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept;
IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept;
IFACEMETHODIMP MoveSizeCancel() noexcept; IFACEMETHODIMP_(void)
RestoreOrginalTransparency() noexcept;
IFACEMETHODIMP_(bool) IFACEMETHODIMP_(bool)
IsDragEnabled() noexcept { return m_dragEnabled; } IsDragEnabled() noexcept { return m_dragEnabled; }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
MoveWindowIntoZoneByIndex(HWND window, int index) noexcept; MoveWindowIntoZoneByIndex(HWND window, int index) noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(bool)
MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept; MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
CycleActiveZoneSet(DWORD vkCode) noexcept; CycleActiveZoneSet(DWORD vkCode) noexcept;
IFACEMETHODIMP_(std::wstring) IFACEMETHODIMP_(std::wstring)
@@ -296,13 +222,15 @@ public:
SaveWindowProcessToZoneIndex(HWND window) noexcept; SaveWindowProcessToZoneIndex(HWND window) noexcept;
IFACEMETHODIMP_(IZoneSet*) IFACEMETHODIMP_(IZoneSet*)
ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } ActiveZoneSet() noexcept { return m_activeZoneSet.get(); }
IFACEMETHODIMP_(void)
ShowZoneWindow() noexcept;
IFACEMETHODIMP_(void)
HideZoneWindow() noexcept;
protected: protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
private: private:
void ShowZoneWindow() noexcept;
void HideZoneWindow() noexcept;
void LoadSettings() noexcept; void LoadSettings() noexcept;
void InitializeZoneSets(MONITORINFO const& mi) noexcept; void InitializeZoneSets(MONITORINFO const& mi) noexcept;
void CalculateZoneSet() noexcept; void CalculateZoneSet() noexcept;
@@ -330,6 +258,14 @@ private:
size_t m_keyCycle{}; size_t m_keyCycle{};
static const UINT m_showAnimationDuration = 200; // ms static const UINT m_showAnimationDuration = 200; // ms
static const UINT m_flashDuration = 700; // 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) ZoneWindow::ZoneWindow(HINSTANCE hinstance)
@@ -341,6 +277,16 @@ ZoneWindow::ZoneWindow(HINSTANCE hinstance)
wcex.lpszClassName = L"SuperFancyZones_ZoneWindow"; wcex.lpszClassName = L"SuperFancyZones_ZoneWindow";
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
RegisterClassExW(&wcex); 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) 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; 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_dragEnabled = dragEnabled;
m_windowMoveSize = window; m_windowMoveSize = window;
m_drawHints = true; m_drawHints = true;
@@ -435,6 +397,8 @@ IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnable
IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept
{ {
RestoreOrginalTransparency();
if (m_windowMoveSize != window) if (m_windowMoveSize != window)
{ {
return E_INVALIDARG; return E_INVALIDARG;
@@ -455,10 +419,15 @@ IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexc
return S_OK; return S_OK;
} }
IFACEMETHODIMP ZoneWindow::MoveSizeCancel() noexcept IFACEMETHODIMP_(void)
ZoneWindow::RestoreOrginalTransparency() noexcept
{ {
HideZoneWindow(); if (m_host->isMakeDraggedWindowTransparentActive() && draggedWindow != nullptr)
return S_OK; {
SetLayeredWindowAttributes(draggedWindow, draggedWindowCrKey, draggedWindowInitialAlpha, draggedWindowDwFlags);
SetWindowLong(draggedWindow, GWL_EXSTYLE, draggedWindowExstyle);
draggedWindow = nullptr;
}
} }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
@@ -470,14 +439,18 @@ ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept
} }
} }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(bool)
ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode, bool cycle) noexcept
{ {
if (m_activeZoneSet) if (m_activeZoneSet)
{ {
m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode); if (m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode, cycle))
SaveWindowProcessToZoneIndex(window); {
SaveWindowProcessToZoneIndex(window);
return true;
}
} }
return false;
} }
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
@@ -514,8 +487,8 @@ ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept
} }
} }
#pragma region private IFACEMETHODIMP_(void)
void ZoneWindow::ShowZoneWindow() noexcept ZoneWindow::ShowZoneWindow() noexcept
{ {
if (m_window) if (m_window)
{ {
@@ -536,7 +509,8 @@ void ZoneWindow::ShowZoneWindow() noexcept
} }
} }
void ZoneWindow::HideZoneWindow() noexcept IFACEMETHODIMP_(void)
ZoneWindow::HideZoneWindow() noexcept
{ {
if (m_window) if (m_window)
{ {
@@ -548,6 +522,8 @@ void ZoneWindow::HideZoneWindow() noexcept
} }
} }
#pragma region private
void ZoneWindow::LoadSettings() noexcept void ZoneWindow::LoadSettings() noexcept
{ {
JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId); JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId);
@@ -630,7 +606,8 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
{ {
switch (message) switch (message)
{ {
case WM_NCDESTROY: { case WM_NCDESTROY:
{
::DefWindowProc(m_window.get(), message, wparam, lparam); ::DefWindowProc(m_window.get(), message, wparam, lparam);
SetWindowLongPtr(m_window.get(), GWLP_USERDATA, 0); SetWindowLongPtr(m_window.get(), GWLP_USERDATA, 0);
} }
@@ -640,7 +617,8 @@ LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
return 1; return 1;
case WM_PRINTCLIENT: case WM_PRINTCLIENT:
case WM_PAINT: { case WM_PAINT:
{
PAINTSTRUCT ps; PAINTSTRUCT ps;
wil::unique_hdc hdc{ reinterpret_cast<HDC>(wparam) }; wil::unique_hdc hdc{ reinterpret_cast<HDC>(wparam) };
if (!hdc) if (!hdc)
@@ -677,9 +655,18 @@ void ZoneWindow::OnPaint(wil::unique_hdc& hdc) noexcept
if (bufferedPaint) if (bufferedPaint)
{ {
ZoneWindowDrawUtils::DrawBackdrop(hdcMem, clientRect); ZoneWindowDrawUtils::DrawBackdrop(hdcMem, clientRect);
if (m_activeZoneSet && m_host) 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); EndBufferedPaint(bufferedPaint, TRUE);
@@ -757,6 +744,12 @@ void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::Inp
void ZoneWindow::FlashZones() noexcept void ZoneWindow::FlashZones() noexcept
{ {
// "Turning FLASHING_ZONE option off"
if (true)
{
return;
}
m_flashMode = true; m_flashMode = true;
ShowWindow(m_window.get(), SW_SHOWNA); 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. * @param ptScreen Cursor coordinates where window is droped.
*/ */
IFACEMETHOD(MoveSizeEnd)(HWND window, POINT const& ptScreen) = 0; 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 * @returns Boolean indicating is giving hints about active zone layout enabled. Hints are
* given while dragging window while holding SHIFT key. * 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 window Handle of window which should be assigned to zone.
* @param vkCode Pressed arrow key. * @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). * Cycle through active zone layouts (giving hints about each layout).
* *
* @param vkCode Pressed key representing layout index. * @param vkCode Pressed key representing layout index.
*/ */
IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0; 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. * 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. * 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. * @returns Active zone layout for this work area.
*/ */
IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0; IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0;
IFACEMETHOD_(void, ShowZoneWindow)() = 0;
IFACEMETHOD_(void, HideZoneWindow)() = 0;
}; };
winrt::com_ptr<IZoneWindow> MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, winrt::com_ptr<IZoneWindow> MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor,

View File

@@ -1,17 +1,24 @@
#define IDS_SETTING_DESCRIPTION_SHIFTDRAG 101 #define IDS_SETTING_DESCRIPTION_SHIFTDRAG 101
#define IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS 102 #define IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS 102
#define IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS 103 #define IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS 103
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS 104 #define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS 104
#define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES 105 #define IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES 105
#define IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS 106 #define IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS 106
#define IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR 107 #define IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS 107
#define IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS 108 #define IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT 108
#define IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN 109 #define IDS_SETTING_DESCRIPTION_ZONECOLOR 109
#define IDS_SETTING_DESCRIPTION 110 #define IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR 110
#define IDS_SETTING_LAUNCH_EDITOR_LABEL 111 #define IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR 111
#define IDS_SETTING_LAUNCH_EDITOR_BUTTON 112 #define IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS 112
#define IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION 113 #define IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN 113
#define IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL 114 #define IDS_SETTING_DESCRIPTION 114
#define IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION 115 #define IDS_SETTING_LAUNCH_EDITOR_LABEL 115
#define IDS_SETTINGS_HIGHLIGHT_OPACITY 116 #define IDS_SETTING_LAUNCH_EDITOR_BUTTON 116
#define IDS_FANCYZONES 117 #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")); 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 void Trace::SettingsChanged(const Settings& settings) noexcept
{ {
const auto& editorHotkey = settings.editorHotkey; const auto& editorHotkey = settings.editorHotkey;
@@ -170,6 +180,10 @@ void Trace::SettingsChanged(const Settings& settings) noexcept
TraceLoggingBoolean(settings.overrideSnapHotkeys, "OverrideSnapHotKeys"), TraceLoggingBoolean(settings.overrideSnapHotkeys, "OverrideSnapHotKeys"),
TraceLoggingBoolean(settings.appLastZone_moveWindows, "MoveWindowsToLastZoneOnAppOpening"), TraceLoggingBoolean(settings.appLastZone_moveWindows, "MoveWindowsToLastZoneOnAppOpening"),
TraceLoggingBoolean(settings.use_cursorpos_editor_startupscreen, "UseCursorPosOnEditorStartup"), 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"), TraceLoggingWideString(settings.zoneHightlightColor.c_str(), "ZoneHighlightColor"),
TraceLoggingInt32(settings.zoneHighlightOpacity, "ZoneHighlightOpacity"), TraceLoggingInt32(settings.zoneHighlightOpacity, "ZoneHighlightOpacity"),
TraceLoggingWideString(hotkeyStr.c_str(), "Hotkey"), TraceLoggingWideString(hotkeyStr.c_str(), "Hotkey"),

View File

@@ -15,6 +15,7 @@ public:
static void EnableFancyZones(bool enabled) noexcept; static void EnableFancyZones(bool enabled) noexcept;
static void OnKeyDown(DWORD vkCode, bool win, bool control, bool inMoveSize) noexcept; static void OnKeyDown(DWORD vkCode, bool win, bool control, bool inMoveSize) noexcept;
static void DataChanged() noexcept; static void DataChanged() noexcept;
static void EditorLaunched(int value) noexcept;
}; };
static void SettingsChanged(const Settings& settings) 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; 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 #pragma once
#include "gdiplus.h"
struct Rect struct Rect
{ {
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.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); 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)); ZeroMemory(quad, sizeof(*quad));
quad->rgbReserved = alpha; quad->rgbReserved = alpha;
@@ -47,7 +51,7 @@ inline void InitRGB(_Out_ RGBQUAD *quad, BYTE alpha, COLORREF color)
quad->rgbBlue = GetBValue(color) * alpha / 255; 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; BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi)); ZeroMemory(&bi, sizeof(bi));
@@ -60,63 +64,30 @@ inline void FillRectARGB(wil::unique_hdc& hdc, RECT const *prcFill, BYTE alpha,
RECT fillRect; RECT fillRect;
CopyRect(&fillRect, prcFill); 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()) }; RGBQUAD bitmapBits;
BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; InitRGB(&bitmapBits, alpha, color);
GdiAlphaBlend( StretchDIBits(
hdc.get(), hdc.get(),
fillRect.left, fillRect.left,
fillRect.top, fillRect.top,
fillRect.right - fillRect.left, fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top, fillRect.bottom - fillRect.top,
hdcSrc.get(), 0, 0, 1, 1, bf); 0,
} 0,
1,
} 1,
} &bitmapBits,
} &bi,
DIB_RGB_COLORS,
inline void FrameRectARGB(wil::unique_hdc& hdc, const RECT &rc, BYTE bAlpha, COLORREF clr, int thickness) SRCCOPY);
{
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);
}
} }
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size) inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{ {
// We're interested in the unique part between the first and last #'s // 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 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"; const std::wstring defaultDeviceId = L"FallbackDevice";
if (!deviceId) 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<BYTE>(opacity * 2.55);
return static_cast<unsigned char>(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 namespace FancyZonesUnitTests
{ {
TEST_CLASS(FancyZonesUnitTests) TEST_CLASS (FancyZonesUnitTests)
{ {
HINSTANCE m_hInst; HINSTANCE m_hInst;
winrt::com_ptr<IFancyZonesSettings> m_settings; winrt::com_ptr<IFancyZonesSettings> m_settings;
TEST_METHOD_INITIALIZE(Init) 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 (CreateWithNullHinstance)
}
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++)
{ {
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{}; HINSTANCE m_hInst{};
std::wstring m_settingsLocation = L"FancyZonesUnitTests"; 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_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"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"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_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_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); 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) TEST_METHOD_INITIALIZE(Init)
{ {
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str()); m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsTrue(m_settings != nullptr); Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings); auto fancyZones = MakeFancyZones(m_hInst, m_settings);
Assert::IsTrue(fancyZones != nullptr); Assert::IsTrue(fancyZones != nullptr);
m_zoneWindowHost = fancyZones.as<IZoneWindowHost>(); m_zoneWindowHost = fancyZones.as<IZoneWindowHost>();
Assert::IsTrue(m_zoneWindowHost != nullptr); Assert::IsTrue(m_zoneWindowHost != nullptr);
} }
TEST_METHOD_CLEANUP(Cleanup) TEST_METHOD_CLEANUP(Cleanup)
{ {
auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation); auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
const auto settingsFile = settingsFolder + L"\\settings.json"; const auto settingsFile = settingsFolder + L"\\settings.json";
std::filesystem::remove(settingsFile); std::filesystem::remove(settingsFile);
std::filesystem::remove(settingsFolder); std::filesystem::remove(settingsFolder);
} }
TEST_METHOD(GetZoneHighlightColor) TEST_METHOD (GetZoneColor)
{ {
const auto expected = RGB(171, 175, 238); const auto expected = RGB(171, 175, 238);
const Settings settings{ const Settings settings{
.shiftDrag = true, .shiftDrag = true,
.displayChange_moveWindows = true, .displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true, .virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false, .zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true, .zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false, .overrideSnapHotkeys = false,
.appLastZone_moveWindows = true, .appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true, .use_cursorpos_editor_startupscreen = true,
.zoneHightlightColor = L"#abafee", .zoneColor = L"#abafee",
.zoneHighlightOpacity = 45, .zoneBorderColor = L"FAFAFA",
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), .zoneHightlightColor = L"#FAFAFA",
.excludedApps = L"app\r\napp2", .zoneHighlightOpacity = 45,
.excludedAppsArray = { L"APP", L"APP2" }, .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); auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str()); m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightColor(); const auto actual = m_zoneWindowHost->GetZoneColor();
Assert::AreEqual(expected, actual); Assert::AreEqual(expected, actual);
} }
TEST_METHOD(GetZoneHighlightOpacity) TEST_METHOD (GetZoneBorderColor)
{ {
const auto expected = 88; const auto expected = RGB(171, 175, 238);
const Settings settings{ const Settings settings{
.shiftDrag = true, .shiftDrag = true,
.displayChange_moveWindows = true, .displayChange_moveWindows = true,
.virtualDesktopChange_moveWindows = true, .virtualDesktopChange_moveWindows = true,
.zoneSetChange_flashZones = false, .zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true, .zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false, .overrideSnapHotkeys = false,
.appLastZone_moveWindows = true, .appLastZone_moveWindows = true,
.use_cursorpos_editor_startupscreen = true, .use_cursorpos_editor_startupscreen = true,
.zoneHightlightColor = L"#abafee", .zoneColor = L"#FAFAFA",
.zoneHighlightOpacity = expected, .zoneBorderColor = L"#abafee",
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), .zoneHightlightColor = L"#FAFAFA",
.excludedApps = L"app\r\napp2", .zoneHighlightOpacity = 45,
.excludedAppsArray = { L"APP", L"APP2" }, .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); auto config = serializedPowerToySettings(settings);
m_settings->SetConfig(config.c_str()); m_settings->SetConfig(config.c_str());
const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity(); const auto actual = m_zoneWindowHost->GetZoneBorderColor();
Assert::AreEqual(expected, actual); Assert::AreEqual(expected, actual);
} }
TEST_METHOD(GetCurrentMonitorZoneSetEmpty) TEST_METHOD (GetZoneHighlightColor)
{ {
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor()); const auto expected = RGB(171, 175, 238);
Assert::IsNull(actual); 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) auto config = serializedPowerToySettings(settings);
{ m_settings->SetConfig(config.c_str());
const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr);
Assert::IsNull(actual); 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{}; HINSTANCE m_hInst{};
std::wstring m_settingsLocation = L"FancyZonesUnitTests"; 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_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"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"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_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_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); 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) 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{}; m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
input.vkCode = code; m_settings = MakeFancyZonesSettings(m_hInst, m_settingsLocation.c_str());
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); 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)
tagKBDLLHOOKSTRUCT input{}; {
input.vkCode = VK_LEFT; sendKeyboardInput(VK_SHIFT, true);
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); sendKeyboardInput(VK_LWIN, true);
} sendKeyboardInput(VK_CONTROL, true);
{ auto settingsFolder = PTSettingsHelper::get_module_save_folder_location(m_settingsLocation);
tagKBDLLHOOKSTRUCT input{}; const auto settingsFile = settingsFolder + L"\\settings.json";
input.vkCode = VK_RIGHT; std::filesystem::remove(settingsFile);
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); std::filesystem::remove(settingsFolder);
} }
}
TEST_METHOD(OnKeyDownShiftPressed) TEST_METHOD (OnKeyDownNothingPressed)
{ {
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{};
tagKBDLLHOOKSTRUCT input{}; input.vkCode = VK_LEFT;
input.vkCode = code; Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); }
}
{ {
tagKBDLLHOOKSTRUCT input{}; tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT; input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
} }
}
{ TEST_METHOD (OnKeyDownShiftPressed)
tagKBDLLHOOKSTRUCT input{}; {
input.vkCode = VK_RIGHT; sendKeyboardInput(VK_SHIFT);
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD(OnKeyDownWinPressed) for (DWORD code = '0'; code <= '9'; code++)
{ {
sendKeyboardInput(VK_LWIN); tagKBDLLHOOKSTRUCT input{};
input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++) {
{ tagKBDLLHOOKSTRUCT input{};
tagKBDLLHOOKSTRUCT input{}; input.vkCode = VK_LEFT;
input.vkCode = code; Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); }
}
{ {
tagKBDLLHOOKSTRUCT input{}; tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT; input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
} }
}
{ TEST_METHOD (OnKeyDownWinPressed)
tagKBDLLHOOKSTRUCT input{}; {
input.vkCode = VK_RIGHT; sendKeyboardInput(VK_LWIN);
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
}
TEST_METHOD(OnKeyDownWinShiftPressed) for (DWORD code = '0'; code <= '9'; code++)
{ {
sendKeyboardInput(VK_LWIN); tagKBDLLHOOKSTRUCT input{};
sendKeyboardInput(VK_SHIFT); input.vkCode = code;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
for (DWORD code = '0'; code <= '9'; code++) {
{ tagKBDLLHOOKSTRUCT input{};
tagKBDLLHOOKSTRUCT input{}; input.vkCode = VK_LEFT;
input.vkCode = code; Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); }
}
{ {
tagKBDLLHOOKSTRUCT input{}; tagKBDLLHOOKSTRUCT input{};
input.vkCode = VK_LEFT; input.vkCode = VK_RIGHT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
} }
}
{ TEST_METHOD (OnKeyDownWinShiftPressed)
tagKBDLLHOOKSTRUCT input{}; {
input.vkCode = VK_RIGHT; sendKeyboardInput(VK_LWIN);
Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); sendKeyboardInput(VK_SHIFT);
}
}
TEST_METHOD(OnKeyDownWinCtrlPressed) for (DWORD code = '0'; code <= '9'; code++)
{ {
sendKeyboardInput(VK_LWIN); tagKBDLLHOOKSTRUCT input{};
sendKeyboardInput(VK_CONTROL); 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++) const Settings settings{
{ .overrideSnapHotkeys = false,
tagKBDLLHOOKSTRUCT input{}; };
input.vkCode = code;
Assert::IsTrue(m_fzCallback->OnKeyDown(&input));
}
{ auto config = serializedPowerToySettings(settings);
tagKBDLLHOOKSTRUCT input{}; m_settings->SetConfig(config.c_str());
input.vkCode = VK_LEFT;
Assert::IsFalse(m_fzCallback->OnKeyDown(&input));
}
{ for (DWORD code = '0'; code <= '9'; code++)
tagKBDLLHOOKSTRUCT input{}; {
input.vkCode = VK_RIGHT; tagKBDLLHOOKSTRUCT input{};
Assert::IsFalse(m_fzCallback->OnKeyDown(&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 Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental> <LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental> <LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\</OutDir>
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\</IntDir>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>

View File

@@ -1,32 +1,235 @@
#include "pch.h" #include "pch.h"
#include "Util.h"
#include "lib\util.h" #include "lib\util.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests namespace FancyZonesUnitTests
{ {
TEST_CLASS(UtilUnitTests){ void TestMonitorSetPermutations(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
public: {
TEST_METHOD(TestParseDeviceId){ auto monitorInfoPermutation = monitorInfo;
// 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} do {
// Example output: DELA026#5&10a58c63&0&UID16777488 auto monitorInfoCopy = monitorInfoPermutation;
PCWSTR input = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"; OrderMonitors(monitorInfoCopy);
wchar_t output[256]{}; CustomAssert::AreEqual(monitorInfo, monitorInfoCopy);
ParseDeviceId(input, output, ARRAYSIZE(output)); } while (std::next_permutation(monitorInfoPermutation.begin(), monitorInfoPermutation.end(), [](auto x, auto y) { return x.first < y.first; }));
Assert::AreEqual(0, wcscmp(output, L"DELA026#5&10a58c63&0&UID16777488")); }
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(); 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); 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 namespace Mocks
@@ -43,3 +52,9 @@ namespace Mocks
HWND WindowCreate(HINSTANCE hInst); 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