terminal/src/cascadia/TerminalControl/KeyChord.cpp
Leonard Hecker 70d44c84c8
Make ActionMap compatible with ScanCode-only KeyChords (#10945)
This commit partially reverts d465a47 and introduces an alternative approach by adding Hash and Equals methods to the KeyChords class. Those methods will now favor any existing Vkeys over ScanCodes.

## PR Checklist
* [x] Closes #10933
* [x] I work here
* [x] Tests added/passed

## Validation Steps Performed

* Added a new test, which is ✔️
* Various standard commands still work ✔️
* Hash() returns the same value for all KeyChords that are Equals() ✔️
2021-08-20 00:21:33 +00:00

121 lines
4.7 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "KeyChord.h"
#include "KeyChord.g.cpp"
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
namespace winrt::Microsoft::Terminal::Control::implementation
{
static VirtualKeyModifiers modifiersFromBooleans(bool ctrl, bool alt, bool shift, bool win)
{
VirtualKeyModifiers modifiers = VirtualKeyModifiers::None;
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Control, ctrl);
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Menu, alt);
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Shift, shift);
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Windows, win);
return modifiers;
}
KeyChord::KeyChord(bool ctrl, bool alt, bool shift, bool win, int32_t vkey, int32_t scanCode) noexcept :
KeyChord(modifiersFromBooleans(ctrl, alt, shift, win), vkey, scanCode)
{
}
KeyChord::KeyChord(const VirtualKeyModifiers modifiers, int32_t vkey, int32_t scanCode) noexcept :
_modifiers{ modifiers },
_vkey{ vkey },
_scanCode{ scanCode }
{
// ActionMap needs to identify KeyChords which should "layer" (overwrite) each other.
// For instance win+sc(41) and win+` both specify the same KeyChord on an US keyboard layout
// from the perspective of a user. Either of the two should correctly overwrite the other.
// We can help ActionMap with this by ensuring that Vkey() is always valid.
if (!_vkey)
{
_vkey = MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX);
}
assert(_vkey || _scanCode);
}
uint64_t KeyChord::Hash() const noexcept
{
// Two KeyChords are equal if they have the same modifiers and either identical
// Vkey or ScanCode, with Vkey being preferred. See KeyChord::Equals().
// This forces us to _either_ hash _vkey or _scanCode.
//
// Additionally the hash value with _vkey==123 and _scanCode==123 must be different.
// --> Taint hashes of KeyChord without _vkey.
auto h = static_cast<uint64_t>(_modifiers) << 32;
h |= _vkey ? _vkey : (_scanCode | 0xBABE0000);
// I didn't like how std::hash uses the byte-wise FNV1a for integers.
// So I built my own std::hash with murmurhash3.
h ^= h >> 33;
h *= UINT64_C(0xff51afd7ed558ccd);
h ^= h >> 33;
h *= UINT64_C(0xc4ceb9fe1a85ec53);
h ^= h >> 33;
return h;
}
bool KeyChord::Equals(const Control::KeyChord& other) const noexcept
{
// Two KeyChords are equal if they have the same modifiers and either identical
// Vkey or ScanCode, with Vkey being preferred. Vkey is preferred because:
// ActionMap needs to identify KeyChords which should "layer" (overwrite) each other.
// For instance win+sc(41) and win+` both specify the same KeyChord on an US keyboard layout
// from the perspective of a user. Either of the two should correctly overwrite the other.
//
// Two problems exist here:
// * Since a value of 0 indicates that the Vkey/ScanCode isn't set, we cannot use == to compare them.
// Otherwise we return true, even if the Vkey/ScanCode isn't set on both sides.
// * Whenever Equals() returns true, the Hash() value _must_ be identical.
// For instance the code below ensures the preference of Vkey over ScanCode by:
// this->_vkey || other->_vkey ? ...vkey... : ...scanCode...
// We cannot use "&&", even if it would be technically more correct, as this would mean the
// return value of this function would be dependent on the existence of a Vkey in "other".
// But Hash() has no "other" argument to consider when deciding if its Vkey or ScanCode should be hashed.
//
// Bitwise operators are used because MSVC doesn't support compiling
// boolean operators into bitwise ones at the time of writing.
const auto otherSelf = winrt::get_self<KeyChord>(other);
return _modifiers == otherSelf->_modifiers && ((_vkey | otherSelf->_vkey) ? _vkey == otherSelf->_vkey : _scanCode == otherSelf->_scanCode);
}
VirtualKeyModifiers KeyChord::Modifiers() const noexcept
{
return _modifiers;
}
void KeyChord::Modifiers(const VirtualKeyModifiers value) noexcept
{
_modifiers = value;
}
int32_t KeyChord::Vkey() const noexcept
{
return _vkey;
}
void KeyChord::Vkey(int32_t value) noexcept
{
_vkey = value;
}
int32_t KeyChord::ScanCode() const noexcept
{
return _scanCode;
}
void KeyChord::ScanCode(int32_t value) noexcept
{
_scanCode = value;
}
}