Merge remote-tracking branch 'origin/main' into dev/snickler/net8-upgrade

This commit is contained in:
Jeremy Sinclair 2023-08-16 12:50:18 -04:00
commit c05270aafd
139 changed files with 4272 additions and 1267 deletions

View File

@ -50,6 +50,7 @@ body:
- Always on Top
- Awake
- ColorPicker
- Crop and Lock
- FancyZones
- FancyZones Editor
- File Locksmith

View File

@ -24,6 +24,7 @@ body:
- Always on Top
- Awake
- ColorPicker
- Crop and Lock
- FancyZones
- FancyZones Editor
- File Locksmith

View File

@ -15,6 +15,8 @@ edwinzap
Essey
Garside
Gershaft
Gokce
Guo
hallatore
Harmath
Hemmerlein
@ -22,10 +24,13 @@ Huynh
Jaswal
jefflord
Kamra
Kantarci
Karthick
kevinguo
Krigun
Luecking
Mahalingam
Mikhayelyan
mshtang
Myrvold
naveensrinivasan
@ -33,6 +38,7 @@ nVidia
Ponten
Pooja
robmen
robmikh
Schoen
skycommand
snickler

View File

@ -674,6 +674,7 @@ hcblack
HCERTSTORE
HCRYPTHASH
HCRYPTPROV
hcursor
hcwhite
hdc
hdrop
@ -742,7 +743,7 @@ hstring
hsv
htcfreek
HTCLIENT
HTHUMBNAIL
hthumbnail
HTOUCHINPUT
HTTRANSPARENT
HVal
@ -1529,6 +1530,7 @@ RECTDESTINATION
RECTL
rectp
rects
RECTSOURCE
redirectedfrom
Redist
redistributable
@ -1566,6 +1568,9 @@ Removelnk
renamable
RENAMEONCOLLISION
Renamer
reparent
reparented
reparenting
reparse
reportbug
requery
@ -1998,6 +2003,7 @@ unregistering
unremapped
unsubscribe
unvirtualized
unwide
UOffset
UOI
Updatelayout

View File

@ -115,12 +115,14 @@ TestCase\("[^"]+"
\\Registry
\\registry
\\reinstall
\\release
\\Resize
\\resource
\\Resources
\\restart
\\restore
\\result
\\robmikh
\\rotating
\\runner
\\runtimes

View File

@ -33,6 +33,9 @@
"PowerToys.ColorPickerUI.dll",
"PowerToys.ColorPickerUI.exe",
"PowerToys.CropAndLockModuleInterface.dll",
"PowerToys.CropAndLock.exe",
"PowerToys.PowerOCRModuleInterface.dll",
"PowerToys.PowerOCR.dll",
"PowerToys.PowerOCR.exe",

View File

@ -1,7 +1,7 @@
parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: '-m'
additionalBuildArguments: '/p:RestorePackagesConfig=true -m'
jobs:
- job: Build${{ parameters.platform }}${{ parameters.configuration }}

View File

@ -1,7 +1,7 @@
parameters:
configuration: 'Release'
platform: ''
additionalBuildArguments: '-m'
additionalBuildArguments: '/p:RestorePackagesConfig=true -m'
jobs:
- job: Build${{ parameters.platform }}${{ parameters.configuration }}

View File

@ -55,21 +55,9 @@ steps:
packageType: sdk
version: '7.x'
- task: NuGetToolInstaller@1
displayName: Ensure NuGet Installer
- task: VisualStudioTestPlatformInstaller@1
displayName: Ensure VSTest Platform
- task: NuGetCommand@2
displayName: Restore NuGet packages for PowerToys.sln
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: PowerToys.sln
restoreDirectory: '$(Build.SourcesDirectory)\packages'
- task: VSBuild@1
displayName: 'Build PowerToys.sln'
inputs:
@ -77,18 +65,9 @@ steps:
vsVersion: 17.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: ${{ parameters.additionalBuildArguments }}
msbuildArgs: -restore ${{ parameters.additionalBuildArguments }}
maximumCpuCount: true
- task: NuGetCommand@2
displayName: Restore NuGet packages for BugReportTool.sln
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: tools\BugReportTool\BugReportTool.sln
restoreDirectory: '$(Build.SourcesDirectory)\tools\BugReportTool\packages'
- task: VSBuild@1
displayName: 'Build BugReportTool.sln'
inputs:
@ -96,18 +75,9 @@ steps:
vsVersion: 17.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: ${{ parameters.additionalBuildArguments }}
msbuildArgs: -restore ${{ parameters.additionalBuildArguments }}
maximumCpuCount: true
- task: NuGetCommand@2
displayName: Restore NuGet packages for WebcamReportTool.sln
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: tools\WebcamReportTool\WebcamReportTool.sln
restoreDirectory: '$(Build.SourcesDirectory)\tools\WebcamReportTool\packages'
- task: VSBuild@1
displayName: 'Build WebcamReportTool.sln'
inputs:
@ -115,18 +85,9 @@ steps:
vsVersion: 17.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: ${{ parameters.additionalBuildArguments }}
msbuildArgs: -restore ${{ parameters.additionalBuildArguments }}
maximumCpuCount: true
- task: NuGetCommand@2
displayName: Restore NuGet packages for StylesReportTool.sln
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: tools\StylesReportTool\StylesReportTool.sln
restoreDirectory: '$(Build.SourcesDirectory)\tools\StylesReportTool\packages'
- task: VSBuild@1
displayName: 'Build StylesReportTool.sln'
inputs:
@ -134,18 +95,9 @@ steps:
vsVersion: 17.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: ${{ parameters.additionalBuildArguments }}
msbuildArgs: -restore ${{ parameters.additionalBuildArguments }}
maximumCpuCount: true
- task: NuGetCommand@2
displayName: Restore NuGet packages for PowerToysSetup.sln
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: installer\PowerToysSetup.sln
restoreDirectory: '$(Build.SourcesDirectory)\installer\packages'
- task: PowerShell@2
displayName: Download and install WiX 3.14 development build
inputs:
@ -159,7 +111,7 @@ steps:
vsVersion: 17.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: /t:PowerToysInstaller ${{ parameters.additionalBuildArguments }}
msbuildArgs: /t:PowerToysInstaller -restore ${{ parameters.additionalBuildArguments }}
maximumCpuCount: true
- task: VSBuild@1
@ -180,15 +132,6 @@ steps:
script: git clean -xfd -e *exe -- .\installer\
pwsh: true
- task: NuGetCommand@2
displayName: Restore NuGet packages for PowerToysSetup.sln
inputs:
command: restore
feedsToUse: config
configPath: NuGet.config
restoreSolution: installer\PowerToysSetup.sln
restoreDirectory: '$(Build.SourcesDirectory)\installer\packages'
- task: VSBuild@1
displayName: 'Build PowerToys per-user MSI'
inputs:
@ -196,7 +139,7 @@ steps:
vsVersion: 17.0
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
msbuildArgs: /t:PowerToysInstaller ${{ parameters.additionalBuildArguments }} /p:PerUser=true
msbuildArgs: /t:PowerToysInstaller -restore ${{ parameters.additionalBuildArguments }} /p:PerUser=true
maximumCpuCount: true
- task: VSBuild@1

View File

@ -18,7 +18,7 @@ steps:
inputs:
solution: "**/installer/PowerToysSetup.sln"
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog /t:PowerToysSetupCustomActions /p:RunBuildEvents=true /p:PerUser=${{parameters.perUserArg}}
msbuildArgs: -restore /p:RestorePackagesConfig=true /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog /t:PowerToysSetupCustomActions /p:RunBuildEvents=true /p:PerUser=${{parameters.perUserArg}}
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true

View File

@ -71,7 +71,7 @@ jobs:
packageType: sdk
version: '7.x'
- task: NuGetAuthenticate@0
- task: NuGetAuthenticate@1
- task: NuGetToolInstaller@1
displayName: Use NuGet Installer latest
@ -82,14 +82,6 @@ jobs:
# - Webcam report tool
# - Installer
# - Bootstrapper Installer
- task: NuGetCommand@2
displayName: NuGet restore solutions dependencies
inputs:
command: restore
restoreSolution: '**/*.sln'
selectOrConfig: config
nugetConfigPath: .pipelines/release-nuget.config
- task: PowerShell@2
displayName: Download and install WiX 3.14 development build
inputs:
@ -136,7 +128,7 @@ jobs:
inputs:
solution: '**\PowerToys.sln'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
msbuildArgs: -restore /p:RestorePackagesConfig=true /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true
@ -147,7 +139,7 @@ jobs:
inputs:
solution: '**/tools/BugReportTool/BugReportTool.sln'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
msbuildArgs: -restore /p:RestorePackagesConfig=true /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true
@ -158,7 +150,7 @@ jobs:
inputs:
solution: '**/tools/WebcamReportTool/WebcamReportTool.sln'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
msbuildArgs: -restore /p:RestorePackagesConfig=true /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true
@ -169,7 +161,7 @@ jobs:
inputs:
solution: '**/tools/StylesReportTool/StylesReportTool.sln'
vsVersion: 17.0
msbuildArgs: /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
msbuildArgs: -restore /p:RestorePackagesConfig=true /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:CIBuild=true /bl:$(Build.SourcesDirectory)\msbuild.binlog
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
clean: true
@ -397,14 +389,6 @@ jobs:
script: git clean -xfd -e *exe -- .\installer\
pwsh: true
- task: NuGetCommand@2
displayName: NuGet restore solutions dependencies
inputs:
command: restore
restoreSolution: 'installer/*.sln'
selectOrConfig: config
nugetConfigPath: .pipelines/release-nuget.config
- template: installer-steps.yml
parameters:
versionNumber: ${{ parameters.versionNumber }}

View File

