mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-26 14:07:24 +01:00
Compare commits
7 Commits
shawn/test
...
dev/FHL/ke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85dea93a50 | ||
|
|
e184808068 | ||
|
|
e52ac85a1b | ||
|
|
1e3108efbc | ||
|
|
f7ed043446 | ||
|
|
d90215ee8b | ||
|
|
b2dae5b48e |
@@ -33,7 +33,7 @@ parameters:
|
||||
default: true
|
||||
- name: winAppSDKVersionNumber
|
||||
type: string
|
||||
default: 1.7
|
||||
default: 1.6
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -47,4 +47,4 @@ extends:
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
@@ -61,18 +61,9 @@ jobs:
|
||||
reg add "HKLM\Software\Policies\Microsoft\Edge\WebView2\ReleaseChannels" /v PowerToys.exe /t REG_SZ /d "3"
|
||||
displayName: "Enable WebView2 Canary Channel"
|
||||
|
||||
- ${{ if ne(parameters.platform, 'arm64') }}:
|
||||
- download: current
|
||||
displayName: Download artifacts
|
||||
artifact: $(TestArtifactsName)
|
||||
patterns: |-
|
||||
**
|
||||
!**\*.pdb
|
||||
!**\*.lib
|
||||
- ${{ else }}:
|
||||
- template: steps-download-artifacts-with-azure-cli.yml
|
||||
parameters:
|
||||
artifactName: $(TestArtifactsName)
|
||||
- template: steps-download-artifacts-with-azure-cli.yml
|
||||
parameters:
|
||||
artifactName: $(TestArtifactsName)
|
||||
|
||||
- template: steps-ensure-dotnet-version.yml
|
||||
parameters:
|
||||
|
||||
@@ -72,7 +72,7 @@ stages:
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
|
||||
- ${{ if and(eq(parameters.runTests, true), not(and(eq(platform, 'arm64'), eq(variables['System.PullRequest.IsFork'], true)))) }}:
|
||||
- ${{ if eq(parameters.runTests, true) }}:
|
||||
- stage: Test_${{ platform }}
|
||||
displayName: Test ${{ platform }}
|
||||
dependsOn:
|
||||
|
||||
@@ -27,9 +27,6 @@ steps:
|
||||
buildType: 'specific'
|
||||
project: '55e8140e-57ac-4e5f-8f9c-c7c15b51929d'
|
||||
definition: '104083'
|
||||
specificBuildWithTriggering: false
|
||||
allowPartiallySucceededBuilds: true
|
||||
allowFailedBuilds: true
|
||||
buildVersionToDownload: 'latestFromBranch'
|
||||
branchName: 'refs/heads/release/${{ parameters.versionNumber }}-stable'
|
||||
artifactName: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
|
||||
170
README.md
170
README.md
@@ -34,19 +34,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.90%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.89%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysUserSetup-0.89.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysUserSetup-0.89.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysSetup-0.89.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.89.0/PowerToysSetup-0.89.0-arm64.exe
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.89%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.88%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.88.0/PowerToysUserSetup-0.88.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.88.0/PowerToysUserSetup-0.88.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.88.0/PowerToysSetup-0.88.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.88.0/PowerToysSetup-0.88.0-arm64.exe
|
||||
|
||||
| Description | Filename | sha256 hash |
|
||||
|----------------|----------|-------------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.89.0-x64.exe][ptUserX64] | B4F130CC96F321024A257499247F6FF6DA56612215ED3882E868AAE26C689E33 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.89.0-arm64.exe][ptUserArm64] | F69B00F4E520EB09FA0D1D1669E21910C5225FE7A2EEDC0FA7C283B201A5F9C6 |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.89.0-x64.exe][ptMachineX64] | E18AC8F9023E341CF7DAD35367FB9DDDB6565D83D8155DBCDDB40AE8A24AE731 |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.89.0-arm64.exe][ptMachineArm64] | 17DEADEC601D6061D7AF4F487595CC36D9191813003CC2ECE381017F0EC71FBB |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.88.0-x64.exe][ptUserX64] | 5BBA2E06603CAAE0269DFBC991095C6664FD934130335197C1BA3120E19B7CA3 |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.88.0-arm64.exe][ptUserArm64] | E79723F9F94068C699E01334C8CC0C85F37818EB4664FC772D2B545A1C37C3FA |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.88.0-x64.exe][ptMachineX64] | C43742DB7AA3F8B01FE7AE1DA591F0342767AFE5BBACB72F2968CE5E8EE1E3AC |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.88.0-arm64.exe][ptMachineArm64] | AEE4A67643C886336F31F86C4117BA5F01BCA5E0E99FF34524217DC91AFA7132 |
|
||||
|
||||
This is our preferred method.
|
||||
|
||||
@@ -92,103 +92,141 @@ 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.89 - February 2025 Update
|
||||
### 0.88 - January 2025 Update
|
||||
|
||||
In this release, we focused on new features, stability, accessibility and automation.
|
||||
In this release, we focused on new features, stability, and improvements.
|
||||
|
||||
**✨Highlights**
|
||||
**Highlights**
|
||||
|
||||
- Enhanced Advanced Paste by adding media transcoding support to convert different video and audio file formats! Thanks [@snickler](https://github.com/snickler) for your help!
|
||||
- Fixed crashes when loading thumbnails after the .NET 9 update and resolved PowerLauncher.exe blocking other MSI installers from creating shortcuts!
|
||||
- Fixed accessibility issues across FancyZones, Image Resizer, and Settings to improve screen reader support and clarity!
|
||||
- Enhanced UI automation framework across modules and added new tests to cover manual checks, with more improvements coming!
|
||||
- New utility: ZoomIt - a screen zoom, annotation, and recording tool for technical presentations and demos. This utility from Sysinternals has had its source code released and included in PowerToys. ZoomIt will still continue to be updated and shipped by Sysinternals for users who prefer to have it as a standalone utility outside of PowerToys. Thanks [@markrussinovich](https://github.com/markrussinovich), [@foxmsft](https://github.com/foxmsft) and [@johnstep](https://github.com/johnstep) for contributing the original code and reviewing the PowerToys integration!
|
||||
- Video Conference Mute has been deprecated and was removed from PowerToys.
|
||||
- .Net 9.0.1 fixed many issue in WPF, improving stability for PowerToys Run.
|
||||
|
||||
### General
|
||||
|
||||
- Fixed an issue where updating PowerToys on Windows 11 did not properly update context menu entries, impacting New+, PowerRename, Image Resizer, and File Locksmith.
|
||||
- Updated .NET Packages from 9.0.1 to 9.0.2. Thanks [@snickler](https://github.com/snickler) for this.
|
||||
- Enabled compatibility with VS17.3 and later, for C++23. Thanks [@LNKLEO](https://github.com/LNKLEO) for this.
|
||||
- Applied a workaround for the Windows App SDK applications title bar override that was causing accent color to not be shown on the top bar of applications on Windows 10. Thanks [@pingzing](https://github.com/pingzing)!
|
||||
- Improved the "admin application running" notification checking logic to be less demanding on resources. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Fixed an issue causing many utilities to crash when the GPO to disable data diagnostics was applied.
|
||||
|
||||
### Advanced Paste
|
||||
|
||||
- Added media transcoding support to convert different video and audio file formats, improved UI layouts, refined clipboard handling, and integrated Semantic Kernel for smarter pasting. Thanks [@snickler](https://github.com/snickler) for your help!
|
||||
- Fixed a crash when the application was exiting. (This was a hotfix for 0.87)
|
||||
- Added a Json format validation step to verify if a conversion to Json should be applied.
|
||||
- Fixed accessibility issues when using a screen reader.
|
||||
- Added support for all BitmapDecoder supported image file types to the Image to Text functionality. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed an issue causing Advanced Paste initialization errors to hang the PowerToys main process.
|
||||
|
||||
### FancyZones
|
||||
|
||||
- Fixed accessibility by improving the text for monitors, ensuring clearer naming and help text for screen readers.
|
||||
- Removed Workspaces Editor from the exclusions list so it can be snapped by FancyZones.
|
||||
|
||||
### Image Resizer
|
||||
- Fixed issues with Width and Height fields in Image Resizer's Custom preset, ensuring empty values no longer cause errors, settings save correctly, and auto-scaling behaves as expected. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed accessibility by ensuring screen readers announce selected image dimensions in the combo-box for better navigation.
|
||||
### Keyboard Manager
|
||||
|
||||
- Added an option to make a shortcut remapping only trigger with exact modifiers.
|
||||
|
||||
### Monaco Preview
|
||||
|
||||
- Fixed open link in default browser rather than Microsoft Edge. Thanks [@OldUser101](https://github.com/OldUser101)!
|
||||
|
||||
### Mouse Highlighter
|
||||
|
||||
- Fixed a highlight released on an Administrator window will start fading, instead of staying on the screen indefinitely until the mouse button is pressed again on an unelevated window.
|
||||
- Added support for .resx and .resw files in Peek and File Explorer add-ons. Thanks [@asif4318](https://github.com/asif4318)!
|
||||
- Added a setting to make the code minimap toggle-able in Peek and File Explorer add-ons. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Fixed an issue causing Json format preview setting to not be applied correctly.
|
||||
- Fixed an issue causing the wrong Monaco assets to be used at runtime.
|
||||
|
||||
### Mouse Without Borders
|
||||
- Fixed an issue in service mode where copy-paste and drag-drop file transfers didn’t work, ensuring seamless file operations.
|
||||
- Enabled GPO for enable/disable for Mouse Without Borders in Service Mode. Thanks [@htcfreek](https://github.com/htcfreek) for review and comments!
|
||||
- Fixed code maintainability by refactoring the oversized 'Common' class in Mouse Without Borders into smaller, focused classes for better structure and clarity. Thanks [@mikeclayton](https://github.com/mikeclayton) and thanks [@htcfreek](https://github.com/htcfreek) for review!
|
||||
|
||||
### PowerRename
|
||||
- Supported negative value as Start value in regular expression, e.g. ${start=-1314}
|
||||
- Enhanced RegEx help by adding $, ^, quantifiers, and common patterns for better usability. Thanks [@PesBandi](https://github.com/PesBandi) and thanks [@htcfreek](https://github.com/htcfreek) for review.
|
||||
- Fixed an issue causing clipboard to stop working after going through a UAC screen when using the Service mode. Thanks [@YDKK](https://github.com/YDKK)!
|
||||
|
||||
### New+
|
||||
|
||||
- Fixed an issue causing New+ to override the New file or folder creation from the File Explorer Ribbon buttons or keyboard shortcuts on Windows 10.
|
||||
- When creating file or folders through a template, they should now have the current time as the last modified date. Thanks [@cgaarden](https://github.com/cgaarden)!
|
||||
|
||||
### Peek
|
||||
|
||||
- Fixed an issue causing Peek to not appear if it was previously minimized. Thanks [@asif4318](https://github.com/asif4318)!
|
||||
|
||||
### PowerToys Run
|
||||
- Fixed crashes when loading thumbnails after the .NET 9 update by disabling CETCompat.
|
||||
- Fixed PowerLauncher.exe blocking other MSI installers creating shortcuts. Thanks [@OneBlue](https://github.com/OneBlue)!
|
||||
- Fixed Run’s dark mode detection to work reliably, preventing issues with incorrect theme detection and ensuring a smoother user experience. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed list separator handling in Calculator, allowing functions with multiple arguments to work correctly across different locales. For example pow(2;3) would be replaced with pow(2,3). Thanks [@PesBandi](https://github.com/PesBandi) and thanks [@htcfreek](https://github.com/htcfreek) for review!
|
||||
- Fixed angle unit conversions in the PowerToys Run calculator, allowing quick conversions between radians, degrees, and gradians. Thanks [@OldUser101](https://github.com/OldUser101)!
|
||||
|
||||
- Fixed a transparent border issue on Windows 10. (This was a hotfix for 0.87)
|
||||
- Fixed a crash in the OneNote plugin after the .Net 9 update. (This was a hotfix for 0.87)
|
||||
- Fixed an issue causing the Calculator plugin to return division by zero errors when dividing by hexadecimal numbers. Thanks [@plante-msft](https://github.com/plante-msft)!
|
||||
- Updated the Calculator plugin Mages library to 3.0.0 and added support for the random integer function. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Improved handling of non-base 10 numbers to add support for binary and octal numbers in the Calculator plugin. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added a setting to enable selection of which units to use for trigonometric functions. Thanks [@OldUser101](https://github.com/OldUser101)!
|
||||
- Fixed a .NET 9 regression causing the PowerToys Run dialog to not be draggable. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
- Added context menu buttons for the VS Code Workspaces plugin, for copying the path, opening in File Explorer or in Console. Thanks [@programming-with-ia](https://github.com/programming-with-ia)!
|
||||
- Added some telemetry to gather data on which hotkey is used to trigger PowerToys Run.
|
||||
- Removed the workarounds that were in place to fix some WPF issues that were fixed in .NET 9.0.1.
|
||||
- Fixed a typo in the Value Generator plugin messages. Thanks [@OldUser101](https://github.com/OldUser101)!
|
||||
|
||||
### Quick Accent
|
||||
|
||||
- Added ǎ, ǒ and ǔ to the IPA character set. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added ` (backtick) and ~ (tilde) to the VK_OEM_5 character set. Thanks [@xanatos](https://github.com/xanatos)!
|
||||
- Added ς (final sigma) to the Greek character set. Thanks [@IamSmeagol](https://github.com/IamSmeagol)!
|
||||
- Added the ć character to the Slovenian character set. Thanks [@dsoklic](https://github.com/dsoklic)!
|
||||
- Added the Proto-Indo-European character set.
|
||||
|
||||
### Registry Preview
|
||||
|
||||
- Fixed an issue causing line breaks to not be parsed correctly for REG_MULTI_SZ values. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added a tooltip to values to show multiple lines of data. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added a context menu to enable copying type, value and key paths. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
|
||||
### Settings
|
||||
|
||||
- Enabled GPO for the "run at startup" setting. Thanks [@htcfreek](https://github.com/htcfreek) for review and comments!
|
||||
- Fixed accessibility issue by allowing screen readers to announce the group name for secondary links in Settings pages, instead of reading link descriptions without context.
|
||||
- Fixed an issue where the Color Picker shortcut was not displaying correctly in the Dashboard.
|
||||
- Made the Advanced Paste paste OpenAI configuration modal scrollable.
|
||||
- Fixed the text on the Quick Accent page to refer to "character sets" instead of "character set". Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
- Added the plugin's dll file version and website to the PowerToys Run plugin settings. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Added the Workspaces file to the list of files that gets backed up by the Back up / Restore functionality.
|
||||
- Fixed an issue causing some of the selected character sets to be unselected when opening the character set expander in the Quick Accent page.
|
||||
- Improved GPO logic, icons, info bar layout and enabled state of all modules settings pages. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Fixed some accessibility issues and refactored and improved quality of the code related to image sizes in the Image Resizer page. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed mentions of "Backup" to "Back up" when it should be used as a verb. Thanks [@JackStuart](https://github.com/JackStuart)!
|
||||
- Added a "New" label to Settings to better highlight new utilities that get released. Thanks [@niels9001](https://github.com/niels9001) for the UI tweaks!
|
||||
|
||||
### Text Extractor
|
||||
|
||||
- Fixed many accessibility and UI issues on the overlay UI. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
|
||||
|
||||
### Workspaces
|
||||
|
||||
- Fixed if a window was last placed on a disconnected monitor, it launches minimized and repositions within the main monitor's visible area when restored, instead of remaining off-screen and invisible.
|
||||
- Fixed on ARM64 to correctly display icons for packaged apps by resolving path mismatches.
|
||||
- Fixed an issue causing the Workspaces Editor to start outside of visible desktop area.
|
||||
- Fixed an issue to maintain command line arguments for applications when trying using the "Launch and Edit" feature.
|
||||
|
||||
### Video Conference Mute
|
||||
|
||||
- The module has been deprecated in 0.88.0, being removed from PowerToys.
|
||||
|
||||
### ZoomIt
|
||||
|
||||
- Fixed warning C4706 and related error C2220 during build. Thanks [@xanatos](https://github.com/xanatos)!
|
||||
- New utility: Zoom It - a screen zoom, annotation, and recording tool for technical presentations and demos. This utility from Sysinternals has had its source code released and included in PowerToys. ZoomIt will still continue to be updated and shipped by Sysinternals for users who prefer to have it as a standalone utility outside of PowerToys. Thanks [@markrussinovich](https://github.com/markrussinovich), [@foxmsft](https://github.com/foxmsft) and [@johnstep](https://github.com/johnstep) for contributing the original code and reviewing the PowerToys integration!
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fixed runner-ipc.md doc on the broken link. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed the new plugin checklist by updating the target framework, removing duplicates, and improving statement organization. Thanks [@hlaueriksson](https://github.com/hlaueriksson)!
|
||||
- Updated runner documentation to align with the latest code structure.
|
||||
- Updated the PowerToys Run documentation to reflect documentation pages for new plugins.
|
||||
- Added YubicoOauthOTP plugin mention to thirdPartyRunPlugins.md. Thanks [@dlnilsson](https://github.com/dlnilsson)!
|
||||
|
||||
### Development
|
||||
|
||||
- Stabilized pipeline on ARM64 and forked build.
|
||||
- Added fuzz testing for HostUILib, added as part of pipeline for OneFuzz.
|
||||
- Fixed and improved UI-Test automation framework, and added new test cases for the FancyZones and Hosts module.
|
||||
- Optimized Logger function as AOT compatible, improving performance by 18%.
|
||||
- Made Common.UI and Setting.UI to be AOT compatible.
|
||||
|
||||
### What is being planned for version 0.90
|
||||
- Added fuzz testing for AdvancedPaste, with a new pipeline for OneFuzz.
|
||||
- Added a new CI pipeline to build with the latest WindowsAppSDK.
|
||||
- Added a new CI pipeline to build with the latest webview2 from Edge Canary.
|
||||
- Made the HostsUILib project AOT compatible. Thanks [@snickler](https://github.com/snickler) for your help reviewing this!
|
||||
- Made FilePreviewCommon and MarkdownPreviewHandler AOT compatible. Thanks [@snickler](https://github.com/snickler) for your help reviewing this!
|
||||
- Made the PowerAccent.Core project AOT compatible. Thanks [@snickler](https://github.com/snickler) for your help reviewing this!
|
||||
- Cleaned up some code for AOT compatibility in the Advanced Paste module. Thanks [@snickler](https://github.com/snickler) for your help reviewing this!
|
||||
- Removed the prerelease flag from the PowerToys development DSC configurations. Thanks [@denelon](https://github.com/denelon)!
|
||||
- Improved Dart CI reliability by improving error messages and retrying to the step that installs the correct dotnet version.
|
||||
- Improved Dart CI reliability by fixing retries when downloading the localization files.
|
||||
- Improved Dart CI build times by removing the steps to build the no longer needed abstracted utility nuget packages.
|
||||
- Removed the solution.props file from the solution root.
|
||||
- Fixed PowerToys Run Calculator plugin tests when running in systems with different number formats. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
- Updated many .NET packages from .NET 9.0.0 to 9.0.1 for security fixes. Thanks [@snickler](https://github.com/snickler)!
|
||||
- Refactored the Mouse Without Borders Common.Log.cs and Common.Receiver.cs files. Thanks [@mikeclayton](https://github.com/mikeclayton)!
|
||||
|
||||
For [v0.90][github-next-release-work], we'll work on the items below:
|
||||
#### What is being planned for version 0.89
|
||||
|
||||
For [v0.89][github-next-release-work], we'll work on the items below:
|
||||
|
||||
- New module: PowerToys Run v2
|
||||
- New module: File Actions Menu
|
||||
- Working on installer upgrades
|
||||
- Upgrading keyboard manager's editor UI
|
||||
- Stability / bug fixes
|
||||
- New module: File Actions Menu
|
||||
- PowerToys Run v2 development work
|
||||
|
||||
## PowerToys Community
|
||||
|
||||
|
||||
@@ -94,5 +94,5 @@ Note that we've supplied `Debug` option, so a `%TEMP\PowerToys.DSC.TestConfigure
|
||||
Finally, you can test it with winget by invoking it as such:
|
||||
|
||||
```ps
|
||||
winget configure .\configuration.winget --accept-configuration-agreements --disable-interactivity
|
||||
winget configure .\configuration.dsc.yaml --accept-configuration-agreements --disable-interactivity
|
||||
```
|
||||
@@ -9,14 +9,5 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
public class Button : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.Button";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Button"/> class.
|
||||
/// </summary>
|
||||
public Button()
|
||||
{
|
||||
this.TargetControlType = Button.ExpectedControlType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
|
||||
private WindowsDriver<WindowsElement>? driver;
|
||||
|
||||
protected string? TargetControlType { get; set; }
|
||||
|
||||
internal bool IsMatchingTarget()
|
||||
{
|
||||
var ct = this.ControlType;
|
||||
return string.IsNullOrEmpty(this.TargetControlType) || this.TargetControlType == this.ControlType;
|
||||
}
|
||||
|
||||
internal void SetWindowsElement(WindowsElement windowsElement) => this.windowsElement = windowsElement;
|
||||
|
||||
internal void SetSession(WindowsDriver<WindowsElement> driver) => this.driver = driver;
|
||||
@@ -99,7 +91,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// Click the UI element.
|
||||
/// </summary>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
|
||||
public virtual void Click(bool rightClick = false)
|
||||
public void Click(bool rightClick = false)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
@@ -124,7 +116,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Double Click the UI element.
|
||||
/// </summary>
|
||||
public virtual void DoubleClick()
|
||||
public void DoubleClick()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
@@ -147,6 +139,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method GetAttribute with parameter: attributeName = {attributeName}");
|
||||
var attributeValue = this.windowsElement.GetAttribute(attributeName);
|
||||
Assert.IsNotNull(attributeValue, $"Attribute '{attributeName}' is null.");
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@@ -161,51 +154,17 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElement = FindHelper.Find<T, AppiumWebElement>(
|
||||
() =>
|
||||
{
|
||||
var element = this.windowsElement.FindElement(by.ToSeleniumBy());
|
||||
Assert.IsNotNull(element, $"Element not found using selector: {by}");
|
||||
return element;
|
||||
},
|
||||
this.driver,
|
||||
timeoutMS);
|
||||
|
||||
// leverage findAll to filter out mismatched elements
|
||||
var collection = this.FindAll<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by the selector.
|
||||
/// Shortcut for this.Find<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class type of the element to find.</typeparam>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by the selector.
|
||||
/// Shortcut for this.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to use for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds an element by the selector.
|
||||
/// Shortcut for this.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
return foundElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -215,7 +174,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(By by, int timeoutMS = 3000)
|
||||
public ReadOnlyCollection<T>? FindAll<T>(By by, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.windowsElement, $"WindowsElement is null in method FindAll<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
@@ -223,59 +182,22 @@ namespace Microsoft.PowerToys.UITest
|
||||
() =>
|
||||
{
|
||||
var elements = this.windowsElement.FindElements(by.ToSeleniumBy());
|
||||
Assert.IsTrue(elements.Count > 0, $"Elements not found using selector: {by}");
|
||||
return elements;
|
||||
},
|
||||
this.driver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements ?? new ReadOnlyCollection<T>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by the selector.
|
||||
/// Shortcut for this.FindAll<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class type of the elements to find.</typeparam>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by the selector.
|
||||
/// Shortcut for this.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to use for finding the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by the selector.
|
||||
/// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name for finding the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds.</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
return foundElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a manual operation on the element.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to perform on the element.</param>
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 500 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 500 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsElement> action, int msPreAction = 500, int msPostAction = 500)
|
||||
/// <param name="msPreAction">The number of milliseconds to wait before the action. Default value is 100 ms</param>
|
||||
/// <param name="msPostAction">The number of milliseconds to wait after the action. Default value is 100 ms</param>
|
||||
protected void PerformAction(Action<Actions, WindowsElement> action, int msPreAction = 100, int msPostAction = 100)
|
||||
{
|
||||
if (msPreAction > 0)
|
||||
{
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a HyperLinkButton in the UI test environment.
|
||||
/// HyperLinkButton represents a button control that functions as a hyperlink.
|
||||
/// </summary>
|
||||
public class HyperlinkButton : Button
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.HyperLink";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HyperlinkButton"/> class.
|
||||
/// </summary>
|
||||
public HyperlinkButton()
|
||||
{
|
||||
this.TargetControlType = HyperlinkButton.ExpectedControlType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a NavigationViewItem in the UI test environment.
|
||||
/// NavigationViewItem represents the container for an item in a NavigationView control.
|
||||
/// </summary>
|
||||
public class NavigationViewItem : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.ListItem";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NavigationViewItem"/> class.
|
||||
/// </summary>
|
||||
public NavigationViewItem()
|
||||
{
|
||||
this.TargetControlType = NavigationViewItem.ExpectedControlType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Click the ListItem element.
|
||||
/// </summary>
|
||||
/// <param name="rightClick">If true, performs a right-click; otherwise, performs a left-click. Default value is false</param>
|
||||
public override void Click(bool rightClick = false)
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement, 10, 10);
|
||||
|
||||
if (rightClick)
|
||||
{
|
||||
actions.ContextClick();
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.Click();
|
||||
}
|
||||
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Double Click the ListItem element.
|
||||
/// </summary>
|
||||
public override void DoubleClick()
|
||||
{
|
||||
PerformAction((actions, windowElement) =>
|
||||
{
|
||||
actions.MoveToElement(windowElement, 10, 10);
|
||||
actions.DoubleClick();
|
||||
actions.Build().Perform();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TextBlock in the UI test environment.
|
||||
/// TextBlock provides a lightweight control for displaying small amounts of flow content.
|
||||
/// </summary>
|
||||
public class TextBlock : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.Text";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextBlock"/> class.
|
||||
/// </summary>
|
||||
public TextBlock()
|
||||
{
|
||||
this.TargetControlType = TextBlock.ExpectedControlType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,10 @@ using OpenQA.Selenium;
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a TextBox in the UI test environment.
|
||||
/// TextBox represents a control that can be used to display and edit plain text (single or multi-line).
|
||||
/// Represents a textbox in the UI test environment.
|
||||
/// </summary>
|
||||
public class TextBox : Element
|
||||
{
|
||||
private static readonly string ExpectedControlType = "ControlType.Edit";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextBox"/> class.
|
||||
/// </summary>
|
||||
public TextBox()
|
||||
{
|
||||
this.TargetControlType = TextBox.ExpectedControlType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text of the textbox.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>("Maximize").Click();
|
||||
Find<Button>(By.Name("Maximize")).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -37,7 +37,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>("Restore").Click();
|
||||
Find<Button>(By.Name("Restore")).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -56,7 +56,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>("Minimize").Click();
|
||||
Find<Button>(By.Name("Minimize")).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -74,7 +74,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
if (byClickButton)
|
||||
{
|
||||
Find<Button>("Close").Click();
|
||||
Find<Button>(By.Name("Close")).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -17,6 +17,13 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// </summary>
|
||||
internal static class FindHelper
|
||||
{
|
||||
public static T Find<T, TW>(Func<TW> findElementFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
var item = findElementFunc() as WindowsElement;
|
||||
return NewElement<T>(item, driver, timeoutMS);
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<T>? FindAll<T, TW>(Func<ReadOnlyCollection<TW>> findElementsFunc, WindowsDriver<WindowsElement>? driver, int timeoutMS)
|
||||
where T : Element, new()
|
||||
{
|
||||
@@ -25,7 +32,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
var element = item as WindowsElement;
|
||||
return NewElement<T>(element, driver, timeoutMS);
|
||||
}).Where(item => item.IsMatchingTarget()).ToList();
|
||||
}).ToList();
|
||||
|
||||
return new ReadOnlyCollection<T>(res);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
@@ -40,48 +39,17 @@ namespace Microsoft.PowerToys.UITest
|
||||
where T : Element, new()
|
||||
{
|
||||
Assert.IsNotNull(this.WindowsDriver, $"WindowsElement is null in method Find<{typeof(T).Name}> with parameters: by = {by}, timeoutMS = {timeoutMS}");
|
||||
var foundElement = FindHelper.Find<T, WindowsElement>(
|
||||
() =>
|
||||
{
|
||||
var element = this.WindowsDriver.FindElement(by.ToSeleniumBy());
|
||||
Assert.IsNotNull(element, $"Element not found using selector: {by}");
|
||||
return element;
|
||||
},
|
||||
this.WindowsDriver,
|
||||
timeoutMS);
|
||||
|
||||
// leverage findAll to filter out mismatched elements
|
||||
var collection = this.FindAll<T>(by, timeoutMS);
|
||||
|
||||
Assert.IsTrue(collection.Count > 0, $"Element not found using selector: {by}");
|
||||
|
||||
return collection[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Find<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public T Find<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Find<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
public Element Find(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Find<Element>(By.Name(name), timeoutMS);
|
||||
return foundElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,45 +72,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
this.WindowsDriver,
|
||||
timeoutMS);
|
||||
|
||||
return foundElements ?? new ReadOnlyCollection<T>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.FindAll<T>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.FindAll<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
public ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
return foundElements ?? new ReadOnlyCollection<T>(new List<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using OpenQA.Selenium.Appium;
|
||||
using OpenQA.Selenium.Appium.Windows;
|
||||
|
||||
namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
/// <summary>
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
internal class SessionHelper
|
||||
{
|
||||
// Default session path is PowerToys settings dashboard
|
||||
private readonly string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
|
||||
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
private Process? appDriver;
|
||||
|
||||
public SessionHelper(PowerToysModule scope)
|
||||
{
|
||||
this.sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
|
||||
|
||||
var winAppDriverProcessInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
Verb = "runas",
|
||||
};
|
||||
|
||||
this.appDriver = Process.Start(winAppDriverProcessInfo);
|
||||
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
this.Root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Root.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test environment.
|
||||
/// </summary>
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public SessionHelper Init()
|
||||
{
|
||||
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
this.StartExe(path + this.sessionPath);
|
||||
|
||||
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the test environment.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
appDriver?.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle exceptions if needed
|
||||
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
|
||||
public WindowsDriver<WindowsElement> GetDriver()
|
||||
{
|
||||
Assert.IsNotNull(this.Driver, $"Failed to get driver. Driver is null.");
|
||||
return this.Driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,42 +15,22 @@ namespace Microsoft.PowerToys.UITest
|
||||
/// <summary>
|
||||
/// Base class that should be inherited by all Test Classes.
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class UITestBase
|
||||
{
|
||||
public Session Session { get; set; }
|
||||
|
||||
private readonly SessionHelper sessionHelper;
|
||||
|
||||
private readonly PowerToysModule scope;
|
||||
private readonly TestInit testInit = new TestInit();
|
||||
|
||||
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
this.scope = scope;
|
||||
this.sessionHelper = new SessionHelper(scope).Init();
|
||||
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver());
|
||||
this.testInit.SetScope(scope);
|
||||
this.testInit.Init();
|
||||
this.Session = new Session(this.testInit.GetRoot(), this.testInit.GetDriver());
|
||||
}
|
||||
|
||||
~UITestBase()
|
||||
{
|
||||
this.sessionHelper.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test.
|
||||
/// </summary>
|
||||
[TestInitialize]
|
||||
public void TestInit()
|
||||
{
|
||||
if (this.scope == PowerToysModule.PowerToysSettings)
|
||||
{
|
||||
// close Debug warning dialog if any
|
||||
// Such debug warning dialog seems only appear in PowerToys Settings
|
||||
if (this.FindAll("DEBUG").Count > 0)
|
||||
{
|
||||
this.Find("DEBUG").Find<Button>("Close").Click();
|
||||
}
|
||||
}
|
||||
this.testInit.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,41 +47,6 @@ namespace Microsoft.PowerToys.UITest
|
||||
return this.Session.Find<T>(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the element, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected T Find<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
{
|
||||
return this.Session.Find<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Session.Find(by, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut for this.Session.Find<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the element.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>The found element.</returns>
|
||||
protected Element Find(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Session.Find(name, timeoutMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<T>(by, timeoutMS)
|
||||
@@ -117,41 +62,93 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// Nested class for test initialization.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The class of the elements, should be Element or its derived class.</typeparam>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<T> FindAll<T>(string name, int timeoutMS = 3000)
|
||||
where T : Element, new()
|
||||
private sealed class TestInit
|
||||
{
|
||||
return this.Session.FindAll<T>(By.Name(name), timeoutMS);
|
||||
}
|
||||
private WindowsDriver<WindowsElement> Root { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<Element>(by, timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="by">The selector to find the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(By by, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Session.FindAll<Element>(by, timeoutMS);
|
||||
}
|
||||
private WindowsDriver<WindowsElement>? Driver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Finds all elements by selector.
|
||||
/// Shortcut for this.Session.FindAll<Element>(By.Name(name), timeoutMS)
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the elements.</param>
|
||||
/// <param name="timeoutMS">The timeout in milliseconds (default is 3000).</param>
|
||||
/// <returns>A read-only collection of the found elements.</returns>
|
||||
protected ReadOnlyCollection<Element> FindAll(string name, int timeoutMS = 3000)
|
||||
{
|
||||
return this.Session.FindAll<Element>(By.Name(name), timeoutMS);
|
||||
private static Process? appDriver;
|
||||
|
||||
// Default session path is PowerToys settings dashboard
|
||||
private static string sessionPath = ModuleConfigData.Instance.GetModulePath(PowerToysModule.PowerToysSettings);
|
||||
|
||||
public TestInit()
|
||||
{
|
||||
appDriver = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "C:\\Program Files (x86)\\Windows Application Driver\\WinAppDriver.exe",
|
||||
Verb = "runas",
|
||||
});
|
||||
|
||||
var desktopCapabilities = new AppiumOptions();
|
||||
desktopCapabilities.AddAdditionalCapability("app", "Root");
|
||||
this.Root = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), desktopCapabilities);
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Root.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the test environment.
|
||||
/// </summary>
|
||||
[UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "<Pending>")]
|
||||
public void Init()
|
||||
{
|
||||
string? path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
this.StartExe(path + sessionPath);
|
||||
|
||||
Assert.IsNotNull(this.Driver, $"Failed to initialize the test environment. Driver is null.");
|
||||
|
||||
// Set default timeout to 5 seconds
|
||||
this.Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the test environment.
|
||||
/// </summary>
|
||||
public void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
appDriver?.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle exceptions if needed
|
||||
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new exe and takes control of it.
|
||||
/// </summary>
|
||||
/// <param name="appPath">The path to the application executable.</param>
|
||||
public void StartExe(string appPath)
|
||||
{
|
||||
var opts = new AppiumOptions();
|
||||
opts.AddAdditionalCapability("app", appPath);
|
||||
this.Driver = new WindowsDriver<WindowsElement>(new Uri(ModuleConfigData.Instance.GetWindowsApplicationDriverUrl()), opts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets scope to the Test Class.
|
||||
/// </summary>
|
||||
/// <param name="scope">The PowerToys module to start.</param>
|
||||
public void SetScope(PowerToysModule scope)
|
||||
{
|
||||
sessionPath = ModuleConfigData.Instance.GetModulePath(scope);
|
||||
}
|
||||
|
||||
public WindowsDriver<WindowsElement> GetRoot() => this.Root;
|
||||
|
||||
public WindowsDriver<WindowsElement> GetDriver()
|
||||
{
|
||||
Assert.IsNotNull(this.Driver, $"Failed to get driver. Driver is null.");
|
||||
return this.Driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,22 +5,22 @@ This folder contains samples of DSC files that can be used to configure PowerToy
|
||||
> [!NOTE]
|
||||
> PowerToys DSC user documentation can be found on [Learn Microsoft PowerToys documentation](https://aka.ms/powertoys-docs-dsc-configure).
|
||||
|
||||
### [configuration.winget](./configuration.winget)
|
||||
### [configuration.dsc.yaml](./configuration.dsc.yaml)
|
||||
|
||||
Sample configuration file that changes the enabled state of some modules, changes an integer setting and a hotkey setting.
|
||||
|
||||
### [installAndConfiguration.winget](./installAndConfiguration.winget)
|
||||
### [installAndConfiguration.dsc.yaml](./installAndConfiguration.dsc.yaml)
|
||||
|
||||
Installs PowerToys and applies configuration.winget to it.
|
||||
Installs PowerToys and applies configuration.dsc.yaml to it.
|
||||
|
||||
### [enableAllModules.winget](./enableAllModules.winget)
|
||||
### [enableAllModules.dsc.yaml](./enableAllModules.dsc.yaml)
|
||||
|
||||
Enables all PowerToys utilities.
|
||||
|
||||
### [disableAllModules.winget](./disableAllModules.winget)
|
||||
### [disableAllModules.dsc.yaml](./disableAllModules.dsc.yaml)
|
||||
|
||||
Disables all PowerToys utilities.
|
||||
|
||||
### [configureLauncherPlugins.winget](./configureLauncherPlugins.winget)
|
||||
### [configureLauncherPlugins.dsc.yaml](./configureLauncherPlugins.dsc.yaml)
|
||||
|
||||
Enables PowerToys Run and all its plugins and changes action keyword and global state for the Program plugin.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -18,18 +18,8 @@ namespace Hosts.UITests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Empty-view in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Empty-view is shown if no entries in the list.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Empty-view is NOT shown if 1 or more entries in the list.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Add-an-entry HyperlinkButton in Empty-view works correctly.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Test if Empty-view is shown when no entries are present.
|
||||
/// And 'Add an entry' button from Empty-view is functional.
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestEmptyView()
|
||||
@@ -38,25 +28,20 @@ namespace Hosts.UITests
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// 'Add an entry' button (only show-up when list is empty) should be visible
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 1, "'Add an entry' button should be visible in the empty view");
|
||||
|
||||
// Click 'Add an entry' from empty-view for adding Host override rule
|
||||
this.Find<HyperlinkButton>("Add an entry").Click();
|
||||
this.Find<Button>(By.Name("Add an entry")).Click();
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", false, false);
|
||||
|
||||
// Should have one row now and not more empty view
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<HyperlinkButton>("Add an entry").Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Add an entry")).Count == 0, "'Add an entry' button should be invisible if not empty view");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Adding-entry Button in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Adding-entry Button works correctly.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// Test if 'New entry' button is functional
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestAddingEntry()
|
||||
@@ -64,155 +49,11 @@ namespace Hosts.UITests
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0, "Should have no row after removing all");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0, "Should have no row after removing all");
|
||||
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 1, "Should have one row now");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Multiple-hosts validation logic
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating the Add button should be Disabled if more than 9 hosts in one entry.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating the Add button should be Enabled if less or equal 9 hosts in one entry.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestTooManyHosts()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
|
||||
// only at most 9 hosts allowed in one entry
|
||||
string validHosts = string.Join(" ", "host_1", "host_2", "host_3", "host_4", "host_5", "host_6", "host_7", "host_8", "host_9");
|
||||
|
||||
// should not allow to add more than 9 hosts in one entry, hosts are separated by space
|
||||
string inValidHosts = validHosts + " more_host";
|
||||
|
||||
this.Find<Button>("New entry").Click();
|
||||
|
||||
Assert.IsFalse(this.Find<Button>("Add").Enabled, "Add button should be Disabled by default");
|
||||
|
||||
this.Find<TextBox>("Address").SetText("127.0.0.1");
|
||||
|
||||
this.Find<TextBox>("Hosts").SetText(validHosts);
|
||||
Assert.IsTrue(this.Find<Button>("Add").Enabled, "Add button should be Enabled with validHosts");
|
||||
|
||||
this.Find<TextBox>("Hosts").SetText(inValidHosts);
|
||||
Assert.IsFalse(this.Find<Button>("Add").Enabled, "Add button should be Disabled with inValidHosts");
|
||||
|
||||
this.Find<Button>("Cancel").Click();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Error-message in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating error message should be shown if not run as admin.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestErrorMessageWithNonAdminPermission()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
// Add new URL override and a warning tip should be shown
|
||||
this.AddEntry("192.168.0.1", "localhost", true);
|
||||
|
||||
Assert.IsTrue(
|
||||
this.FindAll<TextBlock>("The hosts file cannot be saved because the program isn't running as administrator.").Count == 1,
|
||||
"Should display host-file saving error if not run as administrator");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test Filter-panel function in the Hosts-File-Editor
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Address filter matching pattern: contains, endsWith, startsWith, exactly-match.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Hosts filter matching pattern: contains, endsWith, startsWith, exactly-match.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating click Filters Button to open filter-panel, and click Filter Button again to close filter-panel.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestFilterControl()
|
||||
{
|
||||
this.CloseWarningDialog();
|
||||
this.RemoveAllEntries();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
this.AddEntry("192.168.0." + i, "localhost_" + i, true);
|
||||
}
|
||||
|
||||
// Open-filter-panel
|
||||
this.Find<Button>("Filters").Click();
|
||||
Assert.IsTrue(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be opened afer click Filter Button");
|
||||
|
||||
var addressFilterCases = new KeyValuePair<string, int>[]
|
||||
{
|
||||
// contains text, expected matched more rows
|
||||
new("168.0", 10),
|
||||
|
||||
// ends with text, expected matched 1 row
|
||||
new("168.0.1", 1),
|
||||
|
||||
// starts with text, expected matched more rows
|
||||
new("192.168.", 10),
|
||||
|
||||
// full text, expected matched 1 row
|
||||
new("192.168.0.1", 1),
|
||||
|
||||
// no-matching text, expected matched no row
|
||||
new("127.0.0", 0),
|
||||
|
||||
// empty filter, should display all rows
|
||||
new(string.Empty, 10),
|
||||
};
|
||||
|
||||
foreach (var (addressFilter, expectedCount) in addressFilterCases)
|
||||
{
|
||||
this.Find<TextBox>("Address").SetText(addressFilter);
|
||||
Assert.IsTrue(this.Find("Entries").FindAll<Button>("Delete").Count == expectedCount);
|
||||
}
|
||||
|
||||
var hostFilterCases = new KeyValuePair<string, int>[]
|
||||
{
|
||||
// contains text, expected matched more rows
|
||||
new("host_", 10),
|
||||
|
||||
// ends with text, expected matched 1 row
|
||||
new("host_4", 1),
|
||||
|
||||
// starts with text, expected matched more rows
|
||||
new("localhost", 10),
|
||||
|
||||
// full text, expected matched 1 row
|
||||
new("localhost_5", 1),
|
||||
|
||||
// empty filter, should display all rows
|
||||
new(string.Empty, 10),
|
||||
};
|
||||
|
||||
foreach (var (hostFilterCase, expectedCount) in hostFilterCases)
|
||||
{
|
||||
this.Find<TextBox>("Hosts").SetText(hostFilterCase);
|
||||
Assert.IsTrue(this.Find("Entries").FindAll<Button>("Delete").Count == expectedCount);
|
||||
}
|
||||
|
||||
// Close-filter-panel
|
||||
this.Find<Button>("Filters").Click();
|
||||
Assert.IsFalse(this.FindAll<Button>("Clear filters").Count == 1, "Filter panel should be closed afer click Filter Button");
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 1, "Should have one row now");
|
||||
}
|
||||
|
||||
private void AddEntry(string ip, string host, bool active = true, bool clickAddEntryButton = true)
|
||||
@@ -220,21 +61,21 @@ namespace Hosts.UITests
|
||||
if (clickAddEntryButton)
|
||||
{
|
||||
// Click 'Add an entry' for adding Host override rule
|
||||
this.Find<Button>("New entry").Click();
|
||||
this.Find<Button>(By.Name("New entry")).Click();
|
||||
}
|
||||
|
||||
// Adding a new host override localhost -> 192.168.0.1
|
||||
Assert.IsFalse(this.Find<Button>("Add").Enabled, "Add button should be Disabled by default");
|
||||
Assert.IsFalse(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Disabled by default");
|
||||
|
||||
Assert.IsTrue(this.Find<TextBox>("Address").SetText(ip).Text == ip);
|
||||
Assert.IsTrue(this.Find<TextBox>("Hosts").SetText(host).Text == host);
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Address")).SetText(ip, false).Text == ip);
|
||||
Assert.IsTrue(this.Find<TextBox>(By.Name("Hosts")).SetText(host, false).Text == host);
|
||||
|
||||
this.Find<ToggleSwitch>("Active").Toggle(active);
|
||||
this.Find<ToggleSwitch>(By.Name("Active")).Toggle(active);
|
||||
|
||||
Assert.IsTrue(this.Find<Button>("Add").Enabled, "Add button should be Enabled after providing valid inputs");
|
||||
Assert.IsTrue(this.Find<Button>(By.Name("Add")).Enabled, "Add button should be Enabled after providing valid inputs");
|
||||
|
||||
// Add the entry
|
||||
this.Find<Button>("Add").Click();
|
||||
this.Find<Button>(By.Name("Add")).Click();
|
||||
|
||||
// 0.5 second delay after adding an entry
|
||||
Task.Delay(500).Wait();
|
||||
@@ -243,25 +84,25 @@ namespace Hosts.UITests
|
||||
private void CloseWarningDialog()
|
||||
{
|
||||
// Find 'Accept' button which come in 'Warning' dialog
|
||||
if (this.FindAll("Warning").Count > 0 &&
|
||||
this.FindAll<Button>("Accept").Count > 0)
|
||||
if (this.FindAll<Window>(By.Name("Warning")).Count > 0 &&
|
||||
this.FindAll<Button>(By.Name("Accept")).Count > 0)
|
||||
{
|
||||
// Hide Warning dialog if any
|
||||
this.Find<Button>("Accept").Click();
|
||||
this.Find<Button>(By.Name("Accept")).Click();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllEntries()
|
||||
{
|
||||
// Delete all existing host-override rules
|
||||
foreach (var deleteBtn in this.FindAll<Button>("Delete"))
|
||||
foreach (var deleteBtn in this.FindAll<Button>(By.Name("Delete")))
|
||||
{
|
||||
deleteBtn.Click();
|
||||
this.Find<Button>("Yes").Click();
|
||||
this.Find<Button>(By.Name("Yes")).Click();
|
||||
}
|
||||
|
||||
// Should have no row left, and no more delete button
|
||||
Assert.IsTrue(this.FindAll<Button>("Delete").Count == 0);
|
||||
Assert.IsTrue(this.FindAll<Button>(By.Name("Delete")).Count == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Hosts.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class HostsSettingTests : UITestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Test Warning Dialog at startup
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description>Validating Warning-Dialog will be shown if 'Show a warning at startup' toggle is On.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating Warning-Dialog will NOT be shown if 'Show a warning at startup' toggle is Off.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating click 'Quit' button in Warning-Dialog, the Hosts File Editor window would be closed.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description>Validating click 'Accept' button in Warning-Dialog, the Hosts File Editor window would NOT be closed.</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestWarningDialog()
|
||||
{
|
||||
this.LaunchFromSetting(showWarning: true);
|
||||
|
||||
// Validating Warning-Dialog will be shown if 'Show a warning at startup' toggle is on
|
||||
Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
|
||||
|
||||
// Quit Hosts File Editor
|
||||
this.Find<Button>("Quit").Click();
|
||||
|
||||
// Wait for 500 ms to make sure Hosts File Editor is closed
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Validating click 'Quit' button in Warning-Dialog, the Hosts File Editor window would be closed
|
||||
Assert.IsTrue(this.IsHostsFileEditorClosed(), "Hosts File Editor should be closed after click Quit button in Warning Dialog");
|
||||
|
||||
// Re-attaching to Setting Windows
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
// wait for 500 ms to make sure Hosts File Editor is launched
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
this.Session.Attach(PowerToysModule.Hosts);
|
||||
|
||||
// Should show warning dialog
|
||||
Assert.IsTrue(this.FindAll("Warning").Count > 0, "Should show warning dialog");
|
||||
|
||||
// Quit Hosts File Editor
|
||||
this.Find<Button>("Accept").Click();
|
||||
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Validating click 'Accept' button in Warning-Dialog, the Hosts File Editor window would NOT be closed
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed after click Accept button in Warning Dialog");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
|
||||
this.LaunchFromSetting(showWarning: false);
|
||||
|
||||
// Should NOT show warning dialog
|
||||
Assert.IsTrue(this.FindAll("Warning").Count == 0, "Should NOT show warning dialog");
|
||||
|
||||
// Host Editor Window should not be closed
|
||||
Assert.IsFalse(this.IsHostsFileEditorClosed(), "Hosts File Editor should NOT be closed");
|
||||
|
||||
// Close Hosts File Editor window
|
||||
this.Session.Find<Window>("Hosts File Editor").Close();
|
||||
|
||||
// Restore back to PowerToysSettings Session
|
||||
this.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
|
||||
private bool IsHostsFileEditorClosed()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Session.FindAll<Window>("Hosts File Editor");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Validate if editor window closed by checking exception.Message
|
||||
return ex.Message.Contains("Currently selected window has been closed");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void LaunchFromSetting(bool showWarning = false, bool launchAsAdmin = false)
|
||||
{
|
||||
// Goto Hosts File Editor setting page
|
||||
if (this.FindAll<NavigationViewItem>("Hosts File Editor").Count == 0)
|
||||
{
|
||||
// Expand Advanced list-group if needed
|
||||
this.Find<NavigationViewItem>("Advanced").Click();
|
||||
}
|
||||
|
||||
this.Find<NavigationViewItem>("Hosts File Editor").Click();
|
||||
|
||||
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
|
||||
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
|
||||
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
|
||||
|
||||
// launch Hosts File Editor
|
||||
this.Find<Button>("Launch Hosts File Editor").Click();
|
||||
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
this.Session.Attach(PowerToysModule.Hosts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
## This is for tracking UI-Tests migration progress for Hosts File Editor Module
|
||||
Refer to [release check list] (https://github.com/microsoft/PowerToys/blob/releaseChecklist/doc/releases/tests-checklist-template.md#hosts-file-editor) for all manual tests.
|
||||
|
||||
### Existing Manual Test-cases run by previous PowerToys owner
|
||||
For existing manual test-cases, we will convert them to UI-Tests and run them in CI and Release pipeline
|
||||
|
||||
* Launch Host File Editor:
|
||||
- [x] Verify the application exits if "Quit" is clicked on the initial warning. (**HostsSettingTests.TestWarningDialog**)
|
||||
- [x] Launch Host File Editor again and click "Accept". The module should not close. (**HostModuleTests.TestEmptyView**)
|
||||
- [ ] Launch Host File Editor again and click "Accept". The module should not close. Open the hosts file (`%WinDir%\System32\Drivers\Etc`) in a text editor that auto-refreshes so you can see the changes applied by the editor in real time. (VSCode is an editor like this, for example)
|
||||
- [ ] Enable and disable lines and verify they are applied to the file.
|
||||
- [ ] Add a new entry and verify it's applied.
|
||||
- [ ] Add manually an entry with more than 9 hosts in hosts file (Windows limitation) and verify it is split correctly on loading and the info bar is shown.
|
||||
- [x] Try to filter for lines and verify you can find them. (**HostModuleTests.TestFilterControl**)
|
||||
- [ ] Click the "Open hosts file" button and verify it opens in your default editor. (likely Notepad)
|
||||
* Test the different settings and verify they are applied:
|
||||
- [ ] Launch as Administrator.
|
||||
- [x] Show a warning at startup. (**HostsSettingTests.TestWarningDialog**)
|
||||
- [ ] Additional lines position.
|
||||
|
||||
### Additional UI-Tests cases
|
||||
- [x] Add manually an entry with more than 9 hosts and Add button should be disabled. (**HostModuleTests.TestTooManyHosts**)
|
||||
- [x] Add manually an entry with less or equal 9 hosts and Add button should be enabled. (**HostModuleTests.TestTooManyHosts**)
|
||||
- [x] Should show empty view if no entries. (**HostModuleTests.TestEmptyView**)
|
||||
- [x] Add a new entry with valid or invalid input (**HostModuleTests.TestAddHost**)
|
||||
- [x] Show save host file error if not run as Administrator. (**HostModuleTests.TestErrorMessageWithNonAdminPermission**)
|
||||
@@ -49,7 +49,7 @@ namespace util
|
||||
}
|
||||
|
||||
// This workaround keeps live zoom enabled after zooming out at level 1 (not zoomed) and disables
|
||||
// live zoom when recording is stopped
|
||||
// live zoom when recording is stopped.
|
||||
#define WINDOWS_CURSOR_RECORDING_WORKAROUND 1
|
||||
|
||||
HINSTANCE g_hInstance;
|
||||
@@ -320,10 +320,10 @@ void EnsureForeground()
|
||||
//----------------------------------------------------------------------------
|
||||
void RestoreForeground()
|
||||
{
|
||||
// If the main window is not visible, move foreground to the next window.
|
||||
// If the main window is not visible, move foreground to the next window
|
||||
if( !IsWindowVisible( g_hWndMain ) ) {
|
||||
|
||||
// Activate the next window by unhiding and hiding the main window.
|
||||
// Activate the next window by unhiding and hiding the main window
|
||||
MoveWindow( g_hWndMain, 0, 0, 0, 0, FALSE );
|
||||
ShowWindow( g_hWndMain, SW_SHOWNA );
|
||||
ShowWindow( g_hWndMain, SW_HIDE );
|
||||
@@ -493,7 +493,7 @@ bool IsAutostartConfigured()
|
||||
// RunningOnWin64
|
||||
//
|
||||
// Returns true if this is the 32-bit version of the executable
|
||||
// and we're on 64-bit Windows.
|
||||
// and we're on 64-bit Windows
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
typedef BOOL (__stdcall *P_IS_WOW64PROCESS)(
|
||||
@@ -559,7 +559,7 @@ BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
|
||||
// Run64bitVersion
|
||||
//
|
||||
// Returns true if this is the 32-bit version of the executable
|
||||
// and we're on 64-bit Windows.
|
||||
// and we're on 64-bit Windows
|
||||
//
|
||||
//--------------------------------------------------------------------
|
||||
DWORD
|
||||
|
||||
@@ -230,7 +230,7 @@ namespace ColorPicker.Helpers
|
||||
|
||||
public bool HandleEnterPressed()
|
||||
{
|
||||
if (!_colorPickerShown)
|
||||
if (!IsColorPickerVisible())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -241,13 +241,14 @@ namespace ColorPicker.Helpers
|
||||
|
||||
public bool HandleEscPressed()
|
||||
{
|
||||
if (!BlockEscapeKeyClosingColorPickerEditor
|
||||
&& (_colorPickerShown || (_colorEditorWindow != null && _colorEditorWindow.IsActive)))
|
||||
if (!BlockEscapeKeyClosingColorPickerEditor)
|
||||
{
|
||||
return EndUserSession();
|
||||
}
|
||||
|
||||
return false;
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void MoveCursor(int xOffset, int yOffset)
|
||||
|
||||
@@ -70,10 +70,17 @@ namespace ColorPicker.Keyboard
|
||||
var virtualCode = e.KeyboardData.VirtualCode;
|
||||
|
||||
// ESC pressed
|
||||
if (virtualCode == KeyInterop.VirtualKeyFromKey(Key.Escape) && e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
|
||||
if (virtualCode == KeyInterop.VirtualKeyFromKey(Key.Escape)
|
||||
&& e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown
|
||||
)
|
||||
{
|
||||
e.Handled = _appStateHandler.HandleEscPressed();
|
||||
return;
|
||||
if (_appStateHandler.IsColorPickerVisible()
|
||||
|| !AppStateHandler.BlockEscapeKeyClosingColorPickerEditor
|
||||
)
|
||||
{
|
||||
e.Handled = _appStateHandler.EndUserSession();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((virtualCode == KeyInterop.VirtualKeyFromKey(Key.Space) || virtualCode == KeyInterop.VirtualKeyFromKey(Key.Enter)) && (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown))
|
||||
|
||||
@@ -487,10 +487,10 @@ namespace FancyZonesEditor
|
||||
}
|
||||
}
|
||||
|
||||
model.Delete();
|
||||
App.FancyZonesEditorIO.SerializeAppliedLayouts();
|
||||
App.FancyZonesEditorIO.SerializeCustomLayouts();
|
||||
App.FancyZonesEditorIO.SerializeDefaultLayouts();
|
||||
model.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,561 @@
|
||||
#include "pch.h"
|
||||
#include "KeyboardManagerEditorLibraryWrapper.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h>
|
||||
#include <common/interop/keyboard_layout.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
void* CreateMappingConfiguration()
|
||||
{
|
||||
return new MappingConfiguration();
|
||||
}
|
||||
|
||||
void DestroyMappingConfiguration(void* config)
|
||||
{
|
||||
delete static_cast<MappingConfiguration*>(config);
|
||||
}
|
||||
|
||||
bool LoadMappingSettings(void* config)
|
||||
{
|
||||
return static_cast<MappingConfiguration*>(config)->LoadSettings();
|
||||
}
|
||||
|
||||
bool SaveMappingSettings(void* config)
|
||||
{
|
||||
return static_cast<MappingConfiguration*>(config)->SaveSettingsToFile();
|
||||
}
|
||||
|
||||
wchar_t* AllocateAndCopyString(const std::wstring& str)
|
||||
{
|
||||
size_t len = str.length();
|
||||
wchar_t* buffer = new wchar_t[len + 1];
|
||||
wcscpy_s(buffer, len + 1, str.c_str());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int GetSingleKeyRemapCount(void* config)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
return static_cast<int>(mapping->singleKeyReMap.size());
|
||||
}
|
||||
|
||||
bool GetSingleKeyRemap(void* config, int index, KeyboardMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
std::vector<std::pair<DWORD, KeyShortcutTextUnion>> allMappings;
|
||||
|
||||
for (const auto& kv : mappingConfig->singleKeyReMap)
|
||||
{
|
||||
allMappings.push_back(kv);
|
||||
}
|
||||
|
||||
if (index < 0 || index >= allMappings.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& kv = allMappings[index];
|
||||
mapping->originalKey = static_cast<int>(kv.first);
|
||||
|
||||
if (kv.second.index() == 0)
|
||||
{
|
||||
mapping->targetKey = AllocateAndCopyString(std::to_wstring(std::get<DWORD>(kv.second)));
|
||||
mapping->isShortcut = false;
|
||||
}
|
||||
else if (kv.second.index() == 1)
|
||||
{
|
||||
mapping->targetKey = AllocateAndCopyString(std::get<Shortcut>(kv.second).ToHstringVK().c_str());
|
||||
mapping->isShortcut = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping->targetKey = AllocateAndCopyString(L"");
|
||||
mapping->isShortcut = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetSingleKeyToTextRemapCount(void* config)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
return static_cast<int>(mapping->singleKeyToTextReMap.size());
|
||||
}
|
||||
|
||||
bool GetSingleKeyToTextRemap(void* config, int index, KeyboardTextMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (index < 0 || index >= mappingConfig->singleKeyToTextReMap.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = mappingConfig->singleKeyToTextReMap.begin();
|
||||
std::advance(it, index);
|
||||
|
||||
mapping->originalKey = static_cast<int>(it->first);
|
||||
std::wstring text = std::get<std::wstring>(it->second);
|
||||
mapping->targetText = AllocateAndCopyString(text);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetShortcutRemapCountByType(void* config, int operationType)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
int count = 0;
|
||||
|
||||
for (const auto& kv : mapping->osLevelShortcutReMap)
|
||||
{
|
||||
bool shouldCount = false;
|
||||
|
||||
|
||||
if (operationType == 0)
|
||||
{
|
||||
if ((kv.second.targetShortcut.index() == 0) ||
|
||||
(kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1)
|
||||
{
|
||||
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2)
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCount)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& appMap : mapping->appSpecificShortcutReMap)
|
||||
{
|
||||
for (const auto& shortcutKv : appMap.second)
|
||||
{
|
||||
bool shouldCount = false;
|
||||
|
||||
if (operationType == 0)
|
||||
{
|
||||
if ((shortcutKv.second.targetShortcut.index() == 0) ||
|
||||
(shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldCount = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCount)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool GetShortcutRemapByType(void* config, int operationType, int index, ShortcutMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> filteredMappings;
|
||||
|
||||
for (const auto& kv : mappingConfig->osLevelShortcutReMap)
|
||||
{
|
||||
bool shouldAdd = false;
|
||||
|
||||
if (operationType == 0) // RemapShortcut
|
||||
{
|
||||
if ((kv.second.targetShortcut.index() == 0) ||
|
||||
(kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1) // RunProgram
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2) // OpenURI
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (kv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAdd)
|
||||
{
|
||||
filteredMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
|
||||
{
|
||||
for (const auto& shortcutKv : appKv.second)
|
||||
{
|
||||
bool shouldAdd = false;
|
||||
|
||||
if (operationType == 0) // RemapShortcut
|
||||
{
|
||||
if ((shortcutKv.second.targetShortcut.index() == 0) ||
|
||||
(shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RemapShortcut))
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 1) // RunProgram
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 2) // OpenURI
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
else if (operationType == 3)
|
||||
{
|
||||
if (shortcutKv.second.targetShortcut.index() == 2)
|
||||
{
|
||||
shouldAdd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldAdd)
|
||||
{
|
||||
filteredMappings.push_back(std::make_tuple(
|
||||
shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0 || index >= filteredMappings.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& [origShortcut, targetShortcutUnion, app] = filteredMappings[index];
|
||||
|
||||
std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
|
||||
mapping->originalKeys = AllocateAndCopyString(origKeysStr);
|
||||
mapping->targetApp = AllocateAndCopyString(app);
|
||||
|
||||
if (targetShortcutUnion.index() == 0)
|
||||
{
|
||||
DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 1)
|
||||
{
|
||||
Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
|
||||
std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();
|
||||
|
||||
mapping->operationType = static_cast<int>(targetShortcut.operationType);
|
||||
|
||||
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
|
||||
mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 2)
|
||||
{
|
||||
std::wstring text = std::get<std::wstring>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(L"");
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(text);
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetShortcutRemapCount(void* config)
|
||||
{
|
||||
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||
int count = static_cast<int>(mapping->osLevelShortcutReMap.size());
|
||||
|
||||
for (const auto& appMap : mapping->appSpecificShortcutReMap)
|
||||
{
|
||||
count += static_cast<int>(appMap.second.size());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool GetShortcutRemap(void* config, int index, ShortcutMapping* mapping)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
std::vector<std::tuple<Shortcut, KeyShortcutTextUnion, std::wstring>> allMappings;
|
||||
|
||||
for (const auto& kv : mappingConfig->osLevelShortcutReMap)
|
||||
{
|
||||
allMappings.push_back(std::make_tuple(kv.first, kv.second.targetShortcut, L""));
|
||||
}
|
||||
|
||||
for (const auto& appKv : mappingConfig->appSpecificShortcutReMap)
|
||||
{
|
||||
for (const auto& shortcutKv : appKv.second)
|
||||
{
|
||||
allMappings.push_back(std::make_tuple(
|
||||
shortcutKv.first, shortcutKv.second.targetShortcut, appKv.first));
|
||||
}
|
||||
}
|
||||
|
||||
if (index < 0 || index >= allMappings.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& [origShortcut, targetShortcutUnion, app] = allMappings[index];
|
||||
|
||||
std::wstring origKeysStr = origShortcut.ToHstringVK().c_str();
|
||||
mapping->originalKeys = AllocateAndCopyString(origKeysStr);
|
||||
|
||||
mapping->targetApp = AllocateAndCopyString(app);
|
||||
|
||||
if (targetShortcutUnion.index() == 0)
|
||||
{
|
||||
DWORD targetKey = std::get<DWORD>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(std::to_wstring(targetKey));
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 1)
|
||||
{
|
||||
Shortcut targetShortcut = std::get<Shortcut>(targetShortcutUnion);
|
||||
std::wstring targetKeysStr = targetShortcut.ToHstringVK().c_str();
|
||||
|
||||
mapping->operationType = static_cast<int>(targetShortcut.operationType);
|
||||
|
||||
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(targetShortcut.runProgramFilePath);
|
||||
mapping->programArgs = AllocateAndCopyString(targetShortcut.runProgramArgs);
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||
mapping->targetText = AllocateAndCopyString(L"");
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
}
|
||||
else if (targetShortcutUnion.index() == 2)
|
||||
{
|
||||
std::wstring text = std::get<std::wstring>(targetShortcutUnion);
|
||||
mapping->targetKeys = AllocateAndCopyString(L"");
|
||||
mapping->operationType = 0;
|
||||
mapping->targetText = AllocateAndCopyString(text);
|
||||
mapping->programPath = AllocateAndCopyString(L"");
|
||||
mapping->programArgs = AllocateAndCopyString(L"");
|
||||
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FreeString(wchar_t* str)
|
||||
{
|
||||
delete[] str;
|
||||
}
|
||||
|
||||
bool AddSingleKeyRemap(void* config, int originalKey, int targetKey)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), static_cast<DWORD>(targetKey));
|
||||
}
|
||||
|
||||
bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (text == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return mappingConfig->AddSingleKeyToTextRemap(static_cast<DWORD>(originalKey), text);
|
||||
}
|
||||
|
||||
bool AddSingleKeyToShortcutRemap(void* config, int originalKey, const wchar_t* targetKeys)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
if (!targetKeys)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Shortcut targetShortcut(targetKeys);
|
||||
|
||||
return mappingConfig->AddSingleKeyRemap(static_cast<DWORD>(originalKey), targetShortcut);
|
||||
}
|
||||
|
||||
bool AddShortcutRemap(void* config,
|
||||
const wchar_t* originalKeys,
|
||||
const wchar_t* targetKeys,
|
||||
const wchar_t* targetApp)
|
||||
{
|
||||
auto mappingConfig = static_cast<MappingConfiguration*>(config);
|
||||
|
||||
Shortcut origShortcut(originalKeys);
|
||||
Shortcut targetShortcut(targetKeys);
|
||||
|
||||
std::wstring app(targetApp ? targetApp : L"");
|
||||
|
||||
if (app.empty())
|
||||
{
|
||||
return mappingConfig->AddOSLevelShortcut(origShortcut, targetShortcut);
|
||||
}
|
||||
else
|
||||
{
|
||||
return mappingConfig->AddAppSpecificShortcut(app, origShortcut, targetShortcut);
|
||||
}
|
||||
}
|
||||
|
||||
void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount)
|
||||
{
|
||||
if (keyName == nullptr || maxCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
LayoutMap layoutMap;
|
||||
std::wstring name = layoutMap.GetKeyName(static_cast<DWORD>(keyCode));
|
||||
wcsncpy_s(keyName, maxCount, name.c_str(), _TRUNCATE);
|
||||
}
|
||||
|
||||
int GetKeyCodeFromName(const wchar_t* keyName)
|
||||
{
|
||||
if (keyName == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LayoutMap layoutMap;
|
||||
std::wstring name(keyName);
|
||||
return static_cast<int>(layoutMap.GetKeyFromName(name));
|
||||
}
|
||||
}
|
||||
|
||||
// Test function to call the remapping helper function
|
||||
|
||||
bool CheckIfRemappingsAreValid()
|
||||
{
|
||||
RemapBuffer remapBuffer;
|
||||
@@ -18,3 +568,26 @@ bool CheckIfRemappingsAreValid()
|
||||
|
||||
return result == ShortcutErrorType::NoError;
|
||||
}
|
||||
|
||||
// Get the list of keyboard keys in Editor
|
||||
int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount)
|
||||
{
|
||||
if (keyList == nullptr || maxCount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LayoutMap layoutMap;
|
||||
auto keyNameList = layoutMap.GetKeyNameList(isShortcut);
|
||||
|
||||
int count = (std::min)(static_cast<int>(keyNameList.size()), maxCount);
|
||||
|
||||
// Transfer the key list to the output struct format
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
keyList[i].keyCode = static_cast<int>(keyNameList[i].first);
|
||||
wcsncpy_s(keyList[i].keyName, keyNameList[i].second.c_str(), _countof(keyList[i].keyName) - 1);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -4,4 +4,69 @@
|
||||
#include <keyboardmanager/common/Input.h>
|
||||
#include <keyboardmanager/common/MappingConfiguration.h>
|
||||
|
||||
struct KeyNamePair
|
||||
{
|
||||
int keyCode;
|
||||
wchar_t keyName[64];
|
||||
};
|
||||
|
||||
struct KeyboardMapping
|
||||
{
|
||||
int originalKey;
|
||||
wchar_t* targetKey;
|
||||
bool isShortcut;
|
||||
};
|
||||
|
||||
struct KeyboardTextMapping
|
||||
{
|
||||
int originalKey;
|
||||
wchar_t* targetText;
|
||||
};
|
||||
|
||||
struct ShortcutMapping
|
||||
{
|
||||
wchar_t* originalKeys;
|
||||
wchar_t* targetKeys;
|
||||
wchar_t* targetApp;
|
||||
int operationType;
|
||||
wchar_t* targetText;
|
||||
wchar_t* programPath;
|
||||
wchar_t* programArgs;
|
||||
wchar_t* uriToOpen;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
__declspec(dllexport) void* CreateMappingConfiguration();
|
||||
__declspec(dllexport) void DestroyMappingConfiguration(void* config);
|
||||
__declspec(dllexport) bool LoadMappingSettings(void* config);
|
||||
__declspec(dllexport) bool SaveMappingSettings(void* config);
|
||||
|
||||
__declspec(dllexport) int GetSingleKeyRemapCount(void* config);
|
||||
__declspec(dllexport) bool GetSingleKeyRemap(void* config, int index, KeyboardMapping* mapping);
|
||||
|
||||
__declspec(dllexport) int GetSingleKeyToTextRemapCount(void* config);
|
||||
__declspec(dllexport) bool GetSingleKeyToTextRemap(void* config, int index, KeyboardTextMapping* mapping);
|
||||
|
||||
__declspec(dllexport) int GetShortcutRemapCountByType(void* config, int operationType);
|
||||
__declspec(dllexport) bool GetShortcutRemapByType(void* config, int operationType, int index, ShortcutMapping* mapping);
|
||||
|
||||
__declspec(dllexport) int GetShortcutRemapCount(void* config);
|
||||
__declspec(dllexport) bool GetShortcutRemap(void* config, int index, ShortcutMapping* mapping);
|
||||
|
||||
__declspec(dllexport) bool AddSingleKeyRemap(void* config, int originalKey, int targetKey);
|
||||
__declspec(dllexport) bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text);
|
||||
__declspec(dllexport) bool AddSingleKeyToShortcutRemap(void* config,
|
||||
int originalKey,
|
||||
const wchar_t* targetKeys);
|
||||
__declspec(dllexport) bool AddShortcutRemap(void* config,
|
||||
const wchar_t* originalKeys,
|
||||
const wchar_t* targetKeys,
|
||||
const wchar_t* targetApp);
|
||||
|
||||
__declspec(dllexport) void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount);
|
||||
__declspec(dllexport) int GetKeyCodeFromName(const wchar_t* keyName);
|
||||
__declspec(dllexport) void FreeString(wchar_t* str);
|
||||
}
|
||||
extern "C" __declspec(dllexport) bool CheckIfRemappingsAreValid();
|
||||
extern "C" __declspec(dllexport) int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount);
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
<ResourceDictionary Source="/Styles/KeyVisual.xaml" />
|
||||
<ResourceDictionary Source="/Styles/CommonStyle.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -7,7 +7,10 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -35,8 +38,14 @@ namespace KeyboardManagerEditorUI
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor logger is initialized");
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor logger is initialized");
|
||||
});
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,10 +55,38 @@ namespace KeyboardManagerEditorUI
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
window = new MainWindow();
|
||||
window.Activate();
|
||||
|
||||
var appWindow = window.AppWindow;
|
||||
|
||||
var windowSize = new Windows.Graphics.SizeInt32(960, 600);
|
||||
appWindow.Resize(windowSize);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary()
|
||||
{
|
||||
Source = new Uri("ms-appx:///Styles/CommonStyle.xaml"),
|
||||
});
|
||||
}).ContinueWith(_ =>
|
||||
{
|
||||
window.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
window.Activate();
|
||||
window.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
(window.Content as FrameworkElement)?.UpdateLayout();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Logger.LogInfo("keyboard-manager WinUI3 editor window is launched");
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
private Window? window;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,25 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class Remapping
|
||||
{
|
||||
public List<string> OriginalKeys { get; set; } = new List<string>();
|
||||
|
||||
public List<string> RemappedKeys { get; set; } = new List<string>();
|
||||
|
||||
public bool IsAllApps { get; set; } = true;
|
||||
|
||||
public string AppName { get; set; } = "All Apps";
|
||||
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
{
|
||||
public class URLShortcut
|
||||
{
|
||||
public List<string> Shortcut { get; set; } = new List<string>();
|
||||
|
||||
public string URL { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyMapping
|
||||
{
|
||||
public int OriginalKey { get; set; }
|
||||
|
||||
public string TargetKey { get; set; } = string.Empty;
|
||||
|
||||
public bool IsShortcut { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyToTextMapping
|
||||
{
|
||||
public int OriginalKey { get; set; }
|
||||
|
||||
public string TargetText { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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 System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public static class KeyboardManagerInterop
|
||||
{
|
||||
private const string DllName = "Powertoys.KeyboardManagerEditorLibraryWrapper.dll";
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern IntPtr CreateMappingConfiguration();
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern void DestroyMappingConfiguration(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool LoadMappingSettings(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SaveMappingSettings(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetSingleKeyRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetSingleKeyRemap(IntPtr config, int index, ref KeyboardMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetSingleKeyToTextRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetSingleKeyToTextRemap(
|
||||
IntPtr config,
|
||||
int index,
|
||||
ref KeyboardTextMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetShortcutRemapCount(IntPtr config);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetShortcutRemap(IntPtr config, int index, ref ShortcutMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetShortcutRemapCountByType(IntPtr config, int operationType);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetShortcutRemapByType(
|
||||
IntPtr config,
|
||||
int operationType,
|
||||
int index,
|
||||
ref ShortcutMapping mapping);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyRemap(IntPtr config, int originalKey, int targetKey);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddSingleKeyToShortcutRemap(
|
||||
IntPtr config,
|
||||
int originalKey,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetKeys);
|
||||
|
||||
[DllImport(DllName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddShortcutRemap(
|
||||
IntPtr config,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string originalKeys,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetKeys,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string targetApp);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern int GetKeyCodeFromName(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string keyName);
|
||||
|
||||
[DllImport(DllName)]
|
||||
internal static extern void FreeString(IntPtr str);
|
||||
|
||||
public static string GetStringAndFree(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string? result = Marshal.PtrToStringUni(ptr);
|
||||
FreeString(ptr);
|
||||
return result ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct KeyboardMapping
|
||||
{
|
||||
public int OriginalKey;
|
||||
public IntPtr TargetKey;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool IsShortcut;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct KeyboardTextMapping
|
||||
{
|
||||
public int OriginalKey;
|
||||
public IntPtr TargetText;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ShortcutMapping
|
||||
{
|
||||
public IntPtr OriginalKeys;
|
||||
public IntPtr TargetKeys;
|
||||
public IntPtr TargetApp;
|
||||
public int OperationType;
|
||||
public IntPtr TargetText;
|
||||
public IntPtr ProgramPath;
|
||||
public IntPtr ProgramArgs;
|
||||
public IntPtr UriToOpen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// 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 System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class KeyboardMappingService : IDisposable
|
||||
{
|
||||
private IntPtr _configHandle;
|
||||
private bool _disposed;
|
||||
|
||||
public KeyboardMappingService()
|
||||
{
|
||||
_configHandle = KeyboardManagerInterop.CreateMappingConfiguration();
|
||||
if (_configHandle == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create mapping configuration");
|
||||
}
|
||||
|
||||
KeyboardManagerInterop.LoadMappingSettings(_configHandle);
|
||||
}
|
||||
|
||||
public List<KeyMapping> GetSingleKeyMappings()
|
||||
{
|
||||
var result = new List<KeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetSingleKeyRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(KeyboardMapping);
|
||||
if (KeyboardManagerInterop.GetSingleKeyRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new KeyMapping
|
||||
{
|
||||
OriginalKey = mapping.OriginalKey,
|
||||
TargetKey = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKey),
|
||||
IsShortcut = mapping.IsShortcut,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ShortcutKeyMapping> GetShortcutMappings()
|
||||
{
|
||||
var result = new List<ShortcutKeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetShortcutRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(ShortcutMapping);
|
||||
if (KeyboardManagerInterop.GetShortcutRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new ShortcutKeyMapping
|
||||
{
|
||||
OriginalKeys = KeyboardManagerInterop.GetStringAndFree(mapping.OriginalKeys),
|
||||
TargetKeys = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKeys),
|
||||
TargetApp = KeyboardManagerInterop.GetStringAndFree(mapping.TargetApp),
|
||||
OperationType = (ShortcutOperationType)mapping.OperationType,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
ProgramPath = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramPath),
|
||||
ProgramArgs = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramArgs),
|
||||
UriToOpen = KeyboardManagerInterop.GetStringAndFree(mapping.UriToOpen),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ShortcutKeyMapping> GetShortcutMappingsByType(ShortcutOperationType operationType)
|
||||
{
|
||||
var result = new List<ShortcutKeyMapping>();
|
||||
int count = KeyboardManagerInterop.GetShortcutRemapCountByType(_configHandle, (int)operationType);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(ShortcutMapping);
|
||||
if (KeyboardManagerInterop.GetShortcutRemapByType(_configHandle, (int)operationType, i, ref mapping))
|
||||
{
|
||||
result.Add(new ShortcutKeyMapping
|
||||
{
|
||||
OriginalKeys = KeyboardManagerInterop.GetStringAndFree(mapping.OriginalKeys),
|
||||
TargetKeys = KeyboardManagerInterop.GetStringAndFree(mapping.TargetKeys),
|
||||
TargetApp = KeyboardManagerInterop.GetStringAndFree(mapping.TargetApp),
|
||||
OperationType = (ShortcutOperationType)mapping.OperationType,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
ProgramPath = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramPath),
|
||||
ProgramArgs = KeyboardManagerInterop.GetStringAndFree(mapping.ProgramArgs),
|
||||
UriToOpen = KeyboardManagerInterop.GetStringAndFree(mapping.UriToOpen),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<KeyToTextMapping> GetKeyToTextMappings()
|
||||
{
|
||||
var result = new List<KeyToTextMapping>();
|
||||
int count = KeyboardManagerInterop.GetSingleKeyToTextRemapCount(_configHandle);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var mapping = default(KeyboardTextMapping);
|
||||
if (KeyboardManagerInterop.GetSingleKeyToTextRemap(_configHandle, i, ref mapping))
|
||||
{
|
||||
result.Add(new KeyToTextMapping
|
||||
{
|
||||
OriginalKey = mapping.OriginalKey,
|
||||
TargetText = KeyboardManagerInterop.GetStringAndFree(mapping.TargetText),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool AddSingleKeyMapping(int originalKey, int targetKey)
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
|
||||
}
|
||||
|
||||
public bool AddSingleKeyMapping(int originalKey, string targetKeys)
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!targetKeys.Contains(';') && int.TryParse(targetKeys, out int targetKey))
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
return KeyboardManagerInterop.AddSingleKeyToShortcutRemap(_configHandle, originalKey, targetKeys);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddShortcutMapping(string originalKeys, string targetKeys, string targetApp = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(originalKeys) || string.IsNullOrEmpty(targetKeys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return KeyboardManagerInterop.AddShortcutRemap(_configHandle, originalKeys, targetKeys, targetApp);
|
||||
}
|
||||
|
||||
public bool SaveSettings()
|
||||
{
|
||||
return KeyboardManagerInterop.SaveMappingSettings(_configHandle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_configHandle != IntPtr.Zero)
|
||||
{
|
||||
KeyboardManagerInterop.DestroyMappingConfiguration(_configHandle);
|
||||
_configHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~KeyboardMappingService()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public class ShortcutKeyMapping
|
||||
{
|
||||
public string OriginalKeys { get; set; } = string.Empty;
|
||||
|
||||
public string TargetKeys { get; set; } = string.Empty;
|
||||
|
||||
public string TargetApp { get; set; } = string.Empty;
|
||||
|
||||
public ShortcutOperationType OperationType { get; set; }
|
||||
|
||||
public string TargetText { get; set; } = string.Empty;
|
||||
|
||||
public string ProgramPath { get; set; } = string.Empty;
|
||||
|
||||
public string ProgramArgs { get; set; } = string.Empty;
|
||||
|
||||
public string UriToOpen { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Interop
|
||||
{
|
||||
public enum ShortcutOperationType
|
||||
{
|
||||
RemapShortcut = 0,
|
||||
RunProgram = 1,
|
||||
OpenUri = 2,
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,22 @@
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
|
||||
<EnableXamlJitOptimization>true</EnableXamlJitOptimization>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Pages\ExistingUI.xaml" />
|
||||
<None Remove="Pages\Programs.xaml" />
|
||||
<None Remove="Pages\Shortcuts.xaml" />
|
||||
<None Remove="Pages\Text.xaml" />
|
||||
<None Remove="Pages\URLs.xaml" />
|
||||
<None Remove="Styles\CommonStyle.xaml" />
|
||||
<None Remove="Styles\InputControl.xaml" />
|
||||
<None Remove="Styles\KeyVisual.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
@@ -33,6 +49,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
@@ -40,7 +57,44 @@
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\" />
|
||||
<Page Update="Styles\CommonStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\URLs.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Text.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Programs.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\Shortcuts.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\InputControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\KeyVisual.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Pages\ExistingUI.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -6,13 +6,67 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:KeyboardManagerEditorUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="using:KeyboardManagerEditorUI.Pages"
|
||||
Title="KeyboardManagerEditorUI"
|
||||
mc:Ignorable="d">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
<Grid
|
||||
x:Name="LayoutRoot"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="titleBar">
|
||||
<StackPanel
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="16,8,8,8"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal">
|
||||
<Image Width="16" Source="ms-appx:///Assets/FluentIconsKeyboardManager.png" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="Keyboard Manager" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="myButton" Click="MyButton_Click">Click Me</Button>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
<NavigationView
|
||||
x:Name="RootView"
|
||||
Grid.Row="1"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsBackEnabled="False"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
PaneDisplayMode="Top"
|
||||
SelectionChanged="RootView_SelectionChanged">
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem Content="Remappings" Tag="Remappings">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="Programs" Tag="Programs">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="Text" Tag="Text">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="URLs" Tag="URLs">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.MenuItems>
|
||||
<NavigationView.Content>
|
||||
<Frame x:Name="NavigationFrame" Margin="0,0,0,0" />
|
||||
</NavigationView.Content>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -20,23 +21,28 @@ using Windows.Foundation.Collections;
|
||||
|
||||
namespace KeyboardManagerEditorUI
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
[DllImport("KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool CheckIfRemappingsAreValid();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(titleBar);
|
||||
RootView.SelectedItem = RootView.MenuItems[0];
|
||||
}
|
||||
|
||||
private void MyButton_Click(object sender, RoutedEventArgs e)
|
||||
private void RootView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
||||
{
|
||||
// Call the C++ function to check if the current remappings are valid
|
||||
myButton.Content = CheckIfRemappingsAreValid() ? "Valid" : "Invalid";
|
||||
if (args.SelectedItem is NavigationViewItem selectedItem)
|
||||
{
|
||||
switch ((string)selectedItem.Tag)
|
||||
{
|
||||
case "Remappings": NavigationFrame.Navigate(typeof(Pages.Shortcuts)); break;
|
||||
case "Programs": NavigationFrame.Navigate(typeof(Pages.Programs)); break;
|
||||
case "Text": NavigationFrame.Navigate(typeof(Pages.Text)); break;
|
||||
case "URLs": NavigationFrame.Navigate(typeof(Pages.URLs)); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Pages.ExistingUI"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<!-- WinUI3 implementation of the existing Keyboard Manager UI -->
|
||||
<Grid Padding="16">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="10"
|
||||
FontSize="24"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Text="Remap keys" />
|
||||
|
||||
<TextBlock
|
||||
Margin="10"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Select the key you want to change and then configure the key, shortcut or text you want it to send."
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock
|
||||
Margin="10"
|
||||
FontSize="16"
|
||||
FontStyle="Italic"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Example of a remapping: Select A and send "Ctrl+C", "A" would be your "Select" and "Ctrl+C" would be your "To send" command."
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="240" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="240" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Text="Selected:" />
|
||||
<Grid Grid.Row="1" Margin="0,8,0,0">
|
||||
<StackPanel>
|
||||
<ToggleButton Click="Button_Click" Content="Select" />
|
||||
<ComboBox
|
||||
x:Name="keyComboBox"
|
||||
Width="250"
|
||||
PlaceholderText="select keys please" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="24,0,24,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Text="" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="0,12,0,0"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Text="To Send:" />
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="0,8,0,0">
|
||||
<StackPanel>
|
||||
<ToggleButton Click="Button_Click" Content="Click to select" />
|
||||
<ComboBox
|
||||
x:Name="newKeyComboBox"
|
||||
Width="250"
|
||||
PlaceholderText="select keys please" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button
|
||||
Margin="10"
|
||||
Content="+ Add Key Remapping"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
// WinUI3 implementation of the Existing Keyboard Manager UI
|
||||
namespace KeyboardManagerEditorUI.Pages
|
||||
{
|
||||
public sealed partial class ExistingUI : UserControl
|
||||
{
|
||||
public class KeyboardKey
|
||||
{
|
||||
public int KeyCode { get; set; }
|
||||
|
||||
public string KeyName { get; set; } = string.Empty;
|
||||
|
||||
public override string ToString() => KeyName;
|
||||
}
|
||||
|
||||
// Struct to hold key code and name pairs
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct KeyNamePair
|
||||
{
|
||||
public int KeyCode;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
||||
public string KeyName;
|
||||
}
|
||||
|
||||
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int GetKeyboardKeysList(bool isShortcut, [Out] KeyNamePair[] keyList, int maxCount);
|
||||
|
||||
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool CheckIfRemappingsAreValid();
|
||||
|
||||
public List<KeyboardKey> KeysList { get; private set; } = new List<KeyboardKey>();
|
||||
|
||||
public ExistingUI()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
LoadKeyboardKeys();
|
||||
|
||||
keyComboBox.ItemsSource = KeysList;
|
||||
keyComboBox.DisplayMemberPath = "KeyName";
|
||||
|
||||
newKeyComboBox.ItemsSource = KeysList;
|
||||
newKeyComboBox.DisplayMemberPath = "KeyCode";
|
||||
}
|
||||
|
||||
private void LoadKeyboardKeys()
|
||||
{
|
||||
const int MaxKeys = 300;
|
||||
KeyNamePair[] keyNamePairs = new KeyNamePair[MaxKeys];
|
||||
|
||||
int count = GetKeyboardKeysList(false, keyNamePairs, MaxKeys);
|
||||
|
||||
KeysList = new List<KeyboardKey>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
KeysList.Add(new KeyboardKey
|
||||
{
|
||||
KeyCode = keyNamePairs[i].KeyCode,
|
||||
KeyName = keyNamePairs[i].KeyName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.AddedItems.Count > 0 && e.AddedItems[0] is KeyboardKey key)
|
||||
{
|
||||
Console.WriteLine($"selected key: {key.KeyName} (code: {key.KeyCode})");
|
||||
}
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var button = sender as Button;
|
||||
if (button == null)
|
||||
{
|
||||
// button.Background = (SolidColorBrush)Application.Current.Resources["SystemControlBackgroundAccentBrush"];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="KeyboardManagerEditorUI.Pages.Programs"
|
||||
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:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:styles="using:KeyboardManagerEditorUI.Styles"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Padding="16">
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Program shortcuts allow you to open specific applications when you use the configured shortcut"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="NewShortcutBtn"
|
||||
Height="36"
|
||||
Margin="0,12,0,0"
|
||||
Click="NewShortcutBtn_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock VerticalAlignment="Center" Text="New" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="16,-2,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Shortcut" />
|
||||
<AppBarSeparator
|
||||
Grid.Column="1"
|
||||
Margin="-6,4,0,4"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="12,-2,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Program" />
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="4"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_ItemClick"
|
||||
ItemsSource="{x:Bind Shortcuts}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:URLShortcut">
|
||||
<Grid Height="48">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="232" />
|
||||
<ColumnDefinition Width="238" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="5"
|
||||
Height="1"
|
||||
Margin="-16,0,-16,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
Opacity="0.8" />
|
||||
<ItemsControl
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<styles:KeyVisual
|
||||
HorizontalAlignment="Left"
|
||||
Content="{Binding}"
|
||||
VisualType="SmallOutline" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock VerticalAlignment="Center" Text="WindowsTerminal.exe" />
|
||||
<FontIcon
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock>
|
||||
<Run FontWeight="SemiBold" Text="Arguments:" />
|
||||
<Run Text="-config json" />
|
||||
<LineBreak />
|
||||
<Run FontWeight="SemiBold" Text="Starting directory:" />
|
||||
<Run Text="C:\Users\Dev\PowerToys\bin" />
|
||||
<LineBreak />
|
||||
<Run FontWeight="SemiBold" Text="Launch state:" />
|
||||
<Run Text="Minimized" />
|
||||
</TextBlock>
|
||||
</ToolTipService.ToolTip>
|
||||
</FontIcon>
|
||||
</StackPanel>
|
||||
<ToggleSwitch
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,0,-112,0"
|
||||
HorizontalAlignment="Right"
|
||||
IsOn="True"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentDialog
|
||||
x:Name="KeyDialog"
|
||||
Title="New program shortcut"
|
||||
Width="480"
|
||||
MinWidth="600"
|
||||
MaxWidth="600"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}"
|
||||
PrimaryButtonText="Save"
|
||||
SecondaryButtonText="Cancel">
|
||||
<StackPanel Width="480" Orientation="Vertical">
|
||||
<TextBlock Margin="0,12,0,8" Text="Shortcut" />
|
||||
<ToggleButton
|
||||
x:Name="OriginalToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<styles:KeyVisual Content="Win (Left)" VisualType="SmallOutline" />
|
||||
<styles:KeyVisual Content="T" VisualType="SmallOutline" />
|
||||
</StackPanel>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<Grid
|
||||
Margin="0,24,0,0"
|
||||
ColumnSpacing="4"
|
||||
RowSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox Header="Application" Text="WindowsTerminal.exe" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Height="32"
|
||||
VerticalAlignment="Bottom">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</Button>
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Header="Arguments"
|
||||
Text="-config" />
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Header="Start in directory"
|
||||
Text="C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.21.10351.0_x64__8wekyb3d8bbwe" />
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Height="32"
|
||||
VerticalAlignment="Bottom">
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</Button>
|
||||
<ComboBox
|
||||
Grid.Row="3"
|
||||
Header="Launch state"
|
||||
SelectedIndex="0">
|
||||
<ComboBoxItem Content="Show window" />
|
||||
<ComboBoxItem Content="Minimized" />
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Pages
|
||||
{
|
||||
public sealed partial class Programs : Page
|
||||
{
|
||||
public ObservableCollection<URLShortcut> Shortcuts { get; set; }
|
||||
|
||||
public Programs()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
Shortcuts = new ObservableCollection<URLShortcut>();
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win (Left)", "T", }, URL = "https://www.bing.com" });
|
||||
}
|
||||
|
||||
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="KeyboardManagerEditorUI.Pages.Shortcuts"
|
||||
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:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:styles="using:KeyboardManagerEditorUI.Styles"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Padding="16">
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Remappings allow you to reconfigure a single key or shortcut to a new key or shortcut"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="NewShortcutBtn"
|
||||
Height="36"
|
||||
Margin="0,12,0,0"
|
||||
Click="NewShortcutBtn_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock VerticalAlignment="Center" Text="New" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="16,-2,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Original key(s)" />
|
||||
<AppBarSeparator
|
||||
Grid.Column="1"
|
||||
Margin="-6,4,0,4"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="12,-2,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="New key(s)" />
|
||||
<AppBarSeparator
|
||||
Grid.Column="2"
|
||||
Margin="-6,4,0,4"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="12,-2,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Applicable apps" />
|
||||
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="4"
|
||||
Height="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="4"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_ItemClick"
|
||||
ItemsSource="{x:Bind RemappedShortcuts}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:Remapping">
|
||||
<Grid Height="48">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" MinWidth="232" />
|
||||
<ColumnDefinition Width="Auto" MinWidth="238" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="5"
|
||||
Height="1"
|
||||
Margin="-16,0,-16,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
Opacity="0.8" />
|
||||
|
||||
<ItemsControl
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind OriginalKeys}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<styles:KeyVisual
|
||||
HorizontalAlignment="Left"
|
||||
Content="{Binding}"
|
||||
VisualType="SmallOutline" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<ItemsControl
|
||||
Grid.Column="1"
|
||||
Margin="0,6,0,6"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}"
|
||||
ItemsSource="{x:Bind RemappedKeys}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<styles:KeyVisual Content="{Binding}" VisualType="Small" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="-4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Text="{x:Bind AppName}" />
|
||||
<ToggleSwitch
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,0,-112,0"
|
||||
HorizontalAlignment="Right"
|
||||
IsOn="{x:Bind IsEnabled, Mode=TwoWay}"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentDialog
|
||||
x:Name="KeyDialog"
|
||||
Title="Remap a shortcut"
|
||||
MinWidth="600"
|
||||
MaxWidth="600"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}"
|
||||
PrimaryButtonText="Save"
|
||||
SecondaryButtonText="Cancel">
|
||||
<Grid>
|
||||
<styles:InputControl x:Name="ShortcutControl" />
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Pages
|
||||
{
|
||||
public sealed partial class Shortcuts : Page, IDisposable
|
||||
{
|
||||
private KeyboardMappingService? _mappingService;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public ObservableCollection<KeyMapping> SingleKeyMappings { get; } = new ObservableCollection<KeyMapping>();
|
||||
|
||||
public ObservableCollection<ShortcutKeyMapping> ShortcutMappings { get; } = new ObservableCollection<ShortcutKeyMapping>();
|
||||
|
||||
public ObservableCollection<Remapping> RemappedShortcuts { get; set; }
|
||||
|
||||
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||
|
||||
public Shortcuts()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
RemappedShortcuts = new ObservableCollection<Remapping>();
|
||||
_mappingService = new KeyboardMappingService();
|
||||
LoadMappings();
|
||||
|
||||
/*
|
||||
RemappedShortcuts = new ObservableCollection<Remapping>();
|
||||
RemappedShortcuts.Add(new Remapping() { OriginalKeys = new List<string>() { "Ctrl", "Shift", "F" }, RemappedKeys = new List<string>() { "Shift", "F" }, IsAllApps = true });
|
||||
RemappedShortcuts.Add(new Remapping() { OriginalKeys = new List<string>() { "Ctrl (Left)" }, RemappedKeys = new List<string>() { "Ctrl (Right)" }, IsAllApps = true });
|
||||
RemappedShortcuts.Add(new Remapping() { OriginalKeys = new List<string>() { "Shift", "M" }, RemappedKeys = new List<string>() { "Ctrl", "M" }, IsAllApps = true });
|
||||
RemappedShortcuts.Add(new Remapping() { OriginalKeys = new List<string>() { "Shift", "Alt", "B" }, RemappedKeys = new List<string>() { "Alt", "B" }, IsAllApps = false, AppName = "outlook.exe" });
|
||||
RemappedShortcuts.Add(new Remapping() { OriginalKeys = new List<string>() { "Numpad 1" }, RemappedKeys = new List<string>() { "Ctrl", "F" }, IsAllApps = true });
|
||||
RemappedShortcuts.Add(new Remapping() { OriginalKeys = new List<string>() { "Numpad 2" }, RemappedKeys = new List<string>() { "Alt", "F" }, IsAllApps = true, AppName = "outlook.exe" });
|
||||
*/
|
||||
}
|
||||
|
||||
private void LoadMappings()
|
||||
{
|
||||
if (_mappingService == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SingleKeyMappings.Clear();
|
||||
ShortcutMappings.Clear();
|
||||
RemappedShortcuts.Clear();
|
||||
|
||||
foreach (var mapping in _mappingService.GetSingleKeyMappings())
|
||||
{
|
||||
SingleKeyMappings.Add(mapping);
|
||||
|
||||
string[] targetKeyCode = mapping.TargetKey.Split(';');
|
||||
var targetKeyNames = new List<string>();
|
||||
|
||||
foreach (var keyCode in targetKeyCode)
|
||||
{
|
||||
if (int.TryParse(keyCode, out int code))
|
||||
{
|
||||
targetKeyNames.Add(GetKeyDisplayName(code));
|
||||
}
|
||||
}
|
||||
|
||||
RemappedShortcuts.Add(new Remapping
|
||||
{
|
||||
OriginalKeys = new List<string> { GetKeyDisplayName(mapping.OriginalKey) },
|
||||
RemappedKeys = targetKeyNames,
|
||||
IsAllApps = true,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut))
|
||||
{
|
||||
ShortcutMappings.Add(mapping);
|
||||
|
||||
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
|
||||
string[] targetKeyCodes = mapping.TargetKeys.Split(';');
|
||||
|
||||
var originalKeyNames = new List<string>();
|
||||
var targetKeyNames = new List<string>();
|
||||
|
||||
foreach (var keyCode in originalKeyCodes)
|
||||
{
|
||||
if (int.TryParse(keyCode, out int code))
|
||||
{
|
||||
originalKeyNames.Add(GetKeyDisplayName(code));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var keyCode in targetKeyCodes)
|
||||
{
|
||||
if (int.TryParse(keyCode, out int code))
|
||||
{
|
||||
targetKeyNames.Add(GetKeyDisplayName(code));
|
||||
}
|
||||
}
|
||||
|
||||
RemappedShortcuts.Add(new Remapping
|
||||
{
|
||||
OriginalKeys = originalKeyNames,
|
||||
RemappedKeys = targetKeyNames,
|
||||
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||
AppName = string.IsNullOrEmpty(mapping.TargetApp) ? "All Apps" : mapping.TargetApp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetKeyDisplayName(int keyCode)
|
||||
{
|
||||
var keyName = new StringBuilder(64);
|
||||
GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
|
||||
return keyName.ToString();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Dispose managed resources
|
||||
_mappingService?.Dispose();
|
||||
_mappingService = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ShortcutControl.SetOriginalKeys(new List<string>());
|
||||
ShortcutControl.SetRemappedKeys(new List<string>());
|
||||
ShortcutControl.SetApp(false, string.Empty);
|
||||
|
||||
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
|
||||
await KeyDialog.ShowAsync();
|
||||
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
|
||||
}
|
||||
|
||||
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
SaveCurrentMapping();
|
||||
LoadMappings();
|
||||
}
|
||||
|
||||
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is Remapping selectedShortcut)
|
||||
{
|
||||
ShortcutControl.SetOriginalKeys(selectedShortcut.OriginalKeys);
|
||||
ShortcutControl.SetRemappedKeys(selectedShortcut.RemappedKeys);
|
||||
ShortcutControl.SetApp(!selectedShortcut.IsAllApps, selectedShortcut.AppName);
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetKeyCode(string keyName)
|
||||
{
|
||||
return KeyboardManagerInterop.GetKeyCodeFromName(keyName);
|
||||
}
|
||||
|
||||
private void SaveCurrentMapping()
|
||||
{
|
||||
if (_mappingService == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<string> originalKeys = ShortcutControl.GetOriginalKeys();
|
||||
List<string> remappedKeys = ShortcutControl.GetRemappedKeys();
|
||||
bool isAppSpecific = ShortcutControl.GetIsAppSpecific();
|
||||
string appName = ShortcutControl.GetAppName();
|
||||
|
||||
// mock data
|
||||
// originalKeys = ["A", "Ctrl"];
|
||||
// remappedKeys = ["B"];
|
||||
if (originalKeys == null || originalKeys.Count == 0 || remappedKeys == null || remappedKeys.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalKeys.Count == 1)
|
||||
{
|
||||
int originalKey = GetKeyCode(originalKeys[0]);
|
||||
if (originalKey != 0)
|
||||
{
|
||||
if (remappedKeys.Count == 1)
|
||||
{
|
||||
int targetKey = GetKeyCode(remappedKeys[0]);
|
||||
if (targetKey != 0)
|
||||
{
|
||||
_mappingService.AddSingleKeyMapping(originalKey, targetKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string targetKeys = string.Join(";", remappedKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture)));
|
||||
_mappingService.AddSingleKeyMapping(originalKey, targetKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string originalKeysString = string.Join(";", originalKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture)));
|
||||
string targetKeysString = string.Join(";", remappedKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
if (isAppSpecific && !string.IsNullOrEmpty(appName))
|
||||
{
|
||||
_mappingService.AddShortcutMapping(originalKeysString, targetKeysString, appName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mappingService.AddShortcutMapping(originalKeysString, targetKeysString);
|
||||
}
|
||||
}
|
||||
|
||||
_mappingService.SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error saving shortcut mapping: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="KeyboardManagerEditorUI.Pages.Text"
|
||||
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:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:styles="using:KeyboardManagerEditorUI.Styles"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Padding="16">
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Text shortcuts allow you to insert text in any inputfield when you use the configured text"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="NewShortcutBtn"
|
||||
Height="36"
|
||||
Margin="0,12,0,0"
|
||||
Click="NewShortcutBtn_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock VerticalAlignment="Center" Text="New" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="16,-2,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Shortcut" />
|
||||
<AppBarSeparator
|
||||
Grid.Column="1"
|
||||
Margin="-6,4,0,4"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="12,-2,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Text" />
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="4"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_ItemClick"
|
||||
ItemsSource="{x:Bind Shortcuts}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:URLShortcut">
|
||||
<Grid Height="48">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="232" />
|
||||
<ColumnDefinition Width="238" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="5"
|
||||
Height="1"
|
||||
Margin="-16,0,-16,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
Opacity="0.8" />
|
||||
<ItemsControl
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<styles:KeyVisual
|
||||
HorizontalAlignment="Left"
|
||||
Content="{Binding}"
|
||||
VisualType="SmallOutline" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="12"
|
||||
Text="{x:Bind URL}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<ToggleSwitch
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,0,-112,0"
|
||||
HorizontalAlignment="Right"
|
||||
IsOn="True"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentDialog
|
||||
x:Name="KeyDialog"
|
||||
Title="New text shortcut"
|
||||
Width="480"
|
||||
Height="360"
|
||||
MinWidth="600"
|
||||
MaxWidth="600"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}"
|
||||
PrimaryButtonText="Save"
|
||||
SecondaryButtonText="Cancel">
|
||||
<StackPanel
|
||||
Width="360"
|
||||
Height="360"
|
||||
Orientation="Vertical">
|
||||
<TextBlock Margin="0,12,0,8" Text="Shortcut" />
|
||||
<ToggleButton
|
||||
x:Name="OriginalToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<styles:KeyVisual Content="Shift" VisualType="SmallOutline" />
|
||||
<styles:KeyVisual Content="Win" VisualType="SmallOutline" />
|
||||
<styles:KeyVisual Content="M" VisualType="SmallOutline" />
|
||||
</StackPanel>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<TextBox
|
||||
Height="200"
|
||||
Margin="0,24,0,0"
|
||||
Header="Text to insert"
|
||||
Text="https://www.microsoft.com"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class Text : Page
|
||||
{
|
||||
public ObservableCollection<URLShortcut> Shortcuts { get; set; }
|
||||
|
||||
public Text()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
Shortcuts = new ObservableCollection<URLShortcut>();
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Alt (Left)", "H" }, URL = "Hello" });
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win (Left)", "P", }, URL = "PowerToys" });
|
||||
}
|
||||
|
||||
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="KeyboardManagerEditorUI.Pages.URLs"
|
||||
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:helper="using:KeyboardManagerEditorUI.Helpers"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Pages"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:styles="using:KeyboardManagerEditorUI.Styles"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Padding="16">
|
||||
<StackPanel Orientation="Vertical" Spacing="12">
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="URL shortcuts allow you to open a URL when you use the configured shortcut"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="NewShortcutBtn"
|
||||
Height="36"
|
||||
Margin="0,12,0,0"
|
||||
Click="NewShortcutBtn_Click">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock VerticalAlignment="Center" Text="New" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="236" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="16,-2,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="Shortcut" />
|
||||
<AppBarSeparator
|
||||
Grid.Column="1"
|
||||
Margin="-6,4,0,4"
|
||||
HorizontalAlignment="Left" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="12,-2,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="URL" />
|
||||
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="4"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_ItemClick"
|
||||
ItemsSource="{x:Bind Shortcuts}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="helper:URLShortcut">
|
||||
<Grid Height="48">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="232" />
|
||||
<ColumnDefinition Width="238" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="84" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
Grid.ColumnSpan="5"
|
||||
Height="1"
|
||||
Margin="-16,0,-16,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
Opacity="0.8" />
|
||||
<ItemsControl
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind Shortcut}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<styles:KeyVisual
|
||||
HorizontalAlignment="Left"
|
||||
Content="{Binding}"
|
||||
VisualType="SmallOutline" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<HyperlinkButton
|
||||
Grid.Column="1"
|
||||
Margin="-12,0,0,0"
|
||||
Content="{x:Bind URL}" />
|
||||
<ToggleSwitch
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,0,-112,0"
|
||||
HorizontalAlignment="Right"
|
||||
IsOn="True"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentDialog
|
||||
x:Name="KeyDialog"
|
||||
Title="New URL shortcut"
|
||||
Width="480"
|
||||
Height="360"
|
||||
MinWidth="600"
|
||||
MaxWidth="600"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}"
|
||||
PrimaryButtonText="Save"
|
||||
SecondaryButtonText="Cancel">
|
||||
<StackPanel
|
||||
Width="320"
|
||||
Height="264"
|
||||
Orientation="Vertical">
|
||||
<TextBlock Margin="0,12,0,8" Text="Shortcut" />
|
||||
<ToggleButton
|
||||
x:Name="OriginalToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<styles:KeyVisual Content="Win (Left)" VisualType="SmallOutline" />
|
||||
<styles:KeyVisual Content="B" VisualType="SmallOutline" />
|
||||
</StackPanel>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<TextBox
|
||||
Margin="0,24,0,0"
|
||||
Header="URL to open"
|
||||
Text="https://www.bing.com" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using KeyboardManagerEditorUI.Helpers;
|
||||
using KeyboardManagerEditorUI.Interop;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Pages
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class URLs : Page, IDisposable
|
||||
{
|
||||
private KeyboardMappingService? _mappingService;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public ObservableCollection<URLShortcut> Shortcuts { get; set; }
|
||||
|
||||
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||
|
||||
public URLs()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
Shortcuts = new ObservableCollection<URLShortcut>();
|
||||
|
||||
_mappingService = new KeyboardMappingService();
|
||||
|
||||
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.OpenUri))
|
||||
{
|
||||
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
|
||||
var originalKeyNames = new List<string>();
|
||||
foreach (var keyCode in originalKeyCodes)
|
||||
{
|
||||
if (int.TryParse(keyCode, out int code))
|
||||
{
|
||||
originalKeyNames.Add(GetKeyDisplayName(code));
|
||||
}
|
||||
}
|
||||
|
||||
var shortcut = new URLShortcut
|
||||
{
|
||||
Shortcut = originalKeyNames,
|
||||
URL = mapping.UriToOpen,
|
||||
};
|
||||
Shortcuts.Add(shortcut);
|
||||
}
|
||||
|
||||
/*
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "https://www.microsoft.com" });
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "P", }, URL = "https://www.bing.com" });
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "https://www.windows.com" });
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "U", }, URL = "https://www.bing.com" });
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Ctrl", "P" }, URL = "https://www.surface.com" });
|
||||
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Alt", "Ctrl", "Shift" }, URL = "https://www.bing.com" });
|
||||
*/
|
||||
}
|
||||
|
||||
public static string GetKeyDisplayName(int keyCode)
|
||||
{
|
||||
var keyName = new StringBuilder(64);
|
||||
GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
|
||||
return keyName.ToString();
|
||||
}
|
||||
|
||||
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
await KeyDialog.ShowAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Dispose managed resources
|
||||
_mappingService?.Dispose();
|
||||
_mappingService = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Styles">
|
||||
|
||||
<x:Double x:Key="ContentDialogMaxWidth">960</x:Double>
|
||||
|
||||
<Style x:Key="CustomShortcutToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="{ThemeResource ToggleButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ToggleButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ToggleButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1,1,1,1" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Checked">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1,1,1,4" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1,1,1,4" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1,1,1,4" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Indeterminate">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminatePointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminatePressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminateDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="KeyboardManagerEditorUI.Styles.InputControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:KeyboardManagerEditorUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Styles"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tk7controls="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="240" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="240" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Margin="0,12,0,0" Text="Original key(s)" />
|
||||
<Grid Grid.Row="1" Margin="0,8,0,0">
|
||||
<ToggleButton
|
||||
x:Name="OriginalToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Checked="OriginalToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="OriginalKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual Content="{Binding}" VisualType="SmallOutline" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Margin="0,8,0,0">
|
||||
<StackPanel
|
||||
Name="KeyStackPanel" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="24,0,24,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Text="" />
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="0,8,0,0">
|
||||
<ToggleButton
|
||||
x:Name="RemappedToggleBtn"
|
||||
Padding="0,24,0,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Checked="RemappedToggleBtn_Checked"
|
||||
Style="{StaticResource CustomShortcutToggleButtonStyle}">
|
||||
<ToggleButton.Content>
|
||||
<ItemsControl x:Name="RemappedKeys">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:KeyVisual Content="{Binding}" VisualType="Small" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="2" Margin="0,8,0,0">
|
||||
<StackPanel
|
||||
Name="NewKeyStackPanel" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<CheckBox
|
||||
x:Name="AllAppsCheckBox"
|
||||
Margin="0,24,0,12"
|
||||
Content="Only apply this remapping to a specific application"
|
||||
IsChecked="True" />
|
||||
<TextBox
|
||||
x:Name="AppNameTextBox"
|
||||
Header="Application name"
|
||||
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}"
|
||||
PlaceholderText="e.g.: outlook.exe" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Styles
|
||||
{
|
||||
public sealed partial class InputControl : UserControl
|
||||
{
|
||||
private List<string> pressedKeys = new List<string>();
|
||||
private List<string> newPressedKeys = new List<string>();
|
||||
|
||||
// Define newMode as a DependencyProperty for binding
|
||||
public static readonly DependencyProperty NewModeProperty =
|
||||
DependencyProperty.Register(
|
||||
"NewMode",
|
||||
typeof(bool),
|
||||
typeof(InputControl),
|
||||
new PropertyMetadata(false, OnNewModeChanged));
|
||||
|
||||
public bool NewMode
|
||||
{
|
||||
get { return (bool)GetValue(NewModeProperty); }
|
||||
set { SetValue(NewModeProperty, value); }
|
||||
}
|
||||
|
||||
public InputControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.KeyDown += (sender, e) => InputControl_KeyDown(sender, e, NewMode);
|
||||
this.KeyUp += (sender, e) => InputControl_KeyUp(sender, e, NewMode);
|
||||
}
|
||||
|
||||
private static void OnNewModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var control = d as InputControl;
|
||||
if (control != null)
|
||||
{
|
||||
bool newMode = (bool)e.NewValue;
|
||||
control.UpdateKeyDisplay(newMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void InputControl_KeyDown(object sender, KeyRoutedEventArgs e, bool newMode)
|
||||
{
|
||||
// Get the key name and add it to the list if it's not already there
|
||||
string keyName = e.Key.ToString();
|
||||
keyName = NormalizeKeyName(keyName);
|
||||
|
||||
var currentKeyList = newMode ? newPressedKeys : pressedKeys;
|
||||
|
||||
if (!currentKeyList.Contains(keyName))
|
||||
{
|
||||
var allowedModifiers = new[] { "Shift", "Ctrl", "LWin", "RWin", "Alt" };
|
||||
if (currentKeyList.All(k => allowedModifiers.Contains(k)) && pressedKeys.Count < 4)
|
||||
{
|
||||
currentKeyList.Add(keyName);
|
||||
UpdateKeyDisplay(newMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InputControl_KeyUp(object sender, KeyRoutedEventArgs e, bool newMode)
|
||||
{
|
||||
// Console.WriteLine(newMode);
|
||||
string keyName = e.Key.ToString();
|
||||
var currentKeyList = newMode ? newPressedKeys : pressedKeys;
|
||||
|
||||
if (!currentKeyList.Contains(keyName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the key name from the list when the key is released
|
||||
// currentKeyList.Remove(keyName);
|
||||
UpdateKeyDisplay(newMode);
|
||||
}
|
||||
|
||||
private void UpdateKeyDisplay(bool newMode)
|
||||
{
|
||||
// Clear current UI elements
|
||||
if (newMode)
|
||||
{
|
||||
NewKeyStackPanel.Children.Clear();
|
||||
} // Assuming keyPanel2 is your second stack panel
|
||||
else
|
||||
{
|
||||
KeyStackPanel.Children.Clear();
|
||||
}
|
||||
|
||||
var currentKeyList = newMode ? newPressedKeys : pressedKeys;
|
||||
|
||||
// Add each pressed key as a TextBlock in the StackPanel
|
||||
foreach (var key in currentKeyList)
|
||||
{
|
||||
Border keyBlockContainer = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Microsoft.UI.Colors.White),
|
||||
Padding = new Thickness(10),
|
||||
Margin = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(3),
|
||||
BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.Black),
|
||||
BorderThickness = new Thickness(1),
|
||||
};
|
||||
|
||||
TextBlock keyBlock = new TextBlock
|
||||
{
|
||||
Text = key,
|
||||
FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Foreground = new SolidColorBrush(Microsoft.UI.Colors.Black),
|
||||
};
|
||||
|
||||
// Add TextBlock inside the Border container
|
||||
keyBlockContainer.Child = keyBlock;
|
||||
|
||||
// Add Border to StackPanel
|
||||
if (newMode)
|
||||
{
|
||||
keyBlockContainer.Background = Application.Current.Resources["AccentButtonBackgroundPressed"] as SolidColorBrush;
|
||||
keyBlockContainer.BorderBrush = Application.Current.Resources["AccentButtonBackgroundPressed"] as SolidColorBrush;
|
||||
keyBlock.Foreground = new SolidColorBrush(Microsoft.UI.Colors.White);
|
||||
NewKeyStackPanel.Children.Add(keyBlockContainer); // For remapping keys
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyStackPanel.Children.Add(keyBlockContainer); // For normal keys
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRemappedKeys(List<string> keys)
|
||||
{
|
||||
RemappedKeys.ItemsSource = keys;
|
||||
}
|
||||
|
||||
public void SetOriginalKeys(List<string> keys)
|
||||
{
|
||||
OriginalKeys.ItemsSource = keys;
|
||||
}
|
||||
|
||||
public List<string> GetOriginalKeys()
|
||||
{
|
||||
return pressedKeys as List<string> ?? new List<string>();
|
||||
}
|
||||
|
||||
public List<string> GetRemappedKeys()
|
||||
{
|
||||
return newPressedKeys as List<string> ?? new List<string>();
|
||||
}
|
||||
|
||||
public bool GetIsAppSpecific()
|
||||
{
|
||||
return AllAppsCheckBox.IsChecked ?? false;
|
||||
}
|
||||
|
||||
public string GetAppName()
|
||||
{
|
||||
return AppNameTextBox.Text ?? string.Empty;
|
||||
}
|
||||
|
||||
private void RemappedToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NewMode = true;
|
||||
RemappedToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
private void OriginalToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NewMode = false;
|
||||
OriginalToggleBtn.IsChecked = false;
|
||||
}
|
||||
|
||||
public void SetApp(bool isSpecificApp, string appName)
|
||||
{
|
||||
if (isSpecificApp)
|
||||
{
|
||||
AllAppsCheckBox.IsChecked = true;
|
||||
AppNameTextBox.Text = appName;
|
||||
AppNameTextBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
AllAppsCheckBox.IsChecked = false;
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void AllAppsCheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppNameTextBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void AllAppsCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AllAppsCheckBox.Checked += AllAppsCheckBox_Checked;
|
||||
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Unchecked;
|
||||
}
|
||||
|
||||
private string NormalizeKeyName(string keyName)
|
||||
{
|
||||
switch (keyName)
|
||||
{
|
||||
case "Control":
|
||||
return "Ctrl";
|
||||
case "Menu":
|
||||
return "Alt";
|
||||
case "LeftWindows":
|
||||
return "LWin";
|
||||
case "RightWindows":
|
||||
return "RWin";
|
||||
default:
|
||||
return keyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// 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 System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.System;
|
||||
|
||||
namespace KeyboardManagerEditorUI.Styles
|
||||
{
|
||||
[TemplatePart(Name = KeyPresenter, Type = typeof(ContentPresenter))]
|
||||
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = "Default", GroupName = "StateStates")]
|
||||
[TemplateVisualState(Name = "Error", GroupName = "StateStates")]
|
||||
public sealed partial class KeyVisual : Control
|
||||
{
|
||||
private const string KeyPresenter = "KeyPresenter";
|
||||
private KeyVisual? _keyVisual;
|
||||
private ContentPresenter? _keyPresenter;
|
||||
|
||||
public object Content
|
||||
{
|
||||
get => (object)GetValue(ContentProperty);
|
||||
set => SetValue(ContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
|
||||
|
||||
public VisualType VisualType
|
||||
{
|
||||
get => (VisualType)GetValue(VisualTypeProperty);
|
||||
set => SetValue(VisualTypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty VisualTypeProperty = DependencyProperty.Register("VisualType", typeof(VisualType), typeof(KeyVisual), new PropertyMetadata(default(VisualType), OnSizeChanged));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool)GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register("IsError", typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsErrorChanged));
|
||||
|
||||
public KeyVisual()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(KeyVisual);
|
||||
this.Style = GetStyleSize("TextKeyVisualStyle");
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
IsEnabledChanged -= KeyVisual_IsEnabledChanged;
|
||||
_keyVisual = (KeyVisual)this;
|
||||
_keyPresenter = (ContentPresenter)_keyVisual.GetTemplateChild(KeyPresenter);
|
||||
Update();
|
||||
SetEnabledState();
|
||||
SetErrorState();
|
||||
IsEnabledChanged += KeyVisual_IsEnabledChanged;
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((KeyVisual)d).Update();
|
||||
}
|
||||
|
||||
private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((KeyVisual)d).Update();
|
||||
}
|
||||
|
||||
private static void OnIsErrorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((KeyVisual)d).SetErrorState();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_keyVisual == null || _keyVisual._keyPresenter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_keyVisual.Content != null)
|
||||
{
|
||||
if (_keyVisual.Content.GetType() == typeof(string))
|
||||
{
|
||||
_keyVisual.Style = GetStyleSize("TextKeyVisualStyle");
|
||||
_keyVisual._keyPresenter.Content = _keyVisual.Content;
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyVisual.Style = GetStyleSize("IconKeyVisualStyle");
|
||||
|
||||
switch ((int)_keyVisual.Content)
|
||||
{
|
||||
/* We can enable other glyphs in the future
|
||||
case 13: // The Enter key or button.
|
||||
_keyVisual._keyPresenter.Content = "\uE751"; break;
|
||||
|
||||
case 8: // The Back key or button.
|
||||
_keyVisual._keyPresenter.Content = "\uE750"; break;
|
||||
|
||||
case 16: // The right Shift key or button.
|
||||
case 160: // The left Shift key or button.
|
||||
case 161: // The Shift key or button.
|
||||
_keyVisual._keyPresenter.Content = "\uE752"; break; */
|
||||
|
||||
case 38: _keyVisual._keyPresenter.Content = "\uE0E4"; break; // The Up Arrow key or button.
|
||||
case 40: _keyVisual._keyPresenter.Content = "\uE0E5"; break; // The Down Arrow key or button.
|
||||
case 37: _keyVisual._keyPresenter.Content = "\uE0E2"; break; // The Left Arrow key or button.
|
||||
case 39: _keyVisual._keyPresenter.Content = "\uE0E3"; break; // The Right Arrow key or button.
|
||||
|
||||
case 91: // The left Windows key
|
||||
case 92: // The right Windows key
|
||||
PathIcon winIcon = (PathIcon)XamlReader.Load(@"<PathIcon xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Data=""M9,17V9h8v8ZM0,17V9H8v8ZM9,8V0h8V8ZM0,8V0H8V8Z"" />") as PathIcon;
|
||||
Viewbox winIconContainer = new Viewbox();
|
||||
winIconContainer.Child = winIcon;
|
||||
winIconContainer.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
winIconContainer.VerticalAlignment = VerticalAlignment.Center;
|
||||
|
||||
double iconDimensions = GetIconSize();
|
||||
winIconContainer.Height = iconDimensions;
|
||||
winIconContainer.Width = iconDimensions;
|
||||
_keyVisual._keyPresenter.Content = winIconContainer;
|
||||
break;
|
||||
default: _keyVisual._keyPresenter.Content = ((VirtualKey)_keyVisual.Content).ToString(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Style GetStyleSize(string styleName)
|
||||
{
|
||||
if (VisualType == VisualType.Small)
|
||||
{
|
||||
return (Style)App.Current.Resources["Small" + styleName];
|
||||
}
|
||||
else if (VisualType == VisualType.SmallOutline)
|
||||
{
|
||||
return (Style)App.Current.Resources["SmallOutline" + styleName];
|
||||
}
|
||||
else if (VisualType == VisualType.LargeOutline)
|
||||
{
|
||||
return (Style)App.Current.Resources["LargeOutline" + styleName];
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Style)App.Current.Resources["Default" + styleName];
|
||||
}
|
||||
}
|
||||
|
||||
public double GetIconSize()
|
||||
{
|
||||
if (VisualType == VisualType.Small || VisualType == VisualType.SmallOutline)
|
||||
{
|
||||
return (double)App.Current.Resources["SmallIconSize"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return (double)App.Current.Resources["DefaultIconSize"];
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
SetEnabledState();
|
||||
}
|
||||
|
||||
private void SetErrorState()
|
||||
{
|
||||
VisualStateManager.GoToState(this, IsError ? "Error" : "Default", true);
|
||||
}
|
||||
|
||||
private void SetEnabledState()
|
||||
{
|
||||
VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
|
||||
}
|
||||
}
|
||||
|
||||
public enum VisualType
|
||||
{
|
||||
Small,
|
||||
SmallOutline,
|
||||
Large,
|
||||
LargeOutline,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:KeyboardManagerEditorUI.Styles">
|
||||
|
||||
<x:Double x:Key="DefaultIconSize">16</x:Double>
|
||||
<x:Double x:Key="SmallIconSize">12</x:Double>
|
||||
<Style x:Key="DefaultTextKeyVisualStyle" TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="32" />
|
||||
<Setter Property="MinHeight" Value="32" />
|
||||
<Setter Property="Background" Value="{ThemeResource AccentButtonBackgroundPressed}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="4,0,0,4" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:KeyVisual">
|
||||
<Grid>
|
||||
<Grid Height="{TemplateBinding MinHeight}">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4" />
|
||||
<Rectangle
|
||||
x:Name="ContentHolder"
|
||||
Fill="{TemplateBinding Background}"
|
||||
RadiusX="4"
|
||||
RadiusY="4" />
|
||||
<ContentPresenter
|
||||
x:Name="KeyPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Content}"
|
||||
FontSize="12"
|
||||
FontWeight="Normal"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
</Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentHolder.Fill" Value="{ThemeResource AccentButtonBackgroundDisabled}" />
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource AccentButtonForegroundDisabled}" />
|
||||
<Setter Target="ContentHolder.Stroke" Value="{ThemeResource AccentButtonBorderBrushDisabled}" />
|
||||
<!--<Setter Target="ContentHolder.StrokeThickness" Value="{TemplateBinding BorderThickness}" />-->
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="StateStates">
|
||||
<VisualState x:Name="Default" />
|
||||
<VisualState x:Name="Error">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentHolder.Fill" Value="{ThemeResource InfoBarErrorSeverityBackgroundBrush}" />
|
||||
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconBackground}" />
|
||||
<Setter Target="ContentHolder.Stroke" Value="{ThemeResource InfoBarErrorSeverityIconBackground}" />
|
||||
<Setter Target="ContentHolder.StrokeThickness" Value="2" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SmallTextKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="40" />
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="12,0,12,2" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="LargeOutlineTextKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrongStrokeColorDefaultBrush}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SmallOutlineTextKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="40" />
|
||||
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrongStrokeColorDefaultBrush}" />
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="BorderThickness" Value="1,1,1,1" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="8,0,8,2" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style
|
||||
x:Key="DefaultIconKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="56" />
|
||||
<Setter Property="MinHeight" Value="48" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<Setter Property="Padding" Value="16,8,16,8" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SmallIconKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="40" />
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SmallOutlineIconKeyVisualStyle"
|
||||
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
|
||||
TargetType="local:KeyVisual">
|
||||
<Setter Property="MinWidth" Value="40" />
|
||||
<Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="FontSize" Value="9" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -136,25 +136,13 @@ namespace Wox.Infrastructure
|
||||
var link = new ShellLink();
|
||||
const int STGM_READ = 0;
|
||||
|
||||
// Make sure not to open exclusive handles.
|
||||
// See: https://github.com/microsoft/WSL/issues/11276
|
||||
const int STGM_SHARE_DENY_NONE = 0x00000040;
|
||||
const int STGM_TRANSACTED = 0x00010000;
|
||||
|
||||
try
|
||||
{
|
||||
((IPersistFile)link).Load(path, STGM_READ | STGM_SHARE_DENY_NONE | STGM_TRANSACTED);
|
||||
((IPersistFile)link).Load(path, STGM_READ);
|
||||
}
|
||||
catch (System.IO.FileNotFoundException ex)
|
||||
{
|
||||
Log.Exception("Path could not be retrieved " + path, ex, GetType(), path);
|
||||
Marshal.ReleaseComObject(link);
|
||||
return string.Empty;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Exception("Exception loading path " + path, ex, GetType(), path);
|
||||
Marshal.ReleaseComObject(link);
|
||||
Log.Exception("Path could not be retrieved", ex, GetType(), path);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user