Skip to content

Commit

Permalink
Add support for win32-input-mode to conhost, ConPTY, Terminal (#6309)
Browse files Browse the repository at this point in the history
Adds support for `win32-input-mode` to conhost, conpty, and the Windows
Terminal.

* The shared `terminalInput` class supports sending these sequences when
  a VT client application requests this mode.
* ConPTY supports synthesizing `INPUT_RECORD`s from the input sent to it
  from a terminal
* ConPTY requests this mode immediately on startup (if started with a
  new flag, `PSEUDOCONSOLE_WIN32_INPUT_MODE`)
* The Terminal now supports sending this input as well, when conpty asks
  for it.

Also adds a new ConPTY flag `PSEUDOCONSOLE_WIN32_INPUT_MODE` which
requests this functionality from conpty, and the Terminal requests this
by default.

Also adds `experimental.input.forceVT` as a global setting to let a user
opt-out of this behavior, if they don't want it / this ends up breaking
horribly.

## Validation Steps Performed
* played with this mode in vtpipeterm
* played with this mode in Terminal
* checked a bunch of scenarios, as outlined in a [comment] on #4999

[comment]: #4999 (comment)

References #4999: The megathread
References #5887: The spec

Closes #879
Closes #2865
Closes #530 
Closes #3079
Closes #1119
Closes #1694 
Closes #3608 
Closes #4334
Closes #4446
  • Loading branch information
zadjii-msft authored Jun 8, 2020
1 parent 55d8df4 commit f327618
Show file tree
Hide file tree
Showing 55 changed files with 619 additions and 66 deletions.
8 changes: 4 additions & 4 deletions src/cascadia/PublicTerminalCore/HwndTerminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,11 +557,11 @@ catch (...)
return false;
}

void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode) noexcept
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept
try
{
const auto flags = getControlKeyState();
_terminal->SendKeyEvent(vkey, scanCode, flags);
_terminal->SendKeyEvent(vkey, scanCode, flags, keyDown);
}
CATCH_LOG();

Expand Down Expand Up @@ -590,10 +590,10 @@ try
}
CATCH_LOG();

void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode)
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown)
{
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
publicTerminal->_SendKeyEvent(vkey, scanCode);
publicTerminal->_SendKeyEvent(vkey, scanCode, keyDown);
}

void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
Expand Down
6 changes: 3 additions & 3 deletions src/cascadia/PublicTerminalCore/HwndTerminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
Expand Down Expand Up @@ -92,7 +92,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode);
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal);
Expand All @@ -115,7 +115,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
bool _CanSendVTMouseInput() const noexcept;
bool _SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;

void _SendKeyEvent(WORD vkey, WORD scanCode) noexcept;
void _SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept;
void _SendCharEvent(wchar_t ch, WORD scanCode) noexcept;

// Inherited via IControlAccessibilityInfo
Expand Down
5 changes: 4 additions & 1 deletion src/cascadia/TerminalApp/GlobalAppSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
static constexpr std::string_view LaunchModeKey{ "launchMode" };
static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" };
static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };

static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };

static constexpr std::string_view ForceFullRepaintRenderingKey{ "experimental.rendering.forceFullRepaint" };
static constexpr std::string_view SoftwareRenderingKey{ "experimental.rendering.software" };
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
static constexpr std::string_view ForceVTInputKey{ "experimental.input.forceVT" };

// Launch mode values
static constexpr std::wstring_view DefaultLaunchModeValue{ L"default" };
Expand Down Expand Up @@ -130,6 +131,7 @@ void GlobalAppSettings::ApplyToSettings(TerminalSettings& settings) const noexce
settings.CopyOnSelect(_CopyOnSelect);
settings.ForceFullRepaintRendering(_ForceFullRepaintRendering);
settings.SoftwareRendering(_SoftwareRendering);
settings.ForceVTInput(_ForceVTInput);
}

// Method Description:
Expand Down Expand Up @@ -220,6 +222,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);

JsonUtils::GetBool(json, SoftwareRenderingKey, _SoftwareRendering);
JsonUtils::GetBool(json, ForceVTInputKey, _ForceVTInput);

// GetBool will only override the current value if the key exists
JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
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 @@ -77,6 +77,7 @@ class TerminalApp::GlobalAppSettings final
GETSET_PROPERTY(bool, SnapToGridOnResize, true);
GETSET_PROPERTY(bool, ForceFullRepaintRendering, false);
GETSET_PROPERTY(bool, SoftwareRendering, false);
GETSET_PROPERTY(bool, ForceVTInput, false);
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor

