mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
[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:
4
.github/actions/spell-check/expect.txt
vendored
4
.github/actions/spell-check/expect.txt
vendored
@@ -205,6 +205,7 @@ comdlg
|
|||||||
comexp
|
comexp
|
||||||
cominterop
|
cominterop
|
||||||
commandpalette
|
commandpalette
|
||||||
|
commoncontrols
|
||||||
compmgmt
|
compmgmt
|
||||||
COMPOSITIONFULL
|
COMPOSITIONFULL
|
||||||
CONFIGW
|
CONFIGW
|
||||||
@@ -677,6 +678,7 @@ jpnime
|
|||||||
Jsons
|
Jsons
|
||||||
jsonval
|
jsonval
|
||||||
jxr
|
jxr
|
||||||
|
kbmcontrols
|
||||||
keybd
|
keybd
|
||||||
KEYBDDATA
|
KEYBDDATA
|
||||||
KEYBDINPUT
|
KEYBDINPUT
|
||||||
@@ -1530,6 +1532,7 @@ tlc
|
|||||||
TPMLEFTALIGN
|
TPMLEFTALIGN
|
||||||
TPMRETURNCMD
|
TPMRETURNCMD
|
||||||
TNP
|
TNP
|
||||||
|
Toggleable
|
||||||
Toolhelp
|
Toolhelp
|
||||||
toolwindow
|
toolwindow
|
||||||
TOPDOWNDIB
|
TOPDOWNDIB
|
||||||
@@ -2203,6 +2206,7 @@ wft
|
|||||||
wikimedia
|
wikimedia
|
||||||
wikipedia
|
wikipedia
|
||||||
windowedge
|
windowedge
|
||||||
|
WINDOWSAPPRUNTIME
|
||||||
windowsml
|
windowsml
|
||||||
winexe
|
winexe
|
||||||
winforms
|
winforms
|
||||||
|
|||||||
@@ -106,7 +106,12 @@
|
|||||||
"PowerToys.SvgThumbnailProvider.dll",
|
"PowerToys.SvgThumbnailProvider.dll",
|
||||||
"PowerToys.SvgThumbnailProvider.exe",
|
"PowerToys.SvgThumbnailProvider.exe",
|
||||||
"PowerToys.SvgThumbnailProviderCpp.dll",
|
"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.HostsModuleInterface.dll",
|
||||||
"WinUI3Apps\\PowerToys.HostsUILib.dll",
|
"WinUI3Apps\\PowerToys.HostsUILib.dll",
|
||||||
"WinUI3Apps\\PowerToys.Hosts.dll",
|
"WinUI3Apps\\PowerToys.Hosts.dll",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<NuGetAuditMode>direct</NuGetAuditMode>
|
<NuGetAuditMode>direct</NuGetAuditMode>
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
|
||||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||||
|
<RestoreEnablePackagePruning Condition=" '$(VisualStudioVersion)' == '17.0'">false </RestoreEnablePackagePruning>
|
||||||
|
|
||||||
<!-- Enable Microsoft.Testing.Platform -->
|
<!-- Enable Microsoft.Testing.Platform -->
|
||||||
<EnableMSTestRunner>true</EnableMSTestRunner>
|
<EnableMSTestRunner>true</EnableMSTestRunner>
|
||||||
|
|||||||
@@ -497,6 +497,31 @@
|
|||||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngine/KeyboardManagerEngine.vcxproj" Id="ba661f5b-1d5a-4ffc-9bf1-fc39df280bdd" />
|
<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" />
|
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManagerEngineLibrary.vcxproj" Id="e496b7fc-1e99-4bab-849b-0e8367040b02" />
|
||||||
</Folder>
|
</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/">
|
<Folder Name="/modules/keyboardmanager/Tests/">
|
||||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorTest/KeyboardManagerEditorTest.vcxproj" Id="62173d9a-6724-4c00-a1c8-fb646480a9ec" />
|
<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" />
|
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj" Id="7f4b3a60-bc27-45a7-8000-68b0b6ea7466" />
|
||||||
@@ -720,31 +745,6 @@
|
|||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
</Project>
|
</Project>
|
||||||
</Folder>
|
</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/">
|
<Folder Name="/modules/MouseWithoutBorders/">
|
||||||
<Project Path="src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj">
|
<Project Path="src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj">
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls">
|
xmlns:commoncontrols="using:Microsoft.PowerToys.Common.UI.Controls">
|
||||||
|
|
||||||
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="local:KeyVisual" />
|
<Style BasedOn="{StaticResource DefaultKeyVisualStyle}" TargetType="commoncontrols:KeyVisual" />
|
||||||
|
|
||||||
<Style x:Key="DefaultKeyVisualStyle" TargetType="local:KeyVisual">
|
<Style x:Key="DefaultKeyVisualStyle" TargetType="commoncontrols:KeyVisual">
|
||||||
<Setter Property="MinWidth" Value="16" />
|
<Setter Property="MinWidth" Value="16" />
|
||||||
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
|
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
|
||||||
<Setter Property="IsTabStop" Value="False" />
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate TargetType="local:KeyVisual">
|
<ControlTemplate TargetType="commoncontrols:KeyVisual">
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="KeyHolder"
|
x:Name="KeyHolder"
|
||||||
MinWidth="{TemplateBinding MinWidth}"
|
MinWidth="{TemplateBinding MinWidth}"
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<Grid.BackgroundTransition>
|
<Grid.BackgroundTransition>
|
||||||
<BrushTransition Duration="0:0:0.083" />
|
<BrushTransition Duration="0:0:0.083" />
|
||||||
</Grid.BackgroundTransition>
|
</Grid.BackgroundTransition>
|
||||||
<local:KeyCharPresenter
|
<commoncontrols:KeyCharPresenter
|
||||||
x:Name="KeyPresenter"
|
x:Name="KeyPresenter"
|
||||||
Margin="{TemplateBinding Padding}"
|
Margin="{TemplateBinding Padding}"
|
||||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
@@ -87,12 +87,12 @@
|
|||||||
<Style
|
<Style
|
||||||
x:Key="SubtleKeyVisualStyle"
|
x:Key="SubtleKeyVisualStyle"
|
||||||
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
||||||
TargetType="local:KeyVisual">
|
TargetType="commoncontrols:KeyVisual">
|
||||||
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
<Setter Property="Background" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
<Setter Property="BorderBrush" Value="{ThemeResource SubtleFillColorTransparentBrush}" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate TargetType="local:KeyVisual">
|
<ControlTemplate TargetType="commoncontrols:KeyVisual">
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="KeyHolder"
|
x:Name="KeyHolder"
|
||||||
MinWidth="{TemplateBinding MinWidth}"
|
MinWidth="{TemplateBinding MinWidth}"
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<Grid.BackgroundTransition>
|
<Grid.BackgroundTransition>
|
||||||
<BrushTransition Duration="0:0:0.083" />
|
<BrushTransition Duration="0:0:0.083" />
|
||||||
</Grid.BackgroundTransition>
|
</Grid.BackgroundTransition>
|
||||||
<local:KeyCharPresenter
|
<commoncontrols:KeyCharPresenter
|
||||||
x:Name="KeyPresenter"
|
x:Name="KeyPresenter"
|
||||||
Margin="{TemplateBinding Padding}"
|
Margin="{TemplateBinding Padding}"
|
||||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
@@ -145,14 +145,14 @@
|
|||||||
<Style
|
<Style
|
||||||
x:Key="AccentKeyVisualStyle"
|
x:Key="AccentKeyVisualStyle"
|
||||||
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
||||||
TargetType="local:KeyVisual">
|
TargetType="commoncontrols:KeyVisual">
|
||||||
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
|
<Setter Property="Background" Value="{ThemeResource AccentFillColorDefaultBrush}" />
|
||||||
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
|
<Setter Property="Foreground" Value="{ThemeResource TextOnAccentFillColorPrimaryBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="{ThemeResource AccentControlElevationBorderBrush}" />
|
<Setter Property="BorderBrush" Value="{ThemeResource AccentControlElevationBorderBrush}" />
|
||||||
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
|
<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate TargetType="local:KeyVisual">
|
<ControlTemplate TargetType="commoncontrols:KeyVisual">
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="KeyHolder"
|
x:Name="KeyHolder"
|
||||||
MinWidth="{TemplateBinding MinWidth}"
|
MinWidth="{TemplateBinding MinWidth}"
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
<Grid.BackgroundTransition>
|
<Grid.BackgroundTransition>
|
||||||
<BrushTransition Duration="0:0:0.083" />
|
<BrushTransition Duration="0:0:0.083" />
|
||||||
</Grid.BackgroundTransition>
|
</Grid.BackgroundTransition>
|
||||||
<local:KeyCharPresenter
|
<commoncontrols:KeyCharPresenter
|
||||||
x:Name="KeyPresenter"
|
x:Name="KeyPresenter"
|
||||||
Margin="{TemplateBinding Padding}"
|
Margin="{TemplateBinding Padding}"
|
||||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
|||||||
@@ -287,4 +287,8 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
{
|
{
|
||||||
return CommonSharedConstants::POWER_DISPLAY_TERMINATE_APP_MESSAGE;
|
return CommonSharedConstants::POWER_DISPLAY_TERMINATE_APP_MESSAGE;
|
||||||
}
|
}
|
||||||
|
hstring Constants::OpenNewKeyboardManagerEvent()
|
||||||
|
{
|
||||||
|
return CommonSharedConstants::OPEN_NEW_KEYBOARD_MANAGER_EVENT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ namespace winrt::PowerToys::Interop::implementation
|
|||||||
static hstring PowerDisplayToggleMessage();
|
static hstring PowerDisplayToggleMessage();
|
||||||
static hstring PowerDisplayApplyProfileMessage();
|
static hstring PowerDisplayApplyProfileMessage();
|
||||||
static hstring PowerDisplayTerminateAppMessage();
|
static hstring PowerDisplayTerminateAppMessage();
|
||||||
|
static hstring OpenNewKeyboardManagerEvent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ namespace PowerToys
|
|||||||
static String PowerDisplayToggleMessage();
|
static String PowerDisplayToggleMessage();
|
||||||
static String PowerDisplayApplyProfileMessage();
|
static String PowerDisplayApplyProfileMessage();
|
||||||
static String PowerDisplayTerminateAppMessage();
|
static String PowerDisplayTerminateAppMessage();
|
||||||
|
static String OpenNewKeyboardManagerEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ namespace CommonSharedConstants
|
|||||||
const wchar_t LIGHT_SWITCH_LIGHT_THEME_EVENT[] = L"Local\\PowerToysLightSwitch-LightThemeEvent-50077121-2ffc-4841-9c86-ab1bd3f9baca";
|
const wchar_t LIGHT_SWITCH_LIGHT_THEME_EVENT[] = L"Local\\PowerToysLightSwitch-LightThemeEvent-50077121-2ffc-4841-9c86-ab1bd3f9baca";
|
||||||
const wchar_t LIGHT_SWITCH_DARK_THEME_EVENT[] = L"Local\\PowerToysLightSwitch-DarkThemeEvent-b3a835c0-eaa2-49b0-b8eb-f793e3df3368";
|
const wchar_t LIGHT_SWITCH_DARK_THEME_EVENT[] = L"Local\\PowerToysLightSwitch-DarkThemeEvent-b3a835c0-eaa2-49b0-b8eb-f793e3df3368";
|
||||||
|
|
||||||
|
// Path to events used by Keyboard Manager
|
||||||
|
const wchar_t OPEN_NEW_KEYBOARD_MANAGER_EVENT[] = L"Local\\PowerToysOpenNewKeyboardManagerEvent-9c1d2e3f-4b5a-6c7d-8e9f-0a1b2c3d4e5f";
|
||||||
|
|
||||||
// used from quick access window
|
// used from quick access window
|
||||||
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";
|
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";
|
||||||
const wchar_t CMDPAL_EXIT_EVENT[] = L"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd";
|
const wchar_t CMDPAL_EXIT_EVENT[] = L"Local\\PowerToysCmdPal-ExitEvent-eb73f6be-3f22-4b36-aee3-62924ba40bfd";
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.Common.Properties {
|
|||||||
// class via a tool like ResGen or Visual Studio.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// with the /str option, or rebuild your VS project.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
internal class Resources {
|
internal class Resources {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
using PowerToys.Interop;
|
||||||
|
|
||||||
|
namespace PowerToysExtension.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the new Keyboard Manager editor.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed partial class OpenNewKeyboardManagerEditorCommand : InvokableCommand
|
||||||
|
{
|
||||||
|
public OpenNewKeyboardManagerEditorCommand()
|
||||||
|
{
|
||||||
|
Name = "Open New Keyboard Manager Editor";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CommandResult Invoke()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.OpenNewKeyboardManagerEvent());
|
||||||
|
evt.Set();
|
||||||
|
return CommandResult.Dismiss();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return CommandResult.ShowToast($"Failed to open New Keyboard Manager Editor: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using PowerToysExtension.Commands;
|
using PowerToysExtension.Commands;
|
||||||
using PowerToysExtension.Helpers;
|
using PowerToysExtension.Helpers;
|
||||||
@@ -18,6 +21,16 @@ internal sealed class KeyboardManagerModuleCommandProvider : ModuleCommandProvid
|
|||||||
var title = SettingsWindow.KBM.ModuleDisplayName();
|
var title = SettingsWindow.KBM.ModuleDisplayName();
|
||||||
var icon = SettingsWindow.KBM.ModuleIcon();
|
var icon = SettingsWindow.KBM.ModuleIcon();
|
||||||
|
|
||||||
|
if (IsUseNewEditorEnabled())
|
||||||
|
{
|
||||||
|
yield return new ListItem(new OpenNewKeyboardManagerEditorCommand())
|
||||||
|
{
|
||||||
|
Title = Resources.KeyboardManager_OpenNewEditor_Title,
|
||||||
|
Subtitle = Resources.KeyboardManager_OpenNewEditor_Subtitle,
|
||||||
|
Icon = icon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.KBM, title) { Id = "com.microsoft.powertoys.keyboardManager.openSettings" })
|
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.KBM, title) { Id = "com.microsoft.powertoys.keyboardManager.openSettings" })
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
@@ -25,4 +38,37 @@ internal sealed class KeyboardManagerModuleCommandProvider : ModuleCommandProvid
|
|||||||
Icon = icon,
|
Icon = icon,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsUseNewEditorEnabled()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settingsPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"Microsoft",
|
||||||
|
"PowerToys",
|
||||||
|
"Keyboard Manager",
|
||||||
|
"settings.json");
|
||||||
|
|
||||||
|
if (!File.Exists(settingsPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = File.ReadAllText(settingsPath);
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
|
||||||
|
if (doc.RootElement.TryGetProperty("properties", out var properties) &&
|
||||||
|
properties.TryGetProperty("useNewEditor", out var useNewEditor))
|
||||||
|
{
|
||||||
|
return useNewEditor.GetBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If we can't read the setting, default to not showing the command
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -861,6 +861,24 @@ namespace PowerToysExtension.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Keyboard Manager: Open Editor.
|
||||||
|
/// </summary>
|
||||||
|
internal static string KeyboardManager_OpenNewEditor_Title {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("KeyboardManager_OpenNewEditor_Title", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Open the Keyboard Manager remap editor.
|
||||||
|
/// </summary>
|
||||||
|
internal static string KeyboardManager_OpenNewEditor_Subtitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("KeyboardManager_OpenNewEditor_Subtitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Open Light Switch settings.
|
/// Looks up a localized string similar to Open Light Switch settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -408,6 +408,12 @@
|
|||||||
<data name="KeyboardManager_Settings_Subtitle" xml:space="preserve">
|
<data name="KeyboardManager_Settings_Subtitle" xml:space="preserve">
|
||||||
<value>Open Keyboard Manager settings</value>
|
<value>Open Keyboard Manager settings</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="KeyboardManager_OpenNewEditor_Title" xml:space="preserve">
|
||||||
|
<value>Keyboard Manager: Open Editor</value>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_OpenNewEditor_Subtitle" xml:space="preserve">
|
||||||
|
<value>Open the Keyboard Manager remap editor</value>
|
||||||
|
</data>
|
||||||
<!-- Light Switch Module -->
|
<!-- Light Switch Module -->
|
||||||
<data name="LightSwitch_Toggle_Title" xml:space="preserve">
|
<data name="LightSwitch_Toggle_Title" xml:space="preserve">
|
||||||
<value>Light Switch: Toggle theme</value>
|
<value>Light Switch: Toggle theme</value>
|
||||||
|
|||||||
@@ -140,12 +140,14 @@ namespace EditorHelpers
|
|||||||
// Win+L
|
// Win+L
|
||||||
if (shortcut.winKey != ModifierKey::Disabled && shortcut.ctrlKey == ModifierKey::Disabled && shortcut.altKey == ModifierKey::Disabled && shortcut.shiftKey == ModifierKey::Disabled && shortcut.actionKey == 0x4C)
|
if (shortcut.winKey != ModifierKey::Disabled && shortcut.ctrlKey == ModifierKey::Disabled && shortcut.altKey == ModifierKey::Disabled && shortcut.shiftKey == ModifierKey::Disabled && shortcut.actionKey == 0x4C)
|
||||||
{
|
{
|
||||||
|
Logger::info(L"Illegal shortcut detected: Win+L");
|
||||||
return ShortcutErrorType::WinL;
|
return ShortcutErrorType::WinL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Alt+Del
|
// Ctrl+Alt+Del
|
||||||
if (shortcut.winKey == ModifierKey::Disabled && shortcut.ctrlKey != ModifierKey::Disabled && shortcut.altKey != ModifierKey::Disabled && shortcut.shiftKey == ModifierKey::Disabled && shortcut.actionKey == VK_DELETE)
|
if (shortcut.winKey == ModifierKey::Disabled && shortcut.ctrlKey != ModifierKey::Disabled && shortcut.altKey != ModifierKey::Disabled && shortcut.shiftKey == ModifierKey::Disabled && shortcut.actionKey == VK_DELETE)
|
||||||
{
|
{
|
||||||
|
Logger::info(L"Illegal shortcut detected: Ctrl+Alt+Del");
|
||||||
return ShortcutErrorType::CtrlAltDel;
|
return ShortcutErrorType::CtrlAltDel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,8 @@ ShortcutControl& ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, s
|
|||||||
textInputMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed UIElement
|
textInputMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed UIElement
|
||||||
unicodeTextKeysInput.Margin(textInputMargin);
|
unicodeTextKeysInput.Margin(textInputMargin);
|
||||||
|
|
||||||
unicodeTextKeysInput.AcceptsReturn(false);
|
unicodeTextKeysInput.AcceptsReturn(true);
|
||||||
|
unicodeTextKeysInput.TextWrapping(TextWrapping::Wrap);
|
||||||
//unicodeTextKeysInput.Visibility(Visibility::Collapsed);
|
//unicodeTextKeysInput.Visibility(Visibility::Collapsed);
|
||||||
unicodeTextKeysInput.Width(EditorConstants::TableDropDownHeight);
|
unicodeTextKeysInput.Width(EditorConstants::TableDropDownHeight);
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ SingleKeyRemapControl::SingleKeyRemapControl(StackPanel table, StackPanel row, c
|
|||||||
textBoxMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed grid
|
textBoxMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed grid
|
||||||
textBoxMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing;
|
textBoxMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing;
|
||||||
textInput.Margin(textBoxMargin);
|
textInput.Margin(textBoxMargin);
|
||||||
textInput.AcceptsReturn(false);
|
textInput.AcceptsReturn(true);
|
||||||
|
textInput.TextWrapping(TextWrapping::Wrap);
|
||||||
textInput.Visibility(Visibility::Collapsed);
|
textInput.Visibility(Visibility::Collapsed);
|
||||||
textInput.Width(EditorConstants::TableDropDownHeight);
|
textInput.Width(EditorConstants::TableDropDownHeight);
|
||||||
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(textInput);
|
singleKeyRemapControlLayout.as<StackPanel>().Children().Append(textInput);
|
||||||
|
|||||||
@@ -1,20 +1,752 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "KeyboardManagerEditorLibraryWrapper.h"
|
#include "KeyboardManagerEditorLibraryWrapper.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <common/utils/logger_helper.h>
|
#include <common/utils/logger_helper.h>
|
||||||
#include <keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h>
|
#include <keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h>
|
||||||
|
#include <keyboardmanager/KeyboardManagerEditorLibrary/EditorHelpers.h>
|
||||||
|
#include <common/interop/keyboard_layout.h>
|
||||||
|
|
||||||
// Test function to call the remapping helper function
|
extern "C"
|
||||||
|
|
||||||
bool CheckIfRemappingsAreValid()
|
|
||||||
{
|
{
|
||||||
RemapBuffer remapBuffer;
|
void* CreateMappingConfiguration()
|
||||||
|
{
|
||||||
// Mock valid key to key remappings
|
return new MappingConfiguration();
|
||||||
remapBuffer.push_back(RemapBufferRow{ RemapBufferItem({ (DWORD)0x41, (DWORD)0x42 }), std::wstring() });
|
}
|
||||||
remapBuffer.push_back(RemapBufferRow{ RemapBufferItem({ (DWORD)0x42, (DWORD)0x43 }), std::wstring() });
|
|
||||||
|
void DestroyMappingConfiguration(void* config)
|
||||||
auto result = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(remapBuffer);
|
{
|
||||||
|
delete static_cast<MappingConfiguration*>(config);
|
||||||
return result == ShortcutErrorType::NoError;
|
}
|
||||||
|
|
||||||
|
bool LoadMappingSettings(void* config)
|
||||||
|
{
|
||||||
|
return static_cast<MappingConfiguration*>(config)->LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveMappingSettings(void* config)
|
||||||
|
{
|
||||||
|
return static_cast<MappingConfiguration*>(config)->SaveSettingsToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t* AllocateAndCopyString(const std::wstring& str)
|
||||||
|
{
|
||||||
|
size_t len = str.length();
|
||||||
|
wchar_t* buffer = new wchar_t[len + 1];
|
||||||
|
wcscpy_s(buffer, len + 1, str.c_str());
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSingleKeyRemapCount(void* config)
|
||||||
|
{
|
||||||
|
auto mapping = static_cast<MappingConfiguration*>(config);
|
||||||
|
return static_cast<int>(mapping->singleKeyReMap.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetSingleKeyRemap(void* config, int index, 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;
|
||||||
|
|
||||||
|
switch (operationType)
|
||||||
|
{
|
||||||
|
case 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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // RunProgram
|
||||||
|
if (kv.second.targetShortcut.index() == 1 &&
|
||||||
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||||
|
{
|
||||||
|
shouldAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // OpenURI
|
||||||
|
if (kv.second.targetShortcut.index() == 1 &&
|
||||||
|
std::get<Shortcut>(kv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||||
|
{
|
||||||
|
shouldAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
if (kv.second.targetShortcut.index() == 2)
|
||||||
|
{
|
||||||
|
shouldAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
switch (operationType)
|
||||||
|
{
|
||||||
|
case 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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // RunProgram
|
||||||
|
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||||
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::RunProgram)
|
||||||
|
{
|
||||||
|
shouldAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // OpenURI
|
||||||
|
if (shortcutKv.second.targetShortcut.index() == 1 &&
|
||||||
|
std::get<Shortcut>(shortcutKv.second.targetShortcut).operationType == Shortcut::OperationType::OpenURI)
|
||||||
|
{
|
||||||
|
shouldAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
if (shortcutKv.second.targetShortcut.index() == 2)
|
||||||
|
{
|
||||||
|
shouldAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
switch (targetShortcut.operationType)
|
||||||
|
{
|
||||||
|
case 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"");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Shortcut::OperationType::OpenURI:
|
||||||
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||||
|
mapping->targetText = AllocateAndCopyString(L"");
|
||||||
|
mapping->programPath = AllocateAndCopyString(L"");
|
||||||
|
mapping->programArgs = AllocateAndCopyString(L"");
|
||||||
|
mapping->uriToOpen = AllocateAndCopyString(targetShortcut.uriToOpen);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
mapping->targetKeys = AllocateAndCopyString(targetKeysStr);
|
||||||
|
mapping->targetText = AllocateAndCopyString(L"");
|
||||||
|
mapping->programPath = AllocateAndCopyString(L"");
|
||||||
|
mapping->programArgs = AllocateAndCopyString(L"");
|
||||||
|
mapping->uriToOpen = AllocateAndCopyString(L"");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Logger::info(L"Getting key code for key name: {0}", keyName ? keyName : L"null");
|
||||||
|
|
||||||
|
if (keyName == nullptr)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutMap layoutMap;
|
||||||
|
std::wstring name(keyName);
|
||||||
|
int keyCode = static_cast<int>(layoutMap.GetKeyFromName(name));
|
||||||
|
Logger::info(L"Key code for key name {0}: {1}", keyName, keyCode);
|
||||||
|
return keyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
Logger::info(L"Checking if shortcut is illegal: {0}", shortcutKeys ? shortcutKeys : L"null");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list of keyboard keys in Editor
|
||||||
|
int GetKeyboardKeysList(bool isShortcut, KeyNamePair* keyList, int maxCount)
|
||||||
|
{
|
||||||
|
if (keyList == nullptr || maxCount <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutMap layoutMap;
|
||||||
|
auto keyNameList = layoutMap.GetKeyNameList(isShortcut);
|
||||||
|
|
||||||
|
int count = (std::min)(static_cast<int>(keyNameList.size()), maxCount);
|
||||||
|
|
||||||
|
// Transfer the key list to the output struct format
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
keyList[i].keyCode = static_cast<int>(keyNameList[i].first);
|
||||||
|
wcsncpy_s(keyList[i].keyName, keyNameList[i].second.c_str(), _countof(keyList[i].keyName) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
@@ -4,4 +4,83 @@
|
|||||||
#include <keyboardmanager/common/Input.h>
|
#include <keyboardmanager/common/Input.h>
|
||||||
#include <keyboardmanager/common/MappingConfiguration.h>
|
#include <keyboardmanager/common/MappingConfiguration.h>
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) bool CheckIfRemappingsAreValid();
|
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);
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<Application
|
|
||||||
x:Class="KeyboardManagerEditorUI.App"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:KeyboardManagerEditorUI">
|
|
||||||
<Application.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
|
||||||
<!-- Other merged dictionaries here -->
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
<!-- Other app resources here -->
|
|
||||||
</ResourceDictionary>
|
|
||||||
</Application.Resources>
|
|
||||||
</Application>
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,48 @@
|
|||||||
|
<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 DefaultIconLabelControlStyle}" TargetType="local:IconLabelControl" />
|
||||||
|
|
||||||
|
<Style x:Key="DefaultIconLabelControlStyle" TargetType="local:IconLabelControl">
|
||||||
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="local:IconLabelControl">
|
||||||
|
<Grid
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
ColumnSpacing="4"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<FontIcon
|
||||||
|
x:Name="TypeIcon"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||||
|
FontSize="{TemplateBinding FontSize}"
|
||||||
|
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
x:Name="LabelText"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,-1,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="{TemplateBinding FontSize}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
Text="{TemplateBinding Label}"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// 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 KeyboardManagerEditorUI.Helpers;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Controls
|
||||||
|
{
|
||||||
|
[TemplatePart(Name = TypeIconPart, Type = typeof(FontIcon))]
|
||||||
|
[TemplatePart(Name = LabelTextPart, Type = typeof(TextBlock))]
|
||||||
|
public sealed partial class IconLabelControl : Control
|
||||||
|
{
|
||||||
|
private const string TypeIconPart = "TypeIcon";
|
||||||
|
private const string LabelTextPart = "LabelText";
|
||||||
|
|
||||||
|
private FontIcon? _typeIcon;
|
||||||
|
private TextBlock? _labelText;
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ActionTypeProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(ActionType),
|
||||||
|
typeof(ActionType),
|
||||||
|
typeof(IconLabelControl),
|
||||||
|
new PropertyMetadata(ActionType.Text, OnActionTypeChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LabelProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(Label),
|
||||||
|
typeof(string),
|
||||||
|
typeof(IconLabelControl),
|
||||||
|
new PropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
public ActionType ActionType
|
||||||
|
{
|
||||||
|
get => (ActionType)GetValue(ActionTypeProperty);
|
||||||
|
set => SetValue(ActionTypeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Label
|
||||||
|
{
|
||||||
|
get => (string)GetValue(LabelProperty);
|
||||||
|
set => SetValue(LabelProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconLabelControl()
|
||||||
|
{
|
||||||
|
this.DefaultStyleKey = typeof(IconLabelControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
_typeIcon = GetTemplateChild(TypeIconPart) as FontIcon;
|
||||||
|
_labelText = GetTemplateChild(LabelTextPart) as TextBlock;
|
||||||
|
|
||||||
|
UpdateIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnActionTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is IconLabelControl control)
|
||||||
|
{
|
||||||
|
control.UpdateIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIcon()
|
||||||
|
{
|
||||||
|
if (_typeIcon == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_typeIcon.Glyph = ActionType switch
|
||||||
|
{
|
||||||
|
ActionType.Program => "\uECAA",
|
||||||
|
ActionType.Text => "\uE8D2",
|
||||||
|
ActionType.Shortcut => "\uEDA7",
|
||||||
|
ActionType.MouseClick => "\uE962",
|
||||||
|
ActionType.Url => "\uE774",
|
||||||
|
_ => "\uE8A5",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="KeyboardManagerEditorUI.Controls.UnifiedMappingControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:commoncontrols="using:Microsoft.PowerToys.Common.UI.Controls"
|
||||||
|
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||||
|
Loaded="UserControl_Loaded"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
Width="720"
|
||||||
|
MinHeight="480"
|
||||||
|
ColumnSpacing="24">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Left Column: Original Input (Trigger) -->
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="8">
|
||||||
|
<TextBlock x:Uid="TriggerLabel" FontWeight="SemiBold" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="TriggerTypeComboBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectionChanged="TriggerTypeComboBox_SelectionChanged">
|
||||||
|
<ComboBoxItem
|
||||||
|
x:Uid="TriggerType_KeyOrShortcut"
|
||||||
|
IsSelected="True"
|
||||||
|
Tag="KeyOrShortcut">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="TriggerType_KeyOrShortcut_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
<!--
|
||||||
|
<ComboBoxItem x:Uid="TriggerType_Mouse" Tag="Mouse">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="16" Glyph="" />
|
||||||
|
<TextBlock x:Uid="TriggerType_Mouse_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
-->
|
||||||
|
</ComboBox>
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
Margin="0,12,0,12"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||||
|
|
||||||
|
<tkcontrols:SwitchPresenter
|
||||||
|
x:Name="TriggerSwitchPresenter"
|
||||||
|
TargetType="x:String"
|
||||||
|
Value="{Binding SelectedItem.Tag, ElementName=TriggerTypeComboBox}">
|
||||||
|
|
||||||
|
<!-- Key or Shortcut Trigger -->
|
||||||
|
<tkcontrols:Case Value="KeyOrShortcut">
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="8">
|
||||||
|
<ToggleButton
|
||||||
|
x:Name="TriggerKeyToggleBtn"
|
||||||
|
MinHeight="86"
|
||||||
|
Padding="8,24,8,24"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Checked="TriggerKeyToggleBtn_Checked"
|
||||||
|
Style="{StaticResource CustomShortcutToggleButtonStyle}"
|
||||||
|
Unchecked="TriggerKeyToggleBtn_Unchecked">
|
||||||
|
<ToggleButton.Content>
|
||||||
|
<ItemsControl x:Name="TriggerKeys">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<controls:WrapPanel
|
||||||
|
HorizontalSpacing="4"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
VerticalSpacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual
|
||||||
|
Padding="8"
|
||||||
|
Background="{ThemeResource ControlFillColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
Content="{Binding}"
|
||||||
|
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||||
|
FontSize="16"
|
||||||
|
Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ToggleButton.Content>
|
||||||
|
</ToggleButton>
|
||||||
|
<CheckBox
|
||||||
|
x:Name="AllowChordsCheckBox"
|
||||||
|
x:Uid="AllowChordsCheckBox"
|
||||||
|
Click="AllowChordsCheckBox_Click"
|
||||||
|
IsChecked="True" />
|
||||||
|
</StackPanel>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
<!-- Mouse Button Trigger -->
|
||||||
|
<tkcontrols:Case Value="Mouse">
|
||||||
|
<ComboBox
|
||||||
|
x:Name="MouseTriggerComboBox"
|
||||||
|
MinHeight="86"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Center">
|
||||||
|
<ComboBoxItem x:Uid="MouseButton_Left" Tag="LeftMouse" />
|
||||||
|
<ComboBoxItem x:Uid="MouseButton_Center" Tag="CenterMouse" />
|
||||||
|
<ComboBoxItem x:Uid="MouseButton_Right" Tag="RightMouse" />
|
||||||
|
<ComboBoxItem x:Uid="MouseButton_Button1" Tag="Button1" />
|
||||||
|
<ComboBoxItem x:Uid="MouseButton_Button2" Tag="Button2" />
|
||||||
|
</ComboBox>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
</tkcontrols:SwitchPresenter>
|
||||||
|
<CheckBox
|
||||||
|
x:Name="AppSpecificCheckBox"
|
||||||
|
x:Uid="AppSpecificCheckBox"
|
||||||
|
IsEnabled="False" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="AppNameTextBox"
|
||||||
|
x:Uid="AppNameTextBox"
|
||||||
|
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||||
|
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||||
|
GotFocus="AppNameTextBox_GotFocus"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
</StackPanel>
|
||||||
|
<!-- Arrow Separator -->
|
||||||
|
<Grid
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
RowSpacing="8">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Rectangle
|
||||||
|
Width="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource TextFillColorDisabledBrush}" />
|
||||||
|
<Viewbox
|
||||||
|
Grid.Row="1"
|
||||||
|
Width="12"
|
||||||
|
Height="12">
|
||||||
|
<PathIcon Data="{StaticResource ArrowIconData}" Foreground="{ThemeResource TextFillColorDisabledBrush}" />
|
||||||
|
</Viewbox>
|
||||||
|
<Rectangle
|
||||||
|
Grid.Row="2"
|
||||||
|
Width="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource TextFillColorDisabledBrush}" />
|
||||||
|
</Grid>
|
||||||
|
<!-- Right Column: Action Output -->
|
||||||
|
<StackPanel
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="8">
|
||||||
|
<TextBlock x:Uid="ActionLabel" FontWeight="SemiBold" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="ActionTypeComboBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectionChanged="ActionTypeComboBox_SelectionChanged">
|
||||||
|
<ComboBoxItem
|
||||||
|
x:Uid="ActionType_KeyOrShortcut"
|
||||||
|
IsSelected="True"
|
||||||
|
Tag="KeyOrShortcut">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="ActionType_KeyOrShortcut_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem x:Uid="ActionType_Text" Tag="Text">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="ActionType_Text_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem x:Uid="ActionType_OpenUrl" Tag="OpenUrl">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="ActionType_OpenUrl_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem x:Uid="ActionType_OpenApp" Tag="OpenApp">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="ActionType_OpenApp_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
<!--
|
||||||
|
<ComboBoxItem x:Uid="ActionType_MouseClick" Tag="MouseClick">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="ActionType_MouseClick_Text" />
|
||||||
|
</StackPanel>
|
||||||
|
</ComboBoxItem>
|
||||||
|
-->
|
||||||
|
</ComboBox>
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
Margin="0,12,0,12"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
|
||||||
|
<tkcontrols:SwitchPresenter
|
||||||
|
x:Name="ActionSwitchPresenter"
|
||||||
|
TargetType="x:String"
|
||||||
|
Value="{Binding SelectedItem.Tag, ElementName=ActionTypeComboBox}">
|
||||||
|
<!-- Key or Shortcut Action -->
|
||||||
|
<tkcontrols:Case Value="KeyOrShortcut">
|
||||||
|
<ToggleButton
|
||||||
|
x:Name="ActionKeyToggleBtn"
|
||||||
|
MinHeight="86"
|
||||||
|
Padding="8,24,8,24"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
Checked="ActionKeyToggleBtn_Checked"
|
||||||
|
Style="{StaticResource CustomShortcutToggleButtonStyle}"
|
||||||
|
Unchecked="ActionKeyToggleBtn_Unchecked">
|
||||||
|
<ToggleButton.Content>
|
||||||
|
<ItemsControl x:Name="ActionKeys">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<controls:WrapPanel
|
||||||
|
HorizontalSpacing="4"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
VerticalSpacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual
|
||||||
|
Padding="8"
|
||||||
|
Background="{ThemeResource CustomAccentBackgroundBrush}"
|
||||||
|
BorderThickness="0"
|
||||||
|
Content="{Binding}"
|
||||||
|
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||||
|
FontSize="16"
|
||||||
|
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||||
|
Style="{StaticResource DefaultKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ToggleButton.Content>
|
||||||
|
</ToggleButton>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
<!-- Text Action -->
|
||||||
|
<tkcontrols:Case Value="Text">
|
||||||
|
<TextBox
|
||||||
|
x:Name="TextContentBox"
|
||||||
|
x:Uid="TextContentBox"
|
||||||
|
MinHeight="120"
|
||||||
|
MaxHeight="240"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||||
|
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||||
|
GotFocus="TextContentBox_GotFocus"
|
||||||
|
TextChanged="TextContentBox_TextChanged"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</tkcontrols:Case>
|
||||||
|
<!-- Open URL Action -->
|
||||||
|
<tkcontrols:Case Value="OpenUrl">
|
||||||
|
<TextBox
|
||||||
|
x:Name="UrlPathInput"
|
||||||
|
x:Uid="UrlPathInput"
|
||||||
|
Background="{ThemeResource TextControlBackgroundFocused}"
|
||||||
|
BorderBrush="{ThemeResource ControlStrokeColorDefaultBrush}"
|
||||||
|
GotFocus="UrlPathInput_GotFocus"
|
||||||
|
TextChanged="UrlPathInput_TextChanged" />
|
||||||
|
</tkcontrols:Case>
|
||||||
|
<!-- Open App Action -->
|
||||||
|
<tkcontrols:Case Value="OpenApp">
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="16">
|
||||||
|
<Grid ColumnSpacing="8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox
|
||||||
|
x:Name="ProgramPathInput"
|
||||||
|
x:Uid="ProgramPathInput"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
GotFocus="ProgramPathInput_GotFocus"
|
||||||
|
TextChanged="ProgramPathInput_TextChanged" />
|
||||||
|
<Button
|
||||||
|
x:Name="ProgramPathSelectButton"
|
||||||
|
x:Uid="ProgramPathSelectButton"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Click="ProgramPathSelectButton_Click"
|
||||||
|
Content="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=16}"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}" />
|
||||||
|
</Grid>
|
||||||
|
<TextBox
|
||||||
|
x:Name="ProgramArgsInput"
|
||||||
|
x:Uid="ProgramArgsInput"
|
||||||
|
GotFocus="ProgramArgsInput_GotFocus" />
|
||||||
|
<Grid ColumnSpacing="8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox
|
||||||
|
x:Name="StartInPathInput"
|
||||||
|
x:Uid="StartInPathInput"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
GotFocus="StartInPathInput_GotFocus" />
|
||||||
|
<Button
|
||||||
|
x:Name="StartInSelectButton"
|
||||||
|
x:Uid="StartInSelectButton"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Click="StartInSelectButton_Click"
|
||||||
|
Content="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=16}"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}" />
|
||||||
|
</Grid>
|
||||||
|
<ComboBox
|
||||||
|
x:Name="ElevationComboBox"
|
||||||
|
x:Uid="ElevationComboBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectedIndex="0">
|
||||||
|
<ComboBoxItem x:Uid="Elevation_Normal" />
|
||||||
|
<ComboBoxItem x:Uid="Elevation_Elevated" />
|
||||||
|
<ComboBoxItem x:Uid="Elevation_DifferentUser" />
|
||||||
|
</ComboBox>
|
||||||
|
<ComboBox
|
||||||
|
x:Name="IfRunningComboBox"
|
||||||
|
x:Uid="IfRunningComboBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectedIndex="0">
|
||||||
|
<ComboBoxItem x:Uid="IfRunning_ShowWindow" />
|
||||||
|
<ComboBoxItem x:Uid="IfRunning_StartAnother" />
|
||||||
|
<ComboBoxItem x:Uid="IfRunning_DoNothing" />
|
||||||
|
<ComboBoxItem x:Uid="IfRunning_Close" />
|
||||||
|
<ComboBoxItem x:Uid="IfRunning_EndTask" />
|
||||||
|
</ComboBox>
|
||||||
|
<ComboBox
|
||||||
|
x:Name="VisibilityComboBox"
|
||||||
|
x:Uid="VisibilityComboBox"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
SelectedIndex="0">
|
||||||
|
<ComboBoxItem x:Uid="Visibility_Normal" />
|
||||||
|
<ComboBoxItem x:Uid="Visibility_Hidden" />
|
||||||
|
<ComboBoxItem x:Uid="Visibility_Minimized" />
|
||||||
|
<ComboBoxItem x:Uid="Visibility_Maximized" />
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
<!-- Mouse Click Action (Placeholder) -->
|
||||||
|
<tkcontrols:Case Value="MouseClick">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="MouseClickPlaceholder"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</tkcontrols:Case>
|
||||||
|
</tkcontrols:SwitchPresenter>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- Validation InfoBar spanning all columns -->
|
||||||
|
<InfoBar
|
||||||
|
x:Name="ValidationInfoBar"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
Margin="0,16,0,0"
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="False"
|
||||||
|
Severity="Warning" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,997 @@
|
|||||||
|
// 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 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;
|
||||||
|
|
||||||
|
#pragma warning disable SA1124 // Do not use regions
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unified control that consolidates all mapping input types:
|
||||||
|
/// - Key/Shortcut remapping (InputControl)
|
||||||
|
/// - Text output (TextPageInputControl)
|
||||||
|
/// - URL opening (UrlPageInputControl)
|
||||||
|
/// - App launching (AppPageInputControl)
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class UnifiedMappingControl : UserControl, IDisposable, IKeyboardHookTarget
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
|
||||||
|
private readonly ObservableCollection<string> _triggerKeys = new();
|
||||||
|
private readonly ObservableCollection<string> _actionKeys = new();
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _internalUpdate;
|
||||||
|
|
||||||
|
private KeyInputMode _currentInputMode = KeyInputMode.OriginalKeys;
|
||||||
|
|
||||||
|
// Dirty tracking: marks fields that have had content then were cleared
|
||||||
|
private bool _textContentDirty;
|
||||||
|
private bool _urlPathDirty;
|
||||||
|
private bool _programPathDirty;
|
||||||
|
|
||||||
|
public bool AllowChords { get; set; } = true;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised whenever the validation state of the control changes (inputs filled/cleared).
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler? ValidationStateChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Enums
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the type of trigger for the mapping.
|
||||||
|
/// </summary>
|
||||||
|
public enum TriggerType
|
||||||
|
{
|
||||||
|
KeyOrShortcut,
|
||||||
|
Mouse,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the type of action to perform.
|
||||||
|
/// </summary>
|
||||||
|
public enum ActionType
|
||||||
|
{
|
||||||
|
KeyOrShortcut,
|
||||||
|
Text,
|
||||||
|
OpenUrl,
|
||||||
|
OpenApp,
|
||||||
|
MouseClick,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the mouse button options.
|
||||||
|
/// </summary>
|
||||||
|
public enum MouseButton
|
||||||
|
{
|
||||||
|
LeftMouse,
|
||||||
|
RightMouse,
|
||||||
|
ScrollUp,
|
||||||
|
ScrollDown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current trigger type.
|
||||||
|
/// </summary>
|
||||||
|
public TriggerType CurrentTriggerType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (TriggerTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||||
|
{
|
||||||
|
return item.Tag?.ToString() switch
|
||||||
|
{
|
||||||
|
"Mouse" => TriggerType.Mouse,
|
||||||
|
_ => TriggerType.KeyOrShortcut,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return TriggerType.KeyOrShortcut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current action type.
|
||||||
|
/// </summary>
|
||||||
|
public ActionType CurrentActionType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (ActionTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||||
|
{
|
||||||
|
return item.Tag?.ToString() switch
|
||||||
|
{
|
||||||
|
"Text" => ActionType.Text,
|
||||||
|
"OpenUrl" => ActionType.OpenUrl,
|
||||||
|
"OpenApp" => ActionType.OpenApp,
|
||||||
|
"MouseClick" => ActionType.MouseClick,
|
||||||
|
_ => ActionType.KeyOrShortcut,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActionType.KeyOrShortcut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
|
public UnifiedMappingControl()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
|
||||||
|
TriggerKeys.ItemsSource = _triggerKeys;
|
||||||
|
ActionKeys.ItemsSource = _actionKeys;
|
||||||
|
|
||||||
|
_triggerKeys.CollectionChanged += (_, _) => RaiseValidationStateChanged();
|
||||||
|
_actionKeys.CollectionChanged += (_, _) => RaiseValidationStateChanged();
|
||||||
|
|
||||||
|
this.Unloaded += UnifiedMappingControl_Unloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Lifecycle Events
|
||||||
|
|
||||||
|
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Set up event handlers for app-specific checkbox
|
||||||
|
AppSpecificCheckBox.Checked += AppSpecificCheckBox_Changed;
|
||||||
|
AppSpecificCheckBox.Unchecked += AppSpecificCheckBox_Changed;
|
||||||
|
|
||||||
|
// Activate keyboard hook for the trigger input
|
||||||
|
if (TriggerKeyToggleBtn.IsChecked == true)
|
||||||
|
{
|
||||||
|
_currentInputMode = KeyInputMode.OriginalKeys;
|
||||||
|
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnifiedMappingControl_Unloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Trigger Type Handling
|
||||||
|
|
||||||
|
private void TriggerTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (TriggerTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||||
|
{
|
||||||
|
string? tag = item.Tag?.ToString();
|
||||||
|
|
||||||
|
// Cleanup keyboard hook when switching to mouse
|
||||||
|
if (tag == "Mouse")
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (TriggerKeyToggleBtn.IsChecked == true)
|
||||||
|
{
|
||||||
|
_currentInputMode = KeyInputMode.OriginalKeys;
|
||||||
|
|
||||||
|
// Uncheck action toggle if checked
|
||||||
|
if (ActionKeyToggleBtn?.IsChecked == true)
|
||||||
|
{
|
||||||
|
ActionKeyToggleBtn.IsChecked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerKeyToggleBtn_Unchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_currentInputMode == KeyInputMode.OriginalKeys)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Action Type Handling
|
||||||
|
|
||||||
|
private void ActionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ActionTypeComboBox?.SelectedItem is ComboBoxItem item)
|
||||||
|
{
|
||||||
|
string? tag = item.Tag?.ToString();
|
||||||
|
|
||||||
|
// Cleanup keyboard hook when switching away from key/shortcut
|
||||||
|
if (tag != "KeyOrShortcut")
|
||||||
|
{
|
||||||
|
if (_currentInputMode == KeyInputMode.RemappedKeys)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActionKeyToggleBtn?.IsChecked == true)
|
||||||
|
{
|
||||||
|
ActionKeyToggleBtn.IsChecked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HideValidationMessage();
|
||||||
|
RaiseValidationStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ActionKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ActionKeyToggleBtn.IsChecked == true)
|
||||||
|
{
|
||||||
|
_currentInputMode = KeyInputMode.RemappedKeys;
|
||||||
|
|
||||||
|
// Uncheck trigger toggle if checked
|
||||||
|
if (TriggerKeyToggleBtn?.IsChecked == true)
|
||||||
|
{
|
||||||
|
TriggerKeyToggleBtn.IsChecked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardHookHelper.Instance.ActivateHook(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ActionKeyToggleBtn_Unchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_currentInputMode == KeyInputMode.RemappedKeys)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region App-Specific Handling
|
||||||
|
|
||||||
|
private void AppSpecificCheckBox_Changed(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_internalUpdate)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
|
||||||
|
AppNameTextBox.Visibility = AppSpecificCheckBox.IsChecked == true
|
||||||
|
? Visibility.Visible
|
||||||
|
: Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppSpecificCheckBoxState()
|
||||||
|
{
|
||||||
|
// Only enable app-specific remapping for shortcuts (multiple keys).
|
||||||
|
bool isShortcut = _triggerKeys.Count > 1;
|
||||||
|
bool alreadyChecked = AppSpecificCheckBox.IsChecked == true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_internalUpdate = true;
|
||||||
|
|
||||||
|
AppSpecificCheckBox.IsEnabled = isShortcut || alreadyChecked;
|
||||||
|
if (!isShortcut && !alreadyChecked)
|
||||||
|
{
|
||||||
|
AppSpecificCheckBox.IsChecked = false;
|
||||||
|
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_internalUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TextBox Focus Handlers
|
||||||
|
|
||||||
|
private void AppNameTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TextContentBox_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TextContentBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
_textContentDirty = true;
|
||||||
|
RaiseValidationStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UrlPathInput_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UrlPathInput_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
_urlPathDirty = true;
|
||||||
|
RaiseValidationStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProgramPathInput_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProgramPathInput_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
{
|
||||||
|
_programPathDirty = true;
|
||||||
|
RaiseValidationStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProgramArgsInput_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartInPathInput_GotFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region File/Folder Pickers
|
||||||
|
|
||||||
|
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var picker = new FileOpenPicker();
|
||||||
|
|
||||||
|
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||||
|
InitializeWithWindow.Initialize(picker, hwnd);
|
||||||
|
|
||||||
|
picker.FileTypeFilter.Add(".exe");
|
||||||
|
|
||||||
|
StorageFile file = await picker.PickSingleFileAsync();
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
ProgramPathInput.Text = file.Path;
|
||||||
|
RaiseValidationStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartInSelectButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var picker = new FolderPicker();
|
||||||
|
|
||||||
|
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||||
|
InitializeWithWindow.Initialize(picker, hwnd);
|
||||||
|
|
||||||
|
picker.FileTypeFilter.Add("*");
|
||||||
|
|
||||||
|
StorageFolder folder = await picker.PickSingleFolderAsync();
|
||||||
|
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
StartInPathInput.Text = folder.Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IKeyboardHookTarget Implementation
|
||||||
|
|
||||||
|
public void OnKeyDown(VirtualKey key, List<string> formattedKeys)
|
||||||
|
{
|
||||||
|
if (_currentInputMode == KeyInputMode.OriginalKeys)
|
||||||
|
{
|
||||||
|
_triggerKeys.Clear();
|
||||||
|
foreach (var keyName in formattedKeys)
|
||||||
|
{
|
||||||
|
_triggerKeys.Add(keyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppSpecificCheckBoxState();
|
||||||
|
}
|
||||||
|
else if (_currentInputMode == KeyInputMode.RemappedKeys)
|
||||||
|
{
|
||||||
|
_actionKeys.Clear();
|
||||||
|
foreach (var keyName in formattedKeys)
|
||||||
|
{
|
||||||
|
_actionKeys.Add(keyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearKeys()
|
||||||
|
{
|
||||||
|
if (_currentInputMode == KeyInputMode.OriginalKeys)
|
||||||
|
{
|
||||||
|
_triggerKeys.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_actionKeys.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnInputLimitReached()
|
||||||
|
{
|
||||||
|
ShowNotificationTip("Shortcuts can only have up to 4 modifier keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API - Getters
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the trigger keys.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetTriggerKeys() => _triggerKeys.ToList();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the action keys (for Key/Shortcut action type).
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetActionKeys() => _actionKeys.ToList();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the selected mouse trigger.
|
||||||
|
/// </summary>
|
||||||
|
public MouseButton? GetMouseTrigger()
|
||||||
|
{
|
||||||
|
if (MouseTriggerComboBox?.SelectedItem is ComboBoxItem item)
|
||||||
|
{
|
||||||
|
return item.Tag?.ToString() switch
|
||||||
|
{
|
||||||
|
"LeftMouse" => MouseButton.LeftMouse,
|
||||||
|
"RightMouse" => MouseButton.RightMouse,
|
||||||
|
"ScrollUp" => MouseButton.ScrollUp,
|
||||||
|
"ScrollDown" => MouseButton.ScrollDown,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the text content (for Text action type).
|
||||||
|
/// </summary>
|
||||||
|
public string GetTextContent() => TextContentBox?.Text ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the URL (for OpenUrl action type).
|
||||||
|
/// </summary>
|
||||||
|
public string GetUrl() => UrlPathInput?.Text ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the program path (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public string GetProgramPath() => ProgramPathInput?.Text ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the program arguments (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public string GetProgramArgs() => ProgramArgsInput?.Text ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start-in directory (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public string GetStartInDirectory() => StartInPathInput?.Text ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the mapping is app-specific.
|
||||||
|
/// </summary>
|
||||||
|
public bool GetIsAppSpecific()
|
||||||
|
{
|
||||||
|
return AppSpecificCheckBox?.IsChecked ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the app name for app-specific mappings.
|
||||||
|
/// </summary>
|
||||||
|
public string GetAppName()
|
||||||
|
{
|
||||||
|
return GetIsAppSpecific() ? (AppNameTextBox?.Text ?? string.Empty) : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the elevation level (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public ElevationLevel GetElevationLevel() => (ElevationLevel)(ElevationComboBox?.SelectedIndex ?? 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the window visibility (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public StartWindowType GetVisibility() => (StartWindowType)(VisibilityComboBox?.SelectedIndex ?? 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the if-running action (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public ProgramAlreadyRunningAction GetIfRunningAction() => (ProgramAlreadyRunningAction)(IfRunningComboBox?.SelectedIndex ?? 0);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API - Validation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when all required fields for the current action type are filled.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsInputComplete()
|
||||||
|
{
|
||||||
|
// Trigger keys are always required
|
||||||
|
if (_triggerKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CurrentActionType switch
|
||||||
|
{
|
||||||
|
ActionType.KeyOrShortcut => _actionKeys.Count > 0,
|
||||||
|
ActionType.Text => !string.IsNullOrWhiteSpace(TextContentBox?.Text),
|
||||||
|
ActionType.OpenUrl => !string.IsNullOrWhiteSpace(UrlPathInput?.Text),
|
||||||
|
ActionType.OpenApp => !string.IsNullOrWhiteSpace(ProgramPathInput?.Text),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API - Setters
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the trigger keys.
|
||||||
|
/// </summary>
|
||||||
|
public void SetTriggerKeys(List<string> keys)
|
||||||
|
{
|
||||||
|
_triggerKeys.Clear();
|
||||||
|
if (keys != null)
|
||||||
|
{
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
_triggerKeys.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppSpecificCheckBoxState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the action keys.
|
||||||
|
/// </summary>
|
||||||
|
public void SetActionKeys(List<string> keys)
|
||||||
|
{
|
||||||
|
_actionKeys.Clear();
|
||||||
|
if (keys != null)
|
||||||
|
{
|
||||||
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
|
_actionKeys.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the action type.
|
||||||
|
/// </summary>
|
||||||
|
public void SetActionType(ActionType actionType)
|
||||||
|
{
|
||||||
|
int index = actionType switch
|
||||||
|
{
|
||||||
|
ActionType.Text => 1,
|
||||||
|
ActionType.OpenUrl => 2,
|
||||||
|
ActionType.OpenApp => 3,
|
||||||
|
ActionType.MouseClick => 4,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ActionTypeComboBox != null)
|
||||||
|
{
|
||||||
|
ActionTypeComboBox.SelectedIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the text content (for Text action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetTextContent(string text)
|
||||||
|
{
|
||||||
|
if (TextContentBox != null)
|
||||||
|
{
|
||||||
|
TextContentBox.Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the URL (for OpenUrl action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetUrl(string url)
|
||||||
|
{
|
||||||
|
if (UrlPathInput != null)
|
||||||
|
{
|
||||||
|
UrlPathInput.Text = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the program path (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetProgramPath(string path)
|
||||||
|
{
|
||||||
|
if (ProgramPathInput != null)
|
||||||
|
{
|
||||||
|
ProgramPathInput.Text = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the program arguments (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetProgramArgs(string args)
|
||||||
|
{
|
||||||
|
if (ProgramArgsInput != null)
|
||||||
|
{
|
||||||
|
ProgramArgsInput.Text = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the start-in directory (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetStartInDirectory(string path)
|
||||||
|
{
|
||||||
|
if (StartInPathInput != null)
|
||||||
|
{
|
||||||
|
StartInPathInput.Text = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the elevation level (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetElevationLevel(ElevationLevel elevationLevel)
|
||||||
|
{
|
||||||
|
if (ElevationComboBox != null)
|
||||||
|
{
|
||||||
|
ElevationComboBox.SelectedIndex = (int)elevationLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the window visibility (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetVisibility(StartWindowType visibility)
|
||||||
|
{
|
||||||
|
if (VisibilityComboBox != null)
|
||||||
|
{
|
||||||
|
VisibilityComboBox.SelectedIndex = (int)visibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the if-already-running action (for OpenApp action type).
|
||||||
|
/// </summary>
|
||||||
|
public void SetIfRunningAction(ProgramAlreadyRunningAction ifRunningAction)
|
||||||
|
{
|
||||||
|
if (IfRunningComboBox != null)
|
||||||
|
{
|
||||||
|
IfRunningComboBox.SelectedIndex = (int)ifRunningAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether the mapping is app-specific.
|
||||||
|
/// </summary>
|
||||||
|
public void SetAppSpecific(bool isAppSpecific, string appName)
|
||||||
|
{
|
||||||
|
if (AppSpecificCheckBox != null)
|
||||||
|
{
|
||||||
|
AppSpecificCheckBox.IsChecked = isAppSpecific;
|
||||||
|
if (isAppSpecific && AppNameTextBox != null)
|
||||||
|
{
|
||||||
|
AppNameTextBox.Text = appName;
|
||||||
|
AppNameTextBox.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Helper Methods
|
||||||
|
|
||||||
|
private void UncheckAllToggleButtons()
|
||||||
|
{
|
||||||
|
if (TriggerKeyToggleBtn?.IsChecked == true)
|
||||||
|
{
|
||||||
|
TriggerKeyToggleBtn.IsChecked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActionKeyToggleBtn?.IsChecked == true)
|
||||||
|
{
|
||||||
|
ActionKeyToggleBtn.IsChecked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupKeyboardHook()
|
||||||
|
{
|
||||||
|
KeyboardHookHelper.Instance.CleanupHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseValidationStateChanged()
|
||||||
|
{
|
||||||
|
UpdateInlineValidation();
|
||||||
|
ValidationStateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows or hides the inline validation InfoBar based on the current state.
|
||||||
|
/// Only shows errors for output fields that have been interacted with (had content then cleared).
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateInlineValidation()
|
||||||
|
{
|
||||||
|
// Only validate the active action type's output field
|
||||||
|
switch (CurrentActionType)
|
||||||
|
{
|
||||||
|
case ActionType.Text:
|
||||||
|
if (TextContentBox != null && _textContentDirty && string.IsNullOrWhiteSpace(TextContentBox.Text))
|
||||||
|
{
|
||||||
|
ShowValidationErrorFromType(ValidationErrorType.EmptyTargetText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.OpenUrl:
|
||||||
|
if (UrlPathInput != null && _urlPathDirty && string.IsNullOrWhiteSpace(UrlPathInput.Text))
|
||||||
|
{
|
||||||
|
ShowValidationErrorFromType(ValidationErrorType.EmptyUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActionType.OpenApp:
|
||||||
|
if (ProgramPathInput != null && _programPathDirty && string.IsNullOrWhiteSpace(ProgramPathInput.Text))
|
||||||
|
{
|
||||||
|
ShowValidationErrorFromType(ValidationErrorType.EmptyProgramPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
HideValidationMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets all inputs to their default state.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_triggerKeys.Clear();
|
||||||
|
_actionKeys.Clear();
|
||||||
|
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
|
||||||
|
_currentInputMode = KeyInputMode.OriginalKeys;
|
||||||
|
|
||||||
|
// Reset dirty tracking
|
||||||
|
_textContentDirty = false;
|
||||||
|
_urlPathDirty = false;
|
||||||
|
_programPathDirty = false;
|
||||||
|
|
||||||
|
// Hide any validation messages
|
||||||
|
HideValidationMessage();
|
||||||
|
|
||||||
|
// Reset combo boxes
|
||||||
|
if (TriggerTypeComboBox != null)
|
||||||
|
{
|
||||||
|
TriggerTypeComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActionTypeComboBox != null)
|
||||||
|
{
|
||||||
|
ActionTypeComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MouseTriggerComboBox != null)
|
||||||
|
{
|
||||||
|
MouseTriggerComboBox.SelectedIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset text inputs
|
||||||
|
if (TextContentBox != null)
|
||||||
|
{
|
||||||
|
TextContentBox.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UrlPathInput != null)
|
||||||
|
{
|
||||||
|
UrlPathInput.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProgramPathInput != null)
|
||||||
|
{
|
||||||
|
ProgramPathInput.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProgramArgsInput != null)
|
||||||
|
{
|
||||||
|
ProgramArgsInput.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StartInPathInput != null)
|
||||||
|
{
|
||||||
|
StartInPathInput.Text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AppNameTextBox != null)
|
||||||
|
{
|
||||||
|
AppNameTextBox.Text = string.Empty;
|
||||||
|
AppNameTextBox.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset checkboxes
|
||||||
|
if (AppSpecificCheckBox != null)
|
||||||
|
{
|
||||||
|
AppSpecificCheckBox.IsChecked = false;
|
||||||
|
AppSpecificCheckBox.IsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset app combo boxes
|
||||||
|
if (ElevationComboBox != null)
|
||||||
|
{
|
||||||
|
ElevationComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IfRunningComboBox != null)
|
||||||
|
{
|
||||||
|
IfRunningComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VisibilityComboBox != null)
|
||||||
|
{
|
||||||
|
VisibilityComboBox.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HideValidationMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets only the toggle buttons without clearing the key displays.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetToggleButtons()
|
||||||
|
{
|
||||||
|
UncheckAllToggleButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Notifications
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a warning notification in the InfoBar.
|
||||||
|
/// </summary>
|
||||||
|
public void ShowNotificationTip(string message)
|
||||||
|
{
|
||||||
|
ShowValidationMessage("Warning", message, InfoBarSeverity.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows an error in the InfoBar with title and message.
|
||||||
|
/// </summary>
|
||||||
|
public void ShowValidationError(string title, string message)
|
||||||
|
{
|
||||||
|
ShowValidationMessage(title, message, InfoBarSeverity.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a validation error based on the error type.
|
||||||
|
/// </summary>
|
||||||
|
public void ShowValidationErrorFromType(ValidationErrorType errorType)
|
||||||
|
{
|
||||||
|
if (ValidationHelper.ValidationMessages.TryGetValue(errorType, out var messageInfo))
|
||||||
|
{
|
||||||
|
ShowValidationError(messageInfo.Title, messageInfo.Message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowValidationError("Validation Error", "An unknown validation error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows a message in the InfoBar with the specified severity.
|
||||||
|
/// </summary>
|
||||||
|
private void ShowValidationMessage(string title, string message, InfoBarSeverity severity)
|
||||||
|
{
|
||||||
|
if (ValidationInfoBar != null)
|
||||||
|
{
|
||||||
|
ValidationInfoBar.Title = title;
|
||||||
|
ValidationInfoBar.Message = message;
|
||||||
|
ValidationInfoBar.Severity = severity;
|
||||||
|
ValidationInfoBar.IsOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the validation InfoBar.
|
||||||
|
/// </summary>
|
||||||
|
public void HideValidationMessage()
|
||||||
|
{
|
||||||
|
if (ValidationInfoBar != null)
|
||||||
|
{
|
||||||
|
ValidationInfoBar.IsOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
CleanupKeyboardHook();
|
||||||
|
HideValidationMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void AllowChordsCheckBox_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AllowChords = AllowChordsCheckBox.IsChecked == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning restore SA1124 // Do not use regions
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// 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 ActionType
|
||||||
|
{
|
||||||
|
Program,
|
||||||
|
Text,
|
||||||
|
Shortcut,
|
||||||
|
MouseClick,
|
||||||
|
Url,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// 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
|
||||||
|
{
|
||||||
|
internal interface IToggleableShortcut
|
||||||
|
{
|
||||||
|
public List<string> Shortcut { get; set; }
|
||||||
|
|
||||||
|
bool IsActive { get; set; }
|
||||||
|
|
||||||
|
string Id { get; set; }
|
||||||
|
|
||||||
|
string AppName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
// 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;
|
||||||
|
VirtualKey? actionKeyChord = null;
|
||||||
|
|
||||||
|
foreach (var key in _keyPressOrder)
|
||||||
|
{
|
||||||
|
if (!_currentlyPressedKeys.Contains(key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RemappingHelper.IsModifierKey(key))
|
||||||
|
{
|
||||||
|
if (!modifierKeys.Contains(key))
|
||||||
|
{
|
||||||
|
modifierKeys.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (actionKey.HasValue && _activeTarget.AllowChords)
|
||||||
|
{
|
||||||
|
actionKeyChord = 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionKeyChord.HasValue && _activeTarget.AllowChords)
|
||||||
|
{
|
||||||
|
keyList.Add(_mappingService.GetKeyDisplayName((int)actionKeyChord.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
|
||||||
|
{
|
||||||
|
bool AllowChords { get; }
|
||||||
|
|
||||||
|
void OnKeyDown(VirtualKey key, List<string> formattedKeys);
|
||||||
|
|
||||||
|
void OnKeyUp(VirtualKey key, List<string> formattedKeys)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearKeys();
|
||||||
|
|
||||||
|
void OnInputLimitReached();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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 static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Helpers
|
||||||
|
{
|
||||||
|
public class ProgramShortcut : IToggleableShortcut
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
public bool IsAllApps { get; set; } = true;
|
||||||
|
|
||||||
|
public string AppName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string StartInDirectory { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Elevation { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string IfRunningAction { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Visibility { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// 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, IToggleableShortcut
|
||||||
|
{
|
||||||
|
public List<string> Shortcut { 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; } = string.Empty;
|
||||||
|
|
||||||
|
private bool IsEnabledValue { get; set; } = true;
|
||||||
|
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsActive { 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
// 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 KeyboardManagerEditorUI.Settings;
|
||||||
|
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, bool saveToSettings = true)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
string targetKeysString = string.Join(";", remappedKeys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||||
|
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapShortcut,
|
||||||
|
OriginalKeys = originalKey.ToString(CultureInfo.InvariantCulture),
|
||||||
|
TargetKeys = targetKeysString,
|
||||||
|
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||||
|
};
|
||||||
|
if (remappedKeys.Count == 1)
|
||||||
|
{
|
||||||
|
int targetKey = mappingService.GetKeyCodeFromName(remappedKeys[0]);
|
||||||
|
if (targetKey != 0)
|
||||||
|
{
|
||||||
|
mappingService.AddSingleKeyMapping(originalKey, targetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mappingService.AddSingleKeyMapping(originalKey, targetKeysString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveToSettings)
|
||||||
|
{
|
||||||
|
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)));
|
||||||
|
|
||||||
|
ShortcutKeyMapping shortcutKeyMapping = new ShortcutKeyMapping()
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapShortcut,
|
||||||
|
OriginalKeys = originalKeysString,
|
||||||
|
TargetKeys = targetKeysString,
|
||||||
|
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAppSpecific && !string.IsNullOrEmpty(appName))
|
||||||
|
{
|
||||||
|
mappingService.AddShortcutMapping(originalKeysString, targetKeysString, appName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mappingService.AddShortcutMapping(originalKeysString, targetKeysString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveToSettings)
|
||||||
|
{
|
||||||
|
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappingService.SaveSettings();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error saving mapping: " + ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool DeleteRemapping(KeyboardMappingService mappingService, Remapping remapping, bool deleteFromSettings = true)
|
||||||
|
{
|
||||||
|
if (mappingService == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (remapping.Shortcut.Count == 1)
|
||||||
|
{
|
||||||
|
// Single key mapping
|
||||||
|
int originalKey = mappingService.GetKeyCodeFromName(remapping.Shortcut[0]);
|
||||||
|
if (originalKey != 0)
|
||||||
|
{
|
||||||
|
if (mappingService.DeleteSingleKeyMapping(originalKey))
|
||||||
|
{
|
||||||
|
if (deleteFromSettings)
|
||||||
|
{
|
||||||
|
SettingsManager.RemoveShortcutKeyMappingFromSettings(remapping.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappingService.SaveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (remapping.Shortcut.Count > 1)
|
||||||
|
{
|
||||||
|
// Shortcut mapping
|
||||||
|
string originalKeysString = string.Join(";", remapping.Shortcut.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteResult && deleteFromSettings)
|
||||||
|
{
|
||||||
|
SettingsManager.RemoveShortcutKeyMappingFromSettings(remapping.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 : IToggleableShortcut
|
||||||
|
{
|
||||||
|
public List<string> Shortcut { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
public string Text { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsAllApps { get; set; } = true;
|
||||||
|
|
||||||
|
public string AppName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 : IToggleableShortcut
|
||||||
|
{
|
||||||
|
public List<string> Shortcut { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
public string URL { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsAllApps { get; set; } = true;
|
||||||
|
|
||||||
|
public string AppName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// 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,
|
||||||
|
EmptyUrl,
|
||||||
|
EmptyProgramPath,
|
||||||
|
OneKeyMapping,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
// 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 KeyboardManagerEditorUI.Interop;
|
||||||
|
using KeyboardManagerEditorUI.Settings;
|
||||||
|
using ManagedCommon;
|
||||||
|
|
||||||
|
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.") },
|
||||||
|
{ ValidationErrorType.EmptyUrl, ("Missing URL", "Please enter the URL to open when the shortcut is pressed.") },
|
||||||
|
{ ValidationErrorType.EmptyProgramPath, ("Missing Program Path", "Please enter the program path to launch when the shortcut is pressed.") },
|
||||||
|
{ ValidationErrorType.OneKeyMapping, ("Invalid Remapping", "A single key cannot be remapped to a Program or URL shortcut. Please choose a combination of keys.") },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static ValidationErrorType ValidateKeyMapping(
|
||||||
|
List<string> originalKeys,
|
||||||
|
List<string> remappedKeys,
|
||||||
|
bool isAppSpecific,
|
||||||
|
string appName,
|
||||||
|
KeyboardMappingService mappingService,
|
||||||
|
bool isEditMode = false,
|
||||||
|
Remapping? editingRemapping = null)
|
||||||
|
{
|
||||||
|
if (originalKeys == null || originalKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyOriginalKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remappedKeys == null || remappedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyRemappedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((originalKeys.Count > 1 && ContainsOnlyModifierKeys(originalKeys)) ||
|
||||||
|
(remappedKeys.Count > 1 && ContainsOnlyModifierKeys(remappedKeys)))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.ModifierOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyAppName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalKeys.Count > 1 && IsIllegalShortcut(originalKeys, mappingService))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.IllegalShortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDuplicateMapping(originalKeys, isEditMode, mappingService, appName))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.DuplicateMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
bool isEditMode = false)
|
||||||
|
{
|
||||||
|
if (keys == null || keys.Count == 0)
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyOriginalKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(textContent))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyTargetText;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.Count > 1 && ContainsOnlyModifierKeys(keys))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.ModifierOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAppSpecific && string.IsNullOrWhiteSpace(appName))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyAppName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.Count > 1 && IsIllegalShortcut(keys, mappingService))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.IllegalShortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsDuplicateMapping(keys, isEditMode, mappingService, appName))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.DuplicateMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationErrorType.NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValidationErrorType ValidateUrlMapping(
|
||||||
|
List<string> originalKeys,
|
||||||
|
string url,
|
||||||
|
bool isAppSpecific,
|
||||||
|
string appName,
|
||||||
|
KeyboardMappingService mappingService,
|
||||||
|
bool isEditMode = false,
|
||||||
|
Remapping? editingRemapping = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidateProgramOrUrlMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValidationErrorType ValidateAppMapping(
|
||||||
|
List<string> originalKeys,
|
||||||
|
string programPath,
|
||||||
|
bool isAppSpecific,
|
||||||
|
string appName,
|
||||||
|
KeyboardMappingService mappingService,
|
||||||
|
bool isEditMode = false,
|
||||||
|
Remapping? editingRemapping = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(programPath))
|
||||||
|
{
|
||||||
|
return ValidationErrorType.EmptyProgramPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidateProgramOrUrlMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDuplicateMapping(List<string> keys, bool isEditMode, KeyboardMappingService mappingService, string appName)
|
||||||
|
{
|
||||||
|
int upperLimit = isEditMode ? 1 : 0;
|
||||||
|
string shortcutKeysString = BuildKeyCodeString(keys, mappingService);
|
||||||
|
return SettingsManager.EditorSettings.ShortcutSettingsDictionary.Values
|
||||||
|
.Count(settings => KeyboardManagerInterop.AreShortcutsEqual(settings.Shortcut.OriginalKeys, shortcutKeysString) &&
|
||||||
|
(string.IsNullOrEmpty(settings.Shortcut.TargetApp) || string.IsNullOrEmpty(appName) || settings.Shortcut.TargetApp == appName)) > upperLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSelfMapping(List<string> originalKeys, List<string> remappedKeys, KeyboardMappingService mappingService)
|
||||||
|
{
|
||||||
|
if (mappingService == null || originalKeys == null || remappedKeys == null ||
|
||||||
|
originalKeys.Count == 0 || remappedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string originalKeysString = BuildKeyCodeString(originalKeys, mappingService);
|
||||||
|
string remappedKeysString = BuildKeyCodeString(remappedKeys, mappingService);
|
||||||
|
|
||||||
|
return KeyboardManagerInterop.AreShortcutsEqual(originalKeysString, remappedKeysString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ContainsOnlyModifierKeys(List<string> keys)
|
||||||
|
{
|
||||||
|
if (keys == null || keys.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys.All(key =>
|
||||||
|
{
|
||||||
|
int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(key);
|
||||||
|
var keyType = (KeyType)KeyboardManagerInterop.GetKeyType(keyCode);
|
||||||
|
return keyType != KeyType.Action;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsKeyOrphaned(int originalKey, KeyboardMappingService mappingService)
|
||||||
|
{
|
||||||
|
// Check single key mappings
|
||||||
|
foreach (var mapping in mappingService.GetSingleKeyMappings())
|
||||||
|
{
|
||||||
|
if (!mapping.IsShortcut && int.TryParse(mapping.TargetKey, out int targetKey) && targetKey == originalKey)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ValidationErrorType ValidateProgramOrUrlMapping(
|
||||||
|
List<string> originalKeys,
|
||||||
|
bool isAppSpecific,
|
||||||
|
string appName,
|
||||||
|
KeyboardMappingService mappingService,
|
||||||
|
bool isEditMode = false,
|
||||||
|
Remapping? editingRemapping = null)
|
||||||
|
{
|
||||||
|
if (originalKeys.Count < 2)
|
||||||
|
{
|
||||||
|
return ValidationErrorType.OneKeyMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationErrorType error = ValidateKeyMapping(originalKeys, originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
|
||||||
|
|
||||||
|
return error == ValidationErrorType.SelfMapping ? ValidationErrorType.NoError : error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsIllegalShortcut(List<string> keys, KeyboardMappingService mappingService)
|
||||||
|
{
|
||||||
|
string shortcutKeysString = BuildKeyCodeString(keys, mappingService);
|
||||||
|
Logger.LogInfo($"Checking if shortcut is illegal: {shortcutKeysString}");
|
||||||
|
return KeyboardManagerInterop.IsShortcutIllegal(shortcutKeysString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildKeyCodeString(List<string> keys, KeyboardMappingService mappingService)
|
||||||
|
{
|
||||||
|
return string.Join(";", keys.Select(k => mappingService.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Interop
|
||||||
|
{
|
||||||
|
public class KeyMapping
|
||||||
|
{
|
||||||
|
public int OriginalKey { get; set; }
|
||||||
|
|
||||||
|
public string TargetKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsShortcut { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Interop
|
||||||
|
{
|
||||||
|
public class KeyToTextMapping
|
||||||
|
{
|
||||||
|
public int OriginalKey { get; set; }
|
||||||
|
|
||||||
|
public string TargetText { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Interop
|
||||||
|
{
|
||||||
|
public enum KeyType
|
||||||
|
{
|
||||||
|
Win = 0,
|
||||||
|
Ctrl = 1,
|
||||||
|
Alt = 2,
|
||||||
|
Shift = 3,
|
||||||
|
Action = 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
// 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.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Interop
|
||||||
|
{
|
||||||
|
public static class KeyboardManagerInterop
|
||||||
|
{
|
||||||
|
private const string DllName = "Powertoys.KeyboardManagerEditorLibraryWrapper.dll";
|
||||||
|
private const CallingConvention Convention = CallingConvention.Cdecl;
|
||||||
|
|
||||||
|
// Configuration Management
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern IntPtr CreateMappingConfiguration();
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern void DestroyMappingConfiguration(IntPtr config);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool LoadMappingSettings(IntPtr config);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool SaveMappingSettings(IntPtr config);
|
||||||
|
|
||||||
|
// Get Mapping Functions
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern int GetSingleKeyRemapCount(IntPtr config);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool GetSingleKeyRemap(IntPtr config, int index, ref SingleKeyMapping mapping);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern int GetSingleKeyToTextRemapCount(IntPtr config);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool GetSingleKeyToTextRemap(IntPtr config, int index, ref KeyboardTextMapping mapping);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern int GetShortcutRemapCount(IntPtr config);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool GetShortcutRemap(IntPtr config, int index, ref ShortcutMapping mapping);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern int GetShortcutRemapCountByType(IntPtr config, int operationType);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool GetShortcutRemapByType(IntPtr config, int operationType, int index, ref ShortcutMapping mapping);
|
||||||
|
|
||||||
|
// Add Mapping Functions
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool AddSingleKeyRemap(IntPtr config, int originalKey, int targetKey);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool AddSingleKeyToTextRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetText);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool AddSingleKeyToShortcutRemap(IntPtr config, int originalKey, [MarshalAs(UnmanagedType.LPWStr)] string targetKeys);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
[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, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool DeleteSingleKeyRemap(IntPtr mappingConfiguration, int originalKey);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool DeleteSingleKeyToTextRemap(IntPtr config, int originalKey);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool DeleteShortcutRemap(IntPtr mappingConfiguration, [MarshalAs(UnmanagedType.LPWStr)] string originalKeys, [MarshalAs(UnmanagedType.LPWStr)] string targetApp);
|
||||||
|
|
||||||
|
// Key Utility Functions
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern int GetKeyCodeFromName([MarshalAs(UnmanagedType.LPWStr)] string keyName);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
internal static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
internal static extern int GetKeyType(int keyCode);
|
||||||
|
|
||||||
|
// Validation Functions
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool IsShortcutIllegal([MarshalAs(UnmanagedType.LPWStr)] string shortcutKeys);
|
||||||
|
|
||||||
|
[DllImport(DllName, CallingConvention = Convention, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
internal static extern bool AreShortcutsEqual([MarshalAs(UnmanagedType.LPWStr)] string lShort, [MarshalAs(UnmanagedType.LPWStr)] string rShortcut);
|
||||||
|
|
||||||
|
// String Management Functions
|
||||||
|
[DllImport(DllName, CallingConvention = Convention)]
|
||||||
|
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, CharSet = CharSet.Unicode)]
|
||||||
|
public struct SingleKeyMapping
|
||||||
|
{
|
||||||
|
public int OriginalKey;
|
||||||
|
public IntPtr TargetKey;
|
||||||
|
[MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public bool IsShortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
public struct KeyboardTextMapping
|
||||||
|
{
|
||||||
|
public int OriginalKey;
|
||||||
|
public IntPtr TargetText;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
public struct ShortcutMapping
|
||||||
|
{
|
||||||
|
public IntPtr OriginalKeys;
|
||||||
|
public IntPtr TargetKeys;
|
||||||
|
public IntPtr TargetApp;
|
||||||
|
public int OperationType;
|
||||||
|
public IntPtr TargetText;
|
||||||
|
public IntPtr ProgramPath;
|
||||||
|
public IntPtr ProgramArgs;
|
||||||
|
public IntPtr UriToOpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int keyCode = KeyboardManagerInterop.GetKeyCodeFromName(keyName);
|
||||||
|
Logger.LogInfo($"Key code for key name {keyName}: {keyCode}");
|
||||||
|
return keyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 AddShortcutMapping(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,16 +8,36 @@
|
|||||||
<RootNamespace>KeyboardManagerEditorUI</RootNamespace>
|
<RootNamespace>KeyboardManagerEditorUI</RootNamespace>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<EnableMsixTooling>true</EnableMsixTooling>
|
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<WindowsPackageType>None</WindowsPackageType>
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
<AssemblyName>PowerToys.KeyboardManagerEditorUI</AssemblyName>
|
<AssemblyName>PowerToys.KeyboardManagerEditorUI</AssemblyName>
|
||||||
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\$(MSBuildProjectName)</OutputPath>
|
<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>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
|
||||||
|
<EnableXamlJitOptimization>true</EnableXamlJitOptimization>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Assets\KeyboardManagerEditor\Keyboard.ico" />
|
||||||
|
<None Remove="Styles\Colors.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Remove="KeyboardManagerEditorXAML\App.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ApplicationDefinition Include="KeyboardManagerEditorXAML\App.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Manifest Include="$(ApplicationManifest)" />
|
<Manifest Include="$(ApplicationManifest)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -27,12 +47,16 @@
|
|||||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||||
package has not yet been restored.
|
package has not yet been restored.
|
||||||
-->
|
-->
|
||||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||||
<ProjectCapability Include="Msix" />
|
<ProjectCapability Include="Msix" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Common" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||||
|
<PackageReference Include="WinUIEx" />
|
||||||
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
|
<!-- 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" />
|
<PackageReference Include="Microsoft.Web.WebView2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -40,9 +64,26 @@
|
|||||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\common\Common.UI.Controls\Common.UI.Controls.csproj" />
|
||||||
|
<ProjectReference Include="..\KeyboardManagerEditorLibraryWrapper\KeyboardManagerEditorLibraryWrapper.vcxproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Assets\" />
|
<Content Update="Assets\KeyboardManagerEditor\Keyboard.ico">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Update="Assets\KeyboardManagerEditor\Square150x150Logo.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Styles\Colors.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Styles\Button.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -50,7 +91,7 @@
|
|||||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||||
the Windows App SDK Nuget package has not yet been restored.
|
the Windows App SDK Nuget package has not yet been restored.
|
||||||
-->
|
-->
|
||||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Application
|
||||||
|
x:Class="KeyboardManagerEditorUI.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:KeyboardManagerEditorUI">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||||
|
<ResourceDictionary Source="/Controls/IconLabelControl.xaml" />
|
||||||
|
<ResourceDictionary Source="/Styles/Button.xaml" />
|
||||||
|
<ResourceDictionary Source="/Styles/Colors.xaml" />
|
||||||
|
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml" />
|
||||||
|
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<x:Double x:Key="ContentDialogMaxWidth">960</x:Double>
|
||||||
|
<!-- Icons -->
|
||||||
|
<x:String x:Key="ArrowIconData">M12.001 2C17.5238 2 22.001 6.47715 22.001 12C22.001 17.5228 17.5238 22 12.001 22C6.47813 22 2.00098 17.5228 2.00098 12C2.00098 6.47715 6.47813 2 12.001 2ZM12.7813 7.46897L12.6972 7.39635C12.4362 7.2027 12.078 7.20031 11.8146 7.38918L11.7206 7.46897L11.648 7.55308C11.4544 7.81407 11.452 8.17229 11.6409 8.43568L11.7206 8.52963L14.4403 11.2493H7.75027L7.6485 11.2561C7.31571 11.3013 7.05227 11.5647 7.00712 11.8975L7.00027 11.9993L7.00712 12.1011C7.05227 12.4339 7.31571 12.6973 7.6485 12.7424L7.75027 12.7493H14.4403L11.72 15.4697L11.6474 15.5538C11.4295 15.8474 11.4536 16.264 11.7198 16.5303C11.9861 16.7967 12.4027 16.8209 12.6964 16.6032L12.7805 16.5306L16.782 12.5306L16.8547 12.4464C17.0484 12.1854 17.0508 11.8272 16.8619 11.5638L16.7821 11.4698L12.7813 7.46897L12.6972 7.39635L12.7813 7.46897Z</x:String>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
@@ -7,7 +7,12 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using KeyboardManagerEditorUI.Helpers;
|
||||||
|
using KeyboardManagerEditorUI.Settings;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Dispatching;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
@@ -29,14 +34,22 @@ namespace KeyboardManagerEditorUI
|
|||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="App"/> class.
|
||||||
/// Initializes the singleton application object. This is the first line of authored code
|
/// 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().
|
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
this.InitializeComponent();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
Logger.InitializeLogger("\\Keyboard Manager\\WinUI3Editor\\Logs");
|
||||||
Logger.LogInfo("keyboard-manager WinUI3 editor logger is initialized");
|
});
|
||||||
|
|
||||||
|
UnhandledException += App_UnhandledException;
|
||||||
|
|
||||||
|
SettingsManager.CorrelateServiceAndEditorMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,11 +58,28 @@ namespace KeyboardManagerEditorUI
|
|||||||
/// <param name="args">Details about the launch request and process.</param>
|
/// <param name="args">Details about the launch request and process.</param>
|
||||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
window = new MainWindow();
|
MainWindow = new MainWindow();
|
||||||
window.Activate();
|
|
||||||
|
MainWindow.DispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
MainWindow.Activate();
|
||||||
|
MainWindow.DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||||
|
{
|
||||||
|
(MainWindow.Content as FrameworkElement)?.UpdateLayout();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Logger.LogInfo("keyboard-manager WinUI3 editor window is launched");
|
Logger.LogInfo("keyboard-manager WinUI3 editor window is launched");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Window? window;
|
/// <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!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<winuiex:WindowEx
|
||||||
|
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="860"
|
||||||
|
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">
|
||||||
|
<TitleBar.IconSource>
|
||||||
|
<ImageIconSource ImageSource="/Assets/KeyboardManagerEditor/FluentIconsKeyboardManager.png" />
|
||||||
|
</TitleBar.IconSource>
|
||||||
|
</TitleBar>
|
||||||
|
|
||||||
|
<pages:MainPage
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch" />
|
||||||
|
</Grid>
|
||||||
|
</winuiex:WindowEx>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using KeyboardManagerEditorUI.Helpers;
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Windowing;
|
||||||
|
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 WinUIEx;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI
|
||||||
|
{
|
||||||
|
public sealed partial class MainWindow : WindowEx
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
SetTitleBar();
|
||||||
|
this.Activated += MainWindow_Activated;
|
||||||
|
this.Closed += MainWindow_Closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTitleBar()
|
||||||
|
{
|
||||||
|
ExtendsContentIntoTitleBar = true;
|
||||||
|
this.SetIcon(@"Assets\KeyboardManagerEditor\Keyboard.ico");
|
||||||
|
this.SetTitleBar(titleBar);
|
||||||
|
Title = "Keyboard Manager";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<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"
|
|
||||||
Title="KeyboardManagerEditorUI"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<StackPanel
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<Button x:Name="myButton" Click="MyButton_Click">Click Me</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Window>
|
|
||||||
@@ -1,42 +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.Runtime.InteropServices;
|
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
|
||||||
using Microsoft.UI.Xaml.Data;
|
|
||||||
using Microsoft.UI.Xaml.Input;
|
|
||||||
using Microsoft.UI.Xaml.Media;
|
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
|
||||||
using Windows.Foundation;
|
|
||||||
using Windows.Foundation.Collections;
|
|
||||||
|
|
||||||
namespace KeyboardManagerEditorUI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class MainWindow : Window
|
|
||||||
{
|
|
||||||
[DllImport("KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
private static extern bool CheckIfRemappingsAreValid();
|
|
||||||
|
|
||||||
public MainWindow()
|
|
||||||
{
|
|
||||||
this.InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MyButton_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
// Call the C++ function to check if the current remappings are valid
|
|
||||||
myButton.Content = CheckIfRemappingsAreValid() ? "Valid" : "Invalid";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
<mp:PhoneIdentity PhoneProductId="edb1d2cd-ef93-4f89-9db6-4edf04ff20a5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
<mp:PhoneIdentity PhoneProductId="edb1d2cd-ef93-4f89-9db6-4edf04ff20a5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>KeyboardManagerEditorUI</DisplayName>
|
<DisplayName>Keyboard Manager</DisplayName>
|
||||||
<PublisherDisplayName>haoliuu</PublisherDisplayName>
|
<PublisherDisplayName>haoliuu</PublisherDisplayName>
|
||||||
<Logo>Assets\StoreLogo.png</Logo>
|
<Logo>Assets\KeyboardManagerEditor\StoreLogo.png</Logo>
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
@@ -34,13 +34,13 @@
|
|||||||
Executable="$targetnametoken$.exe"
|
Executable="$targetnametoken$.exe"
|
||||||
EntryPoint="$targetentrypoint$">
|
EntryPoint="$targetentrypoint$">
|
||||||
<uap:VisualElements
|
<uap:VisualElements
|
||||||
DisplayName="KeyboardManagerEditorUI"
|
DisplayName="Keyboard Manager"
|
||||||
Description="KeyboardManagerEditorUI"
|
Description="Keyboard Manager"
|
||||||
BackgroundColor="transparent"
|
BackgroundColor="transparent"
|
||||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
Square150x150Logo="Assets\KeyboardManagerEditor\Square150x150Logo.png"
|
||||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
Square44x44Logo="Assets\KeyboardManagerEditor\Square44x44Logo.png">
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
<uap:DefaultTile Wide310x150Logo="Assets\KeyboardManagerEditor\Wide310x150Logo.png" />
|
||||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
<uap:SplashScreen Image="Assets\KeyboardManagerEditor\SplashScreen.png" />
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
</Application>
|
</Application>
|
||||||
</Applications>
|
</Applications>
|
||||||
|
|||||||
@@ -0,0 +1,548 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<Page
|
||||||
|
x:Class="KeyboardManagerEditorUI.Pages.MainPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:commoncontrols="using:Microsoft.PowerToys.Common.UI.Controls"
|
||||||
|
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.Controls"
|
||||||
|
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
|
||||||
|
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Page.Resources>
|
||||||
|
<x:Double x:Key="ContentDialogMaxWidth">800</x:Double>
|
||||||
|
<x:Double x:Key="ContentDialogMaxHeight">800</x:Double>
|
||||||
|
<Style
|
||||||
|
x:Key="OriginalKeyVisualStyle"
|
||||||
|
BasedOn="{StaticResource DefaultKeyVisualStyle}"
|
||||||
|
TargetType="commoncontrols:KeyVisual">
|
||||||
|
<Setter Property="Padding" Value="6" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Background" Value="{ThemeResource ControlFillColorDefaultBrush}" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
|
<Setter Property="RenderKeyAsGlyph" Value="True" />
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
x:Key="RemappedKeyVisualStyle"
|
||||||
|
BasedOn="{StaticResource OriginalKeyVisualStyle}"
|
||||||
|
TargetType="commoncontrols:KeyVisual">
|
||||||
|
<Setter Property="Background" Value="{ThemeResource CustomAccentBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
x:Key="RemappedIconLabelControlStyle"
|
||||||
|
BasedOn="{StaticResource DefaultIconLabelControlStyle}"
|
||||||
|
TargetType="controls:IconLabelControl">
|
||||||
|
<Setter Property="Background" Value="{ThemeResource CustomAccentBackgroundBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="6" />
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||||
|
<Setter Property="Foreground" Value="{ThemeResource AccentTextFillColorPrimaryBrush}" />
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="ItemDividerStyle" TargetType="Rectangle">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Grid.ColumnSpan" Value="2" />
|
||||||
|
<Setter Property="Height" Value="1" />
|
||||||
|
<Setter Property="Margin" Value="-16,0,-16,-8" />
|
||||||
|
<Setter Property="Fill" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
<tkconverters:DoubleToVisibilityConverter
|
||||||
|
x:Key="CountToVisibilityConverter"
|
||||||
|
FalseValue="Collapsed"
|
||||||
|
GreaterThan="0"
|
||||||
|
TrueValue="Visible" />
|
||||||
|
<tkconverters:StringVisibilityConverter
|
||||||
|
x:Key="StringVisibilityConverter"
|
||||||
|
EmptyValue="Collapsed"
|
||||||
|
NotEmptyValue="Visible" />
|
||||||
|
</Page.Resources>
|
||||||
|
|
||||||
|
<Grid Padding="16" RowSpacing="16">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="48" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid VerticalAlignment="Top">
|
||||||
|
<Button
|
||||||
|
x:Name="NewRemappingBtn"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Click="NewRemappingBtn_Click">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<FontIcon FontSize="14" Glyph="" />
|
||||||
|
<TextBlock x:Uid="NewRemappingBtn_Text" VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
Grid.Row="1"
|
||||||
|
Background="{ThemeResource LayerFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="{ThemeResource OverlayCornerRadius}">
|
||||||
|
<tkcontrols:SwitchPresenter
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
TargetType="x:String"
|
||||||
|
Value="{x:Bind MappingState, Mode=OneWay}">
|
||||||
|
<tkcontrols:Case Value="Empty">
|
||||||
|
<StackPanel
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Spacing="8">
|
||||||
|
<FontIcon
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="24"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Glyph="" />
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="EmptyStateTitle"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
CharacterSpacing="12"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="EmptyStateDescription"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
TextAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
<tkcontrols:Case Value="HasMappings">
|
||||||
|
<ScrollViewer VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="24">
|
||||||
|
<!-- Remappings Section -->
|
||||||
|
<StackPanel Orientation="Vertical" Visibility="{x:Bind RemappingList.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="RemappingsHeader"
|
||||||
|
Margin="16,16,0,8"
|
||||||
|
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||||
|
<ListView
|
||||||
|
IsItemClickEnabled="True"
|
||||||
|
ItemClick="RemappingsList_ItemClick"
|
||||||
|
ItemsSource="{x:Bind RemappingList}"
|
||||||
|
ScrollViewer.VerticalScrollMode="Disabled"
|
||||||
|
SelectionMode="None">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="helper:Remapping">
|
||||||
|
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="MapsToText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind RemappedKeys}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource RemappedKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="InText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<ToggleSwitch
|
||||||
|
IsOn="{x:Bind IsActive}"
|
||||||
|
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||||
|
Toggled="ToggleSwitch_Toggled" />
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}">
|
||||||
|
<Button.Flyout>
|
||||||
|
<MenuFlyout>
|
||||||
|
<MenuFlyoutItem
|
||||||
|
x:Uid="DeleteMenuItem"
|
||||||
|
Click="DeleteMapping_Click"
|
||||||
|
Icon="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Tag="{x:Bind}" />
|
||||||
|
</MenuFlyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Text Section -->
|
||||||
|
<StackPanel Orientation="Vertical" Visibility="{x:Bind TextMappings.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="TextMappingsHeader"
|
||||||
|
Margin="16,16,0,8"
|
||||||
|
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||||
|
<ListView
|
||||||
|
IsItemClickEnabled="True"
|
||||||
|
ItemClick="TextMappingsList_ItemClick"
|
||||||
|
ItemsSource="{x:Bind TextMappings}"
|
||||||
|
ScrollViewer.VerticalScrollMode="Disabled"
|
||||||
|
SelectionMode="None">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="helper:TextMapping">
|
||||||
|
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="InsertsText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl
|
||||||
|
ActionType="Text"
|
||||||
|
Label="{x:Bind Text}"
|
||||||
|
Style="{StaticResource RemappedIconLabelControlStyle}" />
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="InText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<ToggleSwitch
|
||||||
|
IsOn="{x:Bind IsActive}"
|
||||||
|
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||||
|
Toggled="ToggleSwitch_Toggled" />
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}">
|
||||||
|
<Button.Flyout>
|
||||||
|
<MenuFlyout>
|
||||||
|
<MenuFlyoutItem
|
||||||
|
x:Uid="DeleteMenuItem"
|
||||||
|
Click="DeleteMapping_Click"
|
||||||
|
Icon="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Tag="{x:Bind}" />
|
||||||
|
</MenuFlyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Programs Section -->
|
||||||
|
<StackPanel Orientation="Vertical" Visibility="{x:Bind ProgramShortcuts.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="ProgramsHeader"
|
||||||
|
Margin="16,16,0,8"
|
||||||
|
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
IsItemClickEnabled="True"
|
||||||
|
ItemClick="ProgramShortcutsList_ItemClick"
|
||||||
|
ItemsSource="{x:Bind ProgramShortcuts}"
|
||||||
|
ScrollViewer.VerticalScrollMode="Disabled"
|
||||||
|
SelectionMode="None">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="helper:ProgramShortcut">
|
||||||
|
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip>
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock x:Uid="TooltipArguments" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text="{x:Bind Args}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
Visibility="{x:Bind StartInDirectory, Converter={StaticResource StringVisibilityConverter}}">
|
||||||
|
<TextBlock x:Uid="TooltipStartIn" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text="{x:Bind StartInDirectory}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock x:Uid="TooltipElevation" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text="{x:Bind Elevation}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock x:Uid="TooltipIfRunning" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text="{x:Bind IfRunningAction}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock x:Uid="TooltipWindow" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text="{x:Bind Visibility}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</ToolTip>
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="OpensText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl
|
||||||
|
ActionType="Program"
|
||||||
|
Label="{x:Bind AppToRun}"
|
||||||
|
Style="{StaticResource RemappedIconLabelControlStyle}" />
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="InText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<ToggleSwitch
|
||||||
|
IsOn="{x:Bind IsActive}"
|
||||||
|
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||||
|
Toggled="ToggleSwitch_Toggled" />
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}">
|
||||||
|
<Button.Flyout>
|
||||||
|
<MenuFlyout>
|
||||||
|
<MenuFlyoutItem
|
||||||
|
x:Uid="DeleteMenuItem"
|
||||||
|
Click="DeleteMapping_Click"
|
||||||
|
Icon="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Tag="{x:Bind}" />
|
||||||
|
</MenuFlyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- URLs Section -->
|
||||||
|
<StackPanel Orientation="Vertical" Visibility="{x:Bind UrlShortcuts.Count, Mode=OneWay, Converter={StaticResource CountToVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="UrlsHeader"
|
||||||
|
Margin="16,16,0,8"
|
||||||
|
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||||
|
<ListView
|
||||||
|
IsItemClickEnabled="True"
|
||||||
|
ItemClick="UrlShortcutsList_ItemClick"
|
||||||
|
ItemsSource="{x:Bind UrlShortcuts}"
|
||||||
|
ScrollViewer.VerticalScrollMode="Disabled"
|
||||||
|
SelectionMode="None">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="helper:URLShortcut">
|
||||||
|
<Grid MinHeight="48" Padding="0,8,0,8">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Rectangle Style="{StaticResource ItemDividerStyle}" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<ItemsControl VerticalAlignment="Center" ItemsSource="{x:Bind Shortcut}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<commoncontrols:KeyVisual Content="{Binding}" Style="{StaticResource OriginalKeyVisualStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="OpensText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl
|
||||||
|
ActionType="Url"
|
||||||
|
Label="{x:Bind URL}"
|
||||||
|
Style="{StaticResource RemappedIconLabelControlStyle}" />
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
Visibility="{x:Bind AppName, Converter={StaticResource StringVisibilityConverter}}">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="InText"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
|
<controls:IconLabelControl ActionType="Program" Label="{x:Bind AppName}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<ToggleSwitch
|
||||||
|
IsOn="{x:Bind IsActive}"
|
||||||
|
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}"
|
||||||
|
Toggled="ToggleSwitch_Toggled" />
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}">
|
||||||
|
<Button.Flyout>
|
||||||
|
<MenuFlyout>
|
||||||
|
<MenuFlyoutItem
|
||||||
|
x:Uid="DeleteMenuItem"
|
||||||
|
Click="DeleteMapping_Click"
|
||||||
|
Icon="{ui:FontIcon Glyph=,
|
||||||
|
FontSize=14}"
|
||||||
|
Tag="{x:Bind}" />
|
||||||
|
</MenuFlyout>
|
||||||
|
</Button.Flyout>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
</tkcontrols:SwitchPresenter>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Content Dialog for new remapping -->
|
||||||
|
<ContentDialog
|
||||||
|
x:Name="RemappingDialog"
|
||||||
|
x:Uid="RemappingDialog"
|
||||||
|
Width="760"
|
||||||
|
MinWidth="800"
|
||||||
|
MinHeight="500"
|
||||||
|
MaxWidth="900"
|
||||||
|
DefaultButton="Primary"
|
||||||
|
IsPrimaryButtonEnabled="False"
|
||||||
|
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||||
|
<controls:UnifiedMappingControl x:Name="UnifiedMappingControl" Margin="0,16,0,0" />
|
||||||
|
</ContentDialog>
|
||||||
|
|
||||||
|
<!-- Confirmation Dialog for delete -->
|
||||||
|
<ContentDialog
|
||||||
|
x:Name="DeleteConfirmationDialog"
|
||||||
|
x:Uid="DeleteConfirmationDialog"
|
||||||
|
DefaultButton="Primary">
|
||||||
|
<TextBlock x:Uid="DeleteConfirmationDialogContent" TextWrapping="Wrap" />
|
||||||
|
</ContentDialog>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,897 @@
|
|||||||
|
// 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.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using KeyboardManagerEditorUI.Controls;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A consolidated page that displays all mappings from Remappings, Text, Programs, and URLs pages.
|
||||||
|
/// </summary>
|
||||||
|
#pragma warning disable SA1124 // Do not use regions
|
||||||
|
public sealed partial class MainPage : Page, IDisposable, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private KeyboardMappingService? _mappingService;
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _isEditMode;
|
||||||
|
private EditingItem? _editingItem;
|
||||||
|
private string _mappingState = "Empty";
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public string MappingState
|
||||||
|
{
|
||||||
|
get => _mappingState;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_mappingState != value)
|
||||||
|
{
|
||||||
|
_mappingState = value;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MappingState)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<Remapping> RemappingList { get; } = new();
|
||||||
|
|
||||||
|
public ObservableCollection<TextMapping> TextMappings { get; } = new();
|
||||||
|
|
||||||
|
public ObservableCollection<ProgramShortcut> ProgramShortcuts { get; } = new();
|
||||||
|
|
||||||
|
public ObservableCollection<URLShortcut> UrlShortcuts { get; } = new();
|
||||||
|
|
||||||
|
[DllImport("PowerToys.KeyboardManagerEditorLibraryWrapper.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
|
||||||
|
private static extern void GetKeyDisplayName(int keyCode, [Out] StringBuilder keyName, int maxLength);
|
||||||
|
|
||||||
|
private sealed class EditingItem
|
||||||
|
{
|
||||||
|
public enum ItemType
|
||||||
|
{
|
||||||
|
Remapping,
|
||||||
|
TextMapping,
|
||||||
|
ProgramShortcut,
|
||||||
|
UrlShortcut,
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemType Type { get; set; }
|
||||||
|
|
||||||
|
public object Item { get; set; } = null!;
|
||||||
|
|
||||||
|
public List<string> OriginalTriggerKeys { get; set; } = new();
|
||||||
|
|
||||||
|
public string? AppName { get; set; }
|
||||||
|
|
||||||
|
public bool IsAllApps { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainPage()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mappingService = new KeyboardMappingService();
|
||||||
|
LoadAllMappings();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Failed to initialize KeyboardMappingService in MainPage page: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Unloaded += All_Unloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void All_Unloaded(object sender, RoutedEventArgs e) => Dispose();
|
||||||
|
|
||||||
|
#region Dialog Show Methods
|
||||||
|
|
||||||
|
private async void NewRemappingBtn_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_isEditMode = false;
|
||||||
|
_editingItem = null;
|
||||||
|
UnifiedMappingControl.Reset();
|
||||||
|
RemappingDialog.Title = "New remapping";
|
||||||
|
await ShowRemappingDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RemappingsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ClickedItem is not Remapping remapping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEditMode = true;
|
||||||
|
_editingItem = new EditingItem
|
||||||
|
{
|
||||||
|
Type = EditingItem.ItemType.Remapping,
|
||||||
|
Item = remapping,
|
||||||
|
OriginalTriggerKeys = remapping.Shortcut.ToList(),
|
||||||
|
AppName = remapping.AppName,
|
||||||
|
IsAllApps = remapping.IsAllApps,
|
||||||
|
};
|
||||||
|
|
||||||
|
UnifiedMappingControl.Reset();
|
||||||
|
UnifiedMappingControl.SetTriggerKeys(remapping.Shortcut.ToList());
|
||||||
|
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.KeyOrShortcut);
|
||||||
|
UnifiedMappingControl.SetActionKeys(remapping.RemappedKeys.ToList());
|
||||||
|
UnifiedMappingControl.SetAppSpecific(!remapping.IsAllApps, remapping.AppName);
|
||||||
|
RemappingDialog.Title = "Edit remapping";
|
||||||
|
await ShowRemappingDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TextMappingsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ClickedItem is not TextMapping textMapping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEditMode = true;
|
||||||
|
_editingItem = new EditingItem
|
||||||
|
{
|
||||||
|
Type = EditingItem.ItemType.TextMapping,
|
||||||
|
Item = textMapping,
|
||||||
|
OriginalTriggerKeys = textMapping.Shortcut.ToList(),
|
||||||
|
AppName = textMapping.AppName,
|
||||||
|
IsAllApps = textMapping.IsAllApps,
|
||||||
|
};
|
||||||
|
|
||||||
|
UnifiedMappingControl.Reset();
|
||||||
|
UnifiedMappingControl.SetTriggerKeys(textMapping.Shortcut.ToList());
|
||||||
|
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.Text);
|
||||||
|
UnifiedMappingControl.SetTextContent(textMapping.Text);
|
||||||
|
UnifiedMappingControl.SetAppSpecific(!textMapping.IsAllApps, textMapping.AppName);
|
||||||
|
RemappingDialog.Title = "Edit remapping";
|
||||||
|
await ShowRemappingDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ProgramShortcutsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ClickedItem is not ProgramShortcut programShortcut)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEditMode = true;
|
||||||
|
_editingItem = new EditingItem
|
||||||
|
{
|
||||||
|
Type = EditingItem.ItemType.ProgramShortcut,
|
||||||
|
Item = programShortcut,
|
||||||
|
OriginalTriggerKeys = programShortcut.Shortcut.ToList(),
|
||||||
|
AppName = programShortcut.AppName,
|
||||||
|
IsAllApps = programShortcut.IsAllApps,
|
||||||
|
};
|
||||||
|
|
||||||
|
UnifiedMappingControl.Reset();
|
||||||
|
UnifiedMappingControl.SetTriggerKeys(programShortcut.Shortcut.ToList());
|
||||||
|
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.OpenApp);
|
||||||
|
UnifiedMappingControl.SetProgramPath(programShortcut.AppToRun);
|
||||||
|
UnifiedMappingControl.SetProgramArgs(programShortcut.Args);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(programShortcut.Id) &&
|
||||||
|
SettingsManager.EditorSettings.ShortcutSettingsDictionary.TryGetValue(programShortcut.Id, out var settings))
|
||||||
|
{
|
||||||
|
var mapping = settings.Shortcut;
|
||||||
|
UnifiedMappingControl.SetStartInDirectory(mapping.StartInDirectory);
|
||||||
|
UnifiedMappingControl.SetElevationLevel(mapping.Elevation);
|
||||||
|
UnifiedMappingControl.SetVisibility(mapping.Visibility);
|
||||||
|
UnifiedMappingControl.SetIfRunningAction(mapping.IfRunningAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
UnifiedMappingControl.SetAppSpecific(!programShortcut.IsAllApps, programShortcut.AppName);
|
||||||
|
RemappingDialog.Title = "Edit remapping";
|
||||||
|
await ShowRemappingDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void UrlShortcutsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.ClickedItem is not URLShortcut urlShortcut)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEditMode = true;
|
||||||
|
_editingItem = new EditingItem
|
||||||
|
{
|
||||||
|
Type = EditingItem.ItemType.UrlShortcut,
|
||||||
|
Item = urlShortcut,
|
||||||
|
OriginalTriggerKeys = urlShortcut.Shortcut.ToList(),
|
||||||
|
AppName = urlShortcut.AppName,
|
||||||
|
IsAllApps = urlShortcut.IsAllApps,
|
||||||
|
};
|
||||||
|
|
||||||
|
UnifiedMappingControl.Reset();
|
||||||
|
UnifiedMappingControl.SetTriggerKeys(urlShortcut.Shortcut.ToList());
|
||||||
|
UnifiedMappingControl.SetActionType(UnifiedMappingControl.ActionType.OpenUrl);
|
||||||
|
UnifiedMappingControl.SetUrl(urlShortcut.URL);
|
||||||
|
UnifiedMappingControl.SetAppSpecific(!urlShortcut.IsAllApps, urlShortcut.AppName);
|
||||||
|
RemappingDialog.Title = "Edit remapping";
|
||||||
|
await ShowRemappingDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async System.Threading.Tasks.Task ShowRemappingDialog()
|
||||||
|
{
|
||||||
|
RemappingDialog.PrimaryButtonClick += RemappingDialog_PrimaryButtonClick;
|
||||||
|
UnifiedMappingControl.ValidationStateChanged += UnifiedMappingControl_ValidationStateChanged;
|
||||||
|
RemappingDialog.IsPrimaryButtonEnabled = UnifiedMappingControl.IsInputComplete();
|
||||||
|
|
||||||
|
await RemappingDialog.ShowAsync();
|
||||||
|
|
||||||
|
RemappingDialog.PrimaryButtonClick -= RemappingDialog_PrimaryButtonClick;
|
||||||
|
UnifiedMappingControl.ValidationStateChanged -= UnifiedMappingControl_ValidationStateChanged;
|
||||||
|
_isEditMode = false;
|
||||||
|
_editingItem = null;
|
||||||
|
KeyboardHookHelper.Instance.CleanupHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnifiedMappingControl_ValidationStateChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!UnifiedMappingControl.IsInputComplete())
|
||||||
|
{
|
||||||
|
RemappingDialog.IsPrimaryButtonEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mappingService != null)
|
||||||
|
{
|
||||||
|
List<string> triggerKeys = UnifiedMappingControl.GetTriggerKeys();
|
||||||
|
if (triggerKeys?.Count > 0)
|
||||||
|
{
|
||||||
|
ValidationErrorType error = ValidateMapping(UnifiedMappingControl.CurrentActionType, triggerKeys);
|
||||||
|
if (error != ValidationErrorType.NoError)
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ShowValidationErrorFromType(error);
|
||||||
|
RemappingDialog.IsPrimaryButtonEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UnifiedMappingControl.HideValidationMessage();
|
||||||
|
RemappingDialog.IsPrimaryButtonEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Save Logic
|
||||||
|
|
||||||
|
private void RemappingDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.HideValidationMessage();
|
||||||
|
|
||||||
|
if (_mappingService == null)
|
||||||
|
{
|
||||||
|
Logger.LogError("Mapping service is null, cannot save mapping");
|
||||||
|
UnifiedMappingControl.ShowValidationError("Error", "Mapping service is not available.");
|
||||||
|
args.Cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<string> triggerKeys = UnifiedMappingControl.GetTriggerKeys();
|
||||||
|
|
||||||
|
if (triggerKeys == null || triggerKeys.Count == 0)
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ShowValidationError("Missing Original Keys", "Please enter at least one original key to create a remapping.");
|
||||||
|
args.Cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationErrorType validationError = ValidateMapping(UnifiedMappingControl.CurrentActionType, triggerKeys);
|
||||||
|
if (validationError != ValidationErrorType.NoError)
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ShowValidationErrorFromType(validationError);
|
||||||
|
args.Cancel = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isEditMode && _editingItem != null)
|
||||||
|
{
|
||||||
|
DeleteExistingMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saved = UnifiedMappingControl.CurrentActionType switch
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ActionType.KeyOrShortcut => SaveKeyOrShortcutMapping(triggerKeys),
|
||||||
|
UnifiedMappingControl.ActionType.Text => SaveTextMapping(triggerKeys),
|
||||||
|
UnifiedMappingControl.ActionType.OpenUrl => SaveUrlMapping(triggerKeys),
|
||||||
|
UnifiedMappingControl.ActionType.OpenApp => SaveProgramMapping(triggerKeys),
|
||||||
|
UnifiedMappingControl.ActionType.MouseClick => throw new NotImplementedException("Mouse click remapping is not yet supported."),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (saved)
|
||||||
|
{
|
||||||
|
LoadAllMappings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ShowValidationError("Save Failed", "Failed to save the remapping. Please try again.");
|
||||||
|
args.Cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NotImplementedException ex)
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ShowValidationError("Not Implemented", ex.Message);
|
||||||
|
args.Cancel = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error saving mapping: " + ex.Message);
|
||||||
|
UnifiedMappingControl.ShowValidationError("Error", "An error occurred while saving: " + ex.Message);
|
||||||
|
args.Cancel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationErrorType ValidateMapping(UnifiedMappingControl.ActionType actionType, List<string> triggerKeys)
|
||||||
|
{
|
||||||
|
bool isAppSpecific = UnifiedMappingControl.GetIsAppSpecific();
|
||||||
|
string appName = UnifiedMappingControl.GetAppName();
|
||||||
|
Remapping? editingRemapping = _isEditMode && _editingItem?.Item is Remapping r ? r : null;
|
||||||
|
|
||||||
|
return actionType switch
|
||||||
|
{
|
||||||
|
UnifiedMappingControl.ActionType.KeyOrShortcut => ValidationHelper.ValidateKeyMapping(
|
||||||
|
triggerKeys, UnifiedMappingControl.GetActionKeys(), isAppSpecific, appName, _mappingService!, _isEditMode, editingRemapping),
|
||||||
|
UnifiedMappingControl.ActionType.Text => ValidationHelper.ValidateTextMapping(
|
||||||
|
triggerKeys, UnifiedMappingControl.GetTextContent(), isAppSpecific, appName, _mappingService!, _isEditMode),
|
||||||
|
UnifiedMappingControl.ActionType.OpenUrl => ValidationHelper.ValidateUrlMapping(
|
||||||
|
triggerKeys, UnifiedMappingControl.GetUrl(), isAppSpecific, appName, _mappingService!, _isEditMode),
|
||||||
|
UnifiedMappingControl.ActionType.OpenApp => ValidationHelper.ValidateAppMapping(
|
||||||
|
triggerKeys, UnifiedMappingControl.GetProgramPath(), isAppSpecific, appName, _mappingService!, _isEditMode),
|
||||||
|
_ => ValidationErrorType.NoError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteExistingMapping()
|
||||||
|
{
|
||||||
|
if (_editingItem == null || _mappingService == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (_editingItem.Type)
|
||||||
|
{
|
||||||
|
case EditingItem.ItemType.Remapping when _editingItem.Item is Remapping remapping:
|
||||||
|
RemappingHelper.DeleteRemapping(_mappingService, remapping);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (_editingItem.Item is IToggleableShortcut shortcut)
|
||||||
|
{
|
||||||
|
DeleteShortcutMapping(_editingItem.OriginalTriggerKeys, _editingItem.AppName ?? string.Empty);
|
||||||
|
if (!string.IsNullOrEmpty(shortcut.Id))
|
||||||
|
{
|
||||||
|
SettingsManager.RemoveShortcutKeyMappingFromSettings(shortcut.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error deleting existing mapping: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteShortcutMapping(List<string> originalKeys, string targetApp = "")
|
||||||
|
{
|
||||||
|
bool deleted = originalKeys.Count == 1
|
||||||
|
? DeleteSingleKeyToTextMapping(originalKeys[0])
|
||||||
|
: DeleteMultiKeyMapping(originalKeys, targetApp);
|
||||||
|
|
||||||
|
if (deleted)
|
||||||
|
{
|
||||||
|
_mappingService!.SaveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeleteMultiKeyMapping(List<string> originalKeys, string targetApp = "")
|
||||||
|
{
|
||||||
|
string originalKeysString = string.Join(";", originalKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||||
|
return _mappingService!.DeleteShortcutMapping(originalKeysString, targetApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SaveKeyOrShortcutMapping(List<string> triggerKeys)
|
||||||
|
{
|
||||||
|
List<string> actionKeys = UnifiedMappingControl.GetActionKeys();
|
||||||
|
if (actionKeys == null || actionKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RemappingHelper.SaveMapping(
|
||||||
|
_mappingService!,
|
||||||
|
triggerKeys,
|
||||||
|
actionKeys,
|
||||||
|
UnifiedMappingControl.GetIsAppSpecific(),
|
||||||
|
UnifiedMappingControl.GetAppName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SaveTextMapping(List<string> triggerKeys)
|
||||||
|
{
|
||||||
|
string textContent = UnifiedMappingControl.GetTextContent();
|
||||||
|
bool isAppSpecific = UnifiedMappingControl.GetIsAppSpecific();
|
||||||
|
string appName = UnifiedMappingControl.GetAppName();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(textContent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggerKeys.Count == 1
|
||||||
|
? SaveSingleKeyToTextMapping(triggerKeys[0], textContent, isAppSpecific, appName)
|
||||||
|
: SaveShortcutToTextMapping(triggerKeys, textContent, isAppSpecific, appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SaveSingleKeyToTextMapping(string keyName, string textContent, bool isAppSpecific, string appName)
|
||||||
|
{
|
||||||
|
int originalKey = _mappingService!.GetKeyCodeFromName(keyName);
|
||||||
|
if (originalKey == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapText,
|
||||||
|
OriginalKeys = originalKey.ToString(CultureInfo.InvariantCulture),
|
||||||
|
TargetKeys = textContent,
|
||||||
|
TargetText = textContent,
|
||||||
|
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool saved = _mappingService.AddSingleKeyToTextMapping(originalKey, textContent);
|
||||||
|
if (saved)
|
||||||
|
{
|
||||||
|
_mappingService.SaveSettings();
|
||||||
|
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SaveShortcutToTextMapping(List<string> triggerKeys, string textContent, bool isAppSpecific, string appName)
|
||||||
|
{
|
||||||
|
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||||
|
|
||||||
|
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapText,
|
||||||
|
OriginalKeys = originalKeysString,
|
||||||
|
TargetKeys = textContent,
|
||||||
|
TargetText = textContent,
|
||||||
|
TargetApp = isAppSpecific ? appName : string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool saved = isAppSpecific && !string.IsNullOrEmpty(appName)
|
||||||
|
? _mappingService!.AddShortcutMapping(originalKeysString, textContent, appName, ShortcutOperationType.RemapText)
|
||||||
|
: _mappingService!.AddShortcutMapping(originalKeysString, textContent, operationType: ShortcutOperationType.RemapText);
|
||||||
|
|
||||||
|
if (saved)
|
||||||
|
{
|
||||||
|
_mappingService.SaveSettings();
|
||||||
|
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SaveUrlMapping(List<string> triggerKeys)
|
||||||
|
{
|
||||||
|
string url = UnifiedMappingControl.GetUrl();
|
||||||
|
if (string.IsNullOrEmpty(url))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||||
|
|
||||||
|
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.OpenUri,
|
||||||
|
OriginalKeys = originalKeysString,
|
||||||
|
TargetKeys = originalKeysString,
|
||||||
|
UriToOpen = url,
|
||||||
|
TargetApp = UnifiedMappingControl.GetIsAppSpecific() ? UnifiedMappingControl.GetAppName() : string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool saved = _mappingService!.AddShortcutMapping(shortcutKeyMapping);
|
||||||
|
if (saved)
|
||||||
|
{
|
||||||
|
_mappingService.SaveSettings();
|
||||||
|
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SaveProgramMapping(List<string> triggerKeys)
|
||||||
|
{
|
||||||
|
string programPath = UnifiedMappingControl.GetProgramPath();
|
||||||
|
if (string.IsNullOrEmpty(programPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string originalKeysString = string.Join(";", triggerKeys.Select(k => _mappingService!.GetKeyCodeFromName(k).ToString(CultureInfo.InvariantCulture)));
|
||||||
|
|
||||||
|
var shortcutKeyMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RunProgram,
|
||||||
|
OriginalKeys = originalKeysString,
|
||||||
|
TargetKeys = originalKeysString,
|
||||||
|
ProgramPath = programPath,
|
||||||
|
ProgramArgs = UnifiedMappingControl.GetProgramArgs(),
|
||||||
|
StartInDirectory = UnifiedMappingControl.GetStartInDirectory(),
|
||||||
|
IfRunningAction = UnifiedMappingControl.GetIfRunningAction(),
|
||||||
|
Visibility = UnifiedMappingControl.GetVisibility(),
|
||||||
|
Elevation = UnifiedMappingControl.GetElevationLevel(),
|
||||||
|
TargetApp = UnifiedMappingControl.GetIsAppSpecific() ? UnifiedMappingControl.GetAppName() : string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool saved = _mappingService!.AddShortcutMapping(shortcutKeyMapping);
|
||||||
|
if (saved)
|
||||||
|
{
|
||||||
|
_mappingService.SaveSettings();
|
||||||
|
SettingsManager.AddShortcutKeyMappingToSettings(shortcutKeyMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delete Handlers
|
||||||
|
|
||||||
|
private async void DeleteMapping_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not MenuFlyoutItem menuFlyoutItem || _mappingService == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await DeleteConfirmationDialog.ShowAsync() != ContentDialogResult.Primary)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (menuFlyoutItem.Tag)
|
||||||
|
{
|
||||||
|
case Remapping remapping:
|
||||||
|
HandleRemappingDelete(remapping);
|
||||||
|
UpdateHasAnyMappings();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IToggleableShortcut shortcut:
|
||||||
|
HandleShortcutDelete(shortcut);
|
||||||
|
LoadAllMappings();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error deleting mapping: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRemappingDelete(Remapping remapping)
|
||||||
|
{
|
||||||
|
if (!remapping.IsActive)
|
||||||
|
{
|
||||||
|
SettingsManager.RemoveShortcutKeyMappingFromSettings(remapping.Id);
|
||||||
|
LoadRemappings();
|
||||||
|
}
|
||||||
|
else if (RemappingHelper.DeleteRemapping(_mappingService!, remapping))
|
||||||
|
{
|
||||||
|
LoadRemappings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Failed to delete remapping: {string.Join("+", remapping.Shortcut)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleShortcutDelete(IToggleableShortcut shortcut)
|
||||||
|
{
|
||||||
|
bool deleted = shortcut.Shortcut.Count == 1
|
||||||
|
? DeleteSingleKeyToTextMapping(shortcut.Shortcut[0]) // Remapping has its own handler, single key will always be text mapping
|
||||||
|
: DeleteMultiKeyShortcut(shortcut);
|
||||||
|
|
||||||
|
if (deleted)
|
||||||
|
{
|
||||||
|
_mappingService!.SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsManager.RemoveShortcutKeyMappingFromSettings(shortcut.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeleteMultiKeyShortcut(IToggleableShortcut shortcut)
|
||||||
|
{
|
||||||
|
string originalKeys = string.Join(";", shortcut.Shortcut.Select(k => _mappingService!.GetKeyCodeFromName(k)));
|
||||||
|
return _mappingService!.DeleteShortcutMapping(originalKeys, shortcut.AppName);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Toggle Switch Handlers
|
||||||
|
|
||||||
|
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not ToggleSwitch toggleSwitch || toggleSwitch.DataContext is not IToggleableShortcut shortcut || _mappingService == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (toggleSwitch.IsOn)
|
||||||
|
{
|
||||||
|
EnableShortcut(shortcut);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisableShortcut(shortcut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Error toggling shortcut active state: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnableShortcut(IToggleableShortcut shortcut)
|
||||||
|
{
|
||||||
|
if (shortcut is Remapping remapping)
|
||||||
|
{
|
||||||
|
RemappingHelper.SaveMapping(_mappingService!, remapping.Shortcut, remapping.RemappedKeys, !remapping.IsAllApps, remapping.AppName, false);
|
||||||
|
shortcut.IsActive = true;
|
||||||
|
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortcutKeyMapping shortcutKeyMapping = SettingsManager.EditorSettings.ShortcutSettingsDictionary[shortcut.Id].Shortcut;
|
||||||
|
bool saved = shortcut.Shortcut.Count == 1
|
||||||
|
? _mappingService!.AddSingleKeyToTextMapping(_mappingService.GetKeyCodeFromName(shortcut.Shortcut[0]), shortcutKeyMapping.TargetText)
|
||||||
|
: shortcutKeyMapping.OperationType == ShortcutOperationType.RemapText
|
||||||
|
? _mappingService!.AddShortcutMapping(shortcutKeyMapping.OriginalKeys, shortcutKeyMapping.TargetText, operationType: ShortcutOperationType.RemapText)
|
||||||
|
: _mappingService!.AddShortcutMapping(shortcutKeyMapping);
|
||||||
|
|
||||||
|
if (saved)
|
||||||
|
{
|
||||||
|
shortcut.IsActive = true;
|
||||||
|
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||||
|
_mappingService.SaveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableShortcut(IToggleableShortcut shortcut)
|
||||||
|
{
|
||||||
|
if (shortcut is Remapping remapping)
|
||||||
|
{
|
||||||
|
shortcut.IsActive = false;
|
||||||
|
RemappingHelper.DeleteRemapping(_mappingService!, remapping, false);
|
||||||
|
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleted = shortcut.Shortcut.Count == 1
|
||||||
|
? DeleteSingleKeyToTextMapping(shortcut.Shortcut[0])
|
||||||
|
: DeleteMultiKeyMapping(shortcut.Shortcut, shortcut.AppName);
|
||||||
|
|
||||||
|
if (deleted)
|
||||||
|
{
|
||||||
|
shortcut.IsActive = false;
|
||||||
|
SettingsManager.ToggleShortcutKeyMappingActiveState(shortcut.Id);
|
||||||
|
_mappingService!.SaveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeleteSingleKeyToTextMapping(string keyName)
|
||||||
|
{
|
||||||
|
int originalKey = _mappingService!.GetKeyCodeFromName(keyName);
|
||||||
|
return originalKey != 0 && _mappingService.DeleteSingleKeyToTextMapping(originalKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Load Methods
|
||||||
|
|
||||||
|
private void LoadAllMappings()
|
||||||
|
{
|
||||||
|
LoadRemappings();
|
||||||
|
LoadTextMappings();
|
||||||
|
LoadProgramShortcuts();
|
||||||
|
LoadUrlShortcuts();
|
||||||
|
UpdateHasAnyMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHasAnyMappings()
|
||||||
|
{
|
||||||
|
bool hasAny = RemappingList.Count > 0 || TextMappings.Count > 0 || ProgramShortcuts.Count > 0 || UrlShortcuts.Count > 0;
|
||||||
|
MappingState = hasAny ? "HasMappings" : "Empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadRemappings()
|
||||||
|
{
|
||||||
|
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.RemapShortcut, out var remapShortcutIds);
|
||||||
|
|
||||||
|
if (_mappingService == null || remapShortcutIds == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemappingList.Clear();
|
||||||
|
|
||||||
|
foreach (var id in remapShortcutIds)
|
||||||
|
{
|
||||||
|
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||||
|
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||||
|
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||||
|
var remappedKeyNames = ParseKeyCodes(mapping.TargetKeys);
|
||||||
|
|
||||||
|
RemappingList.Add(new Remapping
|
||||||
|
{
|
||||||
|
Shortcut = originalKeyNames,
|
||||||
|
RemappedKeys = remappedKeyNames,
|
||||||
|
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||||
|
AppName = mapping.TargetApp ?? string.Empty,
|
||||||
|
Id = shortcutSettings.Id,
|
||||||
|
IsActive = shortcutSettings.IsActive,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadTextMappings()
|
||||||
|
{
|
||||||
|
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.RemapText, out var remapShortcutIds);
|
||||||
|
|
||||||
|
if (_mappingService == null || remapShortcutIds == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextMappings.Clear();
|
||||||
|
|
||||||
|
foreach (var id in remapShortcutIds)
|
||||||
|
{
|
||||||
|
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||||
|
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||||
|
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||||
|
|
||||||
|
TextMappings.Add(new TextMapping
|
||||||
|
{
|
||||||
|
Shortcut = originalKeyNames,
|
||||||
|
Text = mapping.TargetText,
|
||||||
|
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||||
|
AppName = mapping.TargetApp ?? string.Empty,
|
||||||
|
Id = shortcutSettings.Id,
|
||||||
|
IsActive = shortcutSettings.IsActive,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadProgramShortcuts()
|
||||||
|
{
|
||||||
|
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.RunProgram, out var remapShortcutIds);
|
||||||
|
|
||||||
|
if (_mappingService == null || remapShortcutIds == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgramShortcuts.Clear();
|
||||||
|
|
||||||
|
foreach (var id in remapShortcutIds)
|
||||||
|
{
|
||||||
|
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||||
|
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||||
|
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||||
|
|
||||||
|
ProgramShortcuts.Add(new ProgramShortcut
|
||||||
|
{
|
||||||
|
Shortcut = originalKeyNames,
|
||||||
|
AppToRun = mapping.ProgramPath,
|
||||||
|
Args = mapping.ProgramArgs,
|
||||||
|
IsActive = shortcutSettings.IsActive,
|
||||||
|
Id = shortcutSettings.Id,
|
||||||
|
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||||
|
AppName = mapping.TargetApp ?? string.Empty,
|
||||||
|
StartInDirectory = mapping.StartInDirectory,
|
||||||
|
Elevation = mapping.Elevation.ToString(),
|
||||||
|
IfRunningAction = mapping.IfRunningAction.ToString(),
|
||||||
|
Visibility = mapping.Visibility.ToString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadUrlShortcuts()
|
||||||
|
{
|
||||||
|
SettingsManager.EditorSettings.ShortcutsByOperationType.TryGetValue(ShortcutOperationType.OpenUri, out var remapShortcutIds);
|
||||||
|
|
||||||
|
if (_mappingService == null || remapShortcutIds == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlShortcuts.Clear();
|
||||||
|
|
||||||
|
foreach (var id in remapShortcutIds)
|
||||||
|
{
|
||||||
|
ShortcutSettings shortcutSettings = SettingsManager.EditorSettings.ShortcutSettingsDictionary[id];
|
||||||
|
ShortcutKeyMapping mapping = shortcutSettings.Shortcut;
|
||||||
|
var originalKeyNames = ParseKeyCodes(mapping.OriginalKeys);
|
||||||
|
|
||||||
|
UrlShortcuts.Add(new URLShortcut
|
||||||
|
{
|
||||||
|
Shortcut = originalKeyNames,
|
||||||
|
URL = mapping.UriToOpen,
|
||||||
|
Id = shortcutSettings.Id,
|
||||||
|
IsActive = shortcutSettings.IsActive,
|
||||||
|
IsAllApps = string.IsNullOrEmpty(mapping.TargetApp),
|
||||||
|
AppName = mapping.TargetApp ?? string.Empty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> ParseKeyCodes(string keyCodesString)
|
||||||
|
{
|
||||||
|
return keyCodesString.Split(';')
|
||||||
|
.Where(keyCode => int.TryParse(keyCode, out int code))
|
||||||
|
.Select(keyCode => _mappingService!.GetKeyDisplayName(int.Parse(keyCode, CultureInfo.InvariantCulture)))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_mappingService?.Dispose();
|
||||||
|
_mappingService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore SA1124 // Do not use regions
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using KeyboardManagerEditorUI.Interop;
|
||||||
|
|
||||||
|
namespace KeyboardManagerEditorUI.Settings
|
||||||
|
{
|
||||||
|
internal static class SettingsManager
|
||||||
|
{
|
||||||
|
private static readonly string _settingsDirectory = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"Microsoft",
|
||||||
|
"PowerToys",
|
||||||
|
"Keyboard Manager");
|
||||||
|
|
||||||
|
private static readonly string _settingsFilePath = Path.Combine(_settingsDirectory, "editorSettings.json");
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { WriteIndented = true };
|
||||||
|
|
||||||
|
private static readonly KeyboardMappingService _mappingService = new KeyboardMappingService();
|
||||||
|
|
||||||
|
public static EditorSettings EditorSettings { get; set; }
|
||||||
|
|
||||||
|
static SettingsManager()
|
||||||
|
{
|
||||||
|
EditorSettings = LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EditorSettings LoadSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(_settingsFilePath))
|
||||||
|
{
|
||||||
|
EditorSettings createdSettings = CreateSettingsFromKeyboardManagerService();
|
||||||
|
WriteSettings(createdSettings);
|
||||||
|
return createdSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = File.ReadAllText(_settingsFilePath);
|
||||||
|
return JsonSerializer.Deserialize<EditorSettings>(json, _jsonOptions) ?? 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() => WriteSettings(EditorSettings);
|
||||||
|
|
||||||
|
private static EditorSettings CreateSettingsFromKeyboardManagerService()
|
||||||
|
{
|
||||||
|
EditorSettings settings = new EditorSettings();
|
||||||
|
|
||||||
|
// Process all shortcut mappings (RunProgram, OpenUri, RemapShortcut, RemapText)
|
||||||
|
foreach (ShortcutKeyMapping mapping in _mappingService.GetShortcutMappings())
|
||||||
|
{
|
||||||
|
AddShortcutMapping(settings, mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process single key to key mappings
|
||||||
|
foreach (var mapping in _mappingService.GetSingleKeyMappings())
|
||||||
|
{
|
||||||
|
var shortcutMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapShortcut,
|
||||||
|
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||||
|
TargetKeys = mapping.TargetKey,
|
||||||
|
};
|
||||||
|
AddShortcutMapping(settings, shortcutMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process single key to text mappings
|
||||||
|
foreach (var mapping in _mappingService.GetKeyToTextMappings())
|
||||||
|
{
|
||||||
|
var shortcutMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapText,
|
||||||
|
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||||
|
TargetKeys = mapping.TargetText,
|
||||||
|
TargetText = mapping.TargetText,
|
||||||
|
};
|
||||||
|
AddShortcutMapping(settings, shortcutMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CorrelateServiceAndEditorMappings()
|
||||||
|
{
|
||||||
|
bool shortcutSettingsChanged = false;
|
||||||
|
|
||||||
|
// Process all shortcut mappings
|
||||||
|
foreach (ShortcutKeyMapping mapping in _mappingService.GetShortcutMappings())
|
||||||
|
{
|
||||||
|
if (!EditorSettings.ShortcutSettingsDictionary.Values.Any(s => s.Shortcut.OriginalKeys == mapping.OriginalKeys))
|
||||||
|
{
|
||||||
|
AddShortcutMapping(EditorSettings, mapping);
|
||||||
|
shortcutSettingsChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process single key to key mappings
|
||||||
|
foreach (var mapping in _mappingService.GetSingleKeyMappings())
|
||||||
|
{
|
||||||
|
var shortcutMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapShortcut,
|
||||||
|
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||||
|
TargetKeys = mapping.TargetKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!MappingExists(shortcutMapping))
|
||||||
|
{
|
||||||
|
AddShortcutMapping(EditorSettings, shortcutMapping);
|
||||||
|
shortcutSettingsChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process single key to text mappings
|
||||||
|
foreach (var mapping in _mappingService.GetKeyToTextMappings())
|
||||||
|
{
|
||||||
|
var shortcutMapping = new ShortcutKeyMapping
|
||||||
|
{
|
||||||
|
OperationType = ShortcutOperationType.RemapText,
|
||||||
|
OriginalKeys = mapping.OriginalKey.ToString(CultureInfo.InvariantCulture),
|
||||||
|
TargetKeys = mapping.TargetText,
|
||||||
|
TargetText = mapping.TargetText,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!EditorSettings.ShortcutSettingsDictionary.Values.Any(s => s.Shortcut.OriginalKeys == shortcutMapping.OriginalKeys))
|
||||||
|
{
|
||||||
|
AddShortcutMapping(EditorSettings, shortcutMapping);
|
||||||
|
shortcutSettingsChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark inactive mappings
|
||||||
|
var singleKeyMappings = _mappingService.GetSingleKeyMappings();
|
||||||
|
var keyToTextMappings = _mappingService.GetKeyToTextMappings();
|
||||||
|
var shortcutKeyMappings = _mappingService.GetShortcutMappings();
|
||||||
|
|
||||||
|
foreach (ShortcutSettings shortcutSettings in EditorSettings.ShortcutSettingsDictionary.Values.ToList())
|
||||||
|
{
|
||||||
|
bool foundInService = IsMappingActiveInService(
|
||||||
|
shortcutSettings,
|
||||||
|
keyToTextMappings,
|
||||||
|
singleKeyMappings,
|
||||||
|
shortcutKeyMappings);
|
||||||
|
|
||||||
|
if (!foundInService)
|
||||||
|
{
|
||||||
|
shortcutSettingsChanged = true;
|
||||||
|
shortcutSettings.IsActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortcutSettingsChanged)
|
||||||
|
{
|
||||||
|
WriteSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddShortcutKeyMappingToSettings(ShortcutKeyMapping shortcutKeyMapping)
|
||||||
|
{
|
||||||
|
AddShortcutMapping(EditorSettings, shortcutKeyMapping);
|
||||||
|
WriteSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoveShortcutKeyMappingFromSettings(string guid)
|
||||||
|
{
|
||||||
|
ShortcutOperationType operationType = EditorSettings.ShortcutSettingsDictionary[guid].Shortcut.OperationType;
|
||||||
|
EditorSettings.ShortcutSettingsDictionary.Remove(guid);
|
||||||
|
|
||||||
|
if (EditorSettings.ShortcutsByOperationType.TryGetValue(operationType, out var value))
|
||||||
|
{
|
||||||
|
value.Remove(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ToggleShortcutKeyMappingActiveState(string guid)
|
||||||
|
{
|
||||||
|
if (EditorSettings.ShortcutSettingsDictionary.TryGetValue(guid, out ShortcutSettings? shortcutSettings))
|
||||||
|
{
|
||||||
|
shortcutSettings.IsActive = !shortcutSettings.IsActive;
|
||||||
|
WriteSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddShortcutMapping(EditorSettings settings, ShortcutKeyMapping mapping)
|
||||||
|
{
|
||||||
|
string guid = Guid.NewGuid().ToString();
|
||||||
|
var shortcutSettings = new ShortcutSettings
|
||||||
|
{
|
||||||
|
Id = guid,
|
||||||
|
Shortcut = mapping,
|
||||||
|
IsActive = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
settings.ShortcutSettingsDictionary[guid] = shortcutSettings;
|
||||||
|
|
||||||
|
if (!settings.ShortcutsByOperationType.TryGetValue(mapping.OperationType, out System.Collections.Generic.List<string>? value))
|
||||||
|
{
|
||||||
|
value = new System.Collections.Generic.List<string>();
|
||||||
|
settings.ShortcutsByOperationType[mapping.OperationType] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
value.Add(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MappingExists(ShortcutKeyMapping mapping)
|
||||||
|
{
|
||||||
|
return EditorSettings.ShortcutSettingsDictionary.Values.Any(s =>
|
||||||
|
s.Shortcut.OperationType == mapping.OperationType &&
|
||||||
|
s.Shortcut.OriginalKeys == mapping.OriginalKeys &&
|
||||||
|
s.Shortcut.TargetKeys == mapping.TargetKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsMappingActiveInService(
|
||||||
|
ShortcutSettings shortcutSettings,
|
||||||
|
List<KeyToTextMapping> keyToTextMappings,
|
||||||
|
List<KeyMapping> singleKeyMappings,
|
||||||
|
List<ShortcutKeyMapping> shortcutKeyMappings)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(shortcutSettings.Shortcut.OriginalKeys))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSingleKey = shortcutSettings.Shortcut.OriginalKeys.Split(';').Length == 1;
|
||||||
|
|
||||||
|
if (isSingleKey && int.TryParse(shortcutSettings.Shortcut.OriginalKeys, out int keyCode))
|
||||||
|
{
|
||||||
|
if (shortcutSettings.Shortcut.OperationType == ShortcutOperationType.RemapText)
|
||||||
|
{
|
||||||
|
return keyToTextMappings.Any(m =>
|
||||||
|
m.OriginalKey == keyCode &&
|
||||||
|
m.TargetText == shortcutSettings.Shortcut.TargetText);
|
||||||
|
}
|
||||||
|
else if (shortcutSettings.Shortcut.OperationType == ShortcutOperationType.RemapShortcut)
|
||||||
|
{
|
||||||
|
return singleKeyMappings.Any(m =>
|
||||||
|
m.OriginalKey == keyCode &&
|
||||||
|
m.TargetKey == shortcutSettings.Shortcut.TargetKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shortcutKeyMappings.Any(m => m.OriginalKeys == shortcutSettings.Shortcut.OriginalKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<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="NewRemappingBtn_Text.Text" xml:space="preserve">
|
||||||
|
<value>Add new remapping</value>
|
||||||
|
</data>
|
||||||
|
<data name="EmptyStateTitle.Text" xml:space="preserve">
|
||||||
|
<value>Nothing mapped yet</value>
|
||||||
|
</data>
|
||||||
|
<data name="EmptyStateDescription.Text" xml:space="preserve">
|
||||||
|
<value>Create a key or shortcut remapping to customize how your keyboard works.</value>
|
||||||
|
</data>
|
||||||
|
<data name="RemappingsHeader.Text" xml:space="preserve">
|
||||||
|
<value>Keys and shortcuts</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextMappingsHeader.Text" xml:space="preserve">
|
||||||
|
<value>Text</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProgramsHeader.Text" xml:space="preserve">
|
||||||
|
<value>Programs</value>
|
||||||
|
</data>
|
||||||
|
<data name="UrlsHeader.Text" xml:space="preserve">
|
||||||
|
<value>Urls</value>
|
||||||
|
</data>
|
||||||
|
<data name="MapsToText.Text" xml:space="preserve">
|
||||||
|
<value>maps to</value>
|
||||||
|
</data>
|
||||||
|
<data name="InsertsText.Text" xml:space="preserve">
|
||||||
|
<value>inserts</value>
|
||||||
|
</data>
|
||||||
|
<data name="OpensText.Text" xml:space="preserve">
|
||||||
|
<value>opens</value>
|
||||||
|
</data>
|
||||||
|
<data name="InText.Text" xml:space="preserve">
|
||||||
|
<value>in</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteMenuItem.Text" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="TooltipArguments.Text" xml:space="preserve">
|
||||||
|
<value>Arguments:</value>
|
||||||
|
</data>
|
||||||
|
<data name="TooltipStartIn.Text" xml:space="preserve">
|
||||||
|
<value>Start in:</value>
|
||||||
|
</data>
|
||||||
|
<data name="TooltipElevation.Text" xml:space="preserve">
|
||||||
|
<value>Elevation:</value>
|
||||||
|
</data>
|
||||||
|
<data name="TooltipIfRunning.Text" xml:space="preserve">
|
||||||
|
<value>If running:</value>
|
||||||
|
</data>
|
||||||
|
<data name="TooltipWindow.Text" xml:space="preserve">
|
||||||
|
<value>Window:</value>
|
||||||
|
</data>
|
||||||
|
<data name="RemappingDialog.Title" xml:space="preserve">
|
||||||
|
<value>Add new remapping</value>
|
||||||
|
</data>
|
||||||
|
<data name="RemappingDialog.PrimaryButtonText" xml:space="preserve">
|
||||||
|
<value>Save</value>
|
||||||
|
</data>
|
||||||
|
<data name="RemappingDialog.CloseButtonText" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteConfirmationDialog.Title" xml:space="preserve">
|
||||||
|
<value>Are you sure?</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteConfirmationDialogContent.Text" xml:space="preserve">
|
||||||
|
<value>You are about to delete this remapping.</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteConfirmationDialog.PrimaryButtonText" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteConfirmationDialog.CloseButtonText" xml:space="preserve">
|
||||||
|
<value>Cancel</value>
|
||||||
|
</data>
|
||||||
|
<data name="TriggerLabel.Text" xml:space="preserve">
|
||||||
|
<value>Trigger</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionLabel.Text" xml:space="preserve">
|
||||||
|
<value>Action</value>
|
||||||
|
</data>
|
||||||
|
<data name="TriggerType_KeyOrShortcut.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Key or shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="TriggerType_KeyOrShortcut_Text.Text" xml:space="preserve">
|
||||||
|
<value>Key or shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="TriggerType_Mouse.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Mouse button</value>
|
||||||
|
</data>
|
||||||
|
<data name="TriggerType_Mouse_Text.Text" xml:space="preserve">
|
||||||
|
<value>Mouse button</value>
|
||||||
|
</data>
|
||||||
|
<data name="AllowChordsCheckBox.Content" xml:space="preserve">
|
||||||
|
<value>Allow chords</value>
|
||||||
|
</data>
|
||||||
|
<data name="ExactMatchCheckBox.Content" xml:space="preserve">
|
||||||
|
<value>Exact match</value>
|
||||||
|
</data>
|
||||||
|
<data name="MouseButton_Left.Content" xml:space="preserve">
|
||||||
|
<value>Left</value>
|
||||||
|
</data>
|
||||||
|
<data name="MouseButton_Center.Content" xml:space="preserve">
|
||||||
|
<value>Center</value>
|
||||||
|
</data>
|
||||||
|
<data name="MouseButton_Right.Content" xml:space="preserve">
|
||||||
|
<value>Right</value>
|
||||||
|
</data>
|
||||||
|
<data name="MouseButton_Button1.Content" xml:space="preserve">
|
||||||
|
<value>Button 1</value>
|
||||||
|
</data>
|
||||||
|
<data name="MouseButton_Button2.Content" xml:space="preserve">
|
||||||
|
<value>Button 2</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppSpecificCheckBox.Content" xml:space="preserve">
|
||||||
|
<value>Only apply to a specific app</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppNameTextBox.Header" xml:space="preserve">
|
||||||
|
<value>App name</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppNameTextBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Enter app name (e.g., notepad.exe)</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_KeyOrShortcut.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Remap to key or shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_KeyOrShortcut_Text.Text" xml:space="preserve">
|
||||||
|
<value>Remap to key or shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_Text.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Insert text</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_Text_Text.Text" xml:space="preserve">
|
||||||
|
<value>Insert text</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_OpenUrl.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Open URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_OpenUrl_Text.Text" xml:space="preserve">
|
||||||
|
<value>Open URL</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_OpenApp.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Open app</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_OpenApp_Text.Text" xml:space="preserve">
|
||||||
|
<value>Open app</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_MouseClick.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Remap to mouse click</value>
|
||||||
|
</data>
|
||||||
|
<data name="ActionType_MouseClick_Text.Text" xml:space="preserve">
|
||||||
|
<value>Remap to mouse click</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextContentBox.Header" xml:space="preserve">
|
||||||
|
<value>Text to type</value>
|
||||||
|
</data>
|
||||||
|
<data name="TextContentBox.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>Enter the text to type when triggered</value>
|
||||||
|
</data>
|
||||||
|
<data name="UrlPathInput.Header" xml:space="preserve">
|
||||||
|
<value>URL to open</value>
|
||||||
|
</data>
|
||||||
|
<data name="UrlPathInput.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>https://example.com</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProgramPathInput.Header" xml:space="preserve">
|
||||||
|
<value>Program path</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProgramPathInput.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>C:\Program Files\...</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProgramPathSelectButton.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Select program path</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProgramArgsInput.Header" xml:space="preserve">
|
||||||
|
<value>Arguments (optional)</value>
|
||||||
|
</data>
|
||||||
|
<data name="ProgramArgsInput.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>--arg1 value1</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartInPathInput.Header" xml:space="preserve">
|
||||||
|
<value>Start in directory (optional)</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartInPathInput.PlaceholderText" xml:space="preserve">
|
||||||
|
<value>C:\Users\...</value>
|
||||||
|
</data>
|
||||||
|
<data name="StartInSelectButton.AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Select start directory</value>
|
||||||
|
</data>
|
||||||
|
<data name="ElevationComboBox.Header" xml:space="preserve">
|
||||||
|
<value>Run as</value>
|
||||||
|
</data>
|
||||||
|
<data name="Elevation_Normal.Content" xml:space="preserve">
|
||||||
|
<value>Normal</value>
|
||||||
|
</data>
|
||||||
|
<data name="Elevation_Elevated.Content" xml:space="preserve">
|
||||||
|
<value>Elevated</value>
|
||||||
|
</data>
|
||||||
|
<data name="Elevation_DifferentUser.Content" xml:space="preserve">
|
||||||
|
<value>Different user</value>
|
||||||
|
</data>
|
||||||
|
<data name="IfRunningComboBox.Header" xml:space="preserve">
|
||||||
|
<value>If already running</value>
|
||||||
|
</data>
|
||||||
|
<data name="IfRunning_ShowWindow.Content" xml:space="preserve">
|
||||||
|
<value>Show window</value>
|
||||||
|
</data>
|
||||||
|
<data name="IfRunning_StartAnother.Content" xml:space="preserve">
|
||||||
|
<value>Start another</value>
|
||||||
|
</data>
|
||||||
|
<data name="IfRunning_DoNothing.Content" xml:space="preserve">
|
||||||
|
<value>Do nothing</value>
|
||||||
|
</data>
|
||||||
|
<data name="IfRunning_Close.Content" xml:space="preserve">
|
||||||
|
<value>Close</value>
|
||||||
|
</data>
|
||||||
|
<data name="IfRunning_EndTask.Content" xml:space="preserve">
|
||||||
|
<value>End task</value>
|
||||||
|
</data>
|
||||||
|
<data name="VisibilityComboBox.Header" xml:space="preserve">
|
||||||
|
<value>Window visibility</value>
|
||||||
|
</data>
|
||||||
|
<data name="Visibility_Normal.Content" xml:space="preserve">
|
||||||
|
<value>Normal</value>
|
||||||
|
</data>
|
||||||
|
<data name="Visibility_Hidden.Content" xml:space="preserve">
|
||||||
|
<value>Hidden</value>
|
||||||
|
</data>
|
||||||
|
<data name="Visibility_Minimized.Content" xml:space="preserve">
|
||||||
|
<value>Minimized</value>
|
||||||
|
</data>
|
||||||
|
<data name="Visibility_Maximized.Content" xml:space="preserve">
|
||||||
|
<value>Maximized</value>
|
||||||
|
</data>
|
||||||
|
<data name="MouseClickPlaceholder.Text" xml:space="preserve">
|
||||||
|
<value>Mouse click action - coming soon</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,759 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Key="RightAlignedCompactToggleSwitchStyle"
|
||||||
|
BasedOn="{StaticResource DefaultToggleSwitchStyle}"
|
||||||
|
TargetType="ToggleSwitch">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="Height" Value="36" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ToggleSwitch">
|
||||||
|
<Grid
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Control.IsTemplateFocusTarget="True"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}">
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="HeaderContentPresenter"
|
||||||
|
Grid.Row="0"
|
||||||
|
Margin="{ThemeResource ToggleSwitchTopHeaderMargin}"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
x:DeferLoadStrategy="Lazy"
|
||||||
|
AutomationProperties.AccessibilityView="Raw"
|
||||||
|
Content="{TemplateBinding Header}"
|
||||||
|
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||||
|
Foreground="{ThemeResource ToggleSwitchHeaderForeground}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<Grid
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="12" MaxWidth="12" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid
|
||||||
|
x:Name="SwitchAreaGrid"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
Margin="0,5"
|
||||||
|
Background="{ThemeResource ToggleSwitchContainerBackground}"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="OffContentPresenter"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
AutomationProperties.AccessibilityView="Raw"
|
||||||
|
Content="{TemplateBinding OffContent}"
|
||||||
|
ContentTemplate="{TemplateBinding OffContentTemplate}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Opacity="0" />
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="OnContentPresenter"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
AutomationProperties.AccessibilityView="Raw"
|
||||||
|
Content="{TemplateBinding OnContent}"
|
||||||
|
ContentTemplate="{TemplateBinding OnContentTemplate}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Opacity="0" />
|
||||||
|
<Rectangle
|
||||||
|
x:Name="OuterBorder"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="2"
|
||||||
|
Width="40"
|
||||||
|
Height="20"
|
||||||
|
Fill="{ThemeResource ToggleSwitchFillOff}"
|
||||||
|
RadiusX="10"
|
||||||
|
RadiusY="10"
|
||||||
|
Stroke="{ThemeResource ToggleSwitchStrokeOff}"
|
||||||
|
StrokeThickness="{ThemeResource ToggleSwitchOuterBorderStrokeThickness}" />
|
||||||
|
<Rectangle
|
||||||
|
x:Name="SwitchKnobBounds"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="2"
|
||||||
|
Width="40"
|
||||||
|
Height="20"
|
||||||
|
Fill="{ThemeResource ToggleSwitchFillOn}"
|
||||||
|
Opacity="0"
|
||||||
|
RadiusX="10"
|
||||||
|
RadiusY="10"
|
||||||
|
Stroke="{ThemeResource ToggleSwitchStrokeOn}"
|
||||||
|
StrokeThickness="{ThemeResource ToggleSwitchOnStrokeThickness}" />
|
||||||
|
<Grid
|
||||||
|
x:Name="SwitchKnob"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="2"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<Border
|
||||||
|
x:Name="SwitchKnobOn"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
Margin="0,0,3,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Background="{ThemeResource ToggleSwitchKnobFillOn}"
|
||||||
|
BackgroundSizing="OuterBorderEdge"
|
||||||
|
BorderBrush="{ThemeResource ToggleSwitchKnobStrokeOn}"
|
||||||
|
CornerRadius="7"
|
||||||
|
Opacity="0"
|
||||||
|
RenderTransformOrigin="0.5, 0.5">
|
||||||
|
<Border.RenderTransform>
|
||||||
|
<CompositeTransform />
|
||||||
|
</Border.RenderTransform>
|
||||||
|
</Border>
|
||||||
|
<Rectangle
|
||||||
|
x:Name="SwitchKnobOff"
|
||||||
|
Width="12"
|
||||||
|
Height="12"
|
||||||
|
Margin="3,0,0,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Fill="{ThemeResource ToggleSwitchKnobFillOff}"
|
||||||
|
RadiusX="7"
|
||||||
|
RadiusY="7"
|
||||||
|
RenderTransformOrigin="0.5, 0.5">
|
||||||
|
<Rectangle.RenderTransform>
|
||||||
|
<CompositeTransform />
|
||||||
|
</Rectangle.RenderTransform>
|
||||||
|
</Rectangle>
|
||||||
|
<Grid.RenderTransform>
|
||||||
|
<TranslateTransform x:Name="KnobTranslateTransform" />
|
||||||
|
</Grid.RenderTransform>
|
||||||
|
</Grid>
|
||||||
|
<Thumb
|
||||||
|
x:Name="SwitchThumb"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
AutomationProperties.AccessibilityView="Raw">
|
||||||
|
<Thumb.Template>
|
||||||
|
<ControlTemplate TargetType="Thumb">
|
||||||
|
<Rectangle Fill="Transparent" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Thumb.Template>
|
||||||
|
</Thumb>
|
||||||
|
</Grid>
|
||||||
|
<VisualStateManager.VisualStateGroups>
|
||||||
|
<VisualStateGroup x:Name="CommonStates">
|
||||||
|
<VisualState x:Name="Normal">
|
||||||
|
<Storyboard>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Stroke">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOff}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOff}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOff}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOn}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOn}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOn}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="Background">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContainerBackground}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="PointerOver">
|
||||||
|
<Storyboard>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPointerOver}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPointerOver}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPointerOver}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPointerOver}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPointerOver}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPointerOver}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPointerOver}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="14" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="14" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="14" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="14" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Pressed">
|
||||||
|
<VisualState.Setters>
|
||||||
|
<Setter Target="SwitchKnobOn.HorizontalAlignment" Value="Right" />
|
||||||
|
<Setter Target="SwitchKnobOn.Margin" Value="0,0,3,0" />
|
||||||
|
<Setter Target="SwitchKnobOff.HorizontalAlignment" Value="Left" />
|
||||||
|
<Setter Target="SwitchKnobOff.Margin" Value="3,0,0,0" />
|
||||||
|
</VisualState.Setters>
|
||||||
|
<Storyboard>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffPressed}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffPressed}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnPressed}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnPressed}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffPressed}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnPressed}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundPressed}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="17" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="14" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="17" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlFasterAnimationDuration}"
|
||||||
|
Value="14" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="Disabled">
|
||||||
|
<Storyboard>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchHeaderForegroundDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchContentForegroundDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchStrokeOffDisabled}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchFillOffDisabled}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchFillOnDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Stroke">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchStrokeOnDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Fill">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOffDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Background">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleSwitchKnobFillOnDisabled}" />
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
<ColorAnimationUsingKeyFrames Storyboard.TargetName="SwitchAreaGrid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
|
||||||
|
<LinearColorKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="{ThemeResource ToggleSwitchContainerBackgroundDisabled}" />
|
||||||
|
</ColorAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOn"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Width">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames
|
||||||
|
EnableDependentAnimation="True"
|
||||||
|
Storyboard.TargetName="SwitchKnobOff"
|
||||||
|
Storyboard.TargetProperty="Height">
|
||||||
|
<SplineDoubleKeyFrame
|
||||||
|
KeySpline="{StaticResource ControlFastOutSlowInKeySpline}"
|
||||||
|
KeyTime="{StaticResource ControlNormalAnimationDuration}"
|
||||||
|
Value="12" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
<VisualStateGroup x:Name="ToggleStates">
|
||||||
|
<VisualStateGroup.Transitions>
|
||||||
|
<VisualTransition
|
||||||
|
x:Name="DraggingToOnTransition"
|
||||||
|
GeneratedDuration="0"
|
||||||
|
From="Dragging"
|
||||||
|
To="On">
|
||||||
|
<Storyboard>
|
||||||
|
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOnOffset}" TargetName="SwitchKnob" />
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualTransition>
|
||||||
|
<VisualTransition
|
||||||
|
x:Name="OnToDraggingTransition"
|
||||||
|
GeneratedDuration="0"
|
||||||
|
From="On"
|
||||||
|
To="Dragging">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="0" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="0" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="0" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualTransition>
|
||||||
|
<VisualTransition
|
||||||
|
x:Name="DraggingToOffTransition"
|
||||||
|
GeneratedDuration="0"
|
||||||
|
From="Dragging"
|
||||||
|
To="Off">
|
||||||
|
<Storyboard>
|
||||||
|
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobCurrentToOffOffset}" TargetName="SwitchKnob" />
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualTransition>
|
||||||
|
<VisualTransition
|
||||||
|
x:Name="OnToOffTransition"
|
||||||
|
GeneratedDuration="0"
|
||||||
|
From="On"
|
||||||
|
To="Off">
|
||||||
|
<Storyboard>
|
||||||
|
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOnToOffOffset}" TargetName="SwitchKnob" />
|
||||||
|
</Storyboard>
|
||||||
|
</VisualTransition>
|
||||||
|
<VisualTransition
|
||||||
|
x:Name="OffToOnTransition"
|
||||||
|
GeneratedDuration="0"
|
||||||
|
From="Off"
|
||||||
|
To="On">
|
||||||
|
<Storyboard>
|
||||||
|
<RepositionThemeAnimation FromHorizontalOffset="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.KnobOffToOnOffset}" TargetName="SwitchKnob" />
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualTransition>
|
||||||
|
</VisualStateGroup.Transitions>
|
||||||
|
<VisualState x:Name="Dragging" />
|
||||||
|
<VisualState x:Name="Off" />
|
||||||
|
<VisualState x:Name="On">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation
|
||||||
|
Storyboard.TargetName="KnobTranslateTransform"
|
||||||
|
Storyboard.TargetProperty="X"
|
||||||
|
To="20"
|
||||||
|
Duration="0" />
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobBounds" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOn" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="1" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="SwitchKnobOff" Storyboard.TargetProperty="Opacity">
|
||||||
|
<LinearDoubleKeyFrame KeyTime="{StaticResource ControlFasterAnimationDuration}" Value="0" />
|
||||||
|
</DoubleAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
<VisualStateGroup x:Name="ContentStates">
|
||||||
|
<VisualState x:Name="OffContent">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation
|
||||||
|
Storyboard.TargetName="OffContentPresenter"
|
||||||
|
Storyboard.TargetProperty="Opacity"
|
||||||
|
To="1"
|
||||||
|
Duration="0" />
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OffContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0">
|
||||||
|
<DiscreteObjectKeyFrame.Value>
|
||||||
|
<x:Boolean>True</x:Boolean>
|
||||||
|
</DiscreteObjectKeyFrame.Value>
|
||||||
|
</DiscreteObjectKeyFrame>
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
<VisualState x:Name="OnContent">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation
|
||||||
|
Storyboard.TargetName="OnContentPresenter"
|
||||||
|
Storyboard.TargetProperty="Opacity"
|
||||||
|
To="1"
|
||||||
|
Duration="0" />
|
||||||
|
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="OnContentPresenter" Storyboard.TargetProperty="IsHitTestVisible">
|
||||||
|
<DiscreteObjectKeyFrame KeyTime="0">
|
||||||
|
<DiscreteObjectKeyFrame.Value>
|
||||||
|
<x:Boolean>True</x:Boolean>
|
||||||
|
</DiscreteObjectKeyFrame.Value>
|
||||||
|
</DiscreteObjectKeyFrame>
|
||||||
|
</ObjectAnimationUsingKeyFrames>
|
||||||
|
</Storyboard>
|
||||||
|
</VisualState>
|
||||||
|
</VisualStateGroup>
|
||||||
|
</VisualStateManager.VisualStateGroups>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key="Default">
|
||||||
|
<SolidColorBrush
|
||||||
|
x:Key="CustomAccentBackgroundBrush"
|
||||||
|
Opacity="0.3"
|
||||||
|
Color="{ThemeResource SystemAccentColorLight2}" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary x:Key="Light">
|
||||||
|
<SolidColorBrush
|
||||||
|
x:Key="CustomAccentBackgroundBrush"
|
||||||
|
Opacity="0.2"
|
||||||
|
Color="{ThemeResource SystemAccentColorDark1}" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary x:Key="HighContrast">
|
||||||
|
<SolidColorBrush x:Key="CustomAccentBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -538,7 +538,10 @@ namespace KeyboardEventHandlers
|
|||||||
// Release original shortcut state (release in reverse order of shortcut to be accurate)
|
// Release original shortcut state (release in reverse order of shortcut to be accurate)
|
||||||
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
Helpers::SetModifierKeyEvents(it->first, it->second.modifierKeysInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||||
|
|
||||||
Helpers::SetTextKeyEvents(keyEventList, remapping);
|
// Send modifier release events first, then paste text via clipboard
|
||||||
|
ii.SendVirtualInput(keyEventList);
|
||||||
|
keyEventList.clear();
|
||||||
|
Helpers::SendTextViaClipboard(remapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
it->second.isShortcutInvoked = true;
|
it->second.isShortcutInvoked = true;
|
||||||
@@ -719,7 +722,8 @@ namespace KeyboardEventHandlers
|
|||||||
else if (remapToText)
|
else if (remapToText)
|
||||||
{
|
{
|
||||||
auto& remapping = std::get<std::wstring>(it->second.targetShortcut);
|
auto& remapping = std::get<std::wstring>(it->second.targetShortcut);
|
||||||
Helpers::SetTextKeyEvents(keyEventList, remapping);
|
Helpers::SendTextViaClipboard(remapping);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ii.SendVirtualInput(keyEventList);
|
ii.SendVirtualInput(keyEventList);
|
||||||
@@ -1793,9 +1797,7 @@ namespace KeyboardEventHandlers
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<INPUT> keyEventList;
|
Helpers::SendTextViaClipboard(*remapping);
|
||||||
Helpers::SetTextKeyEvents(keyEventList, *remapping);
|
|
||||||
ii.SendVirtualInput(keyEventList);
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,4 +60,85 @@ namespace RemappingLogicTests
|
|||||||
Assert::AreEqual<unsigned int>(0, inputs[1].ki.wScan);
|
Assert::AreEqual<unsigned int>(0, inputs[1].ki.wScan);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tests for the SetTextKeyEvents method
|
||||||
|
TEST_CLASS (SetTextKeyEventsTests)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Test that plain ASCII text produces KEYEVENTF_UNICODE events with correct scan codes
|
||||||
|
TEST_METHOD (SetTextKeyEvents_ShouldUseUnicodeFlag_WhenTextIsPlainAscii)
|
||||||
|
{
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
std::wstring text = L"abc";
|
||||||
|
|
||||||
|
Helpers::SetTextKeyEvents(inputs, text);
|
||||||
|
|
||||||
|
// 3 characters × 2 events (down+up) = 6 events
|
||||||
|
Assert::AreEqual<size_t>(6, inputs.size());
|
||||||
|
for (size_t i = 0; i < inputs.size(); i++)
|
||||||
|
{
|
||||||
|
Assert::AreEqual(true, bool(inputs[i].ki.dwFlags & KEYEVENTF_UNICODE));
|
||||||
|
}
|
||||||
|
Assert::AreEqual<unsigned short>(L'a', inputs[0].ki.wScan);
|
||||||
|
Assert::AreEqual<unsigned short>(L'b', inputs[2].ki.wScan);
|
||||||
|
Assert::AreEqual<unsigned short>(L'c', inputs[4].ki.wScan);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that each character generates a keydown and keyup event pair
|
||||||
|
TEST_METHOD (SetTextKeyEvents_ShouldGenerateDownUpPairs_WhenTextHasMultipleChars)
|
||||||
|
{
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
std::wstring text = L"xy";
|
||||||
|
|
||||||
|
Helpers::SetTextKeyEvents(inputs, text);
|
||||||
|
|
||||||
|
Assert::AreEqual<size_t>(4, inputs.size());
|
||||||
|
// First event: 'x' keydown (no KEYEVENTF_KEYUP flag)
|
||||||
|
Assert::AreEqual<unsigned short>(L'x', inputs[0].ki.wScan);
|
||||||
|
Assert::IsFalse(bool(inputs[0].ki.dwFlags & KEYEVENTF_KEYUP));
|
||||||
|
// Second event: 'x' keyup
|
||||||
|
Assert::AreEqual<unsigned short>(L'x', inputs[1].ki.wScan);
|
||||||
|
Assert::AreEqual(true, bool(inputs[1].ki.dwFlags & KEYEVENTF_KEYUP));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that newline characters are passed through as Unicode events (actual newline handling is done via clipboard)
|
||||||
|
TEST_METHOD (SetTextKeyEvents_ShouldPassNewlinesAsUnicode_WhenTextContainsNewlines)
|
||||||
|
{
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
std::wstring text = L"a\r\nb";
|
||||||
|
|
||||||
|
Helpers::SetTextKeyEvents(inputs, text);
|
||||||
|
|
||||||
|
// All 4 characters (a, \r, \n, b) × 2 events = 8 events
|
||||||
|
Assert::AreEqual<size_t>(8, inputs.size());
|
||||||
|
Assert::AreEqual<unsigned short>(L'a', inputs[0].ki.wScan);
|
||||||
|
Assert::AreEqual<unsigned short>(L'\r', inputs[2].ki.wScan);
|
||||||
|
Assert::AreEqual<unsigned short>(L'\n', inputs[4].ki.wScan);
|
||||||
|
Assert::AreEqual<unsigned short>(L'b', inputs[6].ki.wScan);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test empty string produces no events
|
||||||
|
TEST_METHOD (SetTextKeyEvents_ShouldProduceNoEvents_WhenTextIsEmpty)
|
||||||
|
{
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
std::wstring text = L"";
|
||||||
|
|
||||||
|
Helpers::SetTextKeyEvents(inputs, text);
|
||||||
|
|
||||||
|
Assert::AreEqual<size_t>(0, inputs.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that extraInfo flag is set correctly for KBM identification
|
||||||
|
TEST_METHOD (SetTextKeyEvents_ShouldSetExtraInfoFlag_WhenTextIsProvided)
|
||||||
|
{
|
||||||
|
std::vector<INPUT> inputs;
|
||||||
|
std::wstring text = L"a";
|
||||||
|
|
||||||
|
Helpers::SetTextKeyEvents(inputs, text);
|
||||||
|
|
||||||
|
Assert::AreEqual<size_t>(2, inputs.size());
|
||||||
|
Assert::AreEqual(KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, inputs[0].ki.dwExtraInfo);
|
||||||
|
Assert::AreEqual(KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, inputs[1].ki.dwExtraInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "Helpers.h"
|
#include "Helpers.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <common/interop/shared_constants.h>
|
#include <common/interop/shared_constants.h>
|
||||||
#include <common/utils/process_path.h>
|
#include <common/utils/process_path.h>
|
||||||
@@ -325,12 +327,161 @@ namespace Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to set clipboard text. Returns true on success.
|
||||||
|
static bool SetClipboardText(const std::wstring& text)
|
||||||
|
{
|
||||||
|
if (!OpenClipboard(nullptr))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmptyClipboard();
|
||||||
|
|
||||||
|
size_t byteSize = (text.size() + 1) * sizeof(wchar_t);
|
||||||
|
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, byteSize);
|
||||||
|
if (!hMem)
|
||||||
|
{
|
||||||
|
CloseClipboard();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t* pMem = static_cast<wchar_t*>(GlobalLock(hMem));
|
||||||
|
if (!pMem)
|
||||||
|
{
|
||||||
|
GlobalFree(hMem);
|
||||||
|
CloseClipboard();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcscpy_s(pMem, text.size() + 1, text.c_str());
|
||||||
|
GlobalUnlock(hMem);
|
||||||
|
|
||||||
|
if (!SetClipboardData(CF_UNICODETEXT, hMem))
|
||||||
|
{
|
||||||
|
GlobalFree(hMem);
|
||||||
|
CloseClipboard();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude this entry from clipboard history and cloud clipboard so the
|
||||||
|
// temporary paste text does not pollute the user's clipboard history.
|
||||||
|
static const UINT excludeFromHistory = RegisterClipboardFormat(L"ExcludeClipboardContentFromMonitorProcessing");
|
||||||
|
if (excludeFromHistory != 0)
|
||||||
|
{
|
||||||
|
HGLOBAL hExclude = GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
|
||||||
|
if (hExclude)
|
||||||
|
{
|
||||||
|
SetClipboardData(excludeFromHistory, hExclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseClipboard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate Ctrl+V paste keystroke, tagged with KBM flag so our own hook
|
||||||
|
// passes it through without re-intercepting.
|
||||||
|
static void SendPasteKeystroke()
|
||||||
|
{
|
||||||
|
INPUT pasteInputs[4]{};
|
||||||
|
|
||||||
|
pasteInputs[0].type = INPUT_KEYBOARD;
|
||||||
|
pasteInputs[0].ki.wVk = VK_CONTROL;
|
||||||
|
pasteInputs[0].ki.wScan = static_cast<WORD>(MapVirtualKey(VK_CONTROL, MAPVK_VK_TO_VSC));
|
||||||
|
pasteInputs[0].ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||||
|
|
||||||
|
pasteInputs[1].type = INPUT_KEYBOARD;
|
||||||
|
pasteInputs[1].ki.wVk = 'V';
|
||||||
|
pasteInputs[1].ki.wScan = static_cast<WORD>(MapVirtualKey('V', MAPVK_VK_TO_VSC));
|
||||||
|
pasteInputs[1].ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||||
|
|
||||||
|
pasteInputs[2].type = INPUT_KEYBOARD;
|
||||||
|
pasteInputs[2].ki.wVk = 'V';
|
||||||
|
pasteInputs[2].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||||
|
pasteInputs[2].ki.wScan = static_cast<WORD>(MapVirtualKey('V', MAPVK_VK_TO_VSC));
|
||||||
|
pasteInputs[2].ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||||
|
|
||||||
|
pasteInputs[3].type = INPUT_KEYBOARD;
|
||||||
|
pasteInputs[3].ki.wVk = VK_CONTROL;
|
||||||
|
pasteInputs[3].ki.dwFlags = KEYEVENTF_KEYUP;
|
||||||
|
pasteInputs[3].ki.wScan = static_cast<WORD>(MapVirtualKey(VK_CONTROL, MAPVK_VK_TO_VSC));
|
||||||
|
pasteInputs[3].ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
|
||||||
|
|
||||||
|
SendInput(ARRAYSIZE(pasteInputs), pasteInputs, sizeof(INPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serializes clipboard operations so rapid text remappings don't race.
|
||||||
|
static std::mutex clipboardMutex;
|
||||||
|
|
||||||
|
// Function to send text via clipboard paste (Ctrl+V).
|
||||||
|
// Saves the previous clipboard content and restores it asynchronously.
|
||||||
|
// The clipboard entry is excluded from clipboard history via
|
||||||
|
// ExcludeClipboardContentFromMonitorProcessing (set in SetClipboardText).
|
||||||
|
bool SendTextViaClipboard(const std::wstring& text)
|
||||||
|
{
|
||||||
|
// Acquire the mutex so that the entire snapshot-paste-restore cycle
|
||||||
|
// is atomic with respect to other text remapping calls.
|
||||||
|
std::unique_lock<std::mutex> lock(clipboardMutex);
|
||||||
|
|
||||||
|
// Snapshot current clipboard state
|
||||||
|
bool hadOriginalText = false;
|
||||||
|
std::wstring originalClipboardText;
|
||||||
|
if (OpenClipboard(nullptr))
|
||||||
|
{
|
||||||
|
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
|
||||||
|
{
|
||||||
|
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
|
||||||
|
if (hData)
|
||||||
|
{
|
||||||
|
wchar_t* pText = static_cast<wchar_t*>(GlobalLock(hData));
|
||||||
|
if (pText)
|
||||||
|
{
|
||||||
|
originalClipboardText = pText;
|
||||||
|
hadOriginalText = true;
|
||||||
|
GlobalUnlock(hData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place our text on the clipboard (with history exclusion)
|
||||||
|
if (!SetClipboardText(text))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendPasteKeystroke();
|
||||||
|
|
||||||
|
// Restore clipboard after a delay on a background thread.
|
||||||
|
// Ctrl+V is asynchronous (SendInput queues the input), so the target
|
||||||
|
// app needs time to process the keystroke and read the clipboard.
|
||||||
|
// The lock is moved into the thread so the next call blocks until
|
||||||
|
// restoration completes.
|
||||||
|
std::thread([lock = std::move(lock), originalClipboardText = std::move(originalClipboardText), hadOriginalText]() {
|
||||||
|
Sleep(500);
|
||||||
|
if (hadOriginalText)
|
||||||
|
{
|
||||||
|
SetClipboardText(originalClipboardText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (OpenClipboard(nullptr))
|
||||||
|
{
|
||||||
|
EmptyClipboard();
|
||||||
|
CloseClipboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to filter the key codes for artificial key codes
|
// Function to filter the key codes for artificial key codes
|
||||||
int32_t FilterArtificialKeys(const int32_t& key)
|
int32_t FilterArtificialKeys(const int32_t& key)
|
||||||
{
|
{
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
// If a key is remapped to VK_WIN_BOTH, we send VK_LWIN instead
|
|
||||||
case CommonSharedConstants::VK_WIN_BOTH:
|
case CommonSharedConstants::VK_WIN_BOTH:
|
||||||
return VK_LWIN;
|
return VK_LWIN;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ namespace Helpers
|
|||||||
// Function to set key events for remapping text.
|
// Function to set key events for remapping text.
|
||||||
void SetTextKeyEvents(std::vector<INPUT>& keyEventArray, const std::wstring& remapping);
|
void SetTextKeyEvents(std::vector<INPUT>& keyEventArray, const std::wstring& remapping);
|
||||||
|
|
||||||
|
// Function to send text via clipboard paste (Ctrl+V). Saves and restores previous clipboard content.
|
||||||
|
bool SendTextViaClipboard(const std::wstring& text);
|
||||||
|
|
||||||
// Function to return window handle for a full screen UWP app
|
// Function to return window handle for a full screen UWP app
|
||||||
HWND GetFullscreenUWPWindowHandle();
|
HWND GetFullscreenUWPWindowHandle();
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ public:
|
|||||||
{
|
{
|
||||||
RemapShortcut = 0,
|
RemapShortcut = 0,
|
||||||
RunProgram = 1,
|
RunProgram = 1,
|
||||||
OpenURI = 2
|
OpenURI = 2,
|
||||||
|
RemapText = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum StartWindowType
|
enum StartWindowType
|
||||||
@@ -47,7 +48,7 @@ public:
|
|||||||
Normal = 0,
|
Normal = 0,
|
||||||
Hidden = 1,
|
Hidden = 1,
|
||||||
Minimized = 2,
|
Minimized = 2,
|
||||||
Maximized = 2
|
Maximized = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ProgramAlreadyRunningAction
|
enum ProgramAlreadyRunningAction
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <common/utils/logger_helper.h>
|
#include <common/utils/logger_helper.h>
|
||||||
#include <common/interop/shared_constants.h>
|
#include <common/interop/shared_constants.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
||||||
{
|
{
|
||||||
@@ -37,6 +39,8 @@ namespace
|
|||||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ToggleShortcut";
|
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ToggleShortcut";
|
||||||
|
const wchar_t JSON_KEY_EDITOR_SHORTCUT[] = L"EditorShortcut";
|
||||||
|
const wchar_t JSON_KEY_USE_NEW_EDITOR[] = L"useNewEditor";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the PowerToy Module Interface and all the required methods.
|
// Implement the PowerToy Module Interface and all the required methods.
|
||||||
@@ -56,11 +60,22 @@ private:
|
|||||||
// Hotkey for toggling the module
|
// Hotkey for toggling the module
|
||||||
Hotkey m_hotkey = { .key = 0 };
|
Hotkey m_hotkey = { .key = 0 };
|
||||||
|
|
||||||
|
// Hotkey for opening the editor
|
||||||
|
Hotkey m_editorHotkey = { .key = 0 };
|
||||||
|
|
||||||
|
// Whether to use the new WinUI3 editor
|
||||||
|
bool m_useNewEditor = false;
|
||||||
|
|
||||||
ULONGLONG m_lastHotkeyToggleTime = 0;
|
ULONGLONG m_lastHotkeyToggleTime = 0;
|
||||||
|
|
||||||
HANDLE m_hProcess = nullptr;
|
HANDLE m_hProcess = nullptr;
|
||||||
|
HANDLE m_hEditorProcess = nullptr;
|
||||||
|
|
||||||
HANDLE m_hTerminateEngineEvent = nullptr;
|
HANDLE m_hTerminateEngineEvent = nullptr;
|
||||||
|
HANDLE m_open_new_editor_event_handle{ nullptr };
|
||||||
|
std::thread m_toggle_thread;
|
||||||
|
std::atomic<bool> m_toggle_thread_running{ false };
|
||||||
|
|
||||||
|
|
||||||
void refresh_process_state()
|
void refresh_process_state()
|
||||||
{
|
{
|
||||||
@@ -174,6 +189,49 @@ private:
|
|||||||
m_hotkey.alt = false;
|
m_hotkey.alt = false;
|
||||||
m_hotkey.key = 'K';
|
m_hotkey.key = 'K';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse editor shortcut
|
||||||
|
if (settingsObject.GetView().Size())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto jsonEditorHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES)
|
||||||
|
.GetNamedObject(JSON_KEY_EDITOR_SHORTCUT);
|
||||||
|
m_editorHotkey.win = jsonEditorHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||||
|
m_editorHotkey.alt = jsonEditorHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||||
|
m_editorHotkey.shift = jsonEditorHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||||
|
m_editorHotkey.ctrl = jsonEditorHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||||
|
m_editorHotkey.key = static_cast<unsigned char>(jsonEditorHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
Logger::error("Failed to initialize Keyboard Manager editor shortcut");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_editorHotkey.key)
|
||||||
|
{
|
||||||
|
// Set default: Win+Shift+Q
|
||||||
|
m_editorHotkey.win = true;
|
||||||
|
m_editorHotkey.shift = true;
|
||||||
|
m_editorHotkey.ctrl = false;
|
||||||
|
m_editorHotkey.alt = false;
|
||||||
|
m_editorHotkey.key = 'Q';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse useNewEditor setting
|
||||||
|
if (settingsObject.GetView().Size())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
|
||||||
|
m_useNewEditor = propertiesObject.GetNamedBoolean(JSON_KEY_USE_NEW_EDITOR, false);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
Logger::warn("Failed to parse useNewEditor setting, defaulting to false");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the settings file.
|
// Load the settings file.
|
||||||
@@ -214,17 +272,30 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_open_new_editor_event_handle = CreateDefaultEvent(CommonSharedConstants::OPEN_NEW_KEYBOARD_MANAGER_EVENT);
|
||||||
|
|
||||||
init_settings();
|
init_settings();
|
||||||
};
|
};
|
||||||
|
|
||||||
~KeyboardManager()
|
~KeyboardManager()
|
||||||
{
|
{
|
||||||
|
StopOpenEditorListener();
|
||||||
stop_engine();
|
stop_engine();
|
||||||
if (m_hTerminateEngineEvent)
|
if (m_hTerminateEngineEvent)
|
||||||
{
|
{
|
||||||
CloseHandle(m_hTerminateEngineEvent);
|
CloseHandle(m_hTerminateEngineEvent);
|
||||||
m_hTerminateEngineEvent = nullptr;
|
m_hTerminateEngineEvent = nullptr;
|
||||||
}
|
}
|
||||||
|
if (m_open_new_editor_event_handle)
|
||||||
|
{
|
||||||
|
CloseHandle(m_open_new_editor_event_handle);
|
||||||
|
m_open_new_editor_event_handle = nullptr;
|
||||||
|
}
|
||||||
|
if (m_hEditorProcess)
|
||||||
|
{
|
||||||
|
CloseHandle(m_hEditorProcess);
|
||||||
|
m_hEditorProcess = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the powertoy and free memory
|
// Destroy the powertoy and free memory
|
||||||
@@ -296,6 +367,7 @@ public:
|
|||||||
// Log telemetry
|
// Log telemetry
|
||||||
Trace::EnableKeyboardManager(true);
|
Trace::EnableKeyboardManager(true);
|
||||||
start_engine();
|
start_engine();
|
||||||
|
StartOpenEditorListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable the powertoy
|
// Disable the powertoy
|
||||||
@@ -304,6 +376,7 @@ public:
|
|||||||
m_enabled = false;
|
m_enabled = false;
|
||||||
// Log telemetry
|
// Log telemetry
|
||||||
Trace::EnableKeyboardManager(false);
|
Trace::EnableKeyboardManager(false);
|
||||||
|
StopOpenEditorListener();
|
||||||
stop_engine();
|
stop_engine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,25 +392,148 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the invocation hotkey for toggling
|
// Return the invocation hotkeys for toggling and opening the editor
|
||||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||||
{
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
// Hotkey 0: toggle engine
|
||||||
if (m_hotkey.key)
|
if (m_hotkey.key)
|
||||||
{
|
{
|
||||||
if (hotkeys && buffer_size >= 1)
|
if (hotkeys && buffer_size > count)
|
||||||
{
|
{
|
||||||
hotkeys[0] = m_hotkey;
|
hotkeys[count] = m_hotkey;
|
||||||
}
|
}
|
||||||
return 1;
|
count++;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
// Hotkey 1: open editor (only when using new editor)
|
||||||
|
if (m_useNewEditor && m_editorHotkey.key)
|
||||||
{
|
{
|
||||||
return 0;
|
if (hotkeys && buffer_size > count)
|
||||||
|
{
|
||||||
|
hotkeys[count] = m_editorHotkey;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartOpenEditorListener()
|
||||||
|
{
|
||||||
|
if (m_toggle_thread_running || !m_open_new_editor_event_handle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_toggle_thread_running = true;
|
||||||
|
m_toggle_thread = std::thread([this]() {
|
||||||
|
while (m_toggle_thread_running)
|
||||||
|
{
|
||||||
|
const DWORD wait_result = WaitForSingleObject(m_open_new_editor_event_handle, 500);
|
||||||
|
if (!m_toggle_thread_running)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wait_result == WAIT_OBJECT_0)
|
||||||
|
{
|
||||||
|
launch_editor();
|
||||||
|
ResetEvent(m_open_new_editor_event_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopOpenEditorListener()
|
||||||
|
{
|
||||||
|
if (!m_toggle_thread_running)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_toggle_thread_running = false;
|
||||||
|
if (m_open_new_editor_event_handle)
|
||||||
|
{
|
||||||
|
SetEvent(m_open_new_editor_event_handle);
|
||||||
|
}
|
||||||
|
if (m_toggle_thread.joinable())
|
||||||
|
{
|
||||||
|
m_toggle_thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool launch_editor()
|
||||||
|
{
|
||||||
|
// Check if editor is already running
|
||||||
|
if (m_hEditorProcess)
|
||||||
|
{
|
||||||
|
if (WaitForSingleObject(m_hEditorProcess, 0) == WAIT_TIMEOUT)
|
||||||
|
{
|
||||||
|
// Editor still running, bring it to front
|
||||||
|
DWORD editorPid = GetProcessId(m_hEditorProcess);
|
||||||
|
if (editorPid)
|
||||||
|
{
|
||||||
|
AllowSetForegroundWindow(editorPid);
|
||||||
|
|
||||||
|
// Find the editor's main window and set it to foreground
|
||||||
|
EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
|
||||||
|
DWORD windowPid = 0;
|
||||||
|
GetWindowThreadProcessId(hwnd, &windowPid);
|
||||||
|
if (windowPid == static_cast<DWORD>(lParam) && IsWindowVisible(hwnd))
|
||||||
|
{
|
||||||
|
SetForegroundWindow(hwnd);
|
||||||
|
if (IsIconic(hwnd))
|
||||||
|
{
|
||||||
|
ShowWindow(hwnd, SW_RESTORE);
|
||||||
|
}
|
||||||
|
return FALSE; // Stop enumerating
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}, static_cast<LPARAM>(editorPid));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CloseHandle(m_hEditorProcess);
|
||||||
|
m_hEditorProcess = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||||
|
std::wstring executable_args = std::to_wstring(powertoys_pid);
|
||||||
|
|
||||||
|
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||||
|
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||||
|
sei.lpFile = L"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.exe";
|
||||||
|
sei.nShow = SW_SHOWNORMAL;
|
||||||
|
sei.lpParameters = executable_args.data();
|
||||||
|
if (ShellExecuteExW(&sei) == false)
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to start new keyboard manager editor");
|
||||||
|
auto message = get_last_error_message(GetLastError());
|
||||||
|
if (message.has_value())
|
||||||
|
{
|
||||||
|
Logger::error(message.value());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hEditorProcess = sei.hProcess;
|
||||||
|
|
||||||
|
// Log telemetry for editor launch
|
||||||
|
if (m_hEditorProcess)
|
||||||
|
{
|
||||||
|
Trace::LaunchEditor(true); // true = launched via hotkey/event
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_hEditorProcess != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Process the hotkey event
|
// Process the hotkey event
|
||||||
virtual bool on_hotkey(size_t /*hotkeyId*/) override
|
virtual bool on_hotkey(size_t hotkeyId) override
|
||||||
{
|
{
|
||||||
if (!m_enabled)
|
if (!m_enabled)
|
||||||
{
|
{
|
||||||
@@ -352,6 +548,9 @@ public:
|
|||||||
}
|
}
|
||||||
m_lastHotkeyToggleTime = now;
|
m_lastHotkeyToggleTime = now;
|
||||||
|
|
||||||
|
if (hotkeyId == 0)
|
||||||
|
{
|
||||||
|
// Toggle engine on/off
|
||||||
refresh_process_state();
|
refresh_process_state();
|
||||||
if (m_active)
|
if (m_active)
|
||||||
{
|
{
|
||||||
@@ -361,6 +560,12 @@ public:
|
|||||||
{
|
{
|
||||||
start_engine();
|
start_engine();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (hotkeyId == 1)
|
||||||
|
{
|
||||||
|
// Open the new editor (only in new editor mode)
|
||||||
|
launch_editor();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,3 +20,14 @@ void Trace::EnableKeyboardManager(const bool enabled) noexcept
|
|||||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||||
TraceLoggingBoolean(enabled, "Enabled"));
|
TraceLoggingBoolean(enabled, "Enabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log when the editor is launched
|
||||||
|
void Trace::LaunchEditor(const bool viaHotkey) noexcept
|
||||||
|
{
|
||||||
|
TraceLoggingWriteWrapper(
|
||||||
|
g_hProvider,
|
||||||
|
"KeyboardManager_LaunchEditor",
|
||||||
|
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||||
|
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||||
|
TraceLoggingBoolean(viaHotkey, "ViaHotkey"));
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ class Trace : public telemetry::TraceBase
|
|||||||
public:
|
public:
|
||||||
// Log if the user has KBM enabled or disabled - Can also be used to see how often users have to restart the keyboard hook
|
// Log if the user has KBM enabled or disabled - Can also be used to see how often users have to restart the keyboard hook
|
||||||
static void EnableKeyboardManager(const bool enabled) noexcept;
|
static void EnableKeyboardManager(const bool enabled) noexcept;
|
||||||
|
|
||||||
|
// Log when the editor is launched
|
||||||
|
static void LaunchEditor(const bool viaHotkey) noexcept;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using System.Threading;
|
|||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.PowerToys.Settings.UI.Controls;
|
using Microsoft.PowerToys.Settings.UI.Controls;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library;
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
using PowerToys.Interop;
|
using PowerToys.Interop;
|
||||||
|
|
||||||
namespace Microsoft.PowerToys.QuickAccess.Services
|
namespace Microsoft.PowerToys.QuickAccess.Services
|
||||||
@@ -27,6 +29,12 @@ namespace Microsoft.PowerToys.QuickAccess.Services
|
|||||||
if (moduleRun)
|
if (moduleRun)
|
||||||
{
|
{
|
||||||
_coordinator?.OnModuleLaunched(moduleType);
|
_coordinator?.OnModuleLaunched(moduleType);
|
||||||
|
|
||||||
|
// Send telemetry event for module launch from Quick Access
|
||||||
|
if (moduleType == ModuleType.KeyboardManager)
|
||||||
|
{
|
||||||
|
PowerToysTelemetry.Log.WriteEvent(new ModuleLaunchedFromSettingsEvent("KeyboardManagerWinUI"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_coordinator?.HideFlyout();
|
_coordinator?.HideFlyout();
|
||||||
|
|||||||
@@ -126,6 +126,13 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
eventHandle.Set();
|
eventHandle.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
case ModuleType.KeyboardManager:
|
||||||
|
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.OpenNewKeyboardManagerEvent()))
|
||||||
|
{
|
||||||
|
eventHandle.Set();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
public partial class QuickAccessViewModel : Observable
|
public partial class QuickAccessViewModel : Observable
|
||||||
{
|
{
|
||||||
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
|
private readonly ISettingsRepository<GeneralSettings> _settingsRepository;
|
||||||
|
|
||||||
|
// Pulling in KBMSettingsRepository separately as we need to listen to changes in the
|
||||||
|
// UseNewEditor property to determine the visibility of the KeyboardManager quick access item.
|
||||||
|
private readonly SettingsRepository<KeyboardManagerSettings> _kbmSettingsRepository;
|
||||||
private readonly IQuickAccessLauncher _launcher;
|
private readonly IQuickAccessLauncher _launcher;
|
||||||
private readonly Func<ModuleType, bool> _isModuleGpoDisabled;
|
private readonly Func<ModuleType, bool> _isModuleGpoDisabled;
|
||||||
private readonly Func<ModuleType, bool> _isModuleGpoEnabled;
|
private readonly Func<ModuleType, bool> _isModuleGpoEnabled;
|
||||||
@@ -44,6 +48,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
|
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
|
||||||
_settingsRepository.SettingsChanged += OnSettingsChanged;
|
_settingsRepository.SettingsChanged += OnSettingsChanged;
|
||||||
|
|
||||||
|
_kbmSettingsRepository = SettingsRepository<KeyboardManagerSettings>.GetInstance(SettingsUtils.Default);
|
||||||
|
_kbmSettingsRepository.SettingsChanged += OnKbmSettingsChanged;
|
||||||
|
|
||||||
InitializeItems();
|
InitializeItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +74,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
|
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
|
||||||
AddFlyoutMenuItem(ModuleType.FancyZones);
|
AddFlyoutMenuItem(ModuleType.FancyZones);
|
||||||
AddFlyoutMenuItem(ModuleType.Hosts);
|
AddFlyoutMenuItem(ModuleType.Hosts);
|
||||||
|
AddFlyoutMenuItem(ModuleType.KeyboardManager);
|
||||||
AddFlyoutMenuItem(ModuleType.LightSwitch);
|
AddFlyoutMenuItem(ModuleType.LightSwitch);
|
||||||
|
|
||||||
// AddFlyoutMenuItem(ModuleType.PowerDisplay); // TEMPORARILY_DISABLED: PowerDisplay
|
// AddFlyoutMenuItem(ModuleType.PowerDisplay); // TEMPORARILY_DISABLED: PowerDisplay
|
||||||
@@ -89,7 +97,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
{
|
{
|
||||||
Title = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
|
Title = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
|
||||||
Tag = moduleType,
|
Tag = moduleType,
|
||||||
Visible = _isModuleGpoEnabled(moduleType) || Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType),
|
Visible = GetItemVisibility(moduleType),
|
||||||
Description = GetModuleToolTip(moduleType),
|
Description = GetModuleToolTip(moduleType),
|
||||||
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||||
Command = new RelayCommand(() => _launcher.Launch(moduleType)),
|
Command = new RelayCommand(() => _launcher.Launch(moduleType)),
|
||||||
@@ -115,11 +123,38 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
{
|
{
|
||||||
if (item.Tag is ModuleType moduleType)
|
if (item.Tag is ModuleType moduleType)
|
||||||
{
|
{
|
||||||
item.Visible = _isModuleGpoEnabled(moduleType) || Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
|
bool visible = GetItemVisibility(moduleType);
|
||||||
|
|
||||||
|
item.Visible = visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnKbmSettingsChanged(KeyboardManagerSettings newSettings)
|
||||||
|
{
|
||||||
|
if (_dispatcherQueue != null)
|
||||||
|
{
|
||||||
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
RefreshItemsVisibility();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetItemVisibility(ModuleType moduleType)
|
||||||
|
{
|
||||||
|
// Generally, if gpo is enabled or if module enabled, then quick access item is visible.
|
||||||
|
bool visible = _isModuleGpoEnabled(moduleType) || Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
|
||||||
|
|
||||||
|
// For KeyboardManager Quick Access item is only shown when using the new editor
|
||||||
|
if (moduleType == ModuleType.KeyboardManager)
|
||||||
|
{
|
||||||
|
visible = visible && _kbmSettingsRepository.SettingsConfig.Properties.UseNewEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetModuleToolTip(ModuleType moduleType)
|
private string GetModuleToolTip(ModuleType moduleType)
|
||||||
{
|
{
|
||||||
return moduleType switch
|
return moduleType switch
|
||||||
@@ -127,6 +162,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
ModuleType.ColorPicker => SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
ModuleType.ColorPicker => SettingsRepository<ColorPickerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||||
ModuleType.FancyZones => SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(),
|
ModuleType.FancyZones => SettingsRepository<FancyZonesSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(),
|
||||||
ModuleType.PowerDisplay => SettingsRepository<PowerDisplaySettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
ModuleType.PowerDisplay => SettingsRepository<PowerDisplaySettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||||
|
ModuleType.KeyboardManager => SettingsRepository<KeyboardManagerSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.DefaultEditorShortcut.ToString(),
|
||||||
ModuleType.LightSwitch => SettingsRepository<LightSwitchSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ToggleThemeHotkey.Value.ToString(),
|
ModuleType.LightSwitch => SettingsRepository<LightSwitchSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ToggleThemeHotkey.Value.ToString(),
|
||||||
ModuleType.PowerLauncher => SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.ToString(),
|
ModuleType.PowerLauncher => SettingsRepository<PowerLauncherSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenPowerLauncher.ToString(),
|
||||||
ModuleType.PowerOCR => SettingsRepository<PowerOcrSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
ModuleType.PowerOCR => SettingsRepository<PowerOcrSettings>.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(),
|
||||||
|
|||||||
@@ -23,15 +23,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
|
|
||||||
public HotkeySettings DefaultToggleShortcut => new HotkeySettings(true, false, false, true, 0x4B);
|
public HotkeySettings DefaultToggleShortcut => new HotkeySettings(true, false, false, true, 0x4B);
|
||||||
|
|
||||||
|
public HotkeySettings DefaultEditorShortcut => new HotkeySettings(true, false, false, true, 0x51);
|
||||||
|
|
||||||
public KeyboardManagerProperties()
|
public KeyboardManagerProperties()
|
||||||
{
|
{
|
||||||
ToggleShortcut = DefaultToggleShortcut;
|
ToggleShortcut = DefaultToggleShortcut;
|
||||||
|
EditorShortcut = DefaultEditorShortcut;
|
||||||
KeyboardConfigurations = new GenericProperty<List<string>>(new List<string> { "default", });
|
KeyboardConfigurations = new GenericProperty<List<string>>(new List<string> { "default", });
|
||||||
ActiveConfiguration = new GenericProperty<string>("default");
|
ActiveConfiguration = new GenericProperty<string>("default");
|
||||||
}
|
}
|
||||||
|
|
||||||
public HotkeySettings ToggleShortcut { get; set; }
|
public HotkeySettings ToggleShortcut { get; set; }
|
||||||
|
|
||||||
|
public HotkeySettings EditorShortcut { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("useNewEditor")]
|
||||||
|
public bool UseNewEditor { get; set; }
|
||||||
|
|
||||||
public string ToJsonString()
|
public string ToJsonString()
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(this);
|
return JsonSerializer.Serialize(this);
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
() => Properties.ToggleShortcut,
|
() => Properties.ToggleShortcut,
|
||||||
value => Properties.ToggleShortcut = value ?? Properties.DefaultToggleShortcut,
|
value => Properties.ToggleShortcut = value ?? Properties.DefaultToggleShortcut,
|
||||||
"Toggle_Shortcut"),
|
"Toggle_Shortcut"),
|
||||||
|
new HotkeyAccessor(
|
||||||
|
() => Properties.EditorShortcut,
|
||||||
|
value => Properties.EditorShortcut = value ?? Properties.DefaultEditorShortcut,
|
||||||
|
"Editor_Shortcut"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return hotkeyAccessors.ToArray();
|
return hotkeyAccessors.ToArray();
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
using Microsoft.PowerToys.Telemetry.Events;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events
|
||||||
|
{
|
||||||
|
[EventData]
|
||||||
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||||
|
public class ModuleLaunchedFromSettingsEvent : EventBase, IEvent
|
||||||
|
{
|
||||||
|
public string ModuleName { get; set; }
|
||||||
|
|
||||||
|
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||||
|
|
||||||
|
public ModuleLaunchedFromSettingsEvent(string moduleName)
|
||||||
|
{
|
||||||
|
EventName = "PowerToys_ModuleLaunchedFromSettings";
|
||||||
|
ModuleName = moduleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,7 +97,7 @@ namespace ViewModelTests
|
|||||||
var expectedEntry = new AppSpecificKeysDataModel();
|
var expectedEntry = new AppSpecificKeysDataModel();
|
||||||
expectedEntry.OriginalKeys = entry.OriginalKeys;
|
expectedEntry.OriginalKeys = entry.OriginalKeys;
|
||||||
expectedEntry.NewRemapKeys = entry.NewRemapKeys;
|
expectedEntry.NewRemapKeys = entry.NewRemapKeys;
|
||||||
expectedEntry.TargetApp = "All Apps";
|
expectedEntry.TargetApp = "All apps";
|
||||||
expectedResult.Add(expectedEntry);
|
expectedResult.Add(expectedEntry);
|
||||||
|
|
||||||
Assert.AreEqual(expectedResult.Count, result.Count);
|
Assert.AreEqual(expectedResult.Count, result.Count);
|
||||||
@@ -123,7 +123,7 @@ namespace ViewModelTests
|
|||||||
var expectedEntry = new AppSpecificKeysDataModel();
|
var expectedEntry = new AppSpecificKeysDataModel();
|
||||||
expectedEntry.OriginalKeys = entry.OriginalKeys;
|
expectedEntry.OriginalKeys = entry.OriginalKeys;
|
||||||
expectedEntry.NewRemapKeys = entry.NewRemapKeys;
|
expectedEntry.NewRemapKeys = entry.NewRemapKeys;
|
||||||
expectedEntry.TargetApp = "All Apps";
|
expectedEntry.TargetApp = "All apps";
|
||||||
expectedResult.Add(expectedEntry);
|
expectedResult.Add(expectedEntry);
|
||||||
var x = expectedResult[0].Equals(result[0]);
|
var x = expectedResult[0].Equals(result[0]);
|
||||||
Assert.AreEqual(expectedResult.Count, result.Count);
|
Assert.AreEqual(expectedResult.Count, result.Count);
|
||||||
@@ -181,7 +181,7 @@ namespace ViewModelTests
|
|||||||
var expectedFirstEntry = new AppSpecificKeysDataModel();
|
var expectedFirstEntry = new AppSpecificKeysDataModel();
|
||||||
expectedFirstEntry.OriginalKeys = firstListEntry.OriginalKeys;
|
expectedFirstEntry.OriginalKeys = firstListEntry.OriginalKeys;
|
||||||
expectedFirstEntry.NewRemapKeys = firstListEntry.NewRemapKeys;
|
expectedFirstEntry.NewRemapKeys = firstListEntry.NewRemapKeys;
|
||||||
expectedFirstEntry.TargetApp = "All Apps";
|
expectedFirstEntry.TargetApp = "All apps";
|
||||||
expectedResult.Add(expectedFirstEntry);
|
expectedResult.Add(expectedFirstEntry);
|
||||||
var expectedSecondEntry = new AppSpecificKeysDataModel();
|
var expectedSecondEntry = new AppSpecificKeysDataModel();
|
||||||
expectedSecondEntry.OriginalKeys = secondListEntry.OriginalKeys;
|
expectedSecondEntry.OriginalKeys = secondListEntry.OriginalKeys;
|
||||||
|
|||||||
@@ -15,6 +15,27 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<local:NavigablePage.Resources>
|
<local:NavigablePage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key="Default">
|
||||||
|
<LinearGradientBrush x:Key="CardGradient2Brush" StartPoint="0,0" EndPoint="0.5, 1">
|
||||||
|
<GradientStop Offset="0" Color="#38C8AEC4" />
|
||||||
|
<GradientStop Offset="1" Color="#383286EE" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary x:Key="Light">
|
||||||
|
<LinearGradientBrush x:Key="CardGradient2Brush" StartPoint="0,0" EndPoint="1, 1">
|
||||||
|
<GradientStop Offset="0.0" Color="#FFF6F9FF" />
|
||||||
|
<GradientStop Offset="0.4" Color="#FFEFF5FF" />
|
||||||
|
<GradientStop Offset="0.7" Color="#FFF7FAFD" />
|
||||||
|
<GradientStop Offset="1.0" Color="#FFF5F8FA" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary x:Key="HighContrast">
|
||||||
|
<SolidColorBrush x:Key="CardGradient2Brush" Color="Transparent" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
|
||||||
<tkconverters:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
|
<tkconverters:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
|
||||||
<Style x:Name="KeysListViewContainerStyle" TargetType="ListViewItem">
|
<Style x:Name="KeysListViewContainerStyle" TargetType="ListViewItem">
|
||||||
<Setter Property="IsTabStop" Value="False" />
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
@@ -34,50 +55,49 @@
|
|||||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||||
Style="{StaticResource AccentKeyVisualStyle}" />
|
Style="{StaticResource AccentKeyVisualStyle}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
</ResourceDictionary>
|
||||||
<!--<DataTemplate x:Name="KeysListViewTemplate" x:DataType="Lib:KeysDataModel">
|
|
||||||
<StackPanel
|
|
||||||
Name="KeyboardManager_RemappedKeysListItem"
|
|
||||||
x:Uid="KeyboardManager_RemappedKeysListItem"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
Height="56">
|
|
||||||
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>-->
|
|
||||||
<!--<DataTemplate x:Name="ShortcutKeysListViewTemplate" x:DataType="Lib:AppSpecificKeysDataModel">
|
|
||||||
<StackPanel
|
|
||||||
Name="KeyboardManager_RemappedShortcutsListItem"
|
|
||||||
x:Uid="KeyboardManager_RemappedShortcutsListItem"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
Height="56">
|
|
||||||
|
|
||||||
</DataTemplate>-->
|
|
||||||
</local:NavigablePage.Resources>
|
</local:NavigablePage.Resources>
|
||||||
|
|
||||||
<controls:SettingsPageControl x:Uid="KeyboardManager" ModuleImageSource="ms-appx:///Assets/Settings/Modules/KBM.png">
|
<controls:SettingsPageControl x:Uid="KeyboardManager" ModuleImageSource="ms-appx:///Assets/Settings/Modules/KBM.png">
|
||||||
<controls:SettingsPageControl.ModuleContent>
|
<controls:SettingsPageControl.ModuleContent>
|
||||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsExpander
|
||||||
Name="KeyboardManagerEnableToggle"
|
Name="KeyboardManagerEnableToggle"
|
||||||
x:Uid="KeyboardManager_EnableToggle"
|
x:Uid="KeyboardManager_EnableToggle"
|
||||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}">
|
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
|
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsCard.Description>
|
<tkcontrols:SettingsExpander.Description>
|
||||||
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
|
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
|
||||||
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
|
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
|
||||||
</HyperlinkButton>
|
</HyperlinkButton>
|
||||||
</tkcontrols:SettingsCard.Description>
|
</tkcontrols:SettingsExpander.Description>
|
||||||
</tkcontrols:SettingsCard>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
</controls:GPOInfoControl>
|
|
||||||
|
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsCard
|
||||||
Name="ToggleShortcut"
|
Name="ToggleShortcut"
|
||||||
x:Uid="KeyboardManager_Toggle_Shortcut"
|
x:Uid="KeyboardManager_Toggle_Shortcut"
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
||||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ToggleShortcut, Mode=TwoWay}" />
|
<controls:ShortcutControl
|
||||||
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AllowDisable="True"
|
||||||
|
HotkeySettings="{x:Bind Path=ViewModel.ToggleShortcut, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
|
</tkcontrols:SettingsExpander.Items>
|
||||||
|
</tkcontrols:SettingsExpander>
|
||||||
|
</controls:GPOInfoControl>
|
||||||
|
|
||||||
|
<tkcontrols:SwitchPresenter TargetType="x:Boolean" Value="{x:Bind ViewModel.UseNewEditor, Mode=OneWay}">
|
||||||
|
<tkcontrols:Case Value="False">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<tkcontrols:SettingsCard
|
||||||
|
x:Uid="KeyboardManager_TryNewExperience"
|
||||||
|
MinHeight="64"
|
||||||
|
Margin="0,0,0,24"
|
||||||
|
Background="{ThemeResource CardGradient2Brush}"
|
||||||
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
|
IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
||||||
|
<ToggleSwitch IsOn="{x:Bind ViewModel.UseNewEditor, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
<controls:SettingsGroup x:Uid="KeyboardManager_Keys" IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
<controls:SettingsGroup x:Uid="KeyboardManager_Keys" IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsCard
|
||||||
Name="KeyboardManagerRemapKeyboardButton"
|
Name="KeyboardManagerRemapKeyboardButton"
|
||||||
@@ -229,6 +249,43 @@
|
|||||||
</ListView>
|
</ListView>
|
||||||
</controls:SettingsGroup>
|
</controls:SettingsGroup>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
|
||||||
|
<tkcontrols:Case Value="True">
|
||||||
|
<controls:SettingsGroup x:Uid="KeyboardManager_Editor" IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="16">
|
||||||
|
<tkcontrols:SettingsExpander
|
||||||
|
x:Uid="KeyboardManager_OpenNewEditor"
|
||||||
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
|
IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}"
|
||||||
|
IsExpanded="True">
|
||||||
|
<Button
|
||||||
|
x:Uid="KeyboardManager_LaunchEditor_Button"
|
||||||
|
Command="{Binding Path=OpenNewEditorCommand}"
|
||||||
|
Style="{StaticResource AccentButtonStyle}" />
|
||||||
|
<tkcontrols:SettingsExpander.Items>
|
||||||
|
<!-- HACK: ShortcutControl does not work correctly if it's the first or last item in the expander, so we add invisible cards. -->
|
||||||
|
<tkcontrols:SettingsCard Visibility="Collapsed" />
|
||||||
|
<tkcontrols:SettingsCard Name="KBMEditorOpenShortcut" x:Uid="KeyboardManager_Editor_Open_Shortcut">
|
||||||
|
<controls:ShortcutControl
|
||||||
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AllowDisable="True"
|
||||||
|
HotkeySettings="{x:Bind Path=ViewModel.EditorShortcut, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
|
<tkcontrols:SettingsCard Visibility="Collapsed" />
|
||||||
|
</tkcontrols:SettingsExpander.Items>
|
||||||
|
</tkcontrols:SettingsExpander>
|
||||||
|
<HyperlinkButton
|
||||||
|
x:Uid="KeyboardManager_GoBackClassic"
|
||||||
|
Padding="0"
|
||||||
|
Click="GoBackClassic_Click"
|
||||||
|
FontSize="12"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
</StackPanel>
|
||||||
|
</controls:SettingsGroup>
|
||||||
|
</tkcontrols:Case>
|
||||||
|
</tkcontrols:SwitchPresenter>
|
||||||
|
</StackPanel>
|
||||||
</controls:SettingsPageControl.ModuleContent>
|
</controls:SettingsPageControl.ModuleContent>
|
||||||
<controls:SettingsPageControl.PrimaryLinks>
|
<controls:SettingsPageControl.PrimaryLinks>
|
||||||
<controls:PageLink x:Uid="LearnMore_KBM" Link="https://aka.ms/PowerToysOverview_KeyboardManager" />
|
<controls:PageLink x:Uid="LearnMore_KBM" Link="https://aka.ms/PowerToysOverview_KeyboardManager" />
|
||||||
|
|||||||
@@ -91,5 +91,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
|||||||
{
|
{
|
||||||
ViewModel.RefreshEnabledState();
|
ViewModel.RefreshEnabledState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GoBackClassic_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.UseNewEditor = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,6 +557,10 @@ opera.exe</value>
|
|||||||
<data name="KeyboardManager_RemapKeyboardButton.Header" xml:space="preserve">
|
<data name="KeyboardManager_RemapKeyboardButton.Header" xml:space="preserve">
|
||||||
<value>Remap a key</value>
|
<value>Remap a key</value>
|
||||||
<comment>Keyboard Manager remap keyboard button content</comment>
|
<comment>Keyboard Manager remap keyboard button content</comment>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_Editor.Header" xml:space="preserve">
|
||||||
|
<value>Editor</value>
|
||||||
|
<comment>Keyboard Manager new editor header</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="KeyboardManager_Keys.Header" xml:space="preserve">
|
<data name="KeyboardManager_Keys.Header" xml:space="preserve">
|
||||||
<value>Keys</value>
|
<value>Keys</value>
|
||||||
@@ -571,9 +575,29 @@ opera.exe</value>
|
|||||||
<comment>Keyboard Manager remap keyboard header</comment>
|
<comment>Keyboard Manager remap keyboard header</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="KeyboardManager_All_Apps_Description" xml:space="preserve">
|
<data name="KeyboardManager_All_Apps_Description" xml:space="preserve">
|
||||||
<value>All Apps</value>
|
<value>All apps</value>
|
||||||
<comment>Should be the same as EditShortcuts_AllApps from keyboard manager editor</comment>
|
<comment>Should be the same as EditShortcuts_AllApps from keyboard manager editor</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="KeyboardManager_TryNewExperience.Header" xml:space="preserve">
|
||||||
|
<value>Try the new editor</value>
|
||||||
|
<comment>Keyboard Manager toggle to switch to the new unified editor</comment>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_TryNewExperience.Description" xml:space="preserve">
|
||||||
|
<value>Switch to the new editor. You can switch back at any time.</value>
|
||||||
|
<comment>Description for the new experience toggle</comment>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_OpenNewEditor.Header" xml:space="preserve">
|
||||||
|
<value>Editor</value>
|
||||||
|
<comment>Keyboard Manager button to open the new unified editor</comment>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_OpenNewEditor.Description" xml:space="preserve">
|
||||||
|
<value>Set and manage your remappings</value>
|
||||||
|
<comment>Description for the new editor button</comment>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_GoBackClassic.Content" xml:space="preserve">
|
||||||
|
<value>Switch back to the classic editor</value>
|
||||||
|
<comment>Keyboard Manager link to switch back to the classic editor UI</comment>
|
||||||
|
</data>
|
||||||
<data name="Shortcut.Header" xml:space="preserve">
|
<data name="Shortcut.Header" xml:space="preserve">
|
||||||
<value>Shortcut</value>
|
<value>Shortcut</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1883,10 +1907,22 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
|
|||||||
<value>Customize the shortcut to activate this module</value>
|
<value>Customize the shortcut to activate this module</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="KeyboardManager_Toggle_Shortcut.Header" xml:space="preserve">
|
<data name="KeyboardManager_Toggle_Shortcut.Header" xml:space="preserve">
|
||||||
<value>Toggle shortcut</value>
|
<value>Shortcut</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="KeyboardManager_Toggle_Shortcut.Description" xml:space="preserve">
|
<data name="KeyboardManager_Toggle_Shortcut.Description" xml:space="preserve">
|
||||||
<value>Use a shortcut to toggle this module on or off (note that the Settings UI will not update)</value>
|
<value>Enable or disable this module (Note: the Settings UI will not update)</value>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_Editor_Shortcut.Header" xml:space="preserve">
|
||||||
|
<value>Editor shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_Editor_Open_Shortcut.Header" xml:space="preserve">
|
||||||
|
<value>Open editor shortcut</value>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_Editor_Open_Shortcut.Description" xml:space="preserve">
|
||||||
|
<value>Customize the shortcut to open the Keyboard Manager editor</value>
|
||||||
|
</data>
|
||||||
|
<data name="KeyboardManager_LaunchEditor_Button.Content" xml:space="preserve">
|
||||||
|
<value>Open editor</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasteAsPlainText_Shortcut.Header" xml:space="preserve">
|
<data name="PasteAsPlainText_Shortcut.Header" xml:space="preserve">
|
||||||
<value>Paste as plain text directly</value>
|
<value>Paste as plain text directly</value>
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ using System.Text.Json;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
using global::PowerToys.GPOWrapper;
|
using global::PowerToys.GPOWrapper;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library;
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
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.Library.ViewModels.Commands;
|
||||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||||
using Microsoft.PowerToys.Settings.Utilities;
|
using Microsoft.PowerToys.Settings.Utilities;
|
||||||
using Microsoft.Win32;
|
using Microsoft.PowerToys.Telemetry;
|
||||||
|
|
||||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
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.
|
// Default editor path. Can be removed once the new WinUI3 editor is released.
|
||||||
private const string KeyboardManagerEditorPath = "KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe";
|
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;
|
private Process editor;
|
||||||
|
|
||||||
@@ -57,6 +56,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
|
|
||||||
private ICommand _remapKeyboardCommand;
|
private ICommand _remapKeyboardCommand;
|
||||||
private ICommand _editShortcutCommand;
|
private ICommand _editShortcutCommand;
|
||||||
|
private ICommand _openNewEditorCommand;
|
||||||
private KeyboardManagerProfile _profile;
|
private KeyboardManagerProfile _profile;
|
||||||
|
|
||||||
private Func<string, int> SendConfigMSG { get; }
|
private Func<string, int> SendConfigMSG { get; }
|
||||||
@@ -181,7 +181,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
{
|
{
|
||||||
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
|
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
|
||||||
{
|
{
|
||||||
[ModuleName] = [ToggleShortcut],
|
[ModuleName] = [ToggleShortcut, EditorShortcut],
|
||||||
};
|
};
|
||||||
|
|
||||||
return hotkeysDict;
|
return hotkeysDict;
|
||||||
@@ -192,11 +192,55 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
get => Settings.Properties.ToggleShortcut;
|
get => Settings.Properties.ToggleShortcut;
|
||||||
set
|
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));
|
OnPropertyChanged(nameof(ToggleShortcut));
|
||||||
NotifySettingsChanged();
|
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 EditShortcutCommand => _editShortcutCommand ?? (_editShortcutCommand = new RelayCommand(OnEditShortcut));
|
||||||
|
|
||||||
|
public ICommand OpenNewEditorCommand => _openNewEditorCommand ?? (_openNewEditorCommand = new RelayCommand(OnOpenNewEditor));
|
||||||
|
|
||||||
public void OnRemapKeyboard()
|
public void OnRemapKeyboard()
|
||||||
{
|
{
|
||||||
OpenEditor((int)KeyboardManagerEditorType.KeyEditor);
|
OpenEditor((int)KeyboardManagerEditorType.KeyEditor);
|
||||||
@@ -272,6 +318,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
OpenEditor((int)KeyboardManagerEditorType.ShortcutEditor);
|
OpenEditor((int)KeyboardManagerEditorType.ShortcutEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnOpenNewEditor()
|
||||||
|
{
|
||||||
|
OpenNewEditor();
|
||||||
|
}
|
||||||
|
|
||||||
private static void BringProcessToFront(Process process)
|
private static void BringProcessToFront(Process process)
|
||||||
{
|
{
|
||||||
if (process == null)
|
if (process == null)
|
||||||
@@ -305,41 +356,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch the new editor if:
|
string path = Path.Combine(Environment.CurrentDirectory, KeyboardManagerEditorPath);
|
||||||
// 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);
|
|
||||||
Logger.LogInfo($"Starting {ModuleName} editor from {path}");
|
Logger.LogInfo($"Starting {ModuleName} editor from {path}");
|
||||||
|
|
||||||
// InvariantCulture: type represents the KeyboardManagerEditorType enum value
|
// 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)
|
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()
|
public void NotifyFileChanged()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(RemapKeys));
|
OnPropertyChanged(nameof(RemapKeys));
|
||||||
|
|||||||
Reference in New Issue
Block a user