diff --git a/src/cascadia/TerminalApp/App.cpp b/src/cascadia/TerminalApp/App.cpp index 6e139d8d0..5c4f80a90 100644 --- a/src/cascadia/TerminalApp/App.cpp +++ b/src/cascadia/TerminalApp/App.cpp @@ -405,7 +405,7 @@ namespace winrt::TerminalApp::implementation // - 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()); diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index d12f33ce4..d9a00d42e 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -1,152 +1,347 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "AppKeyBindings.h" - -using namespace winrt::Microsoft::Terminal; - -namespace winrt::TerminalApp::implementation -{ - void AppKeyBindings::SetKeyBinding(TerminalApp::ShortcutAction const& action, - Settings::KeyChord const& chord) - { - _keyShortcuts[chord] = action; - } - - bool AppKeyBindings::TryKeyChord(Settings::KeyChord const& kc) - { - const auto keyIter = _keyShortcuts.find(kc); - if (keyIter != _keyShortcuts.end()) - { - const auto action = keyIter->second; - return _DoAction(action); - } - return false; - } - - bool AppKeyBindings::_DoAction(ShortcutAction action) - { - switch (action) - { - case ShortcutAction::CopyText: - _CopyTextHandlers(); - return true; - case ShortcutAction::PasteText: - _PasteTextHandlers(); - return true; - case ShortcutAction::NewTab: - _NewTabHandlers(); - return true; - case ShortcutAction::OpenSettings: - _OpenSettingsHandlers(); - return true; - - case ShortcutAction::NewTabProfile0: - _NewTabWithProfileHandlers(0); - return true; - case ShortcutAction::NewTabProfile1: - _NewTabWithProfileHandlers(1); - return true; - case ShortcutAction::NewTabProfile2: - _NewTabWithProfileHandlers(2); - return true; - case ShortcutAction::NewTabProfile3: - _NewTabWithProfileHandlers(3); - return true; - case ShortcutAction::NewTabProfile4: - _NewTabWithProfileHandlers(4); - return true; - case ShortcutAction::NewTabProfile5: - _NewTabWithProfileHandlers(5); - return true; - case ShortcutAction::NewTabProfile6: - _NewTabWithProfileHandlers(6); - return true; - case ShortcutAction::NewTabProfile7: - _NewTabWithProfileHandlers(7); - return true; - case ShortcutAction::NewTabProfile8: - _NewTabWithProfileHandlers(8); - return true; - - case ShortcutAction::NewWindow: - _NewWindowHandlers(); - return true; - case ShortcutAction::CloseWindow: - _CloseWindowHandlers(); - return true; - case ShortcutAction::CloseTab: - _CloseTabHandlers(); - return true; - - case ShortcutAction::ScrollUp: - _ScrollUpHandlers(); - return true; - case ShortcutAction::ScrollDown: - _ScrollDownHandlers(); - return true; - case ShortcutAction::ScrollUpPage: - _ScrollUpPageHandlers(); - return true; - case ShortcutAction::ScrollDownPage: - _ScrollDownPageHandlers(); - return true; - case ShortcutAction::NextTab: - _NextTabHandlers(); - return true; - case ShortcutAction::PrevTab: - _PrevTabHandlers(); - return true; - case ShortcutAction::SwitchToTab0: - _SwitchToTabHandlers(0); - return true; - case ShortcutAction::SwitchToTab1: - _SwitchToTabHandlers(1); - return true; - case ShortcutAction::SwitchToTab2: - _SwitchToTabHandlers(2); - return true; - case ShortcutAction::SwitchToTab3: - _SwitchToTabHandlers(3); - return true; - case ShortcutAction::SwitchToTab4: - _SwitchToTabHandlers(4); - return true; - case ShortcutAction::SwitchToTab5: - _SwitchToTabHandlers(5); - return true; - case ShortcutAction::SwitchToTab6: - _SwitchToTabHandlers(6); - return true; - case ShortcutAction::SwitchToTab7: - _SwitchToTabHandlers(7); - return true; - case ShortcutAction::SwitchToTab8: - _SwitchToTabHandlers(8); - return true; - } - return false; - } - - // -------------------------------- Events --------------------------------- - DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs); - DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs); - DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs); - DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs); - DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs); - DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs); - DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs); - DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs); - DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs); - DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs); - DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs); - DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs); - DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs); - DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs); - - -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "AppKeyBindings.h" +#include "KeyChordSerialization.h" + +using namespace winrt::Microsoft::Terminal; +using namespace winrt::TerminalApp; +using namespace winrt::Windows::Data::Json; + +static constexpr std::wstring_view KEYS_KEY{ L"keys" }; +static constexpr std::wstring_view COMMAND_KEY{ L"command" }; + +static constexpr std::wstring_view COPYTEXT_KEY{ L"copy" }; +static constexpr std::wstring_view PASTETEXT_KEY{ L"paste" }; +static constexpr std::wstring_view NEWTAB_KEY{ L"newTab" }; +static constexpr std::wstring_view NEWTABWITHPROFILE0_KEY{ L"newTabProfile0" }; +static constexpr std::wstring_view NEWTABWITHPROFILE1_KEY{ L"newTabProfile1" }; +static constexpr std::wstring_view NEWTABWITHPROFILE2_KEY{ L"newTabProfile2" }; +static constexpr std::wstring_view NEWTABWITHPROFILE3_KEY{ L"newTabProfile3" }; +static constexpr std::wstring_view NEWTABWITHPROFILE4_KEY{ L"newTabProfile4" }; +static constexpr std::wstring_view NEWTABWITHPROFILE5_KEY{ L"newTabProfile5" }; +static constexpr std::wstring_view NEWTABWITHPROFILE6_KEY{ L"newTabProfile6" }; +static constexpr std::wstring_view NEWTABWITHPROFILE7_KEY{ L"newTabProfile7" }; +static constexpr std::wstring_view NEWTABWITHPROFILE8_KEY{ L"newTabProfile8" }; +static constexpr std::wstring_view NEWWINDOW_KEY{ L"newWindow" }; +static constexpr std::wstring_view CLOSEWINDOW_KEY{ L"closeWindow" }; +static constexpr std::wstring_view CLOSETAB_KEY{ L"closeTab" }; +static constexpr std::wstring_view SWITCHTOTAB_KEY{ L"switchToTab" }; +static constexpr std::wstring_view NEXTTAB_KEY{ L"nextTab" }; +static constexpr std::wstring_view PREVTAB_KEY{ L"prevTab" }; +static constexpr std::wstring_view INCREASEFONTSIZE_KEY{ L"increaseFontSize" }; +static constexpr std::wstring_view DECREASEFONTSIZE_KEY{ L"decreaseFontSize" }; +static constexpr std::wstring_view SCROLLUP_KEY{ L"scrollUp" }; +static constexpr std::wstring_view SCROLLDOWN_KEY{ L"scrollDown" }; +static constexpr std::wstring_view SCROLLUPPAGE_KEY{ L"scrollUpPage" }; +static constexpr std::wstring_view SCROLLDOWNPAGE_KEY{ L"scrollDownPage" }; +static constexpr std::wstring_view SWITCHTOTAB0_KEY{ L"switchToTab0" }; +static constexpr std::wstring_view SWITCHTOTAB1_KEY{ L"switchToTab1" }; +static constexpr std::wstring_view SWITCHTOTAB2_KEY{ L"switchToTab2" }; +static constexpr std::wstring_view SWITCHTOTAB3_KEY{ L"switchToTab3" }; +static constexpr std::wstring_view SWITCHTOTAB4_KEY{ L"switchToTab4" }; +static constexpr std::wstring_view SWITCHTOTAB5_KEY{ L"switchToTab5" }; +static constexpr std::wstring_view SWITCHTOTAB6_KEY{ L"switchToTab6" }; +static constexpr std::wstring_view SWITCHTOTAB7_KEY{ L"switchToTab7" }; +static constexpr std::wstring_view SWITCHTOTAB8_KEY{ L"switchToTab8" }; +static constexpr std::wstring_view OPENSETTINGS_KEY{ L"openSettings" }; + +// Specifically use a map here over an unordered_map. We want to be able to +// iterate over these entries in-order when we're serializing the keybindings. +static const std::map commandNames { + { COPYTEXT_KEY, ShortcutAction::CopyText }, + { PASTETEXT_KEY, ShortcutAction::PasteText }, + { NEWTAB_KEY, ShortcutAction::NewTab }, + { NEWTABWITHPROFILE0_KEY, ShortcutAction::NewTabProfile0 }, + { NEWTABWITHPROFILE1_KEY, ShortcutAction::NewTabProfile1 }, + { NEWTABWITHPROFILE2_KEY, ShortcutAction::NewTabProfile2 }, + { NEWTABWITHPROFILE3_KEY, ShortcutAction::NewTabProfile3 }, + { NEWTABWITHPROFILE4_KEY, ShortcutAction::NewTabProfile4 }, + { NEWTABWITHPROFILE5_KEY, ShortcutAction::NewTabProfile5 }, + { NEWTABWITHPROFILE6_KEY, ShortcutAction::NewTabProfile6 }, + { NEWTABWITHPROFILE7_KEY, ShortcutAction::NewTabProfile7 }, + { NEWTABWITHPROFILE8_KEY, ShortcutAction::NewTabProfile8 }, + { NEWWINDOW_KEY, ShortcutAction::NewWindow }, + { CLOSEWINDOW_KEY, ShortcutAction::CloseWindow }, + { CLOSETAB_KEY, ShortcutAction::CloseTab }, + { NEXTTAB_KEY, ShortcutAction::NextTab }, + { PREVTAB_KEY, ShortcutAction::PrevTab }, + { INCREASEFONTSIZE_KEY, ShortcutAction::IncreaseFontSize }, + { DECREASEFONTSIZE_KEY, ShortcutAction::DecreaseFontSize }, + { SCROLLUP_KEY, ShortcutAction::ScrollUp }, + { SCROLLDOWN_KEY, ShortcutAction::ScrollDown }, + { SCROLLUPPAGE_KEY, ShortcutAction::ScrollUpPage }, + { SCROLLDOWNPAGE_KEY, ShortcutAction::ScrollDownPage }, + { SWITCHTOTAB0_KEY, ShortcutAction::SwitchToTab0 }, + { SWITCHTOTAB1_KEY, ShortcutAction::SwitchToTab1 }, + { SWITCHTOTAB2_KEY, ShortcutAction::SwitchToTab2 }, + { SWITCHTOTAB3_KEY, ShortcutAction::SwitchToTab3 }, + { SWITCHTOTAB4_KEY, ShortcutAction::SwitchToTab4 }, + { SWITCHTOTAB5_KEY, ShortcutAction::SwitchToTab5 }, + { SWITCHTOTAB6_KEY, ShortcutAction::SwitchToTab6 }, + { SWITCHTOTAB7_KEY, ShortcutAction::SwitchToTab7 }, + { SWITCHTOTAB8_KEY, ShortcutAction::SwitchToTab8 }, +}; + +namespace winrt::TerminalApp::implementation +{ + void AppKeyBindings::SetKeyBinding(const TerminalApp::ShortcutAction& action, + const Settings::KeyChord& chord) + { + _keyShortcuts[chord] = action; + } + + bool AppKeyBindings::TryKeyChord(const Settings::KeyChord& kc) + { + const auto keyIter = _keyShortcuts.find(kc); + if (keyIter != _keyShortcuts.end()) + { + const auto action = keyIter->second; + return _DoAction(action); + } + return false; + } + + bool AppKeyBindings::_DoAction(ShortcutAction action) + { + switch (action) + { + case ShortcutAction::CopyText: + _CopyTextHandlers(); + return true; + case ShortcutAction::PasteText: + _PasteTextHandlers(); + return true; + case ShortcutAction::NewTab: + _NewTabHandlers(); + return true; + case ShortcutAction::OpenSettings: + _OpenSettingsHandlers(); + return true; + + case ShortcutAction::NewTabProfile0: + _NewTabWithProfileHandlers(0); + return true; + case ShortcutAction::NewTabProfile1: + _NewTabWithProfileHandlers(1); + return true; + case ShortcutAction::NewTabProfile2: + _NewTabWithProfileHandlers(2); + return true; + case ShortcutAction::NewTabProfile3: + _NewTabWithProfileHandlers(3); + return true; + case ShortcutAction::NewTabProfile4: + _NewTabWithProfileHandlers(4); + return true; + case ShortcutAction::NewTabProfile5: + _NewTabWithProfileHandlers(5); + return true; + case ShortcutAction::NewTabProfile6: + _NewTabWithProfileHandlers(6); + return true; + case ShortcutAction::NewTabProfile7: + _NewTabWithProfileHandlers(7); + return true; + case ShortcutAction::NewTabProfile8: + _NewTabWithProfileHandlers(8); + return true; + + case ShortcutAction::NewWindow: + _NewWindowHandlers(); + return true; + case ShortcutAction::CloseWindow: + _CloseWindowHandlers(); + return true; + case ShortcutAction::CloseTab: + _CloseTabHandlers(); + return true; + + case ShortcutAction::ScrollUp: + _ScrollUpHandlers(); + return true; + case ShortcutAction::ScrollDown: + _ScrollDownHandlers(); + return true; + case ShortcutAction::ScrollUpPage: + _ScrollUpPageHandlers(); + return true; + case ShortcutAction::ScrollDownPage: + _ScrollDownPageHandlers(); + return true; + + case ShortcutAction::NextTab: + _NextTabHandlers(); + return true; + case ShortcutAction::PrevTab: + _PrevTabHandlers(); + return true; + + case ShortcutAction::SwitchToTab0: + _SwitchToTabHandlers(0); + return true; + case ShortcutAction::SwitchToTab1: + _SwitchToTabHandlers(1); + return true; + case ShortcutAction::SwitchToTab2: + _SwitchToTabHandlers(2); + return true; + case ShortcutAction::SwitchToTab3: + _SwitchToTabHandlers(3); + return true; + case ShortcutAction::SwitchToTab4: + _SwitchToTabHandlers(4); + return true; + case ShortcutAction::SwitchToTab5: + _SwitchToTabHandlers(5); + return true; + case ShortcutAction::SwitchToTab6: + _SwitchToTabHandlers(6); + return true; + case ShortcutAction::SwitchToTab7: + _SwitchToTabHandlers(7); + return true; + case ShortcutAction::SwitchToTab8: + _SwitchToTabHandlers(8); + return true; + + default: + return false; + } + return false; + } + + // -------------------------------- Events --------------------------------- + DEFINE_EVENT(AppKeyBindings, CopyText, _CopyTextHandlers, TerminalApp::CopyTextEventArgs); + DEFINE_EVENT(AppKeyBindings, PasteText, _PasteTextHandlers, TerminalApp::PasteTextEventArgs); + DEFINE_EVENT(AppKeyBindings, NewTab, _NewTabHandlers, TerminalApp::NewTabEventArgs); + DEFINE_EVENT(AppKeyBindings, NewTabWithProfile, _NewTabWithProfileHandlers, TerminalApp::NewTabWithProfileEventArgs); + DEFINE_EVENT(AppKeyBindings, NewWindow, _NewWindowHandlers, TerminalApp::NewWindowEventArgs); + DEFINE_EVENT(AppKeyBindings, CloseWindow, _CloseWindowHandlers, TerminalApp::CloseWindowEventArgs); + DEFINE_EVENT(AppKeyBindings, CloseTab, _CloseTabHandlers, TerminalApp::CloseTabEventArgs); + DEFINE_EVENT(AppKeyBindings, SwitchToTab, _SwitchToTabHandlers, TerminalApp::SwitchToTabEventArgs); + DEFINE_EVENT(AppKeyBindings, NextTab, _NextTabHandlers, TerminalApp::NextTabEventArgs); + DEFINE_EVENT(AppKeyBindings, PrevTab, _PrevTabHandlers, TerminalApp::PrevTabEventArgs); + DEFINE_EVENT(AppKeyBindings, IncreaseFontSize, _IncreaseFontSizeHandlers, TerminalApp::IncreaseFontSizeEventArgs); + DEFINE_EVENT(AppKeyBindings, DecreaseFontSize, _DecreaseFontSizeHandlers, TerminalApp::DecreaseFontSizeEventArgs); + DEFINE_EVENT(AppKeyBindings, ScrollUp, _ScrollUpHandlers, TerminalApp::ScrollUpEventArgs); + DEFINE_EVENT(AppKeyBindings, ScrollDown, _ScrollDownHandlers, TerminalApp::ScrollDownEventArgs); + DEFINE_EVENT(AppKeyBindings, ScrollUpPage, _ScrollUpPageHandlers, TerminalApp::ScrollUpPageEventArgs); + DEFINE_EVENT(AppKeyBindings, ScrollDownPage, _ScrollDownPageHandlers, TerminalApp::ScrollDownPageEventArgs); + DEFINE_EVENT(AppKeyBindings, OpenSettings, _OpenSettingsHandlers, TerminalApp::OpenSettingsEventArgs); + + // Method Description: + // - Deserialize an AppKeyBindings from the key mappings that are in the + // array `json`. The json array should contain an array of objects with + // both a `command` string and a `keys` array, where `command` is one of + // the names listed in `commandNames`, and `keys` is an array of + // keypresses. Currently, the array should contain a single string, which + // can be deserialized into a KeyChord. + // Arguments: + // - json: and array of JsonObject's to deserialize into our _keyShortcuts mapping. + // Return Value: + // - the newly constructed AppKeyBindings object. + TerminalApp::AppKeyBindings AppKeyBindings::FromJson(const JsonArray& json) + { + TerminalApp::AppKeyBindings newBindings{}; + + for (const auto& value : json) + { + if (value.ValueType() == JsonValueType::Object) + { + JsonObject obj = value.GetObjectW(); + if (obj.HasKey(COMMAND_KEY) && obj.HasKey(KEYS_KEY)) + { + const auto commandString = obj.GetNamedString(COMMAND_KEY); + const auto keys = obj.GetNamedArray(KEYS_KEY); + if (keys.Size() != 1) + { + continue; + } + const auto keyChordString = keys.GetAt(0).GetString(); + ShortcutAction action; + + // Try matching the command to one we have + auto found = commandNames.find(commandString); + if (found != commandNames.end()) + { + action = found->second; + } + else + { + continue; + } + + // Try parsing the chord + try + { + auto chord = KeyChordSerialization::FromString(keyChordString); + newBindings.SetKeyBinding(action, chord); + } + catch (...) + { + continue; + } + } + } + } + return newBindings; + } + + // Function Description: + // - Small helper to insert a given KeyChord, ShortcutAction pair into the + // given json array + // Arguments: + // - bindingsArray: The JsonArray to insert the object into. + // - chord: The KeyChord to serailize and place in the json array + // - actionName: the name of the ShortcutAction to use with this KeyChord + static void _AddShortcutToJsonArray(const JsonArray& bindingsArray, + const Settings::KeyChord& chord, + const std::wstring_view& actionName) + { + const auto keyString = KeyChordSerialization::ToString(chord); + if (keyString == L"") + { + return; + } + + winrt::Windows::Data::Json::JsonObject jsonObject; + winrt::Windows::Data::Json::JsonArray keysArray; + keysArray.Append(JsonValue::CreateStringValue(keyString)); + jsonObject.Insert(KEYS_KEY, keysArray); + jsonObject.Insert(COMMAND_KEY, JsonValue::CreateStringValue(actionName)); + + bindingsArray.Append(jsonObject); + } + + // Method Description: + // - Serialize this AppKeyBindings to a json array of objects. Each object + // in the array represents a single keybinding, mapping a KeyChord to a + // ShortcutAction. + // Return Value: + // - a JsonArray which is an equivalent serialization of this object. + Windows::Data::Json::JsonArray AppKeyBindings::ToJson() + { + winrt::Windows::Data::Json::JsonArray bindingsArray; + + // Iterate over all the possible actions in the names list, and see if + // it has a binding. + for (auto& actionName : commandNames) + { + const auto searchedForName = actionName.first; + const auto searchedForAction = actionName.second; + for (const auto& kv : _keyShortcuts) + { + const auto chord = kv.first; + const auto command = kv.second; + if (command == searchedForAction) + { + _AddShortcutToJsonArray(bindingsArray, chord, searchedForName); + } + } + } + + return bindingsArray; + } +} diff --git a/src/cascadia/TerminalApp/AppKeyBindings.h b/src/cascadia/TerminalApp/AppKeyBindings.h index f739c40ad..d3f0290e1 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.h +++ b/src/cascadia/TerminalApp/AppKeyBindings.h @@ -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); diff --git a/src/cascadia/TerminalApp/AppKeyBindings.idl b/src/cascadia/TerminalApp/AppKeyBindings.idl index 7b121cf3a..b2d514479 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.idl +++ b/src/cascadia/TerminalApp/AppKeyBindings.idl @@ -63,6 +63,9 @@ namespace TerminalApp { AppKeyBindings(); + Windows.Data.Json.JsonArray ToJson(); + static AppKeyBindings FromJson(Windows.Data.Json.JsonArray json); + void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord); event CopyTextEventArgs CopyText; diff --git a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp index 25d021b24..156cf7233 100644 --- a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp @@ -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; } @@ -190,9 +193,18 @@ std::unique_ptr 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; } diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp index 6a2d2cd14..3129296c8 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp @@ -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; @@ -154,6 +159,9 @@ JsonObject GlobalAppSettings::ToJson() const jsonObject.Insert(REQUESTED_THEME_KEY, 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; } diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h index ba1f07f75..e091bab04 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.h +++ b/src/cascadia/TerminalApp/GlobalAppSettings.h @@ -35,6 +35,7 @@ public: 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; diff --git a/src/cascadia/TerminalApp/KeyChordSerialization.cpp b/src/cascadia/TerminalApp/KeyChordSerialization.cpp new file mode 100644 index 000000000..20816066a --- /dev/null +++ b/src/cascadia/TerminalApp/KeyChordSerialization.cpp @@ -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 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+]", +// 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 }; +} diff --git a/src/cascadia/TerminalApp/KeyChordSerialization.h b/src/cascadia/TerminalApp/KeyChordSerialization.h new file mode 100644 index 000000000..8f9cf4f26 --- /dev/null +++ b/src/cascadia/TerminalApp/KeyChordSerialization.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include + +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); +}; diff --git a/src/cascadia/TerminalApp/TerminalApp.vcxproj b/src/cascadia/TerminalApp/TerminalApp.vcxproj index 6a70d00dd..0aa7e8fb1 100644 --- a/src/cascadia/TerminalApp/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/TerminalApp.vcxproj @@ -41,6 +41,7 @@ + AppKeyBindings.idl @@ -58,6 +59,7 @@ + Create @@ -128,4 +130,4 @@ - \ No newline at end of file +