306 lines
12 KiB
C++
306 lines
12 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "KeyChordSerialization.h"
|
|
#include "KeyChordSerialization.g.cpp"
|
|
|
|
#include <til/static_map.h>
|
|
#include <til/u8u16convert.h>
|
|
|
|
using namespace winrt::Microsoft::Terminal::Control;
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
|
using namespace Microsoft::Terminal::Settings::Model::JsonUtils;
|
|
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
|
|
|
|
constexpr std::wstring_view CTRL_KEY{ L"ctrl" };
|
|
constexpr std::wstring_view SHIFT_KEY{ L"shift" };
|
|
constexpr std::wstring_view ALT_KEY{ L"alt" };
|
|
constexpr std::wstring_view WIN_KEY{ L"win" };
|
|
|
|
#define VKEY_NAME_PAIRS(XX) \
|
|
XX(VK_RETURN, L"enter") \
|
|
XX(VK_TAB, L"tab") \
|
|
XX(VK_SPACE, L"space") \
|
|
XX(VK_BACK, L"backspace") \
|
|
XX(VK_APPS, L"menu", L"app") \
|
|
XX(VK_INSERT, L"insert") \
|
|
XX(VK_DELETE, L"delete") \
|
|
XX(VK_HOME, L"home") \
|
|
XX(VK_END, L"end") \
|
|
XX(VK_NEXT, L"pgdn", L"pagedown") \
|
|
XX(VK_PRIOR, L"pgup", L"pageup") \
|
|
XX(VK_ESCAPE, L"esc", L"escape") \
|
|
XX(VK_LEFT, L"left") \
|
|
XX(VK_RIGHT, L"right") \
|
|
XX(VK_UP, L"up") \
|
|
XX(VK_DOWN, L"down") \
|
|
XX(VK_F1, L"f1") \
|
|
XX(VK_F2, L"f2") \
|
|
XX(VK_F3, L"f3") \
|
|
XX(VK_F4, L"f4") \
|
|
XX(VK_F5, L"f5") \
|
|
XX(VK_F6, L"f6") \
|
|
XX(VK_F7, L"f7") \
|
|
XX(VK_F8, L"f8") \
|
|
XX(VK_F9, L"f9") \
|
|
XX(VK_F10, L"f10") \
|
|
XX(VK_F11, L"f11") \
|
|
XX(VK_F12, L"f12") \
|
|
XX(VK_F13, L"f13") \
|
|
XX(VK_F14, L"f14") \
|
|
XX(VK_F15, L"f15") \
|
|
XX(VK_F16, L"f16") \
|
|
XX(VK_F17, L"f17") \
|
|
XX(VK_F18, L"f18") \
|
|
XX(VK_F19, L"f19") \
|
|
XX(VK_F20, L"f20") \
|
|
XX(VK_F21, L"f21") \
|
|
XX(VK_F22, L"f22") \
|
|
XX(VK_F23, L"f23") \
|
|
XX(VK_F24, L"f24") \
|
|
XX(VK_ADD, L"numpad_plus", L"numpad_add") \
|
|
XX(VK_SUBTRACT, L"numpad_minus", L"numpad_subtract") \
|
|
XX(VK_MULTIPLY, L"numpad_multiply") \
|
|
XX(VK_DIVIDE, L"numpad_divide") \
|
|
XX(VK_DECIMAL, L"numpad_period", L"numpad_decimal") \
|
|
XX(VK_NUMPAD0, L"numpad0", L"numpad_0") \
|
|
XX(VK_NUMPAD1, L"numpad1", L"numpad_1") \
|
|
XX(VK_NUMPAD2, L"numpad2", L"numpad_2") \
|
|
XX(VK_NUMPAD3, L"numpad3", L"numpad_3") \
|
|
XX(VK_NUMPAD4, L"numpad4", L"numpad_4") \
|
|
XX(VK_NUMPAD5, L"numpad5", L"numpad_5") \
|
|
XX(VK_NUMPAD6, L"numpad6", L"numpad_6") \
|
|
XX(VK_NUMPAD7, L"numpad7", L"numpad_7") \
|
|
XX(VK_NUMPAD8, L"numpad8", L"numpad_8") \
|
|
XX(VK_NUMPAD9, L"numpad9", L"numpad_9") \
|
|
XX(VK_OEM_PLUS, L"plus")
|
|
|
|
// Function Description:
|
|
// - Deserializes the given string into a new KeyChord instance. If this
|
|
// fails to translate the string into a keychord, it will throw a
|
|
// hresult_invalid_argument exception.
|
|
// - The string should fit the format "[ctrl+][alt+][shift+]<keyName>",
|
|
// where each modifier is optional, and keyName is either one of the
|
|
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-zA-Z.
|
|
// Arguments:
|
|
// - hstr: the string to parse into a keychord.
|
|
// Return Value:
|
|
// - a newly constructed KeyChord
|
|
static KeyChord _fromString(std::wstring_view wstr)
|
|
{
|
|
using nameToVkeyPair = std::pair<std::wstring_view, int32_t>;
|
|
static const til::static_map nameToVkey{
|
|
// The above VKEY_NAME_PAIRS macro contains a list of key-binding names for each virtual key.
|
|
// This god-awful macro inverts VKEY_NAME_PAIRS and creates a static map of key-binding names to virtual keys.
|
|
// clang-format off
|
|
#define GENERATOR_1(vkey, name1) nameToVkeyPair{ name1, vkey },
|
|
#define GENERATOR_2(vkey, name1, name2) nameToVkeyPair{ name1, vkey }, nameToVkeyPair{ name2, vkey },
|
|
#define GENERATOR_3(vkey, name1, name2, name3) nameToVkeyPair{ name1, vkey }, nameToVkeyPair{ name2, vkey }, nameToVkeyPair{ name3, vkey },
|
|
#define GENERATOR_N(vkey, name1, name2, name3, MACRO, ...) MACRO
|
|
#define GENERATOR(...) GENERATOR_N(__VA_ARGS__, GENERATOR_3, GENERATOR_2, GENERATOR_1)(__VA_ARGS__)
|
|
VKEY_NAME_PAIRS(GENERATOR)
|
|
#undef GENERATOR_1
|
|
#undef GENERATOR_2
|
|
#undef GENERATOR_3
|
|
#undef GENERATOR_N
|
|
#undef GENERATOR
|
|
// clang-format on
|
|
};
|
|
|
|
VirtualKeyModifiers modifiers = VirtualKeyModifiers::None;
|
|
int32_t vkey = 0;
|
|
|
|
while (!wstr.empty())
|
|
{
|
|
const auto part = til::prefix_split(wstr, L"+");
|
|
|
|
if (til::equals_insensitive_ascii(part, CTRL_KEY))
|
|
{
|
|
modifiers |= VirtualKeyModifiers::Control;
|
|
}
|
|
else if (til::equals_insensitive_ascii(part, ALT_KEY))
|
|
{
|
|
modifiers |= VirtualKeyModifiers::Menu;
|
|
}
|
|
else if (til::equals_insensitive_ascii(part, SHIFT_KEY))
|
|
{
|
|
modifiers |= VirtualKeyModifiers::Shift;
|
|
}
|
|
else if (til::equals_insensitive_ascii(part, WIN_KEY))
|
|
{
|
|
modifiers |= VirtualKeyModifiers::Windows;
|
|
}
|
|
else
|
|
{
|
|
if (vkey)
|
|
{
|
|
// Key bindings like Ctrl+A+B are not valid.
|
|
throw winrt::hresult_invalid_argument();
|
|
}
|
|
|
|
// Characters 0-9, a-z, A-Z directly map to virtual keys.
|
|
if (part.size() == 1)
|
|
{
|
|
const auto wch = til::toupper_ascii(part[0]);
|
|
if ((wch >= L'0' && wch <= L'9') || (wch >= L'A' && wch <= L'Z'))
|
|
{
|
|
vkey = static_cast<int32_t>(wch);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// nameToVkey contains a few more mappings like "F11".
|
|
if (const auto it = nameToVkey.find(part); it != nameToVkey.end())
|
|
{
|
|
vkey = it->second;
|
|
continue;
|
|
}
|
|
|
|
// If we haven't found a key, attempt a keyboard mapping
|
|
if (part.size() == 1)
|
|
{
|
|
const auto oemVk = VkKeyScanW(part[0]);
|
|
if (oemVk != -1)
|
|
{
|
|
vkey = oemVk & 0xff;
|
|
const auto oemModifiers = oemVk >> 8;
|
|
// NOTE: WI_UpdateFlag _replaces_ a bit. This code _adds_ a bit.
|
|
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Shift, WI_IsFlagSet(oemModifiers, 1U));
|
|
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Control, WI_IsFlagSet(oemModifiers, 2U));
|
|
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Menu, WI_IsFlagSet(oemModifiers, 4U));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
throw winrt::hresult_invalid_argument();
|
|
}
|
|
}
|
|
|
|
return KeyChord{ modifiers, vkey };
|
|
}
|
|
|
|
// Function Description:
|
|
// - Serialize this keychord into a string representation.
|
|
// - The string will fit the format "[ctrl+][alt+][shift+]<keyName>",
|
|
// where each modifier is optional, and keyName is either one of the
|
|
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-z.
|
|
// Return Value:
|
|
// - a string which is an equivalent serialization of this object.
|
|
static std::wstring _toString(const KeyChord& chord)
|
|
{
|
|
using vkeyToNamePair = std::pair<int32_t, std::wstring_view>;
|
|
static const til::static_map vkeyToName{
|
|
// The above VKEY_NAME_PAIRS macro contains a list of key-binding strings for each virtual key.
|
|
// This macro picks the first (most preferred) name and creates a static map of virtual keys to key-binding names.
|
|
#define GENERATOR(vkey, name1, ...) vkeyToNamePair{ vkey, name1 },
|
|
VKEY_NAME_PAIRS(GENERATOR)
|
|
#undef GENERATOR
|
|
};
|
|
|
|
if (!chord)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
const auto modifiers = chord.Modifiers();
|
|
const auto vkey = chord.Vkey();
|
|
std::wstring buffer;
|
|
|
|
// Add modifiers
|
|
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Windows))
|
|
{
|
|
buffer.append(WIN_KEY);
|
|
buffer.push_back(L'+');
|
|
}
|
|
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Control))
|
|
{
|
|
buffer.append(CTRL_KEY);
|
|
buffer.push_back(L'+');
|
|
}
|
|
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu))
|
|
{
|
|
buffer.append(ALT_KEY);
|
|
buffer.push_back(L'+');
|
|
}
|
|
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift))
|
|
{
|
|
buffer.append(SHIFT_KEY);
|
|
buffer.push_back(L'+');
|
|
}
|
|
|
|
// Quick lookup: ranges of vkeys that correlate directly to a key.
|
|
if ((vkey >= L'0' && vkey <= L'9') || (vkey >= L'A' && vkey <= L'Z'))
|
|
{
|
|
buffer.push_back(til::tolower_ascii(gsl::narrow_cast<wchar_t>(vkey)));
|
|
return buffer;
|
|
}
|
|
|
|
if (const auto it = vkeyToName.find(vkey); it != vkeyToName.end())
|
|
{
|
|
buffer.append(it->second);
|
|
return buffer;
|
|
}
|
|
|
|
const auto mappedChar = MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR);
|
|
if (mappedChar != 0)
|
|
{
|
|
buffer.push_back(gsl::narrow_cast<wchar_t>(mappedChar));
|
|
return buffer;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr)
|
|
{
|
|
return _fromString(hstr);
|
|
}
|
|
|
|
winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord)
|
|
{
|
|
return hstring{ _toString(chord) };
|
|
}
|
|
|
|
KeyChord ConversionTrait<KeyChord>::FromJson(const Json::Value& json)
|
|
{
|
|
try
|
|
{
|
|
std::string keyChordText;
|
|
if (json.isString())
|
|
{
|
|
// "keys": "ctrl+c"
|
|
keyChordText = json.asString();
|
|
}
|
|
else if (json.isArray() && json.size() == 1 && json[0].isString())
|
|
{
|
|
// "keys": [ "ctrl+c" ]
|
|
keyChordText = json[0].asString();
|
|
}
|
|
else
|
|
{
|
|
throw winrt::hresult_invalid_argument{};
|
|
}
|
|
return _fromString(til::u8u16(keyChordText));
|
|
}
|
|
catch (...)
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
bool ConversionTrait<KeyChord>::CanConvert(const Json::Value& json)
|
|
{
|
|
return json.isString() || (json.isArray() && json.size() == 1 && json[0].isString());
|
|
}
|
|
|
|
Json::Value ConversionTrait<KeyChord>::ToJson(const KeyChord& val)
|
|
{
|
|
return til::u16u8(_toString(val));
|
|
}
|
|
|
|
std::string ConversionTrait<KeyChord>::TypeDescription() const
|
|
{
|
|
return "key chord";
|
|
}
|