[FHL] Save Remapping Config and Data (#37787)

* load URL remapping

* Save Remapping Config and Data

* update mock data for testing

* fix app mapping

* update xaml
This commit is contained in:
Hao Liu
2025-03-06 17:56:13 +08:00
committed by GitHub
parent 1e3108efbc
commit e52ac85a1b
13 changed files with 247 additions and 24 deletions

View File

@@ -495,6 +495,20 @@ bool GetShortcutRemapByType(void* config, int operationType, int index, Shortcut
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,
@@ -527,6 +541,18 @@ bool GetShortcutRemapByType(void* config, int operationType, int index, Shortcut
std::wstring name = layoutMap.GetKeyName(static_cast<DWORD>(keyCode));
wcsncpy_s(keyName, maxCount, name.c_str(), _TRUNCATE);
}
int GetKeyCodeFromName(const wchar_t* keyName)
{
if (keyName == nullptr)
{
return 0;
}
LayoutMap layoutMap;
std::wstring name(keyName);
return static_cast<int>(layoutMap.GetKeyFromName(name));
}
}
// Test function to call the remapping helper function

View File

@@ -56,12 +56,16 @@ extern "C"
__declspec(dllexport) bool AddSingleKeyRemap(void* config, int originalKey, int targetKey);
__declspec(dllexport) bool AddSingleKeyToTextRemap(void* config, int originalKey, const wchar_t* text);
__declspec(dllexport) bool AddSingleKeyToShortcutRemap(void* config,
int originalKey,
const wchar_t* targetKeys);
__declspec(dllexport) bool AddShortcutRemap(void* config,
const wchar_t* originalKeys,
const wchar_t* targetKeys,
const wchar_t* targetApp);
__declspec(dllexport) void GetKeyDisplayName(int keyCode, wchar_t* keyName, int maxCount);
__declspec(dllexport) int GetKeyCodeFromName(const wchar_t* keyName);
__declspec(dllexport) void FreeString(wchar_t* str);
}
extern "C" __declspec(dllexport) bool CheckIfRemappingsAreValid();

View File

@@ -18,7 +18,7 @@ namespace KeyboardManagerEditorUI.Helpers
public bool IsAllApps { get; set; } = true;
public string AppName { get; set; } = "All apps";
public string AppName { get; set; } = "All Apps";
public bool IsEnabled { get; set; } = true;
}

View File

@@ -68,6 +68,13 @@ namespace KeyboardManagerEditorUI.Interop
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddSingleKeyRemap(IntPtr config, int originalKey, int targetKey);
[DllImport(DllName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddSingleKeyToShortcutRemap(
IntPtr config,
int originalKey,
[MarshalAs(UnmanagedType.LPWStr)] string targetKeys);
[DllImport(DllName)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool AddShortcutRemap(
@@ -76,6 +83,10 @@ namespace KeyboardManagerEditorUI.Interop
[MarshalAs(UnmanagedType.LPWStr)] string targetKeys,
[MarshalAs(UnmanagedType.LPWStr)] string targetApp);
[DllImport(DllName)]
internal static extern int GetKeyCodeFromName(
[MarshalAs(UnmanagedType.LPWStr)] string keyName);
[DllImport(DllName)]
internal static extern void FreeString(IntPtr str);

View File

@@ -129,8 +129,30 @@ namespace KeyboardManagerEditorUI.Interop
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
}
public bool AddSingleKeyMapping(int originalKey, string targetKeys)
{
if (string.IsNullOrEmpty(targetKeys))
{
return false;
}
if (!targetKeys.Contains(';') && int.TryParse(targetKeys, out int targetKey))
{
return KeyboardManagerInterop.AddSingleKeyRemap(_configHandle, originalKey, targetKey);
}
else
{
return KeyboardManagerInterop.AddSingleKeyToShortcutRemap(_configHandle, originalKey, targetKeys);
}
}
public bool AddShortcutMapping(string originalKeys, string targetKeys, string targetApp = "")
{
if (string.IsNullOrEmpty(originalKeys) || string.IsNullOrEmpty(targetKeys))
{
return false;
}
return KeyboardManagerInterop.AddShortcutRemap(_configHandle, originalKeys, targetKeys, targetApp);
}

View File

@@ -55,9 +55,6 @@
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Assets\" />
</ItemGroup>
<ItemGroup>
<Page Update="Styles\CommonStyle.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -119,8 +119,7 @@
Grid.Column="1"
Orientation="Horizontal"
Spacing="8">
<Image Width="16" Source="ms-appx:///Assets/Images/FluentIconsKeyboardManager.png" />
<TextBlock VerticalAlignment="Center" Text="Keyboardmanager.exe" />
<TextBlock VerticalAlignment="Center" Text="WindowsTerminal.exe" />
<FontIcon
VerticalAlignment="Center"
FontSize="14"
@@ -173,8 +172,8 @@
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<StackPanel Orientation="Horizontal" Spacing="8">
<styles:KeyVisual Content="Win" VisualType="SmallOutline" />
<styles:KeyVisual Content="U" VisualType="SmallOutline" />
<styles:KeyVisual Content="Win (Left)" VisualType="SmallOutline" />
<styles:KeyVisual Content="T" VisualType="SmallOutline" />
</StackPanel>
</ToggleButton.Content>
</ToggleButton>
@@ -193,7 +192,7 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Header="Application" Text="Keyboardmanager.exe" />
<TextBox Header="Application" Text="WindowsTerminal.exe" />
<Button
Grid.Column="1"
Height="32"
@@ -203,11 +202,11 @@
<TextBox
Grid.Row="1"
Header="Arguments"
Text="-config json" />
Text="-config" />
<TextBox
Grid.Row="2"
Header="Start in directory"
Text="C:\Users\Dev\PowerToys\bin" />
Text="C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.21.10351.0_x64__8wekyb3d8bbwe" />
<Button
Grid.Row="2"
Grid.Column="1"

View File

@@ -30,7 +30,7 @@ namespace KeyboardManagerEditorUI.Pages
this.InitializeComponent();
Shortcuts = new ObservableCollection<URLShortcut>();
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "U", }, URL = "https://www.bing.com" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win (Left)", "T", }, URL = "https://www.bing.com" });
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)

