Compare commits

..

4 Commits

Author SHA1 Message Date
Leilei Zhang
bdaf644f02 test sign 2026-01-19 14:29:46 +08:00
moooyo
329c8c2616 refactor(imageresizer): disable AI feature and cache functionality (#44759)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Co-authored-by: Yu Leng <yuleng@microsoft.com>
2026-01-19 10:51:10 +08:00
Niels Laute
b081e413b1 Upgrade MarkdownTextBlock (#44793)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR:

- upgrades `CommunityToolkit.WinUI.Labs.MarkdownTextBlock` to version
`0.1.260116-build.2514`. This update includes a bunch of improvements
for markdown rendering in its default config, and fixes a couple of bug
with regards to rendering large images getting clipped when resizing the
window.
- replaces an incorrect image in the `Command Palette Sample Page`
extension for the markdown + images sample.

Before vs after:

<img width="910" height="234" alt="image"
src="https://github.com/user-attachments/assets/b3dad76c-a89e-4b47-90f8-d3c64f00615f"
/>


<img width="1245" height="827" alt="image"
src="https://github.com/user-attachments/assets/00037fb5-453e-4d85-83c9-92c265b9f968"
/>



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-19 09:56:30 +08:00
leileizhang
290fa01adf [ImageResizer] Temporarily disable AI Super Resolution feature (#44768)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Temporarily disables the AI Super Resolution feature in Image Resizer
while keeping all code intact for re-enabling in a future release.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-01-17 11:28:47 +08:00
69 changed files with 114 additions and 7432 deletions

View File

@@ -105,13 +105,7 @@
"PowerToys.SvgThumbnailProvider.dll",
"PowerToys.SvgThumbnailProvider.exe",
"PowerToys.SvgThumbnailProviderCpp.dll",
"PowerToys.KeyboardManager.dll",
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
"KeyboardManagerEditorUI\\PowerToys.KeyboardManagerEditorUI.exe",
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
"PowerToys.KeyboardManagerEditorLibraryWrapper.dll",
"WinUI3Apps\\PowerToys.HostsModuleInterface.dll",
"WinUI3Apps\\PowerToys.HostsUILib.dll",
"WinUI3Apps\\PowerToys.Hosts.dll",

View File

@@ -504,6 +504,14 @@ jobs:
Remove-Item -Force -Recurse "$(JobOutputDirectory)/_appx" -ErrorAction:Ignore
displayName: Re-pack the new CmdPal package after signing
- pwsh: |
$testsPath = "$(Build.SourcesDirectory)/$(BuildPlatform)/$(BuildConfiguration)/tests"
if (Test-Path $testsPath) {
Remove-Item -Path $testsPath -Recurse -Force
Write-Host "Removed tests folder to reduce signing workload: $testsPath"
}
displayName: Remove tests folder before signing
- template: steps-esrp-signing.yml
parameters:
displayName: Sign Core PowerToys

View File

@@ -17,7 +17,6 @@
<NuGetAuditMode>direct</NuGetAuditMode>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
<PlatformTarget>$(Platform)</PlatformTarget>
<RestoreEnablePackagePruning Condition=" '$(VisualStudioVersion)' == '17.0'">false </RestoreEnablePackagePruning>
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">

View File

@@ -25,9 +25,8 @@
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.260107-build.2454" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.260116-build.2514" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />

View File

@@ -1540,7 +1540,6 @@ SOFTWARE.
- CommunityToolkit.WinUI.Converters
- CommunityToolkit.WinUI.Extensions
- CommunityToolkit.WinUI.UI.Controls.DataGrid
- CommunityToolkit.WinUI.UI.Controls.Markdown
- ControlzEx
- HelixToolkit
- HelixToolkit.Core.Wpf

View File

@@ -490,31 +490,6 @@
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngine/KeyboardManagerEngine.vcxproj" Id="ba661f5b-1d5a-4ffc-9bf1-fc39df280bdd" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManagerEngineLibrary.vcxproj" Id="e496b7fc-1e99-4bab-849b-0e8367040b02" />
</Folder>
<Folder Name="/modules/keyboardmanager/MouseUtils/">
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
</Folder>
<Folder Name="/modules/keyboardmanager/MouseUtils/Tests/">
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/keyboardmanager/Tests/">
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorTest/KeyboardManagerEditorTest.vcxproj" Id="62173d9a-6724-4c00-a1c8-fb646480a9ec" />
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj" Id="7f4b3a60-bc27-45a7-8000-68b0b6ea7466" />
@@ -718,6 +693,31 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/MouseUtils/">
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
</Folder>
<Folder Name="/modules/MouseUtils/Tests/">
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/MouseWithoutBorders/">
<Project Path="src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />

View File

@@ -69,14 +69,14 @@ internal sealed partial class SampleMarkdownImagesPage : ContentPage
### Web URL
```xml
![painting](https://i.imgur.com/93XJSNh.png)
![painting](https://raw.githubusercontent.com/microsoft/PowerToys/refs/heads/main/doc/images/overview/Original/AdvancedPaste.png)
```
![painting](https://i.imgur.com/93XJSNh.png)
![painting](https://raw.githubusercontent.com/microsoft/PowerToys/refs/heads/main/doc/images/overview/Original/AdvancedPaste.png)
```xml
![painting](https://i.imgur.com/93XJSNh.png?--x-cmdpal-fit=fit)
![painting](https://raw.githubusercontent.com/microsoft/PowerToys/refs/heads/main/doc/images/overview/Original/AdvancedPaste.png?--x-cmdpal-fit=fit)
```
![painting](https://i.imgur.com/93XJSNh.png?--x-cmdpal-fit=fit)
![painting](https://raw.githubusercontent.com/microsoft/PowerToys/refs/heads/main/doc/images/overview/Original/AdvancedPaste.png?--x-cmdpal-fit=fit)
### File URL (PNG):
```xml

View File

@@ -81,7 +81,7 @@ Result:
Result:
![painting](https://i.imgur.com/93XJSNh.png)
![painting](https://raw.githubusercontent.com/microsoft/PowerToys/refs/heads/main/doc/images/overview/Original/AdvancedPaste.png)
### Links

View File

@@ -67,12 +67,27 @@ namespace ImageResizer
// Fix for .net 3.1.19 making Image Resizer not adapt to DPI changes.
NativeMethods.SetProcessDPIAware();
// TODO: Re-enable AI Super Resolution in next release by removing this #if block
// Temporarily disable AI Super Resolution feature (hide from UI but keep code)
#if true // Set to false to re-enable AI Super Resolution
AiAvailabilityState = AiAvailabilityState.NotSupported;
ResizeBatch.SetAiSuperResolutionService(Services.NoOpAiSuperResolutionService.Instance);
// Skip AI detection mode as well
if (e?.Args?.Length > 0 && e.Args[0] == "--detect-ai")
{
Services.AiAvailabilityCacheService.SaveCache(AiAvailabilityState.NotSupported);
Environment.Exit(0);
return;
}
#else
// Check for AI detection mode (called by Runner in background)
if (e?.Args?.Length > 0 && e.Args[0] == "--detect-ai")
{
RunAiDetectionMode();
return;
}
#endif
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredImageResizerEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
{
@@ -173,31 +188,8 @@ namespace ImageResizer
/// </summary>
private static AiAvailabilityState CheckAiAvailability()
{
try
{
// Check Windows AI service model ready state
// it's so slow, why?
var readyState = Services.WinAiSuperResolutionService.GetModelReadyState();
// Map AI service state to our availability state
switch (readyState)
{
case Microsoft.Windows.AI.AIFeatureReadyState.Ready:
return AiAvailabilityState.Ready;
case Microsoft.Windows.AI.AIFeatureReadyState.NotReady:
return AiAvailabilityState.ModelNotReady;
case Microsoft.Windows.AI.AIFeatureReadyState.DisabledByUser:
case Microsoft.Windows.AI.AIFeatureReadyState.NotSupportedOnCurrentSystem:
default:
return AiAvailabilityState.NotSupported;
}
}
catch (Exception)
{
return AiAvailabilityState.NotSupported;
}
// AI feature disabled - always return NotSupported
return AiAvailabilityState.NotSupported;
}
/// <summary>

View File

@@ -39,29 +39,8 @@ namespace ImageResizer.Services
/// </summary>
public static AiAvailabilityState? LoadCache()
{
try
{
if (!File.Exists(CachePath))
{
return null;
}
var json = File.ReadAllText(CachePath);
var cache = JsonSerializer.Deserialize<AiCapabilityCache>(json);
if (!IsCacheValid(cache))
{
return null;
}
return (AiAvailabilityState)cache.State;
}
catch (Exception ex)
{
// Read failure (file locked, corrupted JSON, etc.) - return null and use fallback
Logger.LogError($"Failed to load AI cache: {ex.Message}");
return null;
}
// Cache disabled - always return null to use default value
return null;
}
/// <summary>
@@ -70,32 +49,8 @@ namespace ImageResizer.Services
/// </summary>
public static void SaveCache(AiAvailabilityState state)
{
try
{
var cache = new AiCapabilityCache
{
Version = CacheVersion,
State = (int)state,
WindowsBuild = Environment.OSVersion.Version.ToString(),
Architecture = RuntimeInformation.ProcessArchitecture.ToString(),
Timestamp = DateTime.UtcNow.ToString("o"),
};
var dir = Path.GetDirectoryName(CachePath);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
var json = JsonSerializer.Serialize(cache, SerializerOptions);
File.WriteAllText(CachePath, json);
Logger.LogInfo($"AI cache saved: {state}");
}
catch (Exception ex)
{
Logger.LogError($"Failed to save AI cache: {ex.Message}");
}
// Cache disabled - do not save anything
return;
}
/// <summary>

View File

@@ -1,735 +1,20 @@
#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 <keyboardmanager/KeyboardManagerEditorLibrary/EditorHelpers.h>
#include <common/interop/keyboard_layout.h>
extern "C"
// Test function to call the remapping helper function
bool CheckIfRemappingsAreValid()
{
void* CreateMappingConfiguration()
{
return new MappingConfiguration();
}
RemapBuffer remapBuffer;
void DestroyMappingConfiguration(void* config)
{
delete static_cast<MappingConfiguration*>(config);
}
// Mock valid key to key remappings
remapBuffer.push_back(RemapBufferRow{ RemapBufferItem({ (DWORD)0x41, (DWORD)0x42 }), std::wstring() });
remapBuffer.push_back(RemapBufferRow{ RemapBufferItem({ (DWORD)0x42, (DWORD)0x43 }), std::wstring() });
bool LoadMappingSettings(void* config)
{
return static_cast<MappingConfiguration*>(config)->LoadSettings();
}
auto result = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer);
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, SingleKeyMapping* 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);
// Remap to single key
if (kv.second.index() == 0)
{
mapping->targetKey = AllocateAndCopyString(std::to_wstring(std::get<DWORD>(kv.second)));
mapping->isShortcut = false;
}
// Remap to shortcut
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,
int operationType,
const wchar_t* appPathOrUri,
const wchar_t* args,
const wchar_t* startDirectory,
int elevation,
int ifRunningAction,
int visibility)
{
auto mappingConfig = static_cast<MappingConfiguration*>(config);
Shortcut originalShortcut(originalKeys);
KeyShortcutTextUnion targetShortcut;
switch (operationType)
{
case 1:
targetShortcut = Shortcut(targetKeys);
std::get<Shortcut>(targetShortcut).runProgramFilePath = std::wstring(appPathOrUri);
if (args)
{
std::get<Shortcut>(targetShortcut).runProgramArgs = std::wstring(args);
}
if (startDirectory)
{
std::get<Shortcut>(targetShortcut).runProgramStartInDir = std::wstring(startDirectory);
}
std::get<Shortcut>(targetShortcut).elevationLevel = static_cast<Shortcut::ElevationLevel>(elevation);
std::get<Shortcut>(targetShortcut).alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(ifRunningAction);
std::get<Shortcut>(targetShortcut).startWindowType = static_cast<Shortcut::StartWindowType>(visibility);
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
break;
case 2:
targetShortcut = Shortcut(targetKeys);
std::get<Shortcut>(targetShortcut).uriToOpen = std::wstring(appPathOrUri);
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
break;
case 3:
targetShortcut = std::wstring(targetKeys);
break;
default:
targetShortcut = Shortcut(targetKeys);
std::get<Shortcut>(targetShortcut).operationType = static_cast<Shortcut::OperationType>(operationType);
break;
}
std::wstring app(targetApp ? targetApp : L"");
if (app.empty())
{
return mappingConfig->AddOSLevelShortcut(originalShortcut, targetShortcut);
}
else
{
return mappingConfig->AddAppSpecificShortcut(app, originalShortcut, 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));
}
// Function to get the type of a key (Win, Ctrl, Alt, Shift, or Action)
int GetKeyType(int key)
{
return static_cast<int>(Helpers::GetKeyType(static_cast<DWORD>(key)));
}
// Function to check if a shortcut is illegal
bool IsShortcutIllegal(const wchar_t* shortcutKeys)
{
if (!shortcutKeys)
{
return false;
}
Shortcut shortcut(shortcutKeys);
ShortcutErrorType result = EditorHelpers::IsShortcutIllegal(shortcut);
// Return true if an error was detected (anything other than NoError)
return result != ShortcutErrorType::NoError;
}
// Function to check if two shortcuts are equal
bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort)
{
if (!lShort || !rShort)
{
return false;
}
Shortcut lhs(lShort);
Shortcut rhs(rShort);
return lhs == rhs;
}
// Function to delete a single key remapping
bool DeleteSingleKeyRemap(void* config, int originalKey)
{
auto mappingConfig = static_cast<MappingConfiguration*>(config);
// Find and delete the single key remapping
auto it = mappingConfig->singleKeyReMap.find(static_cast<DWORD>(originalKey));
if (it != mappingConfig->singleKeyReMap.end())
{
mappingConfig->singleKeyReMap.erase(it);
return true;
}
return false;
}
bool DeleteSingleKeyToTextRemap(void* config, int originalKey)
{
auto mappingConfig = static_cast<MappingConfiguration*>(config);
auto it = mappingConfig->singleKeyToTextReMap.find(originalKey);
if (it != mappingConfig->singleKeyToTextReMap.end())
{
mappingConfig->singleKeyToTextReMap.erase(it);
return true;
}
return false;
}
// Function to delete a shortcut remapping
bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp)
{
auto mappingConfig = static_cast<MappingConfiguration*>(config);
if (originalKeys == nullptr)
{
return false;
}
std::wstring appName = targetApp ? targetApp : L"";
Shortcut shortcut(originalKeys);
// Determine the type of remapping to delete based on the app name
if (appName.empty())
{
// Delete OS level shortcut mapping
auto it = mappingConfig->osLevelShortcutReMap.find(shortcut);
if (it != mappingConfig->osLevelShortcutReMap.end())
{
mappingConfig->osLevelShortcutReMap.erase(it);
return true;
}
}
else
{
// Delete app-specific shortcut mapping
auto appIt = mappingConfig->appSpecificShortcutReMap.find(appName);
if (appIt != mappingConfig->appSpecificShortcutReMap.end())
{
auto shortcutIt = appIt->second.find(shortcut);
if (shortcutIt != appIt->second.end())
{
appIt->second.erase(shortcutIt);
// If the app-specific mapping is empty, remove the app entry
if (appIt->second.empty())
{
mappingConfig->appSpecificShortcutReMap.erase(appIt);
}
return true;
}
}
}
return false;
}
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;
}

View File

@@ -4,83 +4,4 @@
#include <keyboardmanager/common/Input.h>
#include <keyboardmanager/common/MappingConfiguration.h>
struct KeyNamePair
{
int keyCode;
wchar_t keyName[64];
};
struct SingleKeyMapping
{
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, SingleKeyMapping* 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,
int operationType,
const wchar_t* appPathOrUri = nullptr,
const wchar_t* args = nullptr,
const wchar_t* startDirectory = nullptr,
int elevation = 0,
int ifRunningAction = 0,
int visibility = 0);
__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);
__declspec(dllexport) int GetKeyType(int keyCode);
__declspec(dllexport) bool IsShortcutIllegal(const wchar_t* shortcutKeys);
__declspec(dllexport) bool AreShortcutsEqual(const wchar_t* lShort, const wchar_t* rShort);
__declspec(dllexport) bool DeleteSingleKeyRemap(void* config, int originalKey);
__declspec(dllexport) bool DeleteSingleKeyToTextRemap(void* config, int originalKey);
__declspec(dllexport) bool DeleteShortcutRemap(void* config, const wchar_t* originalKeys, const wchar_t* targetApp);
}
extern "C" __declspec(dllexport) int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount);
extern "C" __declspec(dllexport) bool CheckIfRemappingsAreValid();

View File

@@ -8,13 +8,9 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="/Controls/KeyVisual/KeyVisual.xaml" />
<ResourceDictionary Source="/Controls/KeyVisual/KeyCharPresenter.xaml" />
<ResourceDictionary Source="/Styles/Button.xaml" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<x:Double x:Key="ContentDialogMaxWidth">960</x:Double>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -7,12 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Settings;
using ManagedCommon;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -34,22 +29,14 @@ namespace KeyboardManagerEditorUI
public partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
Task.Run(() =>
{
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
});
UnhandledException += App_UnhandledException;
SettingsManager.CorrelateServiceAndEditorMappings();
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
Logger.LogInfo("keyboard-manager WinUI3 editor logger is initialized");
}
/// <summary>
@@ -58,28 +45,11 @@ namespace KeyboardManagerEditorUI
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
MainWindow = new MainWindow();
MainWindow.DispatcherQueue.TryEnqueue(() =>
{
MainWindow.Activate();
MainWindow.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
(MainWindow.Content as FrameworkElement)?.UpdateLayout();
});
});
window = new MainWindow();
window.Activate();
Logger.LogInfo("keyboard-manager WinUI3 editor window is launched");
}
/// <summary>
/// Log the unhandled exception for the editor.
/// </summary>
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
Logger.LogError("Unhandled exception", e.Exception);
}
internal static MainWindow MainWindow { get; private set; } = null!;
private Window? window;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,125 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.AppPageInputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel
Width="360"
Height="600"
Orientation="Vertical"
Spacing="8">
<!-- Shortcut section -->
<TextBlock
x:Uid="AppPageInputControlShortcutTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ShortcutToggleBtn"
Padding="0,24,0,24"
HorizontalAlignment="Stretch"
Checked="ShortcutToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="ShortcutKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel
HorizontalSpacing="4"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<StackPanel
Orientation="Horizontal"
Spacing="4"
Margin="0,8,0,0">
<TextBox
x:Uid="AppPageInputControlExampleTextBox"
x:Name="ProgramPathInput"
Width="220" />
<Button
x:Name="ProgramPathSelectButton"
x:Uid="AppPageInputControlPathSelectButton"
Click="ProgramPathSelectButton_Click"
VerticalAlignment="Bottom"
Width="120"/>
</StackPanel>
<TextBlock
x:Uid="AppPageInputControlExtraOptionsTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<StackPanel
Orientation="Vertical"
Spacing="8"
Margin="0,8,0,0">
<TextBox
x:Uid="AppPageInputControlArgumentsTextBox"
x:Name="ProgramArgsInput"
Width="360" />
<StackPanel
Orientation="Horizontal"
Spacing="4">
<TextBox
x:Uid="AppPageInputControlStartInTextBox"
x:Name="StartInPathInput"
Width="220" />
<Button
x:Name="StartInSelectButton"
x:Uid="AppPageInputControlStartInSelectButton"
VerticalAlignment="Bottom"
Click="StartInSelectButton_Click"
Width="120"/>
</StackPanel>
<ComboBox
x:Name="ElevationComboBox"
x:Uid="AppPageInputControlElevationComboBox"
SelectedValue="Normal"
Width="360">
<x:String>Normal</x:String>
<x:String>Elevated</x:String>
<x:String>Different user</x:String>
</ComboBox>
<ComboBox
x:Name="IfRunningComboBox"
x:Uid="AppPageInputControlIfRunningComboBox"
SelectedValue="Show window"
Width="360">
<x:String>Show window</x:String>
<x:String>Start another</x:String>
<x:String>Do nothing</x:String>
<x:String>Close</x:String>
<x:String>End task</x:String>
</ComboBox>
<ComboBox
x:Name="VisibilityComboBox"
x:Uid="AppPageInputControlVisibilityComboBox"
SelectedValue="Normal"
Width="360">
<x:String>Normal</x:String>
<x:String>Hidden</x:String>
<x:String>Minimized</x:String>
<x:String>Maximized</x:String>
</ComboBox>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -1,253 +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.Collections.Generic;
using System.Collections.ObjectModel;
using KeyboardManagerEditorUI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.System;
using WinRT.Interop;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class AppPageInputControl : UserControl, IKeyboardHookTarget
{
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
// private bool _internalUpdate;
public AppPageInputControl()
{
this.InitializeComponent();
this.ShortcutKeys.ItemsSource = _shortcutKeys;
ShortcutToggleBtn.IsChecked = true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
KeyboardHookHelper.Instance.ActivateHook(this);
ProgramPathInput.GotFocus += ProgramInputBox_GotFocus;
ProgramArgsInput.GotFocus += InputArgs_GotFocus;
}
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ShortcutToggleBtn.IsChecked == true)
{
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
KeyboardHookHelper.Instance.CleanupHook();
}
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
_shortcutKeys.Clear();
foreach (var keyName in formattedKeys)
{
_shortcutKeys.Add(keyName);
}
}
private void ProgramInputBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clean up the keyboard hook when the text box gains focus
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
private void InputArgs_GotFocus(object sender, RoutedEventArgs e)
{
// if (_internalUpdate)
// {
// return;
// }
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
currentNotification = new TeachingTip
{
Title = "Input Limit",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
Target = ShortcutToggleBtn,
};
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
};
notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
if (notificationTimer != null)
{
notificationTimer.Stop();
notificationTimer = null;
}
if (currentNotification != null && currentNotification.IsOpen)
{
currentNotification.IsOpen = false;
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(currentNotification))
{
rootPanel.Children.Remove(currentNotification);
}
currentNotification = null;
}
}
public void ClearKeys()
{
_shortcutKeys.Clear();
}
public List<string> GetShortcutKeys()
{
List<string> keys = new List<string>();
foreach (var key in _shortcutKeys)
{
keys.Add(key);
}
return keys;
}
public string GetProgramPathContent()
{
return ProgramPathInput.Text;
}
public string GetProgramArgsContent()
{
return ProgramArgsInput.Text;
}
public string GetStartInDirectory()
{
return StartInPathInput.Text;
}
public ElevationLevel GetElevationLevel()
{
return (ElevationLevel)ElevationComboBox.SelectedIndex;
}
public StartWindowType GetVisibility()
{
return (StartWindowType)VisibilityComboBox.SelectedIndex;
}
public ProgramAlreadyRunningAction GetIfRunningAction()
{
return (ProgramAlreadyRunningAction)IfRunningComboBox.SelectedIndex;
}
public void SetShortcutKeys(List<string> keys)
{
if (keys != null)
{
_shortcutKeys.Clear();
foreach (var key in keys)
{
_shortcutKeys.Add(key);
}
}
}
public void SetProgramPathContent(string text)
{
ProgramPathInput.Text = text;
}
public void SetProgramArgsContent(string text)
{
ProgramArgsInput.Text = text;
}
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker();
// Get the window handle (HWND) for the current window
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
// Set file type filter to .exe
picker.FileTypeFilter.Add(".exe");
// Show the picker
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
ProgramPathInput.Text = file.Path;
}
}
private async void StartInSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FolderPicker();
// Get the window handle (HWND) for the current window
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
// Set file type filter (required even for folders)
picker.FileTypeFilter.Add("*");
// Show the picker
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
StartInPathInput.Text = folder.Path;
}
}
}
}

View File

@@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.InputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
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 x:Uid="InputControlOriginalKeysTextBlock" Margin="0,12,0,0" />
<Grid Grid.Column="2">
<TextBlock x:Uid="InputControlNewKeysTextBlock" Margin="0,12,0,0" />
</Grid>
<Grid Grid.Row="1" Margin="0,8,0,0">
<ToggleButton
x:Name="OriginalToggleBtn"
MinHeight="86"
Padding="8,24,8,24"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="OriginalToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="OriginalKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel
HorizontalSpacing="4"
Orientation="Horizontal"
VerticalSpacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
</Grid>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="24,0,24,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Text="&#xE0AB;" />
<Grid
Grid.Row="1"
Grid.Column="2"
Margin="0,8,0,0">
<ToggleButton
x:Name="RemappedToggleBtn"
MinHeight="86"
Padding="8,24,8,24"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Checked="RemappedToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="RemappedKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel
HorizontalSpacing="4"
Orientation="Horizontal"
VerticalSpacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
</Grid>
</Grid>
<CheckBox
x:Name="AllAppsCheckBox"
x:Uid="InputControlAllAppsCheckBox"
Margin="0,24,0,12" />
<TextBox
x:Name="AppNameTextBox"
x:Uid="InputControlAppNameTextBox"
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}" />
</StackPanel>
</UserControl>

View File

@@ -1,419 +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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Windows.System;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class InputControl : UserControl, IDisposable, IKeyboardHookTarget
{
// Collection to store original and remapped keys
private ObservableCollection<string> _originalKeys = new ObservableCollection<string>();
private ObservableCollection<string> _remappedKeys = new ObservableCollection<string>();
// TeachingTip for notifications
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
private bool _disposed;
public static readonly DependencyProperty InputModeProperty =
DependencyProperty.Register(
"InputMode",
typeof(KeyInputMode),
typeof(InputControl),
new PropertyMetadata(KeyInputMode.OriginalKeys));
public KeyInputMode InputMode
{
get { return (KeyInputMode)GetValue(InputModeProperty); }
set { SetValue(InputModeProperty, value); }
}
public InputControl()
{
this.InitializeComponent();
this.OriginalKeys.ItemsSource = _originalKeys;
this.RemappedKeys.ItemsSource = _remappedKeys;
this.Unloaded += InputControl_Unloaded;
// Set the default focus state
OriginalToggleBtn.IsChecked = true;
// Ensure AllAppsCheckBox is in the correct state initially
UpdateAllAppsCheckBoxState();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
AllAppsCheckBox.Checked += AllAppsCheckBox_Checked;
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Unchecked;
AppNameTextBox.GotFocus += AppNameTextBox_GotFocus;
}
private void InputControl_Unloaded(object sender, RoutedEventArgs e)
{
// Reset the control when it is unloaded
Reset();
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
if (InputMode == KeyInputMode.RemappedKeys)
{
_remappedKeys.Clear();
foreach (var keyName in formattedKeys)
{
_remappedKeys.Add(keyName);
}
}
else
{
_originalKeys.Clear();
foreach (var keyName in formattedKeys)
{
_originalKeys.Add(keyName);
}
}
UpdateAllAppsCheckBoxState();
}
public void ClearKeys()
{
if (InputMode == KeyInputMode.RemappedKeys)
{
_remappedKeys.Clear();
}
else
{
_originalKeys.Clear();
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
public void CleanupKeyboardHook()
{
KeyboardHookHelper.Instance.CleanupHook();
}
private void RemappedToggleBtn_Checked(object sender, RoutedEventArgs e)
{
// Only set NewMode to true if RemappedToggleBtn is checked
if (RemappedToggleBtn.IsChecked == true)
{
InputMode = KeyInputMode.RemappedKeys;
// Make sure OriginalToggleBtn is unchecked
if (OriginalToggleBtn.IsChecked == true)
{
OriginalToggleBtn.IsChecked = false;
}
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
CleanupKeyboardHook();
}
}
private void OriginalToggleBtn_Checked(object sender, RoutedEventArgs e)
{
// Only set NewMode to false if OriginalToggleBtn is checked
if (OriginalToggleBtn.IsChecked == true)
{
InputMode = KeyInputMode.OriginalKeys;
// Make sure RemappedToggleBtn is unchecked
if (RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
KeyboardHookHelper.Instance.ActivateHook(this);
}
}
private void AllAppsCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked == true)
{
OriginalToggleBtn.IsChecked = false;
}
CleanupKeyboardHook();
AppNameTextBox.Visibility = Visibility.Visible;
}
private void AllAppsCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
AppNameTextBox.Visibility = Visibility.Collapsed;
}
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Reset the focus state when the AppNameTextBox is focused
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked == true)
{
OriginalToggleBtn.IsChecked = false;
}
CleanupKeyboardHook();
}
public void SetRemappedKeys(List<string> keys)
{
_remappedKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_remappedKeys.Add(key);
}
}
UpdateAllAppsCheckBoxState();
}
public void SetOriginalKeys(List<string> keys)
{
_originalKeys.Clear();
if (keys != null)
{
foreach (var key in keys)
{
_originalKeys.Add(key);
}
}
}
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;
}
}
public List<string> GetOriginalKeys()
{
return _originalKeys.ToList();
}
public List<string> GetRemappedKeys()
{
return _remappedKeys.ToList();
}
public bool GetIsAppSpecific()
{
return AllAppsCheckBox.IsChecked ?? false;
}
public string GetAppName()
{
return AppNameTextBox.Text ?? string.Empty;
}
public void SetUpToggleButtonInitialStatus()
{
// Ensure OriginalToggleBtn is checked
if (OriginalToggleBtn != null && OriginalToggleBtn.IsChecked != true)
{
OriginalToggleBtn.IsChecked = true;
}
// Make sure RemappedToggleBtn is not checked
if (RemappedToggleBtn != null && RemappedToggleBtn.IsChecked == true)
{
RemappedToggleBtn.IsChecked = false;
}
}
public void UpdateAllAppsCheckBoxState()
{
// Only enable app-specific remapping for shortcuts (multiple keys)
bool isShortcut = _originalKeys.Count > 1;
AllAppsCheckBox.IsEnabled = isShortcut;
// If it's not a shortcut, ensure the checkbox is unchecked and app textbox is hidden
if (!isShortcut)
{
AllAppsCheckBox.IsChecked = false;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
}
public void ShowNotificationTip(string message)
{
// If there's already an active notification, close and remove it first
CloseExistingNotification();
// Create a new notification
currentNotification = new TeachingTip
{
Title = "Input Limit Reached",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
};
// Target the toggle button that triggered the notification
currentNotification.Target = InputMode == KeyInputMode.RemappedKeys ? RemappedToggleBtn : OriginalToggleBtn;
// Add the notification to the root panel and show it
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
// Create a timer to auto-dismiss the notification
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
notificationTimer = null;
};
notificationTimer.Start();
}
}
// Helper method to close existing notifications
private void CloseExistingNotification()
{
// Stop any running timer
if (notificationTimer != null)
{
notificationTimer.Stop();
notificationTimer = null;
}
// Close and remove any existing notification
if (currentNotification != null && currentNotification.IsOpen)
{
currentNotification.IsOpen = false;
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Remove(currentNotification);
}
currentNotification = null;
}
}
public void ResetToggleButtons()
{
// Reset toggle button status without clearing the key displays
if (RemappedToggleBtn != null)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null)
{
OriginalToggleBtn.IsChecked = false;
}
}
public void Reset()
{
// Reset displayed keys
_originalKeys.Clear();
_remappedKeys.Clear();
// Reset toggle button status
if (RemappedToggleBtn != null)
{
RemappedToggleBtn.IsChecked = false;
}
if (OriginalToggleBtn != null)
{
OriginalToggleBtn.IsChecked = false;
}
InputMode = KeyInputMode.OriginalKeys;
// Reset app name text box
if (AppNameTextBox != null)
{
AppNameTextBox.Text = string.Empty;
}
UpdateAllAppsCheckBoxState();
// Close any existing notifications
CloseExistingNotification();
// Reset the focus status
if (this.FocusState != FocusState.Unfocused)
{
this.IsTabStop = false;
this.IsTabStop = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
CleanupKeyboardHook();
CloseExistingNotification();
Reset();
}
_disposed = true;
}
}
}
}

View File

@@ -1,73 +0,0 @@
<?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.Controls"
xmlns:ui="using:CommunityToolkit.WinUI">
<Style BasedOn="{StaticResource DefaultKeyCharPresenterStyle}" TargetType="local:KeyCharPresenter" />
<Style x:Key="DefaultKeyCharPresenterStyle" TargetType="local:KeyCharPresenter">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid Height="{TemplateBinding FontSize}">
<TextBlock
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Text="{TemplateBinding Content}"
TextLineBounds="Tight" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="WindowsKeyCharPresenterStyle"
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
TargetType="local:KeyCharPresenter">
<!-- Scale to visually align the height of the Windows logo and text -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid Height="{TemplateBinding FontSize}">
<Viewbox>
<PathIcon Data="M9 20H0V11H9V20ZM20 20H11V11H20V20ZM9 9H0V0H9V9ZM20 9H11V0H20V9Z" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="GlyphKeyCharPresenterStyle"
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
TargetType="local:KeyCharPresenter">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyCharPresenter">
<Grid>
<Viewbox>
<FontIcon
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Glyph="{TemplateBinding Content}" />
</Viewbox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,32 +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.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
namespace KeyboardManagerEditorUI.Controls;
public sealed partial class KeyCharPresenter : Control
{
public KeyCharPresenter()
{
DefaultStyleKey = typeof(KeyCharPresenter);
}
public object Content
{
get => (object)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyCharPresenter), new PropertyMetadata(default(string)));
}

View File

@@ -1,190 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:KeyboardManagerEditorUI.Controls">
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="local:KeyVisual" />
<Style x:Key="DefaultKeyVisualStyle" TargetType="local:KeyVisual">
<Setter Property="MinWidth" Value="16" />
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="MinHeight" Value="16" />
<Setter Property="Background" Value="{ThemeResource ControlFillColorInputActiveBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="8" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="14" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid
x:Name="KeyHolder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Grid.BackgroundTransition>
<local:KeyCharPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource CardStrokeColorDefaultSolidBrush}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource ControlStrokeColorDefaultBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="2" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="SubtleKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="local:KeyVisual">
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid
x:Name="KeyHolder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Grid.BackgroundTransition>
<local:KeyCharPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="AccentKeyVisualStyle"
BasedOn="{StaticResource DefaultKeyVisualStyle}"
TargetType="local:KeyVisual">
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource AccentControlElevationBorderBrush}" />
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid
x:Name="KeyHolder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</Grid.BackgroundTransition>
<local:KeyCharPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource AccentButtonBackgroundDisabled}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource AccentButtonBorderBrushDisabled}" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource AccentButtonForegroundDisabled}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Invalid">
<VisualState.Setters>
<Setter Target="KeyHolder.Background" Value="{ThemeResource SystemFillColorCriticalBackgroundBrush}" />
<Setter Target="KeyHolder.BorderBrush" Value="{ThemeResource SystemFillColorCriticalBrush}" />
<Setter Target="KeyHolder.BorderThickness" Value="2" />
<Setter Target="KeyPresenter.Foreground" Value="{ThemeResource SystemFillColorCriticalBrush}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -1,168 +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 Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
namespace KeyboardManagerEditorUI.Controls
{
[TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))]
[TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
[TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")]
[TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")]
public sealed partial class KeyVisual : Control
{
private const string KeyPresenter = "KeyPresenter";
private const string NormalState = "Normal";
private const string DisabledState = "Disabled";
private const string InvalidState = "Invalid";
private KeyCharPresenter _keyPresenter;
public object Content
{
get => (object)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
public bool IsInvalid
{
get => (bool)GetValue(IsInvalidProperty);
set => SetValue(IsInvalidProperty, value);
}
public static readonly DependencyProperty IsInvalidProperty = DependencyProperty.Register(nameof(IsInvalid), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsInvalidChanged));
public bool RenderKeyAsGlyph
{
get => (bool)GetValue(RenderKeyAsGlyphProperty);
set => SetValue(RenderKeyAsGlyphProperty, value);
}
public static readonly DependencyProperty RenderKeyAsGlyphProperty = DependencyProperty.Register(nameof(RenderKeyAsGlyph), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnContentChanged));
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public KeyVisual()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
{
this.DefaultStyleKey = typeof(KeyVisual);
}
protected override void OnApplyTemplate()
{
IsEnabledChanged -= KeyVisual_IsEnabledChanged;
_keyPresenter = (KeyCharPresenter)this.GetTemplateChild(KeyPresenter);
Update();
SetVisualStates();
IsEnabledChanged += KeyVisual_IsEnabledChanged;
base.OnApplyTemplate();
}
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).SetVisualStates();
}
private static void OnIsInvalidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((KeyVisual)d).SetVisualStates();
}
private void SetVisualStates()
{
if (this != null)
{
if (IsInvalid)
{
VisualStateManager.GoToState(this, InvalidState, true);
}
else if (!IsEnabled)
{
VisualStateManager.GoToState(this, DisabledState, true);
}
else
{
VisualStateManager.GoToState(this, NormalState, true);
}
}
}
private void Update()
{
if (Content == null)
{
return;
}
if (Content is string)
{
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
return;
}
if (Content is int keyCode)
{
VirtualKey virtualKey = (VirtualKey)keyCode;
switch (virtualKey)
{
case VirtualKey.Enter:
SetGlyphOrText("\uE751", virtualKey);
break;
case VirtualKey.Back:
SetGlyphOrText("\uE750", virtualKey);
break;
case VirtualKey.Shift:
case (VirtualKey)160: // Left Shift
case (VirtualKey)161: // Right Shift
SetGlyphOrText("\uE752", virtualKey);
break;
case VirtualKey.Up:
_keyPresenter.Content = "\uE0E4";
break;
case VirtualKey.Down:
_keyPresenter.Content = "\uE0E5";
break;
case VirtualKey.Left:
_keyPresenter.Content = "\uE0E2";
break;
case VirtualKey.Right:
_keyPresenter.Content = "\uE0E3";
break;
case VirtualKey.LeftWindows:
case VirtualKey.RightWindows:
_keyPresenter.Style = (Style)Application.Current.Resources["WindowsKeyCharPresenterStyle"];
break;
}
}
}
private void SetGlyphOrText(string glyph, VirtualKey key)
{
if (RenderKeyAsGlyph)
{
_keyPresenter.Content = glyph;
_keyPresenter.Style = (Style)Application.Current.Resources["GlyphKeyCharPresenterStyle"];
}
else
{
_keyPresenter.Content = key.ToString();
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
}
}
private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetVisualStates();
}
}
}

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.TextPageInputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel
Width="360"
Height="360"
Orientation="Vertical">
<!-- Shortcut section -->
<TextBlock
x:Uid="TextPageInputControlShortcutKeysTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ShortcutToggleBtn"
Padding="0,24,0,24"
HorizontalAlignment="Stretch"
Checked="ShortcutToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="ShortcutKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<!-- Text section -->
<TextBox
x:Name="TextContentBox"
x:Uid="TextPageInputControlTextContentTextBox"
Height="120"
Margin="0,8,0,0"
AcceptsReturn="True"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
FontSize="13"
TextWrapping="Wrap" />
<!-- App specific section -->
<CheckBox
x:Name="AllAppsCheckBox"
x:Uid="TextPageInputControlAllAppsCheckBox"
Margin="0,8,0,0"
Foreground="{ThemeResource TextFillColorPrimaryBrush}" />
<TextBox
x:Name="AppNameTextBox"
x:Uid="TextPageInputControlAllAppsTextBox"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
IsEnabled="{Binding ElementName=AllAppsCheckBox, Path=IsChecked}" />
</StackPanel>
</UserControl>

View File

@@ -1,252 +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.Collections.Generic;
using System.Collections.ObjectModel;
using KeyboardManagerEditorUI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.System;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class TextPageInputControl : UserControl, IKeyboardHookTarget
{
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
private bool _internalUpdate;
public TextPageInputControl()
{
this.InitializeComponent();
this.ShortcutKeys.ItemsSource = _shortcutKeys;
ShortcutToggleBtn.IsChecked = true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
KeyboardHookHelper.Instance.ActivateHook(this);
TextContentBox.GotFocus += TextContentBox_GotFocus;
AllAppsCheckBox.Checked += AllAppsCheckBox_Changed;
AllAppsCheckBox.Unchecked += AllAppsCheckBox_Changed;
AppNameTextBox.GotFocus += AppNameTextBox_GotFocus;
AppNameTextBox.Visibility = AllAppsCheckBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
}
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ShortcutToggleBtn.IsChecked == true)
{
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
KeyboardHookHelper.Instance.CleanupHook();
}
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
_shortcutKeys.Clear();
foreach (var keyName in formattedKeys)
{
_shortcutKeys.Add(keyName);
}
UpdateAllAppsCheckBoxState();
}
private void TextContentBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clean up the keyboard hook when the text box gains focus
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
public void UpdateAllAppsCheckBoxState()
{
// Only enable app-specific remapping for shortcuts (multiple keys)
bool isShortcut = _shortcutKeys.Count > 1;
AllAppsCheckBox.IsEnabled = isShortcut;
// If it's not a shortcut, ensure the checkbox is unchecked and app textbox is hidden
try
{
if (!isShortcut)
{
_internalUpdate = true;
AllAppsCheckBox.IsChecked = false;
AppNameTextBox.Visibility = Visibility.Collapsed;
}
else if (AllAppsCheckBox.IsChecked == true)
{
AppNameTextBox.Visibility = Visibility.Visible;
}
}
finally
{
_internalUpdate = false;
}
}
private void AllAppsCheckBox_Changed(object sender, RoutedEventArgs e)
{
if (_internalUpdate)
{
return;
}
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
AppNameTextBox.Visibility = AllAppsCheckBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
}
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
{
if (_internalUpdate)
{
return;
}
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
currentNotification = new TeachingTip
{
Title = "Input Limit",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
Target = ShortcutToggleBtn,
};
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
};
notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
if (notificationTimer != null)
{
notificationTimer.Stop();
notificationTimer = null;
}
if (currentNotification != null && currentNotification.IsOpen)
{
currentNotification.IsOpen = false;
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(currentNotification))
{
rootPanel.Children.Remove(currentNotification);
}
currentNotification = null;
}
}
public void ClearKeys()
{
_shortcutKeys.Clear();
UpdateAllAppsCheckBoxState();
}
public List<string> GetShortcutKeys()
{
List<string> keys = new List<string>();
foreach (var key in _shortcutKeys)
{
keys.Add(key);
}
return keys;
}
public string GetTextContent()
{
return TextContentBox.Text;
}
public bool GetIsAppSpecific()
{
return AllAppsCheckBox.IsChecked ?? false;
}
public string GetAppName()
{
return AllAppsCheckBox.IsChecked == true ? AppNameTextBox.Text : string.Empty;
}
public void SetShortcutKeys(List<string> keys)
{
if (keys != null)
{
_shortcutKeys.Clear();
foreach (var key in keys)
{
_shortcutKeys.Add(key);
}
}
UpdateAllAppsCheckBoxState();
}
public void SetTextContent(string text)
{
TextContentBox.Text = text;
}
public void SetAppSpecific(bool isAppSpecific, string appName)
{
AllAppsCheckBox.IsChecked = isAppSpecific;
if (isAppSpecific)
{
AppNameTextBox.Text = appName;
}
}
}
}

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="KeyboardManagerEditorUI.Controls.UrlPageInputControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="UserControl_Loaded"
mc:Ignorable="d">
<StackPanel
Width="360"
Height="360"
Orientation="Vertical"
Spacing="8">
<!-- Shortcut section -->
<TextBlock
x:Uid="UrlPageInputControlShortcutTextBlock"
Margin="0,12,0,8"
FontWeight="SemiBold" />
<ToggleButton
x:Name="ShortcutToggleBtn"
Padding="0,24,0,24"
HorizontalAlignment="Stretch"
Checked="ShortcutToggleBtn_Checked"
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<ItemsControl x:Name="ShortcutKeys">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel HorizontalSpacing="4" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:KeyVisual Content="{Binding}" Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ToggleButton.Content>
</ToggleButton>
<TextBox
x:Name="UrlPathInput"
x:Uid="UrlPageInputControlToOpenTextBox"
Margin="0,8,0,0"
AcceptsReturn="True"
Background="{ThemeResource TextControlBackgroundFocused}"
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
FontSize="13"
TextWrapping="Wrap"
Width="360" />
</StackPanel>
</UserControl>

View File

@@ -1,167 +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.Collections.Generic;
using System.Collections.ObjectModel;
using KeyboardManagerEditorUI.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.System;
using WinRT.Interop;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
namespace KeyboardManagerEditorUI.Controls
{
public sealed partial class UrlPageInputControl : UserControl, IKeyboardHookTarget
{
private ObservableCollection<string> _shortcutKeys = new ObservableCollection<string>();
private TeachingTip? currentNotification;
private DispatcherTimer? notificationTimer;
// private bool _internalUpdate;
public UrlPageInputControl()
{
this.InitializeComponent();
this.ShortcutKeys.ItemsSource = _shortcutKeys;
ShortcutToggleBtn.IsChecked = true;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
KeyboardHookHelper.Instance.ActivateHook(this);
UrlPathInput.GotFocus += UrlInputBox_GotFocus;
}
private void ShortcutToggleBtn_Checked(object sender, RoutedEventArgs e)
{
if (ShortcutToggleBtn.IsChecked == true)
{
KeyboardHookHelper.Instance.ActivateHook(this);
}
else
{
KeyboardHookHelper.Instance.CleanupHook();
}
}
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
{
_shortcutKeys.Clear();
foreach (var keyName in formattedKeys)
{
_shortcutKeys.Add(keyName);
}
}
private void UrlInputBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clean up the keyboard hook when the text box gains focus
KeyboardHookHelper.Instance.CleanupHook();
if (ShortcutToggleBtn != null && ShortcutToggleBtn.IsChecked == true)
{
ShortcutToggleBtn.IsChecked = false;
}
}
public void OnInputLimitReached()
{
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
}
public void ShowNotificationTip(string message)
{
CloseExistingNotification();
currentNotification = new TeachingTip
{
Title = "Input Limit",
Subtitle = message,
IsLightDismissEnabled = true,
PreferredPlacement = TeachingTipPlacementMode.Top,
XamlRoot = this.XamlRoot,
IconSource = new SymbolIconSource { Symbol = Symbol.Important },
Target = ShortcutToggleBtn,
};
if (this.Content is Panel rootPanel)
{
rootPanel.Children.Add(currentNotification);
currentNotification.IsOpen = true;
notificationTimer = new DispatcherTimer();
notificationTimer.Interval = TimeSpan.FromMilliseconds(EditorConstants.DefaultNotificationTimeout);
notificationTimer.Tick += (s, e) =>
{
CloseExistingNotification();
};
notificationTimer.Start();
}
}
private void CloseExistingNotification()
{
if (notificationTimer != null)
{
notificationTimer.Stop();
notificationTimer = null;
}
if (currentNotification != null && currentNotification.IsOpen)
{
currentNotification.IsOpen = false;
if (this.Content is Panel rootPanel && rootPanel.Children.Contains(currentNotification))
{
rootPanel.Children.Remove(currentNotification);
}
currentNotification = null;
}
}
public void ClearKeys()
{
_shortcutKeys.Clear();
}
public List<string> GetShortcutKeys()
{
List<string> keys = new List<string>();
foreach (var key in _shortcutKeys)
{
keys.Add(key);
}
return keys;
}
public string GetUrlPathContent()
{
return UrlPathInput.Text;
}
public void SetShortcutKeys(List<string> keys)
{
if (keys != null)
{
_shortcutKeys.Clear();
foreach (var key in keys)
{
_shortcutKeys.Add(key);
}
}
}
public void SetUrlPathContent(string text)
{
UrlPathInput.Text = text;
}
}
}

View File

@@ -1,18 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Helpers
{
public static class EditorConstants
{
// Default notification timeout
public const int DefaultNotificationTimeout = 1500;
}
}

View File

@@ -1,12 +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 KeyboardManagerEditorUI.Helpers
{
public enum KeyInputMode
{
OriginalKeys,
RemappedKeys,
}
}

View File

@@ -1,239 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Interop;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.System;
namespace KeyboardManagerEditorUI.Helpers
{
public class KeyboardHookHelper : IDisposable
{
private static KeyboardHookHelper? _instance;
public static KeyboardHookHelper Instance => _instance ??= new KeyboardHookHelper();
private KeyboardMappingService _mappingService;
private HotkeySettingsControlHook? _keyboardHook;
// The active page using this keyboard hook
private IKeyboardHookTarget? _activeTarget;
private HashSet<VirtualKey> _currentlyPressedKeys = new();
private List<VirtualKey> _keyPressOrder = new();
private bool _disposed;
// Singleton to make sure only one instance of the hook is active
private KeyboardHookHelper()
{
_mappingService = new KeyboardMappingService();
}
public void ActivateHook(IKeyboardHookTarget target)
{
CleanupHook();
_activeTarget = target;
_currentlyPressedKeys.Clear();
_keyPressOrder.Clear();
_keyboardHook = new HotkeySettingsControlHook(
KeyDown,
KeyUp,
() => true,
(key, extraInfo) => true);
}
public void CleanupHook()
{
if (_keyboardHook != null)
{
_keyboardHook.Dispose();
_keyboardHook = null;
}
_currentlyPressedKeys.Clear();
_keyPressOrder.Clear();
_activeTarget = null;
}
private void KeyDown(int key)
{
if (_activeTarget == null)
{
return;
}
VirtualKey virtualKey = (VirtualKey)key;
if (_currentlyPressedKeys.Contains(virtualKey))
{
return;
}
// if no keys are pressed, clear the lists when a new key is pressed
if (_currentlyPressedKeys.Count == 0)
{
_activeTarget.ClearKeys();
_keyPressOrder.Clear();
}
// Count current modifiers
int modifierCount = _currentlyPressedKeys.Count(k => RemappingHelper.IsModifierKey(k));
// If adding this key would exceed the limits (4 modifiers + 1 action key), don't add it and show notification
if ((RemappingHelper.IsModifierKey(virtualKey) && modifierCount >= 4) ||
(!RemappingHelper.IsModifierKey(virtualKey) && _currentlyPressedKeys.Count >= 5))
{
_activeTarget.OnInputLimitReached();
return;
}
// Check if this is a different variant of a modifier key already pressed
if (RemappingHelper.IsModifierKey(virtualKey))
{
// Remove existing variant of this modifier key if a new one is pressed
// This is to ensure that only one variant of a modifier key is displayed at a time
RemoveExistingModifierVariant(virtualKey);
}
if (_currentlyPressedKeys.Add(virtualKey))
{
_keyPressOrder.Add(virtualKey);
// Notify the target page
_activeTarget.OnKeyDown(virtualKey, GetFormattedKeyList());
}
}
private void KeyUp(int key)
{
if (_activeTarget == null)
{
return;
}
VirtualKey virtualKey = (VirtualKey)key;
if (_currentlyPressedKeys.Remove(virtualKey))
{
_keyPressOrder.Remove(virtualKey);
_activeTarget.OnKeyUp(virtualKey, GetFormattedKeyList());
}
}
// Display the modifier keys and the action key in order, e.g. "Ctrl + Alt + A"
private List<string> GetFormattedKeyList()
{
if (_activeTarget == null)
{
return new List<string>();
}
List<string> keyList = new List<string>();
List<VirtualKey> modifierKeys = new List<VirtualKey>();
VirtualKey? actionKey = null;
foreach (var key in _keyPressOrder)
{
if (!_currentlyPressedKeys.Contains(key))
{
continue;
}
if (RemappingHelper.IsModifierKey(key))
{
if (!modifierKeys.Contains(key))
{
modifierKeys.Add(key);
}
}
else
{
actionKey = key;
}
}
foreach (var key in modifierKeys)
{
keyList.Add(_mappingService.GetKeyDisplayName((int)key));
}
if (actionKey.HasValue)
{
keyList.Add(_mappingService.GetKeyDisplayName((int)actionKey.Value));
}
return keyList;
}
private void RemoveExistingModifierVariant(VirtualKey key)
{
KeyType keyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)key);
// No need to remove if the key is an action key
if (keyType == KeyType.Action)
{
return;
}
foreach (var existingKey in _currentlyPressedKeys.ToList())
{
if (existingKey != key)
{
KeyType existingKeyType = (KeyType)KeyboardManagerInterop.GetKeyType((int)existingKey);
// Remove the existing key if it is a modifier key and has the same type as the new key
if (existingKeyType == keyType)
{
_currentlyPressedKeys.Remove(existingKey);
_keyPressOrder.Remove(existingKey);
}
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
CleanupHook();
_mappingService?.Dispose();
}
_disposed = true;
}
}
}
public interface IKeyboardHookTarget
{
void OnKeyDown(VirtualKey key, List<string> formattedKeys);
void OnKeyUp(VirtualKey key, List<string> formattedKeys)
{
}
void ClearKeys();
void OnInputLimitReached();
}
}

View File

@@ -1,25 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Helpers
{
public class ProgramShortcut
{
public List<string> Shortcut { get; set; } = new List<string>();
public string AppToRun { get; set; } = string.Empty;
public string Args { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public string Id { get; set; } = string.Empty;
}
}

View File

@@ -1,47 +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.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Helpers
{
public partial class Remapping : INotifyPropertyChanged
{
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";
private bool IsEnabledValue { get; set; } = true;
public event PropertyChangedEventHandler? PropertyChanged;
public bool IsEnabled
{
get => IsEnabledValue;
set
{
if (IsEnabledValue != value)
{
IsEnabledValue = value;
OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -1,144 +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.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Interop;
using ManagedCommon;
using Windows.System;
namespace KeyboardManagerEditorUI.Helpers
{
public static class RemappingHelper
{
public static bool SaveMapping(KeyboardMappingService mappingService, List<string> originalKeys, List<string> remappedKeys, bool isAppSpecific, string appName)
{
if (mappingService == null)
{
Logger.LogError("Mapping service is null, cannot save mapping");
return false;
}
try
{
if (originalKeys == null || originalKeys.Count == 0 || remappedKeys == null || remappedKeys.Count == 0)
{
return false;
}
if (originalKeys.Count == 1)
{
int originalKey = mappingService.GetKeyCodeFromName(originalKeys[0]);
if (originalKey != 0)
{
if (remappedKeys.Count == 1)
{
int targetKey = mappingService.GetKeyCodeFromName(remappedKeys[0]);
if (targetKey != 0)
{
mappingService.AddSingleKeyMapping(originalKey, targetKey);
}
}
else
{
string targetKeys = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
mappingService.AddSingleKeyMapping(originalKey, targetKeys);
}
}
}
else
{
string originalKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
string targetKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
if (isAppSpecific && !string.IsNullOrEmpty(appName))
{
mappingService.AddShortcutMapping(originalKeysString, targetKeysString, appName);
}
else
{
mappingService.AddShortcutMapping(originalKeysString, targetKeysString);
}
}
return mappingService.SaveSettings();
}
catch (Exception ex)
{
Logger.LogError("Error saving mapping: " + ex.Message);
return false;
}
}
public static bool DeleteRemapping(KeyboardMappingService mappingService, Remapping remapping)
{
if (mappingService == null)
{
return false;
}
try
{
if (remapping.OriginalKeys.Count == 1)
{
// Single key mapping
int originalKey = mappingService.GetKeyCodeFromName(remapping.OriginalKeys[0]);
if (originalKey != 0)
{
if (mappingService.DeleteSingleKeyMapping(originalKey))
{
// Save settings after successful deletion
return mappingService.SaveSettings();
}
}
}
else if (remapping.OriginalKeys.Count > 1)
{
// Shortcut mapping
string originalKeysString = string.Join(";", remapping.OriginalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
bool deleteResult;
if (!remapping.IsAllApps && !string.IsNullOrEmpty(remapping.AppName))
{
// App-specific shortcut key mapping
deleteResult = mappingService.DeleteShortcutMapping(originalKeysString, remapping.AppName);
}
else
{
// Global shortcut key mapping
deleteResult = mappingService.DeleteShortcutMapping(originalKeysString);
}
return deleteResult ? mappingService.SaveSettings() : false;
}
return false;
}
catch (Exception ex)
{
Logger.LogError($"Error deleting remapping: {ex.Message}");
return false;
}
}
public static bool IsModifierKey(VirtualKey key)
{
return key == VirtualKey.Control
|| key == VirtualKey.LeftControl
|| key == VirtualKey.RightControl
|| key == VirtualKey.Menu
|| key == VirtualKey.LeftMenu
|| key == VirtualKey.RightMenu
|| key == VirtualKey.Shift
|| key == VirtualKey.LeftShift
|| key == VirtualKey.RightShift
|| key == VirtualKey.LeftWindows
|| key == VirtualKey.RightWindows;
}
}
}

View File

@@ -1,19 +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 Microsoft.Windows.ApplicationModel.Resources;
namespace KeyboardManagerEditorUI.Helpers
{
internal static class ResourceLoaderInstance
{
internal static ResourceLoader ResourceLoader { get; private set; }
static ResourceLoaderInstance()
{
ResourceLoader = new ResourceLoader("PowerToys.KeyboardManagerEditorUI.pri");
}
internal static string GetString(string resourceId) => "Hello";
}
}

View File

@@ -1,25 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Helpers
{
public class TextMapping
{
public List<string> Keys { get; set; } = new List<string>();
public string Text { get; set; } = string.Empty;
public bool IsAllApps { get; set; } = true;
public string AppName { get; set; } = "All Apps";
public bool IsActive { get; set; } = true;
}
}

View File

@@ -1,21 +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.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;
public bool IsActive { get; set; } = true;
}
}

View File

@@ -1,25 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Helpers
{
public enum ValidationErrorType
{
NoError,
EmptyOriginalKeys,
EmptyRemappedKeys,
ModifierOnly,
EmptyAppName,
IllegalShortcut,
DuplicateMapping,
SelfMapping,
EmptyTargetText,
}
}

View File

@@ -1,318 +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.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Interop;
namespace KeyboardManagerEditorUI.Helpers
{
public static class ValidationHelper
{
public static readonly Dictionary<ValidationErrorType, (string Title, string Message)> ValidationMessages = new()
{
{ ValidationErrorType.EmptyOriginalKeys, ("Missing Original Keys", "Please enter at least one original key to create a remapping.") },
{ ValidationErrorType.EmptyRemappedKeys, ("Missing Target Keys", "Please enter at least one target key to create a remapping.") },
{ ValidationErrorType.ModifierOnly, ("Invalid Shortcut", "Shortcuts must contain at least one action key in addition to modifier keys (Ctrl, Alt, Shift, Win).") },
{ ValidationErrorType.EmptyAppName, ("Missing Application Name", "You've selected app-specific remapping but haven't specified an application name. Please enter the application name.") },
{ ValidationErrorType.IllegalShortcut, ("Reserved System Shortcut", "Win+L and Ctrl+Alt+Delete are reserved system shortcuts and cannot be remapped.") },
{ ValidationErrorType.DuplicateMapping, ("Duplicate Remapping", "This key or shortcut is already remapped.") },
{ ValidationErrorType.SelfMapping, ("Invalid Remapping", "A key or shortcut cannot be remapped to itself. Please choose a different target.") },
{ ValidationErrorType.EmptyTargetText, ("Missing Target Text", "Please enter the text to be inserted when the shortcut is pressed.") },
};
public static ValidationErrorType ValidateKeyMapping(
List<string> originalKeys,
List<string> remappedKeys,
bool isAppSpecific,
string appName,
KeyboardMappingService mappingService,
bool isEditMode = false,
Remapping? editingRemapping = null)
{
// Check if original keys are empty
if (originalKeys == null || originalKeys.Count == 0)
{
return ValidationErrorType.EmptyOriginalKeys;
}
// Check if remapped keys are empty
if (remappedKeys == null || remappedKeys.Count == 0)
{
return ValidationErrorType.EmptyRemappedKeys;
}
// Check if shortcut contains only modifier keys
if ((originalKeys.Count > 1 && ContainsOnlyModifierKeys(originalKeys)) ||
(remappedKeys.Count > 1 && ContainsOnlyModifierKeys(remappedKeys)))
{
return ValidationErrorType.ModifierOnly;
}
// Check if app specific is checked but no app name is provided
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
{
return ValidationErrorType.EmptyAppName;
}
// Check if this is a shortcut (multiple keys) and if it's an illegal combination
if (originalKeys.Count > 1)
{
string shortcutKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
if (KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString))
{
return ValidationErrorType.IllegalShortcut;
}
}
// Check for duplicate mappings
if (IsDuplicateMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping))
{
return ValidationErrorType.DuplicateMapping;
}
// Check for self-mapping
if (IsSelfMapping(originalKeys, remappedKeys, mappingService))
{
return ValidationErrorType.SelfMapping;
}
return ValidationErrorType.NoError;
}
public static ValidationErrorType ValidateTextMapping(
List<string> keys,
string textContent,
bool isAppSpecific,
string appName,
KeyboardMappingService mappingService)
{
// Check if original keys are empty
if (keys == null || keys.Count == 0)
{
return ValidationErrorType.EmptyOriginalKeys;
}
// Check if text content is empty
if (string.IsNullOrWhiteSpace(textContent))
{
return ValidationErrorType.EmptyTargetText;
}
// Check if shortcut contains only modifier keys
if (keys.Count > 1 && ContainsOnlyModifierKeys(keys))
{
return ValidationErrorType.ModifierOnly;
}
// Check if app specific is checked but no app name is provided
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
{
return ValidationErrorType.EmptyAppName;
}
// Check if this is a shortcut (multiple keys) and if it's an illegal combination
if (keys.Count > 1)
{
string shortcutKeysString = string.Join(";", keys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
if (KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString))
{
return ValidationErrorType.IllegalShortcut;
}
}
// No errors found
return ValidationErrorType.NoError;
}
// Temporary program shorctut validation
public static ValidationErrorType ValidateProgramOrUrlMapping(
List<string> originalKeys,
bool isAppSpecific,
string appName,
KeyboardMappingService mappingService,
bool isEditMode = false,
Remapping? editingRemapping = null)
{
ValidationErrorType error = ValidateKeyMapping(originalKeys, originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
if (error == ValidationErrorType.SelfMapping)
{
return ValidationErrorType.NoError;
}
return error;
}
public static bool IsDuplicateMapping(
List<string> originalKeys,
bool isAppSpecific,
string appName,
KeyboardMappingService mappingService,
bool isEditMode = false,
Remapping? editingRemapping = null)
{
if (mappingService == null || originalKeys == null || originalKeys.Count == 0)
{
return false;
}
// For single key remapping
if (originalKeys.Count == 1)
{
int originalKeyCode = mappingService.GetKeyCodeFromName(originalKeys[0]);
if (originalKeyCode == 0)
{
return false;
}
// Check if the key is already remapped
foreach (var mapping in mappingService.GetSingleKeyMappings())
{
if (mapping.OriginalKey == originalKeyCode)
{
// Skip if the remapping is the same as the one being edited
if (isEditMode && editingRemapping != null &&
editingRemapping.OriginalKeys.Count == 1 &&
mappingService.GetKeyCodeFromName(editingRemapping.OriginalKeys[0]) == originalKeyCode)
{
continue;
}
return true;
}
}
}
// For shortcut remapping
else
{
string originalKeysString = string.Join(";", originalKeys.Select(
k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
// Don't check for duplicates if the original keys are the same as the remapping being edited
bool isEditingExistingRemapping = false;
if (isEditMode && editingRemapping != null)
{
string editingOriginalKeysString = string.Join(";", editingRemapping.OriginalKeys.Select(k =>
mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
if (KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, editingOriginalKeysString))
{
isEditingExistingRemapping = true;
}
}
// Check if the shortcut is already remapped in the same app context
foreach (var mapping in mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut))
{
if (KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, mapping.OriginalKeys))
{
// If both are global (all apps)
if (!isAppSpecific && string.IsNullOrEmpty(mapping.TargetApp))
{
// Skip if the remapping is the same as the one being edited
if (editingRemapping != null && editingRemapping.OriginalKeys.Count > 1 && editingRemapping.IsAllApps && isEditingExistingRemapping)
{
continue;
}
return true;
}
// If both are for the same specific app
else if (isAppSpecific && !string.IsNullOrEmpty(mapping.TargetApp)
&& string.Equals(mapping.TargetApp, appName, StringComparison.OrdinalIgnoreCase))
{
// Skip if the remapping is the same as the one being edited
if (editingRemapping != null && editingRemapping.OriginalKeys.Count > 1 && !editingRemapping.IsAllApps &&
string.Equals(editingRemapping.AppName, appName, StringComparison.OrdinalIgnoreCase) && isEditingExistingRemapping)
{
continue;
}
return true;
}
}
}
}
return false;
}
public static bool IsSelfMapping(List<string> originalKeys, List<string> remappedKeys, KeyboardMappingService mappingService)
{
if (mappingService == null)
{
return false;
}
// If either list is empty, it's not a self-mapping
if (originalKeys == null || remappedKeys == null ||
originalKeys.Count == 0 || remappedKeys.Count == 0)
{
return false;
}
string originalKeysString = string.Join(";", originalKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
string remappedKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
return KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, remappedKeysString);
}
public static bool ContainsOnlyModifierKeys(List<string> keys)
{
if (keys == null || keys.Count == 0)
{
return false;
}
foreach (string key in keys)
{
int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(key);
var keyType = (KeyType)KeyboardManagerInterop.GetKeyType(keyCode);
// If any key is an action key, return false
if (keyType == KeyType.Action)
{
return false;
}
}
// All keys are modifier keys
return true;
}
public static bool IsKeyOrphaned(int originalKey, KeyboardMappingService mappingService)
{
// Check all single key mappings
foreach (var mapping in mappingService.GetSingleKeyMappings())
{
if (!mapping.IsShortcut && int.TryParse(mapping.TargetKey, out int targetKey) && targetKey == originalKey)
{
return false;
}
}
// Check all shortcut mappings
foreach (var mapping in mappingService.GetShortcutMappings())
{
string[] targetKeys = mapping.TargetKeys.Split(';');
if (targetKeys.Length == 1 && int.TryParse(targetKeys[0], out int shortcutTargetKey) && shortcutTargetKey == originalKey)
{
return false;
}
}
// No mapping found for the original key
return true;
}
}
}

View File

@@ -1,21 +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.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; }
}
}

View File

@@ -1,19 +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.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;
}
}

View File

@@ -1,21 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Interop
{
public enum KeyType
{
Win = 0,
Ctrl = 1,
Alt = 2,
Shift = 3,
Action = 4,
}
}

View File

@@ -1,165 +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.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";
// Configuration Management
[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);
// Get Mapping Functions
[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 SingleKeyMapping 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);
// Add Mapping Functions
[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 AddSingleKeyToTextRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetText);
[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,
int operationType = 0,
[MarshalAs(UnmanagedType.LPWStr)] string appPathOrUri = "",
[MarshalAs(UnmanagedType.LPWStr)] string? args = null,
[MarshalAs(UnmanagedType.LPWStr)] string? startDirectory = null,
int elevation = 0,
int ifRunningAction = 0,
int visibility = 0);
// Delete Mapping Functions
[DllImport(DllName)]
internal static extern bool DeleteSingleKeyRemap(IntPtr mappingConfiguration, int originalKey);
[DllImport(DllName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeleteSingleKeyToTextRemap(IntPtr config, int originalKey);
[DllImport(DllName)]
internal static extern bool DeleteShortcutRemap(IntPtr mappingConfiguration, [MarshalAs(UnmanagedType.LPWStr)] string originalKeys, [MarshalAs(UnmanagedType.LPWStr)] string targetApp);
// Key Utility Functions
[DllImport(DllName)]
internal static extern int GetKeyCodeFromName([MarshalAs(UnmanagedType.LPWStr)] string keyName);
[DllImport(DllName, CharSet = CharSet.Unicode)]
internal static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
[DllImport(DllName)]
internal static extern int GetKeyType(int keyCode);
// Validation Functions
[DllImport(DllName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool IsShortcutIllegal([MarshalAs(UnmanagedType.LPWStr)] string shortcutKeys);
[DllImport(DllName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AreShortcutsEqual([MarshalAs(UnmanagedType.LPWStr)] string lShort, [MarshalAs(UnmanagedType.LPWStr)] string rShortcut);
// String Management Functions
[DllImport(DllName)]
internal static extern void FreeString(IntPtr str);
public static string GetStringAndFree(IntPtr handle)
{
if (handle == IntPtr.Zero)
{
return string.Empty;
}
string? result = Marshal.PtrToStringUni(handle);
FreeString(handle);
return result ?? string.Empty;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SingleKeyMapping
{
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;
}
}

View File

@@ -1,294 +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.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using ManagedCommon;
namespace KeyboardManagerEditorUI.Interop
{
public class KeyboardMappingService : IDisposable
{
private IntPtr _configHandle;
private bool _disposed;
public KeyboardMappingService()
{
_configHandle = KeyboardManagerInterop.CreateMappingConfiguration();
if (_configHandle == IntPtr.Zero)
{
Logger.LogError("Failed to create mapping configuration");
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(SingleKeyMapping);
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 string GetKeyDisplayName(int keyCode)
{
var keyName = new StringBuilder(64);
KeyboardManagerInterop.GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
return keyName.ToString();
}
public int GetKeyCodeFromName(string keyName)
{
if (string.IsNullOrEmpty(keyName))
{
return 0;
}
return KeyboardManagerInterop.GetKeyCodeFromName(keyName);
}
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 AddSingleKeyToTextMapping(int originalKey, string targetText)
{
if (string.IsNullOrEmpty(targetText))
{
return false;
}
return KeyboardManagerInterop.AddSingleKeyToTextRemap(_configHandle, originalKey, targetText);
}
public bool AddShortcutMapping(string originalKeys, string targetKeys, string targetApp = "", ShortcutOperationType operationType = ShortcutOperationType.RemapShortcut)
{
if (string.IsNullOrEmpty(originalKeys) || string.IsNullOrEmpty(targetKeys))
{
return false;
}
return KeyboardManagerInterop.AddShortcutRemap(_configHandle, originalKeys, targetKeys, targetApp, (int)operationType);
}
public bool AddShorcutMapping(ShortcutKeyMapping shortcutKeyMapping)
{
if (string.IsNullOrEmpty(shortcutKeyMapping.OriginalKeys) || string.IsNullOrEmpty(shortcutKeyMapping.TargetKeys))
{
return false;
}
if (shortcutKeyMapping.OperationType == ShortcutOperationType.RunProgram && string.IsNullOrEmpty(shortcutKeyMapping.ProgramPath))
{
return false;
}
if (shortcutKeyMapping.OperationType == ShortcutOperationType.OpenUri && string.IsNullOrEmpty(shortcutKeyMapping.UriToOpen))
{
return false;
}
if (shortcutKeyMapping.OperationType == ShortcutOperationType.RunProgram)
{
return KeyboardManagerInterop.AddShortcutRemap(
_configHandle,
shortcutKeyMapping.OriginalKeys,
shortcutKeyMapping.TargetKeys,
shortcutKeyMapping.TargetApp,
(int)shortcutKeyMapping.OperationType,
shortcutKeyMapping.ProgramPath,
string.IsNullOrEmpty(shortcutKeyMapping.ProgramArgs) ? null : shortcutKeyMapping.ProgramArgs,
string.IsNullOrEmpty(shortcutKeyMapping.StartInDirectory) ? null : shortcutKeyMapping.StartInDirectory,
(int)shortcutKeyMapping.Elevation,
(int)shortcutKeyMapping.IfRunningAction,
(int)shortcutKeyMapping.Visibility);
}
else if (shortcutKeyMapping.OperationType == ShortcutOperationType.OpenUri)
{
return KeyboardManagerInterop.AddShortcutRemap(
_configHandle,
shortcutKeyMapping.OriginalKeys,
shortcutKeyMapping.TargetKeys,
shortcutKeyMapping.TargetApp,
(int)shortcutKeyMapping.OperationType,
shortcutKeyMapping.UriToOpen);
}
return KeyboardManagerInterop.AddShortcutRemap(
_configHandle,
shortcutKeyMapping.OriginalKeys,
shortcutKeyMapping.TargetKeys,
shortcutKeyMapping.TargetApp,
(int)shortcutKeyMapping.OperationType);
}
public bool SaveSettings()
{
return KeyboardManagerInterop.SaveMappingSettings(_configHandle);
}
public bool DeleteSingleKeyMapping(int originalKey)
{
return KeyboardManagerInterop.DeleteSingleKeyRemap(_configHandle, originalKey);
}
public bool DeleteSingleKeyToTextMapping(int originalKey)
{
if (originalKey == 0)
{
return false;
}
return KeyboardManagerInterop.DeleteSingleKeyToTextRemap(_configHandle, originalKey);
}
public bool DeleteShortcutMapping(string originalKeys, string targetApp = "")
{
if (string.IsNullOrEmpty(originalKeys))
{
return false;
}
return KeyboardManagerInterop.DeleteShortcutRemap(_configHandle, originalKeys, targetApp ?? string.Empty);
}
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);
}
}
}

View File

@@ -1,103 +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.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 StartInDirectory { get; set; } = string.Empty;
public ElevationLevel Elevation { get; set; } = ElevationLevel.NonElevated;
public ProgramAlreadyRunningAction IfRunningAction { get; set; } = ProgramAlreadyRunningAction.ShowWindow;
public StartWindowType Visibility { get; set; } = StartWindowType.Normal;
public string UriToOpen { get; set; } = string.Empty;
public enum ElevationLevel
{
NonElevated = 0,
Elevated = 1,
DifferentUser = 2,
}
public enum StartWindowType
{
Normal = 0,
Hidden = 1,
Minimized = 2,
Maximized = 3,
}
public enum ProgramAlreadyRunningAction
{
ShowWindow = 0,
StartAnother = 1,
DoNothing = 2,
Close = 3,
EndTask = 4,
CloseAndEndTask = 5,
}
public override bool Equals(object? obj)
{
if (obj is not ShortcutKeyMapping other)
{
return false;
}
return OriginalKeys == other.OriginalKeys &&
TargetKeys == other.TargetKeys &&
TargetApp == other.TargetApp &&
OperationType == other.OperationType &&
TargetText == other.TargetText &&
ProgramPath == other.ProgramPath &&
ProgramArgs == other.ProgramArgs &&
StartInDirectory == other.StartInDirectory &&
Elevation == other.Elevation &&
IfRunningAction == other.IfRunningAction &&
Visibility == other.Visibility &&
UriToOpen == other.UriToOpen;
}
public override int GetHashCode()
{
HashCode hash = default(HashCode);
hash.Add(OriginalKeys);
hash.Add(TargetKeys);
hash.Add(TargetApp);
hash.Add(OperationType);
hash.Add(TargetText);
hash.Add(ProgramPath);
hash.Add(ProgramArgs);
hash.Add(StartInDirectory);
hash.Add(Elevation);
hash.Add(IfRunningAction);
hash.Add(Visibility);
hash.Add(UriToOpen);
return hash.ToHashCode();
}
}
}

View File

@@ -1,20 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace KeyboardManagerEditorUI.Interop
{
public enum ShortcutOperationType
{
RemapShortcut = 0,
RunProgram = 1,
OpenUri = 2,
RemapText = 3,
}
}

View File

@@ -15,23 +15,9 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AssemblyName>PowerToys.KeyboardManagerEditorUI</AssemblyName>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.KeyboardManagerEditorUI.pri</ProjectPriFileName>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)</OutputPath>
</PropertyGroup>
<PropertyGroup>
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
<EnableXamlJitOptimization>true</EnableXamlJitOptimization>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\Keyboard.ico" />
<None Remove="Pages\Programs.xaml" />
<None Remove="Pages\Text.xaml" />
<None Remove="Pages\URLs.xaml" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
@@ -45,11 +31,8 @@
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
<PackageReference Include="WinUIEx" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
<PackageReference Include="Microsoft.Web.WebView2" />
</ItemGroup>
@@ -59,40 +42,7 @@
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\Keyboard.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Square150x150Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\UrlPageInputControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Styles\Button.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\Remappings.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Folder Include="Assets\" />
</ItemGroup>
<!--

View File

@@ -1,73 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<winuiex:WindowEx
<Window
x:Class="KeyboardManagerEditorUI.MainWindow"
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"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:KeyboardManagerEditorUI.Pages"
xmlns:winuiex="using:WinUIEx"
Title="KeyboardManagerEditorUI"
Width="1440"
Height="900"
MinWidth="480"
MinHeight="320"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid
x:Name="LayoutRoot"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="titleBar" Title="Keyboard Manager">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="/Assets/FluentIconsKeyboardManager.png" />
</TitleBar.LeftHeader>
</TitleBar>
<NavigationView
x:Name="RootView"
Grid.Row="1"
IsBackButtonVisible="Collapsed"
IsBackEnabled="False"
IsPaneToggleButtonVisible="False"
IsSettingsVisible="False"
PaneDisplayMode="Top"
SelectionChanged="RootView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem Content="Remappings" Tag="Remappings">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xEDA7;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Text" Tag="Text">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8D2;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Programs" Tag="Programs">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xECAA;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="URLs" Tag="URLs">
<NavigationViewItem.Icon>
<FontIcon Glyph="&#xE8A7;" />
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
<NavigationView.Content>
<Frame x:Name="NavigationFrame" Margin="0,0,0,0" />
</NavigationView.Content>
</NavigationView>
</Grid>
</winuiex:WindowEx>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button x:Name="myButton" Click="MyButton_Click">Click Me</Button>
</StackPanel>
</Window>

View File

@@ -4,14 +4,10 @@
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 KeyboardManagerEditorUI.Helpers;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -21,60 +17,26 @@ using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using WinUIEx;
namespace KeyboardManagerEditorUI
{
public sealed partial class MainWindow : WindowEx
/// <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);
this.Activated += MainWindow_Activated;
this.Closed += MainWindow_Closed;
// Set the default page
RootView.SelectedItem = RootView.MenuItems[0];
IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(this);
WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon(@"Assets\Keyboard.ico");
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
private void MyButton_Click(object sender, RoutedEventArgs e)
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
// Release the keyboard hook when the window is deactivated
KeyboardHookHelper.Instance.CleanupHook();
}
}
private void MainWindow_Closed(object sender, WindowEventArgs args)
{
KeyboardHookHelper.Instance.Dispose();
this.Activated -= MainWindow_Activated;
this.Closed -= MainWindow_Closed;
}
private void RootView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
// Cleanup the keyboard hook when the selected page changes
KeyboardHookHelper.Instance.CleanupHook();
if (args.SelectedItem is NavigationViewItem selectedItem)
{
switch ((string)selectedItem.Tag)
{
case "Remappings": NavigationFrame.Navigate(typeof(Pages.Remappings)); 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;
}
}
// Call the C++ function to check if the current remappings are valid
myButton.Content = CheckIfRemappingsAreValid() ? "Valid" : "Invalid";
}
}
}

View File

@@ -15,7 +15,7 @@
<mp:PhoneIdentity PhoneProductId="edb1d2cd-ef93-4f89-9db6-4edf04ff20a5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>Keyboard Manager</DisplayName>
<DisplayName>KeyboardManagerEditorUI</DisplayName>
<PublisherDisplayName>haoliuu</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
@@ -34,8 +34,8 @@
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="Keyboard Manager"
Description="Keyboard Manager"
DisplayName="KeyboardManagerEditorUI"
Description="KeyboardManagerEditorUI"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">

View File

@@ -1,185 +0,0 @@
<?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:controls="using:KeyboardManagerEditorUI.Controls"
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"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
x:Uid="ProgramsPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewShortcutBtn"
Height="36"
Margin="0,12,0,0"
Click="NewShortcutBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock
x:Uid="ProgramsPageNewTextBlock"
VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="ProgramsPageShortcutTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="ProgramsPageProgramsTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<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 Shortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:ProgramShortcut">
<Grid Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="476" />
<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>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<TextBlock
VerticalAlignment="Center"
Text="{x:Bind AppToRun}" />
<FontIcon
VerticalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE946;">
<ToolTipService.ToolTip>
<TextBlock>
<Run
x:Uid="ProgramsPageArgumentsRun"
FontWeight="SemiBold" />
<Run Text="{x:Bind Args}" />
</TextBlock>
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<StackPanel
Orientation="Horizontal"
Spacing="8"
Grid.ColumnSpan="4"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<ToggleSwitch
IsOn="{x:Bind IsActive}"
Toggled="ToggleSwitch_Toggled"/>
<Button
x:Uid="ProgramsPageDeleteButton"
AutomationProperties.Name="Delete program shortcut"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="ProgramsPageKeyDialog"
Width="480"
MinWidth="600"
MaxWidth="600"
MinHeight="600"
MaxHeight="600"
Height="600"
PrimaryButtonClick="KeyDialog_PrimaryButtonClick"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:AppPageInputControl x:Name="AppShortcutControl" />
</Grid>
</ContentDialog>
</Grid>
</Page>

View File

@@ -1,334 +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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using KeyboardManagerEditorUI.Settings;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
namespace KeyboardManagerEditorUI.Pages
{
public sealed partial class Programs : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
// Flag to indicate if the user is editing an existing mapping
private bool _isEditMode;
private ProgramShortcut? _editingMapping;
private bool _disposed;
// The list of text mappings
public ObservableCollection<ProgramShortcut> Shortcuts { get; set; } = new ObservableCollection<ProgramShortcut> { };
public Programs()
{
this.InitializeComponent();
try
{
_mappingService = new KeyboardMappingService();
LoadProgramShortcuts();
}
catch (Exception ex)
{
Logger.LogError("Failed to initialize KeyboardMappingService: " + ex.Message);
}
this.Unloaded += Text_Unloaded;
}
private void Text_Unloaded(object sender, RoutedEventArgs e)
{
Dispose();
}
private void LoadProgramShortcuts()
{
if (_mappingService == null)
{
return;
}
Shortcuts.Clear();
foreach (var shortcutSettings in SettingsManager.GetShortcutSettingsByOperationType(ShortcutOperationType.RunProgram))
{
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
var originalKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
Shortcuts.Add(new ProgramShortcut
{
Shortcut = originalKeyNames,
AppToRun = mapping.ProgramPath,
Args = mapping.ProgramArgs,
IsActive = shortcutSettings.IsActive,
Id = shortcutSettings.Id,
});
}
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingMapping = null;
AppShortcutControl.ClearKeys();
AppShortcutControl.SetProgramPathContent(string.Empty);
AppShortcutControl.SetProgramArgsContent(string.Empty);
await KeyDialog.ShowAsync();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is ProgramShortcut selectedMapping)
{
_isEditMode = true;
_editingMapping = selectedMapping;
AppShortcutControl.SetShortcutKeys(selectedMapping.Shortcut);
AppShortcutControl.SetProgramPathContent(selectedMapping.AppToRun);
AppShortcutControl.SetProgramArgsContent(selectedMapping.Args);
await KeyDialog.ShowAsync();
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
return;
}
List<string> keys = AppShortcutControl.GetShortcutKeys();
string programPath = AppShortcutControl.GetProgramPathContent();
string programArgs = AppShortcutControl.GetProgramArgsContent();
ElevationLevel elevationLevel = AppShortcutControl.GetElevationLevel();
StartWindowType startWindowType = AppShortcutControl.GetVisibility();
ProgramAlreadyRunningAction programAlreadyRunningAction = AppShortcutControl.GetIfRunningAction();
// Validate inputs
ValidationErrorType errorType = ValidationHelper.ValidateProgramOrUrlMapping(keys, false, string.Empty, _mappingService);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
bool saved = false;
try
{
// Delete existing mapping if in edit mode
if (_isEditMode && _editingMapping != null)
{
if (_editingMapping.Shortcut.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(_editingMapping.Shortcut[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyMapping(originalKey);
}
}
else
{
string originalKeys = string.Join(";", _editingMapping.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeys);
}
SettingsManager.RemoveShortcutKeyMappingFromSettings(_editingMapping.Id);
}
// Shortcut to text mapping
string originalKeysString = string.Join(";", keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
// if (isAppSpecific && !string.IsNullOrEmpty(appName))
// {
// saved = _mappingService.AddShortcutMapping(originalKeysString, programPath, appName, ShortcutOperationType.RemapText);
// }
// else
// {
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
{
OperationType = ShortcutOperationType.RunProgram,
OriginalKeys = originalKeysString,
TargetKeys = originalKeysString,
ProgramPath = programPath,
ProgramArgs = programArgs,
IfRunningAction = programAlreadyRunningAction,
Visibility = startWindowType,
Elevation = elevationLevel,
};
saved = _mappingService.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
_mappingService.SaveSettings();
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
LoadProgramShortcuts(); // Refresh the list
}
}
catch (Exception ex)
{
Logger.LogError("Error saving text mapping: " + ex.Message);
args.Cancel = true;
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (_mappingService == null || !(sender is Button button) || !(button.DataContext is ProgramShortcut shortcut))
{
return;
}
try
{
bool deleted = false;
if (shortcut.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys);
}
if (deleted)
{
_mappingService.SaveSettings();
Shortcuts.Remove(shortcut);
SettingsManager.RemoveShortcutKeyMappingFromSettings(shortcut.Id);
LoadProgramShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error deleting text mapping: " + ex.Message);
}
}
private void ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
// if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out (string Title, string Message) error))
// {
// ValidationTip.Title = error.Title;
// ValidationTip.Subtitle = error.Message;
// ValidationTip.IsOpen = true;
// args.Cancel = true;
// }
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_mappingService?.Dispose();
_mappingService = null;
}
_disposed = true;
}
}
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch && toggleSwitch.DataContext is ProgramShortcut shortcut && _mappingService != null)
{
try
{
if (toggleSwitch.IsOn)
{
bool saved = false;
ShortcutKeyMapping shortcutKeyMapping = SettingsManager.EditorSettings.ShortcutSettingsDictionary[shortcut.Id].Shortcut;
saved = _mappingService.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
shortcut.IsActive = true;
_mappingService.SaveSettings();
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
}
else
{
toggleSwitch.IsOn = false;
}
}
else
{
bool deleted = false;
if (shortcut.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys);
}
if (deleted)
{
shortcut.IsActive = false;
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
_mappingService.SaveSettings();
}
else
{
toggleSwitch.IsOn = true;
}
LoadProgramShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error toggling shortcut active state: " + ex.Message);
}
}
}
}
}

View File

@@ -1,218 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Page
x:Class="KeyboardManagerEditorUI.Pages.Remappings"
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:helper="using:KeyboardManagerEditorUI.Helpers"
xmlns:local="using:KeyboardManagerEditorUI.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.UI.Controls"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
x:Name="RemapInstruction"
x:Uid="RemappingsPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewRemappingBtn"
Height="36"
Margin="0,12,0,0"
Click="NewRemappingBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock x:Uid="RemappingsPageNewTextBlock" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
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
x:Uid="RemappingsPageOriginalKeysTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="RemappingsPageNewKeysTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="2"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="RemmapingsPageApplicableAppsTextBlock"
Grid.Column="2"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<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 RemappingList}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:Remapping">
<Grid Height="Auto" MinHeight="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="236" />
<ColumnDefinition Width="Auto" MinWidth="236" />
<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>
<tkcontrols:WrapPanel
MaxWidth="230"
HorizontalSpacing="4"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</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>
<tkcontrols:WrapPanel
MaxWidth="230"
HorizontalSpacing="4"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource AccentKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
Grid.Column="2"
Margin="-4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind AppName}" />
<Button
x:Uid="RemappingsPageDeleteButton"
Grid.Column="3"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="Delete remapping"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="RemappingsPageKeyDialog"
MinWidth="600"
MaxWidth="600"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:InputControl x:Name="RemappingControl" />
</Grid>
</ContentDialog>
<TeachingTip
x:Name="OrphanedKeysTeachingTip"
x:Uid="RemmapingsPageOrphanedKeysTeachingTip"
ActionButtonClick="OrphanedKeysTeachingTip_ActionButtonClick"
ActionButtonStyle="{StaticResource AccentButtonStyle}"
CloseButtonClick="OrphanedKeysTeachingTip_CloseButtonClick"
IsLightDismissEnabled="False"
PreferredPlacement="Center">
<TeachingTip.IconSource>
<SymbolIconSource Symbol="Important" />
</TeachingTip.IconSource>
</TeachingTip>
<TeachingTip
x:Name="ValidationTeachingTip"
x:Uid="RemappingsPageValidationTeachingTip"
CloseButtonClick="ValidationTeachingTip_CloseButtonClick"
IsLightDismissEnabled="True"
PreferredPlacement="Center">
<TeachingTip.IconSource>
<SymbolIconSource Symbol="Important" />
</TeachingTip.IconSource>
</TeachingTip>
</Grid>
</Page>

View File

@@ -1,370 +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.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 System.Threading.Tasks;
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 Microsoft.Windows.ApplicationModel.Resources;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Foundation.Collections;
using static KeyboardManagerEditorUI.Helpers.ValidationHelper;
namespace KeyboardManagerEditorUI.Pages
{
/// <summary>
/// The Remapping page that allow users to configure a single key or shortcut to a new key or shortcut
/// </summary>
public sealed partial class Remappings : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
// Flag to indicate if the user is editing an existing remapping
private bool _isEditMode;
private Remapping? _editingRemapping;
private bool _disposed;
// The list of single key mappings
public ObservableCollection<KeyMapping> SingleKeyMappings { get; } = new ObservableCollection<KeyMapping>();
// The list of shortcut key mappings
public ObservableCollection<ShortcutKeyMapping> ShortcutKeyMappings { get; } = new ObservableCollection<ShortcutKeyMapping>();
// The full list of remappings
public ObservableCollection<Remapping> RemappingList { get; set; }
public Remappings()
{
this.InitializeComponent();
RemappingList = new ObservableCollection<Remapping>();
_mappingService = new KeyboardMappingService();
// Load all existing remappings
LoadMappings();
this.Unloaded += Remappings_Unloaded;
}
private void Remappings_Unloaded(object sender, RoutedEventArgs e)
{
// Make sure we unregister the handler when the page is unloaded
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private void RegisterWindowActivationHandler()
{
// Get the current window that contains this page
if (App.MainWindow is Window window)
{
// Register for window activation events
window.Activated += Dialog_WindowActivated;
}
}
private void UnregisterWindowActivationHandler()
{
// Unregister to prevent memory leaks
if (App.MainWindow is Window window)
{
window.Activated -= Dialog_WindowActivated;
}
}
private void Dialog_WindowActivated(object sender, WindowActivatedEventArgs args)
{
// When window is deactivated (user switched to another app)
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
// Make sure to cleanup the keyboard hook when the window loses focus
KeyboardHookHelper.Instance.CleanupHook();
RemappingControl.ResetToggleButtons();
RemappingControl.UpdateAllAppsCheckBoxState();
}
}
private async void NewRemappingBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingRemapping = null;
RemappingControl.SetOriginalKeys(new List<string>());
RemappingControl.SetRemappedKeys(new List<string>());
RemappingControl.SetApp(false, string.Empty);
RemappingControl.SetUpToggleButtonInitialStatus();
RegisterWindowActivationHandler();
// Show the dialog to add a new remapping
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is Remapping selectedRemapping && selectedRemapping.IsEnabled)
{
// Set to edit mode
_isEditMode = true;
_editingRemapping = selectedRemapping;
RemappingControl.SetOriginalKeys(selectedRemapping.OriginalKeys);
RemappingControl.SetRemappedKeys(selectedRemapping.RemappedKeys);
RemappingControl.SetApp(!selectedRemapping.IsAllApps, selectedRemapping.AppName);
RemappingControl.SetUpToggleButtonInitialStatus();
RegisterWindowActivationHandler();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
// Reset the edit status
_isEditMode = false;
_editingRemapping = null;
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
List<string> originalKeys = RemappingControl.GetOriginalKeys();
List<string> remappedKeys = RemappingControl.GetRemappedKeys();
bool isAppSpecific = RemappingControl.GetIsAppSpecific();
string appName = RemappingControl.GetAppName();
// Make sure _mappingService is not null before validating and saving
if (_mappingService == null)
{
Logger.LogError("Mapping service is null, cannot validate mapping");
return;
}
// Validate the remapping
ValidationErrorType errorType = ValidationHelper.ValidateKeyMapping(
originalKeys, remappedKeys, isAppSpecific, appName, _mappingService, _isEditMode, _editingRemapping);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
// Check for orphaned keys
if (originalKeys.Count == 1 && _mappingService != null)
{
int originalKeyCode = _mappingService.GetKeyCodeFromName(originalKeys[0]);
if (IsKeyOrphaned(originalKeyCode, _mappingService))
{
string keyName = _mappingService.GetKeyDisplayName(originalKeyCode);
OrphanedKeysTeachingTip.Target = RemappingControl;
OrphanedKeysTeachingTip.Subtitle = $"The key {keyName} will become orphaned (inaccessible) after remapping. Please confirm if you want to proceed.";
OrphanedKeysTeachingTip.Tag = args;
OrphanedKeysTeachingTip.IsOpen = true;
args.Cancel = true;
return;
}
}
// If in edit mode, delete the existing remapping before saving the new one
if (_isEditMode && _editingRemapping != null)
{
if (!RemappingHelper.DeleteRemapping(_mappingService!, _editingRemapping))
{
return;
}
}
// If no errors, proceed to save the remapping
bool saved = RemappingHelper.SaveMapping(_mappingService!, originalKeys, remappedKeys, isAppSpecific, appName);
if (saved)
{
// Display the remapping in the list after saving
LoadMappings();
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (sender is Button button && button.DataContext is Remapping remapping)
{
if (RemappingHelper.DeleteRemapping(_mappingService!, remapping))
{
LoadMappings();
}
}
}
private void ValidationTeachingTip_CloseButtonClick(TeachingTip sender, object args)
{
sender.IsOpen = false;
}
private void OrphanedKeysTeachingTip_ActionButtonClick(TeachingTip sender, object args)
{
// User pressed continue anyway button
sender.IsOpen = false;
if (_isEditMode && _editingRemapping != null)
{
if (!RemappingHelper.DeleteRemapping(_mappingService!, _editingRemapping))
{
return;
}
}
bool saved = RemappingHelper.SaveMapping(
_mappingService!, RemappingControl.GetOriginalKeys(), RemappingControl.GetRemappedKeys(), RemappingControl.GetIsAppSpecific(), RemappingControl.GetAppName());
if (saved)
{
KeyDialog.Hide();
LoadMappings();
}
}
private void OrphanedKeysTeachingTip_CloseButtonClick(TeachingTip sender, object args)
{
// Just close the teaching tip if the user canceled
sender.IsOpen = false;
}
private void LoadMappings()
{
if (_mappingService == null)
{
return;
}
SingleKeyMappings.Clear();
ShortcutKeyMappings.Clear();
RemappingList.Clear();
// Load all single key mappings
foreach (var mapping in _mappingService.GetSingleKeyMappings())
{
SingleKeyMappings.Add(mapping);
string[] targetKeyCodes = mapping.TargetKey.Split(';');
var targetKeyNames = new List<string>();
foreach (var keyCode in targetKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
targetKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
RemappingList.Add(new Remapping
{
OriginalKeys = new List<string> { _mappingService.GetKeyDisplayName(mapping.OriginalKey) },
RemappedKeys = targetKeyNames,
IsAllApps = true,
});
}
// Load all shortcut key mappings
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapShortcut))
{
ShortcutKeyMappings.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(_mappingService.GetKeyDisplayName(code));
}
}
foreach (var keyCode in targetKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
targetKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
RemappingList.Add(new Remapping
{
OriginalKeys = originalKeyNames,
RemappedKeys = targetKeyNames,
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
AppName = string.IsNullOrEmpty(mapping.TargetApp) ? "All Apps" : mapping.TargetApp,
});
}
}
private bool ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
var (title, message) = ValidationMessages[errorType];
ValidationTeachingTip.Title = title;
ValidationTeachingTip.Subtitle = message;
ValidationTeachingTip.Target = RemappingControl;
ValidationTeachingTip.Tag = args;
ValidationTeachingTip.IsOpen = true;
args.Cancel = true;
return false;
}
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;
}
}
}
}

View File

@@ -1,200 +0,0 @@
<?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:controls="using:KeyboardManagerEditorUI.Controls"
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:tkcontrols="using:CommunityToolkit.WinUI.UI.Controls"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel Orientation="Vertical" Spacing="12">
<TextBlock
x:Uid="TextPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewShortcutBtn"
Height="36"
Margin="0,12,0,0"
Click="NewShortcutBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock x:Uid="TextPageNewTextBlock" VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{StaticResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="TextPageOriginalKeysTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="TextPageTextTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="2"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="TextPageApplicableAppsTextBlock"
Grid.Column="2"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<Rectangle
Grid.Row="0"
Grid.ColumnSpan="4"
Height="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
<ListView
x:Name="MappingsList"
Grid.Row="1"
Grid.ColumnSpan="4"
IsItemClickEnabled="True"
ItemClick="ListView_ItemClick"
ItemsSource="{x:Bind TextMappings}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:TextMapping">
<Grid MinHeight="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.ColumnSpan="4"
Height="1"
Margin="-16,0,-16,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Fill="{ThemeResource CardStrokeColorDefaultBrush}"
Opacity="0.8" />
<!-- Shortcut keys -->
<ItemsControl
Grid.Column="0"
VerticalAlignment="Center"
ItemsSource="{x:Bind Keys}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="4" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Text content -->
<TextBlock
Grid.Column="1"
Margin="-4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Text="{x:Bind Text}"
TextTrimming="CharacterEllipsis" />
<!-- App name -->
<TextBlock
Grid.Column="2"
Margin="-4,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="12"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind AppName}"
TextTrimming="CharacterEllipsis" />
<!-- Delete button -->
<Button
x:Uid="TextPageDeleteButton"
Grid.Column="3"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="Delete remapping"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="TextPageKeyDialog"
Width="480"
Height="360"
MinWidth="600"
MaxWidth="600"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:TextPageInputControl x:Name="TextInputControl" />
</Grid>
</ContentDialog>
<TeachingTip
x:Name="ValidationTip"
x:Uid="TextPageValidationTip"
IsLightDismissEnabled="True"
PreferredPlacement="Center">
<TeachingTip.IconSource>
<SymbolIconSource Symbol="Important" />
</TeachingTip.IconSource>
</TeachingTip>
</Grid>
</Page>

View File

@@ -1,332 +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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
namespace KeyboardManagerEditorUI.Pages
{
public sealed partial class Text : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
// Flag to indicate if the user is editing an existing mapping
private bool _isEditMode;
private TextMapping? _editingMapping;
private bool _disposed;
// The list of text mappings
public ObservableCollection<TextMapping> TextMappings { get; } = new ObservableCollection<TextMapping>();
public Text()
{
this.InitializeComponent();
try
{
_mappingService = new KeyboardMappingService();
LoadTextMappings();
}
catch (Exception ex)
{
Logger.LogError("Failed to initialize KeyboardMappingService: " + ex.Message);
System.Diagnostics.Debug.WriteLine(ex.Message);
}
this.Unloaded += Text_Unloaded;
}
private void Text_Unloaded(object sender, RoutedEventArgs e)
{
// Make sure we unregister the handler when the page is unloaded
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private void RegisterWindowActivationHandler()
{
// Get the current window that contains this page
if (App.MainWindow is Window window)
{
// Register for window activation events
window.Activated += Dialog_WindowActivated;
}
}
private void UnregisterWindowActivationHandler()
{
// Unregister to prevent memory leaks
if (App.MainWindow is Window window)
{
window.Activated -= Dialog_WindowActivated;
}
}
private void Dialog_WindowActivated(object sender, WindowActivatedEventArgs args)
{
// When window is deactivated (user switched to another app)
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
// Make sure to cleanup the keyboard hook when the window loses focus
KeyboardHookHelper.Instance.CleanupHook();
TextInputControl.ClearKeys();
TextInputControl.UpdateAllAppsCheckBoxState();
}
}
private void LoadTextMappings()
{
if (_mappingService == null)
{
return;
}
TextMappings.Clear();
// Load key-to-text mappings
var keyToTextMappings = _mappingService.GetKeyToTextMappings();
foreach (var mapping in keyToTextMappings)
{
TextMappings.Add(new TextMapping
{
Keys = new List<string> { _mappingService.GetKeyDisplayName(mapping.OriginalKey) },
Text = mapping.TargetText,
IsAllApps = true,
AppName = "All Apps",
});
}
// Load shortcut-to-text mappings
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.RemapText))
{
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
var originalKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(_mappingService.GetKeyDisplayName(code));
}
}
TextMappings.Add(new TextMapping
{
Keys = originalKeyNames,
Text = mapping.TargetText,
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
AppName = string.IsNullOrEmpty(mapping.TargetApp) ? "All Apps" : mapping.TargetApp,
});
}
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
{
_isEditMode = false;
_editingMapping = null;
TextInputControl.ClearKeys();
TextInputControl.SetTextContent(string.Empty);
TextInputControl.SetAppSpecific(false, string.Empty);
RegisterWindowActivationHandler();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is TextMapping selectedMapping)
{
_isEditMode = true;
_editingMapping = selectedMapping;
TextInputControl.SetShortcutKeys(selectedMapping.Keys);
TextInputControl.SetTextContent(selectedMapping.Text);
TextInputControl.SetAppSpecific(!selectedMapping.IsAllApps, selectedMapping.AppName);
RegisterWindowActivationHandler();
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
UnregisterWindowActivationHandler();
KeyboardHookHelper.Instance.CleanupHook();
// Reset the edit status
_isEditMode = false;
_editingMapping = null;
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
return;
}
List<string> keys = TextInputControl.GetShortcutKeys();
string textContent = TextInputControl.GetTextContent();
bool isAppSpecific = TextInputControl.GetIsAppSpecific();
string appName = TextInputControl.GetAppName();
// Validate inputs
ValidationErrorType errorType = ValidationHelper.ValidateTextMapping(
keys, textContent, isAppSpecific, appName, _mappingService);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
bool saved = false;
try
{
// Delete existing mapping if in edit mode
if (_isEditMode && _editingMapping != null)
{
if (_editingMapping.Keys.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(_editingMapping.Keys[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
string originalKeys = string.Join(";", _editingMapping.Keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeys, _editingMapping.IsAllApps ? string.Empty : _editingMapping.AppName);
}
}
// Add new mapping
if (keys.Count == 1)
{
// Single key to text mapping
int originalKey = _mappingService.GetKeyCodeFromName(keys[0]);
if (originalKey != 0)
{
saved = _mappingService.AddSingleKeyToTextMapping(originalKey, textContent);
}
}
else
{
// Shortcut to text mapping
string originalKeysString = string.Join(";", keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
if (isAppSpecific && !string.IsNullOrEmpty(appName))
{
saved = _mappingService.AddShortcutMapping(originalKeysString, textContent, appName, ShortcutOperationType.RemapText);
}
else
{
saved = _mappingService.AddShortcutMapping(originalKeysString, textContent, operationType: ShortcutOperationType.RemapText);
}
}
if (saved)
{
_mappingService.SaveSettings();
LoadTextMappings(); // Refresh the list
}
}
catch (Exception ex)
{
Logger.LogError("Error saving text mapping: " + ex.Message);
args.Cancel = true;
}
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (_mappingService == null || !(sender is Button button) || !(button.DataContext is TextMapping mapping))
{
return;
}
try
{
bool deleted = false;
if (mapping.Keys.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(mapping.Keys[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", mapping.Keys.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys, mapping.IsAllApps ? string.Empty : mapping.AppName);
}
if (deleted)
{
_mappingService.SaveSettings();
TextMappings.Remove(mapping);
}
}
catch (Exception ex)
{
Logger.LogError("Error deleting text mapping: " + ex.Message);
}
}
private void ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out (string Title, string Message) error))
{
ValidationTip.Title = error.Title;
ValidationTip.Subtitle = error.Message;
ValidationTip.IsOpen = true;
args.Cancel = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_mappingService?.Dispose();
_mappingService = null;
}
_disposed = true;
}
}
}
}

View File

@@ -1,159 +0,0 @@
<?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:controls="using:KeyboardManagerEditorUI.Controls"
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"
mc:Ignorable="d">
<Grid Padding="16">
<StackPanel
Orientation="Vertical"
Spacing="12">
<TextBlock
x:Uid="UrlPageInstructionTextBlock"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
<Button
x:Name="NewShortcutBtn"
Height="36"
Margin="0,12,0,0"
Click="NewShortcutBtn_Click">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE109;" />
<TextBlock
x:Uid="UrlPageNewTextBlock"
VerticalAlignment="Center" />
</StackPanel>
</Button>
<Grid
HorizontalAlignment="Stretch"
Background="{ThemeResource LayerFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{ThemeResource OverlayCornerRadius}">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="236" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="84" />
</Grid.ColumnDefinitions>
<TextBlock
x:Uid="UrlPageShortcutTextBlock"
Grid.Column="0"
Margin="16,-2,0,0"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<AppBarSeparator
Grid.Column="1"
Margin="-6,4,0,4"
HorizontalAlignment="Left" />
<TextBlock
x:Uid="UrlPageUrlTextBlock"
Grid.Column="1"
Margin="12,-2,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<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 Shortcuts}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="helper:URLShortcut">
<Grid Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="348" />
<ColumnDefinition Width="476" />
<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>
<controls:KeyVisual
HorizontalAlignment="Left"
Content="{Binding}"
Style="{StaticResource DefaultKeyVisualStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<HyperlinkButton
Grid.Column="1"
Margin="-12,0,0,0"
Content="{x:Bind URL}" />
<Button
x:Uid="UrlPageDeleteButton"
Grid.ColumnSpan="4"
Margin="0,0,4,0"
Padding="8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="Delete URL shortcut"
Background="Transparent"
BorderThickness="0"
Click="DeleteButton_Click">
<FontIcon
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Glyph="&#xE74D;" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackPanel>
<ContentDialog
x:Name="KeyDialog"
x:Uid="UrlPageKeyDialog"
Width="480"
Height="360"
MinWidth="600"
MaxWidth="600"
PrimaryButtonClick="KeyDialog_PrimaryButtonClick"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<Grid>
<controls:UrlPageInputControl x:Name="UrlShortcutControl" />
</Grid>
</ContentDialog>
</Grid>
</Page>

View File

@@ -1,261 +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.Collections.Generic;
using System.Collections.ObjectModel;
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;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
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;
private bool _isEditMode;
private URLShortcut? _editingMapping;
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();
LoadUrlShortcuts();
}
public void LoadUrlShortcuts()
{
if (_mappingService == null)
{
return;
}
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);
}
}
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)
{
_isEditMode = false;
_editingMapping = null;
UrlShortcutControl.ClearKeys();
UrlShortcutControl.SetUrlPathContent(string.Empty);
await KeyDialog.ShowAsync();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is URLShortcut urlShortcut)
{
_isEditMode = true;
_editingMapping = urlShortcut;
UrlShortcutControl.SetShortcutKeys(urlShortcut.Shortcut);
UrlShortcutControl.SetUrlPathContent(urlShortcut.URL);
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;
}
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (_mappingService == null)
{
return;
}
List<string> keys = UrlShortcutControl.GetShortcutKeys();
string urlPath = UrlShortcutControl.GetUrlPathContent();
// Validate inputs
ValidationErrorType errorType = ValidationHelper.ValidateProgramOrUrlMapping(keys, false, string.Empty, _mappingService);
if (errorType != ValidationErrorType.NoError)
{
ShowValidationError(errorType, args);
return;
}
bool saved = false;
try
{
// Delete existing mapping if in edit mode
if (_isEditMode && _editingMapping != null)
{
if (_editingMapping.Shortcut.Count == 1)
{
int originalKey = _mappingService.GetKeyCodeFromName(_editingMapping.Shortcut[0]);
if (originalKey != 0)
{
_mappingService.DeleteSingleKeyMapping(originalKey);
}
}
else
{
string originalKeys = string.Join(";", _editingMapping.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.DeleteShortcutMapping(originalKeys);
}
}
// Shortcut to text mapping
string originalKeysString = string.Join(";", keys.Select(k => _mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
// if (isAppSpecific && !string.IsNullOrEmpty(appName))
// {
// saved = _mappingService.AddShortcutMapping(originalKeysString, programPath, appName, ShortcutOperationType.RemapText);
// }
// else
// {
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
{
OperationType = ShortcutOperationType.OpenUri,
OriginalKeys = originalKeysString,
TargetKeys = originalKeysString,
UriToOpen = urlPath,
};
saved = _mappingService.AddShorcutMapping(shortcutKeyMapping);
if (saved)
{
_mappingService.SaveSettings();
LoadUrlShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error saving text mapping: " + ex.Message);
args.Cancel = true;
}
}
private void ShowValidationError(ValidationErrorType errorType, ContentDialogButtonClickEventArgs args)
{
// if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out (string Title, string Message) error))
// {
// ValidationTip.Title = error.Title;
// ValidationTip.Subtitle = error.Message;
// ValidationTip.IsOpen = true;
// args.Cancel = true;
// }
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
if (_mappingService == null || !(sender is Button button) || !(button.DataContext is URLShortcut shortcut))
{
return;
}
try
{
bool deleted = false;
if (shortcut.Shortcut.Count == 1)
{
// Single key mapping
int originalKey = _mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]);
if (originalKey != 0)
{
deleted = _mappingService.DeleteSingleKeyToTextMapping(originalKey);
}
}
else
{
// Shortcut mapping
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService.GetKeyCodeFromName(k)));
deleted = _mappingService.DeleteShortcutMapping(originalKeys);
}
if (deleted)
{
_mappingService.SaveSettings();
Shortcuts.Remove(shortcut);
LoadUrlShortcuts();
}
}
catch (Exception ex)
{
Logger.LogError("Error deleting text mapping: " + ex.Message);
}
}
}
}

View File

@@ -1,20 +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.Collections.Generic;
using KeyboardManagerEditorUI.Interop;
namespace KeyboardManagerEditorUI.Settings
{
public class EditorSettings
{
public Dictionary<string, ShortcutSettings> ShortcutSettingsDictionary { get; set; } = new Dictionary<string, ShortcutSettings>();
public Dictionary<string, List<string>> ProfileDictionary { get; set; } = new Dictionary<string, List<string>>();
public Dictionary<ShortcutOperationType, List<string>> ShortcutsByOperationType { get; set; } = new Dictionary<ShortcutOperationType, List<string>>();
public string ActiveProfile { get; set; } = string.Empty;
}
}

View File

@@ -1,225 +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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Interop;
namespace KeyboardManagerEditorUI.Settings
{
internal static class SettingsManager
{
private static readonly string _settingsDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"PowerToys",
"KeyboardManager");
private static readonly string _settingsFilePath = Path.Combine(_settingsDirectory, "settings.json");
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
};
private static KeyboardMappingService? _mappingService;
public static EditorSettings EditorSettings { get; set; }
static SettingsManager()
{
_mappingService = new KeyboardMappingService();
EditorSettings = LoadSettings();
}
public static EditorSettings LoadSettings()
{
try
{
if (!File.Exists(_settingsFilePath))
{
EditorSettings createdSettings = CreateSettingsFromKeyboardManagerService();
WriteSettings(createdSettings);
return createdSettings;
}
string json = File.ReadAllText(_settingsFilePath);
var settings = JsonSerializer.Deserialize<EditorSettings>(json, _jsonOptions);
return settings ?? new EditorSettings();
}
catch (Exception)
{
return new EditorSettings();
}
}
public static bool WriteSettings(EditorSettings editorSettings)
{
try
{
Directory.CreateDirectory(_settingsDirectory);
string json = JsonSerializer.Serialize(editorSettings, _jsonOptions);
File.WriteAllText(_settingsFilePath, json);
return true;
}
catch (Exception)
{
return false;
}
}
public static bool WriteSettings()
{
return WriteSettings(EditorSettings);
}
private static EditorSettings CreateSettingsFromKeyboardManagerService()
{
EditorSettings settings = new EditorSettings();
foreach (ShortcutKeyMapping mapping in _mappingService!.GetShortcutMappings())
{
string guid = Guid.NewGuid().ToString();
ShortcutSettings shortcutSettings = new ShortcutSettings
{
Id = guid,
Shortcut = mapping,
IsActive = true,
};
settings.ShortcutSettingsDictionary[guid] = shortcutSettings;
if (settings.ShortcutsByOperationType.TryGetValue(mapping.OperationType, out List<string>? value))
{
value.Add(guid);
}
else
{
settings.ShortcutsByOperationType[mapping.OperationType] = new List<string> { guid };
}
}
return settings;
}
public static void CorrelateServiceAndEditorMappings()
{
bool shortcutSettingsChanged = false;
if (_mappingService is null)
{
return;
}
List<ShortcutKeyMapping> shortcutKeyMappings = _mappingService.GetShortcutMappings();
foreach (ShortcutKeyMapping mapping in shortcutKeyMappings)
{
if (!EditorSettings.ShortcutSettingsDictionary.Values.Any(s => s.Shortcut.Equals(mapping)))
{
shortcutSettingsChanged = true;
string guid = Guid.NewGuid().ToString();
ShortcutSettings shortcutSettings = new ShortcutSettings
{
Id = guid,
Shortcut = mapping,
IsActive = true,
};
EditorSettings.ShortcutSettingsDictionary[guid] = shortcutSettings;
if (EditorSettings.ShortcutsByOperationType.TryGetValue(mapping.OperationType, out List<string>? value))
{
value.Add(guid);
}
else
{
EditorSettings.ShortcutsByOperationType[mapping.OperationType] = new List<string> { guid };
}
}
}
foreach (ShortcutSettings shortcutSettings in EditorSettings.ShortcutSettingsDictionary.Values.ToList())
{
if (!shortcutKeyMappings.Any(m => m.Equals(shortcutSettings.Shortcut)))
{
shortcutSettingsChanged = true;
ToggleShortcutKeyMappingActiveState(shortcutSettings.Id);
}
}
if (shortcutSettingsChanged)
{
WriteSettings();
}
}
public static List<ShortcutSettings> GetShortcutSettingsByOperationType(ShortcutOperationType operationType)
{
List<ShortcutSettings> shortcutSettingsListForType = new List<ShortcutSettings>();
if (EditorSettings.ShortcutsByOperationType.TryGetValue(operationType, out List<string>? guids))
{
foreach (string guid in guids)
{
if (EditorSettings.ShortcutSettingsDictionary.TryGetValue(guid, out ShortcutSettings? shortcutSettings))
{
shortcutSettingsListForType.Add(shortcutSettings);
}
}
}
return shortcutSettingsListForType;
}
public static void AddShortcutKeyMappingToSettings(ShortcutKeyMapping shortcutKeyMapping)
{
ShortcutSettings shortcutSettings = new ShortcutSettings
{
Id = Guid.NewGuid().ToString(),
Shortcut = shortcutKeyMapping,
IsActive = true,
};
EditorSettings.ShortcutSettingsDictionary[shortcutSettings.Id] = shortcutSettings;
if (EditorSettings.ShortcutsByOperationType.TryGetValue(shortcutSettings.Shortcut.OperationType, out List<string>? value))
{
value.Add(shortcutSettings.Id);
}
else
{
EditorSettings.ShortcutsByOperationType[shortcutSettings.Shortcut.OperationType] = new List<string> { shortcutSettings.Id };
}
WriteSettings();
}
public static void RemoveShortcutKeyMappingFromSettings(string guid)
{
ShortcutOperationType operationType = EditorSettings.ShortcutSettingsDictionary[guid].Shortcut.OperationType;
EditorSettings.ShortcutSettingsDictionary.Remove(guid);
if (EditorSettings.ShortcutsByOperationType.TryGetValue(operationType, out List<string>? value))
{
value.Remove(guid);
}
WriteSettings();
}
public static void ToggleShortcutKeyMappingActiveState(string guid)
{
if (EditorSettings.ShortcutSettingsDictionary.TryGetValue(guid, out ShortcutSettings? shortcutSettings))
{
shortcutSettings.IsActive = !shortcutSettings.IsActive;
WriteSettings();
}
}
}
}

View File

@@ -1,24 +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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KeyboardManagerEditorUI.Interop;
namespace KeyboardManagerEditorUI.Settings
{
public class ShortcutSettings
{
public string Id { get; set; } = string.Empty;
public ShortcutKeyMapping Shortcut { get; set; } = new ShortcutKeyMapping();
public List<string> Profiles { get; set; } = new List<string>();
public bool IsActive { get; set; } = true;
}
}

View File

@@ -1,312 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="RemappingsPageApplicableAppsTextBlock.Text" xml:space="preserve">
<value>Applicable apps</value>
</data>
<data name="RemappingsPageDeleteButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Delete app shortcut</value>
</data>
<data name="RemappingsPageKeyDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="RemappingsPageKeyDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="RemappingsPageKeyDialog.Title" xml:space="preserve">
<value>Remappings</value>
</data>
<data name="RemappingsPageInstructionTextBlock.Text" xml:space="preserve">
<value>Remappings allow you to reconfigure a single key or shortcut to a new key or shortcut</value>
</data>
<data name="RemappingsPageNewTextBlock.Text" xml:space="preserve">
<value>New</value>
</data>
<data name="RemappingsPageOriginalKeysTextBlock.Text" xml:space="preserve">
<value>Original key(s)</value>
</data>
<data name="RemmapingsPageOrphanedKeysTeachingTip.ActionButtonContent" xml:space="preserve">
<value>Continue anyway</value>
</data>
<data name="RemmapingsPageOrphanedKeysTeachingTip.CloseButtonContent" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="RemmapingsPageOrphanedKeysTeachingTip.Title" xml:space="preserve">
<value>Orphaned Keys Warning</value>
</data>
<data name="RemappingsPageValidationTeachingTip.CloseButtonContent" xml:space="preserve">
<value>OK</value>
</data>
<data name="RemappingsPageNewKeysTextBlock.Text" xml:space="preserve">
<value>New key(s)</value>
</data>
<data name="TextPageInstructionTextBlock.Text" xml:space="preserve">
<value>Text shortcuts allow you to insert text in any input field when you use the configured keyboard shortcut</value>
</data>
<data name="TextPageNewTextBlock.Text" xml:space="preserve">
<value>New</value>
</data>
<data name="TextPageOriginalKeysTextBlock.Text" xml:space="preserve">
<value>Original key(s)</value>
</data>
<data name="TextPageApplicableAppsTextBlock.Text" xml:space="preserve">
<value>Applicable apps</value>
</data>
<data name="TextPageTextTextBlock.Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TextPageKeyDialog.Title" xml:space="preserve">
<value>Text shortcut</value>
</data>
<data name="TextPageKeyDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="TextPageKeyDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="TextPageValidationTip.CloseButtonContent" xml:space="preserve">
<value>OK</value>
</data>
<data name="ProgramsPageInstructionTextBlock.Text" xml:space="preserve">
<value>Program shortcuts allow you to open specific applications when you use the configured shortcut</value>
</data>
<data name="ProgramsPageKeyDialog.Title" xml:space="preserve">
<value>New program shortcut</value>
</data>
<data name="AppPageInputControlExampleTextBox.Header" xml:space="preserve">
<value>Program</value>
</data>
<data name="ProgramsPageNewTextBlock.Text" xml:space="preserve">
<value>New</value>
</data>
<data name="ProgramsPageShortcutTextBlock.Text" xml:space="preserve">
<value>Shortcut</value>
</data>
<data name="ProgramsPageProgramsTextBlock.Text" xml:space="preserve">
<value>Program</value>
</data>
<data name="ProgramsPageArgumentsRun.Text" xml:space="preserve">
<value>Arguments</value>
</data>
<data name="ProgramsPageKeyDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="ProgramsPageKeyDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="UrlPageInstructionTextBlock.Text" xml:space="preserve">
<value>URL shortcuts allow you to open a URL when you use the configured shortcut</value>
</data>
<data name="UrlPageNewTextBlock.Text" xml:space="preserve">
<value>New</value>
</data>
<data name="UrlPageUrlTextBlock.Text" xml:space="preserve">
<value>URL</value>
</data>
<data name="UrlPageShortcutTextBlock.Text" xml:space="preserve">
<value>Shortcut</value>
</data>
<data name="UrlPageKeyDialog.Title" xml:space="preserve">
<value>New URL shortcut</value>
</data>
<data name="UrlPageKeyDialog.PrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="UrlPageKeyDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="InputControlOriginalKeysTextBlock.Text" xml:space="preserve">
<value>Original key(s)</value>
</data>
<data name="InputControlNewKeysTextBlock.Text" xml:space="preserve">
<value>New key(s)</value>
</data>
<data name="InputControlAllAppsCheckBox.Content" xml:space="preserve">
<value>Only apply this remapping to a specific application</value>
</data>
<data name="InputControlAllAppsTextBox.PlaceholderText" xml:space="preserve">
<value>e.g.: outlook.exe</value>
</data>
<data name="InputControlAllAppsTextBox.Header" xml:space="preserve">
<value>Application name</value>
</data>
<data name="TextPageInputControlShortcutKeysTextBlock.Text" xml:space="preserve">
<value>Shortcut key(s)</value>
</data>
<data name="TextPageInputControlTextContentTextBox.PlaceholderText" xml:space="preserve">
<value>Enter text that will be inserted when the shortcut is pressed</value>
</data>
<data name="TextPageInputControlTextContentTextBox.Header" xml:space="preserve">
<value>Text to insert</value>
</data>
<data name="TextPageDeleteButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Delete</value>
</data>
<data name="TextPageInputControlAllAppsCheckBox.Content" xml:space="preserve">
<value>Only apply this remapping to a specific application</value>
</data>
<data name="TextPageInputControlAllAppsTextBox.Header" xml:space="preserve">
<value>Application name</value>
</data>
<data name="TextPageInputControlAllAppsTextBox.PlaceholderText" xml:space="preserve">
<value>e.g.: outlook.exe</value>
</data>
<data name="ProgramsPageDeleteButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Delete remapping</value>
</data>
<data name="AppPageInputControlShortcutTextBlock.Text" xml:space="preserve">
<value>Shortcut</value>
</data>
<data name="AppPageInputControlPathSelectButton.Content" xml:space="preserve">
<value>Select Program</value>
</data>
<data name="AppPageInputControlArgumentsTextBox.Header" xml:space="preserve">
<value>Arguments</value>
</data>
<data name="AppPageInputControlStartInTextBox.Header" xml:space="preserve">
<value>Start in</value>
</data>
<data name="AppPageInputControlStartInSelectButton.Content" xml:space="preserve">
<value>Select directory</value>
</data>
<data name="AppPageInputControlElevationComboBox.Header" xml:space="preserve">
<value>Elevation</value>
</data>
<data name="AppPageInputControlIfRunningComboBox.Header" xml:space="preserve">
<value>If running action</value>
</data>
<data name="AppPageInputControlVisibilityComboBox.Header" xml:space="preserve">
<value>Visibility</value>
</data>
<data name="UrlPageDeleteButton.ToolTipService.ToolTip" xml:space="preserve">
<value>Delete URL remapping</value>
</data>
<data name="UrlPageInputControlShortcutTextBlock.Text" xml:space="preserve">
<value>Shortcut key(s)</value>
</data>
<data name="UrlPageInputControlToOpenTextBox.Header" xml:space="preserve">
<value>URL to open</value>
</data>
<data name="UrlPageInputControlToOpenTextBox.PlaceholderText" xml:space="preserve">
<value>https://github.com/microsoft/PowerToys</value>
</data>
<data name="AppPageInputControlExtraOptionsTextBlock.Text" xml:space="preserve">
<value>More options</value>
</data>
</root>

View File

@@ -1,200 +0,0 @@
<?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">
<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>

View File

@@ -47,7 +47,7 @@ public:
Normal = 0,
Hidden = 1,
Minimized = 2,
Maximized = 3
Maximized = 2
};
enum ProgramAlreadyRunningAction

View File

@@ -36,7 +36,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private const string KeyboardManagerEditorPath = "KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe";
// New WinUI3 editor path. Still in development and do NOT use it in production.
private const string KeyboardManagerEditorUIPath = "WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.exe";
private const string KeyboardManagerEditorUIPath = "KeyboardManagerEditorUI\\PowerToys.KeyboardManagerEditorUI.exe";
private Process editor;
@@ -280,7 +280,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Only read the registry value if the experimentation toggle is enabled
if (isExperimentationEnabled)
{
editorPath = KeyboardManagerEditorUIPath;
// Read the registry value to determine which editor to launch
var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\PowerToys\Keyboard Manager");
if (key != null && (int?)key.GetValue("UseNewEditor") == 1)
{
editorPath = KeyboardManagerEditorUIPath;
}
// Close the registry key
key?.Close();
}
}
catch (Exception e)
@@ -292,12 +300,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
string path = Path.Combine(Environment.CurrentDirectory, editorPath);
Logger.LogInfo($"Starting {PowerToyName} editor from {path}");
// InvariantCulture: type represents the KeyboardManagerEditorType enum va
ProcessStartInfo startInfo = new ProcessStartInfo(path);
startInfo.UseShellExecute = true; // LOAD BEARING
startInfo.Arguments = $"{type.ToString(CultureInfo.InvariantCulture)} {Environment.ProcessId}";
System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", null);
editor = Process.Start(startInfo);
// InvariantCulture: type represents the KeyboardManagerEditorType enum value
editor = Process.Start(path, $"{type.ToString(CultureInfo.InvariantCulture)} {Environment.ProcessId}");
}
catch (Exception e)
{