@ -107,6 +107,10 @@ Randy contributed Registry Preview and some very early conversations about keybo
Find My Mouse is based on Raymond Chen's SuperSonar.
### [@robmikh](https://github.com/robmikh) - Robert Mikhayelyan
Crop And Lock is based on the original work of Robert Mikhayelyan, with Program Manager support from [@kevinguo305](https://github.com/kevinguo305) - Kevin Guo.
### Microsoft InVEST team
This amazing team helped PowerToys develop PowerToys Run and Keyboard manager as well as update our Settings to v2. @alekhyareddy28, @arjunbalgovind, @jyuwono @laviusmotileng-ms, @ryanbodrug-microsoft, @saahmedm, @somil55, @traies, @udit3333
@ -152,8 +156,7 @@ Other contributors:
- [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon - Product Manager
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev lead
- [@donlaci](https://github.com/donlaci) - Laszlo Nemeth - Dev
- [@gokcekantarci](https://github.com/gokcekantarci) - Gokce Kantarci - Dev
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@sosssego](https://github.com/sosssego) - Frederico Moron - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@taras-janea](https://github.com/taras-janea) - Taras Sich - Dev
- [@yuyoyuppe](https://github.com/yuyoyuppe) - Andrey Nekrasov - Dev

View File

@ -530,6 +530,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests\Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests.csproj", "{90F9FA90-2C20-4004-96E6-F3B78151F5A5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CropAndLock", "CropAndLock", "{3B227528-4BA6-4CAF-B44A-A10C78A64849}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLock", "src\modules\CropAndLock\CropAndLock\CropAndLock.vcxproj", "{F5E1146E-B7B3-4E11-85FD-270A500BD78C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLockModuleInterface", "src\modules\CropAndLock\CropAndLockModuleInterface\CropAndLockModuleInterface.vcxproj", "{3157FA75-86CF-4EE2-8F62-C43F776493C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@ -2276,6 +2282,30 @@ Global
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x64.Build.0 = Release|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x86.ActiveCfg = Release|x64
{90F9FA90-2C20-4004-96E6-F3B78151F5A5}.Release|x86.Build.0 = Release|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|ARM64.Build.0 = Debug|ARM64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|x64.ActiveCfg = Debug|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|x64.Build.0 = Debug|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|x86.ActiveCfg = Debug|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Debug|x86.Build.0 = Debug|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|ARM64.ActiveCfg = Release|ARM64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|ARM64.Build.0 = Release|ARM64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|x64.ActiveCfg = Release|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|x64.Build.0 = Release|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|x86.ActiveCfg = Release|x64
{F5E1146E-B7B3-4E11-85FD-270A500BD78C}.Release|x86.Build.0 = Release|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|ARM64.ActiveCfg = Debug|ARM64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|ARM64.Build.0 = Debug|ARM64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|x64.ActiveCfg = Debug|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|x64.Build.0 = Debug|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|x86.ActiveCfg = Debug|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Debug|x86.Build.0 = Debug|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|ARM64.ActiveCfg = Release|ARM64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|ARM64.Build.0 = Release|ARM64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x64.ActiveCfg = Release|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x64.Build.0 = Release|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x86.ActiveCfg = Release|x64
{3157FA75-86CF-4EE2-8F62-C43F776493C6}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2467,6 +2497,9 @@ Global
{500DED3E-CFB5-4ED5-ACC6-02B3D6DC336D} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{D095BE44-1F2E-463E-A494-121892A75EA2} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{90F9FA90-2C20-4004-96E6-F3B78151F5A5} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{3B227528-4BA6-4CAF-B44A-A10C78A64849} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{F5E1146E-B7B3-4E11-85FD-270A500BD78C} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
{3157FA75-86CF-4EE2-8F62-C43F776493C6} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@ -18,12 +18,13 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
| | Current utilities: | |
|--------------|--------------------|--------------|
| [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) |
| [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) |
| [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) |
| [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) | [Peek](https://aka.ms/PowerToysOverview_Peek) |
| [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) |
| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) |
| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) |
| [Crop And Lock](https://aka.ms/PowerToysOverview_CropAndLock) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) |
| [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) |
| [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Mouse Without Borders](https://aka.ms/PowerToysOverview_MouseWithoutBorders) |
| [Peek](https://aka.ms/PowerToysOverview_Peek) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) |
| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Registry Preview](https://aka.ms/PowerToysOverview_RegistryPreview) |
| [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) |
| [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) |
## Installing and running Microsoft PowerToys

View File

@ -1,12 +1,12 @@
# Keyboard Manager UI
## Table of Contents:
1. [C++ XAML Islands](#c---xaml-islands)
1. [C++ XAML Islands](#c-xaml-islands)
1. [Debugging exceptions in XAML Islands](#debugging-exceptions-in-xaml-islands)
2. [Build times](#build-times)
3. [Setting custom backgrounds for Xaml Controls using brushes](#setting-custom-backgrounds-for-xaml-controls-using-brushes)
2. [UI Structure](#ui-structure)
3. [EditKeyboardWindow/EditShortcutsWindow](#editkeyboardwindow-editshortcutswindow)
3. [EditKeyboardWindow / EditShortcutsWindow](#editkeyboardwindow--editshortcutswindow)
1. [OK and Cancel button](#ok-and-cancel-button)
2. [Delete button](#delete-button)
3. [Handling common modifiers in EditKeyboardWindow](#handling-common-modifiers-in-editkeyboardwindow)
@ -51,7 +51,7 @@ Since ComboBoxes are added dynamically, handlers have been added which [update t
When the `EditKeyboardWindow`/`EditShortcutsWindow` is created, [we iterate through the remappings](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L254-L262) stored in `KeyboardManagerState` and add rows to the UI Grid. For both the windows we have `static` buffers [`singleKeyRemapBuffer`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.h#L39-L40) and [`shortcutRemapBuffer`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/ShortcutControl.h#L42-L43) which store the corresponding key/shortcuts as per the selections in the UI if they are valid with no warnings.
## EditKeyboardWindow/EditShortcutsWindow
## EditKeyboardWindow / EditShortcutsWindow
### OK and Cancel button
[On pressing the OK button](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L66-L89) in `EditKeyboardWindow`, first the [`CheckIfRemappingsAreValid` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L10-L44) is executed which performs basic validity checks on the current remappings in the remap buffer (`static SingleKeyRemapControl::singleKeyRemapBuffer`), such as if there are no NULL columns and none of the source keys are repeated. All other validity checks are assumed to happen while the user adds the remapping. If this is found to be invalid a ContentDialog is displayed which shows that some remappings are invalid and if the user proceeds only the valid ones will be applied. If it is valid [`GetOrphanedKeys`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L46-L75) is executed which checks if any keys are orphaned (i.e. the key has been remapped and no other key has been remapped to it, so there is no way to send that key code), and a dialog is shown for notifying the user with a list of orphaned keys. After this the settings are [applied by adding it to the `KeyboardManagerState.singleKeyReMap` member](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L102-L164) and they are saved to the JSON file. `EditShortcutsWindow` differs slightly from this, as there is no orphaned keys check, and [on pressing OK](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp#L32-L47) both the global and app-specific shortcuts are validated and [updated](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L166-L223).

View File

@ -1005,7 +1005,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 27> processesToTerminate = {
std::array<std::wstring_view, 28> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.Awake.exe",
@ -1032,6 +1032,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.MouseWithoutBorders.exe",
L"PowerToys.MouseWithoutBordersHelper.exe",
L"PowerToys.MouseWithoutBordersService.exe",
L"PowerToys.CropAndLock.exe",
L"PowerToys.exe",
};

View File

@ -73,6 +73,9 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
return std::nullopt;
}
// Cleanup old updates before downloading the latest
updating::cleanup_updates();
auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get();
if (!downloaded_installer)
{

View File

@ -16,6 +16,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredColorPickerEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredCropAndLockEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredCropAndLockEnabledValue());
}
GpoRuleConfigured GPOWrapper::GetConfiguredFancyZonesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredFancyZonesEnabledValue());

View File

@ -10,6 +10,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetConfiguredAlwaysOnTopEnabledValue();
static GpoRuleConfigured GetConfiguredAwakeEnabledValue();
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();

View File

@ -14,6 +14,7 @@ namespace PowerToys
static GpoRuleConfigured GetConfiguredAlwaysOnTopEnabledValue();
static GpoRuleConfigured GetConfiguredAwakeEnabledValue();
static GpoRuleConfigured GetConfiguredColorPickerEnabledValue();
static GpoRuleConfigured GetConfiguredCropAndLockEnabledValue();
static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue();
static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue();
static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue();

View File

@ -74,6 +74,11 @@ namespace CommonSharedConstants
// Path to the event used to show Peek
const wchar_t SHOW_PEEK_SHARED_EVENT[] = L"Local\\ShowPeekEvent";
// Path to the events used by CropAndLock
const wchar_t CROP_AND_LOCK_REPARENT_EVENT[] = L"Local\\PowerToysCropAndLockReparentEvent-6060860a-76a1-44e8-8d0e-6355785e9c36";
const wchar_t CROP_AND_LOCK_THUMBNAIL_EVENT[] = L"Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434";
const wchar_t CROP_AND_LOCK_EXIT_EVENT[] = L"Local\\PowerToysCropAndLockExitEvent-d995d409-7b70-482b-bad6-e7c8666f375a";
// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}

View File

@ -60,6 +60,7 @@ struct LogSettings
inline const static std::string hostsLoggerName = "hosts";
inline const static std::wstring hostsLogPath = L"Logs\\hosts-log.txt";
inline const static std::string registryPreviewLoggerName = "registrypreview";
inline const static std::string cropAndLockLoggerName = "crop-and-lock";
inline const static std::wstring registryPreviewLogPath = L"Logs\\registryPreview-log.txt";
inline const static int retention = 30;
std::wstring logLevel;

View File

@ -1,6 +1,7 @@
#include "pch.h"
#include <common/utils/HttpClient.h>
#include <common/utils/string_utils.h>
#include <common/version/version.h>
#include <common/version/helper.h>
@ -185,4 +186,48 @@ namespace updating
co_return download_success ? installer_download_path : std::nullopt;
}
void cleanup_updates()
{
auto update_dir = updating::get_pending_updates_path();
if (std::filesystem::exists(update_dir))
{
// Msi and exe files
for (const auto& entry : std::filesystem::directory_iterator(update_dir))
{
auto entryPath = entry.path().wstring();
std::transform(entryPath.begin(), entryPath.end(), entryPath.begin(), ::towlower);
if (entryPath.ends_with(L".msi") || entryPath.ends_with(L".exe"))
{
std::error_code err;
std::filesystem::remove(entry, err);
if (err.value())
{
Logger::warn("Failed to delete installer file {}. {}", entry.path().string(), err.message());
}
}
}
}
// Log files
auto rootPath{ PTSettingsHelper::get_root_save_folder_location() };
auto currentVersion = left_trim<wchar_t>(get_product_version(), L"v");
if (std::filesystem::exists(rootPath))
{
for (const auto& entry : std::filesystem::directory_iterator(rootPath))
{
auto entryPath = entry.path().wstring();
std::transform(entryPath.begin(), entryPath.end(), entryPath.begin(), ::towlower);
if (entry.is_regular_file() && entryPath.ends_with(L".log") && entryPath.find(currentVersion) == std::string::npos)
{
std::error_code err;
std::filesystem::remove(entry, err);
if (err.value())
{
Logger::warn("Failed to delete log file {}. {}", entry.path().string(), err.message());
}
}
}
}
}
}

View File

@ -28,6 +28,7 @@ namespace updating
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version);
std::filesystem::path get_pending_updates_path();
std::future<nonstd::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease = false);
void cleanup_updates();
// non-localized
constexpr inline std::wstring_view INSTALLER_FILENAME_PATTERN = L"powertoyssetup";

View File

@ -22,6 +22,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_CONFIGURE_ENABLED_ALWAYS_ON_TOP = L"ConfigureEnabledUtilityAlwaysOnTop";
const std::wstring POLICY_CONFIGURE_ENABLED_AWAKE = L"ConfigureEnabledUtilityAwake";
const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker";
const std::wstring POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK = L"ConfigureEnabledUtilityCropAndLock";
const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones";
const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith";
const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview";
@ -129,6 +130,11 @@ namespace powertoys_gpo {
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_COLOR_PICKER);
}
inline gpo_rule_configured_t getConfiguredCropAndLockEnabledValue()
{
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_CROP_AND_LOCK);
}
inline gpo_rule_configured_t getConfiguredFancyZonesEnabledValue()
{
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_FANCYZONES);

View File

@ -54,3 +54,12 @@ inline void replace_chars(std::basic_string<CharT>& s,
std::replace(begin(s), end(s), c, replacement_char);
}
}
inline std::string unwide(const std::wstring& wide)
{
std::string result(wide.length(), 0);
std::transform(begin(wide), end(wide), result.begin(), [](const wchar_t c) {
return static_cast<char>(c);
});
return result;
}

View File

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.2" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.3" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyNamespaces>
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
</policyNamespaces>
<resources minRequiredRevision="1.2"/><!-- Last changed with PowerToys v0.70.0 -->
<resources minRequiredRevision="1.3"/><!-- Last changed with PowerToys v0.73.0 -->
<supportedOn>
<definitions>
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_68_0" displayName="$(string.SUPPORTED_POWERTOYS_0_68_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_69_0" displayName="$(string.SUPPORTED_POWERTOYS_0_69_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_70_0" displayName="$(string.SUPPORTED_POWERTOYS_0_70_0)"/>
<definition name="SUPPORTED_POWERTOYS_0_73_0" displayName="$(string.SUPPORTED_POWERTOYS_0_73_0)"/>
</definitions>
</supportedOn>
<categories>
@ -51,6 +52,16 @@
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityCropAndLock" class="Both" displayName="$(string.ConfigureEnabledUtilityCropAndLock)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityCropAndLock">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_73_0" />
<enabledValue>
<decimal value="1" />
</enabledValue>
<disabledValue>
<decimal value="0" />
</disabledValue>
</policy>
<policy name="ConfigureEnabledUtilityFancyZones" class="Both" displayName="$(string.ConfigureEnabledUtilityFancyZones)" explainText="$(string.ConfigureEnabledUtilityDescription)" key="Software\Policies\PowerToys" valueName="ConfigureEnabledUtilityFancyZones">
<parentCategory ref="PowerToys" />
<supportedOn ref="SUPPORTED_POWERTOYS_0_64_0" />

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.2" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.3" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
<displayName>PowerToys</displayName>
<description>PowerToys</description>
<resources>
@ -13,6 +13,7 @@
<string id="SUPPORTED_POWERTOYS_0_68_0">PowerToys version 0.68.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_69_0">PowerToys version 0.69.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_70_0">PowerToys version 0.70.0 or later</string>
<string id="SUPPORTED_POWERTOYS_0_73_0">PowerToys version 0.73.0 or later</string>
<string id="ConfigureEnabledUtilityDescription">This policy configures the enabled state for a PowerToys utility.
@ -67,6 +68,7 @@ If this setting is disabled, experimentation is not allowed.
<string id="ConfigureEnabledUtilityAlwaysOnTop">Always On Top: Configure enabled state</string>
<string id="ConfigureEnabledUtilityAwake">Awake: Configure enabled state</string>
<string id="ConfigureEnabledUtilityColorPicker">Color Picker: Configure enabled state</string>
<string id="ConfigureEnabledUtilityCropAndLock">Crop And Lock: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFancyZones">FancyZones: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileLocksmith">File Locksmith: Configure enabled state</string>
<string id="ConfigureEnabledUtilityFileExplorerSVGPreview">SVG file preview: Configure enabled state</string>

View File

@ -0,0 +1,56 @@
#include "pch.h"
#include "ChildWindow.h"
namespace util
{
using namespace robmikh::common::desktop;
using namespace robmikh::common::desktop::controls;
}
const std::wstring ChildWindow::ClassName = L"CropAndLock.ChildWindow";
std::once_flag ChildWindowClassRegistration;
void ChildWindow::RegisterWindowClass()
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcex.lpszClassName = ClassName.c_str();
wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION);
winrt::check_bool(RegisterClassExW(&wcex));
}
ChildWindow::ChildWindow(int width, int height, HWND parent)
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
std::call_once(ChildWindowClassRegistration, []() { RegisterWindowClass(); });
auto exStyle = 0;
auto style = WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), L"", style,
0, 0, width, height, parent, nullptr, instance, this));
WINRT_ASSERT(m_window);
ShowWindow(m_window, SW_SHOW);
UpdateWindow(m_window);
}
LRESULT ChildWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_DESTROY:
break;
default:
return base_type::MessageHandler(message, wparam, lparam);
}
return 0;
}

View File

@ -0,0 +1,11 @@
#pragma once
#include <robmikh.common/DesktopWindow.h>
struct ChildWindow : robmikh::common::desktop::DesktopWindow<ChildWindow>
{
static const std::wstring ClassName;
ChildWindow(int width, int height, HWND parent);
LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
private:
static void RegisterWindowClass();
};

View File

@ -0,0 +1,105 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "icon1.ico"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
<MinimalCoreWin>true</MinimalCoreWin>
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{f5e1146e-b7b3-4e11-85fd-270a500bd78c}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>CropAndLock</RootNamespace>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.20348.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '17.0'">v143</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '18.0'">v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<TargetName>PowerToys.$(MSBuildProjectName)</TargetName>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>_CONSOLE;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
<AdditionalIncludeDirectories>$(SolutionDir)src\common\Telemetry;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<AdditionalDependencies>shell32.lib;dwmapi.lib;DbgHelp.lib;gdi32.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">MultiThreadedDebug</RuntimeLibrary>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">MultiThreaded</RuntimeLibrary>
<RuntimeLibrary Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ChildWindow.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="ReparentCropAndLockWindow.cpp" />
<ClCompile Include="ThumbnailCropAndLockWindow.cpp" />
<ClCompile Include="OverlayWindow.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ChildWindow.h" />
<ClInclude Include="CropAndLockWindow.h" />
<ClInclude Include="DisplaysUtil.h" />
<ClInclude Include="ModuleConstants.h" />
<ClInclude Include="ReparentCropAndLockWindow.h" />
<ClInclude Include="ThumbnailCropAndLockWindow.h" />
<ClInclude Include="SettingsWindow.h" />
<ClInclude Include="OverlayWindow.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="ThumbnailUtil.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="WindowRectUtil.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="CropAndLock.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="icon1.ico" />
</ItemGroup>
<ItemGroup>
<Manifest Include="app.manifest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets" Condition="Exists('..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets')" />
</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.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets'))" />
</Target>
</Project>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="PropertySheet.props" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="ThumbnailCropAndLockWindow.cpp" />
<ClCompile Include="OverlayWindow.cpp" />
<ClCompile Include="ReparentCropAndLockWindow.cpp" />
<ClCompile Include="ChildWindow.cpp" />
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ThumbnailCropAndLockWindow.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="SettingsWindow.h" />
<ClInclude Include="OverlayWindow.h" />
<ClInclude Include="DisplaysUtil.h" />
<ClInclude Include="ThumbnailUtil.h" />
<ClInclude Include="WindowRectUtil.h" />
<ClInclude Include="CropAndLockWindow.h" />
<ClInclude Include="ReparentCropAndLockWindow.h" />
<ClInclude Include="ChildWindow.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="ModuleConstants.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="CropAndLock.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="icon1.ico" />
</ItemGroup>
<ItemGroup>
<Manifest Include="app.manifest" />
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,10 @@
#pragma once
struct CropAndLockWindow
{
virtual ~CropAndLockWindow() {}
virtual HWND Handle() = 0;
virtual void CropAndLock(HWND windowToCrop, RECT cropRect) = 0;
virtual void OnClosed(std::function<void(HWND)> callback) = 0;
};

View File

@ -0,0 +1,25 @@
#pragma once
inline RECT ComputeAllDisplaysUnion(std::vector<robmikh::common::desktop::DisplayInfo> const& infos)
{
RECT result = {};
result.left = LONG_MAX;
result.top = LONG_MAX;
result.right = LONG_MIN;
result.bottom = LONG_MIN;
for (auto&& info : infos)
{
auto rect = info.Rect();
result.left = std::min(result.left, rect.left);
result.top = std::min(result.top, rect.top);
result.right = std::max(result.right, rect.right);
result.bottom = std::max(result.bottom, rect.bottom);
}
return result;
}
inline RECT ComputeAllDisplaysUnion()
{
auto infos = robmikh::common::desktop::DisplayInfo::GetAllDisplays();
return ComputeAllDisplaysUnion(infos);
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace NonLocalizable
{
const inline wchar_t ModuleKey[] = L"CropAndLock";
}

View File

@ -0,0 +1,356 @@
#include "pch.h"
#include "OverlayWindow.h"
namespace winrt
{
using namespace Windows::UI;
using namespace Windows::UI::Composition;
}
namespace util
{
using namespace robmikh::common::desktop;
}
const std::wstring OverlayWindow::ClassName = L"CropAndLock.OverlayWindow";
const float OverlayWindow::BorderThickness = 5;
std::once_flag OverlayWindowClassRegistration;
bool IsPointWithinRect(POINT const& point, RECT const& rect)
{
return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom;
}
void OverlayWindow::RegisterWindowClass()
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.lpszClassName = ClassName.c_str();
wcex.hIconSm = LoadIconW(instance, IDI_APPLICATION);
winrt::check_bool(RegisterClassExW(&wcex));
}
OverlayWindow::OverlayWindow(
winrt::Compositor const& compositor,
HWND windowToCrop,
std::function<void(HWND, RECT)> windowCropped)
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
std::call_once(OverlayWindowClassRegistration, []() { RegisterWindowClass(); });
auto exStyle = WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TOPMOST;
auto style = WS_POPUP;
// Get the union of all displays
auto displaysRect = ComputeAllDisplaysUnion();
// Create our window
winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), L"", style,
displaysRect.left, displaysRect.top, displaysRect.right - displaysRect.left, displaysRect.bottom - displaysRect.top, nullptr, nullptr, instance, this));
WINRT_ASSERT(m_window);
// Load cursors
m_standardCursor.reset(winrt::check_pointer(LoadCursorW(nullptr, IDC_ARROW)));
m_crosshairCursor.reset(winrt::check_pointer(LoadCursorW(nullptr, IDC_CROSS)));
m_cursorType = CursorType::Standard;
// Setup the visual tree
m_compositor = compositor;
m_target = CreateWindowTarget(m_compositor);
m_rootVisual = m_compositor.CreateContainerVisual();
m_shadeVisual = m_compositor.CreateSpriteVisual();
m_windowAreaVisual = m_compositor.CreateContainerVisual();
m_selectionVisual = m_compositor.CreateSpriteVisual();
m_target.Root(m_rootVisual);
auto children = m_rootVisual.Children();
children.InsertAtBottom(m_shadeVisual);
children.InsertAtTop(m_windowAreaVisual);
m_windowAreaVisual.Children().InsertAtTop(m_selectionVisual);
m_rootVisual.RelativeSizeAdjustment({ 1, 1 });
m_shadeBrush = m_compositor.CreateNineGridBrush();
m_shadeBrush.IsCenterHollow(true);
m_shadeBrush.Source(m_compositor.CreateColorBrush(winrt::Color{ 255, 0, 0, 0 }));
m_shadeVisual.Brush(m_shadeBrush);
m_shadeVisual.Opacity(0.6f);
m_shadeVisual.RelativeSizeAdjustment({ 1, 1 });
auto selectionBrush = m_compositor.CreateNineGridBrush();
selectionBrush.SetInsets(BorderThickness);
selectionBrush.IsCenterHollow(true);
selectionBrush.Source(m_compositor.CreateColorBrush(winrt::Color{ 255, 255, 0, 0 }));
m_selectionVisual.Brush(selectionBrush);
WINRT_VERIFY(windowToCrop != nullptr);
m_currentWindow = windowToCrop;
SetupOverlay();
m_windowCropped = windowCropped;
ShowWindow(m_window, SW_SHOW);
UpdateWindow(m_window);
SetForegroundWindow(m_window);
}
void OverlayWindow::SetupOverlay()
{
ResetCrop();
// Get the client bounds of the target window
auto windowBounds = ClientAreaInScreenSpace(m_currentWindow);
// Get the union of all displays
auto displaysRect = ComputeAllDisplaysUnion();
// Before we can use the window bounds, we need to
// shift the origin to the top-left most point.
m_currentWindowAreaBounds.left = windowBounds.left - displaysRect.left;
m_currentWindowAreaBounds.top = windowBounds.top - displaysRect.top;
m_currentWindowAreaBounds.right = m_currentWindowAreaBounds.left + (windowBounds.right - windowBounds.left);
m_currentWindowAreaBounds.bottom = m_currentWindowAreaBounds.top + (windowBounds.bottom - windowBounds.top);
auto windowLeft = static_cast<float>(m_currentWindowAreaBounds.left);
auto windowTop = static_cast<float>(m_currentWindowAreaBounds.top);
auto windowWidth = static_cast<float>(windowBounds.right - windowBounds.left);
auto windowHeight = static_cast<float>(windowBounds.bottom - windowBounds.top);
// Change the shade brush to match the window bounds
// We need to make sure the values are non-negative, as they are invalid insets. We
// can sometimes get negative values for the left and top when windows are maximized.
m_shadeBrush.LeftInset(std::max(windowLeft, 0.0f));
m_shadeBrush.TopInset(std::max(windowTop, 0.0f));
m_shadeBrush.RightInset(std::max(static_cast<float>(displaysRect.right - windowBounds.right), 0.0f));
m_shadeBrush.BottomInset(std::max(static_cast<float>(displaysRect.bottom - windowBounds.bottom), 0.0f));
// Change the window area visual to match the window bounds
m_windowAreaVisual.Offset({ windowLeft, windowTop, 0 });
m_windowAreaVisual.Size({ windowWidth, windowHeight });
// Reset the selection visual
m_selectionVisual.Offset({ 0, 0, 0 });
m_selectionVisual.Size({ 0, 0 });
}
LRESULT OverlayWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_DESTROY:
break;
case WM_SETCURSOR:
return OnSetCursor();
case WM_KEYUP:
{
auto key = static_cast<uint32_t>(wparam);
if (key == VK_ESCAPE)
{
DestroyWindow(m_window);
}
}
break;
case WM_LBUTTONDOWN:
{
auto xPos = GET_X_LPARAM(lparam);
auto yPos = GET_Y_LPARAM(lparam);
OnLeftButtonDown(xPos, yPos);
}
break;
case WM_LBUTTONUP:
{
auto xPos = GET_X_LPARAM(lparam);
auto yPos = GET_Y_LPARAM(lparam);
OnLeftButtonUp(xPos, yPos);
}
break;
case WM_MOUSEMOVE:
{
auto xPos = GET_X_LPARAM(lparam);
auto yPos = GET_Y_LPARAM(lparam);
OnMouseMove(xPos, yPos);
}
break;
default:
return base_type::MessageHandler(message, wparam, lparam);
}
return 0;
}
void OverlayWindow::ResetCrop()
{
m_cropStatus = CropStatus::None;
m_startPosition = {};
m_cropRect = {};
}
bool OverlayWindow::OnSetCursor()
{
switch (m_cursorType)
{
case CursorType::Standard:
SetCursor(m_standardCursor.get());
return true;
case CursorType::Crosshair:
SetCursor(m_crosshairCursor.get());
return true;
default:
return false;
}
}
void OverlayWindow::OnLeftButtonDown(int x, int y)
{
if (m_cropStatus == CropStatus::None)
{
if (!IsPointWithinRect({ x, y }, m_currentWindowAreaBounds))
{
DestroyWindow(m_window);
return;
}
m_cropStatus = CropStatus::Ongoing;
x -= m_currentWindowAreaBounds.left;
y -= m_currentWindowAreaBounds.top;
m_selectionVisual.Offset({ x - BorderThickness, y - BorderThickness, 0 });
m_startPosition = { x, y };
}
}
void OverlayWindow::OnLeftButtonUp(int x, int y)
{
if (m_cropStatus == CropStatus::Ongoing)
{
m_cropStatus = CropStatus::Completed;
m_cursorType = CursorType::Standard;
// For debugging, it's easier if the window doesn't block the screen after this point
ShowWindow(m_window, SW_HIDE);
if (x < m_currentWindowAreaBounds.left)
{
x = m_currentWindowAreaBounds.left;
}
else if (x > m_currentWindowAreaBounds.right)
{
x = m_currentWindowAreaBounds.right;
}
if (y < m_currentWindowAreaBounds.top)
{
y = m_currentWindowAreaBounds.top;
}
else if (y > m_currentWindowAreaBounds.bottom)
{
y = m_currentWindowAreaBounds.bottom;
}
x -= m_currentWindowAreaBounds.left;
y -= m_currentWindowAreaBounds.top;
// Compute our crop rect
if (x < m_startPosition.x)
{
m_cropRect.left = x;
m_cropRect.right = m_startPosition.x;
}
else
{
m_cropRect.left = m_startPosition.x;
m_cropRect.right = x;
}
if (y < m_startPosition.y)
{
m_cropRect.top = y;
m_cropRect.bottom = m_startPosition.y;
}
else
{
m_cropRect.top = m_startPosition.y;
m_cropRect.bottom = y;
}
// Exit if the rect is empty
if (m_cropRect.right - m_cropRect.left == 0 || m_cropRect.bottom - m_cropRect.top == 0)
{
DestroyWindow(m_window);
return;
}
// Fire the callback
if (m_windowCropped != nullptr)
{
m_windowCropped(m_currentWindow, m_cropRect);
}
DestroyWindow(m_window);
}
}
void OverlayWindow::OnMouseMove(int x, int y)
{
if (m_cropStatus == CropStatus::None)
{
if (IsPointWithinRect({ x, y }, m_currentWindowAreaBounds))
{
m_cursorType = CursorType::Crosshair;
}
else
{
m_cursorType = CursorType::Standard;
}
}
else if (m_cropStatus == CropStatus::Ongoing)
{
if (x < m_currentWindowAreaBounds.left)
{
x = m_currentWindowAreaBounds.left;
}
else if (x > m_currentWindowAreaBounds.right)
{
x = m_currentWindowAreaBounds.right;
}
if (y < m_currentWindowAreaBounds.top)
{
y = m_currentWindowAreaBounds.top;
}
else if (y > m_currentWindowAreaBounds.bottom)
{
y = m_currentWindowAreaBounds.bottom;
}
x -= m_currentWindowAreaBounds.left;
y -= m_currentWindowAreaBounds.top;
auto offset = m_selectionVisual.Offset();
auto size = m_selectionVisual.Size();
if (x < m_startPosition.x)
{
offset.x = x - BorderThickness;
size.x = (m_startPosition.x - x) + (2 * BorderThickness);
}
else
{
size.x = (x - m_startPosition.x) + (2 * BorderThickness);
}
if (y < m_startPosition.y)
{
offset.y = y - BorderThickness;
size.y = (m_startPosition.y - y) + (2 * BorderThickness);
}
else
{
size.y = (y - m_startPosition.y) + (2 * BorderThickness);
}
m_selectionVisual.Offset(offset);
m_selectionVisual.Size(size);
}
}

View File

@ -0,0 +1,58 @@
#pragma once
#include <robmikh.common/DesktopWindow.h>
struct OverlayWindow : robmikh::common::desktop::DesktopWindow<OverlayWindow>
{
static const std::wstring ClassName;
OverlayWindow(
winrt::Windows::UI::Composition::Compositor const& compositor,
HWND windowToCrop,
std::function<void(HWND, RECT)> windowCropped);
~OverlayWindow() { m_windowCropped = nullptr; DestroyWindow(m_window); }
LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
private:
enum class CursorType
{
Standard,
Crosshair,
};
enum class CropStatus
{
None,
Ongoing,
Completed,
};
static const float BorderThickness;
static void RegisterWindowClass();
void SetupOverlay();
void ResetCrop();
bool OnSetCursor();
void OnLeftButtonDown(int x, int y);
void OnLeftButtonUp(int x, int y);
void OnMouseMove(int x, int y);
private:
std::function<void(HWND, RECT)> m_windowCropped;
winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
winrt::Windows::UI::Composition::CompositionTarget m_target{ nullptr };
winrt::Windows::UI::Composition::ContainerVisual m_rootVisual{ nullptr };
winrt::Windows::UI::Composition::SpriteVisual m_shadeVisual{ nullptr };
winrt::Windows::UI::Composition::ContainerVisual m_windowAreaVisual{ nullptr };
winrt::Windows::UI::Composition::SpriteVisual m_selectionVisual{ nullptr };
winrt::Windows::UI::Composition::CompositionNineGridBrush m_shadeBrush{ nullptr };
HWND m_currentWindow = nullptr;
RECT m_currentWindowAreaBounds = {};
CropStatus m_cropStatus = CropStatus::None;
POINT m_startPosition = {};
RECT m_cropRect = {};
CursorType m_cursorType = CursorType::Standard;
wil::unique_hcursor m_standardCursor;
wil::unique_hcursor m_crosshairCursor;
};

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>

View File

@ -0,0 +1,160 @@
#include "pch.h"
#include "ReparentCropAndLockWindow.h"
const std::wstring ReparentCropAndLockWindow::ClassName = L"CropAndLock.ReparentCropAndLockWindow";
std::once_flag ReparentCropAndLockWindowClassRegistration;
void ReparentCropAndLockWindow::RegisterWindowClass()
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcex.lpszClassName = ClassName.c_str();
wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION);
winrt::check_bool(RegisterClassExW(&wcex));
}
ReparentCropAndLockWindow::ReparentCropAndLockWindow(std::wstring const& titleString, int width, int height)
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
std::call_once(ReparentCropAndLockWindowClassRegistration, []() { RegisterWindowClass(); });
auto exStyle = 0;
auto style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
style &= ~(WS_MAXIMIZEBOX | WS_THICKFRAME);
RECT rect = { 0, 0, width, height};
winrt::check_bool(AdjustWindowRectEx(&rect, style, false, exStyle));
auto adjustedWidth = rect.right - rect.left;
auto adjustedHeight = rect.bottom - rect.top;
winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), titleString.c_str(), style,
CW_USEDEFAULT, CW_USEDEFAULT, adjustedWidth, adjustedHeight, nullptr, nullptr, instance, this));
WINRT_ASSERT(m_window);
m_childWindow = std::make_unique<ChildWindow>(width, height, m_window);
}
ReparentCropAndLockWindow::~ReparentCropAndLockWindow()
{
DisconnectTarget();
DestroyWindow(m_window);
}
LRESULT ReparentCropAndLockWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_DESTROY:
if (m_closedCallback != nullptr && !m_destroyed)
{
m_destroyed = true;
m_closedCallback(m_window);
}
break;
case WM_MOUSEACTIVATE:
if (m_currentTarget != nullptr && GetForegroundWindow() != m_currentTarget)
{
SetForegroundWindow(m_currentTarget);
}
return MA_NOACTIVATE;
case WM_ACTIVATE:
if (static_cast<DWORD>(wparam) == WA_ACTIVE)
{
if (m_currentTarget != nullptr)
{
SetForegroundWindow(m_currentTarget);
}
}
break;
case WM_DPICHANGED:
break;
default:
return base_type::MessageHandler(message, wparam, lparam);
}
return 0;
}
void ReparentCropAndLockWindow::CropAndLock(HWND windowToCrop, RECT cropRect)
{
DisconnectTarget();
m_currentTarget = windowToCrop;
// Adjust the crop rect to be in the window space as reported by win32k
RECT windowRect = {};
winrt::check_bool(GetWindowRect(m_currentTarget, &windowRect));
auto clientRect = ClientAreaInScreenSpace(m_currentTarget);
auto diffX = clientRect.left - windowRect.left;
auto diffY = clientRect.top - windowRect.top;
auto adjustedCropRect = cropRect;
adjustedCropRect.left += diffX;
adjustedCropRect.top += diffY;
adjustedCropRect.right += diffX;
adjustedCropRect.bottom += diffY;
cropRect = adjustedCropRect;
// Save the previous position of the target so that we can restore it.
m_previousPosition = { windowRect.left, windowRect.top };
auto newX = adjustedCropRect.left + windowRect.left;
auto newY = adjustedCropRect.top + windowRect.top;
auto monitor = winrt::check_pointer(MonitorFromWindow(m_currentTarget, MONITOR_DEFAULTTONULL));
uint32_t dpiX = 0;
uint32_t dpiY = 0;
winrt::check_hresult(GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY));
uint32_t dpi = dpiX > dpiY ? dpiX : dpiY;
// Reconfigure our window
auto width = cropRect.right - cropRect.left;
auto height = cropRect.bottom - cropRect.top;
windowRect = { newX, newY, newX + width, newY + height };
auto exStyle = static_cast<DWORD>(GetWindowLongPtrW(m_window, GWL_EXSTYLE));
auto style = static_cast<DWORD>(GetWindowLongPtrW(m_window, GWL_STYLE));
winrt::check_bool(AdjustWindowRectExForDpi(&windowRect, style, false, exStyle, dpi));
auto adjustedWidth = windowRect.right - windowRect.left;
auto adjustedHeight = windowRect.bottom - windowRect.top;
winrt::check_bool(SetWindowPos(m_window, HWND_TOPMOST, windowRect.left, windowRect.top, adjustedWidth, adjustedHeight, SWP_SHOWWINDOW | SWP_NOACTIVATE));
winrt::check_bool(SetWindowPos(m_childWindow->m_window, nullptr, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE));
// Reparent the target window
SetParent(m_currentTarget, m_childWindow->m_window);
auto targetStyle = GetWindowLongPtrW(m_currentTarget, GWL_STYLE);
targetStyle |= WS_CHILD;
SetWindowLongPtrW(m_currentTarget, GWL_STYLE, targetStyle);
auto x = -cropRect.left;
auto y = -cropRect.top;
winrt::check_bool(SetWindowPos(m_currentTarget, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_FRAMECHANGED | SWP_NOZORDER));
}
void ReparentCropAndLockWindow::Hide()
{
DisconnectTarget();
ShowWindow(m_window, SW_HIDE);
}
void ReparentCropAndLockWindow::DisconnectTarget()
{
if (m_currentTarget != nullptr)
{
if (!IsWindow(m_currentTarget))
{
// The child window was closed by other means?
m_currentTarget = nullptr;
return;
}
winrt::check_bool(SetWindowPos(m_currentTarget, nullptr, m_previousPosition.x, m_previousPosition.y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED));
SetParent(m_currentTarget, nullptr);
auto targetStyle = static_cast<DWORD>(GetWindowLongPtrW(m_currentTarget, GWL_STYLE));
targetStyle &= ~WS_CHILD;
SetWindowLongPtrW(m_currentTarget, GWL_STYLE, targetStyle);
m_currentTarget = nullptr;
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <robmikh.common/DesktopWindow.h>
#include "CropAndLockWindow.h"
#include "ChildWindow.h"
struct ReparentCropAndLockWindow : robmikh::common::desktop::DesktopWindow<ReparentCropAndLockWindow>, CropAndLockWindow
{
static const std::wstring ClassName;
ReparentCropAndLockWindow(std::wstring const& titleString, int width, int height);
~ReparentCropAndLockWindow() override;
LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
HWND Handle() override { return m_window; }
void CropAndLock(HWND windowToCrop, RECT cropRect) override;
void OnClosed(std::function<void(HWND)> callback) override { m_closedCallback = callback; }
private:
static void RegisterWindowClass();
void Hide();
void DisconnectTarget();
private:
HWND m_currentTarget = nullptr;
POINT m_previousPosition = {};
std::unique_ptr<ChildWindow> m_childWindow;
bool m_destroyed = false;
std::function<void(HWND)> m_closedCallback;
};

View File

@ -0,0 +1,7 @@
#pragma once
enum class CropAndLockType
{
Reparent,
Thumbnail,
};

View File

@ -0,0 +1,181 @@
#include "pch.h"
#include "ThumbnailCropAndLockWindow.h"
const std::wstring ThumbnailCropAndLockWindow::ClassName = L"CropAndLock.ThumbnailCropAndLockWindow";
std::once_flag ThumbnailCropAndLockWindowClassRegistration;
float ComputeScaleFactor(RECT const& windowRect, RECT const& contentRect)
{
auto windowWidth = static_cast<float>(windowRect.right - windowRect.left);
auto windowHeight = static_cast<float>(windowRect.bottom - windowRect.top);
auto contentWidth = static_cast<float>(contentRect.right - contentRect.left);
auto contentHeight = static_cast<float>(contentRect.bottom - contentRect.top);
auto windowRatio = windowWidth / windowHeight;
auto contentRatio = contentWidth / contentHeight;
auto scaleFactor = windowWidth / contentWidth;
if (windowRatio > contentRatio)
{
scaleFactor = windowHeight / contentHeight;
}
return scaleFactor;
}
RECT ComputeDestRect(RECT const& windowRect, RECT const& contentRect)
{
auto scaleFactor = ComputeScaleFactor(windowRect, contentRect);
auto windowWidth = static_cast<float>(windowRect.right - windowRect.left);
auto windowHeight = static_cast<float>(windowRect.bottom - windowRect.top);
auto contentWidth = static_cast<float>(contentRect.right - contentRect.left) * scaleFactor;
auto contentHeight = static_cast<float>(contentRect.bottom - contentRect.top) * scaleFactor;
auto remainingWidth = windowWidth - contentWidth;
auto remainingHeight = windowHeight - contentHeight;
auto left = static_cast<LONG>(remainingWidth / 2.0f);
auto top = static_cast<LONG>(remainingHeight / 2.0f);
auto right = left + static_cast<LONG>(contentWidth);
auto bottom = top + static_cast<LONG>(contentHeight);
return RECT{ left, top, right, bottom };
}
void ThumbnailCropAndLockWindow::RegisterWindowClass()
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
WNDCLASSEXW wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.hInstance = instance;
wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
wcex.lpszClassName = ClassName.c_str();
wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION);
winrt::check_bool(RegisterClassExW(&wcex));
}
ThumbnailCropAndLockWindow::ThumbnailCropAndLockWindow(std::wstring const& titleString, int width, int height)
{
auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
std::call_once(ThumbnailCropAndLockWindowClassRegistration, []() { RegisterWindowClass(); });
auto exStyle = 0;
auto style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
RECT rect = { 0, 0, width, height};
winrt::check_bool(AdjustWindowRectEx(&rect, style, false, exStyle));
auto adjustedWidth = rect.right - rect.left;
auto adjustedHeight = rect.bottom - rect.top;
winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), titleString.c_str(), style,
CW_USEDEFAULT, CW_USEDEFAULT, adjustedWidth, adjustedHeight, nullptr, nullptr, instance, this));
WINRT_ASSERT(m_window);
}
ThumbnailCropAndLockWindow::~ThumbnailCropAndLockWindow()
{
DisconnectTarget();
DestroyWindow(m_window);
}
LRESULT ThumbnailCropAndLockWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
{
switch (message)
{
case WM_DESTROY:
if (m_closedCallback != nullptr && !m_destroyed)
{
m_destroyed = true;
m_closedCallback(m_window);
}
break;
case WM_SIZE:
case WM_SIZING:
{
if (m_thumbnail != nullptr)
{
RECT clientRect = {};
winrt::check_bool(GetClientRect(m_window, &clientRect));
m_destRect = ComputeDestRect(clientRect, m_sourceRect);
DWM_THUMBNAIL_PROPERTIES properties = {};
properties.dwFlags = DWM_TNP_RECTDESTINATION;
properties.rcDestination = m_destRect;
winrt::check_hresult(DwmUpdateThumbnailProperties(m_thumbnail.get(), &properties));
}
}
break;
default:
return base_type::MessageHandler(message, wparam, lparam);
}
return 0;
}
void ThumbnailCropAndLockWindow::CropAndLock(HWND windowToCrop, RECT cropRect)
{
DisconnectTarget();
m_currentTarget = windowToCrop;
// Adjust the crop rect to be in the window space as reported by the DWM
RECT windowRect = {};
winrt::check_hresult(DwmGetWindowAttribute(m_currentTarget, DWMWA_EXTENDED_FRAME_BOUNDS, reinterpret_cast<void*>(&windowRect), sizeof(windowRect)));
auto clientRect = ClientAreaInScreenSpace(m_currentTarget);
auto diffX = clientRect.left - windowRect.left;
auto diffY = clientRect.top - windowRect.top;
auto adjustedCropRect = cropRect;
adjustedCropRect.left += diffX;
adjustedCropRect.top += diffY;
adjustedCropRect.right += diffX;
adjustedCropRect.bottom += diffY;
cropRect = adjustedCropRect;
// Resize our window
auto width = cropRect.right - cropRect.left;
auto height = cropRect.bottom - cropRect.top;
windowRect = { 0, 0, width, height };
auto exStyle = static_cast<DWORD>(GetWindowLongPtrW(m_window, GWL_EXSTYLE));
auto style = static_cast<DWORD>(GetWindowLongPtrW(m_window, GWL_STYLE));
winrt::check_bool(AdjustWindowRectEx(&windowRect, style, false, exStyle));
auto adjustedWidth = windowRect.right - windowRect.left;
auto adjustedHeight = windowRect.bottom - windowRect.top;
winrt::check_bool(SetWindowPos(m_window, HWND_TOPMOST, 0, 0, adjustedWidth, adjustedHeight, SWP_NOMOVE | SWP_SHOWWINDOW));
// Setup the thumbnail
winrt::check_hresult(DwmRegisterThumbnail(m_window, m_currentTarget, m_thumbnail.addressof()));
clientRect = {};
winrt::check_bool(GetClientRect(m_window, &clientRect));
m_destRect = clientRect;
m_sourceRect = cropRect;
DWM_THUMBNAIL_PROPERTIES properties = {};
properties.dwFlags = DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_OPACITY | DWM_TNP_RECTDESTINATION | DWM_TNP_RECTSOURCE;
properties.fSourceClientAreaOnly = false;
properties.fVisible = true;
properties.opacity = 255;
properties.rcDestination = m_destRect;
properties.rcSource = m_sourceRect;
winrt::check_hresult(DwmUpdateThumbnailProperties(m_thumbnail.get(), &properties));
}
void ThumbnailCropAndLockWindow::Hide()
{
DisconnectTarget();
ShowWindow(m_window, SW_HIDE);
}
void ThumbnailCropAndLockWindow::DisconnectTarget()
{
if (m_currentTarget != nullptr)
{
m_thumbnail.reset();
m_currentTarget = nullptr;
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <robmikh.common/DesktopWindow.h>
#include "CropAndLockWindow.h"
struct ThumbnailCropAndLockWindow : robmikh::common::desktop::DesktopWindow<ThumbnailCropAndLockWindow>, CropAndLockWindow
{
static const std::wstring ClassName;
ThumbnailCropAndLockWindow(std::wstring const& titleString, int width, int height);
~ThumbnailCropAndLockWindow() override;
LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
HWND Handle() override { return m_window; }
void CropAndLock(HWND windowToCrop, RECT cropRect) override;
void OnClosed(std::function<void(HWND)> callback) override { m_closedCallback = callback; }
private:
static void RegisterWindowClass();
void Hide();
void DisconnectTarget();
private:
HWND m_currentTarget = nullptr;
POINT m_previousPosition = {};
unique_hthumbnail m_thumbnail;
RECT m_destRect = {};
RECT m_sourceRect = {};
bool m_destroyed = false;
std::function<void(HWND)> m_closedCallback;
};

View File

@ -0,0 +1,3 @@
#pragma once
typedef wil::unique_any<HTHUMBNAIL, decltype(&::DwmUnregisterThumbnail), ::DwmUnregisterThumbnail> unique_hthumbnail;

View File

@ -0,0 +1,14 @@
#pragma once
inline RECT ClientAreaInScreenSpace(HWND window)
{
POINT clientOrigin = { 0, 0 };
winrt::check_bool(ClientToScreen(window, &clientOrigin));
RECT windowBounds = {};
winrt::check_bool(GetClientRect(window, &windowBounds));
windowBounds.left += clientOrigin.x;
windowBounds.top += clientOrigin.y;
windowBounds.right += clientOrigin.x;
windowBounds.bottom += clientOrigin.y;
return windowBounds;
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -0,0 +1,270 @@
#include "pch.h"
#include "SettingsWindow.h"
#include "OverlayWindow.h"
#include "CropAndLockWindow.h"
#include "ThumbnailCropAndLockWindow.h"
#include "ReparentCropAndLockWindow.h"
#include <common/interop/shared_constants.h>
#include <common/utils/winapi_error.h>
#include <common/utils/logger_helper.h>
#include <common/utils/UnhandledExceptionHandler.h>
#include <common/utils/gpo.h>
#include "ModuleConstants.h"
#include <common/utils/ProcessWaiter.h>
#include "trace.h"
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
namespace winrt
{
using namespace Windows::Foundation;
using namespace Windows::Foundation::Numerics;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
}
namespace util
{
using namespace robmikh::common::desktop;
}
const std::wstring instanceMutexName = L"Local\\PowerToys_CropAndLock_InstanceMutex";
bool m_running = true;
int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _In_ int)
{
// Initialize COM
winrt::init_apartment(winrt::apartment_type::single_threaded);
// Initialize logger automatic logging of exceptions.
LoggerHelpers::init_logger(NonLocalizable::ModuleKey, L"", LogSettings::cropAndLockLoggerName);
InitUnhandledExceptionHandler();
if (powertoys_gpo::getConfiguredCropAndLockEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
{
Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
return 0;
}
// Before we do anything, check to see if we're already running. If we are,
// the hotkey won't register and we'll fail. Instead, we should tell the user
// to kill the other instance and exit this one.
auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str());
if (mutex == nullptr)
{
Logger::error(L"Failed to create mutex. {}", get_last_error_or_default(GetLastError()));
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// CropAndLock is already open.
return 1;
}
std::wstring pid = std::wstring(lpCmdLine);
if (pid.empty())
{
Logger::warn(L"Tried to run Crop And Lock as a standalone.");
MessageBoxW(nullptr, L"CropAndLock can't run as a standalone. Start it from PowerToys.", L"CropAndLock", MB_ICONERROR);
return 1;
}
auto mainThreadId = GetCurrentThreadId();
ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) {
if (err != ERROR_SUCCESS)
{
Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err));
}
else
{
Logger::trace(L"PowerToys runner exited.");
}
Logger::trace(L"Exiting CropAndLock");
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
});
// NOTE: reparenting a window with a different DPI context has consequences.
// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent#remarks
// for more info.
winrt::check_bool(SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2));
// Create the DispatcherQueue that the compositor needs to run
auto controller = util::CreateDispatcherQueueControllerForCurrentThread();
// Setup Composition
auto compositor = winrt::Compositor();
// Create our overlay window
std::unique_ptr<OverlayWindow> overlayWindow;
// Keep a list of our cropped windows
std::vector<std::shared_ptr<CropAndLockWindow>> croppedWindows;
// Handles and thread for the events sent from runner
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
HANDLE m_exit_event_handle;
std::thread m_event_triggers_thread;
std::function<void(HWND)> removeWindowCallback = [&](HWND windowHandle)
{
if (!m_running)
{
// If we're not running, the reference to croppedWindows might no longer be valid and cause a crash at exit time, due to being called by destructors after wWinMain returns.
return;
}
auto pos = std::find_if(croppedWindows.begin(), croppedWindows.end(), [windowHandle](auto window) { return window->Handle() == windowHandle; });
if (pos != croppedWindows.end())
{
croppedWindows.erase(pos);
}
};
std::function<void(CropAndLockType)> ProcessCommand = [&](CropAndLockType mode)
{
std::function<void(HWND, RECT)> windowCroppedCallback = [&, mode](HWND targetWindow, RECT cropRect) {
auto targetInfo = util::WindowInfo(targetWindow);
// TODO: Fix WindowInfo.h to not contain the null char at the end.
auto nullCharIndex = std::wstring::npos;
do
{
nullCharIndex = targetInfo.Title.rfind(L'\0');
if (nullCharIndex != std::wstring::npos)
{
targetInfo.Title.erase(nullCharIndex);
}
} while (nullCharIndex != std::wstring::npos);
std::wstringstream titleStream;
titleStream << targetInfo.Title << L" (Cropped)";
auto title = titleStream.str();
std::shared_ptr<CropAndLockWindow> croppedWindow;
switch (mode)
{
case CropAndLockType::Reparent:
croppedWindow = std::make_shared<ReparentCropAndLockWindow>(title, 800, 600);
Logger::trace(L"Creating a reparent window");
Trace::CropAndLock::CreateReparentWindow();
break;
case CropAndLockType::Thumbnail:
croppedWindow = std::make_shared<ThumbnailCropAndLockWindow>(title, 800, 600);
Logger::trace(L"Creating a thumbnail window");
Trace::CropAndLock::CreateThumbnailWindow();
break;
default:
return;
}
croppedWindow->CropAndLock(targetWindow, cropRect);
croppedWindow->OnClosed(removeWindowCallback);
croppedWindows.push_back(croppedWindow);
};
overlayWindow.reset();
// Get the current window with focus
auto foregroundWindow = GetForegroundWindow();
if (foregroundWindow != nullptr)
{
bool match = false;
for (auto&& croppedWindow : croppedWindows)
{
if (foregroundWindow == croppedWindow->Handle())
{
match = true;
break;
}
}
if (!match)
{
overlayWindow = std::make_unique<OverlayWindow>(compositor, foregroundWindow, windowCroppedCallback);
}
}
};
// Start a thread to listen on the events.
m_reparent_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
if (!m_reparent_event_handle || !m_reparent_event_handle || !m_exit_event_handle)
{
Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
return 1;
}
m_event_triggers_thread = std::thread([&]() {
MSG msg;
HANDLE event_handles[3] = {m_reparent_event_handle, m_thumbnail_event_handle, m_exit_event_handle};
while (m_running)
{
DWORD dwEvt = MsgWaitForMultipleObjects(3, event_handles, false, INFINITE, QS_ALLINPUT);
if (!m_running)
{
break;
}
switch (dwEvt)
{
case WAIT_OBJECT_0:
{
// Reparent Event
bool enqueueSucceeded = controller.DispatcherQueue().TryEnqueue([&]() {
ProcessCommand(CropAndLockType::Reparent);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to reparent a window.");
}
break;
}
case WAIT_OBJECT_0 + 1:
{
// Thumbnail Event
bool enqueueSucceeded = controller.DispatcherQueue().TryEnqueue([&]() {
ProcessCommand(CropAndLockType::Thumbnail);
});
if (!enqueueSucceeded)
{
Logger::error("Couldn't enqueue message to thumbnail a window.");
}
break;
}
case WAIT_OBJECT_0 + 2:
{
// Exit Event
Logger::trace(L"Received an exit event.");
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
break;
}
case WAIT_OBJECT_0 + 3:
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
break;
default:
break;
}
}
});
// Message pump
MSG msg = {};
while (GetMessageW(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
m_running = false;
// Needed to unblock MsgWaitForMultipleObjects one last time
SetEvent(m_reparent_event_handle);
CloseHandle(m_reparent_event_handle);
CloseHandle(m_thumbnail_event_handle);
CloseHandle(m_exit_event_handle);
m_event_triggers_thread.join();
return util::ShutdownDispatcherQueueControllerAndWait(controller, static_cast<int>(msg.wParam));
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.220929.3" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" />
<package id="robmikh.common" version="0.0.22-beta" targetFramework="native" />
</packages>

View File

@ -0,0 +1 @@
#include "pch.h"

View File

@ -0,0 +1,78 @@
#pragma once
// Collision from minWinDef min/max and std
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
// Windows
#include <windows.h>
#include <windowsx.h>
// Must come before C++/WinRT
#include <wil/cppwinrt.h>
// WinRT
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Numerics.h>
#include <winrt/Windows.System.h>
#include <winrt/Windows.UI.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <winrt/Windows.UI.Popups.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <winrt/Windows.Graphics.DirectX.h>
#include <winrt/Windows.Graphics.DirectX.Direct3d11.h>
// WIL
#include <wil/resource.h>
// DirectX
#include <d3d11_4.h>
#include <dxgi1_6.h>
#include <d2d1_3.h>
#include <wincodec.h>
// DWM
#include <dwmapi.h>
// Shell
#include <shellscalingapi.h>
// STL
#include <vector>
#include <string>
#include <atomic>
#include <memory>
#include <algorithm>
#include <mutex>
#include <sstream>
// robmikh.common
#include <robmikh.common/composition.interop.h>
#include <robmikh.common/direct3d11.interop.h>
#include <robmikh.common/d3dHelpers.h>
#include <robmikh.common/graphics.interop.h>
#include <robmikh.common/dispatcherQueue.desktop.interop.h>
#include <robmikh.common/d3dHelpers.desktop.h>
#include <robmikh.common/composition.desktop.interop.h>
#include <robmikh.common/hwnd.interop.h>
#include <robmikh.common/capture.desktop.interop.h>
#include <robmikh.common/DesktopWindow.h>
#include <robmikh.common/DisplayInfo.h>
#include <robmikh.common/shellHelpers.desktop.h>
#include <robmikh.common/ControlsHelper.h>
#include <robmikh.common/WindowInfo.h>
// Helpers
#include "DisplaysUtil.h"
#include "ThumbnailUtil.h"
#include "WindowRectUtil.h"
// PowerToys
#include <ProjectTelemetry.h>
#include <common/logger/logger.h>
// Application resources
#include "resource.h"
#define MAIN_ICON MAKEINTRESOURCEW(IDI_ICON1)

View File

@ -0,0 +1,26 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by CropAndLock.rc
//
#define IDI_ICON1 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys CropAndLock"
#define INTERNAL_NAME "PowerToys.CropAndLock"
#define ORIGINAL_FILENAME "PowerToys.CropAndLock.exe"
// Non-localizable
//////////////////////////////

View File

@ -0,0 +1,95 @@
#include "pch.h"
#include "trace.h"
// Telemetry strings should not be localized.
#define LoggingProviderKey "Microsoft.PowerToys"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
LoggingProviderKey,
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider() noexcept
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider() noexcept
{
TraceLoggingUnregister(g_hProvider);
}
void Trace::CropAndLock::Enable(bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"CropAndLock_EnableCropAndLock",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
void Trace::CropAndLock::ActivateReparent() noexcept
{
TraceLoggingWrite(
g_hProvider,
"CropAndLock_ActivateReparent",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::ActivateThumbnail() noexcept
{
TraceLoggingWrite(
g_hProvider,
"CropAndLock_ActivateThumbnail",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::CreateReparentWindow() noexcept
{
TraceLoggingWrite(
g_hProvider,
"CropAndLock_CreateReparentWindow",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CropAndLock::CreateThumbnailWindow() noexcept
{
TraceLoggingWrite(
g_hProvider,
"CropAndLock_CreateThumbnailWindow",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
// Event to send settings telemetry.
void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey) noexcept
{
std::wstring hotKeyStrReparent =
std::wstring(reparentHotkey.win ? L"Win + " : L"") +
std::wstring(reparentHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(reparentHotkey.shift ? L"Shift + " : L"") +
std::wstring(reparentHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(reparentHotkey.key);
std::wstring hotKeyStrThumbnail =
std::wstring(thumbnailHotkey.win ? L"Win + " : L"") +
std::wstring(thumbnailHotkey.ctrl ? L"Ctrl + " : L"") +
std::wstring(thumbnailHotkey.shift ? L"Shift + " : L"") +
std::wstring(thumbnailHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(thumbnailHotkey.key);
TraceLoggingWrite(
g_hProvider,
"CropAndLock_Settings",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(hotKeyStrReparent.c_str(), "ReparentHotKey"),
TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey")
);
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <modules/interface/powertoy_module_interface.h>
class Trace
{
public:
static void RegisterProvider() noexcept;
static void UnregisterProvider() noexcept;
class CropAndLock
{
public:
static void Enable(bool enabled) noexcept;
static void ActivateReparent() noexcept;
static void ActivateThumbnail() noexcept;
static void CreateReparentWindow() noexcept;
static void CreateThumbnailWindow() noexcept;
static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
};
};

View File

@ -0,0 +1,40 @@
#include <windows.h>
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{3157fa75-86cf-4ee2-8f62-c43f776493c6}</ProjectGuid>
<RootNamespace>CropAndLockModuleInterface</RootNamespace>
<ProjectName>CropAndLockModuleInterface</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
<TargetName>PowerToys.CropAndLockModuleInterface</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\CropAndLock\ModuleConstants.h" />
<ClInclude Include="..\CropAndLock\trace.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\CropAndLock\trace.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="CropAndLockModuleInterface.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.211019.2\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets" Condition="Exists('..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets')" />
</ImportGroup>
<Import Project="..\..\..\..\deps\spdlog.props" />
<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.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220929.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\robmikh.common.0.0.22-beta\build\native\robmikh.common.targets'))" />
</Target>
</Project>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\CropAndLock\trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\CropAndLock\ModuleConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\CropAndLock\trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="CropAndLockModuleInterface.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,334 @@
#include "pch.h"
#include <modules/interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
#include <common/utils/resources.h>
#include <common/utils/winapi_error.h>
#include <CropAndLock/trace.h>
#include <CropAndLock/ModuleConstants.h>
#include <shellapi.h>
#include <common/interop/shared_constants.h>
namespace NonLocalizable
{
const wchar_t ModulePath[] = L"PowerToys.CropAndLock.exe";
}
namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_WIN[] = L"win";
const wchar_t JSON_KEY_ALT[] = L"alt";
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_REPARENT_HOTKEY[] = L"reparent-hotkey";
const wchar_t JSON_KEY_THUMBNAIL_HOTKEY[] = L"thumbnail-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
BOOL APIENTRY DllMain( HMODULE /*hModule*/,
DWORD ul_reason_for_call,
LPVOID /*lpReserved*/
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
class CropAndLockModuleInterface : public PowertoyModuleIface
{
public:
// Return the localized display name of the powertoy
virtual PCWSTR get_name() override
{
return app_name.c_str();
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return app_key.c_str();
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredCropAndLockEnabledValue();
}
// Return JSON with the configuration options.
// These are the settings shown on the settings page along with their current values.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Passes JSON with the configuration settings for the powertoy.
// This is called when the user hits Save on the settings page.
virtual void set_config(const wchar_t* config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
parse_hotkey(values);
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
virtual bool on_hotkey(size_t hotkeyId) override
{
if (m_enabled)
{
Logger::trace(L"CropAndLock hotkey pressed");
if (!is_process_running())
{
Enable();
}
if (hotkeyId == 0) { // Same order as set by get_hotkeys
SetEvent(m_reparent_event_handle);
Trace::CropAndLock::ActivateReparent();
}
if (hotkeyId == 1) { // Same order as set by get_hotkeys
SetEvent(m_thumbnail_event_handle);
Trace::CropAndLock::ActivateThumbnail();
}
return true;
}
return false;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
if (hotkeys && buffer_size >= 2)
{
hotkeys[0] = m_reparent_hotkey;
hotkeys[1] = m_thumbnail_hotkey;
}
return 2;
}
// Enable the powertoy
virtual void enable()
{
Logger::info("CropAndLock enabling");
Enable();
}
// Disable the powertoy
virtual void disable()
{
Logger::info("CropAndLock disabling");
Disable(true);
}
// Returns if the powertoy is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Disable(false);
delete this;
}
virtual void send_settings_telemetry() override
{
Logger::info("Send settings telemetry");
Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey);
}
CropAndLockModuleInterface()
{
app_name = L"CropAndLock";
app_key = NonLocalizable::ModuleKey;
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::cropAndLockLoggerName);
m_reparent_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
init_settings();
}
private:
void Enable()
{
m_enabled = true;
// Log telemetry
Trace::CropAndLock::Enable(true);
// Pass the PID.
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
ResetEvent(m_exit_event_handle);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = NonLocalizable::ModulePath;
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei) == false)
{
Logger::error(L"Failed to start CropAndLock");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
else
{
m_hProcess = sei.hProcess;
}
}
void Disable(bool const traceEvent)
{
m_enabled = false;
// We can't just kill the process, since Crop and Lock might need to release any reparented windows first.
SetEvent(m_exit_event_handle);
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
// Log telemetry
if (traceEvent)
{
Trace::CropAndLock::Enable(false);
}
if (m_hProcess)
{
m_hProcess = nullptr;
}
}
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size())
{
try
{
Hotkey _temp_reparent;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_REPARENT_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_reparent.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_reparent.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_reparent.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_reparent.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_reparent.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_reparent_hotkey = _temp_reparent;
}
catch (...)
{
Logger::error("Failed to initialize CropAndLock reparent shortcut from settings. Value will keep unchanged.");
}
try
{
Hotkey _temp_thumbnail;
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_THUMBNAIL_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
_temp_thumbnail.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
_temp_thumbnail.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
_temp_thumbnail.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
_temp_thumbnail.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
_temp_thumbnail.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
m_thumbnail_hotkey = _temp_thumbnail;
}
catch (...)
{
Logger::error("Failed to initialize CropAndLock thumbnail shortcut from settings. Value will keep unchanged.");
}
}
else
{
Logger::info("CropAndLock settings are empty");
}
}
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void init_settings()
{
try
{
// Load and parse the settings file for this PowerToy.
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
parse_hotkey(settings);
}
catch (std::exception&)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
}
}
std::wstring app_name;
std::wstring app_key; //contains the non localized key of the powertoy
bool m_enabled = false;
HANDLE m_hProcess = nullptr;
// TODO: actual default hotkey setting in line with other PowerToys.
Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R' };
Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T' };
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
HANDLE m_exit_event_handle;
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new CropAndLockModuleInterface();
}

View File

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

View File

@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@ -0,0 +1,12 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <Unknwn.h>
#include <winrt/base.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <ProjectTelemetry.h>
#include <TraceLoggingActivity.h>
#include <wil\common.h>
#include <wil\result.h>

View File

@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by CropAndLockModuleInterface.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys CropAndLockModuleInterface"
#define INTERNAL_NAME "PowerToys.CropAndLockModuleInterface"
#define ORIGINAL_FILENAME "PowerToys.CropAndLockModuleInterface.dll"
// Non-localizable
//////////////////////////////

View File

@ -52,22 +52,6 @@
<NoWarn>0436</NoWarn>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="Assets\MeasureTool\SplashScreen.scale-200.png" />
<Content Include="Assets\MeasureTool\LockScreenLogo.scale-200.png" />

View File

@ -249,67 +249,90 @@
<StackPanel
HorizontalAlignment="Center"
Loaded="StackPanel_Loaded"
Orientation="Horizontal"
Spacing="8"
Loaded="StackPanel_Loaded">
Spacing="8">
<ToggleButton
Name="btnBounds"
AutomationProperties.Name="{x:Bind p:Resources.Bounds}"
x:Uid="BtnBounds"
Click="BoundsTool_Click"
Content="&#xEF20;"
Style="{StaticResource ToggleButtonRadioButtonStyle}"
KeyboardAcceleratorPlacementMode="Auto"
ToolTipService.ToolTip="{x:Bind p:Resources.Bounds}">
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="BtnBoundsTooltip" />
</ToolTipService.ToolTip>
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number1" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
<KeyboardAccelerator
Key="Number1"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<ToggleButton
Name="btnSpacing"
AutomationProperties.Name="{x:Bind p:Resources.Spacing}"
x:Uid="BtnSpacing"
Click="MeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.Spacing}">
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="BtnSpacingTooltip" />
</ToolTipService.ToolTip>
<FontIcon Glyph="&#xE948;" />
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number2" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
<KeyboardAccelerator
Key="Number2"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<ToggleButton
Name="btnHorizontalSpacing"
AutomationProperties.Name="{x:Bind p:Resources.HorizontalSpacing}"
x:Uid="BtnHorizontalSpacing"
Click="HorizontalMeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.HorizontalSpacing}">
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="BtnHorizontalSpacingTooltip" />
</ToolTipService.ToolTip>
<FontIcon Glyph="&#xE949;" />
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number3" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
<KeyboardAccelerator
Key="Number3"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<ToggleButton
Name="btnVerticalSpacing"
AutomationProperties.Name="{x:Bind p:Resources.VerticalSpacing}"
x:Uid="BtnVerticalSpacing"
Click="VerticalMeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.VerticalSpacing}">
Style="{StaticResource ToggleButtonRadioButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="BtnVerticalSpacingTooltip" />
</ToolTipService.ToolTip>
<FontIcon Glyph="&#xE949;" RenderTransformOrigin="0.5,0.5">
<FontIcon.RenderTransform>
<RotateTransform Angle="90" />
</FontIcon.RenderTransform>
</FontIcon>
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number4" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
<KeyboardAccelerator
Key="Number4"
Invoked="KeyboardAccelerator_Invoked"
Modifiers="Control" />
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<AppBarSeparator/>
<AppBarSeparator />
<Button
x:Uid="BtnClosePanel"
Click="ClosePanelTool_Click"
Content="&#xE8BB;"
ToolTipService.ToolTip="{x:Bind p:Resources.Close}"
Foreground="{StaticResource CloseButtonBackgroundPointerOver}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="BtnClosePanelTooltip" />
</ToolTipService.ToolTip>
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked"/>
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked" />
</Button.KeyboardAccelerators>
</Button>
</StackPanel>

View File

@ -1,108 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PowerToys.MeasureToolUI.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerToys.MeasureToolUI.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Bounds (Ctrl+1).
/// </summary>
public static string Bounds {
get {
return ResourceManager.GetString("Bounds", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close (Esc).
/// </summary>
public static string Close {
get {
return ResourceManager.GetString("Close", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Horizontal spacing (Ctrl+3).
/// </summary>
public static string HorizontalSpacing {
get {
return ResourceManager.GetString("HorizontalSpacing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Spacing (Ctrl+2).
/// </summary>
public static string Spacing {
get {
return ResourceManager.GetString("Spacing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Vertical spacing (Ctrl+4).
/// </summary>
public static string VerticalSpacing {
get {
return ResourceManager.GetString("VerticalSpacing", resourceCulture);
}
}
}
}

View File

@ -1,135 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Bounds" xml:space="preserve">
<value>Bounds (Ctrl+1)</value>
</data>
<data name="Spacing" xml:space="preserve">
<value>Spacing (Ctrl+2)</value>
</data>
<data name="HorizontalSpacing" xml:space="preserve">
<value>Horizontal spacing (Ctrl+3)</value>
</data>
<data name="VerticalSpacing" xml:space="preserve">
<value>Vertical spacing (Ctrl+4)</value>
</data>
<data name="Close" xml:space="preserve">
<value>Close (Esc)</value>
</data>
</root>

View File

@ -39,4 +39,31 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BtnBounds.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Bounds (Ctrl+1)</value>
</data>
<data name="BtnBoundsTooltip.Text" xml:space="preserve">
<value>Bounds (Ctrl+1)</value>
</data>
<data name="BtnSpacing.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Spacing (Ctrl+2)</value>
</data>
<data name="BtnSpacingTooltip.Text" xml:space="preserve">
<value>Spacing (Ctrl+2)</value>
</data>
<data name="BtnHorizontalSpacing.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Horizontal spacing (Ctrl+3)</value>
</data>
<data name="BtnHorizontalSpacingTooltip.Text" xml:space="preserve">
<value>Horizontal spacing (Ctrl+3)</value>
</data>
<data name="BtnVerticalSpacing.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Vertical spacing (Ctrl+4)</value>
</data>
<data name="BtnVerticalSpacingTooltip.Text" xml:space="preserve">
<value>Vertical spacing (Ctrl+4)</value>
</data>
<data name="BtnClosePanelTooltip.Text" xml:space="preserve">
<value>Close (Esc)</value>
</data>
</root>

View File

@ -171,5 +171,20 @@ LayoutData DefaultLayouts::GetDefaultLayout(MonitorConfigurationType type) const
return iter->second;
}
switch (type)
{
case MonitorConfigurationType::Horizontal:
return LayoutData{};
case MonitorConfigurationType::Vertical:
return LayoutData{
.uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::Rows,
.showSpacing = DefaultValues::ShowSpacing,
.spacing = DefaultValues::Spacing,
.zoneCount = DefaultValues::ZoneCount,
.sensitivityRadius = DefaultValues::SensitivityRadius
};
}
return LayoutData{};
}

View File

@ -96,15 +96,21 @@ namespace FancyZonesUnitTests
.sensitivityRadius = DefaultValues::SensitivityRadius
};
LayoutData rows{
.uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::Rows,
.showSpacing = DefaultValues::ShowSpacing,
.spacing = DefaultValues::Spacing,
.zoneCount = DefaultValues::ZoneCount,
.sensitivityRadius = DefaultValues::SensitivityRadius
};
Assert::IsTrue(priorityGrid == DefaultLayouts::instance().GetDefaultLayout(MonitorConfigurationType::Horizontal));
Assert::IsTrue(priorityGrid == DefaultLayouts::instance().GetDefaultLayout(MonitorConfigurationType::Vertical));
Assert::IsTrue(rows == DefaultLayouts::instance().GetDefaultLayout(MonitorConfigurationType::Vertical));
}
TEST_METHOD (DefaultLayoutsNoFile)
{
// test
DefaultLayouts::instance().LoadData();
LayoutData priorityGrid{
.uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid,
@ -114,8 +120,20 @@ namespace FancyZonesUnitTests
.sensitivityRadius = DefaultValues::SensitivityRadius
};
LayoutData rows{
.uuid = GUID_NULL,
.type = FancyZonesDataTypes::ZoneSetLayoutType::Rows,
.showSpacing = DefaultValues::ShowSpacing,
.spacing = DefaultValues::Spacing,
.zoneCount = DefaultValues::ZoneCount,
.sensitivityRadius = DefaultValues::SensitivityRadius
};
// test
DefaultLayouts::instance().LoadData();
Assert::IsTrue(priorityGrid == DefaultLayouts::instance().GetDefaultLayout(MonitorConfigurationType::Horizontal));
Assert::IsTrue(priorityGrid == DefaultLayouts::instance().GetDefaultLayout(MonitorConfigurationType::Vertical));
Assert::IsTrue(rows == DefaultLayouts::instance().GetDefaultLayout(MonitorConfigurationType::Vertical));
}
};
}

View File

@ -78,8 +78,6 @@ namespace FancyZonesEditor
_themeManager = new ThemeManager(this);
var parseResult = FancyZonesEditorIO.ParseParams();
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Logger.LogInfo("Runner exited");
@ -87,27 +85,8 @@ namespace FancyZonesEditor
Application.Current.Dispatcher.Invoke(Application.Current.Shutdown);
});
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
var parseResult = FancyZonesEditorIO.ParseParams();
parseResult = FancyZonesEditorIO.ParseAppliedLayouts();
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
parseResult = FancyZonesEditorIO.ParseCustomLayouts();
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
parseResult = FancyZonesEditorIO.ParseLayoutHotkeys();
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
@ -121,6 +100,13 @@ namespace FancyZonesEditor
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
parseResult = FancyZonesEditorIO.ParseCustomLayouts();
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
parseResult = FancyZonesEditorIO.ParseDefaultLayouts();
if (!parseResult.Result)
{
@ -128,6 +114,20 @@ namespace FancyZonesEditor
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
parseResult = FancyZonesEditorIO.ParseLayoutHotkeys();
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
parseResult = FancyZonesEditorIO.ParseAppliedLayouts();
if (!parseResult.Result)
{
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
MessageBox.Show(parseResult.Message, FancyZonesEditor.Properties.Resources.Error_Parsing_Data_Title, MessageBoxButton.OK);
}
MainWindowSettingsModel settings = ((App)Current).MainWindowSettings;
settings.UpdateSelectedLayoutModel();

View File

@ -28,7 +28,7 @@ namespace FancyZonesEditor
MainWindowSettingsModel settings = ((App)Application.Current).MainWindowSettings;
settings.SetAppliedModel(model);
App.Overlay.SetLayoutSettings(App.Overlay.Monitors[App.Overlay.CurrentDesktop], model);
App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model);
}
App.FancyZonesEditorIO.SerializeLayoutTemplates();

View File

@ -296,7 +296,7 @@ namespace FancyZonesEditor
model.Persist();
App.Overlay.SetLayoutSettings(App.Overlay.Monitors[App.Overlay.CurrentDesktop], model);
App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model);
App.FancyZonesEditorIO.SerializeAppliedLayouts();
App.FancyZonesEditorIO.SerializeCustomLayouts();
}
@ -318,7 +318,7 @@ namespace FancyZonesEditor
if (mainEditor.CurrentDataContext is LayoutModel model)
{
_settings.SetAppliedModel(model);
App.Overlay.SetLayoutSettings(App.Overlay.Monitors[App.Overlay.CurrentDesktop], model);
App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model);
App.FancyZonesEditorIO.SerializeAppliedLayouts();
App.FancyZonesEditorIO.SerializeCustomLayouts();
}
@ -366,7 +366,7 @@ namespace FancyZonesEditor
_backup = new CanvasLayoutModel(canvas);
}
_defaultLayoutsBackup = new List<LayoutModel>(MainWindowSettingsModel.DefaultLayouts.DefaultLayouts);
_defaultLayoutsBackup = new List<LayoutModel>(MainWindowSettingsModel.DefaultLayouts.Layouts);
Keyboard.ClearFocus();
EditLayoutDialogTitle.Text = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Edit_Template, ((LayoutModel)dataContext).Name);
@ -480,7 +480,7 @@ namespace FancyZonesEditor
// update current settings
if (model == _settings.AppliedModel)
{
App.Overlay.SetLayoutSettings(App.Overlay.Monitors[App.Overlay.CurrentDesktop], model);
App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model);
}
App.FancyZonesEditorIO.SerializeAppliedLayouts();
@ -528,7 +528,7 @@ namespace FancyZonesEditor
{
if (monitor.Settings.ZonesetUuid == model.Uuid)
{
App.Overlay.SetLayoutSettings(monitor, _settings.BlankModel);
monitor.SetLayoutSettings(_settings.BlankModel);
}
}

View File

@ -13,7 +13,7 @@ namespace FancyZonesEditor.Models
{
private static int Count { get; } = Enum.GetValues(typeof(MonitorConfigurationType)).Length;
public List<LayoutModel> DefaultLayouts { get; } = new List<LayoutModel>(Count);
public List<LayoutModel> Layouts { get; } = new List<LayoutModel>(Count);
public DefaultLayoutsModel()
{
@ -23,30 +23,39 @@ namespace FancyZonesEditor.Models
public void Reset(MonitorConfigurationType type)
{
switch (type)
{
case MonitorConfigurationType.Horizontal:
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.PriorityGrid], type);
break;
case MonitorConfigurationType.Vertical:
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.Rows], type);
break;
}
}
public void Reset(string uuid)
{
for (int i = 0; i < Count; i++)
if (Layouts[(int)MonitorConfigurationType.Horizontal].Uuid == uuid)
{
if (DefaultLayouts[i].Uuid == uuid)
{
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.PriorityGrid], (MonitorConfigurationType)i);
break;
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.PriorityGrid], MonitorConfigurationType.Horizontal);
}
if (Layouts[(int)MonitorConfigurationType.Vertical].Uuid == uuid)
{
Set(MainWindowSettingsModel.TemplateModels[(int)LayoutType.Rows], MonitorConfigurationType.Vertical);
}
}
public void Set(LayoutModel layout, MonitorConfigurationType type)
{
if (DefaultLayouts.Count <= (int)type)
if (Layouts.Count <= (int)type)
{
DefaultLayouts.Insert((int)type, layout);
Layouts.Insert((int)type, layout);
}
else
{
DefaultLayouts[(int)type] = layout;
Layouts[(int)type] = layout;
}
FirePropertyChanged();

View File

@ -150,7 +150,7 @@ namespace FancyZonesEditor.Models
{
get
{
return MainWindowSettingsModel.DefaultLayouts.DefaultLayouts[(int)MonitorConfigurationType.Horizontal].Uuid == this.Uuid;
return MainWindowSettingsModel.DefaultLayouts.Layouts[(int)MonitorConfigurationType.Horizontal].Uuid == this.Uuid;
}
}
@ -158,7 +158,7 @@ namespace FancyZonesEditor.Models
{
get
{
return MainWindowSettingsModel.DefaultLayouts.DefaultLayouts[(int)MonitorConfigurationType.Horizontal].Uuid != this.Uuid;
return MainWindowSettingsModel.DefaultLayouts.Layouts[(int)MonitorConfigurationType.Horizontal].Uuid != this.Uuid;
}
}
@ -166,7 +166,7 @@ namespace FancyZonesEditor.Models
{
get
{
return MainWindowSettingsModel.DefaultLayouts.DefaultLayouts[(int)MonitorConfigurationType.Vertical].Uuid == this.Uuid;
return MainWindowSettingsModel.DefaultLayouts.Layouts[(int)MonitorConfigurationType.Vertical].Uuid == this.Uuid;
}
}
@ -174,7 +174,7 @@ namespace FancyZonesEditor.Models
{
get
{
return MainWindowSettingsModel.DefaultLayouts.DefaultLayouts[(int)MonitorConfigurationType.Vertical].Uuid != this.Uuid;
return MainWindowSettingsModel.DefaultLayouts.Layouts[(int)MonitorConfigurationType.Vertical].Uuid != this.Uuid;
}
}

View File

@ -81,7 +81,7 @@ namespace FancyZonesEditor
// set default layouts
DefaultLayouts.Set(priorityGridModel, MonitorConfigurationType.Horizontal);
DefaultLayouts.Set(priorityGridModel, MonitorConfigurationType.Vertical);
DefaultLayouts.Set(rowsModel, MonitorConfigurationType.Vertical);
}
// IsShiftKeyPressed - is the shift key currently being held down
@ -261,11 +261,6 @@ namespace FancyZonesEditor
}
}
if (foundModel == null)
{
foundModel = TemplateModels[(int)LayoutType.PriorityGrid];
}
SetSelectedModel(foundModel);
SetAppliedModel(foundModel);
FirePropertyChanged(nameof(IsCustomLayoutActive));

