70d44c84c8
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() ✔️
121 lines
4.7 KiB
C++
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;
|
|
}
|
|
}
|