View File

@@ -5,6 +5,8 @@
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;
@@ -12,6 +14,7 @@ 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;
@@ -123,7 +126,7 @@ namespace KeyboardManagerEditorUI.Pages
OriginalKeys = originalKeyNames,
RemappedKeys = targetKeyNames,
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
AppName = mapping.TargetApp ?? string.Empty,
AppName = string.IsNullOrEmpty(mapping.TargetApp) ? "All Apps" : mapping.TargetApp,
});
}
}
@@ -158,7 +161,19 @@ namespace KeyboardManagerEditorUI.Pages
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
{
ShortcutControl.SetOriginalKeys(new List<string>());
ShortcutControl.SetRemappedKeys(new List<string>());
ShortcutControl.SetApp(false, string.Empty);
KeyDialog.PrimaryButtonClick += KeyDialog_PrimaryButtonClick;
await KeyDialog.ShowAsync();
KeyDialog.PrimaryButtonClick -= KeyDialog_PrimaryButtonClick;
}
private void KeyDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
SaveCurrentMapping();
LoadMappings();
}
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
@@ -171,5 +186,75 @@ namespace KeyboardManagerEditorUI.Pages
await KeyDialog.ShowAsync();
}
}
public static int GetKeyCode(string keyName)
{
return KeyboardManagerInterop.GetKeyCodeFromName(keyName);
}
private void SaveCurrentMapping()
{
if (_mappingService == null)
{
return;
}
try
{
List<string> originalKeys = ShortcutControl.GetOriginalKeys();
List<string> remappedKeys = ShortcutControl.GetRemappedKeys();
bool isAppSpecific = ShortcutControl.GetIsAppSpecific();
string appName = ShortcutControl.GetAppName();
// mock data
// originalKeys = ["A", "Ctrl"];
// remappedKeys = ["B"];
if (originalKeys == null || originalKeys.Count == 0 || remappedKeys == null || remappedKeys.Count == 0)
{
return;
}
if (originalKeys.Count == 1)
{
int originalKey = GetKeyCode(originalKeys[0]);
if (originalKey != 0)
{
if (remappedKeys.Count == 1)
{
int targetKey = GetKeyCode(remappedKeys[0]);
if (targetKey != 0)
{
_mappingService.AddSingleKeyMapping(originalKey, targetKey);
}
}
else
{
string targetKeys = string.Join(";", remappedKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture)));
_mappingService.AddSingleKeyMapping(originalKey, targetKeys);
}
}
}
else
{
string originalKeysString = string.Join(";", originalKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture)));
string targetKeysString = string.Join(";", remappedKeys.Select(k => GetKeyCode(k).ToString(CultureInfo.InvariantCulture)));
if (isAppSpecific && !string.IsNullOrEmpty(appName))
{
_mappingService.AddShortcutMapping(originalKeysString, targetKeysString, appName);
}
else
{
_mappingService.AddShortcutMapping(originalKeysString, targetKeysString);
}
}
_mappingService.SaveSettings();
}
catch (Exception ex)
{
Logger.LogError("Error saving shortcut mapping: " + ex.Message);
}
}
}
}