View File

@ -14,21 +14,45 @@ namespace FancyZonesEditor.Models
{
public LayoutOverlayWindow Window { get; private set; }
public LayoutSettings Settings { get; set; }
public Device Device { get; set; }
public LayoutSettings Settings
{
get
{
if (_settings != null)
{
return _settings;
}
return DefaultLayoutSettings;
}
set
{
_settings = value;
}
}
public bool IsInitialized
{
get
{
return _settings != null;
}
}
public MonitorConfigurationType MonitorConfigurationType
{
get
{
return Device.MonitorSize.Width > Device.MonitorSize.Height ? MonitorConfigurationType.Horizontal : MonitorConfigurationType.Vertical;
}
}
public Monitor(Rect workArea, Size monitorSize)
{
Window = new LayoutOverlayWindow();
Settings = new LayoutSettings();
// provide a good default for vertical monitors
if (monitorSize.Height > monitorSize.Width)
{
Settings.Type = LayoutType.Rows;
}
Device = new Device(workArea, monitorSize);
if (App.DebugMode)
@ -54,6 +78,8 @@ namespace FancyZonesEditor.Models
Device = new Device(monitorName, monitorInstanceId, monitorSerialNumber, virtualDesktop, dpi, workArea, monitorSize);
}
private LayoutSettings _settings;
public void Scale(double scaleFactor)
{
Device.Scale(scaleFactor);
@ -64,5 +90,48 @@ namespace FancyZonesEditor.Models
Window.Width = workArea.Width;
Window.Height = workArea.Height;
}
public void SetLayoutSettings(LayoutModel model)
{
if (model == null)
{
return;
}
if (_settings == null)
{
_settings = new LayoutSettings();
}
_settings.ZonesetUuid = model.Uuid;
_settings.Type = model.Type;
_settings.SensitivityRadius = model.SensitivityRadius;
_settings.ZoneCount = model.TemplateZoneCount;
if (model is GridLayoutModel grid)
{
_settings.ShowSpacing = grid.ShowSpacing;
_settings.Spacing = grid.Spacing;
}
else
{
_settings.ShowSpacing = false;
_settings.Spacing = 0;
}
}
private LayoutSettings DefaultLayoutSettings
{
get
{
LayoutSettings settings = new LayoutSettings();
if (MonitorConfigurationType == MonitorConfigurationType.Vertical)
{
settings.Type = LayoutType.Rows;
}
return settings;
}
}
}
}

