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

Support remapping keybindings #748

Merged
merged 12 commits into from
May 21, 2019
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ namespace winrt::TerminalApp::implementation
// - <none>
void App::_ReloadSettings()
{
_settings = CascadiaSettings::LoadAll();
_settings = CascadiaSettings::LoadAll(false);
// Re-wire the keybindings to their handlers, as we'll have created a
// new AppKeyBindings object.
_HookupKeyBindings(_settings->GetKeybindings());
Expand Down
509 changes: 351 additions & 158 deletions src/cascadia/TerminalApp/AppKeyBindings.cpp

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/AppKeyBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ namespace winrt::TerminalApp::implementation
{
AppKeyBindings() = default;

static TerminalApp::AppKeyBindings FromJson(Windows::Data::Json::JsonArray const& json);
Windows::Data::Json::JsonArray ToJson();

bool TryKeyChord(winrt::Microsoft::Terminal::Settings::KeyChord const& kc);
void SetKeyBinding(TerminalApp::ShortcutAction const& action, winrt::Microsoft::Terminal::Settings::KeyChord const& chord);

Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/AppKeyBindings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ namespace TerminalApp
{
AppKeyBindings();

Windows.Data.Json.JsonArray ToJson();
static AppKeyBindings FromJson(Windows.Data.Json.JsonArray json);
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord);

event CopyTextEventArgs CopyText;
Expand Down
16 changes: 14 additions & 2 deletions src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ JsonObject CascadiaSettings::ToJson() const
jsonObject.Insert(PROFILES_KEY, profilesArray);
jsonObject.Insert(SCHEMES_KEY, schemesArray);

jsonObject.Insert(KEYBINDINGS_KEY,
_globals.GetKeybindings().ToJson());

return jsonObject;
}

Expand Down Expand Up @@ -190,9 +193,18 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::FromJson(JsonObject json)
}
}

// TODO:MSFT:20700157
// Load the keybindings from the file as well
resultPtr->_CreateDefaultKeybindings();
if (json.HasKey(KEYBINDINGS_KEY))
{
const auto keybindingsObj = json.GetNamedArray(KEYBINDINGS_KEY);
auto loadedBindings = AppKeyBindings::FromJson(keybindingsObj);
resultPtr->_globals.SetKeybindings(loadedBindings);
}
else
{
// Create the default keybindings if we couldn't find any keybindings.
resultPtr->_CreateDefaultKeybindings();
}

return resultPtr;
}
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalApp/GlobalAppSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept
return _keybindings;
}

void GlobalAppSettings::SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept
{
_keybindings = newBindings;
}

bool GlobalAppSettings::GetAlwaysShowTabs() const noexcept
{
return _alwaysShowTabs;
Expand Down Expand Up @@ -153,6 +158,9 @@ JsonObject GlobalAppSettings::ToJson() const
JsonValue::CreateStringValue(_SerializeTheme(_requestedTheme)));
}

// We'll add the keybindings later in CascadiaSettings, because if we do it
// here, they'll appear before the profiles.

return jsonObject;
}

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/GlobalAppSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TerminalApp::GlobalAppSettings final
GUID GetDefaultProfile() const noexcept;

winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept;
void SetKeybindings(winrt::TerminalApp::AppKeyBindings newBindings) noexcept;

bool GetAlwaysShowTabs() const noexcept;
void SetAlwaysShowTabs(const bool showTabs) noexcept;
Expand Down
249 changes: 249 additions & 0 deletions src/cascadia/TerminalApp/KeyChordSerialization.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// 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;

static const std::unordered_map<int32_t, std::wstring_view> 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
};

// 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
winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr)
{
std::wstring wstr{ hstr };

// Split the string on '+'
std::wstring temp;
std::vector<std::wstring> 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[0];
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
// Quick lookup: ranges of vkeys that correlate directly to a key.
if (wch >= L'0' && wch <= L'9')
{
vkey = static_cast<int32_t>(wch);
foundKey = true;
}
else if (wch >= L'a' && wch <= L'z')
{
// subtract 0x20 to shift to uppercase
vkey = static_cast<int32_t>(wch - 0x20);
foundKey = true;
}
else if (wch >= L'A' && wch <= L'Z')
{
vkey = static_cast<int32_t>(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+]<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.
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<wchar_t>(vkey));
serializedSuccessfully = true;
}
else if (vkey >= L'A' && vkey <= L'Z')
{
// add 0x20 to shift to lowercase
buffer += std::wstring(1, static_cast<wchar_t>(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 };
}
12 changes: 12 additions & 0 deletions src/cascadia/TerminalApp/KeyChordSerialization.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once
#include <winrt/Microsoft.Terminal.Settings.h>

class KeyChordSerialization final
{
public:
static winrt::Microsoft::Terminal::Settings::KeyChord FromString(const winrt::hstring& str);
static winrt::hstring ToString(const winrt::Microsoft::Terminal::Settings::KeyChord& chord);
};
4 changes: 3 additions & 1 deletion src/cascadia/TerminalApp/TerminalApp.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<ClInclude Include="GlobalAppSettings.h" />
<ClInclude Include="Profile.h" />
<ClInclude Include="CascadiaSettings.h" />
<ClInclude Include="KeyChordSerialization.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="AppKeyBindings.h">
<DependentUpon>AppKeyBindings.idl</DependentUpon>
Expand All @@ -58,6 +59,7 @@
<ClCompile Include="Profile.cpp" />
<ClCompile Include="CascadiaSettings.cpp" />
<ClCompile Include="CascadiaSettingsSerialization.cpp" />
<ClCompile Include="KeyChordSerialization.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
Expand Down Expand Up @@ -128,4 +130,4 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.1.190405001-prerelease\build\native\Microsoft.UI.Xaml.targets'))" />
</Target>
</Project>
</Project>