[Keyboard Manager] Updated WinUI3 KBM and toggles (#45649)

## Running the Project
**Option 1: Test via runner**
1. Check out branch `niels9001/kbm-ux-consolidation`
2. Build PowerToys project
3. Manually build `Modules/KeyboardManagerEditorUI` project separately
4. Run `runner` project
5. Ensure experimental features are enabled in general settings (should
be on by default)
6. Launch keyboard manager via settings app

**Option 2: Test via installer**
1. Install PowerToys via installer on azure pipeline
1. Launch keyboard manager

## Validation
For each page (Text, Remappings, Programs, URLs):
* Create shortcuts with variable options and ensure they run as expected
* Delete shortcuts and ensure they no longer execute
* Try to create invalid shortcuts to check for proper validation
* Ensure created shortcuts appear in Power Toys Settings Keyboard
manager page
* Try toggling shortcuts
* Try deleting shortcuts while toggled off

### UI
* Any feedback on UI design appreciated as well
<img width="1071" height="671" alt="image"
src="https://github.com/user-attachments/assets/d2e81de0-6d92-4189-9a33-32e94cce74f7"
/>
<img width="2142" height="1341" alt="image"
src="https://github.com/user-attachments/assets/0e4e5685-fdf1-4dfd-ba52-a2e5bc9a66db"
/>



Closes: #15870
Closes: #31902
Closes: #45302
Closes: #36227
Closes: #16093
Closes: #13409
Closes: #9919
Closes:  #9482
Closes: #8798
Closes:  #7054
Closes: #2733
Closes: #2027
Closes: #30167

---------

Co-authored-by: Hao Liu <liuhao3418@gmail.com>
Co-authored-by: chenmy77 <162882040+chenmy77@users.noreply.github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com>
Co-authored-by: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com>
Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This commit is contained in:
Zach Teutsch
2026-03-04 15:46:42 -05:00
committed by GitHub
parent d20ae940d5
commit f651d1a611
85 changed files with 8080 additions and 399 deletions

View File

@@ -12,16 +12,16 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
using Microsoft.PowerToys.Settings.Utilities;
using Microsoft.Win32;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
@@ -38,8 +38,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
// Default editor path. Can be removed once the new WinUI3 editor is released.
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 = "KeyboardManagerEditorUI\\PowerToys.KeyboardManagerEditorUI.exe";
private const string KeyboardManagerEditorUIPath = "WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.exe";
private Process editor;
@@ -57,6 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private ICommand _remapKeyboardCommand;
private ICommand _editShortcutCommand;
private ICommand _openNewEditorCommand;
private KeyboardManagerProfile _profile;
private Func<string, int> SendConfigMSG { get; }
@@ -181,7 +181,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
{
[ModuleName] = [ToggleShortcut],
[ModuleName] = [ToggleShortcut, EditorShortcut],
};
return hotkeysDict;
@@ -192,11 +192,55 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
get => Settings.Properties.ToggleShortcut;
set
{
if (Settings.Properties.ToggleShortcut != value)
if (value != Settings.Properties.ToggleShortcut)
{
Settings.Properties.ToggleShortcut = value ?? Settings.Properties.DefaultToggleShortcut;
Settings.Properties.ToggleShortcut = value == null ? Settings.Properties.DefaultToggleShortcut : value;
OnPropertyChanged(nameof(ToggleShortcut));
NotifySettingsChanged();
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
KeyboardManagerSettings.ModuleName,
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.KeyboardManagerSettings)));
}
}
}
public bool UseNewEditor
{
get => Settings.Properties.UseNewEditor;
set
{
if (Settings.Properties.UseNewEditor != value)
{
Settings.Properties.UseNewEditor = value;
OnPropertyChanged(nameof(UseNewEditor));
NotifySettingsChanged();
}
}
}
public HotkeySettings EditorShortcut
{
get => Settings.Properties.EditorShortcut;
set
{
if (value != Settings.Properties.EditorShortcut)
{
Settings.Properties.EditorShortcut = value == null ? Settings.Properties.DefaultEditorShortcut : value;
OnPropertyChanged(nameof(EditorShortcut));
NotifySettingsChanged();
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
KeyboardManagerSettings.ModuleName,
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.KeyboardManagerSettings)));
}
}
}
@@ -262,6 +306,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public ICommand EditShortcutCommand => _editShortcutCommand ?? (_editShortcutCommand = new RelayCommand(OnEditShortcut));
public ICommand OpenNewEditorCommand => _openNewEditorCommand ?? (_openNewEditorCommand = new RelayCommand(OnOpenNewEditor));
public void OnRemapKeyboard()
{
OpenEditor((int)KeyboardManagerEditorType.KeyEditor);
@@ -272,6 +318,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OpenEditor((int)KeyboardManagerEditorType.ShortcutEditor);
}
public void OnOpenNewEditor()
{
OpenNewEditor();
}
private static void BringProcessToFront(Process process)
{
if (process == null)
@@ -305,41 +356,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
return;
}
// Launch the new editor if:
// 1. the experimentation toggle is enabled in the settings
// 2. the new WinUI3 editor is enabled in the registry. The registry value does not exist by default and is only used for development purposes
string editorPath = KeyboardManagerEditorPath;
try
{
// Check if the experimentation toggle is enabled in the settings
var settingsUtils = SettingsUtils.Default;
bool isExperimentationEnabled = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.EnableExperimentation;
// Only read the registry value if the experimentation toggle is enabled
if (isExperimentationEnabled)
{
// 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)
{
// Fall back to the default editor path if any exception occurs
Logger.LogError("Failed to launch the new WinUI3 Editor", e);
}
string path = Path.Combine(Environment.CurrentDirectory, editorPath);
string path = Path.Combine(Environment.CurrentDirectory, KeyboardManagerEditorPath);
Logger.LogInfo($"Starting {ModuleName} editor from {path}");
// InvariantCulture: type represents the KeyboardManagerEditorType enum value
editor = Process.Start(path, $"{type.ToString(CultureInfo.InvariantCulture)} {Environment.ProcessId}");
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);
PowerToysTelemetry.Log.WriteEvent(new ModuleLaunchedFromSettingsEvent("KeyboardManagerClassic"));
}
catch (Exception e)
{
@@ -347,6 +373,39 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private void OpenNewEditor()
{
try
{
if (editor != null && editor.HasExited)
{
Logger.LogInfo($"Previous instance of {ModuleName} editor exited");
editor = null;
}
if (editor != null)
{
Logger.LogInfo($"The {ModuleName} editor instance {editor.Id} exists. Bringing the process to the front");
BringProcessToFront(editor);
return;
}
string path = Path.Combine(Environment.CurrentDirectory, KeyboardManagerEditorUIPath);
Logger.LogInfo($"Starting {ModuleName} new editor from {path}");
System.Environment.SetEnvironmentVariable("MICROSOFT_WINDOWSAPPRUNTIME_BASE_DIRECTORY", null);
ProcessStartInfo startInfo = new ProcessStartInfo(path);
startInfo.UseShellExecute = true; // LOAD BEARING
startInfo.Arguments = $"{Environment.ProcessId}";
editor = Process.Start(startInfo);
PowerToysTelemetry.Log.WriteEvent(new ModuleLaunchedFromSettingsEvent("KeyboardManagerWinUI"));
}
catch (Exception e)
{
Logger.LogError($"Exception encountered when opening the new {ModuleName} editor", e);
}
}
public void NotifyFileChanged()
{
OnPropertyChanged(nameof(RemapKeys));