GETSET_PROPERTY(bool, StartOnUserLogin, false);
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalConnection/ConptyConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
try
{
const COORD dimensions{ gsl::narrow_cast<SHORT>(_initialCols), gsl::narrow_cast<SHORT>(_initialRows) };
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, PSEUDOCONSOLE_RESIZE_QUIRK | PSEUDOCONSOLE_WIN32_INPUT_MODE, &_inPipe, &_outPipe, &_hPC));
THROW_IF_FAILED(_LaunchAttachedClient());

_startTime = std::chrono::high_resolution_clock::now();
Expand Down
60 changes: 43 additions & 17 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (!handled)
{
// _TrySendKeyEvent pretends it didn't handle F7 for some unknown reason.
(void)_TrySendKeyEvent(VK_F7, 0, modifiers);
(void)_TrySendKeyEvent(VK_F7, 0, modifiers, true);
(void)_TrySendKeyEvent(VK_F7, 0, modifiers, false);
handled = true;
}

Expand All @@ -714,6 +715,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

void TermControl::_KeyDownHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::KeyRoutedEventArgs const& e)
{
_KeyHandler(e, true);
}

void TermControl::_KeyUpHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::KeyRoutedEventArgs const& e)
{
_KeyHandler(e, false);
}

void TermControl::_KeyHandler(Input::KeyRoutedEventArgs const& e, const bool keyDown)
{
// If the current focused element is a child element of searchbox,
// we do not send this event up to terminal
Expand All @@ -722,14 +734,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
return;
}

// mark event as handled and do nothing if...
// - closing
// - key modifier is pressed
// NOTE: for key combos like CTRL + C, two events are fired (one for CTRL, one for 'C'). We care about the 'C' event and then check for key modifiers below.
// Mark the event as handled and do nothing if we're closing, or the key
// was the Windows key.
//
// NOTE: for key combos like CTRL + C, two events are fired (one for
// CTRL, one for 'C'). Since it's possible the terminal is in
// win32-input-mode, then we'll send all these keystrokes to the
// terminal - it's smart enough to ignore the keys it doesn't care
// about.
if (_closing ||
e.OriginalKey() == VirtualKey::Control ||
e.OriginalKey() == VirtualKey::Shift ||
e.OriginalKey() == VirtualKey::Menu ||
e.OriginalKey() == VirtualKey::LeftWindows ||
e.OriginalKey() == VirtualKey::RightWindows)

Expand All @@ -743,19 +756,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
bool handled = false;

// Alt-Numpad# input will send us a character once the user releases Alt, so we should be ignoring the individual keydowns.
// The character will be sent through the TSFInputControl.
// See GH#1401 for more details
if (modifiers.IsAltPressed() && (e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))
// Alt-Numpad# input will send us a character once the user releases
// Alt, so we should be ignoring the individual keydowns. The character
// will be sent through the TSFInputControl. See GH#1401 for more
// details
if (modifiers.IsAltPressed() &&
(e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))

{
e.Handled(true);
return;
}

// GH#2235: Terminal::Settings hasn't been modified to differentiate between AltGr and Ctrl+Alt yet.
// GH#2235: Terminal::Settings hasn't been modified to differentiate
// between AltGr and Ctrl+Alt yet.
// -> Don't check for key bindings if this is an AltGr key combination.
if (!modifiers.IsAltGrPressed())
//
// GH#4999: Only process keybindings on the keydown. If we don't check
// this at all, we'll process the keybinding twice. If we only process
// keybindings on the keyUp, then we'll still send the keydown to the
// connected terminal application, and something like ctrl+shift+T will
// emit a ^T to the pipe.
if (!modifiers.IsAltGrPressed() && keyDown)
{
auto bindings = _settings.KeyBindings();
if (bindings)
Expand All @@ -771,7 +793,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

if (!handled)
{
handled = _TrySendKeyEvent(vkey, scanCode, modifiers);
handled = _TrySendKeyEvent(vkey, scanCode, modifiers, keyDown);
}

// Manually prevent keyboard navigation with tab. We want to send tab to
Expand All @@ -793,7 +815,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Arguments:
// - vkey: The vkey of the key pressed.
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
bool TermControl::_TrySendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates modifiers)
// - keyDown: If true, the key was pressed, otherwise the key was released.
bool TermControl::_TrySendKeyEvent(const WORD vkey,
const WORD scanCode,
const ControlKeyStates modifiers,
const bool keyDown)
{
// When there is a selection active, escape should clear it and NOT flow through
// to the terminal. With any other keypress, it should clear the selection AND
Expand All @@ -818,7 +844,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// If the terminal translated the key, mark the event as handled.
// This will prevent the system from trying to get the character out
// of it and sending us a CharacterReceived event.
const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers) : true;
const auto handled = vkey ? _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown) : true;

