// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "inputBuffer.hpp" #include "dbcs.h" #include "stream.h" #include "../types/inc/GlyphWidth.hpp" #include #include "../interactivity/inc/ServiceLocator.hpp" #define INPUT_BUFFER_DEFAULT_INPUT_MODE (ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT) using Microsoft::Console::Interactivity::ServiceLocator; using Microsoft::Console::VirtualTerminal::TerminalInput; using namespace Microsoft::Console; // Routine Description: // - This method creates an input buffer. // Arguments: // - None // Return Value: // - A new instance of InputBuffer InputBuffer::InputBuffer() : InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE }, WaitQueue{}, _pTtyConnection(nullptr), _termInput(std::bind(&InputBuffer::_HandleTerminalInputCallback, this, std::placeholders::_1)) { // The _termInput's constructor takes a reference to this object's _HandleTerminalInputCallback. // We need to use std::bind to create a reference to that function without a reference to this InputBuffer // initialize buffer header fInComposition = false; } // Routine Description: // - This routine frees the resources associated with an input buffer. // Arguments: // - None // Return Value: InputBuffer::~InputBuffer() { } // Routine Description: // - checks if any partial char data is available for reading operation // Arguments: // - None // Return Value: // - true if partial char data is available, false otherwise bool InputBuffer::IsReadPartialByteSequenceAvailable() { return _readPartialByteSequence.get() != nullptr; } // Routine Description: // - reads any read partial char data available // Arguments: // - peek - if true, data will not be removed after being fetched // Return Value: // - the partial char data. may be nullptr if no data is available std::unique_ptr InputBuffer::FetchReadPartialByteSequence(_In_ bool peek) { if (!IsReadPartialByteSequenceAvailable()) { return std::unique_ptr(); } if (peek) { return IInputEvent::Create(_readPartialByteSequence->ToInputRecord()); } else { std::unique_ptr outEvent; outEvent.swap(_readPartialByteSequence); return outEvent; } } // Routine Description: // - stores partial read char data for a later read. will overwrite // any previously stored data. // Arguments: // - event - The event to store // Return Value: // - None void InputBuffer::StoreReadPartialByteSequence(std::unique_ptr event) { _readPartialByteSequence.swap(event); } // Routine Description: // - checks if any partial char data is available for writing // operation. // Arguments: // - None // Return Value: // - true if partial char data is available, false otherwise bool InputBuffer::IsWritePartialByteSequenceAvailable() { return _writePartialByteSequence.get() != nullptr; } // Routine Description: // - writes any write partial char data available // Arguments: // - peek - if true, data will not be removed after being fetched // Return Value: // - the partial char data. may be nullptr if no data is available std::unique_ptr InputBuffer::FetchWritePartialByteSequence(_In_ bool peek) { if (!IsWritePartialByteSequenceAvailable()) { return std::unique_ptr(); } if (peek) { return IInputEvent::Create(_writePartialByteSequence->ToInputRecord()); } else { std::unique_ptr outEvent; outEvent.swap(_writePartialByteSequence); return outEvent; } } // Routine Description: // - stores partial write char data. will overwrite any previously // stored data. // Arguments: // - event - The event to store // Return Value: // - None void InputBuffer::StoreWritePartialByteSequence(std::unique_ptr event) { _writePartialByteSequence.swap(event); } // Routine Description: // - This routine resets the input buffer information fields to their initial values. // Arguments: // Return Value: // Note: // - The console lock must be held when calling this routine. void InputBuffer::ReinitializeInputBuffer() { ServiceLocator::LocateGlobals().hInputEvent.ResetEvent(); InputMode = INPUT_BUFFER_DEFAULT_INPUT_MODE; _storage.clear(); } // Routine Description: // - Wakes up readers waiting for data to read. // Arguments: // - None // Return Value: // - None void InputBuffer::WakeUpReadersWaitingForData() { WaitQueue.NotifyWaiters(false); } // Routine Description: // - Wakes up any readers waiting for data when a ctrl-c or ctrl-break is input. // Arguments: // - Flag - Indicates reason to terminate the readers. // Return Value: // - None void InputBuffer::TerminateRead(_In_ WaitTerminationReason Flag) { WaitQueue.NotifyWaiters(true, Flag); } // Routine Description: // - Returns the number of events in the input buffer. // Arguments: // - None // Return Value: // - The number of events currently in the input buffer. // Note: // - The console lock must be held when calling this routine. size_t InputBuffer::GetNumberOfReadyEvents() const noexcept { return _storage.size(); } // Routine Description: // - This routine empties the input buffer // Arguments: // - None // Return Value: // - None // Note: // - The console lock must be held when calling this routine. void InputBuffer::Flush() { _storage.clear(); ServiceLocator::LocateGlobals().hInputEvent.ResetEvent(); } // Routine Description: // - This routine removes all but the key events from the buffer. // Arguments: // - None // Return Value: // - None // Note: // - The console lock must be held when calling this routine. void InputBuffer::FlushAllButKeys() { auto newEnd = std::remove_if(_storage.begin(), _storage.end(), [](const std::unique_ptr& event) { return event->EventType() != InputEventType::KeyEvent; }); _storage.erase(newEnd, _storage.end()); } void InputBuffer::SetTerminalConnection(_In_ ITerminalOutputConnection* const pTtyConnection) { this->_pTtyConnection = pTtyConnection; } void InputBuffer::PassThroughWin32MouseRequest(bool enable) { if (_pTtyConnection) { if (enable) { LOG_IF_FAILED(_pTtyConnection->WriteTerminalW(L"\x1b[?1003;1006h")); } else { LOG_IF_FAILED(_pTtyConnection->WriteTerminalW(L"\x1b[?1003;1006l")); } } } // Routine Description: // - This routine reads from the input buffer. // - It can convert returned data to through the currently set Input CP, it can optionally return a wait condition // if there isn't enough data in the buffer, and it can be set to not remove records as it reads them out. // Note: // - The console lock must be held when calling this routine. // Arguments: // - OutEvents - deque to store the read events // - AmountToRead - the amount of events to try to read // - Peek - If true, copy events to pInputRecord but don't remove them from the input buffer. // - WaitForData - if true, wait until an event is input (if there aren't enough to fill client buffer). if false, return immediately // - Unicode - true if the data in key events should be treated as unicode. false if they should be converted by the current input CP. // - Stream - true if read should unpack KeyEvents that have a >1 repeat count. AmountToRead must be 1 if Stream is true. // Return Value: // - STATUS_SUCCESS if records were read into the client buffer and everything is OK. // - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed) // - otherwise a suitable memory/math/string error in NTSTATUS form. [[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ std::deque>& OutEvents, const size_t AmountToRead, const bool Peek, const bool WaitForData, const bool Unicode, const bool Stream) { try { if (_storage.empty()) { if (!WaitForData) { return STATUS_SUCCESS; } return CONSOLE_STATUS_WAIT; } // read from buffer std::deque> events; size_t eventsRead; bool resetWaitEvent; _ReadBuffer(events, AmountToRead, eventsRead, Peek, resetWaitEvent, Unicode, Stream); // copy events to outEvents while (!events.empty()) { OutEvents.push_back(std::move(events.front())); events.pop_front(); } if (resetWaitEvent) { ServiceLocator::LocateGlobals().hInputEvent.ResetEvent(); } return STATUS_SUCCESS; } catch (...) { return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); } } // Routine Description: // - This routine reads a single event from the input buffer. // - It can convert returned data to through the currently set Input CP, it can optionally return a wait condition // if there isn't enough data in the buffer, and it can be set to not remove records as it reads them out. // Note: // - The console lock must be held when calling this routine. // Arguments: // - outEvent - where the read event is stored // - Peek - If true, copy events to pInputRecord but don't remove them from the input buffer. // - WaitForData - if true, wait until an event is input (if there aren't enough to fill client buffer). if false, return immediately // - Unicode - true if the data in key events should be treated as unicode. false if they should be converted by the current input CP. // - Stream - true if read should unpack KeyEvents that have a >1 repeat count. // Return Value: // - STATUS_SUCCESS if records were read into the client buffer and everything is OK. // - CONSOLE_STATUS_WAIT if there weren't enough records to satisfy the request (and waits are allowed) // - otherwise a suitable memory/math/string error in NTSTATUS form. [[nodiscard]] NTSTATUS InputBuffer::Read(_Out_ std::unique_ptr& outEvent, const bool Peek, const bool WaitForData, const bool Unicode, const bool Stream) { NTSTATUS Status; try { std::deque> outEvents; Status = Read(outEvents, 1, Peek, WaitForData, Unicode, Stream); if (!outEvents.empty()) { outEvent.swap(outEvents.front()); } } catch (...) { Status = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException()); } return Status; } // Routine Description: // - This routine reads from a buffer. It does the buffer manipulation. // Arguments: // - outEvents - where read events are placed // - readCount - amount of events to read // - eventsRead - where to store number of events read // - peek - if true , don't remove data from buffer, just copy it. // - resetWaitEvent - on exit, true if buffer became empty. // - unicode - true if read should be done in unicode mode // - streamRead - true if read should unpack KeyEvents that have a >1 repeat count. readCount must be 1 if streamRead is true. // Return Value: // - // Note: // - The console lock must be held when calling this routine. void InputBuffer::_ReadBuffer(_Out_ std::deque>& outEvents, const size_t readCount, _Out_ size_t& eventsRead, const bool peek, _Out_ bool& resetWaitEvent, const bool unicode, const bool streamRead) { // when stream reading, the previous behavior was to only allow reading of a single // event at a time. FAIL_FAST_IF(streamRead && readCount != 1); resetWaitEvent = false; std::deque> readEvents; // we need another var to keep track of how many we've read // because dbcs records count for two when we aren't doing a // unicode read but the eventsRead count should return the number // of events actually put into outRecords. size_t virtualReadCount = 0; while (!_storage.empty() && virtualReadCount < readCount) { bool performNormalRead = true; // for stream reads we need to split any key events that have been coalesced if (streamRead) { if (_storage.front()->EventType() == InputEventType::KeyEvent) { KeyEvent* const pKeyEvent = static_cast(_storage.front().get()); if (pKeyEvent->GetRepeatCount() > 1) { // split the key event std::unique_ptr streamKeyEvent = std::make_unique(*pKeyEvent); streamKeyEvent->SetRepeatCount(1); readEvents.push_back(std::move(streamKeyEvent)); pKeyEvent->SetRepeatCount(pKeyEvent->GetRepeatCount() - 1); performNormalRead = false; } } } if (performNormalRead) { readEvents.push_back(std::move(_storage.front())); _storage.pop_front(); } ++virtualReadCount; if (!unicode) { if (readEvents.back()->EventType() == InputEventType::KeyEvent) { const KeyEvent* const pKeyEvent = static_cast(readEvents.back().get()); if (IsGlyphFullWidth(pKeyEvent->GetCharData())) { ++virtualReadCount; } } } } // the amount of events that were actually read eventsRead = readEvents.size(); // copy the events back if we were supposed to peek if (peek) { if (streamRead) { // we need to check and see if the event was split from a coalesced key event // or if it was unrelated to the current front event in storage if (!readEvents.empty() && !_storage.empty() && readEvents.back()->EventType() == InputEventType::KeyEvent && _storage.front()->EventType() == InputEventType::KeyEvent && _CanCoalesce(static_cast(*readEvents.back()), static_cast(*_storage.front()))) { KeyEvent& keyEvent = static_cast(*_storage.front()); keyEvent.SetRepeatCount(keyEvent.GetRepeatCount() + 1); } else { _storage.push_front(IInputEvent::Create(readEvents.back()->ToInputRecord())); } } else { for (auto it = readEvents.crbegin(); it != readEvents.crend(); ++it) { _storage.push_front(IInputEvent::Create((*it)->ToInputRecord())); } } } // move events read to proper deque while (!readEvents.empty()) { outEvents.push_back(std::move(readEvents.front())); readEvents.pop_front(); } // signal if we emptied the buffer if (_storage.empty()) { resetWaitEvent = true; } } // Routine Description: // - Writes events to the beginning of the input buffer. // Arguments: // - inEvents - events to write to buffer. // - eventsWritten - The number of events written to the buffer on exit. // Return Value: // S_OK if successful. // Note: // - The console lock must be held when calling this routine. size_t InputBuffer::Prepend(_Inout_ std::deque>& inEvents) { try { _vtInputShouldSuppress = true; auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; }); _HandleConsoleSuspensionEvents(inEvents); if (inEvents.empty()) { return STATUS_SUCCESS; } // read all of the records out of the buffer, then write the // prepend ones, then write the original set. We need to do it // this way to handle any coalescing that might occur. // get all of the existing records, "emptying" the buffer std::deque> existingStorage; existingStorage.swap(_storage); // We will need this variable to pass to _WriteBuffer so it can attempt to determine wait status. // However, because we swapped the storage out from under it with an empty deque, it will always // return true after the first one (as it is filling the newly emptied backing deque.) // Then after the second one, because we've inserted some input, it will always say false. bool unusedWaitStatus = false; // write the prepend records size_t prependEventsWritten; _WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus); FAIL_FAST_IF(!(unusedWaitStatus)); // write all previously existing records size_t existingEventsWritten; _WriteBuffer(existingStorage, existingEventsWritten, unusedWaitStatus); FAIL_FAST_IF(!(!unusedWaitStatus)); // We need to set the wait event if there were 0 events in the // input queue when we started. // Because we did interesting manipulation of the wait queue // in order to prepend, we can't trust what _WriteBuffer said // and instead need to set the event if the original backing // buffer (the one we swapped out at the top) was empty // when this whole thing started. if (existingStorage.empty()) { ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); } WakeUpReadersWaitingForData(); return prependEventsWritten; } catch (...) { LOG_HR(wil::ResultFromCaughtException()); return 0; } } // Routine Description: // - Writes event to the input buffer. Wakes up any readers that are // waiting for additional input events. // Arguments: // - inEvent - input event to store in the buffer. // Return Value: // - The number of events that were written to input buffer. // Note: // - The console lock must be held when calling this routine. // - any outside references to inEvent will ben invalidated after // calling this method. size_t InputBuffer::Write(_Inout_ std::unique_ptr inEvent) { try { std::deque> inEvents; inEvents.push_back(std::move(inEvent)); return Write(inEvents); } catch (...) { LOG_HR(wil::ResultFromCaughtException()); return 0; } } // Routine Description: // - Writes events to the input buffer. Wakes up any readers that are // waiting for additional input events. // Arguments: // - inEvents - input events to store in the buffer. // Return Value: // - The number of events that were written to input buffer. // Note: // - The console lock must be held when calling this routine. size_t InputBuffer::Write(_Inout_ std::deque>& inEvents) { try { _vtInputShouldSuppress = true; auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; }); _HandleConsoleSuspensionEvents(inEvents); if (inEvents.empty()) { return 0; } // Write to buffer. size_t EventsWritten; bool SetWaitEvent; _WriteBuffer(inEvents, EventsWritten, SetWaitEvent); if (SetWaitEvent) { ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); } // Alert any writers waiting for space. WakeUpReadersWaitingForData(); return EventsWritten; } catch (...) { LOG_HR(wil::ResultFromCaughtException()); return 0; } } // Routine Description: // - Coalesces input events and transfers them to storage queue. // Arguments: // - inRecords - The events to store. // - eventsWritten - The number of events written since this function // was called. // - setWaitEvent - on exit, true if buffer became non-empty. // Return Value: // - None // Note: // - The console lock must be held when calling this routine. // - will throw on failure void InputBuffer::_WriteBuffer(_Inout_ std::deque>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent) { eventsWritten = 0; setWaitEvent = false; const bool initiallyEmptyQueue = _storage.empty(); const size_t initialInEventsSize = inEvents.size(); const bool vtInputMode = IsInVirtualTerminalInputMode(); while (!inEvents.empty()) { // Pop the next event. // If we're in vt mode, try and handle it with the vt input module. // If it was handled, do nothing else for it. // If there was one event passed in, try coalescing it with the previous event currently in the buffer. // If it's not coalesced, append it to the buffer. std::unique_ptr inEvent = std::move(inEvents.front()); inEvents.pop_front(); if (vtInputMode) { const bool handled = _termInput.HandleKey(inEvent.get()); if (handled) { eventsWritten++; continue; } } // we only check for possible coalescing when storing one // record at a time because this is the original behavior of // the input buffer. Changing this behavior may break stuff // that was depending on it. if (initialInEventsSize == 1 && !_storage.empty()) { // coalescing requires a deque of events, so push it back onto the front. inEvents.push_front(std::move(inEvent)); bool coalesced = false; // this looks kinda weird but we don't want to coalesce a // mouse event and then try to coalesce a key event right after. // // we also pass the whole deque to the coalescing methods // even though they only want one event because it should // be their responsibility to maintain the correct state // of the deque if they process any records in it. if (_CoalesceMouseMovedEvents(inEvents)) { coalesced = true; } else if (_CoalesceRepeatedKeyPressEvents(inEvents)) { coalesced = true; } if (coalesced) { eventsWritten = 1; return; } else { // We didn't coalesce the event. pull it from the queue again, // to keep the state consistent with the start of this block. inEvent = std::move(inEvents.front()); inEvents.pop_front(); } } // At this point, the event was neither coalesced, nor processed by VT. _storage.push_back(std::move(inEvent)); ++eventsWritten; } if (initiallyEmptyQueue && !_storage.empty()) { setWaitEvent = true; } } // Routine Description: // - Checks if the last saved event and the first event of inRecords are // both MOUSE_MOVED events. If they are, the last saved event is // updated with the new mouse position and the first event of inRecords is // dropped. // Arguments: // - inRecords - The incoming records to process. // Return Value: // true if events were coalesced, false if they were not. // Note: // - The size of inRecords must be 1. // - Coalescing here means updating a record that already exists in // the buffer with updated values from an incoming event, instead of // storing the incoming event (which would make the original one // redundant/out of date with the most current state). bool InputBuffer::_CoalesceMouseMovedEvents(_Inout_ std::deque>& inEvents) { FAIL_FAST_IF(!(inEvents.size() == 1)); FAIL_FAST_IF(_storage.empty()); const IInputEvent* const pFirstInEvent = inEvents.front().get(); const IInputEvent* const pLastStoredEvent = _storage.back().get(); if (pFirstInEvent->EventType() == InputEventType::MouseEvent && pLastStoredEvent->EventType() == InputEventType::MouseEvent) { const MouseEvent* const pInMouseEvent = static_cast(pFirstInEvent); const MouseEvent* const pLastMouseEvent = static_cast(pLastStoredEvent); if (pInMouseEvent->IsMouseMoveEvent() && pLastMouseEvent->IsMouseMoveEvent()) { // update mouse moved position MouseEvent* const pMouseEvent = static_cast(_storage.back().release()); pMouseEvent->SetPosition(pInMouseEvent->GetPosition()); std::unique_ptr tempPtr(pMouseEvent); tempPtr.swap(_storage.back()); inEvents.pop_front(); return true; } } return false; } // Routine Description: // - checks two KeyEvents to see if they're similar enough to be coalesced // Arguments: // - a - the first KeyEvent // - b - the other KeyEvent // Return Value: // - true if the events could be coalesced, false otherwise bool InputBuffer::_CanCoalesce(const KeyEvent& a, const KeyEvent& b) const noexcept { if (WI_IsFlagSet(a.GetActiveModifierKeys(), NLS_IME_CONVERSION) && a.GetCharData() == b.GetCharData() && a.GetActiveModifierKeys() == b.GetActiveModifierKeys()) { return true; } // other key events check else if (a.GetVirtualScanCode() == b.GetVirtualScanCode() && a.GetCharData() == b.GetCharData() && a.GetActiveModifierKeys() == b.GetActiveModifierKeys()) { return true; } return false; } // Routine Description:: // - If the last input event saved and the first input event in inRecords // are both a keypress down event for the same key, update the repeat // count of the saved event and drop the first from inRecords. // Arguments: // - inRecords - The incoming records to process. // Return Value: // true if events were coalesced, false if they were not. // Note: // - The size of inRecords must be 1. // - Coalescing here means updating a record that already exists in // the buffer with updated values from an incoming event, instead of // storing the incoming event (which would make the original one // redundant/out of date with the most current state). bool InputBuffer::_CoalesceRepeatedKeyPressEvents(_Inout_ std::deque>& inEvents) { FAIL_FAST_IF(!(inEvents.size() == 1)); FAIL_FAST_IF(_storage.empty()); const IInputEvent* const pFirstInEvent = inEvents.front().get(); const IInputEvent* const pLastStoredEvent = _storage.back().get(); if (pFirstInEvent->EventType() == InputEventType::KeyEvent && pLastStoredEvent->EventType() == InputEventType::KeyEvent) { const KeyEvent* const pInKeyEvent = static_cast(pFirstInEvent); const KeyEvent* const pLastKeyEvent = static_cast(pLastStoredEvent); if (pInKeyEvent->IsKeyDown() && pLastKeyEvent->IsKeyDown() && !IsGlyphFullWidth(pInKeyEvent->GetCharData()) && _CanCoalesce(*pInKeyEvent, *pLastKeyEvent)) { // increment repeat count KeyEvent* const pKeyEvent = static_cast(_storage.back().release()); WORD repeatCount = pKeyEvent->GetRepeatCount() + pInKeyEvent->GetRepeatCount(); pKeyEvent->SetRepeatCount(repeatCount); std::unique_ptr tempPtr(pKeyEvent); tempPtr.swap(_storage.back()); inEvents.pop_front(); return true; } } return false; } // Routine Description: // - Handles records that suspend/resume the console. // Arguments: // - records - records to check for pause/unpause events // Return Value: // - None // Note: // - The console lock must be held when calling this routine. // - will throw exception on error void InputBuffer::_HandleConsoleSuspensionEvents(_Inout_ std::deque>& inEvents) { CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); std::deque> outEvents; while (!inEvents.empty()) { std::unique_ptr currEvent = std::move(inEvents.front()); inEvents.pop_front(); if (currEvent->EventType() == InputEventType::KeyEvent) { const KeyEvent* const pKeyEvent = static_cast(currEvent.get()); if (pKeyEvent->IsKeyDown()) { if (WI_IsFlagSet(gci.Flags, CONSOLE_SUSPENDED) && !IsSystemKey(pKeyEvent->GetVirtualKeyCode())) { UnblockWriteConsole(CONSOLE_OUTPUT_SUSPENDED); continue; } else if (WI_IsFlagSet(InputMode, ENABLE_LINE_INPUT) && pKeyEvent->IsPauseKey()) { WI_SetFlag(gci.Flags, CONSOLE_SUSPENDED); continue; } } } outEvents.push_back(std::move(currEvent)); } inEvents.swap(outEvents); } // Routine Description: // - Returns true if this input buffer is in VT Input mode. // Arguments: // // Return Value: // - Returns true if this input buffer is in VT Input mode. bool InputBuffer::IsInVirtualTerminalInputMode() const { return WI_IsFlagSet(InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT); } // Routine Description: // - Handler for inserting key sequences into the buffer when the terminal emulation layer // has determined a key can be converted appropriately into a sequence of inputs // Arguments: // - inEvents - Series of input records to insert into the buffer // Return Value: // - void InputBuffer::_HandleTerminalInputCallback(std::deque>& inEvents) { try { // add all input events to the storage queue while (!inEvents.empty()) { std::unique_ptr inEvent = std::move(inEvents.front()); inEvents.pop_front(); _storage.push_back(std::move(inEvent)); } if (!_vtInputShouldSuppress) { ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); WakeUpReadersWaitingForData(); } } catch (...) { LOG_HR(wil::ResultFromCaughtException()); } } TerminalInput& InputBuffer::GetTerminalInput() { return _termInput; }