Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce vk() and sc() key chord specifiers #10666

Merged
15 commits merged into from
Jul 20, 2021
Merged

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Jul 15, 2021

This commit introduces an alternative to specifying key bindings as a combination of key modifiers and a character. It allows you to specify an explicit virtual key as vk(nnn).
Additionally this commit makes it possible to bind actions to scan codes. As scan code 41 appears to be the button below the Escape key on virtually all keyboards, we'll be able to bind the quake mode hotkey to win+sc(41) and have it work consistently across most if not all keyboard layouts.

PR Checklist

Validation Steps Performed

The following was tested both on US and DE keyboard layouts:

  • Ctrl+, opens settings ✔️
  • Win+` opens quake mode window ✔️
  • Ctrl+plus/minus increase/decrease font size ✔️

@ghost ghost added Area-Settings Issues related to settings and customizability, for console or terminal Impact-Correctness It be wrong. Issue-Bug It either shouldn't be doing this or needs an investigation. Issue-Task It's a feature request, but it doesn't really need a major design. Priority-1 A description (P1) Product-Terminal The new Windows Terminal. labels Jul 15, 2021
@github-actions

This comment has been minimized.

Comment on lines +10 to +11
KeyChord(Windows.System.VirtualKeyModifiers modifiers, Int32 vkey, Int32 scanCode);
KeyChord(Boolean ctrl, Boolean alt, Boolean shift, Boolean win, Int32 vkey, Int32 scanCode);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to add a 5th constructor, so I removed 1 instead.

}

// This key chord is not explicitly bound
return nullptr;
return _GetActionByKeyChordInternal({ modifiers, 0, keys.ScanCode() }).value_or(nullptr);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll now have to run _GetActionByKeyChordInternal twice, since the given keys might have both a vkey as well as a scan code set, but the internal hashmaps have only key bindings loaded that contain either of the two.

I used to have a comment explaining this here, but I must've lost it at some point. I'll re-add one.

Comment on lines +173 to +174
{ "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus" },
{ "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+minus" },
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes zooming for non-US keyboards.

@@ -298,7 +298,7 @@
{ "command": "commandPalette", "keys":"ctrl+shift+p" },
{ "command": "identifyWindow" },
{ "command": "openWindowRenamer" },
{ "command": "quakeMode", "keys":"win+`" },
{ "command": "quakeMode", "keys":"win+sc(41)" },
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes quake mode for non-US keyboards.

{
if (k != nullptr)
if (auto summonArgs = cmd.ActionAndArgs().Args().try_as<Settings::Model::GlobalSummonArgs>())
Copy link
Member Author

@lhecker lhecker Jul 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By casting the cmd prematurely, we won't have to cast it again in AppHost::_GlobalHotkeyPressed.
Additionally we won't have to hold onto the _hotkeyActions map.

args.SummonBehavior().MoveToCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::ToCurrent);
args.SummonBehavior().ToggleVisibility(summonArgs.ToggleVisibility());
args.SummonBehavior().DropdownDuration(summonArgs.DropdownDuration());
const auto& summonArgs = til::at(_hotkeys, hotkeyIndex);
Copy link
Member Author

@lhecker lhecker Jul 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see here, a lot of casting is gone without the _hotkeyActions map.
Otherwise this code is still the same - just less indented.
(Suppressing white-space changes in GitHub makes this PR less scary.)

winrt::com_ptr<IVirtualDesktopManager> _desktopManager{ nullptr };

bool _shouldCreateWindow{ false };
bool _useNonClientArea{ false };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_useNonClientArea wasn't initialized during construction. I moved them down so this class uses less padding.

