Repository: microsoft/terminal (language: cpp)

Issue:
Add support for DECBKM (Backarrow Key Mode)
# Description of the new feature/enhancement

Backarrow Key mode controls whether the backspace key should produces a `BS` or `DEL` control sequence. This was a mode supported by the VT320 and later terminals.

I initially thought this would be useful for people that were complaining about the backspace key not sending what they expected it to, but having reread some of those issues I don't think they're actually using the VT input mode, so I'm having my doubts about that now.

Someone might still find it useful, though, and if nothing else it improves our VT conformance, so I'm happy to create a PR for it if you want it.

# Proposed technical implementation details (optional)

I've currently implemented it as another section in the `TerminalInput::HandleKey` method, treating `VK_BACK` (and the various modifier combinations) as a special case. Previously the `VK_BACK` key was handled in a number of different mapping tables.

This would also fix the `Alt`+`Shift` combos which aren't currently correct. 


Discussion / hints:

Fix patch:
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index b5f048bc3c2..0c94916712e 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -496,6 +496,7 @@ DECALN
 DECANM
 DECAUPSS
 DECAWM
+DECBKM
 DECCKM
 DECCOLM
 DECCRA
diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp
index afa1de0f797..5a0bf610eb6 100644
--- a/src/terminal/adapter/DispatchTypes.hpp
+++ b/src/terminal/adapter/DispatchTypes.hpp
@@ -384,6 +384,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
         ATT610_StartCursorBlink = DECPrivateMode(12),
         DECTCEM_TextCursorEnableMode = DECPrivateMode(25),
         XTERM_EnableDECCOLMSupport = DECPrivateMode(40),
+        DECBKM_BackarrowKeyMode = DECPrivateMode(67),
         VT200_MOUSE_MODE = DECPrivateMode(1000),
         BUTTON_EVENT_MOUSE_MODE = DECPrivateMode(1002),
         ANY_EVENT_MOUSE_MODE = DECPrivateMode(1003),
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index 549d4b3f366..400096db064 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -978,23 +978,7 @@ bool AdaptDispatch::_SetInputMode(const TerminalInput::Mode mode, const bool ena
     // impact on the actual connected terminal. We can't remove this check,
     // because SSH <=7.7 is out in the wild on all versions of Windows <=2004.
 
-    // GH#12799 - If the app requested that we disable focus events, DON'T pass
-    // that through. ConPTY would _always_ like to know about focus events.
-
-    return !_api.IsConsolePty() ||
-           !_api.IsVtInputEnabled() ||
-           (!enable && mode == TerminalInput::Mode::FocusEvent);
-
-    // Another way of writing the above statement is:
-    //
-    // const bool inConpty = _api.IsConsolePty();
-    // const bool shouldPassthrough = inConpty && _api.IsVtInputEnabled();
-    // const bool disabledFocusEvents = inConpty && (!enable && mode == TerminalInput::Mode::FocusEvent);
-    // return !shouldPassthrough || disabledFocusEvents;
-    //
-    // It's like a "filter" left to right. Due to the early return via
-    // !IsConsolePty, once you're at the !enable part, IsConsolePty can only be
-    // true anymore.
+    return !_api.IsConsolePty() || !_api.IsVtInputEnabled();
 }
 
 // Routine Description:
@@ -1038,6 +1022,9 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
     case DispatchTypes::ModeParams::XTERM_EnableDECCOLMSupport:
         success = EnableDECCOLMSupport(enable);
         break;
+    case DispatchTypes::ModeParams::DECBKM_BackarrowKeyMode:
+        success = _SetInputMode(TerminalInput::Mode::BackarrowKey, enable);
+        break;
     case DispatchTypes::ModeParams::VT200_MOUSE_MODE:
         success = EnableVT200MouseMode(enable);
         break;
@@ -1874,6 +1861,9 @@ bool AdaptDispatch::HardReset()
     EnableSGRExtendedMouseMode(false);
     EnableAnyEventMouseMode(false);
 
+    // Reset the Backarrow Key mode
+    _SetInputMode(TerminalInput::Mode::BackarrowKey, false);
+
     // Delete all current tab stops and reapply
     _ResetTabStops();
 
