mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-26 05:57:35 +01:00
Compare commits
4 Commits
zt/kbm-tog
...
stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdaf644f02 | ||
|
|
329c8c2616 | ||
|
|
b081e413b1 | ||
|
|
290fa01adf |
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -69,14 +69,14 @@ internal sealed partial class SampleMarkdownImagesPage : ContentPage
|
||||
|
||||
### Web URL
|
||||
```xml
|
||||

|
||||

|
||||
```
|
||||

|
||||

|
||||
|
||||
```xml
|
||||

|
||||

|
||||
```
|
||||

|
||||

|
||||
|
||||
### File URL (PNG):
|
||||
```xml
|
||||
|
||||
@@ -81,7 +81,7 @@ Result:
|
||||
|
||||
Result:
|
||||
|
||||

|
||||

|
||||
|
||||
### Links
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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="" />
|
||||
|
||||
<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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -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="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="Text" Tag="Text">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="Programs" Tag="Programs">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem Content="URLs" Tag="URLs">
|
||||
<NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
</NavigationView.MenuItems>
|
||||
<NavigationView.Content>
|
||||
<Frame x:Name="NavigationFrame" Margin="0,0,0,0" />
|
||||
</NavigationView.Content>
|
||||
</NavigationView>
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button x:Name="myButton" Click="MyButton_Click">Click Me</Button>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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="" />
|
||||
<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="">
|
||||
<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="" />
|
||||
</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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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="" />
|
||||
<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="" />
|
||||
</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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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="" />
|
||||
<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="" />
|
||||
</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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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="" />
|
||||
<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="" />
|
||||
</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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
Normal = 0,
|
||||
Hidden = 1,
|
||||
Minimized = 2,
|
||||
Maximized = 3
|
||||
Maximized = 2
|
||||
};
|
||||
|
||||
enum ProgramAlreadyRunningAction
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user