-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a KeyChordListener to the Settings UI (#10652)
## Summary of the Pull Request Replaces the key chord editor in the actions page with a listener instead of a plain text box. ## References #6900 - Settings UI Epic ## Detailed Description of the Pull Request / Additional comments - `Actions` page: - Replace `Keys` with `CurrentKeys` for consistency with `Action`/`CurrentAction` - `ProposedKeys` is now a `Control::KeyChord` - removes key chord validation (now we don't need it) - removes accept/cancel shortcuts (nowhere we could use it now) - `KeyChordListener`: - `Keys`: dependency property that hooks us up to a system to the committed setting value - this is the key binding view model, which propagates the change to the settings model clone on "accept changes" - We bind to `PreviewKeyDown` to intercept the key event _before_ some special key bindings are handled (i.e. "select all" in the text box) - `CoreWindow` is used to get the modifier keys because (1) it's easier than updating on each key press and (2) that approach resulted in a strange bug where the <kbd>Alt</kbd> key-up event was not detected - `LosingFocus` means that we have completed our operation and want to commit our changes to the key binding view model - `KeyDown` does most of the magic of updating `Keys`. We filter out any key chords that could be problematic (i.e. <kbd>Shift</kbd>+<kbd>Tab</kbd> and <kbd>Tab</kbd> for keyboard navigation) ## Validation Steps Performed - Tested a few key chords: - ✅single key: <kbd>X</kbd> - ✅key with modifier(s): <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>X</kbd> - ❌plain modifier: <kbd>Ctrl</kbd> - ✅key that is used by text box: <kbd>Ctrl+A</kbd> - ✅key that is used by Windows Terminal: <kbd>Alt</kbd>+<kbd>F4</kbd> - ❌key that is taken by Windows OS: <kbd>Windows</kbd>+<kbd>X</kbd> - ✅key that is not taken by Windows OS: <kbd>Windows</kbd>+<kbd>Shift</kbd>+<kbd>X</kbd> - Known issue: - global key taken by Windows Terminal: (i.e. quake mode keybinding) - Behavior: global key binding executed - Expected: key chord recorded ## Demo ![Key Chord Listener Demo](https://user-images.githubusercontent.com/11050425/125538094-08ea4eaa-21eb-4488-a74c-6ce65324cdf1.gif)
- Loading branch information
1 parent
293c36d
commit 8947909
Showing
12 changed files
with
259 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
src/cascadia/TerminalSettingsEditor/KeyChordListener.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
#include "pch.h" | ||
#include "KeyChordListener.h" | ||
#include "KeyChordListener.g.cpp" | ||
#include "LibraryResources.h" | ||
|
||
using namespace winrt::Windows::UI::Core; | ||
using namespace winrt::Windows::UI::Xaml; | ||
using namespace winrt::Windows::UI::Xaml::Controls; | ||
using namespace winrt::Windows::UI::Xaml::Data; | ||
using namespace winrt::Windows::Foundation; | ||
using namespace winrt::Windows::System; | ||
using namespace winrt::Windows::UI::Xaml::Input; | ||
|
||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation | ||
{ | ||
DependencyProperty KeyChordListener::_KeysProperty{ nullptr }; | ||
|
||
static constexpr std::array ModifierKeys{ | ||
VirtualKey::Shift, | ||
VirtualKey::Control, | ||
VirtualKey::Menu, | ||
VirtualKey::LeftWindows, | ||
VirtualKey::RightWindows, | ||
VirtualKey::LeftShift, | ||
VirtualKey::LeftControl, | ||
VirtualKey::RightControl, | ||
VirtualKey::LeftMenu, | ||
VirtualKey::RightMenu | ||
}; | ||
|
||
static VirtualKeyModifiers _GetModifiers() | ||
{ | ||
const auto window{ CoreWindow::GetForCurrentThread() }; | ||
|
||
VirtualKeyModifiers flags = VirtualKeyModifiers::None; | ||
for (const auto mod : ModifierKeys) | ||
{ | ||
const auto state = window.GetKeyState(mod); | ||
const auto isDown = WI_IsFlagSet(state, CoreVirtualKeyStates::Down); | ||
|
||
if (isDown) | ||
{ | ||
switch (mod) | ||
{ | ||
case VirtualKey::Control: | ||
case VirtualKey::LeftControl: | ||
case VirtualKey::RightControl: | ||
flags |= VirtualKeyModifiers::Control; | ||
break; | ||
case VirtualKey::Menu: | ||
case VirtualKey::LeftMenu: | ||
case VirtualKey::RightMenu: | ||
flags |= VirtualKeyModifiers::Menu; | ||
break; | ||
case VirtualKey::Shift: | ||
case VirtualKey::LeftShift: | ||
flags |= VirtualKeyModifiers::Shift; | ||
break; | ||
case VirtualKey::LeftWindows: | ||
case VirtualKey::RightWindows: | ||
flags |= VirtualKeyModifiers::Windows; | ||
break; | ||
} | ||
} | ||
} | ||
return flags; | ||
} | ||
|
||
KeyChordListener::KeyChordListener() | ||
{ | ||
InitializeComponent(); | ||
_InitializeProperties(); | ||
} | ||
|
||
void KeyChordListener::_InitializeProperties() | ||
{ | ||
// Initialize any KeyChordListener dependency properties here. | ||
// This performs a lazy load on these properties, instead of | ||
// initializing them when the DLL loads. | ||
if (!_KeysProperty) | ||
{ | ||
_KeysProperty = | ||
DependencyProperty::Register( | ||
L"Keys", | ||
xaml_typename<Control::KeyChord>(), | ||
xaml_typename<Editor::KeyChordListener>(), | ||
PropertyMetadata{ nullptr, PropertyChangedCallback{ &KeyChordListener::_OnKeysChanged } }); | ||
} | ||
} | ||
|
||
void KeyChordListener::_OnKeysChanged(DependencyObject const& d, DependencyPropertyChangedEventArgs const& e) | ||
{ | ||
if (auto control{ d.try_as<Editor::KeyChordListener>() }) | ||
{ | ||
auto controlImpl{ get_self<KeyChordListener>(control) }; | ||
TextBox tb{ controlImpl->FindName(L"KeyChordTextBox").as<TextBox>() }; | ||
tb.Text(Model::KeyChordSerialization::ToString(unbox_value<Control::KeyChord>(e.NewValue()))); | ||
if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(tb) }) | ||
{ | ||
automationPeer.RaiseNotificationEvent( | ||
Automation::Peers::AutomationNotificationKind::ActionCompleted, | ||
Automation::Peers::AutomationNotificationProcessing::MostRecent, | ||
tb.Text(), | ||
L"KeyChordListenerText"); | ||
} | ||
} | ||
} | ||
|
||
void KeyChordListener::KeyChordTextBox_KeyDown(IInspectable const& /*sender*/, KeyRoutedEventArgs const& e) | ||
{ | ||
const auto key{ e.OriginalKey() }; | ||
for (const auto mod : ModifierKeys) | ||
{ | ||
if (key == mod) | ||
{ | ||
// Ignore modifier keys | ||
return; | ||
} | ||
} | ||
|
||
const auto modifiers{ _GetModifiers() }; | ||
if (key == VirtualKey::Tab && (modifiers == VirtualKeyModifiers::None || modifiers == VirtualKeyModifiers::Shift)) | ||
{ | ||
// [Shift]+[Tab] && [Tab] are needed for keyboard navigation | ||
return; | ||
} | ||
|
||
// Permitted key events are used to update _Keys | ||
Keys({ modifiers, static_cast<int32_t>(key) }); | ||
e.Handled(true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
#pragma once | ||
|
||
#include "KeyChordListener.g.h" | ||
#include "Utils.h" | ||
|
||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation | ||
{ | ||
struct KeyChordListener : KeyChordListenerT<KeyChordListener> | ||
{ | ||
public: | ||
KeyChordListener(); | ||
|
||
void KeyChordTextBox_KeyDown(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); | ||
|
||
DEPENDENCY_PROPERTY(Control::KeyChord, Keys); | ||
|
||
private: | ||
static void _InitializeProperties(); | ||
static void _OnKeysChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e); | ||
}; | ||
} | ||
|
||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation | ||
{ | ||
BASIC_FACTORY(KeyChordListener); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
namespace Microsoft.Terminal.Settings.Editor | ||
{ | ||
[default_interface] runtimeclass KeyChordListener : Windows.UI.Xaml.Controls.UserControl | ||
{ | ||
KeyChordListener(); | ||
|
||
Microsoft.Terminal.Control.KeyChord Keys; | ||
static Windows.UI.Xaml.DependencyProperty KeysProperty { get; }; | ||
} | ||
} |
Oops, something went wrong.