@@ -2088,7 +2078,9 @@ bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
 // - True if handled successfully. False otherwise.
 bool AdaptDispatch::EnableFocusEventMode(const bool enabled)
 {
-    return _SetInputMode(TerminalInput::Mode::FocusEvent, enabled);
+    // GH#12799 - If the app requested that we disable focus events, DON'T pass
+    // that through. ConPTY would _always_ like to know about focus events.
+    return _SetInputMode(TerminalInput::Mode::FocusEvent, enabled) || !enabled;
 }
 
 //Routine Description:
diff --git a/src/terminal/adapter/ut_adapter/inputTest.cpp b/src/terminal/adapter/ut_adapter/inputTest.cpp
index ace7de68019..f1c9bf2698b 100644
--- a/src/terminal/adapter/ut_adapter/inputTest.cpp
+++ b/src/terminal/adapter/ut_adapter/inputTest.cpp
@@ -43,6 +43,7 @@ class Microsoft::Console::VirtualTerminal::InputTest
     TEST_METHOD(TerminalInputNullKeyTests);
     TEST_METHOD(DifferentModifiersTest);
     TEST_METHOD(CtrlNumTest);
+    TEST_METHOD(BackarrowKeyModeTest);
 
     wchar_t GetModifierChar(const bool fShift, const bool fAlt, const bool fCtrl)
     {
@@ -788,3 +789,65 @@ void InputTest::CtrlNumTest()
     s_expectedInput = L"9";
     TestKey(pInput, uiKeystate, vkey);
 }