View File

@@ -33,11 +33,8 @@ namespace KeyboardManagerEditorUI.Pages
this.InitializeComponent();
Shortcuts = new ObservableCollection<URLShortcut>();
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "Hello" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "P", }, URL = "Nice!" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "I like it" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "U", }, URL = "Yes" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Ctrl", "P" }, URL = "OK" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Alt (Left)", "H" }, URL = "Hello" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win (Left)", "P", }, URL = "PowerToys" });
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)

View File

@@ -156,16 +156,15 @@
Style="{StaticResource CustomShortcutToggleButtonStyle}">
<ToggleButton.Content>
<StackPanel Orientation="Horizontal" Spacing="8">
<styles:KeyVisual Content="Shift" VisualType="SmallOutline" />
<styles:KeyVisual Content="Win" VisualType="SmallOutline" />
<styles:KeyVisual Content="M" VisualType="SmallOutline" />
<styles:KeyVisual Content="Win (Left)" VisualType="SmallOutline" />
<styles:KeyVisual Content="B" VisualType="SmallOutline" />
</StackPanel>
</ToggleButton.Content>
</ToggleButton>
<TextBox
Margin="0,24,0,0"
Header="URL to open"
Text="https://www.microsoft.com" />
Text="https://www.bing.com" />
</StackPanel>
</ContentDialog>
</Grid>

View File

@@ -7,8 +7,11 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -24,21 +27,60 @@ 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
public sealed partial class URLs : Page, IDisposable
{
private KeyboardMappingService? _mappingService;
private bool _disposed;
public ObservableCollection<URLShortcut> Shortcuts { get; set; }
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
public URLs()
{
this.InitializeComponent();
Shortcuts = new ObservableCollection<URLShortcut>();
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "https://www.microsoft.com" });
_mappingService = new KeyboardMappingService();
foreach (var mapping in _mappingService.GetShortcutMappingsByType(ShortcutOperationType.OpenUri))
{
string[] originalKeyCodes = mapping.OriginalKeys.Split(';');
var originalKeyNames = new List<string>();
foreach (var keyCode in originalKeyCodes)
{
if (int.TryParse(keyCode, out int code))
{
originalKeyNames.Add(GetKeyDisplayName(code));
}
}
var shortcut = new URLShortcut
{
Shortcut = originalKeyNames,
URL = mapping.UriToOpen,
};
Shortcuts.Add(shortcut);
}
/*
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "https://www.microsoft.com" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "P", }, URL = "https://www.bing.com" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Shift", "Win", "M" }, URL = "https://www.windows.com" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Win", "U", }, URL = "https://www.bing.com" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Ctrl", "P" }, URL = "https://www.surface.com" });
Shortcuts.Add(new URLShortcut() { Shortcut = new List<string>() { "Alt", "Ctrl", "Shift" }, URL = "https://www.bing.com" });
*/
}
public static string GetKeyDisplayName(int keyCode)
{
var keyName = new StringBuilder(64);
GetKeyDisplayName(keyCode, keyName, keyName.Capacity);
return keyName.ToString();
}
private async void NewShortcutBtn_Click(object sender, RoutedEventArgs e)
@@ -50,5 +92,26 @@ namespace KeyboardManagerEditorUI.Pages
{
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;
}
}
}
}

View File

@@ -36,6 +36,26 @@ namespace KeyboardManagerEditorUI.Styles
RemappedKeys.ItemsSource = keys;
}
public List<string> GetOriginalKeys()
{
return OriginalKeys.ItemsSource as List<string> ?? new List<string>();
}
public List<string> GetRemappedKeys()
{
return RemappedKeys.ItemsSource as List<string> ?? new List<string>();
}
public bool GetIsAppSpecific()
{
return AllAppsCheckBox.IsChecked ?? false;
}
public string GetAppName()
{
return AppNameTextBox.Text ?? string.Empty;
}
private void OriginalToggleBtn_Checked(object sender, RoutedEventArgs e)
{
RemappedToggleBtn.IsChecked = false;