if (_cursorTimer.has_value())
{
Expand Down
4 changes: 3 additions & 1 deletion src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _SetFontSize(int fontSize);
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _KeyUpHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
void _PointerMovedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
Expand Down Expand Up @@ -218,8 +219,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _ScrollbarUpdater(Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollbar, const int viewTop, const int viewHeight, const int bufferSize);
static Windows::UI::Xaml::Thickness _ParseThicknessFromPadding(const hstring padding);

void _KeyHandler(Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e, const bool keyDown);
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
bool _CanSendVTMouseInput();

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Tapped="_TappedHandler"
PointerWheelChanged="_MouseWheelHandler"
PreviewKeyDown="_KeyDownHandler"
KeyUp="_KeyUpHandler"
CharacterReceived="_CharacterHandler"
GotFocus="_GotFocusHandler"
LostFocus="_LostFocusHandler">
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ namespace Microsoft::Terminal::Core
virtual bool SetDefaultForeground(const DWORD color) noexcept = 0;
virtual bool SetDefaultBackground(const DWORD color) noexcept = 0;

virtual bool EnableWin32InputMode(const bool win32InputMode) noexcept = 0;
virtual bool SetCursorKeysMode(const bool applicationMode) noexcept = 0;
virtual bool SetKeypadMode(const bool applicationMode) noexcept = 0;
virtual bool EnableVT200MouseMode(const bool enabled) noexcept = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/ITerminalInput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace Microsoft::Terminal::Core
ITerminalInput& operator=(const ITerminalInput&) = default;
ITerminalInput& operator=(ITerminalInput&&) = default;

virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0;
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0;

Expand Down
21 changes: 17 additions & 4 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
_suppressApplicationTitle = settings.SuppressApplicationTitle();
_startingTitle = settings.StartingTitle();

_terminalInput->ForceDisableWin32InputMode(settings.ForceVTInput());

// TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
// have a smaller scrollback. We should do this carefully - if the new buffer
// size is smaller than where the mutable viewport currently is, we'll want
Expand Down Expand Up @@ -408,10 +410,14 @@ bool Terminal::IsTrackingMouseInput() const noexcept
// - vkey: The vkey of the last pressed key.
// - scanCode: The scan code of the last pressed key.
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// - keyDown: If true, the key was pressed, otherwise the key was released.
// Return Value:
// - true if we translated the key event, and it should not be processed any further.
// - false if we did not translate the key, and it should be processed into a character.
bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states)
bool Terminal::SendKeyEvent(const WORD vkey,
const WORD scanCode,
const ControlKeyStates states,
const bool keyDown)
{
TrySnapOnInput();
_StoreKeyEvent(vkey, scanCode);
Expand Down Expand Up @@ -452,7 +458,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlK
return false;
}

KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
KeyEvent keyEv{ keyDown, 1, vkey, scanCode, ch, states.Value() };
return _terminalInput->HandleKey(&keyEv);
}

Expand Down Expand Up @@ -513,8 +519,15 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
vkey = _VirtualKeyFromCharacter(ch);
}

KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
return _terminalInput->HandleKey(&keyEv);
// Unfortunately, the UI doesn't give us both a character down and a
// character up event, only a character received event. So fake sending both
// to the terminal input translator. Unless it's in win32-input-mode, it'll
// ignore the keyup.
KeyEvent keyDown{ true, 1, vkey, scanCode, ch, states.Value() };
KeyEvent keyUp{ false, 1, vkey, scanCode, ch, states.Value() };
const auto handledDown = _terminalInput->HandleKey(&keyDown);
const auto handledUp = _terminalInput->HandleKey(&keyUp);
return handledDown || handledUp;
}

// Method Description:
Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class Microsoft::Terminal::Core::Terminal final :
bool SetDefaultForeground(const COLORREF color) noexcept override;
bool SetDefaultBackground(const COLORREF color) noexcept override;

bool EnableWin32InputMode(const bool win32InputMode) noexcept override;
bool SetCursorKeysMode(const bool applicationMode) noexcept override;
bool SetKeypadMode(const bool applicationMode) noexcept override;
bool EnableVT200MouseMode(const bool enabled) noexcept override;
Expand All @@ -117,7 +118,7 @@ class Microsoft::Terminal::Core::Terminal final :

#pragma region ITerminalInput
// These methods are defined in Terminal.cpp
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override;
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override;

Expand Down
6 changes: 6 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,12 @@ try
}
CATCH_LOG_RETURN_FALSE()

bool Terminal::EnableWin32InputMode(const bool win32InputMode) noexcept
{
_terminalInput->ChangeWin32InputMode(win32InputMode);
return true;
}

bool Terminal::SetCursorKeysMode(const bool applicationMode) noexcept
{
_terminalInput->ChangeCursorKeysMode(applicationMode);
Expand Down
Loading

0 comments on commit f327618

Please sign in to comment.