"constexpr std::string_view $($VariableName){",
($jsonData | ForEach-Object { "R`"#($_`n)#`"" }),
"};"
) | Out-File -FilePath $OutPath -Encoding utf8
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to add support for "( and )" escaping (now it uses R"#(...)#" instead), and I thought I could clean this file up a bit. 👍

@lhecker lhecker force-pushed the dev/lhecker/7539-key-bindings-impl branch from 09aa4f4 to 0bb6e34 Compare July 15, 2021 13:00
// Return Value:
// - <none>
void IslandWindow::SetGlobalHotkeys(const std::vector<winrt::Microsoft::Terminal::Control::KeyChord>& hotkeyList)
void IslandWindow::RegisterHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By passing the hotkey not as a vector but rather one by one, we can get rid of the std::vector of key chords in the AppHost.

Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meh, these are all nits, but the PR is still in draft, so I'll hold off ✔️-ing until the final version

src/cascadia/WindowsTerminal/IslandWindow.h Outdated Show resolved Hide resolved
src/cascadia/WindowsTerminal/IslandWindow.h Outdated Show resolved Hide resolved
if (cmd.has_value())
const auto modifiers = keys.Modifiers();

if (auto vkey = keys.Vkey())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be tremendously dumb to oneline this as

        return _GetActionByKeyChordInternal({ modifiers, keys.Vkey(), 0 }).value_or( _GetActionByKeyChordInternal({ modifiers, 0, keys.ScanCode() }).value_or(nullptr));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would certainly be nice! But it would also eagerly check both variants, before returning either, and I'm not sure if that's a good idea...

@KalleOlaviNiemitalo
Copy link

Should this be added to profiles.schema.json too?

@lhecker
Copy link
Member Author

lhecker commented Jul 15, 2021

@KalleOlaviNiemitalo Yeah I can do that. 👍
The space in the description field is rather limited though, so I've planned to add the actual, exhaustive, description to the online docs only.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@KalleOlaviNiemitalo
Copy link

Rather than the description, I meant this regular expression

"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",

@lhecker
Copy link
Member Author

lhecker commented Jul 15, 2021

@KalleOlaviNiemitalo Oh wow that's a rather... comprehensive regex. 😄
I'm not sure if it's worth keeping it that complex, since it would get out of sync every time the VKEY_NAME_PAIRS list is changed in the code. (And it uses backtracking...)

For now I'll just remove the backtracking from the regex and update it with the 5 new key chord types.

@lhecker lhecker marked this pull request as ready for review July 15, 2021 23:28
Copy link
Member Author

@lhecker lhecker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining TODOs:

  • Add a comment to ActionMap::GetActionByKeyChord ✔️
  • Add a unit test for vk(nnn) / sc(nnn) parsing I can't run LocalTests_SettingsModel tests. 😟

The remaining implementation in this PR is "ready" from my side though.

@@ -4,13 +4,13 @@
"title": "Microsoft's Windows Terminal Settings Profile Schema",
"definitions": {
"KeyChordSegment": {
"pattern": "^(?<modifier>(?<mod1>ctrl|alt|shift|win)(?:\\+(?<mod2>ctrl|alt|shift|win)(?<!\\k<mod1>))?(?:\\+(?<mod3>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>))?(?:\\+(?<mod4>ctrl|alt|shift|win)(?<!\\k<mod1>|\\k<mod2>|\\k<mod3>))?\\+)?(?<key>[^\\s+]|app|menu|backspace|tab|enter|esc|escape|space|pgup|pageup|pgdn|pagedown|end|home|left|up|right|down|insert|delete|(?<!shift.+)(?:numpad_?[0-9]|numpad_(?:period|decimal))|numpad_(?:multiply|plus|add|minus|subtract|divide)|f[1-9]|f1[0-9]|f2[0-4]|plus)$",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's an actually correct regex for all the things this accepts, I won't be shocked, but I don't think it's valuable to try and optimize this more past this point IMO.

Copy link
Member Author

@lhecker lhecker Jul 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I made it less correct than it was, because the previous regexp was really really hard to understand. 😅

// The below function was used to test from_wchars for unsafety and conformance with clang's strtoul.
// The test was run as:
// clang++ -fsanitize=address,undefined,fuzzer -std=c++17 file.cpp
// and was run for 20min across 16 jobs in parallel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to file a backlog follow-up to add this test to onefuzz after #10431 merges

std::vector<winrt::Microsoft::Terminal::Control::KeyChord> _hotkeys{};
winrt::Windows::Foundation::Collections::IMapView<winrt::Microsoft::Terminal::Control::KeyChord, winrt::Microsoft::Terminal::Settings::Model::Command> _hotkeyActions{ nullptr };

std::vector<winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs> _hotkeys;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does somewhat violate the design where any action could have been promoted to be "global" :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if @zadjii-msft is okay with this ("YAGNI"), then okay

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we'll need it, at least not for a while. Methinks the event horizon for "slam arbitrary actions into the tray flyout" is decently far away.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just changed it because I found the code hard to read and I applied the YAGNI principle to it. I find it easier to understand now, as less casts/checks are necessary.
But of course I can't know how others about it. If you feel like I should revert it I'll gladly do so!
I'll wait a bit before I merge it. :)

Copy link
Member Author

@lhecker lhecker Jul 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course we can also convert the type of this vector to be std::vector<Command> in the future and retain the same advantages (well I mean the advantages as I perceived them). 🙂

@lhecker lhecker added the AutoMerge Marked for automatic merge by the bot when requirements are met label Jul 20, 2021
@ghost
Copy link

ghost commented Jul 20, 2021

Hello @lhecker!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 10b12ac into main Jul 20, 2021
@ghost ghost deleted the dev/lhecker/7539-key-bindings-impl branch July 20, 2021 22:35
Rosefield pushed a commit to Rosefield/terminal that referenced this pull request Jul 22, 2021
This commit introduces an alternative to specifying key bindings as a combination of key modifiers and a character. It allows you to specify an explicit virtual key as `vk(nnn)`.
Additionally this commit makes it possible to bind actions to scan codes. As scan code 41 appears to be the button below the Escape key on virtually all keyboards, we'll be able to bind the quake mode hotkey to `win+sc(41)` and have it work consistently across most if not all keyboard layouts.

## PR Checklist
* [x] Closes microsoft#7539, Closes microsoft#10203
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

The following was tested both on US and DE keyboard layouts:
* Ctrl+, opens settings ✔️
* Win+` opens quake mode window ✔️
* Ctrl+plus/minus increase/decrease font size ✔️
ghost pushed a commit that referenced this pull request Aug 5, 2021
`VkKeyScanW` as well as `MapVirtualKeyW` are used throughout
the project, but are input method sensitive functions.

Since #10666 `win+sc(41)` is used as the quake mode keybinding,
which is then mapped to a virtual key in order to call `RegisterHotKey`.
This mapping is highly dependent on the input method and the quake mode
key binding will fail to work once the input method was changed.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #10729
* [x] I work here
* [ ] Tests added/passed

## Validation Steps Performed

* win+` opens quake window before & after changing keyboard layout ✔️
* keyboard layout changes while WT is minimized trigger reloaded ✔️
@zadjii-msft zadjii-msft mentioned this pull request Aug 9, 2021
3 tasks
ghost pushed a commit that referenced this pull request Aug 11, 2021
## Summary of the Pull Request

This isn't a fix for #10875, but it is logging that help identify the root cause here. The logging may additionally be helpful for some of the other issues we're seeing elsewhere in the repo, namely #10340. 

@lhecker is actually working on the fix for #10875, so hopefully this test will help validate.

## References
* Regressed in #10666.
* logging for #8888

## PR Checklist
* [x] Closes nothing
* [x] I work here
* [x] Tests added, and they absolutely fail, but they're localtests, so ¯\\\_(ツ)_/¯
* [n/a] Requires documentation to be updated

## details

While I was here, I noticed that `KeyBindingsTests::KeyChords` has been broken for some time now. So I fixed that too.
@ghost
Copy link

ghost commented Aug 31, 2021

🎉Windows Terminal Preview v1.11.2421.0 has been released which incorporates this pull request.:tada:

Handy links:

@DarthJahus
Copy link

👏 This is a huge win for key bindings in general. I wish all video games switched to scan codes instead of being layout dependent.

@MyloCyrus
Copy link

vk(nnn)

vk(nnn)

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Settings Issues related to settings and customizability, for console or terminal AutoMerge Marked for automatic merge by the bot when requirements are met Impact-Correctness It be wrong. Issue-Bug It either shouldn't be doing this or needs an investigation. Issue-Task It's a feature request, but it doesn't really need a major design. Needs-Second It's a PR that needs another sign-off Priority-1 A description (P1) Product-Terminal The new Windows Terminal.
Projects
None yet
6 participants