View File

@ -180,30 +180,6 @@ namespace FancyZonesEditor
}
}
public void SetLayoutSettings(Monitor monitor, LayoutModel model)
{
if (model == null)
{
return;
}
monitor.Settings.ZonesetUuid = model.Uuid;
monitor.Settings.Type = model.Type;
monitor.Settings.SensitivityRadius = model.SensitivityRadius;
monitor.Settings.ZoneCount = model.TemplateZoneCount;
if (model is GridLayoutModel grid)
{
monitor.Settings.ShowSpacing = grid.ShowSpacing;
monitor.Settings.Spacing = grid.Spacing;
}
else
{
monitor.Settings.ShowSpacing = false;
monitor.Settings.Spacing = 0;
}
}
public void OpenEditor(LayoutModel model)
{
Logger.LogTrace();

View File

@ -1159,15 +1159,17 @@ namespace FancyZonesEditor.Utils
foreach (var layout in layouts)
{
if (layout.Layout.Uuid != null && layout.Layout.Uuid != string.Empty)
{
LayoutModel defaultLayoutModel = null;
MonitorConfigurationType type = JsonTagToMonitorConfigurationType(layout.MonitorConfiguration);
if (layout.Layout.Uuid != null && layout.Layout.Uuid != string.Empty)
{
foreach (var customLayout in MainWindowSettingsModel.CustomModels)
{
if (customLayout.Uuid == layout.Layout.Uuid)
{
MainWindowSettingsModel.DefaultLayouts.Set(customLayout, type);
defaultLayoutModel = customLayout;
break;
}
}
@ -1175,8 +1177,28 @@ namespace FancyZonesEditor.Utils
else
{
LayoutType layoutType = JsonTagToLayoutType(layout.Layout.Type);
MonitorConfigurationType type = JsonTagToMonitorConfigurationType(layout.MonitorConfiguration);
MainWindowSettingsModel.DefaultLayouts.Set(MainWindowSettingsModel.TemplateModels[(int)layoutType], type);
defaultLayoutModel = MainWindowSettingsModel.TemplateModels[(int)layoutType];
defaultLayoutModel.TemplateZoneCount = layout.Layout.ZoneCount;
defaultLayoutModel.SensitivityRadius = layout.Layout.SensitivityRadius;
if (defaultLayoutModel is GridLayoutModel gridDefaultLayoutModel)
{
gridDefaultLayoutModel.ShowSpacing = layout.Layout.ShowSpacing;
gridDefaultLayoutModel.Spacing = layout.Layout.Spacing;
}
MainWindowSettingsModel.DefaultLayouts.Set(defaultLayoutModel, type);
}
if (defaultLayoutModel != null)
{
foreach (Monitor monitor in App.Overlay.Monitors)
{
if (!monitor.IsInitialized && monitor.MonitorConfigurationType == type)
{
monitor.SetLayoutSettings(defaultLayoutModel);
}
}
}
}

View File

@ -40,7 +40,7 @@ namespace Microsoft.Plugin.WindowWalker.Components
/// <summary>
/// Gets the list of all open windows
/// </summary>
internal List<Window> Windows => windows;
internal List<Window> Windows => new List<Window>(windows);
/// <summary>
/// Gets an instance property of this class that makes sure that

View File

@ -7,6 +7,7 @@
#ifndef PCH_H
#define PCH_H
#define NOMINMAX
// add headers that you want to pre-compile here
#include "framework.h"

View File

@ -14,6 +14,7 @@ namespace PowerRenameUI
ExplorerItemsSource ExplorerItems { get; };
Windows.Foundation.Collections.IObservableVector<PatternSnippet> SearchRegExShortcuts { get; };
Windows.Foundation.Collections.IObservableVector<PatternSnippet> DateTimeShortcuts { get; };
Windows.Foundation.Collections.IObservableVector<PatternSnippet> CounterShortcuts { get; };
String OriginalCount;
String RenamedCount;

View File

@ -323,6 +323,8 @@
<Flyout x:Name="DateTimeFlyout" ShouldConstrainToRootBounds="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="Auto" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@ -364,7 +366,45 @@
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock x:Uid="CounterCheatSheet_Title"
Margin="0,10,0,0"
FontWeight="SemiBold" Grid.Row="2" />
<ListView Margin="-4,12,0,0"
IsItemClickEnabled="True"
ItemClick="DateTimeItemClick"
ItemsSource="{x:Bind CounterShortcuts}"
SelectionMode="None" Grid.Row="3">
<ListView.ItemTemplate>
<DataTemplate
x:DataType="local:PatternSnippet">
<Grid Margin="-10,0,0,0"
ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<Border Padding="8"
HorizontalAlignment="Left"
Background="{ThemeResource ButtonBackground}"
BorderBrush="{ThemeResource ButtonBorderBrush}"
BorderThickness="1"
CornerRadius="4">
<TextBlock
FontFamily="Consolas"
Foreground="{ThemeResource ButtonForeground}"
Text="{x:Bind Code}" />
</Border>
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind Description}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Flyout>
</Button.Flyout>
@ -391,9 +431,9 @@
HorizontalAlignment="Stretch"
AutomationProperties.LabeledBy="{Binding ElementName=ApplyToLabel}"
SelectedIndex="0">
<x:String>Filename + extension</x:String>
<x:String>Filename only</x:String>
<x:String>Extension only</x:String>
<ComboBoxItem x:Uid="RenameParts_FilenameAndExtension" />
<ComboBoxItem x:Uid="RenameParts_FilenameOnly" />
<ComboBoxItem x:Uid="RenameParts_ExtensionOnly" />
</ComboBox>
<AppBarSeparator Margin="5,0,5,0" />
<ToggleButton

View File

@ -21,6 +21,7 @@
#include "microsoft.ui.xaml.window.h"
#include <winrt/Microsoft.UI.Interop.h>
#include <winrt/Windows.UI.ViewManagement.h>
#include <winrt/Microsoft.UI.Windowing.h>
#include <common/Themes/theme_helpers.h>
#include <common/Themes/theme_listener.h>
@ -133,8 +134,7 @@ namespace winrt::PowerRenameUI::implementation
GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE::MDT_EFFECTIVE_DPI, &x_dpi, &x_dpi);
UINT window_dpi = GetDpiForWindow(m_window);
int width = 1400;
int height = 800;
const auto& [width, height] = CSettingsInstance().GetLastWindowSize();
winrt::Windows::Graphics::RectInt32 rect;
// Scale window size
@ -196,6 +196,13 @@ namespace winrt::PowerRenameUI::implementation
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$ff", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_MilliSeconds2D").ValueAsString()));
m_dateTimeShortcuts.Append(winrt::make<PatternSnippet>(L"$f", manager.MainResourceMap().GetValue(L"Resources/DateTimeCheatSheet_MilliSeconds1D").ValueAsString()));
m_CounterShortcuts = winrt::single_threaded_observable_vector<PowerRenameUI::PatternSnippet>();
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Simple").ValueAsString()));
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${start=10}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Start").ValueAsString()));
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${increment=5}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Increment").ValueAsString()));
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${padding=8}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Padding").ValueAsString()));
m_CounterShortcuts.Append(winrt::make<PatternSnippet>(L"${increment=3,padding=4,start=900}", manager.MainResourceMap().GetValue(L"Resources/CounterCheatSheet_Complex").ValueAsString()));
InitializeComponent();
listView_ExplorerItems().ApplyTemplate();
@ -278,6 +285,23 @@ namespace winrt::PowerRenameUI::implementation
InitAutoComplete();
SearchReplaceChanged();
InvalidateItemListViewState();
SizeChanged({ this, &MainWindow::OnSizeChanged });
Closed({ this, &MainWindow::OnClosed });
}
void MainWindow::OnSizeChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::WindowSizeChangedEventArgs const& /*args*/)
{
const auto appWindow =
Microsoft::UI::Windowing::AppWindow::GetFromWindowId(Microsoft::UI::GetWindowIdFromWindow(m_window));
const auto [width, height] = appWindow.Size();
CSettingsInstance().UpdateLastWindowSize(static_cast<int>(width), static_cast<int>(height));
}
void MainWindow::OnClosed(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::WindowEventArgs const&)
{
CSettingsInstance().Save();
}
void MainWindow::InvalidateItemListViewState()

View File

@ -74,6 +74,9 @@ namespace winrt::PowerRenameUI::implementation
MainWindow();
void OnSizeChanged(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::WindowSizeChangedEventArgs const&);
void OnClosed(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::WindowEventArgs const&);
void InvalidateItemListViewState();
Windows::Foundation::Collections::IObservableVector<hstring> SearchMRU() { return m_searchMRUList; }
@ -81,6 +84,8 @@ namespace winrt::PowerRenameUI::implementation
PowerRenameUI::ExplorerItemsSource ExplorerItems() { return m_explorerItems; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> SearchRegExShortcuts() { return m_searchRegExShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> DateTimeShortcuts() { return m_dateTimeShortcuts; }
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> CounterShortcuts() { return m_CounterShortcuts; }
hstring OriginalCount();
void OriginalCount(hstring value);
hstring RenamedCount();
@ -93,6 +98,7 @@ namespace winrt::PowerRenameUI::implementation
void ShowRenamed(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);
private:
winrt::Windows::Foundation::Size m_lastWindowSize;
bool m_allSelected;
winrt::Windows::Foundation::Collections::IObservableVector<hstring> m_searchMRUList;
@ -100,6 +106,7 @@ namespace winrt::PowerRenameUI::implementation
PowerRenameUI::ExplorerItemsSource m_explorerItems;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_searchRegExShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_dateTimeShortcuts;
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_CounterShortcuts;
// Used by PowerRenameManagerEvents
HRESULT OnRename(_In_ IPowerRenameItem* renameItem);

View File

@ -150,6 +150,24 @@
<data name="ReplaceBox.PlaceholderText" xml:space="preserve">
<value>Replace with</value>
</data>
<data name="CounterCheatSheet_Title.Text" xml:space="preserve">
<value>Replace using advanced counter syntax.</value>
</data>
<data name="CounterCheatSheet_Simple" xml:space="preserve">
<value>A simple counter that you can use anywhere in a replace string.</value>
</data>
<data name="CounterCheatSheet_Start" xml:space="preserve">
<value>A counter with a customized start value.</value>
</data>
<data name="CounterCheatSheet_Increment" xml:space="preserve">
<value>A counter with a customized increment value.</value>
</data>
<data name="CounterCheatSheet_Padding" xml:space="preserve">
<value>A counter with a customized number of leading padding zeroes.</value>
</data>
<data name="CounterCheatSheet_Complex" xml:space="preserve">
<value>A counter showing multiple customized properties.</value>
</data>
<data name="DateTimeCheatSheet_Title.Text" xml:space="preserve">
<value>Replace using file creation date and time</value>
</data>
@ -336,6 +354,13 @@
<data name="ErrorMessage_InvalidChar" xml:space="preserve">
<value>File name contains invalid character(s):&#xD;&#xA; &gt; &lt; | " : ? * \ /</value>
</data>
<data name="RenameParts_FilenameAndExtension.Content" xml:space="preserve">
<value>Filename + extension</value>
</data>
<data name="RenameParts_FilenameOnly.Content" xml:space="preserve">
<value>Filename only</value>
</data>
<data name="RenameParts_ExtensionOnly.Content" xml:space="preserve">
<value>Extension only</value>
</data>
</root>

View File

@ -3,6 +3,7 @@
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define NOMINMAX
// Windows Header Files:
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>

View File

@ -0,0 +1,46 @@
#include <pch.h>
#include "Enumerating.h"
#include <common\utils\string_utils.h>
std::vector<EnumOptions> parseEnumOptions(const std::wstring& replaceWith)
{
static const std::wregex enumStartRegex(LR"(start=(\d+))");
static const std::wregex enumIncrementRegex(LR"(increment=(-?\d+))");
static const std::wregex enumPaddingRegex(LR"(padding=(\d+))");
std::string buf;
std::vector<EnumOptions> options;
std::wregex enumGroupRegex(LR"(\$\{.*?\})");
for (std::wsregex_iterator i{ begin(replaceWith), end(replaceWith), enumGroupRegex }, end; i != end; ++i)
{
std::wsmatch match = *i;
std::wstring matchString = match.str();
EnumOptions option;
option.replaceStrSpan.offset = match.position();
option.replaceStrSpan.length = match.length();
std::wsmatch subMatch;
if (std::regex_search(matchString, subMatch, enumStartRegex))
{
buf = unwide(subMatch[1].str());
std::from_chars(buf.data(), buf.data() + buf.size(), option.start.emplace());
}
if (std::regex_search(matchString, subMatch, enumIncrementRegex))
{
buf = unwide(subMatch[1].str());
std::from_chars(buf.data(), buf.data() + buf.size(), option.increment.emplace());
}
if (std::regex_search(matchString, subMatch, enumPaddingRegex))
{
buf = unwide(subMatch[1].str());
std::from_chars(buf.data(), buf.data() + buf.size(), option.padding.emplace());
}
options.emplace_back(std::move(option));
}
return options;
}

View File

@ -0,0 +1,55 @@
#pragma once
#include "pch.h"
struct EnumSpan
{
size_t offset = 0;
size_t length = 0;
};
struct EnumOptions
{
std::optional<int> start;
std::optional<int> increment;
std::optional<uint32_t> padding;
EnumSpan replaceStrSpan;
std::strong_ordering operator<=>(const EnumOptions& rhs) const noexcept
{
return std::make_tuple(start, increment, padding) <=> std::make_tuple(rhs.start, rhs.increment, rhs.padding);
}
bool operator==(const EnumOptions& rhs) const noexcept
{
return std::make_tuple(start, increment, padding) == std::make_tuple(rhs.start, rhs.increment, rhs.padding);
}
};
std::vector<EnumOptions> parseEnumOptions(const std::wstring& replaceWith);
struct Enumerator
{
inline Enumerator(EnumOptions options) :
start{ options.start.value_or(0) }, increment{ options.increment.value_or(1) }, padding{ options.padding.value_or(0) }, replaceStrSpan{ options.replaceStrSpan }
{
}
inline int32_t enumerate(const unsigned long index) const { return start + static_cast<int32_t>(index * increment); }
inline size_t printTo(wchar_t* buf, const size_t bufSize, const unsigned long index) const
{
const int32_t enumeratedIndex = enumerate(index);
wchar_t format[32];
swprintf_s(format, sizeof(format) / sizeof(wchar_t), L"%%0%ud", padding);
return swprintf_s(buf, bufSize, format, enumeratedIndex);
}
EnumSpan replaceStrSpan;
private:
int start;
int increment;
uint32_t padding;
};

View File

@ -237,12 +237,10 @@ HRESULT GetTransformedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR sour
bool isFileTimeUsed(_In_ PCWSTR source)
{
bool used = false;
std::wstring patterns[] = { L"(([^\\$]|^)(\\$\\$)*)\\$Y", L"(([^\\$]|^)(\\$\\$)*)\\$M", L"(([^\\$]|^)(\\$\\$)*)\\$D",
L"(([^\\$]|^)(\\$\\$)*)\\$h", L"(([^\\$]|^)(\\$\\$)*)\\$m", L"(([^\\$]|^)(\\$\\$)*)\\$s", L"(([^\\$]|^)(\\$\\$)*)\\$f" };
size_t patternsLength = ARRAYSIZE(patterns);
for (size_t i = 0; !used && i < patternsLength; i++)
static const std::array patterns = { std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$Y" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$M" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$D" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$h" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$m" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$s" }, std::wregex{ L"(([^\\$]|^)(\\$\\$)*)\\$f" } };
for (size_t i = 0; !used && i < patterns.size(); i++)
{
if (std::regex_search(source, std::wregex(patterns[i])))
if (std::regex_search(source, patterns[i]))
{
used = true;
}
@ -328,10 +326,10 @@ HRESULT GetDatedFileName(_Out_ PWSTR result, UINT cchMax, _In_ PCWSTR source, SY
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%03d"), L"$01", fileTime.wMilliseconds);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$fff"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMilliseconds/10);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%02d"), L"$01", fileTime.wMilliseconds / 10);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$ff"), replaceTerm);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMilliseconds/100);
StringCchPrintf(replaceTerm, MAX_PATH, TEXT("%s%d"), L"$01", fileTime.wMilliseconds / 100);
res = regex_replace(res, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$f"), replaceTerm);
hr = StringCchCopy(result, cchMax, res.c_str());

View File

@ -102,6 +102,9 @@ HRESULT CPowerRenameEnum::_ParseEnumItems(_In_ IEnumShellItems* pesi, _In_ int d
l->Compare(r, SICHINT_DISPLAY, &res);
return res < 0;
};
// We need to sort only the first layer, because later ones are enumerated correctly
if (depth == 0)
std::sort(begin(items), end(items), cmpShellItems);
for (const auto& item : items)

View File

@ -58,7 +58,7 @@ public:
IFACEMETHOD(PutFlags)(_In_ DWORD flags) = 0;
IFACEMETHOD(PutFileTime)(_In_ SYSTEMTIME fileTime) = 0;
IFACEMETHOD(ResetFileTime)() = 0;
IFACEMETHOD(Replace)(_In_ PCWSTR source, _Outptr_ PWSTR* result) = 0;
IFACEMETHOD(Replace)(_In_ PCWSTR source, _Outptr_ PWSTR* result, unsigned long& enumIndex) = 0;
};
interface __declspec(uuid("C7F59201-4DE1-4855-A3A2-26FC3279C8A5")) IPowerRenameItem : public IUnknown

