// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "KeyChordSerialization.h" using namespace winrt::Microsoft::Terminal::Settings; static constexpr std::wstring_view CTRL_KEY{ L"ctrl" }; static constexpr std::wstring_view SHIFT_KEY{ L"shift" }; static constexpr std::wstring_view ALT_KEY{ L"alt" }; static constexpr int MAX_CHORD_PARTS = 4; // clang-format off static const std::unordered_map vkeyNamePairs { { VK_BACK , L"backspace"}, { VK_TAB , L"tab"}, { VK_RETURN , L"enter" }, { VK_ESCAPE , L"esc" }, { VK_SPACE , L"space" }, { VK_PRIOR , L"pgup" }, { VK_NEXT , L"pgdn" }, { VK_END , L"end" }, { VK_HOME , L"home" }, { VK_LEFT , L"left" }, { VK_UP , L"up" }, { VK_RIGHT , L"right" }, { VK_DOWN , L"down" }, { VK_INSERT , L"insert" }, { VK_DELETE , L"delete" }, { VK_NUMPAD0 , L"numpad_0" }, { VK_NUMPAD1 , L"numpad_1" }, { VK_NUMPAD2 , L"numpad_2" }, { VK_NUMPAD3 , L"numpad_3" }, { VK_NUMPAD4 , L"numpad_4" }, { VK_NUMPAD5 , L"numpad_5" }, { VK_NUMPAD6 , L"numpad_6" }, { VK_NUMPAD7 , L"numpad_7" }, { VK_NUMPAD8 , L"numpad_8" }, { VK_NUMPAD9 , L"numpad_9" }, { VK_MULTIPLY , L"numpad_multiply" }, { VK_ADD , L"numpad_plus" }, { VK_SUBTRACT , L"numpad_minus" }, { VK_DECIMAL , L"numpad_period" }, { VK_DIVIDE , L"numpad_divide" }, { VK_F1 , L"f1" }, { VK_F2 , L"f2" }, { VK_F3 , L"f3" }, { VK_F4 , L"f4" }, { VK_F5 , L"f5" }, { VK_F6 , L"f6" }, { VK_F7 , L"f7" }, { VK_F8 , L"f8" }, { VK_F9 , L"f9" }, { VK_F10 , L"f10" }, { VK_F11 , L"f11" }, { VK_F12 , L"f12" }, { VK_F13 , L"f13" }, { VK_F14 , L"f14" }, { VK_F15 , L"f15" }, { VK_F16 , L"f16" }, { VK_F17 , L"f17" }, { VK_F18 , L"f18" }, { VK_F19 , L"f19" }, { VK_F20 , L"f20" }, { VK_F21 , L"f21" }, { VK_F22 , L"f22" }, { VK_F23 , L"f23" }, { VK_F24 , L"f24" }, { VK_OEM_PLUS , L"plus" }, { VK_OEM_COMMA , L"," }, { VK_OEM_MINUS , L"-" }, { VK_OEM_PERIOD , L"." } // TODO: // These all look like they'd be good keybindings, but change based on keyboard // layout. How do we deal with that? // #define VK_OEM_NEC_EQUAL 0x92 // '=' key on numpad // #define VK_OEM_1 0xBA // ';:' for US // #define VK_OEM_2 0xBF // '/?' for US // #define VK_OEM_3 0xC0 // '`~' for US // #define VK_OEM_4 0xDB // '[{' for US // #define VK_OEM_5 0xDC // '\|' for US // #define VK_OEM_6 0xDD // ']}' for US // #define VK_OEM_7 0xDE // ''"' for US }; // clang-format on // 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+]", // 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 winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) { std::wstring wstr{ hstr }; // Split the string on '+' std::wstring temp; std::vector parts; std::wstringstream wss(wstr); while (std::getline(wss, temp, L'+')) { parts.push_back(temp); // If we have > 4, something's wrong. if (parts.size() > MAX_CHORD_PARTS) { throw winrt::hresult_invalid_argument(); } } KeyModifiers modifiers = KeyModifiers::None; int32_t vkey = 0; // Look for ctrl, shift, alt. Anything else might be a key for (const auto& part : parts) { std::wstring lowercase = part; std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), std::towlower); if (lowercase == CTRL_KEY) { modifiers |= KeyModifiers::Ctrl; } else if (lowercase == ALT_KEY) { modifiers |= KeyModifiers::Alt; } else if (lowercase == SHIFT_KEY) { modifiers |= KeyModifiers::Shift; } else { bool foundKey = false; // For potential keys, look through the pairs of strings and vkeys if (part.size() == 1) { const wchar_t wch = part.at(0); // Quick lookup: ranges of vkeys that correlate directly to a key. if (wch >= L'0' && wch <= L'9') { vkey = static_cast(wch); foundKey = true; } else if (wch >= L'a' && wch <= L'z') { // subtract 0x20 to shift to uppercase vkey = static_cast(wch - 0x20); foundKey = true; } else if (wch >= L'A' && wch <= L'Z') { vkey = static_cast(wch); foundKey = true; } } // If we didn't find the key with a quick lookup, search the // table to see if we have a matching name. if (!foundKey) { for (const auto& pair : vkeyNamePairs) { if (pair.second == part) { vkey = pair.first; foundKey = true; break; } } } // If we weren't able to find a match, throw an exception. if (!foundKey) { throw winrt::hresult_invalid_argument(); } } } return winrt::Microsoft::Terminal::Settings::KeyChord{ modifiers, vkey }; } // Function Description: // - Serialize this keychord into a string represenation. // - The string will fit the format "[ctrl+][alt+][shift+]", // 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. winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) { bool serializedSuccessfully = false; const auto modifiers = chord.Modifiers(); const auto vkey = chord.Vkey(); std::wstring buffer{ L"" }; // Add modifiers if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl)) { buffer += CTRL_KEY; buffer += L"+"; } if (WI_IsFlagSet(modifiers, KeyModifiers::Alt)) { buffer += ALT_KEY; buffer += L"+"; } if (WI_IsFlagSet(modifiers, KeyModifiers::Shift)) { buffer += SHIFT_KEY; buffer += L"+"; } // Quick lookup: ranges of vkeys that correlate directly to a key. if (vkey >= L'0' && vkey <= L'9') { buffer += std::wstring(1, static_cast(vkey)); serializedSuccessfully = true; } else if (vkey >= L'A' && vkey <= L'Z') { // add 0x20 to shift to lowercase buffer += std::wstring(1, static_cast(vkey + 0x20)); serializedSuccessfully = true; } else { if (vkeyNamePairs.find(vkey) != vkeyNamePairs.end()) { buffer += vkeyNamePairs.at(vkey); serializedSuccessfully = true; } } if (!serializedSuccessfully) { buffer = L""; } return winrt::hstring{ buffer }; }