mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-24 04:12:32 +08:00
Merge remote-tracking branch 'microsoft/main' into dev/feature/projects
This commit is contained in:
commit
12308babc7
@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"xamlstyler.console": {
|
||||
"version": "3.2206.4",
|
||||
"version": "3.2404.2",
|
||||
"commands": [
|
||||
"xstyler"
|
||||
]
|
||||
|
5
.github/actions/spell-check/allow/code.txt
vendored
5
.github/actions/spell-check/allow/code.txt
vendored
@ -221,3 +221,8 @@ artanh
|
||||
arsinh
|
||||
arcosh
|
||||
|
||||
# Linux
|
||||
|
||||
dbus
|
||||
anypass
|
||||
gpg
|
||||
|
8
.github/actions/spell-check/expect.txt
vendored
8
.github/actions/spell-check/expect.txt
vendored
@ -97,6 +97,7 @@ AUTOUPDATE
|
||||
AValid
|
||||
awakeness
|
||||
AWAYMODE
|
||||
azcliversion
|
||||
azman
|
||||
backtracer
|
||||
bbwe
|
||||
@ -121,6 +122,7 @@ BLURREGION
|
||||
bmi
|
||||
bms
|
||||
BNumber
|
||||
BODGY
|
||||
BOKMAL
|
||||
bootstrapper
|
||||
BOOTSTRAPPERINSTALLFOLDER
|
||||
@ -167,6 +169,7 @@ CENTERALIGN
|
||||
ceq
|
||||
certlm
|
||||
certmgr
|
||||
cfp
|
||||
cguid
|
||||
CHANGECBCHAIN
|
||||
changecursor
|
||||
@ -757,6 +760,7 @@ KEYEVENTF
|
||||
KEYIMAGE
|
||||
keynum
|
||||
keyremaps
|
||||
keyvault
|
||||
KILLFOCUS
|
||||
killrunner
|
||||
Knownfolders
|
||||
@ -1347,6 +1351,9 @@ RRF
|
||||
rrr
|
||||
rsop
|
||||
Rsp
|
||||
rstringalnum
|
||||
rstringalpha
|
||||
rstringdigit
|
||||
Rstrtmgr
|
||||
RTB
|
||||
RTLREADING
|
||||
@ -1361,6 +1368,7 @@ runtimeclass
|
||||
runtimeobject
|
||||
runtimepack
|
||||
runtimes
|
||||
ruuid
|
||||
rvm
|
||||
rwin
|
||||
rwl
|
||||
|
94
.github/workflows/msstore-submissions.yml
vendored
94
.github/workflows/msstore-submissions.yml
vendored
@ -5,56 +5,80 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
|
||||
microsoft_store:
|
||||
name: Publish Microsoft Store
|
||||
environment: store
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: BODGY - Set up Gnome Keyring for future Cert Auth
|
||||
run: |-
|
||||
sudo apt-get install -y gnome-keyring
|
||||
export $(dbus-launch --sh-syntax)
|
||||
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --unlock)
|
||||
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh)
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
enable-AzPSSession: true
|
||||
|
||||
- name: Get latest URL from public releases
|
||||
id: releaseVars
|
||||
run: |
|
||||
release=$(curl https://api.github.com/repos/Microsoft/PowerToys/releases | jq '[.[]|select(.name | contains("Release"))][0]')
|
||||
assets=$(jq -n "$release" | jq '.assets')
|
||||
powerToysSetup=$(jq -n "$assets" | jq '[.[]|select(.name | contains("PowerToysUserSetup"))]')
|
||||
echo ::set-output name=powerToysInstallerX64Url::$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("x64"))][0].browser_download_url')
|
||||
echo ::set-output name=powerToysInstallerArm64Url::$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url')
|
||||
echo powerToysInstallerX64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("x64"))][0].browser_download_url') >> $GITHUB_OUTPUT
|
||||
echo powerToysInstallerArm64Url=$(jq -n "$powerToysSetup" | jq -r '[.[]|select(.name | contains("arm64"))][0].browser_download_url') >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: microsoft/setup-msstore-cli@v1
|
||||
|
||||
- name: Fetch Store Credential
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |-
|
||||
az keyvault secret download --vault-name ${{ secrets.AZURE_KEYVAULT_NAME }} -n ${{ secrets.AZURE_AUTH_CERT_NAME }} -f cert.pfx.b64
|
||||
base64 -d < cert.pfx.b64 > cert.pfx
|
||||
|
||||
- name: Configure Store Credentials
|
||||
uses: microsoft/store-submission@v1
|
||||
with:
|
||||
command: configure
|
||||
type: win32
|
||||
seller-id: ${{ secrets.SELLER_ID }}
|
||||
product-id: ${{ secrets.PRODUCT_ID }}
|
||||
tenant-id: ${{ secrets.TENANT_ID }}
|
||||
client-id: ${{ secrets.CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CLIENT_SECRET }}
|
||||
run: |-
|
||||
msstore reconfigure -cfp cert.pfx -c ${{ secrets.AZURE_CLIENT_ID }} -t ${{ secrets.AZURE_TENANT_ID }} -s ${{ secrets.SELLER_ID }}
|
||||
|
||||
- name: Update draft submission
|
||||
uses: microsoft/store-submission@v1
|
||||
with:
|
||||
command: update
|
||||
product-update: '{
|
||||
"packages":[
|
||||
{
|
||||
"packageUrl":"${{ steps.releaseVars.outputs.powerToysInstallerX64Url }}",
|
||||
"languages":["zh-hans", "zh-hant", "en", "cs", "nl", "fr", "pt", "pt-br", "de", "hu", "it", "ja", "ko", "pl", "ru", "es", "tr"],
|
||||
"architectures":["X64"],
|
||||
"installerParameters":"/quiet /norestart",
|
||||
"isSilentInstall":true
|
||||
},
|
||||
{
|
||||
"packageUrl":"${{ steps.releaseVars.outputs.powerToysInstallerArm64Url }}",
|
||||
"languages":["zh-hans", "zh-hant", "en", "cs", "nl", "fr", "pt", "pt-br", "de", "hu", "it", "ja", "ko", "pl", "ru", "es", "tr"],
|
||||
"architectures":["Arm64"],
|
||||
"installerParameters":"/quiet /norestart",
|
||||
"isSilentInstall":true
|
||||
}
|
||||
]
|
||||
}'
|
||||
run: |-
|
||||
msstore submission update ${{ secrets.PRODUCT_ID }} '{
|
||||
"packages":[
|
||||
{
|
||||
"packageUrl":"${{ steps.releaseVars.outputs.powerToysInstallerX64Url }}",
|
||||
"languages":["zh-hans", "zh-hant", "en", "cs", "nl", "fr", "pt", "pt-br", "de", "hu", "it", "ja", "ko", "pl", "ru", "es", "tr"],
|
||||
"architectures":["X64"],
|
||||
"installerParameters":"/quiet /norestart",
|
||||
"isSilentInstall":true
|
||||
},
|
||||
{
|
||||
"packageUrl":"${{ steps.releaseVars.outputs.powerToysInstallerArm64Url }}",
|
||||
"languages":["zh-hans", "zh-hant", "en", "cs", "nl", "fr", "pt", "pt-br", "de", "hu", "it", "ja", "ko", "pl", "ru", "es", "tr"],
|
||||
"architectures":["Arm64"],
|
||||
"installerParameters":"/quiet /norestart",
|
||||
"isSilentInstall":true
|
||||
}
|
||||
]
|
||||
}'
|
||||
|
||||
- name: Publish Submission
|
||||
uses: microsoft/store-submission@v1
|
||||
with:
|
||||
command: publish
|
||||
run: |-
|
||||
msstore submission publish ${{ secrets.PRODUCT_ID }}
|
||||
|
||||
- name: Clean up auth certificate
|
||||
if: always()
|
||||
run: |-
|
||||
rm -f cert.pfx cert.pfx.b64
|
||||
|
@ -41,6 +41,6 @@ jobs:
|
||||
platform: arm64
|
||||
${{ if eq(variables['System.PullRequest.IsFork'], 'False') }}:
|
||||
enableCaching: true
|
||||
- template: ./templates/run-ui-tests-ci.yml
|
||||
parameters:
|
||||
platform: x64
|
||||
# - template: ./templates/run-ui-tests-ci.yml
|
||||
# parameters:
|
||||
# platform: x64
|
||||
|
@ -24,10 +24,10 @@ jobs:
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
pool:
|
||||
demands: ImageOverride -equals SHINE-VS17-Latest
|
||||
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
timeoutInMinutes: 120
|
||||
strategy:
|
||||
maxParallel: 10
|
||||
|
@ -3,10 +3,10 @@ jobs:
|
||||
- job: Precheck
|
||||
pool:
|
||||
demands: ImageOverride -equals SHINE-VS17-Latest
|
||||
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
|
@ -9,10 +9,10 @@ jobs:
|
||||
variables:
|
||||
SrcPath: $(Build.Repository.LocalPath)
|
||||
pool:
|
||||
${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
|
||||
name: SHINE-OSS-Testing-x64
|
||||
${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}:
|
||||
${{ if eq(variables['System.CollectionId'], 'cb55739e-4afe-46a3-970f-1b49d8ee7564') }}:
|
||||
name: SHINE-INT-Testing-x64
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-Testing-x64
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
|
@ -43,7 +43,7 @@ steps:
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.sln"
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: /p:CIBuild=true /target:PowerToysInstaller /bl:$(Build.SourcesDirectory)\msbuild.binlog /p:RunBuildEvents=false /p:PerUser=${{parameters.perUserArg}}
|
||||
msbuildArgs: /p:CIBuild=true /p:BuildProjectReferences=false /target:PowerToysInstaller /bl:$(Build.SourcesDirectory)\msbuild.binlog /p:RunBuildEvents=false /p:PerUser=${{parameters.perUserArg}}
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
clean: false # don't undo our hard work above by deleting the CustomActions dll
|
||||
|
@ -76,7 +76,7 @@ extends:
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
IsPipeline: 1 # The installer uses this to detect whether it should pick up localizations
|
||||
SkipCppCodeAnalysis: 1 # Skip the code analysis to speed up release CI. It runs on PR CI, anyway
|
||||
IsExperimentationLive: 1 # The build and installer use this to turn on experimentation
|
||||
# IsExperimentationLive: 1 # The build and installer use this to turn on experimentation
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
|
@ -85,7 +85,7 @@
|
||||
<UsePrecompiledHeaders Condition="'$(TF_BUILD)' != ''">false</UsePrecompiledHeaders>
|
||||
|
||||
<!-- Change this to bust the cache -->
|
||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202310210737</MSBuildCacheCacheUniverse>
|
||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202407100737</MSBuildCacheCacheUniverse>
|
||||
|
||||
<!--
|
||||
Visual Studio telemetry reads various ApplicationInsights.config files and other files after the project is finished, likely in a detached process.
|
||||
|
@ -86,7 +86,7 @@
|
||||
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="4.145.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="5.50.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
<PackageVersion Include="Vanara.PInvoke.User32" Version="3.4.11" />
|
||||
<PackageVersion Include="Vanara.PInvoke.Shell32" Version="3.4.11" />
|
||||
|
@ -1365,7 +1365,7 @@ EXHIBIT A -Mozilla Public License.
|
||||
- System.ServiceProcess.ServiceController 8.0.0
|
||||
- System.Text.Encoding.CodePages 8.0.0
|
||||
- UnicodeInformation 2.6.0
|
||||
- UnitsNet 4.145.0
|
||||
- UnitsNet 5.50.0
|
||||
- UTF.Unknown 2.5.1
|
||||
- Vanara.PInvoke.Shell32 3.4.11
|
||||
- Vanara.PInvoke.User32 3.4.11
|
||||
|
195
README.md
195
README.md
@ -41,19 +41,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
|
||||
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.82%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F54
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysUserSetup-0.81.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysUserSetup-0.81.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysSetup-0.81.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.81.0/PowerToysSetup-0.81.0-arm64.exe
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.83%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.82%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/V0.82.1/PowerToysUserSetup-0.82.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/V0.82.1/PowerToysUserSetup-0.82.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/V0.82.1/PowerToysSetup-0.82.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/V0.82.1/PowerToysSetup-0.82.1-arm64.exe
|
||||
|
||||
| Description | Filename | sha256 hash |
|
||||
|----------------|----------|-------------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.81.0-x64.exe][ptUserX64] | E62B1EE81954A75355C04E7567B1C9AAD6034AA0C61AD22587F8746D0DC488C8 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.81.0-arm64.exe][ptUserArm64] | 75330A2DB4F9EF9B548B3B58F8BF3262C8C67E680042639BBBBC87EA244F24E2 |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.81.0-x64.exe][ptMachineX64] | 29F151B01FE3C94D4FD75F2D6E8F09A6C0F0962385B83A5A733F6717312F639D |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.81.0-arm64.exe][ptMachineArm64] | FCE636220E1FB854771258D9558E07B7532728AD4C722A7920338DEE60DEECF7 |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.82.1-x64.exe][ptUserX64] | B594C9A32125079186DCE776431E2DC77B896774D2AEE2ACF51BAB8791683485 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.82.1-arm64.exe][ptUserArm64] | 41C1D9C0E8FA7EFFCE6F605C92C143AE933F8C999A2933A4D9D1115B16F14F67 |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.82.1-x64.exe][ptMachineX64] | B8FA7E7C8F88B69E070E234F561D32807634E2E9D782EDBB9DC35F3A454F2264 |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.82.1-arm64.exe][ptMachineArm64] | 58F22306F22CF9878C6DDE6AC128388DF4DFF78B76165E38A695490E55B3C8C4 |
|
||||
|
||||
This is our preferred method.
|
||||
|
||||
@ -99,155 +99,134 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/
|
||||
|
||||
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
|
||||
|
||||
### 0.81 - Build 2024 Update
|
||||
### 0.82 - June 2024 Update
|
||||
|
||||
In this release, we focused on new features, stability and improvements.
|
||||
In this release, we focused on stability and improvements.
|
||||
|
||||
**Highlights**
|
||||
|
||||
- New utility: Advanced Paste - This is an evolution based on feedback of the Paste As Plain Text utility to do more. It can paste as plain text, markdown, or json directly with the new UX or with a direct keystroke invoke. These are fully locally executed. In addition, it now has an AI powered option as well if you wish with the free form text box. The AI feature is 100% opt-in and requires an Open AI key. This new system will allow us to have more freedom in the future to quickly add in new features like pasting an image directly to a file or handle additional meta data types past just text.
|
||||
- Thanks [@craigloewen-msft](https://github.com/craigloewen-msft) for the core functionality and [@niels9001](https://github.com/niels9001) for the UI/UX design!
|
||||
- Command Not Found now uses the PowerShell Gallery release and now supports ARM64. Thanks [@carlos-zamora](https://github.com/carlos-zamora)!
|
||||
- Fixed most accessibility issues opened after the latest accessibility review.
|
||||
- Refactored, packaged and released the main Environment Variables Editor, Hosts File Editor and Registry Preview utilities functionality as controls to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome!
|
||||
|
||||
### General
|
||||
|
||||
- Fixed crashes on older CPUS by updating .NET to 8.0.4. (This was a hotfix for 0.80)
|
||||
- New feature added to PowerRename to allow using sequences of random characters and UUIDs when renaming files. Thanks [@jhirvioja](https://github.com/jhirvioja)!
|
||||
- Improvements in the Paste As JSON feature to better handle other CSV delimiters and converting from ini files. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed UI issues that were reported after upgrading to WPF UI on Color Picker and PowerToys Run.
|
||||
- Bug fixes and stability.
|
||||
|
||||
### Advanced Paste
|
||||
|
||||
- New utility: Advanced Paste - This is an evolution based on feedback of the Paste As Plain Text utility to do more. It can paste as plain text, markdown, or json directly with the new UX or with a direct keystroke invoke. These are fully locally executed. In addition, it now has an AI powered option as well if you wish with the free form text box. The AI feature is 100% opt-in and requires an Open AI key. This new system will allow us to have more freedom in the future to quickly add in new features like pasting an image directly to a file or handle additional meta data types past just text.
|
||||
- Thanks [@craigloewen-msft](https://github.com/craigloewen-msft) for the core functionality and [@niels9001](https://github.com/niels9001) for the UI/UX design!
|
||||
|
||||
### AlwaysOnTop
|
||||
|
||||
- Enable border anti-aliasing. Thanks [@ewancg](https://github.com/ewancg)!
|
||||
- Fixed an issue causing external applications triggering Advanced Paste. (This was a hotfix for 0.81)
|
||||
- Added a GPO rule to disallow using online models in Advanced Paste. (This was a hotfix for 0.81)
|
||||
- Improved CSV delimiter handling and plain text parsing for the Paste as JSON feature. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added support to convert from ini in the Paste as JSON feature. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed a memory leak caused by images not being properly cleaned out from clipboard history.
|
||||
- Added an option to hide the UI when it loses focus. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Improved telemetry to get better data about token usage and if clipboard history is a popular feature. Thanks [@craigloewen-msft](https://github.com/craigloewen-msft)!
|
||||
|
||||
### Color Picker
|
||||
|
||||
- Improved accessibility by making the Settings and Copy to clipboard buttons focusable.
|
||||
- Improved accessibility by supporting picking a color using the keyboard.
|
||||
- Fixed the opaque background corners in the picker that were introduced after the upgrade to WPFUI.
|
||||
|
||||
### Command Not Found
|
||||
### Developer Files Preview (Monaco)
|
||||
|
||||
- Upgraded the Command Not Found to use the new PowerShell Gallery release and support ARM64. Thanks [@carlos-zamora](https://github.com/carlos-zamora)!
|
||||
- Improved the syntax highlight for .gitignore files. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Checking for the sticky scroll option in code behind was being done twice. Removed one of the checks. Thanks [@downarowiczd](https://github.com/downarowiczd)!
|
||||
|
||||
### Environment Variables Editor
|
||||
|
||||
- Refactored, packaged and released the main Environment Variables Editor functionality as a control to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome!
|
||||
|
||||
### FancyZones
|
||||
|
||||
- Fixed window wrap around behavior when overriding Windows key and arrow shortcuts on single monitor scenarios. Thanks [@DanRosenberry](https://github.com/DanRosenberry)!
|
||||
- Improved accessibility of the editor by listing the keyboard shortcuts in the Canvas Editor.
|
||||
- Added clarity to the UI section tooltips. Thanks [@anson-poon](https://github.com/anson-poon)!
|
||||
|
||||
### File Explorer add-ons
|
||||
|
||||
- Updated Monaco to 0.47 and added the new sticky scroll setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)!
|
||||
- Added the new font size setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)!
|
||||
- Added support for .srt (subtitle) file previewing in DevFiles viewer. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Fixed a crash when the preview handlers received a 64-bit handle from the OS. Thanks [@z4pf1sh](https://github.com/z4pf1sh)!
|
||||
- Fixed a crash when trying to update window bounds and File Explorer already disposed the preview.
|
||||
|
||||
### Find My Mouse
|
||||
|
||||
- Added the option to have to use the Windows + Control keys to activate. Thanks [@Gentoli](https://github.com/Gentoli)!
|
||||
|
||||
### Hosts File Editor
|
||||
|
||||
- Refactored, packaged and released the main Hosts File Editor functionality as a control to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome!
|
||||
|
||||
### Image Resizer
|
||||
|
||||
- Supported narrator announcing the checkboxes in the UI and the sizes combobox. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Improved accessibility by increasing contrast in the text color of combobox items.
|
||||
- Improved spacing definitions in the UI so that hosts name are not hidden when resizing and icons are well aligned. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Changed the additional lines dialog to show the horizontal scrollbar instead of wrapping contents. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Improved the duplication check's logic to improve performance and take into account features that were introduced after it. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### Installer
|
||||
|
||||
- Fixed some install failures when the folders the DSC module is to be installed in isn't accessible by the WiX installer. (This was a hotfix for 0.80)
|
||||
- Detecting install location for DSC now uses registry instead of WMI to improve performance. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed an error causing the machine scope installer to not install correctly in machines where the documents folder is in a UNC network path. We're still working in a fix for the user scope installer.
|
||||
|
||||
### Keyboard Manager
|
||||
|
||||
- Fixed startup crashes in the editor when the Visual C++ Redistributable wasn't installed. (This was a hotfix for 0.80)
|
||||
- Fixed an accessibility issue where the first button wasn't focused after adding a new row in the editor.
|
||||
- Environment Variables are now expanded in arguments of programs started through a shortcut. Thanks [@HydroH](https://github.com/HydroH)!
|
||||
|
||||
### Paste as Plain Text
|
||||
|
||||
- Paste as Plain Text was removed as a separate utility, since its functionality is now part of the Advanced Paste utility.
|
||||
- Fixed the remaining install failures when the folders the DSC module is to be installed in isn't accessible by the WiX installer for user scope installations.
|
||||
- Fixed an issue causing ARM 64 uninstall process to not correctly finding powershell 7 to run uninstall scripts.
|
||||
|
||||
### Peek
|
||||
|
||||
- Updated icons, tweaked UI and refactored internal code. Thanks [@Jay-o-Way](https://github.com/Jay-o-Way)!
|
||||
- Updated Monaco to 0.47 and added the new sticky scroll setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)!
|
||||
- Added the new font size setting for DevFiles viewer. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)!
|
||||
- Upgrade the SharpCompress dependency to 0.37.2 and fixed archive parsing. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed aliasing in the image viewer.
|
||||
- Added support for .srt (subtitle) file previewing in DevFiles viewer. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Prevent activating Peek when the user is renaming a file. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Added support to preview special folders like Recycle Bin and My PC instead of throwing an error.
|
||||
- Fixed a crash caused by double releasing a COM object from the module interface.
|
||||
|
||||
### Power Rename
|
||||
|
||||
- Fixed the descriptions that were mixed up in the regex helper (\S and \w).
|
||||
- Improved apostrophe character handling for the Capitalize and Titlecase renaming flags. Thanks [@anthonymonforte](https://github.com/anthonymonforte)!
|
||||
- Added a feature to allow using sequences of random characters or UUIDs when renaming files. Thanks [@jhirvioja](https://github.com/jhirvioja)!
|
||||
|
||||
### PowerToys Run
|
||||
|
||||
- Added support for UNC paths starting with // in the Folder plugin. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed the plugin load failed message to list the failed plugins. Thanks [@belkiss](https://github.com/belkiss)!
|
||||
- Icons for MSIX packages are now updated when a package update is detected. Thanks [@HydroH](https://github.com/HydroH)!
|
||||
- Use Mica backdrop instead of Acrylic to fix random crashes caused by the Windows composition being momentarily turned off.
|
||||
- Improved accessibility in the results list action buttons by improving contrast of hovered/focused buttons.
|
||||
- Improved the plugin descriptions for consistency in the UI. Thanks [@HydroH](https://github.com/HydroH)!
|
||||
- Fixed UI scaling for different dpi scenarios.
|
||||
- Fixed crash on a racing condition when updating UWP icon paths in the Program plugin. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed PowerToys Run hanging when trying to close an unresponsive window in the WindowWalker plugin. Thanks [@GhostVaibhav](https://github.com/GhostVaibhav)!
|
||||
- Fixed the example in the UnitConverter description to reduce confusion with the inches abbreviation (now uses "to" instead of "in"). Thanks [@acekirkpatrick](https://github.com/acekirkpatrick)!
|
||||
- Brought the acrylic background back and applied a proper fix to the titlebar accent showing through transparency.
|
||||
- Fixed an issue causing the transparency from the UI disappearing after some time.
|
||||
|
||||
### Quick Accent
|
||||
|
||||
- Added support for the Esperanto character set. Thanks [@salutontalk](https://github.com/salutontalk) and [@ccmywish](https://github.com/ccmywish)!
|
||||
- Added the ǽ and ϑ characters. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added support for the Crimean Tatar character set. Thanks [@cor-bee](https://github.com/cor-bee)!
|
||||
- Added the Numero symbol and double acute accent character. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added the International Phonetic Alphabet characters. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Fixed the character description center positioning. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added feminine and masculine ordinal indicator characters to the Portuguese character set. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
|
||||
|
||||
### Registry Preview
|
||||
### Screen Ruler
|
||||
|
||||
- Refactored, packaged and released the main Registry Preview functionality as a control to be integrated into DevHome. Thanks [@dabhattimsft](https://github.com/dabhattimsft) for validating and integrating into DevHome!
|
||||
|
||||
### Text Extractor
|
||||
|
||||
- Fixed an issue causing the Settings page to not be opened when clicking the Settings button in Text Extractor's overlay. (This was a hotfix for 0.80)
|
||||
- Updated the default activation hotkey to Win+Control+Shift+M, in order to not conflict with the Windows shortcut that restores minimized windows (Win+Shift+M). Thanks [@nx-frost](https://github.com/nx-frost)!
|
||||
|
||||
### Settings
|
||||
|
||||
- Improved UI ordering of the File Explorer add-ons. Thanks [@niels9001](https://github.com/niels9001)!
|
||||
- Applied fixes to theme overriding and cleaned up unneeded code. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed misspells in references to the Hosts File Editor utility. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Improved accessibility of the Select Folder button in the Settings Backup UI.
|
||||
- Improved accessibility by improving focus and tab navigation in the ColorPicker page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Added a description to the fallback encoder setting in the Image Resizer page. Thanks [@Kissaki](https://github.com/Kissaki)!
|
||||
- Refactored and improved performance in the PowerToys Run plugins UI in the Settings page. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed a crash when a user cleared the contents of a Number Box in the PowerToys Run plugins additional options. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Update the PATH environment variables with the user scope PATH when entering the Command Not Found page to improve PowerShell detection.
|
||||
- Disabled the UI to enable/disable clipboard history in the Advanced Paste settings page when clipboard history is disabled by GPO in the system. (This was a hotfix for 0.81)
|
||||
- Updated Advanced Paste's Settings and OOBE page to clarify that the AI use is optional and opt-in. (This was a hotfix for 0.81)
|
||||
- Corrected a spelling fix in Advanced Paste's settings page. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added localization support for the "Configure OpenAI Key" button in Advanced Paste's settings page. Thanks [@zetaloop](https://github.com/zetaloop)!
|
||||
- Fixed extra GPO warnings being shown in Advanced Paste's settings page even if the module is disabled. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed a crash when a PowerToys Run plugin icon path is badly formed.
|
||||
- Disabled the experimentation paths in code behind to improve performance, since there's no current experimentation going on.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Added the WebSearchShortcut plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@Daydreamer-riri](https://github.com/Daydreamer-riri)!
|
||||
- Updated COMMUNITY.md with the project managers that are part of the core team.
|
||||
- Improved the DSC samples.
|
||||
- Added the 1Password plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@KairuDeibisu](https://github.com/KairuDeibisu)!
|
||||
- Added the UnicodeInput plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@nathancartlidge](https://github.com/nathancartlidge)!
|
||||
- Adjusted the readme and release notes to clarify use of AI on Advanced Paste. (This was a hotfix for 0.81)
|
||||
- Added the Edge Workspaces plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@quachpas](https://github.com/quachpas)!
|
||||
- Removed the deprecated Guid plugin from PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@abduljawada](https://github.com/abduljawada)!
|
||||
- Added the PowerHexInspector plugin to PowerToys Run thirdPartyRunPlugins.md docs. Thanks [@NaroZeol](https://github.com/NaroZeol)!
|
||||
- Fixed a broken link in the communication-with-modules.md file. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Updated COMMUNITY.md with missing and former members.
|
||||
|
||||
### Development
|
||||
|
||||
- Updated System.Drawing.Common to 8.0.5 to fix CI builds after the .NET 8.0.5 upgrade was released.
|
||||
- Fixed file permissions when doing a build using cache on PR CI. Thanks [@dfederm](https://github.com/dfederm)!
|
||||
- Removed the Test SDK reference on ARM64 to fix local building for ARM64. Thanks [@dfederm](https://github.com/dfederm)!
|
||||
- Replaced make_pair with RemapBufferRow in Keyboard Manager internal code. Thanks [@masaru-iritani](https://github.com/masaru-iritani)!
|
||||
- Added CODEOWNERS file to protect sensitive parts of the repo. Thanks [@htcfreek](https://github.com/htcfreek) for the help in figuring out how to make the spellcheck folder an exception!
|
||||
- Added comments in code. to make it clear what the error badge in PowerToys Run plugin list in Settings means. Thanks [@Jay-o-Way](https://github.com/Jay-o-Way)!
|
||||
- Enabled caching by default in the PR CI pipelines. Thanks [@dfederm](https://github.com/dfederm)!
|
||||
- Disabled caching for PR started from forks, since those were failing. Thanks [@dfederm](https://github.com/dfederm)!
|
||||
- Removed baseline files for policy checking and turned on the "TSA" process in the release pipelines instead.
|
||||
- Added caching of nuget packages in the PR CI pipelines. Thanks [@dfederm](https://github.com/dfederm)!
|
||||
- Updated the release CI pipelines TouchdownBuildTask to v3.
|
||||
- Moved the release CI pipelines to ESRPv5.
|
||||
- Added a policy for GitHub Copilot Workspaces for the repo on GitHub. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)!
|
||||
- Fixed ci UI tests to point to the correct Visual Studio vstest location after a Visual Studio upgrade. (This was a hotfix for 0.81)
|
||||
- Updated System.Drawing.Common to 8.0.6 to fix CI builds after the .NET 8.0.6 upgrade was released.
|
||||
- Removed an incorrect file reference to long removed documentation from the solution file. Thanks [@Kissaki](https://github.com/Kissaki)!
|
||||
- Upgraded Windows App SDK to 1.5.3.
|
||||
- Removed use of the BinaryFormatter API from Mouse Without Borders, which is expected to be deprecated in .NET 9.
|
||||
- The user scope installer is now sent to the Microsoft store instead of the machine scope installer.
|
||||
- Refactored Mouse Jump's internal code to allow for a future introduction of customizable appearance features. Thanks [@mikeclayton](https://github.com/mikeclayton)!
|
||||
- Removed a noisy error from spell check ci runs.
|
||||
- Improved the ci agent pool selection code.
|
||||
- Updated Xamlstyler.console to 3.2404.2. Thanks [@Jvr2022](https://github.com/Jvr2022)!
|
||||
- Updated UnitsNet to 5.50.0 Thanks [@Jvr2022](https://github.com/Jvr2022)!
|
||||
- Replaced LPINPUT with std::vector of INPUT instances in Keyboard Manager internal code. Thanks [@masaru-iritani](https://github.com/masaru-iritani)!
|
||||
- Improved the Microsoft Store submission ci action to use the proper cli and authentication.
|
||||
|
||||
#### What is being planned for version 0.82
|
||||
#### What is being planned for version 0.83
|
||||
|
||||
For [v0.82][github-next-release-work], we'll work on the items below:
|
||||
For [v0.83][github-next-release-work], we'll work on the items below:
|
||||
|
||||
- Stability / bug fixes
|
||||
- New utility: Dev Projects
|
||||
- Language selection
|
||||
- New module: File Actions Menu
|
||||
|
||||
|
@ -47,7 +47,16 @@ registerAdditionalNewLanguage("id", [".fileExtension"], idDefinition(), monaco)
|
||||
|
||||
* The id can be anything. Recommended is one of the file extensions. For example "php" or "reg".
|
||||
|
||||
4. Execute the steps described in the [monaco_languages.json](#monaco_languagesjson) section.
|
||||
4. In case you wish to add a custom color for a token, you can do so by adding the following line to [`customTokenColors.js`](/src/common/FilePreviewCommon/Assets/Monaco/customTokenColors.js):
|
||||
```javascript
|
||||
{token: 'token-name', foreground: 'ff0000'}
|
||||
```
|
||||
> Replace `token-name` with the name of the token and `ff0000` with the hex code of the desired color.
|
||||
> Note: you can also specify a `background` and a `fontStyle` attribute for your token.
|
||||
|
||||
* Keep in mind that these rules apply to all languages. Therefore, you should not change the colors of any default tokens. Instead, create new tokens specific to the language you are adding.
|
||||
|
||||
5. Execute the steps described in the [monaco_languages.json](#monaco_languagesjson) section.
|
||||
|
||||
### Add a new file extension to an existing language
|
||||
|
||||
|
@ -72,7 +72,7 @@ In order to test the remapping logic, a mocked keyboard input handler had to be
|
||||
The [`MockedInput`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/MockedInput.h) class uses a 256 size `bool` vector to store the key state for each key code. Identifying the foreground process is mocked by simply setting and getting a string value for the name of the current process.
|
||||
|
||||
[To mock the `SendInput` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/MockedInput.cpp#L10-L110), the steps for processing the input are as follows. This implementation is based on public documentation for SendInput and the behavior of key messages and keyboard hooks:
|
||||
- Iterate over all the inputs in the INPUT array argument
|
||||
- Iterate over all the inputs in the `INPUT` vector argument.
|
||||
- If the event is a key up event, then it is considered [`WM_SYSKEYUP`](https://learn.microsoft.com/windows/win32/inputdev/wm-syskeyup) if Alt is held down, otherwise it is `WM_KEYUP`.
|
||||
- If the event is a key down event, then it is considered [`WM_SYSKEYDOWN`](https://learn.microsoft.com/windows/win32/inputdev/wm-syskeydown) if either Alt is held down or if it is F10, otherwise it is `WM_KEYDOWN`.
|
||||
- An optional function which can be set on the `MockedInput` handler can be used to test for the number of times a key event is received by the system with a particular condition using [`sendVirtualInputCallCondition`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/MockedInput.cpp#L48-L52).
|
||||
|
@ -21,7 +21,7 @@ $fileWxs = Get-Content $wxsFilePath;
|
||||
|
||||
$fileExclusionList = @("*.pdb", "*.lastcodeanalysissucceeded", "createdump.exe", "powertoys.exe")
|
||||
|
||||
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "monacoSpecialLanguages.js", "*.pri")
|
||||
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "monacoSpecialLanguages.js", "customTokenColors.js", "*.pri")
|
||||
|
||||
$dllsToIgnore = @("System.CodeDom.dll", "WindowsBase.dll")
|
||||
|
||||
|
@ -357,7 +357,7 @@ UINT __stdcall InstallDSCModuleCA(MSIHANDLE hInstall)
|
||||
ExitOnFailure(hr, "Unable to determine Powershell modules path");
|
||||
}
|
||||
|
||||
const auto modulesPath = baseModulesPath / L"Microsoft.PowerToys.Configure" / get_product_version();
|
||||
const auto modulesPath = baseModulesPath / L"Microsoft.PowerToys.Configure" / get_product_version(false);
|
||||
|
||||
std::error_code errorCode;
|
||||
fs::create_directories(modulesPath, errorCode);
|
||||
@ -411,7 +411,7 @@ UINT __stdcall UninstallDSCModuleCA(MSIHANDLE hInstall)
|
||||
}
|
||||
|
||||
const auto powerToysModulePath = baseModulesPath / L"Microsoft.PowerToys.Configure";
|
||||
const auto versionedModulePath = powerToysModulePath / get_product_version();
|
||||
const auto versionedModulePath = powerToysModulePath / get_product_version(false);
|
||||
|
||||
std::error_code errorCode;
|
||||
|
||||
|
@ -12,5 +12,10 @@ namespace Common.UI
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 22000;
|
||||
}
|
||||
|
||||
public static bool IsGreaterThanWindows11_21H2()
|
||||
{
|
||||
return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build > 22000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/^#.*$/, 'comment'],
|
||||
[/^\s*!.*/, 'invalid'],
|
||||
[/^\s*[^#]+/, "tag"]
|
||||
[/.*((?<!(^|\/))\*\*.*|\*\*(?!(\/|$))).*/, 'invalid'],
|
||||
[/((?:^!\s*(?:\\\s|\S)+)?)((?:^\s*(?:\\\s|\S)+)?)((?:\s+(?:\\\s|\S)+)*)/, ['custom-gitignore.negation', 'tag', 'invalid']]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,3 @@
|
||||
export const customTokenColors = [
|
||||
{token: 'custom-gitignore.negation', foreground: 'c00ce0'}
|
||||
];
|
@ -6,7 +6,7 @@
|
||||
|
||||
// Get URL parameters:
|
||||
// `code` contains the code of the file in base64 encoded
|
||||
// `theme` can be "light" or "dark"
|
||||
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
|
||||
// `lang` is the language of the file
|
||||
// `wrap` if the editor is wrapping or not
|
||||
|
||||
@ -59,19 +59,29 @@
|
||||
<script src="http://[[PT_URL]]/monacoSpecialLanguages.js" type="module"></script>
|
||||
<script type="module">
|
||||
var editor;
|
||||
import { registerAdditionalLanguages } from "http://[[PT_URL]]/monacoSpecialLanguages.js"
|
||||
import { registerAdditionalLanguages } from 'http://[[PT_URL]]/monacoSpecialLanguages.js';
|
||||
import { customTokenColors } from 'http://[[PT_URL]]/customTokenColors.js';
|
||||
require.config({ paths: { vs: 'http://[[PT_URL]]/monacoSRC/min/vs' } });
|
||||
require(['vs/editor/editor.main'], async function () {
|
||||
await registerAdditionalLanguages(monaco)
|
||||
|
||||
// Creates a theme to handle custom tokens
|
||||
monaco.editor.defineTheme('theme', {
|
||||
base: theme, // Sets the base theme to "vs" or "vs-dark" depending on the user's preference
|
||||
inherit: true,
|
||||
rules: customTokenColors,
|
||||
colors: {} // `colors` is a required attribute
|
||||
});
|
||||
|
||||
// Creates the editor
|
||||
// For all parameters: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
|
||||
// For all parameters: https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html
|
||||
editor = monaco.editor.create(document.getElementById('container'), {
|
||||
value: code, // Sets content of the editor
|
||||
language: lang, // Sets language fof the code
|
||||
language: lang, // Sets language of the code
|
||||
readOnly: true, // Sets to readonly
|
||||
theme: theme, // Sets editor theme
|
||||
theme: 'theme', // Sets editor theme
|
||||
minimap: {enabled: false}, // Disables minimap
|
||||
lineNumbersMinChars: "3", //Width of the line numbers
|
||||
lineNumbersMinChars: '3', // Width of the line numbers
|
||||
scrollbar: {
|
||||
// Deactivate shadows
|
||||
shadows: false,
|
||||
@ -79,17 +89,16 @@
|
||||
// Render scrollbar automatically
|
||||
vertical: 'auto',
|
||||
horizontal: 'auto',
|
||||
|
||||
},
|
||||
stickyScroll: {enabled: stickyScroll},
|
||||
fontSize: fontSize,
|
||||
wordWrap: (wrap?"on":"off") // Word wraps
|
||||
wordWrap: (wrap ? 'on' : 'off') // Word wraps
|
||||
});
|
||||
window.onresize = function (){
|
||||
window.onresize = () => {
|
||||
editor.layout();
|
||||
};
|
||||
|
||||
// Add switch wrap button to context menu
|
||||
// Add toggle wrap button to context menu
|
||||
editor.addAction({
|
||||
id: 'text-wrap',
|
||||
|
||||
@ -101,17 +110,17 @@
|
||||
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
|
||||
keybindingContext: null,
|
||||
|
||||
contextMenuGroupId: 'cutcopypaste',
|
||||
contextMenuGroupId: 'cutcopypaste',
|
||||
|
||||
contextMenuOrder: 100,
|
||||
contextMenuOrder: 100,
|
||||
|
||||
// Method that will be executed when the action is triggered.
|
||||
// @param editor The editor instance is passed in as a convenience
|
||||
run: function (ed) {
|
||||
if(wrap){
|
||||
editor.updateOptions({ wordWrap: "off" })
|
||||
}else{
|
||||
editor.updateOptions({ wordWrap: "on" })
|
||||
if (wrap) {
|
||||
editor.updateOptions({ wordWrap: 'off' })
|
||||
} else {
|
||||
editor.updateOptions({ wordWrap: 'on' })
|
||||
}
|
||||
wrap = !wrap;
|
||||
}
|
||||
@ -120,11 +129,11 @@
|
||||
onContextMenu();
|
||||
});
|
||||
|
||||
function onContextMenu(){
|
||||
function onContextMenu() {
|
||||
// Hide context menu items
|
||||
// Code modified from https://stackoverflow.com/questions/48745208/disable-cut-and-copy-in-context-menu-in-monaco-editor/65413517#65413517
|
||||
let menus = require('vs/platform/actions/common/actions').MenuRegistry._menuItems
|
||||
let contextMenuEntry = [...menus].find(entry => entry[0].id == "EditorContext")
|
||||
let contextMenuEntry = [...menus].find(entry => entry[0].id == 'EditorContext')
|
||||
let contextMenuLinks = contextMenuEntry[1]
|
||||
|
||||
let removableIds = ['editor.action.clipboardCutAction', 'editor.action.formatDocument', 'editor.action.formatSelection', 'editor.action.quickCommand', 'editor.action.quickOutline', 'editor.action.refactor', 'editor.action.sourceAction', 'editor.action.rename', undefined, 'editor.action.revealDefinition', 'editor.action.revealDeclaration', 'editor.action.goToTypeDefinition', 'editor.action.goToImplementation', 'editor.action.goToReferences', 'editor.action.changeAll']
|
||||
|
@ -34,6 +34,9 @@
|
||||
<None Update="Assets\Monaco\monacoSpecialLanguages.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Assets\Monaco\customTokenColors.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Content Include="Assets\Monaco\monacoSRC\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -27,18 +27,18 @@ enum class version_architecture
|
||||
version_architecture get_current_architecture();
|
||||
const wchar_t* get_architecture_string(const version_architecture);
|
||||
|
||||
inline std::wstring get_product_version()
|
||||
inline std::wstring get_product_version(bool includeV = true)
|
||||
{
|
||||
static std::wstring version = L"v" + std::to_wstring(VERSION_MAJOR) +
|
||||
static std::wstring version = (includeV ? L"v" : L"") + std::to_wstring(VERSION_MAJOR) +
|
||||
L"." + std::to_wstring(VERSION_MINOR) +
|
||||
L"." + std::to_wstring(VERSION_REVISION);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
inline std::wstring get_std_product_version()
|
||||
inline std::wstring get_std_product_version(bool includeV = true)
|
||||
{
|
||||
static std::wstring version = L"v" + std::to_wstring(VERSION_MAJOR) +
|
||||
static std::wstring version = (includeV ? L"v" : L"") + std::to_wstring(VERSION_MAJOR) +
|
||||
L"." + std::to_wstring(VERSION_MINOR) +
|
||||
L"." + std::to_wstring(VERSION_REVISION) + L".0";
|
||||
|
||||
|
@ -3,15 +3,14 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Settings;
|
||||
using AdvancedPaste.ViewModels;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Graphics;
|
||||
using WinUIEx;
|
||||
using static AdvancedPaste.Helpers.NativeMethods;
|
||||
@ -47,6 +46,7 @@ namespace AdvancedPaste
|
||||
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddSingleton<OptionsViewModel>();
|
||||
services.AddSingleton<IUserSettings, UserSettings>();
|
||||
}).Build();
|
||||
|
||||
viewModel = GetService<OptionsViewModel>();
|
||||
|
@ -2,7 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
@ -20,14 +19,13 @@ namespace AdvancedPaste.Controls
|
||||
public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
private UserSettings _userSettings;
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
public static readonly DependencyProperty PromptProperty = DependencyProperty.Register(
|
||||
nameof(Prompt),
|
||||
typeof(string),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: string.Empty));
|
||||
nameof(Prompt),
|
||||
typeof(string),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: string.Empty));
|
||||
|
||||
public OptionsViewModel ViewModel { get; private set; }
|
||||
|
||||
@ -38,10 +36,10 @@ namespace AdvancedPaste.Controls
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
|
||||
nameof(PlaceholderText),
|
||||
typeof(string),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: string.Empty));
|
||||
nameof(PlaceholderText),
|
||||
typeof(string),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: string.Empty));
|
||||
|
||||
public string PlaceholderText
|
||||
{
|
||||
@ -50,10 +48,10 @@ namespace AdvancedPaste.Controls
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(
|
||||
nameof(Footer),
|
||||
typeof(object),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: null));
|
||||
nameof(Footer),
|
||||
typeof(object),
|
||||
typeof(PromptBox),
|
||||
new PropertyMetadata(defaultValue: null));
|
||||
|
||||
public object Footer
|
||||
{
|
||||
@ -65,7 +63,7 @@ namespace AdvancedPaste.Controls
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_userSettings = new UserSettings();
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
ViewModel = App.GetService<OptionsViewModel>();
|
||||
}
|
||||
@ -80,8 +78,6 @@ namespace AdvancedPaste.Controls
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteGenerateCustomFormatEvent());
|
||||
|
||||
VisualStateManager.GoToState(this, "LoadingState", true);
|
||||
string inputInstructions = InputTxtBox.Text;
|
||||
ViewModel.SaveQuery(inputInstructions);
|
||||
|
@ -3,21 +3,20 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AdvancedPaste.Helpers;
|
||||
using AdvancedPaste.Settings;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using WinUIEx;
|
||||
using WinUIEx.Messaging;
|
||||
using static AdvancedPaste.Helpers.NativeMethods;
|
||||
|
||||
namespace AdvancedPaste
|
||||
{
|
||||
public sealed partial class MainWindow : WindowEx, IDisposable
|
||||
{
|
||||
private WindowMessageMonitor _msgMonitor;
|
||||
private readonly WindowMessageMonitor _msgMonitor;
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
@ -25,6 +24,8 @@ namespace AdvancedPaste
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_userSettings = App.GetService<IUserSettings>();
|
||||
|
||||
AppWindow.SetIcon("Assets/AdvancedPaste/AdvancedPaste.ico");
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(titleBar);
|
||||
@ -32,6 +33,8 @@ namespace AdvancedPaste
|
||||
var loader = ResourceLoaderInstance.ResourceLoader;
|
||||
Title = loader.GetString("WindowTitle");
|
||||
|
||||
Activated += OnActivated;
|
||||
|
||||
_msgMonitor = new WindowMessageMonitor(this);
|
||||
_msgMonitor.WindowMessageReceived += (_, e) =>
|
||||
{
|
||||
@ -47,6 +50,14 @@ namespace AdvancedPaste
|
||||
WindowHelpers.BringToForeground(this.GetWindowHandle());
|
||||
}
|
||||
|
||||
private void OnActivated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
if (_userSettings.CloseAfterLosingFocus && args.WindowActivationState == WindowActivationState.Deactivated)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
@ -66,11 +77,15 @@ namespace AdvancedPaste
|
||||
|
||||
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
||||
{
|
||||
Windows.Win32.PInvoke.ShowWindow((Windows.Win32.Foundation.HWND)this.GetWindowHandle(), Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_HIDE);
|
||||
|
||||
Hide();
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void Hide()
|
||||
{
|
||||
Windows.Win32.PInvoke.ShowWindow(new Windows.Win32.Foundation.HWND(this.GetWindowHandle()), Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_HIDE);
|
||||
}
|
||||
|
||||
public void SetFocus()
|
||||
{
|
||||
MainPage.CustomFormatTextBox.InputTxtBox.Focus(FocusState.Programmatic);
|
||||
|
@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdvancedPaste.Helpers;
|
||||
@ -86,6 +87,11 @@ namespace AdvancedPaste.Pages
|
||||
|
||||
_dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
// Clear to avoid leaks due to Garbage Collection not clearing the bitmap from memory. Fix for https://github.com/microsoft/PowerToys/issues/33423
|
||||
clipboardHistory.Where(x => x.Image is not null)
|
||||
.ToList()
|
||||
.ForEach(x => x.Image.ClearValue(BitmapImage.UriSourceProperty));
|
||||
|
||||
clipboardHistory.Clear();
|
||||
|
||||
foreach (var item in items)
|
||||
@ -225,6 +231,7 @@ namespace AdvancedPaste.Pages
|
||||
var item = e.ClickedItem as ClipboardItem;
|
||||
if (item is not null)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteClipboardItemClicked());
|
||||
if (!string.IsNullOrEmpty(item.Content))
|
||||
{
|
||||
ClipboardHelper.SetClipboardTextContent(item.Content);
|
||||
|
@ -33,6 +33,8 @@ namespace AdvancedPaste.Helpers
|
||||
|
||||
private string _openAIKey;
|
||||
|
||||
private string _modelName = "gpt-3.5-turbo-instruct";
|
||||
|
||||
public bool IsAIEnabled => !string.IsNullOrEmpty(this._openAIKey);
|
||||
|
||||
public AICompletionsHelper()
|
||||
@ -69,14 +71,14 @@ namespace AdvancedPaste.Helpers
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string GetAICompletion(string systemInstructions, string userMessage)
|
||||
private Response<Completions> GetAICompletion(string systemInstructions, string userMessage)
|
||||
{
|
||||
OpenAIClient azureAIClient = new OpenAIClient(_openAIKey);
|
||||
|
||||
var response = azureAIClient.GetCompletions(
|
||||
new CompletionsOptions()
|
||||
{
|
||||
DeploymentName = "gpt-3.5-turbo-instruct",
|
||||
DeploymentName = _modelName,
|
||||
Prompts =
|
||||
{
|
||||
systemInstructions + "\n\n" + userMessage,
|
||||
@ -90,7 +92,7 @@ namespace AdvancedPaste.Helpers
|
||||
Console.WriteLine("Cut off due to length constraints");
|
||||
}
|
||||
|
||||
return response.Value.Choices[0].Text;
|
||||
return response;
|
||||
}
|
||||
|
||||
public AICompletionsResponse AIFormatString(string inputInstructions, string inputString)
|
||||
@ -109,10 +111,16 @@ Output:
|
||||
";
|
||||
|
||||
string aiResponse = null;
|
||||
Response<Completions> rawAIResponse = null;
|
||||
int apiRequestStatus = (int)HttpStatusCode.OK;
|
||||
try
|
||||
{
|
||||
aiResponse = this.GetAICompletion(systemInstructions, userMessage);
|
||||
rawAIResponse = this.GetAICompletion(systemInstructions, userMessage);
|
||||
aiResponse = rawAIResponse.Value.Choices[0].Text;
|
||||
|
||||
int promptTokens = rawAIResponse.Value.Usage.PromptTokens;
|
||||
int completionTokens = rawAIResponse.Value.Usage.CompletionTokens;
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteGenerateCustomFormatEvent(promptTokens, completionTokens, _modelName));
|
||||
}
|
||||
catch (Azure.RequestFailedException error)
|
||||
{
|
||||
|
@ -9,5 +9,7 @@ namespace AdvancedPaste.Settings
|
||||
public bool ShowCustomPreview { get; }
|
||||
|
||||
public bool SendPasteKeyCombination { get; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ namespace AdvancedPaste.Helpers
|
||||
{
|
||||
internal static class JsonHelper
|
||||
{
|
||||
// Ini parts regex
|
||||
private static readonly Regex IniSectionNameRegex = new Regex(@"^\[(.+)\]");
|
||||
private static readonly Regex IniValueLineRegex = new Regex(@"(.+?)\s*=\s*(.*)");
|
||||
|
||||
// List of supported CSV delimiters and Regex to detect separator property
|
||||
private static readonly char[] CsvDelimArry = [',', ';', '\t'];
|
||||
private static readonly Regex CsvSepIdentifierRegex = new Regex(@"^sep=(.)$", RegexOptions.IgnoreCase);
|
||||
@ -45,6 +49,7 @@ namespace AdvancedPaste.Helpers
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.LoadXml(text);
|
||||
Logger.LogDebug("Converted from XML.");
|
||||
jsonText = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.Indented);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -52,6 +57,75 @@ namespace AdvancedPaste.Helpers
|
||||
Logger.LogError("Failed parsing input as xml", ex);
|
||||
}
|
||||
|
||||
// Try convert Ini
|
||||
// (Must come before CSV that ini is not false detected as CSV.)
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonText))
|
||||
{
|
||||
var ini = new Dictionary<string, Dictionary<string, string>>();
|
||||
var lastSectionName = string.Empty;
|
||||
|
||||
string[] lines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Skipp comment lines.
|
||||
// (Comments are lines that starts with a semicolon.
|
||||
// This also skips commented key-value-pairs.)
|
||||
lines = lines.Where(l => !l.StartsWith(';')).ToArray();
|
||||
|
||||
// Validate content as ini
|
||||
// (First line is a section name and second line is a section name or a key-value-pair.
|
||||
// For the second line we check both, in case the first ini section is empty.)
|
||||
if (lines.Length >= 2 && IniSectionNameRegex.IsMatch(lines[0]) &&
|
||||
(IniSectionNameRegex.IsMatch(lines[1]) || IniValueLineRegex.IsMatch(lines[1])))
|
||||
{
|
||||
// Parse and convert Ini
|
||||
foreach (string line in lines)
|
||||
{
|
||||
Match lineSectionNameCheck = IniSectionNameRegex.Match(line);
|
||||
Match lineKeyValuePairCheck = IniValueLineRegex.Match(line);
|
||||
|
||||
if (lineSectionNameCheck.Success)
|
||||
{
|
||||
// Section name (Group 1)
|
||||
lastSectionName = lineSectionNameCheck.Groups[1].Value.Trim();
|
||||
if (string.IsNullOrWhiteSpace(lastSectionName))
|
||||
{
|
||||
throw new FormatException("Invalid ini file format: Empty section name.");
|
||||
}
|
||||
|
||||
ini.Add(lastSectionName, new Dictionary<string, string>());
|
||||
}
|
||||
else if (!lineKeyValuePairCheck.Success)
|
||||
{
|
||||
// Fail if it is not a key-value-pair (and was not detected as section name before).
|
||||
throw new FormatException("Invalid ini file format: Invalid line.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Key-value-pair (Group 1=Key; Group 2=Value)
|
||||
string iniKeyName = lineKeyValuePairCheck.Groups[1].Value.Trim();
|
||||
if (string.IsNullOrWhiteSpace(iniKeyName))
|
||||
{
|
||||
throw new FormatException("Invalid ini file format: Empty value name (key).");
|
||||
}
|
||||
|
||||
string iniValueData = lineKeyValuePairCheck.Groups[2].Value;
|
||||
ini[lastSectionName].Add(iniKeyName, iniValueData);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to JSON
|
||||
Logger.LogDebug("Converted from Ini.");
|
||||
jsonText = JsonConvert.SerializeObject(ini, Newtonsoft.Json.Formatting.Indented);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed parsing input as ini", ex);
|
||||
}
|
||||
|
||||
// Try convert CSV
|
||||
try
|
||||
{
|
||||
|
@ -24,12 +24,15 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool SendPasteKeyCombination { get; private set; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; private set; }
|
||||
|
||||
public UserSettings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
|
||||
ShowCustomPreview = true;
|
||||
SendPasteKeyCombination = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
|
||||
LoadSettingsFromJson();
|
||||
|
||||
@ -61,6 +64,7 @@ namespace AdvancedPaste.Settings
|
||||
{
|
||||
ShowCustomPreview = settings.Properties.ShowCustomPreview;
|
||||
SendPasteKeyCombination = settings.Properties.SendPasteKeyCombination;
|
||||
CloseAfterLosingFocus = settings.Properties.CloseAfterLosingFocus;
|
||||
}
|
||||
|
||||
retry = false;
|
||||
|
@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace AdvancedPaste.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class AdvancedPasteClipboardItemClicked : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
@ -11,6 +11,19 @@ namespace AdvancedPaste.Telemetry
|
||||
[EventData]
|
||||
public class AdvancedPasteGenerateCustomFormatEvent : EventBase, IEvent
|
||||
{
|
||||
public int PromptTokens { get; set; }
|
||||
|
||||
public int CompletionTokens { get; set; }
|
||||
|
||||
public string ModelName { get; set; }
|
||||
|
||||
public AdvancedPasteGenerateCustomFormatEvent(int promptTokens, int completionTokens, string modelName)
|
||||
{
|
||||
this.PromptTokens = promptTokens;
|
||||
this.CompletionTokens = completionTokens;
|
||||
ModelName = modelName;
|
||||
}
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,12 @@ namespace AdvancedPaste.ViewModels
|
||||
public partial class OptionsViewModel : ObservableObject
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private readonly IUserSettings _userSettings;
|
||||
|
||||
private App app = App.Current as App;
|
||||
|
||||
private AICompletionsHelper aiHelper;
|
||||
|
||||
private UserSettings _userSettings;
|
||||
|
||||
public DataPackageView ClipboardData { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
@ -50,10 +49,10 @@ namespace AdvancedPaste.ViewModels
|
||||
[NotifyPropertyChangedFor(nameof(InputTxtBoxErrorText))]
|
||||
private int _apiRequestStatus;
|
||||
|
||||
public OptionsViewModel()
|
||||
public OptionsViewModel(IUserSettings userSettings)
|
||||
{
|
||||
aiHelper = new AICompletionsHelper();
|
||||
_userSettings = new UserSettings();
|
||||
_userSettings = userSettings;
|
||||
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
|
||||
|
@ -135,6 +135,14 @@ namespace ColorPicker.Helpers
|
||||
Application.Current.MainWindow.Opacity = 0;
|
||||
Application.Current.MainWindow.Visibility = Visibility.Visible;
|
||||
_colorPickerShown = true;
|
||||
|
||||
// HACK: WPF UI theme watcher removes the composition target background color, among other weird stuff.
|
||||
// https://github.com/lepoco/wpfui/blob/303f0aefcd59a142bc681415dc4360a34a15f33d/src/Wpf.Ui/Controls/Window/WindowBackdrop.cs#L280
|
||||
// So we set it back with https://github.com/lepoco/wpfui/blob/303f0aefcd59a142bc681415dc4360a34a15f33d/src/Wpf.Ui/Controls/Window/WindowBackdrop.cs#L191
|
||||
// And also reapply the intended backdrop.
|
||||
// This hack fixes: https://github.com/microsoft/PowerToys/issues/31725
|
||||
Wpf.Ui.Controls.WindowBackdrop.RemoveBackground(Application.Current.MainWindow);
|
||||
Wpf.Ui.Controls.WindowBackdrop.ApplyBackdrop(Application.Current.MainWindow, Wpf.Ui.Controls.WindowBackdropType.None);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,9 @@
|
||||
<!-- This package is a dependency of System.Management, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Drawing.Common">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -69,7 +69,7 @@ namespace KeyboardEventHandlers
|
||||
key_count = std::get<Shortcut>(it->second).Size();
|
||||
}
|
||||
|
||||
LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
|
||||
std::vector<INPUT> keyEventList;
|
||||
|
||||
// Handle remaps to VK_WIN_BOTH
|
||||
DWORD target;
|
||||
@ -92,35 +92,31 @@ namespace KeyboardEventHandlers
|
||||
{
|
||||
if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(target), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(target), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
}
|
||||
else
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(target), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(target), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
Shortcut targetShortcut = std::get<Shortcut>(it->second);
|
||||
if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(targetShortcut.GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
i++;
|
||||
Helpers::SetModifierKeyEvents(targetShortcut, ModifierKey::Disabled, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(targetShortcut.GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetModifierKeyEvents(targetShortcut, ModifierKey::Disabled, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
// Dummy key is not required here since SetModifierKeyEvents will only add key-up events for the modifiers here, and the action key key-up is already sent before it
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dummy key is not required here since SetModifierKeyEvents will only add key-down events for the modifiers here, and the action key key-down is already sent after it
|
||||
Helpers::SetModifierKeyEvents(targetShortcut, ModifierKey::Disabled, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(targetShortcut.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
i++;
|
||||
Helpers::SetModifierKeyEvents(targetShortcut, ModifierKey::Disabled, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(targetShortcut.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
}
|
||||
}
|
||||
|
||||
UINT res = ii.SendVirtualInput(key_count, keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
|
||||
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
|
||||
{
|
||||
@ -194,14 +190,12 @@ namespace KeyboardEventHandlers
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
int key_count = 2;
|
||||
LPINPUT keyEventList = new INPUT[size_t(key_count)]();
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, 1, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
std::vector<INPUT> keyEventList;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
|
||||
|
||||
lock.unlock();
|
||||
UINT res = ii.SendVirtualInput(key_count, keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
|
||||
// Reset the long press flag when the key has been lifted.
|
||||
if (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)
|
||||
@ -306,8 +300,7 @@ namespace KeyboardEventHandlers
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t key_count = 0;
|
||||
LPINPUT keyEventList = nullptr;
|
||||
std::vector<INPUT> keyEventList;
|
||||
|
||||
// Remember which win key was pressed initially
|
||||
if (ii.GetVirtualKeyState(VK_RWIN))
|
||||
@ -331,8 +324,6 @@ namespace KeyboardEventHandlers
|
||||
myThread.detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:returning..");
|
||||
return 1;
|
||||
}
|
||||
@ -362,7 +353,6 @@ namespace KeyboardEventHandlers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto threadFunction = [newUri]() {
|
||||
HINSTANCE result = ShellExecute(NULL, L"open", newUri.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
||||
|
||||
@ -380,7 +370,6 @@ namespace KeyboardEventHandlers
|
||||
myThread.detach();
|
||||
}
|
||||
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:returning..");
|
||||
return 1;
|
||||
}
|
||||
@ -394,30 +383,22 @@ namespace KeyboardEventHandlers
|
||||
if (commonKeys == src_size - 1)
|
||||
{
|
||||
// key down for all new shortcut keys except the common modifiers
|
||||
key_count = dest_size - commonKeys;
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
int i = 0;
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
keyEventList = std::vector<INPUT>{};
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dummy key, key up for all the original shortcut modifier keys and key down for all the new shortcut keys but common keys in each are not repeated
|
||||
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + (dest_size) - (2 * static_cast<size_t>(commonKeys));
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
|
||||
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->Ctrl+V, press Win+A, since Win will be released here we need to send a dummy event before it
|
||||
int i = 0;
|
||||
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Release original shortcut state (release in reverse order of shortcut to be accurate)
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
|
||||
|
||||
// Set new shortcut key down state
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
// Modifier state reset might be required for this key depending on the shortcut's action and target modifiers - ex: Win+Caps -> Ctrl+A
|
||||
@ -432,31 +413,23 @@ namespace KeyboardEventHandlers
|
||||
}
|
||||
else if (remapToKey)
|
||||
{
|
||||
// Dummy key, key up for all the original shortcut modifier keys and key down for remapped key
|
||||
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + dest_size;
|
||||
|
||||
// Do not send Disable key
|
||||
if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
|
||||
{
|
||||
key_count--;
|
||||
// Since the original shortcut's action key is pressed, set it to true
|
||||
it->second.isOriginalActionKeyPressed = true;
|
||||
}
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
|
||||
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Win+A, since Win will be released here we need to send a dummy event before it
|
||||
int i = 0;
|
||||
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Release original shortcut state (release in reverse order of shortcut to be accurate)
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Set target key down state
|
||||
if (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
// Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl
|
||||
@ -468,29 +441,14 @@ namespace KeyboardEventHandlers
|
||||
// Remapped to text
|
||||
else
|
||||
{
|
||||
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + src_size;
|
||||
auto& remapping = std::get<std::wstring>(it->second.targetShortcut);
|
||||
|
||||
auto remapping = std::get<std::wstring>(it->second.targetShortcut);
|
||||
const UINT inputEventCount = static_cast<UINT>(remapping.length() * 2);
|
||||
key_count += inputEventCount;
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
|
||||
int i = 0;
|
||||
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Release original shortcut state (release in reverse order of shortcut to be accurate)
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
for (size_t idx = 0; idx < inputEventCount; ++idx)
|
||||
{
|
||||
auto& input = keyEventList[idx + i];
|
||||
input.type = INPUT_KEYBOARD;
|
||||
const bool upEvent = idx & 0x1;
|
||||
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
|
||||
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||
input.ki.wScan = remapping[idx >> 1];
|
||||
}
|
||||
Helpers::SetTextKeyEvents(keyEventList, remapping);
|
||||
}
|
||||
|
||||
it->second.isShortcutInvoked = true;
|
||||
@ -500,12 +458,9 @@ namespace KeyboardEventHandlers
|
||||
state.SetActivatedApp(*activatedApp);
|
||||
}
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:key_count:{}", key_count);
|
||||
Logger::trace(L"ChordKeyboardHandler:keyEventList.size:{}", keyEventList.size());
|
||||
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
|
||||
// Send daily telemetry event for Keyboard Manager key activation.
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
if (activatedApp.has_value())
|
||||
{
|
||||
if (remapToKey)
|
||||
@ -574,84 +529,41 @@ namespace KeyboardEventHandlers
|
||||
if ((it->first.CheckWinKey(data->lParam->vkCode) || it->first.CheckCtrlKey(data->lParam->vkCode) || it->first.CheckAltKey(data->lParam->vkCode) || it->first.CheckShiftKey(data->lParam->vkCode)) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
|
||||
{
|
||||
// Release new shortcut, and set original shortcut keys except the one released
|
||||
size_t key_count = 0;
|
||||
LPINPUT keyEventList = nullptr;
|
||||
std::vector<INPUT> keyEventList;
|
||||
if (remapToShortcut && !isRunProgram)
|
||||
{
|
||||
// if the released key is present in both shortcuts' modifiers (i.e part of the common modifiers)
|
||||
if (std::get<Shortcut>(it->second.targetShortcut).CheckWinKey(data->lParam->vkCode) || std::get<Shortcut>(it->second.targetShortcut).CheckCtrlKey(data->lParam->vkCode) || std::get<Shortcut>(it->second.targetShortcut).CheckAltKey(data->lParam->vkCode) || std::get<Shortcut>(it->second.targetShortcut).CheckShiftKey(data->lParam->vkCode))
|
||||
{
|
||||
// release all new shortcut keys and the common released modifier except the other common modifiers, and add all original shortcut modifiers except the common ones, and dummy key
|
||||
key_count = (dest_size - commonKeys) + (src_size - 1 - commonKeys) + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// release all new shortcut keys except the common modifiers and add all original shortcut modifiers except the common ones, and dummy key
|
||||
key_count = (dest_size - 1) + (src_size - 2) - (2 * static_cast<size_t>(commonKeys)) + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
|
||||
}
|
||||
|
||||
// If the target shortcut's action key is pressed, then it should be released
|
||||
bool isActionKeyPressed = false;
|
||||
if (ii.GetVirtualKeyState((std::get<Shortcut>(it->second.targetShortcut).GetActionKey())))
|
||||
{
|
||||
isActionKeyPressed = true;
|
||||
key_count += 1;
|
||||
}
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
bool isActionKeyPressed = ii.GetVirtualKeyState(std::get<Shortcut>(it->second.targetShortcut).GetActionKey());
|
||||
|
||||
// Release new shortcut state (release in reverse order of shortcut to be accurate)
|
||||
int i = 0;
|
||||
if (isActionKeyPressed)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first, data->lParam->vkCode);
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first, data->lParam->vkCode);
|
||||
|
||||
// Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut), data->lParam->vkCode);
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut), data->lParam->vkCode);
|
||||
|
||||
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->Ctrl+V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it
|
||||
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else if (remapToKey)
|
||||
{
|
||||
// 1 for releasing new key and original shortcut modifiers except the one released and dummy key
|
||||
key_count = dest_size + src_size - 2 + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
|
||||
bool isTargetKeyPressed = false;
|
||||
|
||||
// Do not send Disable key up
|
||||
if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
|
||||
{
|
||||
key_count--;
|
||||
}
|
||||
else if (ii.GetVirtualKeyState(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))))
|
||||
{
|
||||
isTargetKeyPressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isTargetKeyPressed = false;
|
||||
key_count--;
|
||||
}
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
bool isTargetKeyPressed = (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED) && ii.GetVirtualKeyState(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)));
|
||||
|
||||
// Release new key state
|
||||
int i = 0;
|
||||
if (std::get<DWORD>(it->second.targetShortcut) != CommonSharedConstants::VK_DISABLED && isTargetKeyPressed)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
// Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode);
|
||||
|
||||
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it
|
||||
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
// Reset the remap state
|
||||
@ -665,12 +577,7 @@ namespace KeyboardEventHandlers
|
||||
state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
|
||||
}
|
||||
|
||||
// key count can be 0 if both shortcuts have same modifiers and the action key is not held down. delete will throw an error if keyEventList is empty
|
||||
if (key_count > 0)
|
||||
{
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
}
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -678,7 +585,7 @@ namespace KeyboardEventHandlers
|
||||
if (!remapToShortcut || (remapToShortcut && std::get<Shortcut>(it->second.targetShortcut).CheckModifiersKeyboardState(ii)))
|
||||
{
|
||||
// Case 2: If the original shortcut is still held down the keyboard will get a key down message of the action key in the original shortcut and the new shortcut's modifiers will be held down (keys held down send repeated keydown messages)
|
||||
if (((data->lParam->vkCode == it->first.GetActionKey() && !it->first.HasChord())||(data->lParam->vkCode == it->first.GetSecondKey() && it->first.HasChord())) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))
|
||||
if (((data->lParam->vkCode == it->first.GetActionKey() && !it->first.HasChord()) || (data->lParam->vkCode == it->first.GetSecondKey() && it->first.HasChord())) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))
|
||||
{
|
||||
// In case of mapping to disable do not send anything
|
||||
if (remapToKey && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
|
||||
@ -688,69 +595,46 @@ namespace KeyboardEventHandlers
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t key_count = 1;
|
||||
LPINPUT keyEventList = nullptr;
|
||||
std::vector<INPUT> keyEventList;
|
||||
if (remapToShortcut)
|
||||
{
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else if (remapToKey)
|
||||
{
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else if (remapToText)
|
||||
{
|
||||
auto remapping = std::get<std::wstring>(it->second.targetShortcut);
|
||||
const UINT inputEventCount = static_cast<UINT>(remapping.length() * 2);
|
||||
key_count = inputEventCount;
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
|
||||
for (size_t idx = 0; idx < inputEventCount; ++idx)
|
||||
{
|
||||
auto& input = keyEventList[idx];
|
||||
input.type = INPUT_KEYBOARD;
|
||||
const bool upEvent = idx & 0x1;
|
||||
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
|
||||
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||
input.ki.wScan = remapping[idx >> 1];
|
||||
}
|
||||
auto& remapping = std::get<std::wstring>(it->second.targetShortcut);
|
||||
Helpers::SetTextKeyEvents(keyEventList, remapping);
|
||||
}
|
||||
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Case 3: If the action key is released from the original shortcut, keep modifiers of the new shortcut until some other key event which doesn't apply to the original shortcut
|
||||
if (!remapToText && ((!it->first.HasChord() && data->lParam->vkCode == it->first.GetActionKey()) || (it->first.HasChord() && data->lParam->vkCode==it->first.GetSecondKey())) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
|
||||
if (!remapToText && ((!it->first.HasChord() && data->lParam->vkCode == it->first.GetActionKey()) || (it->first.HasChord() && data->lParam->vkCode == it->first.GetSecondKey())) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
|
||||
{
|
||||
size_t key_count = 1;
|
||||
LPINPUT keyEventList = nullptr;
|
||||
std::vector<INPUT> keyEventList;
|
||||
if (remapToShortcut && !it->first.HasChord())
|
||||
{
|
||||
// Just lift the action key for no chords.
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else if (remapToShortcut && it->first.HasChord())
|
||||
{
|
||||
// If it has a chord, we'll want a full clean contemplated in the else, since you can't really repeat chords by pressing the end key again.
|
||||
|
||||
// Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated
|
||||
key_count = (dest_size) + (src_size +1) - (2 * static_cast<size_t>(commonKeys));
|
||||
int i = 0;
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Release new shortcut state (release in reverse order of shortcut to be accurate)
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
|
||||
// Set old shortcut key down state
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
|
||||
|
||||
// Reset the remap state
|
||||
it->second.isShortcutInvoked = false;
|
||||
@ -762,7 +646,6 @@ namespace KeyboardEventHandlers
|
||||
{
|
||||
state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
|
||||
}
|
||||
|
||||
}
|
||||
else if (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
|
||||
{
|
||||
@ -779,29 +662,21 @@ namespace KeyboardEventHandlers
|
||||
// If the keyboard state is clear, we release the target key but do not reset the remap state
|
||||
if (isKeyboardStateClear)
|
||||
{
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If any other key is pressed, then the keyboard state must be reverted back to the physical keys.
|
||||
// This is to take cases like Ctrl+A->D remap and user presses B+Ctrl+A and releases A, or Ctrl+A+B and releases A
|
||||
|
||||
// 1 for releasing new key and original shortcut modifiers, and dummy key
|
||||
key_count = dest_size + (src_size - 1) + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
|
||||
// Release new key state
|
||||
int i = 0;
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Set original shortcut key down state except the action key
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Shift+Win+A and release A, since Win will be pressed here we need to send a dummy event after it
|
||||
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Reset the remap state
|
||||
it->second.isShortcutInvoked = false;
|
||||
@ -816,8 +691,7 @@ namespace KeyboardEventHandlers
|
||||
}
|
||||
}
|
||||
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -854,8 +728,7 @@ namespace KeyboardEventHandlers
|
||||
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, std::get<Shortcut>(it->second.targetShortcut).GetActionKey());
|
||||
}
|
||||
|
||||
size_t key_count;
|
||||
LPINPUT keyEventList = nullptr;
|
||||
std::vector<INPUT> keyEventList;
|
||||
|
||||
// Check if a new remapping should be applied
|
||||
Shortcut currentlyPressed = it->first;
|
||||
@ -868,40 +741,25 @@ namespace KeyboardEventHandlers
|
||||
if (newRemapping.RemapToKey())
|
||||
{
|
||||
DWORD to = std::get<0>(newRemapping.targetShortcut);
|
||||
bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey));
|
||||
key_count = static_cast<size_t>(from.Size()) - 1 + 1 + (isLastKeyStillPressed ? 1 : 0);
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
int i = 0;
|
||||
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
|
||||
{
|
||||
// If the action key from the last shortcut is still being pressed, release it.
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(from.actionKey), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(from.actionKey), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(to), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(to), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
else
|
||||
{
|
||||
Shortcut to = std::get<Shortcut>(newRemapping.targetShortcut);
|
||||
bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey));
|
||||
|
||||
size_t temp_key_count_calculation = static_cast<size_t>(from.Size()) - 1;
|
||||
temp_key_count_calculation += static_cast<size_t>(to.Size()) - 1;
|
||||
temp_key_count_calculation -= static_cast<size_t>(2) * from.GetCommonModifiersCount(to);
|
||||
key_count = temp_key_count_calculation + 1 + (isLastKeyStillPressed ? 1 : 0);
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
|
||||
int i = 0;
|
||||
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
|
||||
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
|
||||
if (ii.GetVirtualKeyState(static_cast<WORD>(from.actionKey)))
|
||||
{
|
||||
// If the action key from the last shortcut is still being pressed, release it.
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(from.actionKey), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(from.actionKey), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
Helpers::SetModifierKeyEvents(to, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, from);
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(to.actionKey), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetModifierKeyEvents(to, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, from);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(to.actionKey), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
newRemapping.isShortcutInvoked = true;
|
||||
}
|
||||
|
||||
@ -917,41 +775,27 @@ namespace KeyboardEventHandlers
|
||||
}
|
||||
else
|
||||
{
|
||||
// Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated
|
||||
key_count = (dest_size) + (src_size - 1) - (2 * static_cast<size_t>(commonKeys));
|
||||
|
||||
// If the target shortcut's action key is pressed, then it should be released and original shortcut's action key should be set
|
||||
bool isActionKeyPressed = false;
|
||||
if (ii.GetVirtualKeyState((std::get<Shortcut>(it->second.targetShortcut).GetActionKey())))
|
||||
{
|
||||
isActionKeyPressed = true;
|
||||
key_count += 2;
|
||||
}
|
||||
|
||||
keyEventList = new INPUT[key_count]{};
|
||||
bool isActionKeyPressed = ii.GetVirtualKeyState(std::get<Shortcut>(it->second.targetShortcut).GetActionKey());
|
||||
|
||||
// Release new shortcut state (release in reverse order of shortcut to be accurate)
|
||||
int i = 0;
|
||||
if (isActionKeyPressed)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
|
||||
|
||||
// Set old shortcut key down state
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get<Shortcut>(it->second.targetShortcut));
|
||||
|
||||
// key down for original shortcut action key with shortcut flag so that we don't invoke the same shortcut remap again
|
||||
if (isActionKeyPressed)
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
// Send current key pressed without shortcut flag so that it can be reprocessed in case the physical keys pressed are a different remapped shortcut
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(data->lParam->vkCode), 0, 0);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(data->lParam->vkCode), 0, 0);
|
||||
|
||||
// Do not send a dummy key as we want the current key press to behave as normal i.e. it can do press+release functionality if required. Required to allow a shortcut to Win key remap invoked directly after shortcut to shortcut is released to open start menu
|
||||
}
|
||||
@ -967,8 +811,7 @@ namespace KeyboardEventHandlers
|
||||
state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
|
||||
}
|
||||
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
@ -1003,30 +846,20 @@ namespace KeyboardEventHandlers
|
||||
|
||||
if (isRemapToDisable || !isOriginalActionKeyPressed)
|
||||
{
|
||||
// Key down for original shortcut modifiers and action key, and current key press
|
||||
size_t key_count = src_size + 1;
|
||||
|
||||
LPINPUT keyEventList = new INPUT[key_count]{};
|
||||
std::vector<INPUT> keyEventList;
|
||||
|
||||
// Set original shortcut key down state
|
||||
int i = 0;
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
|
||||
// Send the original action key only if it is physically pressed. For remappings to keys other than disabled we already check earlier that it is not pressed in this scenario. For remap to disable
|
||||
if (isRemapToDisable && isOriginalActionKeyPressed)
|
||||
{
|
||||
// Set original action key
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
key_count--;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
}
|
||||
|
||||
// Send current key pressed without shortcut flag so that it can be reprocessed in case the physical keys pressed are a different remapped shortcut
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(data->lParam->vkCode), 0, 0);
|
||||
i++;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(data->lParam->vkCode), 0, 0);
|
||||
|
||||
// Do not send a dummy key as we want the current key press to behave as normal i.e. it can do press+release functionality if required. Required to allow a shortcut to Win key remap invoked directly after another shortcut to key remap is released to open start menu
|
||||
|
||||
@ -1041,8 +874,7 @@ namespace KeyboardEventHandlers
|
||||
state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp);
|
||||
}
|
||||
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
@ -1344,7 +1176,7 @@ namespace KeyboardEventHandlers
|
||||
|
||||
if (targetPid != 0 && shortcut.alreadyRunningAction != Shortcut::ProgramAlreadyRunningAction::StartAnother)
|
||||
{
|
||||
if (shortcut.alreadyRunningAction == Shortcut::ProgramAlreadyRunningAction::EndTask)
|
||||
if (shortcut.alreadyRunningAction == Shortcut::ProgramAlreadyRunningAction::EndTask)
|
||||
{
|
||||
TerminateProcessesByName(fileNamePart);
|
||||
return;
|
||||
@ -1803,13 +1635,11 @@ namespace KeyboardEventHandlers
|
||||
// If the argument is either of the Ctrl/Shift/Alt modifier key codes
|
||||
if (Helpers::IsModifierKey(key) && !(key == VK_LWIN || key == VK_RWIN || key == CommonSharedConstants::VK_WIN_BOTH))
|
||||
{
|
||||
int key_count = 1;
|
||||
LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
|
||||
std::vector<INPUT> keyEventList;
|
||||
|
||||
// Use the suppress flag to ensure these are not intercepted by any remapped keys or shortcuts
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(key), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast<WORD>(key), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1834,21 +1664,9 @@ namespace KeyboardEventHandlers
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t keyCount = remapping->length();
|
||||
const UINT eventCount = static_cast<UINT>(keyCount * 2);
|
||||
auto keyEventList = std::make_unique<INPUT[]>(keyCount * 2);
|
||||
|
||||
for (size_t i = 0; i < eventCount; ++i)
|
||||
{
|
||||
auto& input = keyEventList[i];
|
||||
input.type = INPUT_KEYBOARD;
|
||||
const bool upEvent = i & 0x1;
|
||||
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
|
||||
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||
input.ki.wScan = (*remapping)[i >> 1];
|
||||
}
|
||||
|
||||
UINT res = ii.SendVirtualInput(eventCount, keyEventList.get(), sizeof(INPUT));
|
||||
std::vector<INPUT> keyEventList;
|
||||
Helpers::SetTextKeyEvents(keyEventList, *remapping);
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -51,15 +51,13 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// Ctrl and A key states should be unchanged, Alt and V key states should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
|
||||
@ -83,15 +81,13 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp2);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// Ctrl and A key states should be true, Alt and V key states should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
|
||||
@ -115,28 +111,24 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// Activated app should be testApp1
|
||||
Assert::AreEqual(testApp1, testState.GetActivatedApp());
|
||||
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = VK_CONTROL;
|
||||
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
|
||||
};
|
||||
|
||||
// Release A then Ctrl
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// Activated app should be empty string
|
||||
Assert::AreEqual(std::wstring(KeyboardManagerConstants::NoActivatedApp), testState.GetActivatedApp());
|
||||
@ -156,28 +148,24 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp2);
|
||||
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = VK_CONTROL;
|
||||
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
|
||||
};
|
||||
|
||||
// Release A then Ctrl
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// Ctrl, A, Alt and Tab should all be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
|
||||
@ -198,15 +186,13 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// Ctrl and A key states should be unchanged, V key states should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
|
||||
@ -226,15 +212,13 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp2);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// Ctrl and A key states should be true, V key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
|
||||
@ -254,28 +238,24 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// Activated app should be testApp1
|
||||
Assert::AreEqual(testApp1, testState.GetActivatedApp());
|
||||
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = VK_CONTROL;
|
||||
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
|
||||
};
|
||||
|
||||
// Release A then Ctrl
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// Activated app should be empty string
|
||||
Assert::AreEqual(std::wstring(KeyboardManagerConstants::NoActivatedApp), testState.GetActivatedApp());
|
||||
@ -292,28 +272,24 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp2);
|
||||
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = VK_CONTROL;
|
||||
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = KEYEVENTF_KEYUP } }
|
||||
};
|
||||
|
||||
// Release A then Ctrl
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// Ctrl, A, V should all be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
|
||||
@ -334,15 +310,13 @@ namespace RemappingLogicTests
|
||||
// Set the testApp as the foreground process
|
||||
mockedInputHandler.SetForegroundProcess(testApp1);
|
||||
|
||||
const int nInputs = 2;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
input[1].type = INPUT_KEYBOARD;
|
||||
input[1].ki.wVk = actionKey;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = actionKey } }
|
||||
};
|
||||
|
||||
// Send Ctrl+A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// Check if Ctrl+A is released and disable key was not send
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false);
|
||||
|
@ -10,15 +10,15 @@ void MockedInput::SetHookProc(std::function<intptr_t(LowlevelKeyboardEvent*)> ho
|
||||
}
|
||||
|
||||
// Function to simulate keyboard input - arguments and return value based on SendInput function (https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-sendinput)
|
||||
UINT MockedInput::SendVirtualInput(UINT cInputs, LPINPUT pInputs, int /*cbSize*/)
|
||||
void MockedInput::SendVirtualInput(const std::vector<INPUT>& inputs)
|
||||
{
|
||||
// Iterate over inputs
|
||||
for (UINT i = 0; i < cInputs; i++)
|
||||
for (const INPUT& input : inputs)
|
||||
{
|
||||
LowlevelKeyboardEvent keyEvent;
|
||||
LowlevelKeyboardEvent keyEvent{};
|
||||
|
||||
// Distinguish between key and sys key by checking if the key is either F10 (for syskeydown) or if the key message is sent while Alt is held down. SYSKEY messages are also sent if there is no window in focus, but that has not been mocked since it would require many changes. More details on key messages at https://learn.microsoft.com/windows/win32/inputdev/wm-syskeydown
|
||||
if (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
if (input.ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
{
|
||||
if (keyboardState[VK_MENU] == true)
|
||||
{
|
||||
@ -31,7 +31,7 @@ UINT MockedInput::SendVirtualInput(UINT cInputs, LPINPUT pInputs, int /*cbSize*/
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pInputs[i].ki.wVk == VK_F10 || keyboardState[VK_MENU] == true)
|
||||
if (input.ki.wVk == VK_F10 || keyboardState[VK_MENU] == true)
|
||||
{
|
||||
keyEvent.wParam = WM_SYSKEYDOWN;
|
||||
}
|
||||
@ -43,8 +43,8 @@ UINT MockedInput::SendVirtualInput(UINT cInputs, LPINPUT pInputs, int /*cbSize*/
|
||||
KBDLLHOOKSTRUCT lParam = {};
|
||||
|
||||
// Set only vkCode and dwExtraInfo since other values are unused
|
||||
lParam.vkCode = pInputs[i].ki.wVk;
|
||||
lParam.dwExtraInfo = pInputs[i].ki.dwExtraInfo;
|
||||
lParam.vkCode = input.ki.wVk;
|
||||
lParam.dwExtraInfo = input.ki.dwExtraInfo;
|
||||
keyEvent.lParam = &lParam;
|
||||
|
||||
// If the SendVirtualInput call condition is true, increment the count. If no condition is set then always increment the count
|
||||
@ -60,55 +60,53 @@ UINT MockedInput::SendVirtualInput(UINT cInputs, LPINPUT pInputs, int /*cbSize*/
|
||||
if (result == 0)
|
||||
{
|
||||
// If key up flag is set, then set keyboard state to false
|
||||
keyboardState[pInputs[i].ki.wVk] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[input.ki.wVk] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
|
||||
// Handling modifier key codes
|
||||
switch (pInputs[i].ki.wVk)
|
||||
switch (input.ki.wVk)
|
||||
{
|
||||
case VK_CONTROL:
|
||||
if (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
if (input.ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
{
|
||||
keyboardState[VK_LCONTROL] = false;
|
||||
keyboardState[VK_RCONTROL] = false;
|
||||
}
|
||||
break;
|
||||
case VK_LCONTROL:
|
||||
keyboardState[VK_CONTROL] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[VK_CONTROL] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
break;
|
||||
case VK_RCONTROL:
|
||||
keyboardState[VK_CONTROL] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[VK_CONTROL] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
break;
|
||||
case VK_MENU:
|
||||
if (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
if (input.ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
{
|
||||
keyboardState[VK_LMENU] = false;
|
||||
keyboardState[VK_RMENU] = false;
|
||||
}
|
||||
break;
|
||||
case VK_LMENU:
|
||||
keyboardState[VK_MENU] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[VK_MENU] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
break;
|
||||
case VK_RMENU:
|
||||
keyboardState[VK_MENU] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[VK_MENU] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
break;
|
||||
case VK_SHIFT:
|
||||
if (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
if (input.ki.dwFlags & KEYEVENTF_KEYUP)
|
||||
{
|
||||
keyboardState[VK_LSHIFT] = false;
|
||||
keyboardState[VK_RSHIFT] = false;
|
||||
}
|
||||
break;
|
||||
case VK_LSHIFT:
|
||||
keyboardState[VK_SHIFT] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[VK_SHIFT] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
break;
|
||||
case VK_RSHIFT:
|
||||
keyboardState[VK_SHIFT] = (pInputs[i].ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
keyboardState[VK_SHIFT] = (input.ki.dwFlags & KEYEVENTF_KEYUP) ? false : true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cInputs;
|
||||
}
|
||||
|
||||
// Function to simulate keyboard hook behavior
|
||||
|
@ -34,7 +34,7 @@ namespace KeyboardManagerInput
|
||||
void SetHookProc(std::function<intptr_t(LowlevelKeyboardEvent*)> hookProcedure);
|
||||
|
||||
// Function to simulate keyboard input
|
||||
UINT SendVirtualInput(UINT cInputs, LPINPUT pInputs, int cbSize);
|
||||
void SendVirtualInput(const std::vector<INPUT>& inputs);
|
||||
|
||||
// Function to simulate keyboard hook behavior
|
||||
intptr_t MockedKeyboardHook(LowlevelKeyboardEvent* data);
|
||||
|
@ -33,20 +33,22 @@ namespace RemappingLogicTests
|
||||
TEST_METHOD (MockedInput_ShouldSetKeyboardState_OnKeyEvent)
|
||||
{
|
||||
// Send key down and key up for A key (0x41) and check keyboard state both times
|
||||
const int nInputs = 1;
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
|
||||
};
|
||||
|
||||
// Send A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// A key state should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), true);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send A keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// A key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -34,7 +34,7 @@ namespace RemappingLogicTests
|
||||
TEST_METHOD (SetKeyEvent_ShouldUseExtendedKeyFlag_WhenArgumentIsExtendedKey)
|
||||
{
|
||||
const int nInputs = 15;
|
||||
INPUT input[nInputs] = {};
|
||||
std::vector<INPUT> inputs;
|
||||
|
||||
// List of extended keys
|
||||
WORD keyCodes[nInputs] = { VK_RCONTROL, VK_RMENU, VK_NUMLOCK, VK_SNAPSHOT, VK_CANCEL, VK_INSERT, VK_HOME, VK_PRIOR, VK_DELETE, VK_END, VK_NEXT, VK_LEFT, VK_DOWN, VK_RIGHT, VK_UP };
|
||||
@ -42,24 +42,22 @@ namespace RemappingLogicTests
|
||||
for (int i = 0; i < nInputs; i++)
|
||||
{
|
||||
// Set key events for all the extended keys
|
||||
Helpers::SetKeyEvent(input, i, INPUT_KEYBOARD, keyCodes[i], 0, 0);
|
||||
Helpers::SetKeyEvent(inputs, INPUT_KEYBOARD, keyCodes[i], 0, 0);
|
||||
// Extended key flag should be set
|
||||
Assert::AreEqual(true, bool(input[i].ki.dwFlags & KEYEVENTF_EXTENDEDKEY));
|
||||
Assert::AreEqual(true, bool(inputs[i].ki.dwFlags & KEYEVENTF_EXTENDEDKEY));
|
||||
}
|
||||
}
|
||||
|
||||
// Test if SetKeyEvent sets the scan code field to 0 for dummy key
|
||||
TEST_METHOD (SetKeyEvent_ShouldSetScanCodeFieldTo0_WhenArgumentIsDummyKey)
|
||||
{
|
||||
const int nInputs = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
|
||||
INPUT input[nInputs] = {};
|
||||
std::vector<INPUT> inputs{};
|
||||
|
||||
int index = 0;
|
||||
Helpers::SetDummyKeyEvent(input, index, 0);
|
||||
Helpers::SetDummyKeyEvent(inputs, 0);
|
||||
|
||||
// Assert that wScan for both inputs is 0
|
||||
Assert::AreEqual<unsigned int>(0, input[0].ki.wScan);
|
||||
Assert::AreEqual<unsigned int>(0, input[1].ki.wScan);
|
||||
Assert::AreEqual<unsigned int>(0, inputs[0].ki.wScan);
|
||||
Assert::AreEqual<unsigned int>(0, inputs[1].ki.wScan);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -39,22 +39,24 @@ namespace RemappingLogicTests
|
||||
{
|
||||
// Remap A to B
|
||||
testState.AddSingleKeyRemap(0x41, (DWORD)0x42);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
|
||||
};
|
||||
|
||||
// Send A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// A key state should be unchanged, and B key state should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x42), true);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send A keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// A key state should be unchanged, and B key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
@ -66,21 +68,23 @@ namespace RemappingLogicTests
|
||||
{
|
||||
// Remap A to VK_DISABLE (disabled)
|
||||
testState.AddSingleKeyRemap(0x41, CommonSharedConstants::VK_DISABLED);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
|
||||
};
|
||||
|
||||
// Send A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// A key state should be unchanged
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send A keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// A key state should be unchanged
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
@ -91,22 +95,24 @@ namespace RemappingLogicTests
|
||||
{
|
||||
// Remap A to Common Win key
|
||||
testState.AddSingleKeyRemap(0x41, CommonSharedConstants::VK_WIN_BOTH);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
|
||||
};
|
||||
|
||||
// Send A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// A key state should be unchanged, and common Win key state should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LWIN), true);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send A keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// A key state should be unchanged, and common Win key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
@ -126,14 +132,13 @@ namespace RemappingLogicTests
|
||||
|
||||
// Remap Caps Lock to Ctrl
|
||||
testState.AddSingleKeyRemap(VK_CAPITAL, (DWORD)VK_CONTROL);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CAPITAL;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CAPITAL } },
|
||||
};
|
||||
|
||||
// Send Caps Lock keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// SendVirtualInput should be called exactly once with the above condition
|
||||
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
|
||||
@ -152,14 +157,13 @@ namespace RemappingLogicTests
|
||||
|
||||
// Remap Ctrl to Caps Lock
|
||||
testState.AddSingleKeyRemap(VK_CONTROL, (DWORD)VK_CAPITAL);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
};
|
||||
|
||||
// Send Ctrl keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// SendVirtualInput should be called exactly once with the above condition
|
||||
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
|
||||
@ -182,14 +186,13 @@ namespace RemappingLogicTests
|
||||
dest.SetKey(VK_SHIFT);
|
||||
dest.SetKey(0x56);
|
||||
testState.AddSingleKeyRemap(VK_CAPITAL, dest);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CAPITAL;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CAPITAL } },
|
||||
};
|
||||
|
||||
// Send Caps Lock keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// SendVirtualInput should be called exactly twice with the above condition
|
||||
Assert::AreEqual(2, mockedInputHandler.GetSendVirtualInputCallCount());
|
||||
@ -211,14 +214,13 @@ namespace RemappingLogicTests
|
||||
dest.SetKey(VK_CONTROL);
|
||||
dest.SetKey(VK_CAPITAL);
|
||||
testState.AddSingleKeyRemap(VK_CONTROL, dest);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_CONTROL;
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL } },
|
||||
};
|
||||
|
||||
// Send Ctrl keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
// SendVirtualInput should be called exactly once with the above condition
|
||||
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
|
||||
@ -232,23 +234,25 @@ namespace RemappingLogicTests
|
||||
dest.SetKey(VK_CONTROL);
|
||||
dest.SetKey(0x56);
|
||||
testState.AddSingleKeyRemap(0x41, dest);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
|
||||
};
|
||||
|
||||
// Send A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// A key state should be unchanged, and Ctrl, V key state should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send A keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// A key state should be unchanged, and Ctrl, V key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
@ -265,24 +269,26 @@ namespace RemappingLogicTests
|
||||
dest.SetKey(VK_SHIFT);
|
||||
dest.SetKey(0x56);
|
||||
testState.AddSingleKeyRemap(0x41, dest);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = 0x41;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A' } },
|
||||
};
|
||||
|
||||
// Send A keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// A key state should be unchanged, and Ctrl, Shift, V key state should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_SHIFT), true);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = 'A', .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send A keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// A key state should be unchanged, and Ctrl, Shift, V key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false);
|
||||
@ -299,22 +305,24 @@ namespace RemappingLogicTests
|
||||
dest.SetKey(VK_LCONTROL);
|
||||
dest.SetKey(0x56);
|
||||
testState.AddSingleKeyRemap(VK_LCONTROL, dest);
|
||||
const int nInputs = 1;
|
||||
|
||||
INPUT input[nInputs] = {};
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
input[0].ki.wVk = VK_LCONTROL;
|
||||
std::vector<INPUT> inputs1{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } },
|
||||
};
|
||||
|
||||
// Send LCtrl keydown
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs1);
|
||||
|
||||
// LCtrl, V key state should be true
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LCONTROL), true);
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), true);
|
||||
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||
|
||||
std::vector<INPUT> inputs2{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } },
|
||||
};
|
||||
|
||||
// Send LCtrl keyup
|
||||
mockedInputHandler.SendVirtualInput(nInputs, input, sizeof(INPUT));
|
||||
mockedInputHandler.SendVirtualInput(inputs2);
|
||||
|
||||
// LCtrl, V key state should be false
|
||||
Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_LCONTROL), false);
|
||||
|
@ -154,29 +154,29 @@ namespace Helpers
|
||||
}
|
||||
|
||||
// Function to set the value of a key event based on the arguments
|
||||
void SetKeyEvent(LPINPUT keyEventArray, int index, DWORD inputType, WORD keyCode, DWORD flags, ULONG_PTR extraInfo)
|
||||
void SetKeyEvent(std::vector<INPUT>& keyEventArray, DWORD inputType, WORD keyCode, DWORD flags, ULONG_PTR extraInfo)
|
||||
{
|
||||
keyEventArray[index].type = inputType;
|
||||
keyEventArray[index].ki.wVk = keyCode;
|
||||
keyEventArray[index].ki.dwFlags = flags;
|
||||
INPUT keyEvent{};
|
||||
keyEvent.type = inputType;
|
||||
keyEvent.ki.wVk = keyCode;
|
||||
keyEvent.ki.dwFlags = flags;
|
||||
if (IsExtendedKey(keyCode))
|
||||
{
|
||||
keyEventArray[index].ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
||||
keyEvent.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
||||
}
|
||||
keyEventArray[index].ki.dwExtraInfo = extraInfo;
|
||||
keyEvent.ki.dwExtraInfo = extraInfo;
|
||||
|
||||
// Set wScan to the value from MapVirtualKey as some applications may use the scan code for handling input, for instance, Windows Terminal ignores non-character input which has scancode set to 0.
|
||||
// MapVirtualKey returns 0 if the key code does not correspond to a physical key (such as unassigned/reserved keys). More details at https://github.com/microsoft/PowerToys/pull/7143#issue-498877747
|
||||
keyEventArray[index].ki.wScan = static_cast<WORD>(MapVirtualKey(keyCode, MAPVK_VK_TO_VSC));
|
||||
keyEvent.ki.wScan = static_cast<WORD>(MapVirtualKey(keyCode, MAPVK_VK_TO_VSC));
|
||||
keyEventArray.push_back(keyEvent);
|
||||
}
|
||||
|
||||
// Function to set the dummy key events used for remapping shortcuts, required to ensure releasing a modifier doesn't trigger another action (For example, Win->Start Menu or Alt->Menu bar)
|
||||
void SetDummyKeyEvent(LPINPUT keyEventArray, int& index, ULONG_PTR extraInfo)
|
||||
void SetDummyKeyEvent(std::vector<INPUT>& keyEventArray, ULONG_PTR extraInfo)
|
||||
{
|
||||
SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(KeyboardManagerConstants::DUMMY_KEY), 0, extraInfo);
|
||||
index++;
|
||||
SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(KeyboardManagerConstants::DUMMY_KEY), KEYEVENTF_KEYUP, extraInfo);
|
||||
index++;
|
||||
SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(KeyboardManagerConstants::DUMMY_KEY), 0, extraInfo);
|
||||
SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(KeyboardManagerConstants::DUMMY_KEY), KEYEVENTF_KEYUP, extraInfo);
|
||||
}
|
||||
|
||||
// Function to return window handle for a full screen UWP app
|
||||
@ -240,7 +240,7 @@ namespace Helpers
|
||||
}
|
||||
|
||||
// Function to set key events for modifier keys: When shortcutToCompare is passed (non-empty shortcut), then the key event is sent only if both shortcut's don't have the same modifier key. When keyToBeReleased is passed (non-NULL), then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
|
||||
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const ModifierKey& winKeyInvoked, LPINPUT keyEventArray, int& index, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare, const DWORD& keyToBeReleased)
|
||||
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const ModifierKey& winKeyInvoked, std::vector<INPUT>& keyEventArray, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare, const DWORD& keyToBeReleased)
|
||||
{
|
||||
// If key down is to be sent, send in the order Win, Ctrl, Alt, Shift
|
||||
if (isKeyDown)
|
||||
@ -248,23 +248,19 @@ namespace Helpers
|
||||
// If shortcutToCompare is non-empty, then the key event is sent only if both shortcut's don't have the same modifier key. If keyToBeReleased is non-NULL, then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
|
||||
if (shortcutToBeSent.GetWinKey(winKeyInvoked) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetWinKey(winKeyInvoked) != shortcutToCompare.GetWinKey(winKeyInvoked)) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckWinKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(winKeyInvoked)), 0, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(winKeyInvoked)), 0, extraInfoFlag);
|
||||
}
|
||||
if (shortcutToBeSent.GetCtrlKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetCtrlKey() != shortcutToCompare.GetCtrlKey()) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckCtrlKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey()), 0, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey()), 0, extraInfoFlag);
|
||||
}
|
||||
if (shortcutToBeSent.GetAltKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetAltKey() != shortcutToCompare.GetAltKey()) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckAltKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey()), 0, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey()), 0, extraInfoFlag);
|
||||
}
|
||||
if (shortcutToBeSent.GetShiftKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetShiftKey() != shortcutToCompare.GetShiftKey()) && (keyToBeReleased == NULL || !shortcutToBeSent.CheckShiftKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey()), 0, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey()), 0, extraInfoFlag);
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,23 +270,35 @@ namespace Helpers
|
||||
// If shortcutToCompare is non-empty, then the key event is sent only if both shortcut's don't have the same modifier key. If keyToBeReleased is non-NULL, then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
|
||||
if (shortcutToBeSent.GetShiftKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetShiftKey() != shortcutToCompare.GetShiftKey() || shortcutToBeSent.CheckShiftKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey()), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetShiftKey()), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
}
|
||||
if (shortcutToBeSent.GetAltKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetAltKey() != shortcutToCompare.GetAltKey() || shortcutToBeSent.CheckAltKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey()), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetAltKey()), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
}
|
||||
if (shortcutToBeSent.GetCtrlKey() != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetCtrlKey() != shortcutToCompare.GetCtrlKey() || shortcutToBeSent.CheckCtrlKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey()), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetCtrlKey()), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
}
|
||||
if (shortcutToBeSent.GetWinKey(winKeyInvoked) != NULL && (shortcutToCompare.IsEmpty() || shortcutToBeSent.GetWinKey(winKeyInvoked) != shortcutToCompare.GetWinKey(winKeyInvoked) || shortcutToBeSent.CheckWinKey(keyToBeReleased)))
|
||||
{
|
||||
Helpers::SetKeyEvent(keyEventArray, index, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(winKeyInvoked)), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
index++;
|
||||
Helpers::SetKeyEvent(keyEventArray, INPUT_KEYBOARD, static_cast<WORD>(shortcutToBeSent.GetWinKey(winKeyInvoked)), KEYEVENTF_KEYUP, extraInfoFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetTextKeyEvents(std::vector<INPUT>& keyEventArray, const std::wstring& remapping)
|
||||
{
|
||||
for (wchar_t c : remapping)
|
||||
{
|
||||
for (DWORD flag : { 0, KEYEVENTF_KEYUP })
|
||||
{
|
||||
INPUT input{};
|
||||
input.type = INPUT_KEYBOARD;
|
||||
input.ki.dwFlags = KEYEVENTF_UNICODE | flag;
|
||||
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||
input.ki.wScan = c;
|
||||
keyEventArray.push_back(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,13 @@ namespace Helpers
|
||||
KeyType GetKeyType(DWORD key);
|
||||
|
||||
// Function to set the value of a key event based on the arguments
|
||||
void SetKeyEvent(LPINPUT keyEventArray, int index, DWORD inputType, WORD keyCode, DWORD flags, ULONG_PTR extraInfo);
|
||||
void SetKeyEvent(std::vector<INPUT>& keyEventArray, DWORD inputType, WORD keyCode, DWORD flags, ULONG_PTR extraInfo);
|
||||
|
||||
// Function to set the dummy key events used for remapping shortcuts, required to ensure releasing a modifier doesn't trigger another action (For example, Win->Start Menu or Alt->Menu bar)
|
||||
void SetDummyKeyEvent(LPINPUT keyEventArray, int& index, ULONG_PTR extraInfo);
|
||||
void SetDummyKeyEvent(std::vector<INPUT>& keyEventArray, ULONG_PTR extraInfo);
|
||||
|
||||
// Function to set key events for remapping text.
|
||||
void SetTextKeyEvents(std::vector<INPUT>& keyEventArray, const std::wstring& remapping);
|
||||
|
||||
// Function to return window handle for a full screen UWP app
|
||||
HWND GetFullscreenUWPWindowHandle();
|
||||
@ -43,7 +46,7 @@ namespace Helpers
|
||||
std::wstring GetCurrentApplication(bool keepPath);
|
||||
|
||||
// Function to set key events for modifier keys: When shortcutToCompare is passed (non-empty shortcut), then the key event is sent only if both shortcut's don't have the same modifier key. When keyToBeReleased is passed (non-NULL), then the key event is sent if either the shortcuts don't have the same modifier or if the shortcutToBeSent's modifier matches the keyToBeReleased
|
||||
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const ModifierKey& winKeyInvoked, LPINPUT keyEventArray, int& index, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare = Shortcut(), const DWORD& keyToBeReleased = NULL);
|
||||
void SetModifierKeyEvents(const Shortcut& shortcutToBeSent, const ModifierKey& winKeyInvoked, std::vector<INPUT>& keyEventArray, bool isKeyDown, ULONG_PTR extraInfoFlag, const Shortcut& shortcutToCompare = Shortcut(), const DWORD& keyToBeReleased = NULL);
|
||||
|
||||
// Function to filter the key codes for artificial key codes
|
||||
int32_t FilterArtificialKeys(const int32_t& key);
|
||||
|
@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <keyboardmanager/common/InputInterface.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <keyboardmanager/common/Helpers.h>
|
||||
#include <keyboardmanager/common/InputInterface.h>
|
||||
|
||||
namespace KeyboardManagerInput
|
||||
{
|
||||
@ -10,9 +12,16 @@ namespace KeyboardManagerInput
|
||||
{
|
||||
public:
|
||||
// Function to simulate input
|
||||
UINT SendVirtualInput(UINT cInputs, LPINPUT pInputs, int cbSize)
|
||||
void SendVirtualInput(const std::vector<INPUT>& inputs)
|
||||
{
|
||||
return SendInput(cInputs, pInputs, cbSize);
|
||||
std::vector<INPUT> copy = inputs;
|
||||
UINT eventCount = SendInput(static_cast<UINT>(copy.size()), copy.data(), sizeof(INPUT));
|
||||
if (eventCount != copy.size())
|
||||
{
|
||||
Logger::error(
|
||||
L"Failed to send input events. {}",
|
||||
get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the state of a particular key
|
||||
|
@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace KeyboardManagerInput
|
||||
{
|
||||
// Interface used to wrap keyboard input library methods
|
||||
@ -7,7 +11,7 @@ namespace KeyboardManagerInput
|
||||
{
|
||||
public:
|
||||
// Function to simulate input
|
||||
virtual UINT SendVirtualInput(UINT cInputs, LPINPUT pInputs, int cbSize) = 0;
|
||||
virtual void SendVirtualInput(const std::vector<INPUT>& inputs) = 0;
|
||||
|
||||
// Function to get the state of a particular key
|
||||
virtual bool GetVirtualKeyState(int key) = 0;
|
||||
|
@ -10,14 +10,11 @@ namespace KeyboardEventHandlers
|
||||
void SetNumLockToPreviousState(KeyboardManagerInput::InputInterface& ii)
|
||||
{
|
||||
// Num Lock's key state is applied before it is intercepted by low level keyboard hooks, so we have to manually set back the state when we suppress the key. This is done by sending an additional key up, key down set of messages.
|
||||
// We need 2 key events because after Num Lock is suppressed, key up to release num lock key and key down to revert the num lock state
|
||||
int key_count = 2;
|
||||
LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
|
||||
std::vector<INPUT> keyEventList;
|
||||
|
||||
// Use the suppress flag to ensure these are not intercepted by any remapped keys or shortcuts
|
||||
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, VK_NUMLOCK, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, 1, INPUT_KEYBOARD, VK_NUMLOCK, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, VK_NUMLOCK, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, VK_NUMLOCK, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
ii.SendVirtualInput(keyEventList);
|
||||
}
|
||||
}
|
||||
|
@ -44,13 +44,13 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter
|
||||
return first.Value;
|
||||
}
|
||||
|
||||
if (UnitParser.Default.TryParse(unit, unitInfo.UnitType, out Enum enum_unit))
|
||||
if (UnitsNetSetup.Default.UnitParser.TryParse(unit, unitInfo.UnitType, out Enum enum_unit))
|
||||
{
|
||||
return enum_unit;
|
||||
}
|
||||
|
||||
var cultureInfoEnglish = new System.Globalization.CultureInfo("en-US");
|
||||
if (UnitParser.Default.TryParse(unit, unitInfo.UnitType, cultureInfoEnglish, out Enum enum_unit_en))
|
||||
if (UnitsNetSetup.Default.UnitParser.TryParse(unit, unitInfo.UnitType, cultureInfoEnglish, out Enum enum_unit_en))
|
||||
{
|
||||
return enum_unit_en;
|
||||
}
|
||||
|
@ -19,13 +19,13 @@ namespace Microsoft.Plugin.WindowWalker.Components
|
||||
/// <param name="hwnd">handle to the window to exclude</param>
|
||||
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
|
||||
{
|
||||
int renderPolicy = (int)DwmNCRenderingPolicies.Enabled;
|
||||
uint renderPolicy = (uint)DwmNCRenderingPolicies.Enabled;
|
||||
|
||||
_ = NativeMethods.DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
12,
|
||||
ref renderPolicy,
|
||||
sizeof(int));
|
||||
sizeof(uint));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -32,9 +32,9 @@
|
||||
|
||||
<Grid x:Name="RootGrid" MouseDown="OnMouseDown">
|
||||
<!-- We set the background here because the Acrylic can be too translucent / background too bright on Light theme -->
|
||||
<!--<Grid.Background>
|
||||
<Grid.Background>
|
||||
<SolidColorBrush Opacity="0.8" Color="{DynamicResource ApplicationBackgroundColor}" />
|
||||
</Grid.Background>-->
|
||||
</Grid.Background>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -45,11 +45,6 @@
|
||||
Grid.Row="0"
|
||||
Padding="12,4,12,3">
|
||||
<local:LauncherControl x:Name="SearchBox" />
|
||||
<Border.Background>
|
||||
<!-- Setting the background of the search bar to fix https://github.com/microsoft/PowerToys/issues/30206 -->
|
||||
<!-- The title bar accent would bleed if the option to "Show accent color on title bars and windows borders" is enabled on Windows -->
|
||||
<SolidColorBrush Opacity="1" Color="{DynamicResource ApplicationBackgroundColor}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<!-- Have to use a Grid instead of a StackPanel for scrolling to work? -->
|
||||
|
@ -63,7 +63,7 @@ namespace PowerLauncher
|
||||
|
||||
if (OSVersionHelper.IsWindows11())
|
||||
{
|
||||
WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.Mica;
|
||||
WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.Acrylic;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -8,11 +8,13 @@ using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Threading;
|
||||
using Common.UI;
|
||||
using interop;
|
||||
@ -1006,6 +1008,46 @@ namespace PowerLauncher.ViewModel
|
||||
if (MainWindowVisibility != Visibility.Visible)
|
||||
{
|
||||
MainWindowVisibility = Visibility.Visible;
|
||||
|
||||
// HACK: The following code in this if is a fix for https://github.com/microsoft/PowerToys/issues/30206 and https://github.com/microsoft/PowerToys/issues/33135
|
||||
// WPF UI theme watcher removes the composition target background color, among other weird stuff.
|
||||
// https://github.com/lepoco/wpfui/blob/303f0aefcd59a142bc681415dc4360a34a15f33d/src/Wpf.Ui/Controls/Window/WindowBackdrop.cs#L280
|
||||
// So we set it back with https://github.com/lepoco/wpfui/blob/303f0aefcd59a142bc681415dc4360a34a15f33d/src/Wpf.Ui/Controls/Window/WindowBackdrop.cs#L191
|
||||
var window = Application.Current.MainWindow;
|
||||
|
||||
// Only makes sense for Windows 11 or greater, since Windows 10 doesn't have Mica.
|
||||
if (OSVersionHelper.IsWindows11())
|
||||
{
|
||||
Wpf.Ui.Controls.WindowBackdrop.RemoveBackground(window);
|
||||
}
|
||||
|
||||
// Setting uint titlebarPvAttribute = 0xFFFFFFFE; works on 22H2 or higher, 21H2 (aka SV1) this value causes a crash
|
||||
if (OSVersionHelper.IsGreaterThanWindows11_21H2())
|
||||
{
|
||||
// Taken from WPFUI's fix for the title bar issue. We should be able to remove this fix when WPF UI 4 is integrated.
|
||||
// https://github.com/lepoco/wpfui/pull/1122/files#diff-196b404f4db09632665ef546da6c8e57302b2f3e3d082eb4b5c295ae3482d94a
|
||||
IntPtr windowHandle = new WindowInteropHelper(window).Handle;
|
||||
if (windowHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HwndSource windowSource = HwndSource.FromHwnd(windowHandle);
|
||||
|
||||
// Remove background from client area
|
||||
if (windowSource != null && windowSource.Handle != IntPtr.Zero && windowSource?.CompositionTarget != null)
|
||||
{
|
||||
// NOTE: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
// Specifying DWMWA_COLOR_DEFAULT (value 0xFFFFFFFF) for the color will reset the window back to using the system's default behavior for the caption color.
|
||||
uint titlebarPvAttribute = 0xFFFFFFFE;
|
||||
|
||||
_ = Wox.Plugin.Common.Win32.NativeMethods.DwmSetWindowAttribute(
|
||||
windowSource.Handle,
|
||||
(int)Wox.Plugin.Common.Win32.DwmWindowAttributes.CaptionColor, // CaptionColor attribute is only available on Windows 11.
|
||||
ref titlebarPvAttribute,
|
||||
Marshal.SizeOf(typeof(uint)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -66,7 +66,7 @@ namespace Wox.Plugin.Common.Win32
|
||||
public static extern int DwmpActivateLivePreview([MarshalAs(UnmanagedType.Bool)] bool fActivate, IntPtr hWndExclude, IntPtr hWndInsertBefore, LivePreviewTrigger lpt, IntPtr prcFinalRect);
|
||||
|
||||
[DllImport("dwmapi.dll", PreserveSig = false)]
|
||||
public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
|
||||
public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref uint attrValue, int attrSize);
|
||||
|
||||
[DllImport("dwmapi.dll", PreserveSig = false)]
|
||||
public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out int pvAttribute, int cbAttribute);
|
||||
@ -493,28 +493,28 @@ namespace Wox.Plugin.Common.Win32
|
||||
public enum DwmWindowAttributes
|
||||
{
|
||||
NCRenderingEnabled = 1,
|
||||
NCRenderingPolicy,
|
||||
TransitionsForceDisabled,
|
||||
AllowNCPaint,
|
||||
CaptionButtonBounds,
|
||||
NonClientRtlLayout,
|
||||
ForceIconicRepresentation,
|
||||
Flip3DPolicy,
|
||||
ExtendedFrameBounds,
|
||||
HasIconicBitmap,
|
||||
DisallowPeek,
|
||||
ExcludedFromPeek,
|
||||
Cloak,
|
||||
Cloaked,
|
||||
FreezeRepresentation,
|
||||
PassiveUpdateMode,
|
||||
UseHostbackdropbrush,
|
||||
UseImmersiveDarkMode,
|
||||
WindowCornerPreference,
|
||||
BorderColor,
|
||||
CaptionColor,
|
||||
TextColor,
|
||||
VisibleFrameBorderThickness,
|
||||
NCRenderingPolicy = 2,
|
||||
TransitionsForceDisabled = 3,
|
||||
AllowNCPaint = 4,
|
||||
CaptionButtonBounds = 5,
|
||||
NonClientRtlLayout = 6,
|
||||
ForceIconicRepresentation = 7,
|
||||
Flip3DPolicy = 8,
|
||||
ExtendedFrameBounds = 9,
|
||||
HasIconicBitmap = 10,
|
||||
DisallowPeek = 11,
|
||||
ExcludedFromPeek = 12,
|
||||
Cloak = 13,
|
||||
Cloaked = 14,
|
||||
FreezeRepresentation = 15,
|
||||
PassiveUpdateMode = 16,
|
||||
UseHostbackdropbrush = 17,
|
||||
UseImmersiveDarkMode = 20,
|
||||
WindowCornerPreference = 33,
|
||||
BorderColor = 34,
|
||||
CaptionColor = 35,
|
||||
TextColor = 36,
|
||||
VisibleFrameBorderThickness = 37,
|
||||
Last,
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,9 @@
|
||||
<!-- This package is a dependency of System.Drawing.Common, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Drawing.Common">
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.CodeDom">
|
||||
<!-- This package is a dependency of System.Management, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
|
12
src/modules/peek/Peek.Common/Helpers/PathHelper.cs
Normal file
12
src/modules/peek/Peek.Common/Helpers/PathHelper.cs
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Peek.Common.Helpers;
|
||||
|
||||
public static class PathHelper
|
||||
{
|
||||
public static bool IsUncPath(string path) => Uri.TryCreate(path, UriKind.Absolute, out Uri? uri) && uri != null && uri.IsUnc;
|
||||
}
|
26
src/modules/peek/Peek.Common/Helpers/ThreadHelper.cs
Normal file
26
src/modules/peek/Peek.Common/Helpers/ThreadHelper.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Peek.Common.Helpers;
|
||||
|
||||
public static class ThreadHelper
|
||||
{
|
||||
public static void RunOnSTAThread(ThreadStart action)
|
||||
{
|
||||
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
|
||||
{
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread staThread = new(action);
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
staThread.Start();
|
||||
staThread.Join();
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ namespace Peek.Common.Models
|
||||
|
||||
public string Name { get; init; }
|
||||
|
||||
public string ParsingName => string.Empty;
|
||||
|
||||
public string Path { get; init; }
|
||||
|
||||
public string Extension => System.IO.Path.GetExtension(Path).ToLower(CultureInfo.InvariantCulture);
|
||||
|
@ -11,19 +11,15 @@ using Windows.Storage;
|
||||
|
||||
namespace Peek.Common.Models
|
||||
{
|
||||
public class FolderItem : IFileSystemItem
|
||||
public class FolderItem(string path, string name, string parsingName) : IFileSystemItem
|
||||
{
|
||||
private StorageFolder? storageFolder;
|
||||
|
||||
public FolderItem(string path, string name)
|
||||
{
|
||||
Path = path;
|
||||
Name = name;
|
||||
}
|
||||
public string Name { get; init; } = name;
|
||||
|
||||
public string Name { get; init; }
|
||||
public string ParsingName { get; init; } = parsingName;
|
||||
|
||||
public string Path { get; init; }
|
||||
public string Path { get; init; } = path;
|
||||
|
||||
public string Extension => string.Empty;
|
||||
|
||||
@ -38,7 +34,7 @@ namespace Peek.Common.Models
|
||||
{
|
||||
try
|
||||
{
|
||||
storageFolder = await StorageFolder.GetFolderFromPathAsync(Path);
|
||||
storageFolder = string.IsNullOrEmpty(Path) ? null : await StorageFolder.GetFolderFromPathAsync(Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -17,25 +17,24 @@ namespace Peek.Common.Models
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTime? dateModified;
|
||||
try
|
||||
{
|
||||
dateModified = System.IO.File.GetCreationTime(Path);
|
||||
return string.IsNullOrEmpty(Path) ? null : System.IO.File.GetCreationTime(Path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
dateModified = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
return dateModified;
|
||||
}
|
||||
}
|
||||
|
||||
public string Extension { get; }
|
||||
|
||||
public string Name { get; init; }
|
||||
public string Name { get; }
|
||||
|
||||
public string Path { get; init; }
|
||||
public string ParsingName { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public ulong FileSizeBytes => PropertyStoreHelper.TryGetUlongProperty(Path, PropertyKey.FileSizeBytes) ?? 0;
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.Controls.SpecialFolderInformationalPreviewControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid ColumnSpacing="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="Icon" Width="Auto" />
|
||||
<ColumnDefinition x:Name="FileInfo" Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
x:Name="FileIcon"
|
||||
Grid.Column="0"
|
||||
Width="180"
|
||||
Height="180"
|
||||
Source="{x:Bind Source.IconPreview, Mode=OneWay}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="4">
|
||||
|
||||
<TextBlock
|
||||
MaxLines="3"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{x:Bind Source.FileName, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="Wrap">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind Source.FileName, Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{x:Bind FormatFileType(Source.FileType), Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind FormatFileType(Source.FileType), Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{x:Bind FormatFileSize(Source.FileSize), Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind FormatFileSize(Source.FileSize), Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{x:Bind FormatFileDateModified(Source.DateModified), Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind FormatFileDateModified(Source.DateModified), Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Peek.Common.Helpers;
|
||||
using Peek.FilePreviewer.Models;
|
||||
|
||||
namespace Peek.FilePreviewer.Controls;
|
||||
|
||||
public sealed partial class SpecialFolderInformationalPreviewControl : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
nameof(Source),
|
||||
typeof(SpecialFolderPreviewData),
|
||||
typeof(SpecialFolderInformationalPreviewControl),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public SpecialFolderPreviewData? Source
|
||||
{
|
||||
get { return (SpecialFolderPreviewData)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public SpecialFolderInformationalPreviewControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string FormatFileType(string? fileType) => FormatField("UnsupportedFile_FileType", fileType);
|
||||
|
||||
public string FormatFileSize(string? fileSize) => FormatField("UnsupportedFile_FileSize", fileSize);
|
||||
|
||||
public string FormatFileDateModified(string? fileDateModified) => FormatField("UnsupportedFile_DateModified", fileDateModified);
|
||||
|
||||
private static string FormatField(string resourceId, string? fieldValue)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(fieldValue) ? string.Empty : ReadableStringHelper.FormatResourceString(resourceId, fieldValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.Controls.SpecialFolderPreview"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Peek.FilePreviewer.Controls"
|
||||
xmlns:conv="using:Peek.Common.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prev="using:Peek.FilePreviewer.Previewers"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
Margin="24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<controls:SpecialFolderInformationalPreviewControl
|
||||
x:Name="InformationPreview"
|
||||
Source="{x:Bind Source, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsVisibleIfStatesMatch(LoadingState, prev:PreviewState.Loaded), Mode=OneWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Peek.Common.Converters;
|
||||
using Peek.FilePreviewer.Models;
|
||||
using Peek.FilePreviewer.Previewers;
|
||||
|
||||
namespace Peek.FilePreviewer.Controls;
|
||||
|
||||
[INotifyPropertyChanged]
|
||||
public sealed partial class SpecialFolderPreview : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
nameof(Source),
|
||||
typeof(SpecialFolderPreviewData),
|
||||
typeof(SpecialFolderPreview),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty LoadingStateProperty = DependencyProperty.Register(
|
||||
nameof(LoadingState),
|
||||
typeof(PreviewState),
|
||||
typeof(SpecialFolderPreview),
|
||||
new PropertyMetadata(PreviewState.Uninitialized));
|
||||
|
||||
public SpecialFolderPreviewData? Source
|
||||
{
|
||||
get { return (SpecialFolderPreviewData)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public PreviewState? LoadingState
|
||||
{
|
||||
get { return (PreviewState)GetValue(LoadingStateProperty); }
|
||||
set { SetValue(LoadingStateProperty, value); }
|
||||
}
|
||||
|
||||
public SpecialFolderPreview()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Visibility IsVisibleIfStatesMatch(PreviewState? a, PreviewState? b) => VisibilityConverter.Convert(a == b);
|
||||
}
|
@ -83,6 +83,12 @@
|
||||
Source="{x:Bind DrivePreviewer.Preview, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(DrivePreviewer, Previewer.State), Mode=OneWay}" />
|
||||
|
||||
<controls:SpecialFolderPreview
|
||||
x:Name="SpecialFolderPreview"
|
||||
LoadingState="{x:Bind SpecialFolderPreviewer.State, Mode=OneWay}"
|
||||
Source="{x:Bind SpecialFolderPreviewer.Preview, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(SpecialFolderPreviewer, Previewer.State), Mode=OneWay}" />
|
||||
|
||||
<controls:UnsupportedFilePreview
|
||||
x:Name="UnsupportedFilePreview"
|
||||
LoadingState="{x:Bind UnsupportedFilePreviewer.State, Mode=OneWay}"
|
||||
|
@ -53,6 +53,7 @@ namespace Peek.FilePreviewer
|
||||
[NotifyPropertyChangedFor(nameof(ArchivePreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(ShellPreviewHandlerPreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(DrivePreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(SpecialFolderPreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
|
||||
private IPreviewer? previewer;
|
||||
|
||||
@ -105,6 +106,8 @@ namespace Peek.FilePreviewer
|
||||
|
||||
public IDrivePreviewer? DrivePreviewer => Previewer as IDrivePreviewer;
|
||||
|
||||
public ISpecialFolderPreviewer? SpecialFolderPreviewer => Previewer as ISpecialFolderPreviewer;
|
||||
|
||||
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
|
||||
|
||||
public IFileSystemItem Item
|
||||
|
@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Peek.FilePreviewer.Models;
|
||||
|
||||
public partial class SpecialFolderPreviewData : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ImageSource? iconPreview;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileName;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileType;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileSize;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? dateModified;
|
||||
}
|
@ -21,6 +21,8 @@
|
||||
<None Remove="Controls\BrowserControl.xaml" />
|
||||
<None Remove="Controls\DriveControl.xaml" />
|
||||
<None Remove="Controls\ShellPreviewHandlerControl.xaml" />
|
||||
<None Remove="Controls\SpecialFolderPreview\SpecialFolderInformationalPreviewControl.xaml" />
|
||||
<None Remove="Controls\SpecialFolderPreview\SpecialFolderPreview.xaml" />
|
||||
<None Remove="Controls\UnsupportedFilePreview\FailedFallbackPreviewControl.xaml" />
|
||||
<None Remove="Controls\UnsupportedFilePreview\InformationalPreviewControl.xaml" />
|
||||
<None Remove="FilePreview.xaml" />
|
||||
@ -42,6 +44,27 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<COMReference Include="Shell32">
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="SHDocVw">
|
||||
<VersionMinor>1</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>eab22ac0-30c1-11cf-a7eb-0000c05bae0b</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||
@ -71,6 +94,9 @@
|
||||
<Page Update="Controls\ArchiveControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Controls\SpecialFolderPreview\SpecialFolderInformationalPreviewControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Peek.FilePreviewer.Models;
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers;
|
||||
|
||||
public interface ISpecialFolderPreviewer : IPreviewer
|
||||
{
|
||||
public SpecialFolderPreviewData? Preview { get; }
|
||||
}
|
@ -53,6 +53,10 @@ namespace Peek.FilePreviewer.Previewers
|
||||
{
|
||||
return new DrivePreviewer(item);
|
||||
}
|
||||
else if (SpecialFolderPreviewer.IsItemSupported(item))
|
||||
{
|
||||
return new SpecialFolderPreviewer(item);
|
||||
}
|
||||
|
||||
// Other previewer types check their supported file types here
|
||||
return CreateDefaultPreviewer(item);
|
||||
|
@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Peek.Common;
|
||||
using Peek.Common.Models;
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers;
|
||||
|
||||
public enum KnownSpecialFolder
|
||||
{
|
||||
None,
|
||||
RecycleBin,
|
||||
}
|
||||
|
||||
public static class KnownSpecialFolders
|
||||
{
|
||||
private static readonly Lazy<IReadOnlyDictionary<string, KnownSpecialFolder>> FoldersByParsingNameDict = new(GetFoldersByParsingName);
|
||||
|
||||
public static IReadOnlyDictionary<string, KnownSpecialFolder> FoldersByParsingName => FoldersByParsingNameDict.Value;
|
||||
|
||||
private static Dictionary<string, KnownSpecialFolder> GetFoldersByParsingName()
|
||||
{
|
||||
var folders = new (KnownSpecialFolder Folder, string? ParsingName)[]
|
||||
{
|
||||
(KnownSpecialFolder.RecycleBin, GetParsingName("shell:RecycleBinFolder")),
|
||||
};
|
||||
|
||||
return folders.Where(folder => !string.IsNullOrEmpty(folder.ParsingName))
|
||||
.ToDictionary(folder => folder.ParsingName!, folder => folder.Folder);
|
||||
}
|
||||
|
||||
private static string? GetParsingName(string shellName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return CreateShellItemFromShellName(shellName)?.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IShellItem? CreateShellItemFromShellName(string shellName)
|
||||
{
|
||||
// Based on https://stackoverflow.com/a/42966899
|
||||
const string ShellItem = "43826d1e-e718-42ee-bc55-a1e261c37bfe";
|
||||
|
||||
Guid shellItem2Guid = new(ShellItem);
|
||||
int retCode = NativeMethods.SHCreateItemFromParsingName(shellName, IntPtr.Zero, ref shellItem2Guid, out IShellItem? nativeShellItem);
|
||||
|
||||
return retCode == 0 ? nativeShellItem : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Peek.Common.Extensions;
|
||||
using Peek.Common.Helpers;
|
||||
using Peek.Common.Models;
|
||||
using Peek.FilePreviewer.Models;
|
||||
using Peek.FilePreviewer.Previewers.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers;
|
||||
|
||||
public partial class SpecialFolderPreviewer : ObservableObject, ISpecialFolderPreviewer, IDisposable
|
||||
{
|
||||
private readonly DispatcherTimer _syncDetailsDispatcherTimer = new();
|
||||
private ulong _folderSize;
|
||||
private DateTime? _dateModified;
|
||||
|
||||
[ObservableProperty]
|
||||
private SpecialFolderPreviewData preview = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private PreviewState state;
|
||||
|
||||
public SpecialFolderPreviewer(IFileSystemItem file)
|
||||
{
|
||||
_syncDetailsDispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
|
||||
_syncDetailsDispatcherTimer.Tick += DetailsDispatcherTimer_Tick;
|
||||
|
||||
Item = file;
|
||||
Preview.FileName = file.Name;
|
||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
public static bool IsItemSupported(IFileSystemItem item)
|
||||
{
|
||||
// Always allow know special folders.
|
||||
bool isKnownSpecialFolder = KnownSpecialFolders.FoldersByParsingName.ContainsKey(item.ParsingName);
|
||||
|
||||
// Allow empty paths unless Unc; icons don't load correctly for Unc paths.
|
||||
bool isEmptyNonUncPath = string.IsNullOrEmpty(item.Path) && !PathHelper.IsUncPath(item.ParsingName);
|
||||
|
||||
return isKnownSpecialFolder || isEmptyNonUncPath;
|
||||
}
|
||||
|
||||
private IFileSystemItem Item { get; }
|
||||
|
||||
private DispatcherQueue Dispatcher { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_syncDetailsDispatcherTimer.Tick -= DetailsDispatcherTimer_Tick;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public Task<PreviewSize> GetPreviewSizeAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Size? size = new(680, 500);
|
||||
var previewSize = new PreviewSize { MonitorSize = size, UseEffectivePixels = true };
|
||||
return Task.FromResult(previewSize);
|
||||
}
|
||||
|
||||
public async Task LoadPreviewAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
State = PreviewState.Loading;
|
||||
|
||||
var tasks = await Task.WhenAll(LoadIconPreviewAsync(cancellationToken), LoadDisplayInfoAsync(cancellationToken));
|
||||
|
||||
State = tasks.All(task => task) ? PreviewState.Loaded : PreviewState.Error;
|
||||
}
|
||||
|
||||
public async Task CopyAsync()
|
||||
{
|
||||
await Dispatcher.RunOnUiThread(async () =>
|
||||
{
|
||||
var storageItem = await Item.GetStorageItemAsync();
|
||||
ClipboardHelper.SaveToClipboard(storageItem);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> LoadIconPreviewAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
bool isIconValid = false;
|
||||
|
||||
var isTaskSuccessful = await TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await Dispatcher.RunOnUiThread(async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var iconBitmap = await IconHelper.GetIconAsync(Item.ParsingName, cancellationToken);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
isIconValid = iconBitmap != null;
|
||||
Preview.IconPreview = iconBitmap ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
|
||||
});
|
||||
});
|
||||
|
||||
return isIconValid && isTaskSuccessful;
|
||||
}
|
||||
|
||||
public async Task<bool> LoadDisplayInfoAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
bool isDisplayValid = false;
|
||||
|
||||
var isTaskSuccessful = await TaskExtension.RunSafe(async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var fileType = await Task.Run(Item.GetContentTypeAsync);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
isDisplayValid = fileType != null;
|
||||
|
||||
await Dispatcher.RunOnUiThread(() =>
|
||||
{
|
||||
Preview.FileType = fileType;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
RunUpdateDetailsWorkflow(cancellationToken);
|
||||
});
|
||||
|
||||
return isDisplayValid && isTaskSuccessful;
|
||||
}
|
||||
|
||||
private void RunUpdateDetailsWorkflow(CancellationToken cancellationToken)
|
||||
{
|
||||
Task.Run(
|
||||
async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Dispatcher.RunOnUiThread(_syncDetailsDispatcherTimer.Start);
|
||||
ComputeDetails(cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to update special folder details", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Dispatcher.RunOnUiThread(_syncDetailsDispatcherTimer.Stop);
|
||||
}
|
||||
|
||||
await Dispatcher.RunOnUiThread(SyncDetails);
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private void ComputeDetails(CancellationToken cancellationToken)
|
||||
{
|
||||
_dateModified = Item.DateModified;
|
||||
|
||||
switch (KnownSpecialFolders.FoldersByParsingName.GetValueOrDefault(Item.ParsingName, KnownSpecialFolder.None))
|
||||
{
|
||||
case KnownSpecialFolder.None:
|
||||
break;
|
||||
|
||||
case KnownSpecialFolder.RecycleBin:
|
||||
ThreadHelper.RunOnSTAThread(() => { ComputeRecycleBinDetails(cancellationToken); });
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ComputeRecycleBinDetails(CancellationToken cancellationToken)
|
||||
{
|
||||
var shell = new Shell32.Shell();
|
||||
var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
|
||||
|
||||
foreach (dynamic item in recycleBin.Items())
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_folderSize += Convert.ToUInt64(item.Size);
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncDetails()
|
||||
{
|
||||
Preview.FileSize = _folderSize == 0 ? string.Empty : ReadableStringHelper.BytesToReadableString(_folderSize);
|
||||
Preview.DateModified = _dateModified?.ToString(CultureInfo.CurrentCulture) ?? string.Empty;
|
||||
}
|
||||
|
||||
private void DetailsDispatcherTimer_Tick(object? sender, object e)
|
||||
{
|
||||
SyncDetails();
|
||||
}
|
||||
}
|
@ -45,8 +45,6 @@ namespace Peek.FilePreviewer.Previewers
|
||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
public bool IsPreviewLoaded => Preview.IconPreview != null;
|
||||
|
||||
private IFileSystemItem Item { get; }
|
||||
|
||||
private DispatcherQueue Dispatcher { get; }
|
||||
|
@ -7,49 +7,41 @@ using System.IO;
|
||||
using ManagedCommon;
|
||||
using Peek.Common.Models;
|
||||
|
||||
namespace Peek.UI.Extensions
|
||||
namespace Peek.UI.Extensions;
|
||||
|
||||
public static class IShellItemExtensions
|
||||
{
|
||||
public static class IShellItemExtensions
|
||||
public static IFileSystemItem ToIFileSystemItem(this IShellItem shellItem)
|
||||
{
|
||||
public static IFileSystemItem ToIFileSystemItem(this IShellItem shellItem)
|
||||
{
|
||||
string path = shellItem.GetPath();
|
||||
string name = shellItem.GetName();
|
||||
string path = shellItem.GetPath();
|
||||
string name = shellItem.GetName();
|
||||
|
||||
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name);
|
||||
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name, shellItem.GetParsingName());
|
||||
}
|
||||
|
||||
private static string GetPath(this IShellItem shellItem) =>
|
||||
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH, logError: false);
|
||||
|
||||
private static string GetName(this IShellItem shellItem) =>
|
||||
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY, logError: true);
|
||||
|
||||
private static string GetParsingName(this IShellItem shellItem) =>
|
||||
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, logError: true);
|
||||
|
||||
private static string GetNameCore(this IShellItem shellItem, Windows.Win32.UI.Shell.SIGDN displayNameType, bool logError)
|
||||
{
|
||||
try
|
||||
{
|
||||
return shellItem.GetDisplayName(displayNameType);
|
||||
}
|
||||
|
||||
private static string GetPath(this IShellItem shellItem)
|
||||
catch (Exception ex)
|
||||
{
|
||||
string path = string.Empty;
|
||||
try
|
||||
if (logError)
|
||||
{
|
||||
path = shellItem.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: Handle cases that do not have a file system path like Recycle Bin.
|
||||
path = string.Empty;
|
||||
Logger.LogError("Getting path failed. " + ex.Message);
|
||||
Logger.LogError($"Getting {Enum.GetName(displayNameType)} failed. {ex.Message}");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static string GetName(this IShellItem shellItem)
|
||||
{
|
||||
string name = string.Empty;
|
||||
try
|
||||
{
|
||||
name = shellItem.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
name = string.Empty;
|
||||
Logger.LogError("Getting path failed. " + ex.Message);
|
||||
}
|
||||
|
||||
return name;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ private:
|
||||
}
|
||||
|
||||
// Enumerate all Shell Windows to compare the window handle against.
|
||||
IUnknownPtr spEnum{};
|
||||
IUnknownPtr spEnum{}; // _com_ptr_t; no Release required.
|
||||
result = spShellWindows->_NewEnum(&spEnum);
|
||||
if (result != S_OK || spEnum == nullptr)
|
||||
{
|
||||
@ -195,7 +195,7 @@ private:
|
||||
return true; // Might as well assume it's possible it's an explorer window.
|
||||
}
|
||||
|
||||
IEnumVARIANTPtr spEnumVariant{};
|
||||
IEnumVARIANTPtr spEnumVariant{}; // _com_ptr_t; no Release required.
|
||||
result = spEnum.QueryInterface(__uuidof(spEnumVariant), &spEnumVariant);
|
||||
if (result != S_OK || spEnumVariant == nullptr)
|
||||
{
|
||||
@ -219,8 +219,6 @@ private:
|
||||
{
|
||||
VariantClear(&variantElement);
|
||||
spWebBrowserApp->Release();
|
||||
spEnumVariant->Release();
|
||||
spEnum->Release();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -229,8 +227,6 @@ private:
|
||||
VariantClear(&variantElement);
|
||||
}
|
||||
|
||||
spEnumVariant->Release();
|
||||
spEnum->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -475,11 +475,11 @@ namespace PowerAccent.Core
|
||||
LetterKey.VK_7 => new[] { "₇", "⁷" },
|
||||
LetterKey.VK_8 => new[] { "₈", "⁸" },
|
||||
LetterKey.VK_9 => new[] { "₉", "⁹" },
|
||||
LetterKey.VK_A => new[] { "á", "à", "â", "ã" },
|
||||
LetterKey.VK_A => new[] { "á", "à", "â", "ã", "ª" },
|
||||
LetterKey.VK_C => new[] { "ç" },
|
||||
LetterKey.VK_E => new[] { "é", "ê", "€" },
|
||||
LetterKey.VK_I => new[] { "í" },
|
||||
LetterKey.VK_O => new[] { "ô", "ó", "õ" },
|
||||
LetterKey.VK_O => new[] { "ô", "ó", "õ", "º" },
|
||||
LetterKey.VK_P => new[] { "π" },
|
||||
LetterKey.VK_S => new[] { "$" },
|
||||
LetterKey.VK_U => new[] { "ú" },
|
||||
|
@ -15,6 +15,7 @@ namespace PowerRenameUI
|
||||
Windows.Foundation.Collections.IObservableVector<PatternSnippet> SearchRegExShortcuts { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<PatternSnippet> DateTimeShortcuts { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<PatternSnippet> CounterShortcuts { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<PatternSnippet> RandomizerShortcuts { get; };
|
||||
|
||||
String OriginalCount;
|
||||
String RenamedCount;
|
||||
|
@ -327,6 +327,8 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Uid="DateTimeCheatSheet_Title" FontWeight="SemiBold" />
|
||||
<ListView
|
||||
@ -407,6 +409,47 @@
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<TextBlock
|
||||
x:Uid="RandomizerCheatSheet_Title"
|
||||
Grid.Row="4"
|
||||
Margin="0,10,0,0"
|
||||
FontWeight="SemiBold" />
|
||||
<ListView
|
||||
Grid.Row="5"
|
||||
Margin="-4,12,0,0"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="DateTimeItemClick"
|
||||
ItemsSource="{x:Bind RandomizerShortcuts}"
|
||||
SelectionMode="None">
|
||||
<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>
|
||||
@ -508,6 +551,12 @@
|
||||
Height="32"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<ToggleButton
|
||||
x:Name="toggleButton_randItems"
|
||||
x:Uid="ToggleButton_RandItems"
|
||||
Height="32"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
@ -203,6 +203,12 @@ namespace winrt::PowerRenameUI::implementation
|
||||
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()));
|
||||
|
||||
m_RandomizerShortcuts = winrt::single_threaded_observable_vector<PowerRenameUI::PatternSnippet>();
|
||||
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringalnum=9}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Alnum").ValueAsString()));
|
||||
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringalpha=13}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Alpha").ValueAsString()));
|
||||
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${rstringdigit=36}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Digit").ValueAsString()));
|
||||
m_RandomizerShortcuts.Append(winrt::make<PatternSnippet>(L"${ruuidv4}", manager.MainResourceMap().GetValue(L"Resources/RandomizerCheatSheet_Uuid").ValueAsString()));
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
listView_ExplorerItems().ApplyTemplate();
|
||||
@ -283,6 +289,7 @@ namespace winrt::PowerRenameUI::implementation
|
||||
|
||||
button_rename().IsEnabled(false);
|
||||
toggleButton_enumItems().IsChecked(true);
|
||||
toggleButton_randItems().IsChecked(false);
|
||||
InitAutoComplete();
|
||||
SearchReplaceChanged();
|
||||
InvalidateItemListViewState();
|
||||
@ -749,6 +756,15 @@ namespace winrt::PowerRenameUI::implementation
|
||||
UpdateFlag(EnumerateItems, UpdateFlagCommand::Reset);
|
||||
});
|
||||
|
||||
// CheckBox RandomizeItems
|
||||
toggleButton_randItems().Checked([&](auto const&, auto const&) {
|
||||
ValidateFlags(RandomizeItems);
|
||||
UpdateFlag(RandomizeItems, UpdateFlagCommand::Set);
|
||||
});
|
||||
toggleButton_randItems().Unchecked([&](auto const&, auto const&) {
|
||||
UpdateFlag(RandomizeItems, UpdateFlagCommand::Reset);
|
||||
});
|
||||
|
||||
// ButtonSettings
|
||||
button_settings().Click([&](auto const&, auto const&) {
|
||||
OpenSettingsApp();
|
||||
@ -944,6 +960,10 @@ namespace winrt::PowerRenameUI::implementation
|
||||
{
|
||||
toggleButton_enumItems().IsChecked(true);
|
||||
}
|
||||
if (flags & RandomizeItems)
|
||||
{
|
||||
toggleButton_randItems().IsChecked(true);
|
||||
}
|
||||
if (flags & ExcludeFiles)
|
||||
{
|
||||
toggleButton_includeFiles().IsChecked(false);
|
||||
|
@ -85,6 +85,7 @@ namespace winrt::PowerRenameUI::implementation
|
||||
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; }
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> RandomizerShortcuts() { return m_RandomizerShortcuts; }
|
||||
|
||||
hstring OriginalCount();
|
||||
void OriginalCount(hstring value);
|
||||
@ -107,6 +108,7 @@ namespace winrt::PowerRenameUI::implementation
|
||||
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;
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<PowerRenameUI::PatternSnippet> m_RandomizerShortcuts;
|
||||
|
||||
// Used by PowerRenameManagerEvents
|
||||
HRESULT OnRename(_In_ IPowerRenameItem* renameItem);
|
||||
|
@ -151,7 +151,7 @@
|
||||
<value>Replace with</value>
|
||||
</data>
|
||||
<data name="CounterCheatSheet_Title.Text" xml:space="preserve">
|
||||
<value>Replace using advanced counter syntax.</value>
|
||||
<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>
|
||||
@ -295,10 +295,10 @@
|
||||
<value>Capitalize each word</value>
|
||||
</data>
|
||||
<data name="ToggleButton_EnumItems.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Enumerate items</value>
|
||||
<value>Enumeration features</value>
|
||||
</data>
|
||||
<data name="ToggleButton_EnumItems.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Enumerate items</value>
|
||||
<value>Enumeration features</value>
|
||||
</data>
|
||||
<data name="SelectAllCheckBox.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Select or deselect all</value>
|
||||
@ -363,4 +363,25 @@
|
||||
<data name="RenameParts_ExtensionOnly.Content" xml:space="preserve">
|
||||
<value>Extension only</value>
|
||||
</data>
|
||||
<data name="RandomizerCheatSheet_Alnum" xml:space="preserve">
|
||||
<value>Random string with uppercase letters, lowercase letters and 0-9 digits, customized length.</value>
|
||||
</data>
|
||||
<data name="RandomizerCheatSheet_Alpha" xml:space="preserve">
|
||||
<value>Random string with uppercase letters and lowercase letters, customized length.</value>
|
||||
</data>
|
||||
<data name="RandomizerCheatSheet_Digit" xml:space="preserve">
|
||||
<value>Random string with 0-9 digits, customized length.</value>
|
||||
</data>
|
||||
<data name="RandomizerCheatSheet_Title.Text" xml:space="preserve">
|
||||
<value>Replace using random values</value>
|
||||
</data>
|
||||
<data name="RandomizerCheatSheet_Uuid" xml:space="preserve">
|
||||
<value>Random UUID according to v4 specification.</value>
|
||||
</data>
|
||||
<data name="ToggleButton_RandItems.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Random string features</value>
|
||||
</data>
|
||||
<data name="ToggleButton_RandItems.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Random string features</value>
|
||||
</data>
|
||||
</root>
|
@ -661,3 +661,20 @@ bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring CreateGuidStringWithoutBrackets()
|
||||
{
|
||||
GUID guid;
|
||||
if (CoCreateGuid(&guid) == S_OK)
|
||||
{
|
||||
OLECHAR* guidString;
|
||||
if (StringFromCLSID(guid, &guidString) == S_OK)
|
||||
{
|
||||
std::wstring guidStr{ guidString };
|
||||
CoTaskMemFree(guidString);
|
||||
return guidStr.substr(1, guidStr.length() - 2);
|
||||
}
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
@ -26,3 +26,4 @@ void SetRegNumber(const std::wstring& valueName, unsigned int value);
|
||||
bool GetRegBoolean(const std::wstring& valueName, bool defaultValue);
|
||||
void SetRegBoolean(const std::wstring& valueName, bool value);
|
||||
bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime);
|
||||
std::wstring CreateGuidStringWithoutBrackets();
|
@ -17,7 +17,8 @@ enum PowerRenameFlags
|
||||
Uppercase = 0x200,
|
||||
Lowercase = 0x400,
|
||||
Titlecase = 0x800,
|
||||
Capitalized = 0x1000
|
||||
Capitalized = 0x1000,
|
||||
RandomizeItems = 0x2000
|
||||
};
|
||||
|
||||
enum PowerRenameFilters
|
||||
@ -150,3 +151,12 @@ public:
|
||||
(_In_ IEnumShellItems * enumShellItems) = 0;
|
||||
IFACEMETHOD(Cancel)() = 0;
|
||||
};
|
||||
|
||||
interface __declspec(uuid("FAB18E93-2E76-436B-8E26-B1240519AF12")) IPowerRenameRand : public IUnknown
|
||||
{
|
||||
public:
|
||||
IFACEMETHOD(Start)
|
||||
(_In_ IEnumShellItems * enumShellItems) = 0;
|
||||
IFACEMETHOD(Cancel)
|
||||
() = 0;
|
||||
};
|
@ -40,6 +40,7 @@
|
||||
<ClInclude Include="PowerRenameManager.h" />
|
||||
<ClInclude Include="PowerRenameMRU.h" />
|
||||
<ClInclude Include="PowerRenameRegEx.h" />
|
||||
<ClInclude Include="Randomizer.h" />
|
||||
<ClInclude Include="Renaming.h" />
|
||||
<ClInclude Include="Settings.h" />
|
||||
<ClInclude Include="srwlock.h" />
|
||||
@ -56,6 +57,7 @@
|
||||
<ClCompile Include="PowerRenameManager.cpp" />
|
||||
<ClCompile Include="PowerRenameMRU.cpp" />
|
||||
<ClCompile Include="PowerRenameRegEx.cpp" />
|
||||
<ClCompile Include="Randomizer.cpp" />
|
||||
<ClCompile Include="Renaming.cpp" />
|
||||
<ClCompile Include="Settings.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
|
@ -130,23 +130,106 @@ IFACEMETHODIMP CPowerRenameRegEx::GetReplaceTerm(_Outptr_ PWSTR* replaceTerm)
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT CPowerRenameRegEx::_OnEnumerateItemsChanged()
|
||||
HRESULT CPowerRenameRegEx::_OnEnumerateOrRandomizeItemsChanged()
|
||||
{
|
||||
m_enumerators.clear();
|
||||
const auto options = parseEnumOptions(m_RawReplaceTerm);
|
||||
for (const auto e : options)
|
||||
m_enumerators.emplace_back(e);
|
||||
m_randomizer.clear();
|
||||
|
||||
if (m_flags & RandomizeItems)
|
||||
{
|
||||
const auto options = parseRandomizerOptions(m_RawReplaceTerm);
|
||||
|
||||
for (const auto& option : options)
|
||||
{
|
||||
m_randomizer.emplace_back(option);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_flags & EnumerateItems)
|
||||
{
|
||||
const auto options = parseEnumOptions(m_RawReplaceTerm);
|
||||
for (const auto& option : options)
|
||||
{
|
||||
if (m_randomizer.end() ==
|
||||
std::find_if(
|
||||
m_randomizer.begin(),
|
||||
m_randomizer.end(),
|
||||
[option](const Randomizer& r) -> bool { return r.options.replaceStrSpan.offset == option.replaceStrSpan.offset; }
|
||||
))
|
||||
{
|
||||
// Only add as enumerator if we didn't find a randomizer already at this offset.
|
||||
// Every randomizer will also be a valid enumerator according to the definition of enumerators, which allows any string to mean the default enumerator, so it should be interpreted that the user wanted a randomizer if both were found at the same offset of the replace string.
|
||||
m_enumerators.emplace_back(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_replaceWithRandomizerOffsets.clear();
|
||||
m_replaceWithEnumeratorOffsets.clear();
|
||||
|
||||
int32_t offset = 0;
|
||||
int ei = 0; // Enumerators index
|
||||
int ri = 0; // Randomizer index
|
||||
|
||||
std::wstring replaceWith{ m_RawReplaceTerm };
|
||||
// Remove counter expressions and calculate their offsets in replaceWith string.
|
||||
int32_t offset = 0;
|
||||
for (const auto& e : options)
|
||||
|
||||
if ((m_flags & EnumerateItems) && (m_flags & RandomizeItems))
|
||||
{
|
||||
replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length);
|
||||
m_replaceWithEnumeratorOffsets.push_back(offset);
|
||||
offset -= static_cast<int32_t>(e.replaceStrSpan.length);
|
||||
// Both flags are on, we need to merge which ones should be applied.
|
||||
while ((ei < m_enumerators.size()) && (ri < m_randomizer.size()))
|
||||
{
|
||||
const auto& e = m_enumerators[ei];
|
||||
const auto& r = m_randomizer[ri];
|
||||
if (e.replaceStrSpan.offset < r.options.replaceStrSpan.offset)
|
||||
{
|
||||
// if the enumerator is next in line, remove counter expression and calculate offset with it.
|
||||
replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length);
|
||||
m_replaceWithEnumeratorOffsets.push_back(offset);
|
||||
offset -= static_cast<int32_t>(e.replaceStrSpan.length);
|
||||
|
||||
ei++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the randomizer is next in line, remove randomizer expression and calculate offset with it.
|
||||
replaceWith.erase(r.options.replaceStrSpan.offset + offset, r.options.replaceStrSpan.length);
|
||||
m_replaceWithRandomizerOffsets.push_back(offset);
|
||||
offset -= static_cast<int32_t>(r.options.replaceStrSpan.length);
|
||||
|
||||
ri++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_flags & EnumerateItems)
|
||||
{
|
||||
// Continue with all remaining enumerators
|
||||
while (ei < m_enumerators.size())
|
||||
{
|
||||
const auto& e = m_enumerators[ei];
|
||||
replaceWith.erase(e.replaceStrSpan.offset + offset, e.replaceStrSpan.length);
|
||||
m_replaceWithEnumeratorOffsets.push_back(offset);
|
||||
offset -= static_cast<int32_t>(e.replaceStrSpan.length);
|
||||
|
||||
ei++;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_flags & RandomizeItems)
|
||||
{
|
||||
// Continue with all remaining randomizer instances
|
||||
while (ri < m_randomizer.size())
|
||||
{
|
||||
const auto& r = m_randomizer[ri];
|
||||
replaceWith.erase(r.options.replaceStrSpan.offset + offset, r.options.replaceStrSpan.length);
|
||||
m_replaceWithRandomizerOffsets.push_back(offset);
|
||||
offset -= static_cast<int32_t>(r.options.replaceStrSpan.length);
|
||||
|
||||
ri++;
|
||||
}
|
||||
}
|
||||
|
||||
return SHStrDup(replaceWith.data(), &m_replaceTerm);
|
||||
}
|
||||
|
||||
@ -163,8 +246,8 @@ IFACEMETHODIMP CPowerRenameRegEx::PutReplaceTerm(_In_ PCWSTR replaceTerm, bool f
|
||||
CoTaskMemFree(m_replaceTerm);
|
||||
m_RawReplaceTerm = replaceTerm;
|
||||
|
||||
if (m_flags & EnumerateItems)
|
||||
hr = _OnEnumerateItemsChanged();
|
||||
if ((m_flags & RandomizeItems) || (m_flags & EnumerateItems))
|
||||
hr = _OnEnumerateOrRandomizeItemsChanged();
|
||||
else
|
||||
hr = SHStrDup(replaceTerm, &m_replaceTerm);
|
||||
}
|
||||
@ -189,13 +272,20 @@ IFACEMETHODIMP CPowerRenameRegEx::PutFlags(_In_ DWORD flags)
|
||||
if (m_flags != flags)
|
||||
{
|
||||
const bool newEnumerate = flags & EnumerateItems;
|
||||
const bool refreshReplaceTerm = !!(m_flags & EnumerateItems) != newEnumerate;
|
||||
const bool newRandomizer = flags & RandomizeItems;
|
||||
const bool refreshReplaceTerm =
|
||||
(!!(m_flags & EnumerateItems) != newEnumerate) ||
|
||||
(!!(m_flags & RandomizeItems) != newRandomizer);
|
||||
|
||||
m_flags = flags;
|
||||
|
||||
if (refreshReplaceTerm)
|
||||
{
|
||||
CSRWExclusiveAutoLock lock(&m_lock);
|
||||
if (newEnumerate)
|
||||
_OnEnumerateItemsChanged();
|
||||
if (newEnumerate || newRandomizer)
|
||||
{
|
||||
_OnEnumerateOrRandomizeItemsChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
CoTaskMemFree(m_replaceTerm);
|
||||
@ -325,17 +415,75 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, u
|
||||
static const std::wregex zeroGroupRegex(L"(([^\\$]|^)(\\$\\$)*)\\$[0]");
|
||||
static const std::wregex otherGroupsRegex(L"(([^\\$]|^)(\\$\\$)*)\\$([1-9])");
|
||||
|
||||
if (m_flags & EnumerateItems)
|
||||
if ((m_flags & EnumerateItems) || (m_flags & RandomizeItems))
|
||||
{
|
||||
int ei = 0; // Enumerators index
|
||||
int ri = 0; // Randomizer index
|
||||
std::array<wchar_t, MAX_PATH> buffer;
|
||||
int32_t offset = 0;
|
||||
|
||||
for (size_t ei = 0; ei < m_enumerators.size(); ++ei)
|
||||
if ((m_flags & EnumerateItems) && (m_flags & RandomizeItems))
|
||||
{
|
||||
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;
|
||||
// Both flags are on, we need to merge which ones should be applied.
|
||||
while ((ei < m_enumerators.size()) && (ri < m_randomizer.size()))
|
||||
{
|
||||
const auto& e = m_enumerators[ei];
|
||||
const auto& r = m_randomizer[ri];
|
||||
if (e.replaceStrSpan.offset < r.options.replaceStrSpan.offset)
|
||||
{
|
||||
// if the enumerator is next in line, apply it.
|
||||
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;
|
||||
|
||||
ei++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the randomizer is next in line, apply it.
|
||||
std::string randomValue = r.randomize();
|
||||
std::wstring wRandomValue(randomValue.begin(), randomValue.end());
|
||||
replaceTerm.insert(r.options.replaceStrSpan.offset + offset + m_replaceWithRandomizerOffsets[ri], wRandomValue);
|
||||
offset += static_cast<int32_t>(wRandomValue.length());
|
||||
|
||||
if (e.replaceStrSpan.offset == r.options.replaceStrSpan.offset)
|
||||
{
|
||||
// In theory, this shouldn't happen here as we were guarding against it when filling the randomizer and enumerator structures, but it's still here as a fail safe.
|
||||
// Every randomizer will also be a valid enumerator according to the definition of enumerators, which allow any string to mean the default enumerator, so it should be interpreted that the user wanted a randomizer if both were found at the same offset of the replace string.
|
||||
ei++;
|
||||
}
|
||||
|
||||
ri++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_flags & EnumerateItems)
|
||||
{
|
||||
// Replace all remaining enumerators
|
||||
while (ei < m_enumerators.size())
|
||||
{
|
||||
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;
|
||||
|
||||
ei++;
|
||||
}
|
||||
}
|
||||
if (m_flags & RandomizeItems)
|
||||
{
|
||||
// Replace all remaining randomizer instances
|
||||
while (ri < m_randomizer.size())
|
||||
{
|
||||
const auto& r = m_randomizer[ri];
|
||||
std::string randomValue = r.randomize();
|
||||
std::wstring wRandomValue(randomValue.begin(), randomValue.end());
|
||||
replaceTerm.insert(r.options.replaceStrSpan.offset + offset + m_replaceWithRandomizerOffsets[ri], wRandomValue);
|
||||
offset += static_cast<int32_t>(wRandomValue.length());
|
||||
|
||||
ri++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,9 @@
|
||||
#include "srwlock.h"
|
||||
|
||||
#include "Enumerating.h"
|
||||
|
||||
#include "Randomizer.h"
|
||||
|
||||
#include "PowerRenameInterfaces.h"
|
||||
|
||||
#define DEFAULT_FLAGS 0
|
||||
@ -38,7 +41,7 @@ protected:
|
||||
void _OnReplaceTermChanged();
|
||||
void _OnFlagsChanged();
|
||||
void _OnFileTimeChanged();
|
||||
HRESULT _OnEnumerateItemsChanged();
|
||||
HRESULT _OnEnumerateOrRandomizeItemsChanged();
|
||||
|
||||
size_t _Find(std::wstring data, std::wstring toSearch, bool caseInsensitive, size_t pos);
|
||||
|
||||
@ -59,6 +62,9 @@ protected:
|
||||
std::vector<Enumerator> m_enumerators;
|
||||
std::vector<int32_t> m_replaceWithEnumeratorOffsets;
|
||||
|
||||
std::vector<Randomizer> m_randomizer;
|
||||
std::vector<int32_t> m_replaceWithRandomizerOffsets;
|
||||
|
||||
struct RENAME_REGEX_EVENT
|
||||
{
|
||||
IPowerRenameRegExEvents* pEvents;
|
||||
|
55
src/modules/powerrename/lib/Randomizer.cpp
Normal file
55
src/modules/powerrename/lib/Randomizer.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "Randomizer.h"
|
||||
|
||||
std::vector<RandomizerOptions> parseRandomizerOptions(const std::wstring& replaceWith)
|
||||
{
|
||||
static const std::wregex randAlnumRegex(LR"(rstringalnum=(\d+))");
|
||||
static const std::wregex randAlphaRegex(LR"(rstringalpha=(-?\d+))");
|
||||
static const std::wregex randDigitRegex(LR"(rstringdigit=(\d+))");
|
||||
static const std::wregex randUuidRegex(LR"(ruuidv4)");
|
||||
|
||||
std::string buf;
|
||||
std::vector<RandomizerOptions> options;
|
||||
std::wregex randGroupRegex(LR"(\$\{.*?\})");
|
||||
|
||||
for (std::wsregex_iterator i{ begin(replaceWith), end(replaceWith), randGroupRegex }, end; i != end; ++i)
|
||||
{
|
||||
std::wsmatch match = *i;
|
||||
std::wstring matchString = match.str();
|
||||
|
||||
RandomizerOptions option;
|
||||
option.replaceStrSpan.offset = match.position();
|
||||
option.replaceStrSpan.length = match.length();
|
||||
|
||||
std::wsmatch subMatch;
|
||||
if (std::regex_search(matchString, subMatch, randAlnumRegex))
|
||||
{
|
||||
int length = std::stoi(subMatch.str(1));
|
||||
option.alnum = true;
|
||||
option.length = length;
|
||||
}
|
||||
if (std::regex_search(matchString, subMatch, randAlphaRegex))
|
||||
{
|
||||
int length = std::stoi(subMatch.str(1));
|
||||
option.alpha = true;
|
||||
option.length = length;
|
||||
}
|
||||
if (std::regex_search(matchString, subMatch, randDigitRegex))
|
||||
{
|
||||
int length = std::stoi(subMatch.str(1));
|
||||
option.digit = true;
|
||||
option.length = length;
|
||||
}
|
||||
if (std::regex_search(matchString, subMatch, randUuidRegex))
|
||||
{
|
||||
option.uuid = true;
|
||||
}
|
||||
if (option.isValid())
|
||||
{
|
||||
options.push_back(option);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
76
src/modules/powerrename/lib/Randomizer.h
Normal file
76
src/modules/powerrename/lib/Randomizer.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "Helpers.h"
|
||||
|
||||
#include <common\utils\string_utils.h>
|
||||
|
||||
struct ReplaceStrSpan
|
||||
{
|
||||
size_t offset = 0;
|
||||
size_t length = 0;
|
||||
};
|
||||
|
||||
struct RandomizerOptions
|
||||
{
|
||||
std::optional<int> length;
|
||||
std::optional<boolean> alnum;
|
||||
std::optional<boolean> alpha;
|
||||
std::optional<boolean> digit;
|
||||
std::optional<boolean> uuid;
|
||||
ReplaceStrSpan replaceStrSpan;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return alnum.has_value() || alpha.has_value() || digit.has_value() || uuid.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<RandomizerOptions> parseRandomizerOptions(const std::wstring& replaceWith);
|
||||
|
||||
struct Randomizer
|
||||
{
|
||||
RandomizerOptions options;
|
||||
|
||||
inline Randomizer(RandomizerOptions opts) :
|
||||
options(opts) {}
|
||||
|
||||
std::string randomize() const
|
||||
{
|
||||
std::string chars;
|
||||
|
||||
if (options.uuid.value_or(false))
|
||||
{
|
||||
return unwide(CreateGuidStringWithoutBrackets());
|
||||
}
|
||||
if (options.alnum.value_or(false))
|
||||
{
|
||||
chars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
}
|
||||
if (options.alpha.value_or(false))
|
||||
{
|
||||
chars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
}
|
||||
if (options.digit.value_or(false))
|
||||
{
|
||||
chars += "0123456789";
|
||||
}
|
||||
if (chars.empty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> distribution(0, static_cast<int>(chars.size()) - 1);
|
||||
|
||||
for (int i = 0; i < options.length.value_or(10); ++i)
|
||||
{
|
||||
result += chars[distribution(generator)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
@ -27,6 +27,7 @@
|
||||
#include <variant>
|
||||
#include <charconv>
|
||||
#include <string>
|
||||
#include <random>
|
||||
|
||||
#include <ProjectTelemetry.h>
|
||||
|
||||
|
@ -466,6 +466,151 @@ TEST_METHOD (VerifyCounterAllCustomizations)
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerDefaultFlags)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = 0;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=9}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
Assert::AreEqual(L"foo$1bar_${rstringalnum=9}", result);
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerNoRegex)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
Assert::AreEqual(L"foo$1bar_${}", result);
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerNoRandomizerRegEx)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=9}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
Assert::AreEqual(L"foobar_${rstringalnum=9}", result);
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerRegEx)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems | UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=9}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
std::wstring resultStr(result);
|
||||
std::wregex pattern(L"foobar_\\w{9}");
|
||||
Assert::IsTrue(std::regex_match(resultStr, pattern));
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerRegExZeroValue)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems | UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=0}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
Assert::AreEqual(L"foobar_", result);
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerRegExChar)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems | UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalpha=9}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
std::wstring resultStr(result);
|
||||
std::wregex pattern(L"foobar_[A-Za-z]{9}");
|
||||
Assert::IsTrue(std::regex_match(resultStr, pattern));
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerRegExNum)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems | UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringdigit=9}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
std::wstring resultStr(result);
|
||||
std::wregex pattern(L"foobar_\\d{9}");
|
||||
Assert::IsTrue(std::regex_match(resultStr, pattern));
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerRegExUuid)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems | UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${ruuidv4}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
std::wstring resultStr(result);
|
||||
std::wregex pattern(L"foobar_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}");
|
||||
Assert::IsTrue(std::regex_match(resultStr, pattern));
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
TEST_METHOD (VerifyRandomizerRegExAllBackToBack)
|
||||
{
|
||||
CComPtr<IPowerRenameRegEx> renameRegEx;
|
||||
Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK);
|
||||
DWORD flags = RandomizeItems | UseRegularExpressions;
|
||||
Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK);
|
||||
PWSTR result = nullptr;
|
||||
Assert::IsTrue(renameRegEx->PutSearchTerm(L"bar") == S_OK);
|
||||
Assert::IsTrue(renameRegEx->PutReplaceTerm(L"$1bar_${rstringalnum=2}${rstringalpha=2}${rstringdigit=2}${ruuidv4}") == S_OK);
|
||||
unsigned long index = {};
|
||||
Assert::IsTrue(renameRegEx->Replace(L"foobar", &result, index) == S_OK);
|
||||
std::wstring resultStr(result);
|
||||
std::wregex pattern(L"foobar_\\w{2}[A-Za-z]{2}\\d{2}[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}");
|
||||
Assert::IsTrue(std::regex_match(resultStr, pattern));
|
||||
CoTaskMemFree(result);
|
||||
}
|
||||
|
||||
#ifndef TESTS_PARTIAL
|
||||
};
|
||||
}
|
||||
|
@ -400,7 +400,6 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
|
||||
_html = _html.Replace("[[PT_STICKY_SCROLL]]", _settings.StickyScroll ? "1" : "0", StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_FONT_SIZE]]", _settings.FontSize.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_CODE]]", _base64FileCode, StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_STICKY_SCROLL]]", _settings.StickyScroll ? "1" : "0", StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_URL]]", FilePreviewCommon.MonacoHelper.VirtualHostName, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
|
@ -120,6 +120,12 @@ namespace Common
|
||||
/// </summary>
|
||||
public void UpdateWindowBounds(IntPtr hwnd, Rectangle newBounds)
|
||||
{
|
||||
if (this.Disposing || this.IsDisposed)
|
||||
{
|
||||
// For unclear reasons, this can be called when handling an error and the form has already been disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
// We must set the WS_CHILD style to change the form to a control within the Explorer preview pane
|
||||
int windowStyle = NativeMethods.GetWindowLong(Handle, gwlStyle);
|
||||
if ((windowStyle & wsChild) == 0)
|
||||
|
@ -22,6 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
PasteAsJsonShortcut = new();
|
||||
ShowCustomPreview = true;
|
||||
SendPasteKeyCombination = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
@ -31,6 +32,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[CmdConfigureIgnore]
|
||||
public bool SendPasteKeyCombination { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool CloseAfterLosingFocus { get; set; }
|
||||
|
||||
[JsonPropertyName("advanced-paste-ui-hotkey")]
|
||||
public HotkeySettings AdvancedPasteUIShortcut { get; set; }
|
||||
|
||||
|
@ -76,9 +76,16 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
Logger.InitializeLogger("\\Settings\\Logs");
|
||||
Logger.InitializeLogger(@"\Settings\Logs");
|
||||
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
public static void OpenSettingsWindow(Type type = null, bool ensurePageIsSelected = false)
|
||||
|
@ -5,7 +5,6 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using AllExperiments;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
@ -52,13 +51,16 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
|
||||
private static ISettingsUtils settingsUtils = new SettingsUtils();
|
||||
|
||||
private bool ExperimentationToggleSwitchEnabled { get; set; } = true;
|
||||
/* NOTE: Experimentation for OOBE is currently turned off on server side. Keeping this code in a comment to allow future experiments.
|
||||
private bool ExperimentationToggleSwitchEnabled { get; set; } = true;
|
||||
*/
|
||||
|
||||
public OobeShellPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ExperimentationToggleSwitchEnabled = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.EnableExperimentation;
|
||||
// NOTE: Experimentation for OOBE is currently turned off on server side. Keeping this code in a comment to allow future experiments.
|
||||
// ExperimentationToggleSwitchEnabled = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.EnableExperimentation;
|
||||
SetTitleBar();
|
||||
DataContext = ViewModel;
|
||||
OobeShellHandler = this;
|
||||
@ -235,7 +237,8 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
switch (selectedItem.Tag)
|
||||
{
|
||||
case "Overview":
|
||||
case "Overview": NavigationFrame.Navigate(typeof(OobeOverview)); break;
|
||||
/* NOTE: Experimentation for OOBE is currently turned off on server side. Keeping this code in a comment to allow future experiments.
|
||||
if (ExperimentationToggleSwitchEnabled && GPOWrapper.GetAllowExperimentationValue() != GpoRuleConfigured.Disabled)
|
||||
{
|
||||
switch (AllExperiments.Experiments.LandingPageExperiment)
|
||||
@ -255,7 +258,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
NavigationFrame.Navigate(typeof(OobeOverview));
|
||||
break;
|
||||
}
|
||||
|
||||
*/
|
||||
case "WhatsNew": NavigationFrame.Navigate(typeof(OobeWhatsNew)); break;
|
||||
case "AdvancedPaste": NavigationFrame.Navigate(typeof(OobeAdvancedPaste)); break;
|
||||
case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break;
|
||||
|
@ -81,7 +81,7 @@
|
||||
Severity="Informational" />
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_ClipboardHistorySettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
x:Uid="AdvancedPaste_Clipboard_History_Enabled_SettingsCard"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
@ -94,6 +94,9 @@
|
||||
IsOpen="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_CloseAfterLosingFocus" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.CloseAfterLosingFocus, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
@ -35,7 +35,7 @@
|
||||
<tkcontrols:SettingsCard x:Uid="Peek_AlwaysRunNotElevated" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.AlwaysRunNotElevated, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="Peek_CloseAfterLosingFocus">
|
||||
<tkcontrols:SettingsCard x:Uid="Peek_CloseAfterLosingFocus" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.CloseAfterLosingFocus, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user