View File

@ -31,6 +31,7 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Enumerating.h" />
<ClInclude Include="Helpers.h" />
<ClInclude Include="MRUListHandler.h" />
<ClInclude Include="PowerRenameEnum.h" />
@ -47,6 +48,7 @@
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Enumerating.cpp" />
<ClCompile Include="Helpers.cpp" />
<ClCompile Include="MRUListHandler.cpp" />
<ClCompile Include="PowerRenameEnum.cpp" />

View File

@ -923,7 +923,7 @@ DWORD WINAPI CPowerRenameManager::s_regexWorkerThread(_In_ void* pv)
winrt::check_hresult(pwtd->spsrm->GetRenameRegEx(&spRenameRegEx));
UINT itemCount = 0;
unsigned long itemEnumIndex = 1;
unsigned long itemEnumIndex = 0;
winrt::check_hresult(pwtd->spsrm->GetItemCount(&itemCount));
for (UINT u = 0; u < itemCount; u++)

View File

@ -1,5 +1,6 @@
#include "pch.h"
#include "PowerRenameRegEx.h"
#include "Enumerating.h"
#include "Settings.h"
#include <regex>
#include <string>
@ -7,15 +8,17 @@
#include <boost/regex.hpp>
#include <helpers.h>
using namespace std;
using std::conditional_t;
using std::regex_error;
IFACEMETHODIMP_(ULONG) CPowerRenameRegEx::AddRef()
IFACEMETHODIMP_(ULONG)
CPowerRenameRegEx::AddRef()
{
return InterlockedIncrement(&m_refCount);
}
IFACEMETHODIMP_(ULONG) CPowerRenameRegEx::Release()
IFACEMETHODIMP_(ULONG)
CPowerRenameRegEx::Release()
{
long refCount = InterlockedDecrement(&m_refCount);
@ -127,6 +130,26 @@ IFACEMETHODIMP CPowerRenameRegEx::GetReplaceTerm(_Outptr_ PWSTR* replaceTerm)
return hr;
}
HRESULT CPowerRenameRegEx::_OnEnumerateItemsChanged()
{
m_enumerators.clear();
const auto options = parseEnumOptions(m_RawReplaceTerm);
for (const auto e : options)
m_enumerators.emplace_back(e);
m_replaceWithEnumeratorOffsets.clear();
std::wstring replaceWith{ m_RawReplaceTerm };
// Remove counter expressions and calculate their offsets in replaceWith string.
int32_t offset = 0;
for (const auto& e : options)
{
replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length);
m_replaceWithEnumeratorOffsets.push_back(offset);
offset -= static_cast<int32_t>(e.replaceStrSpan.length);
}
return SHStrDup(replaceWith.data(), &m_replaceTerm);
}
IFACEMETHODIMP CPowerRenameRegEx::PutReplaceTerm(_In_ PCWSTR replaceTerm, bool forceRenaming)
{
bool changed = false || forceRenaming;
@ -134,10 +157,15 @@ IFACEMETHODIMP CPowerRenameRegEx::PutReplaceTerm(_In_ PCWSTR replaceTerm, bool f
if (replaceTerm)
{
CSRWExclusiveAutoLock lock(&m_lock);
if (m_replaceTerm == nullptr || lstrcmp(replaceTerm, m_replaceTerm) != 0)
if (m_replaceTerm == nullptr || lstrcmp(replaceTerm, m_RawReplaceTerm.c_str()) != 0)
{
changed = true;
CoTaskMemFree(m_replaceTerm);
m_RawReplaceTerm = replaceTerm;
if (m_flags & EnumerateItems)
hr = _OnEnumerateItemsChanged();
else
hr = SHStrDup(replaceTerm, &m_replaceTerm);
}
}
@ -160,7 +188,20 @@ IFACEMETHODIMP CPowerRenameRegEx::PutFlags(_In_ DWORD flags)
{
if (m_flags != flags)
{
const bool newEnumerate = flags & EnumerateItems;
const bool refreshReplaceTerm = !!(m_flags & EnumerateItems) != newEnumerate;
m_flags = flags;
if (refreshReplaceTerm)
{
CSRWExclusiveAutoLock lock(&m_lock);
if (newEnumerate)
_OnEnumerateItemsChanged();
else
{
CoTaskMemFree(m_replaceTerm);
SHStrDup(m_RawReplaceTerm.c_str(), &m_replaceTerm);
}
}
_OnFlagsChanged();
}
return S_OK;
@ -202,7 +243,7 @@ HRESULT CPowerRenameRegEx::s_CreateInstance(_Outptr_ IPowerRenameRegEx** renameR
{
*renameRegEx = nullptr;
CPowerRenameRegEx *newRenameRegEx = new CPowerRenameRegEx();
CPowerRenameRegEx* newRenameRegEx = new CPowerRenameRegEx();
HRESULT hr = E_OUTOFMEMORY;
if (newRenameRegEx)
{
@ -228,7 +269,20 @@ CPowerRenameRegEx::~CPowerRenameRegEx()
CoTaskMemFree(m_replaceTerm);
}
HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result)
template<bool Std, class Regex = conditional_t<Std, std::wregex, boost::wregex>, class Options = decltype(Regex::icase)>
static std::wstring RegexReplaceEx(const std::wstring& source, const std::wstring& searchTerm, const std::wstring& replaceTerm, const bool matchAll, const bool caseInsensitive)
{
Regex pattern(searchTerm, Options::ECMAScript | (caseInsensitive ? Options::icase : Options{}));
using Flags = conditional_t<Std, std::regex_constants::match_flag_type, boost::regex_constants::match_flags>;
const auto flags = matchAll ? Flags::match_default : Flags::format_first_only;
return regex_replace(source, pattern, replaceTerm, flags);
}
static constexpr std::array RegexReplaceDispatch = { RegexReplaceEx<true>, RegexReplaceEx<false> };
HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, unsigned long& enumIndex)
{
*result = nullptr;
@ -238,7 +292,7 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result)
{
return hr;
}
wstring res = source;
std::wstring res = source;
try
{
// TODO: creating the regex could be costly. May want to cache this.
@ -250,47 +304,49 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result)
fileTimeErrorOccurred = true;
}
std::wstring sourceToUse(source);
std::wstring sourceToUse;
std::wstring originalSource;
sourceToUse.reserve(MAX_PATH);
originalSource.reserve(MAX_PATH);
sourceToUse = source;
originalSource = sourceToUse;
std::wstring searchTerm(m_searchTerm);
std::wstring replaceTerm(L"");
std::wstring replaceTerm;
if (m_useFileTime && !fileTimeErrorOccurred)
{
replaceTerm = wstring(newReplaceTerm);
replaceTerm = newReplaceTerm;
}
else if (m_replaceTerm)
{
replaceTerm = wstring(m_replaceTerm);
replaceTerm = m_replaceTerm;
}
replaceTerm = regex_replace(replaceTerm, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$[0]"), L"$1$$$0");
replaceTerm = regex_replace(replaceTerm, std::wregex(L"(([^\\$]|^)(\\$\\$)*)\\$([1-9])"), L"$1$0$4");
static const std::wregex zeroGroupRegex(L"(([^\\$]|^)(\\$\\$)*)\\$[0]");
static const std::wregex otherGroupsRegex(L"(([^\\$]|^)(\\$\\$)*)\\$([1-9])");
if (m_flags & EnumerateItems)
{
std::array<wchar_t, MAX_PATH> buffer;
int32_t offset = 0;
for (size_t ei = 0; ei < m_enumerators.size(); ++ei)
{
const auto& e = m_enumerators[ei];
const auto replacementLength = static_cast<int32_t>(e.printTo(buffer.data(), buffer.size(), enumIndex));
replaceTerm.insert(e.replaceStrSpan.offset + offset + m_replaceWithEnumeratorOffsets[ei], buffer.data());
offset += replacementLength;
}
}
bool replacedSomething = false;
if (m_flags & UseRegularExpressions)
{
if (_useBoostLib)
{
boost::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? boost::regex::icase | boost::regex::ECMAScript : boost::regex::ECMAScript);
if (m_flags & MatchAllOccurrences)
{
res = boost::regex_replace(wstring(source), pattern, replaceTerm);
}
else
{
res = boost::regex_replace(wstring(source), pattern, replaceTerm, boost::regex_constants::format_first_only);
}
}
else
{
std::wregex pattern(m_searchTerm, (!(m_flags & CaseSensitive)) ? regex_constants::icase | regex_constants::ECMAScript : regex_constants::ECMAScript);
if (m_flags & MatchAllOccurrences)
{
res = regex_replace(wstring(source), pattern, replaceTerm);
}
else
{
res = regex_replace(wstring(source), pattern, replaceTerm, regex_constants::format_first_only);
}
}
replaceTerm = regex_replace(replaceTerm, zeroGroupRegex, L"$1$$$0");
replaceTerm = regex_replace(replaceTerm, otherGroupsRegex, L"$1$0$4");
res = RegexReplaceDispatch[_useBoostLib](source, m_searchTerm, replaceTerm, m_flags & MatchAllOccurrences, !(m_flags & CaseSensitive));
replacedSomething = originalSource != res;
}
else
{
@ -303,16 +359,17 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result)
{
res = sourceToUse.replace(pos, searchTerm.length(), replaceTerm);
pos += replaceTerm.length();
replacedSomething = true;
}
if (!(m_flags & MatchAllOccurrences))
{
break;
}
} while (pos != std::string::npos);
}
hr = SHStrDup(res.c_str(), result);
if (replacedSomething)
enumIndex++;
}
catch (regex_error e)
{

View File

@ -1,9 +1,8 @@
#pragma once
#include "pch.h"
#include <vector>
#include <string>
#include "srwlock.h"
#include "Enumerating.h"
#include "PowerRenameInterfaces.h"
#define DEFAULT_FLAGS 0
@ -27,9 +26,9 @@ public:
IFACEMETHODIMP PutFlags(_In_ DWORD flags);
IFACEMETHODIMP PutFileTime(_In_ SYSTEMTIME fileTime);
IFACEMETHODIMP ResetFileTime();
IFACEMETHODIMP Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result);
IFACEMETHODIMP Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, unsigned long& enumIndex);
static HRESULT s_CreateInstance(_Outptr_ IPowerRenameRegEx **renameRegEx);
static HRESULT s_CreateInstance(_Outptr_ IPowerRenameRegEx** renameRegEx);
protected:
CPowerRenameRegEx();
@ -39,6 +38,7 @@ protected:
void _OnReplaceTermChanged();
void _OnFlagsChanged();
void _OnFileTimeChanged();
HRESULT _OnEnumerateItemsChanged();
size_t _Find(std::wstring data, std::wstring toSearch, bool caseInsensitive, size_t pos);
@ -46,8 +46,9 @@ protected:
DWORD m_flags = DEFAULT_FLAGS;
PWSTR m_searchTerm = nullptr;
PWSTR m_replaceTerm = nullptr;
std::wstring m_RawReplaceTerm;
SYSTEMTIME m_fileTime = {0};
SYSTEMTIME m_fileTime = { 0 };
bool m_useFileTime = false;
CSRWLock m_lock;
@ -55,6 +56,9 @@ protected:
DWORD m_cookie = 0;
std::vector<Enumerator> m_enumerators;
std::vector<int32_t> m_replaceWithEnumeratorOffsets;
struct RENAME_REGEX_EVENT
{
IPowerRenameRegExEvents* pEvents;

View File

@ -86,7 +86,7 @@ bool DoRename(CComPtr<IPowerRenameRegEx>& spRenameRegEx, unsigned long& itemEnum
// Failure here means we didn't match anything or had nothing to match
// Call put_newName with null in that case to reset it
winrt::check_hresult(spRenameRegEx->Replace(sourceName, &newName));
winrt::check_hresult(spRenameRegEx->Replace(sourceName, &newName, itemEnumIndex));
if (useFileTime)
{
@ -166,17 +166,6 @@ bool DoRename(CComPtr<IPowerRenameRegEx>& spRenameRegEx, unsigned long& itemEnum
newNameToUse = nullptr;
}
wchar_t uniqueName[MAX_PATH] = { 0 };
if (newNameToUse != nullptr && (flags & EnumerateItems))
{
unsigned long countUsed = 0;
if (GetEnumeratedFileName(uniqueName, ARRAYSIZE(uniqueName), newNameToUse, nullptr, itemEnumIndex, &countUsed))
{
newNameToUse = uniqueName;
}
itemEnumIndex++;
}
spItem->PutStatus(PowerRenameItemRenameStatus::ShouldRename);
if (newNameToUse != nullptr)
{

View File

@ -25,6 +25,8 @@ namespace
const wchar_t c_replaceText[] = L"ReplaceText";
const wchar_t c_mruEnabled[] = L"MRUEnabled";
const wchar_t c_useBoostLib[] = L"UseBoostLib";
const wchar_t c_lastWindowWidth[] = L"LastWindowWidth";
const wchar_t c_lastWindowHeight[] = L"LastWindowHeight";
}
@ -49,6 +51,8 @@ void CSettings::Save()
jsonData.SetNamedValue(c_searchText, json::value(settings.searchText));
jsonData.SetNamedValue(c_replaceText, json::value(settings.replaceText));
jsonData.SetNamedValue(c_useBoostLib, json::value(settings.useBoostLib));
jsonData.SetNamedValue(c_lastWindowWidth, json::value(settings.lastWindowWidth));
jsonData.SetNamedValue(c_lastWindowHeight, json::value(settings.lastWindowHeight));
json::to_file(jsonFilePath, jsonData);
GetSystemTimeAsFileTime(&lastLoadedTime);
@ -139,6 +143,9 @@ void CSettings::ParseJson()
{
settings.useBoostLib = jsonSettings.GetNamedBoolean(c_useBoostLib);
}
settings.lastWindowWidth = static_cast<int>(jsonSettings.GetNamedNumber(c_lastWindowWidth, DEFAULT_WINDOW_WIDTH));
settings.lastWindowHeight = static_cast<int>(jsonSettings.GetNamedNumber(c_lastWindowHeight, DEFAULT_WINDOW_HEIGHT));
}
catch (const winrt::hresult_error&)
{

View File

@ -6,6 +6,9 @@
class CSettings
{
public:
static constexpr inline int DEFAULT_WINDOW_WIDTH = 1400;
static constexpr inline int DEFAULT_WINDOW_HEIGHT = 800;
CSettings();
inline bool GetEnabled()
@ -25,6 +28,17 @@ public:
Save();
}
inline std::tuple<int, int> GetLastWindowSize() const
{
return std::make_tuple(settings.lastWindowWidth, settings.lastWindowHeight);
}
inline void UpdateLastWindowSize(const int width, const int height)
{
settings.lastWindowWidth = std::max(width, DEFAULT_WINDOW_WIDTH);
settings.lastWindowHeight = std::max(height, DEFAULT_WINDOW_HEIGHT);
}
inline bool GetShowIconOnMenu() const
{
return settings.showIconOnMenu;
@ -134,6 +148,8 @@ private:
unsigned int flags{ 0 };
std::wstring searchText{};
std::wstring replaceText{};
int lastWindowWidth{ DEFAULT_WINDOW_WIDTH };
int lastWindowHeight{ DEFAULT_WINDOW_HEIGHT };
};
void Reload();

View File

@ -21,6 +21,12 @@
#include <shlwapi.h>
#include <ShlObj_core.h>
#include <filesystem>
#include <compare>
#include <regex>
#include <vector>
#include <variant>
#include <charconv>
#include <string>
#include <ProjectTelemetry.h>

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