+
+void InputTest::BackarrowKeyModeTest()
+{
+    Log::Comment(L"Starting test...");
+
+    const auto pInput = new TerminalInput(s_TerminalInputTestCallback);
+    const BYTE vkey = VK_BACK;
+
+    Log::Comment(L"Sending backspace key combos with DECBKM enabled.");
+    pInput->SetInputMode(TerminalInput::Mode::BackarrowKey, true);
+
+    s_expectedInput = L"\x8";
+    TestKey(pInput, 0, vkey);
+
+    s_expectedInput = L"\x8";
+    TestKey(pInput, SHIFT_PRESSED, vkey);
+
+    s_expectedInput = L"\x7f";
+    TestKey(pInput, LEFT_CTRL_PRESSED, vkey);
+
+    s_expectedInput = L"\x7f";
+    TestKey(pInput, LEFT_CTRL_PRESSED | SHIFT_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x8";
+    TestKey(pInput, LEFT_ALT_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x8";
+    TestKey(pInput, LEFT_ALT_PRESSED | SHIFT_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x7f";
+    TestKey(pInput, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x7f";
+    TestKey(pInput, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED | SHIFT_PRESSED, vkey);
+
+    Log::Comment(L"Sending backspace key combos with DECBKM disabled.");
+    pInput->SetInputMode(TerminalInput::Mode::BackarrowKey, false);
+
+    s_expectedInput = L"\x7f";
+    TestKey(pInput, 0, vkey);
+
+    s_expectedInput = L"\x7f";
+    TestKey(pInput, SHIFT_PRESSED, vkey);
+
+    s_expectedInput = L"\x8";
+    TestKey(pInput, LEFT_CTRL_PRESSED, vkey);
+
+    s_expectedInput = L"\x8";
+    TestKey(pInput, LEFT_CTRL_PRESSED | SHIFT_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x7f";
+    TestKey(pInput, LEFT_ALT_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x7f";
+    TestKey(pInput, LEFT_ALT_PRESSED | SHIFT_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x8";
+    TestKey(pInput, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED, vkey);
+
+    s_expectedInput = L"\x1b\x8";
+    TestKey(pInput, LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED | SHIFT_PRESSED, vkey);
+}
diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp
index 79b5ef74f5f..19f9104eb44 100644
--- a/src/terminal/input/terminalInput.cpp
+++ b/src/terminal/input/terminalInput.cpp
@@ -71,9 +71,8 @@ static constexpr std::array<TermKeyMap, 6> s_cursorKeysVt52Mapping{
     TermKeyMap{ VK_END, L"\033F" },
 };
 
-static constexpr std::array<TermKeyMap, 20> s_keypadNumericMapping{
+static constexpr std::array<TermKeyMap, 19> s_keypadNumericMapping{
     TermKeyMap{ VK_TAB, L"\x09" },
-    TermKeyMap{ VK_BACK, L"\x7f" },
     TermKeyMap{ VK_PAUSE, L"\x1a" },
     TermKeyMap{ VK_ESCAPE, L"\x1b" },
     TermKeyMap{ VK_INSERT, L"\x1b[2~" },
@@ -103,9 +102,8 @@ static constexpr std::array<TermKeyMap, 20> s_keypadNumericMapping{
 //It seems to me as though this was used for early numpad implementations, where presently numlock would enable
 //  "numeric" mode, outputting the numbers on the keys, while "application" mode does things like pgup/down, arrow keys, etc.
 //These keys aren't translated at all in numeric mode, so I figured I'd leave them out of the numeric table.
-static constexpr std::array<TermKeyMap, 20> s_keypadApplicationMapping{
+static constexpr std::array<TermKeyMap, 19> s_keypadApplicationMapping{
     TermKeyMap{ VK_TAB, L"\x09" },
-    TermKeyMap{ VK_BACK, L"\x7f" },
     TermKeyMap{ VK_PAUSE, L"\x1a" },
     TermKeyMap{ VK_ESCAPE, L"\x1b" },
     TermKeyMap{ VK_INSERT, L"\x1b[2~" },
@@ -153,9 +151,8 @@ static constexpr std::array<TermKeyMap, 20> s_keypadApplicationMapping{
     // TermKeyMap{ VK_TAB, L"\x1bOI" },   // So I left them here as a reference just in case.
 };
 
-static constexpr std::array<TermKeyMap, 20> s_keypadVt52Mapping{
+static constexpr std::array<TermKeyMap, 19> s_keypadVt52Mapping{
     TermKeyMap{ VK_TAB, L"\x09" },
-    TermKeyMap{ VK_BACK, L"\x7f" },
     TermKeyMap{ VK_PAUSE, L"\x1a" },
     TermKeyMap{ VK_ESCAPE, L"\x1b" },
     TermKeyMap{ VK_INSERT, L"\x1b[2~" },
@@ -211,10 +208,7 @@ static constexpr std::array<TermKeyMap, 22> s_modifierKeyMapping{
 // These sequences are not later updated to encode the modifier state in the
 //      sequence itself, they are just weird exceptional cases to the general
 //      rules above.
-static constexpr std::array<TermKeyMap, 14> s_simpleModifiedKeyMapping{
-    TermKeyMap{ VK_BACK, CTRL_PRESSED, L"\x8" },
-    TermKeyMap{ VK_BACK, ALT_PRESSED, L"\x1b\x7f" },
-    TermKeyMap{ VK_BACK, CTRL_PRESSED | ALT_PRESSED, L"\x1b\x8" },
+static constexpr std::array<TermKeyMap, 11> s_simpleModifiedKeyMapping{
     TermKeyMap{ VK_TAB, CTRL_PRESSED, L"\t" },
     TermKeyMap{ VK_TAB, SHIFT_PRESSED, L"\x1b[Z" },
     TermKeyMap{ VK_DIVIDE, CTRL_PRESSED, L"\x1F" },
@@ -562,6 +556,25 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
         return false;
     }
 
+    // The VK_BACK key depends on the state of Backarrow Key mode (DECBKM).
+    // If the mode is set, we should send BS. If reset, we should send DEL.
+    if (keyEvent.GetVirtualKeyCode() == VK_BACK)
+    {
+        // The Ctrl modifier reverses the interpretation of DECBKM.
+        const auto backarrowMode = _inputMode.test(Mode::BackarrowKey) != keyEvent.IsCtrlPressed();
+        const auto seq = backarrowMode ? L'\x08' : L'\x7f';
+        // The Alt modifier adds an escape prefix.
+        if (keyEvent.IsAltPressed())
+        {
+            _SendEscapedInputSequence(seq);
+        }
+        else
+        {
+            _SendInputSequence({ &seq, 1 });
+        }
+        return true;
+    }
+
     // Many keyboard layouts have an AltGr key, which makes widely used characters accessible.
     // For instance on a German keyboard layout "[" is written by pressing AltGr+8.
     // Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows.
diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp
index 8f0ac5dcbee..80ff4e8c06f 100644
--- a/src/terminal/input/terminalInput.hpp
+++ b/src/terminal/input/terminalInput.hpp
@@ -41,6 +41,7 @@ namespace Microsoft::Console::VirtualTerminal
             Ansi,
             Keypad,
             CursorKey,
+            BackarrowKey,
             Win32,
 
             Utf8MouseEncoding,


Test patch: