X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FConsoleInput.cc;fp=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FConsoleInput.cc;h=192cac2a298f3325d56b52b94414caad4691c572;hp=0000000000000000000000000000000000000000;hb=e79e4a5a87f3e84f7c1777f10a954453a69bf540;hpb=4339da12467b75fb8b6ca831f4bf0081c485ed2c diff --git a/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.cc b/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.cc new file mode 100644 index 0000000..192cac2 --- /dev/null +++ b/node_modules/node-pty/deps/winpty/src/agent/ConsoleInput.cc @@ -0,0 +1,852 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleInput.h" + +#include +#include + +#include +#include + +#include "../include/winpty_constants.h" + +#include "../shared/DebugClient.h" +#include "../shared/StringBuilder.h" +#include "../shared/UnixCtrlChars.h" + +#include "ConsoleInputReencoding.h" +#include "DebugShowInput.h" +#include "DefaultInputMap.h" +#include "DsrSender.h" +#include "UnicodeEncoding.h" +#include "Win32Console.h" + +// MAPVK_VK_TO_VSC isn't defined by the old MinGW. +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC 0 +#endif + +namespace { + +struct MouseRecord { + bool release; + int flags; + COORD coord; + + std::string toString() const; +}; + +std::string MouseRecord::toString() const { + StringBuilder sb(40); + sb << "pos=" << coord.X << ',' << coord.Y + << " flags=0x" << hexOfInt(flags); + if (release) { + sb << " release"; + } + return sb.str_moved(); +} + +const unsigned int kIncompleteEscapeTimeoutMs = 1000u; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { return 0; } \ + } while(0) + +#define ADVANCE() \ + do { \ + pch++; \ + if (pch == stop) { return -1; } \ + } while(0) + +#define SCAN_INT(out, maxLen) \ + do { \ + (out) = 0; \ + CHECK(isdigit(*pch)); \ + const char *begin = pch; \ + do { \ + CHECK(pch - begin + 1 < maxLen); \ + (out) = (out) * 10 + *pch - '0'; \ + ADVANCE(); \ + } while (isdigit(*pch)); \ + } while(0) + +#define SCAN_SIGNED_INT(out, maxLen) \ + do { \ + bool negative = false; \ + if (*pch == '-') { \ + negative = true; \ + ADVANCE(); \ + } \ + SCAN_INT(out, maxLen); \ + if (negative) { \ + (out) = -(out); \ + } \ + } while(0) + +// Match the Device Status Report console input: ESC [ nn ; mm R +// Returns: +// 0 no match +// >0 match, returns length of match +// -1 incomplete match +static int matchDsr(const char *input, int inputSize) +{ + int32_t dummy = 0; + const char *pch = input; + const char *stop = input + inputSize; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + SCAN_INT(dummy, 8); + CHECK(*pch == ';'); ADVANCE(); + SCAN_INT(dummy, 8); + CHECK(*pch == 'R'); + return pch - input + 1; +} + +static int matchMouseDefault(const char *input, int inputSize, + MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + CHECK(*pch == 'M'); ADVANCE(); + out.flags = (*pch - 32) & 0xFF; ADVANCE(); + out.coord.X = (*pch - '!') & 0xFF; + ADVANCE(); + out.coord.Y = (*pch - '!') & 0xFF; + out.release = false; + return pch - input + 1; +} + +static int matchMouse1006(const char *input, int inputSize, MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + int32_t temp; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + CHECK(*pch == '<'); ADVANCE(); + SCAN_INT(out.flags, 8); + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1; + CHECK(*pch == 'M' || *pch == 'm'); + out.release = (*pch == 'm'); + return pch - input + 1; +} + +static int matchMouse1015(const char *input, int inputSize, MouseRecord &out) +{ + const char *pch = input; + const char *stop = input + inputSize; + int32_t temp; + CHECK(*pch == '\x1B'); ADVANCE(); + CHECK(*pch == '['); ADVANCE(); + SCAN_INT(out.flags, 8); out.flags -= 32; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1; + CHECK(*pch == ';'); ADVANCE(); + SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1; + CHECK(*pch == 'M'); + out.release = false; + return pch - input + 1; +} + +// Match a mouse input escape sequence of any kind. +// 0 no match +// >0 match, returns length of match +// -1 incomplete match +static int matchMouseRecord(const char *input, int inputSize, MouseRecord &out) +{ + memset(&out, 0, sizeof(out)); + int ret; + if ((ret = matchMouse1006(input, inputSize, out)) != 0) { return ret; } + if ((ret = matchMouse1015(input, inputSize, out)) != 0) { return ret; } + if ((ret = matchMouseDefault(input, inputSize, out)) != 0) { return ret; } + return 0; +} + +#undef CHECK +#undef ADVANCE +#undef SCAN_INT + +} // anonymous namespace + +ConsoleInput::ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender, + Win32Console &console) : + m_console(console), + m_conin(conin), + m_mouseMode(mouseMode), + m_dsrSender(dsrSender) +{ + addDefaultEntriesToInputMap(m_inputMap); + if (hasDebugFlag("dump_input_map")) { + m_inputMap.dumpInputMap(); + } + + // Configure Quick Edit mode according to the mouse mode. Enable + // InsertMode for two reasons: + // - If it's OFF, it's difficult for the user to turn it ON. The + // properties dialog is inaccesible. winpty still faithfully handles + // the Insert key, which toggles between the insertion and overwrite + // modes. + // - When we modify the QuickEdit setting, if ExtendedFlags is OFF, + // then we must choose the InsertMode setting. I don't *think* this + // case happens, though, because a new console always has ExtendedFlags + // ON. + // See misc/EnableExtendedFlags.txt. + DWORD mode = 0; + if (!GetConsoleMode(conin, &mode)) { + trace("Agent startup: GetConsoleMode failed"); + } else { + mode |= ENABLE_EXTENDED_FLAGS; + mode |= ENABLE_INSERT_MODE; + if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) { + mode |= ENABLE_QUICK_EDIT_MODE; + } else { + mode &= ~ENABLE_QUICK_EDIT_MODE; + } + if (!SetConsoleMode(conin, mode)) { + trace("Agent startup: SetConsoleMode failed"); + } + } + + updateInputFlags(true); +} + +void ConsoleInput::writeInput(const std::string &input) +{ + if (input.size() == 0) { + return; + } + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + std::string dumpString; + for (size_t i = 0; i < input.size(); ++i) { + const char ch = input[i]; + const char ctrl = decodeUnixCtrlChar(ch); + if (ctrl != '\0') { + dumpString += '^'; + dumpString += ctrl; + } else { + dumpString += ch; + } + } + dumpString += " ("; + for (size_t i = 0; i < input.size(); ++i) { + if (i > 0) { + dumpString += ' '; + } + const unsigned char uch = input[i]; + char buf[32]; + winpty_snprintf(buf, "%02X", uch); + dumpString += buf; + } + dumpString += ')'; + trace("input chars: %s", dumpString.c_str()); + } + } + + m_byteQueue.append(input); + doWrite(false); + if (!m_byteQueue.empty() && !m_dsrSent) { + trace("send DSR"); + m_dsrSender.sendDsr(); + m_dsrSent = true; + } + m_lastWriteTick = GetTickCount(); +} + +void ConsoleInput::flushIncompleteEscapeCode() +{ + if (!m_byteQueue.empty() && + (GetTickCount() - m_lastWriteTick) > kIncompleteEscapeTimeoutMs) { + doWrite(true); + m_byteQueue.clear(); + } +} + +void ConsoleInput::updateInputFlags(bool forceTrace) +{ + const DWORD mode = inputConsoleMode(); + const bool newFlagEE = (mode & ENABLE_EXTENDED_FLAGS) != 0; + const bool newFlagMI = (mode & ENABLE_MOUSE_INPUT) != 0; + const bool newFlagQE = (mode & ENABLE_QUICK_EDIT_MODE) != 0; + const bool newFlagEI = (mode & 0x200) != 0; + if (forceTrace || + newFlagEE != m_enableExtendedEnabled || + newFlagMI != m_mouseInputEnabled || + newFlagQE != m_quickEditEnabled || + newFlagEI != m_escapeInputEnabled) { + trace("CONIN modes: Extended=%s, MouseInput=%s QuickEdit=%s EscapeInput=%s", + newFlagEE ? "on" : "off", + newFlagMI ? "on" : "off", + newFlagQE ? "on" : "off", + newFlagEI ? "on" : "off"); + } + m_enableExtendedEnabled = newFlagEE; + m_mouseInputEnabled = newFlagMI; + m_quickEditEnabled = newFlagQE; + m_escapeInputEnabled = newFlagEI; +} + +bool ConsoleInput::shouldActivateTerminalMouse() +{ + // Return whether the agent should activate the terminal's mouse mode. + if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) { + // Some programs (e.g. Cygwin command-line programs like bash.exe and + // python2.7.exe) turn off ENABLE_EXTENDED_FLAGS and turn on + // ENABLE_MOUSE_INPUT, but do not turn off QuickEdit mode and do not + // actually care about mouse input. Only enable the terminal mouse + // mode if ENABLE_EXTENDED_FLAGS is on. See + // misc/EnableExtendedFlags.txt. + return m_mouseInputEnabled && !m_quickEditEnabled && + m_enableExtendedEnabled; + } else if (m_mouseMode == WINPTY_MOUSE_MODE_FORCE) { + return true; + } else { + return false; + } +} + +void ConsoleInput::doWrite(bool isEof) +{ + const char *data = m_byteQueue.c_str(); + std::vector records; + size_t idx = 0; + while (idx < m_byteQueue.size()) { + int charSize = scanInput(records, &data[idx], m_byteQueue.size() - idx, isEof); + if (charSize == -1) + break; + idx += charSize; + } + m_byteQueue.erase(0, idx); + flushInputRecords(records); +} + +void ConsoleInput::flushInputRecords(std::vector &records) +{ + if (records.size() == 0) { + return; + } + DWORD actual = 0; + if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) { + trace("WriteConsoleInputW failed"); + } + records.clear(); +} + +// This behavior isn't strictly correct, because the keypresses (probably?) +// adopt the keyboard state (e.g. Ctrl/Alt/Shift modifiers) of the current +// window station's keyboard, which has no necessary relationship to the winpty +// instance. It's unlikely to be an issue in practice, but it's conceivable. +// (Imagine a foreground SSH server, where the local user holds down Ctrl, +// while the remote user tries to use WSL navigation keys.) I suspect using +// the BackgroundDesktop mechanism in winpty would fix the problem. +// +// https://github.com/rprichard/winpty/issues/116 +static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey) +{ + uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + if (scanCode > 255) { + scanCode = 0; + } + SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey, + (scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u)); +} + +int ConsoleInput::scanInput(std::vector &records, + const char *input, + int inputSize, + bool isEof) +{ + ASSERT(inputSize >= 1); + + // Ctrl-C. + // + // In processed mode, use GenerateConsoleCtrlEvent so that Ctrl-C handlers + // are called. GenerateConsoleCtrlEvent unfortunately doesn't interrupt + // ReadConsole calls[1]. Using WM_KEYDOWN/UP fixes the ReadConsole + // problem, but breaks in background window stations/desktops. + // + // In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding + // table in DefaultInputMap. + // + // [1] https://github.com/rprichard/winpty/issues/116 + if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) { + flushInputRecords(records); + trace("Ctrl-C"); + const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + trace("GenerateConsoleCtrlEvent: %d", ret); + return 1; + } + + if (input[0] == '\x1B') { + // Attempt to match the Device Status Report (DSR) reply. + int dsrLen = matchDsr(input, inputSize); + if (dsrLen > 0) { + trace("Received a DSR reply"); + m_dsrSent = false; + return dsrLen; + } else if (!isEof && dsrLen == -1) { + // Incomplete DSR match. + trace("Incomplete DSR match"); + return -1; + } + + int mouseLen = scanMouseInput(records, input, inputSize); + if (mouseLen > 0 || (!isEof && mouseLen == -1)) { + return mouseLen; + } + } + + // Search the input map. + InputMap::Key match; + bool incomplete; + int matchLen = m_inputMap.lookupKey(input, inputSize, match, incomplete); + if (!isEof && incomplete) { + // Incomplete match -- need more characters (or wait for a + // timeout to signify flushed input). + trace("Incomplete escape sequence"); + return -1; + } else if (matchLen > 0) { + uint32_t winCodePointDn = match.unicodeChar; + if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) { + winCodePointDn = '\0'; + } + uint32_t winCodePointUp = winCodePointDn; + if (match.keyState & LEFT_ALT_PRESSED) { + winCodePointUp = '\0'; + } + appendKeyPress(records, match.virtualKey, + winCodePointDn, winCodePointUp, match.keyState, + match.unicodeChar, match.keyState); + return matchLen; + } + + // Recognize Alt-. + // + // This code doesn't match Alt-ESC, which is encoded as `ESC ESC`, but + // maybe it should. I was concerned that pressing ESC rapidly enough could + // accidentally trigger Alt-ESC. (e.g. The user would have to be faster + // than the DSR flushing mechanism or use a decrepit terminal. The user + // might be on a slow network connection.) + if (input[0] == '\x1B' && inputSize >= 2 && input[1] != '\x1B') { + const int len = utf8CharLength(input[1]); + if (len > 0) { + if (1 + len > inputSize) { + // Incomplete character. + trace("Incomplete UTF-8 character in Alt-"); + return -1; + } + appendUtf8Char(records, &input[1], len, true); + return 1 + len; + } + } + + // A UTF-8 character. + const int len = utf8CharLength(input[0]); + if (len == 0) { + static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); + if (debugInput) { + trace("Discarding invalid input byte: %02X", + static_cast(input[0])); + } + return 1; + } + if (len > inputSize) { + // Incomplete character. + trace("Incomplete UTF-8 character"); + return -1; + } + appendUtf8Char(records, &input[0], len, false); + return len; +} + +int ConsoleInput::scanMouseInput(std::vector &records, + const char *input, + int inputSize) +{ + MouseRecord record; + const int len = matchMouseRecord(input, inputSize, record); + if (len <= 0) { + return len; + } + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + trace("mouse input: %s", record.toString().c_str()); + } + } + + const int button = record.flags & 0x03; + INPUT_RECORD newRecord = {0}; + newRecord.EventType = MOUSE_EVENT; + MOUSE_EVENT_RECORD &mer = newRecord.Event.MouseEvent; + + mer.dwMousePosition.X = + m_mouseWindowRect.Left + + std::max(0, std::min(record.coord.X, + m_mouseWindowRect.width() - 1)); + + mer.dwMousePosition.Y = + m_mouseWindowRect.Top + + std::max(0, std::min(record.coord.Y, + m_mouseWindowRect.height() - 1)); + + // The modifier state is neatly independent of everything else. + if (record.flags & 0x04) { mer.dwControlKeyState |= SHIFT_PRESSED; } + if (record.flags & 0x08) { mer.dwControlKeyState |= LEFT_ALT_PRESSED; } + if (record.flags & 0x10) { mer.dwControlKeyState |= LEFT_CTRL_PRESSED; } + + if (record.flags & 0x40) { + // Mouse wheel + mer.dwEventFlags |= MOUSE_WHEELED; + if (button == 0) { + // up + mer.dwButtonState |= 0x00780000; + } else if (button == 1) { + // down + mer.dwButtonState |= 0xff880000; + } else { + // Invalid -- do nothing + return len; + } + } else { + // Ordinary mouse event + if (record.flags & 0x20) { mer.dwEventFlags |= MOUSE_MOVED; } + if (button == 3) { + m_mouseButtonState = 0; + // Potentially advance double-click detection. + m_doubleClick.released = true; + } else { + const DWORD relevantFlag = + (button == 0) ? FROM_LEFT_1ST_BUTTON_PRESSED : + (button == 1) ? FROM_LEFT_2ND_BUTTON_PRESSED : + (button == 2) ? RIGHTMOST_BUTTON_PRESSED : + 0; + ASSERT(relevantFlag != 0); + if (record.release) { + m_mouseButtonState &= ~relevantFlag; + if (relevantFlag == m_doubleClick.button) { + // Potentially advance double-click detection. + m_doubleClick.released = true; + } else { + // End double-click detection. + m_doubleClick = DoubleClickDetection(); + } + } else if ((m_mouseButtonState & relevantFlag) == 0) { + // The button has been newly pressed. + m_mouseButtonState |= relevantFlag; + // Detect a double-click. This code looks for an exact + // coordinate match, which is stricter than what Windows does, + // but Windows has pixel coordinates, and we only have terminal + // coordinates. + if (m_doubleClick.button == relevantFlag && + m_doubleClick.pos == record.coord && + (GetTickCount() - m_doubleClick.tick < + GetDoubleClickTime())) { + // Record a double-click and end double-click detection. + mer.dwEventFlags |= DOUBLE_CLICK; + m_doubleClick = DoubleClickDetection(); + } else { + // Begin double-click detection. + m_doubleClick.button = relevantFlag; + m_doubleClick.pos = record.coord; + m_doubleClick.tick = GetTickCount(); + } + } + } + } + + mer.dwButtonState |= m_mouseButtonState; + + if (m_mouseInputEnabled && !m_quickEditEnabled) { + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + trace("mouse event: %s", mouseEventToString(mer).c_str()); + } + } + + records.push_back(newRecord); + } + + return len; +} + +void ConsoleInput::appendUtf8Char(std::vector &records, + const char *charBuffer, + const int charLen, + const bool terminalAltEscape) +{ + const uint32_t codePoint = decodeUtf8(charBuffer); + if (codePoint == static_cast(-1)) { + static bool debugInput = isTracingEnabled() && hasDebugFlag("input"); + if (debugInput) { + StringBuilder error(64); + error << "Discarding invalid UTF-8 sequence:"; + for (int i = 0; i < charLen; ++i) { + error << ' '; + error << hexOfInt(charBuffer[i]); + } + trace("%s", error.c_str()); + } + return; + } + + const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint); + uint16_t virtualKey = 0; + uint16_t winKeyState = 0; + uint32_t winCodePointDn = codePoint; + uint32_t winCodePointUp = codePoint; + uint16_t vtKeyState = 0; + + if (charScan != -1) { + virtualKey = charScan & 0xFF; + if (charScan & 0x100) { + winKeyState |= SHIFT_PRESSED; + } + if (charScan & 0x200) { + winKeyState |= LEFT_CTRL_PRESSED; + } + if (charScan & 0x400) { + winKeyState |= RIGHT_ALT_PRESSED; + } + if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) { + // If the terminal escapes a Ctrl- with Alt, then set the + // codepoint to 0. On the other hand, if a character requires + // AltGr (like U+00B2 on a German layout), then VkKeyScan will + // report both Ctrl and Alt pressed, and we should keep the + // codepoint. See https://github.com/rprichard/winpty/issues/109. + winCodePointDn = 0; + winCodePointUp = 0; + } + } + if (terminalAltEscape) { + winCodePointUp = 0; + winKeyState |= LEFT_ALT_PRESSED; + vtKeyState |= LEFT_ALT_PRESSED; + } + + appendKeyPress(records, virtualKey, + winCodePointDn, winCodePointUp, winKeyState, + codePoint, vtKeyState); +} + +void ConsoleInput::appendKeyPress(std::vector &records, + const uint16_t virtualKey, + const uint32_t winCodePointDn, + const uint32_t winCodePointUp, + const uint16_t winKeyState, + const uint32_t vtCodePoint, + const uint16_t vtKeyState) +{ + const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0; + const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0; + const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0; + const bool shift = (winKeyState & SHIFT_PRESSED) != 0; + const bool enhanced = (winKeyState & ENHANCED_KEY) != 0; + bool hasDebugInput = false; + + if (isTracingEnabled()) { + static bool debugInput = hasDebugFlag("input"); + if (debugInput) { + hasDebugInput = true; + InputMap::Key key = { virtualKey, winCodePointDn, winKeyState }; + trace("keypress: %s", key.toString().c_str()); + } + } + + if (m_escapeInputEnabled && + (virtualKey == VK_UP || + virtualKey == VK_DOWN || + virtualKey == VK_LEFT || + virtualKey == VK_RIGHT || + virtualKey == VK_HOME || + virtualKey == VK_END) && + !ctrl && !leftAlt && !rightAlt && !shift) { + flushInputRecords(records); + if (hasDebugInput) { + trace("sending keypress to console HWND"); + } + sendKeyMessage(m_console.hwnd(), true, virtualKey); + sendKeyMessage(m_console.hwnd(), false, virtualKey); + return; + } + + uint16_t stepKeyState = 0; + if (ctrl) { + stepKeyState |= LEFT_CTRL_PRESSED; + appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState); + } + if (leftAlt) { + stepKeyState |= LEFT_ALT_PRESSED; + appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState); + } + if (rightAlt) { + stepKeyState |= RIGHT_ALT_PRESSED; + appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY); + } + if (shift) { + stepKeyState |= SHIFT_PRESSED; + appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState); + } + if (enhanced) { + stepKeyState |= ENHANCED_KEY; + } + if (m_escapeInputEnabled) { + reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState); + } else { + appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState); + } + appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState); + if (enhanced) { + stepKeyState &= ~ENHANCED_KEY; + } + if (shift) { + stepKeyState &= ~SHIFT_PRESSED; + appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState); + } + if (rightAlt) { + stepKeyState &= ~RIGHT_ALT_PRESSED; + appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY); + } + if (leftAlt) { + stepKeyState &= ~LEFT_ALT_PRESSED; + appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState); + } + if (ctrl) { + stepKeyState &= ~LEFT_CTRL_PRESSED; + appendInputRecord(records, FALSE, VK_CONTROL, 0, stepKeyState); + } +} + +void ConsoleInput::appendCPInputRecords(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + uint32_t codePoint, + uint16_t keyState) +{ + // This behavior really doesn't match that of the Windows console (in + // normal, non-escape-mode). Judging by the copy-and-paste behavior, + // Windows apparently handles everything outside of the keyboard layout by + // first sending a sequence of Alt+KeyPad events, then finally a key-up + // event whose UnicodeChar has the appropriate value. For U+00A2 (CENT + // SIGN): + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=79 LAlt-NUMPAD1 ch=0 + // key: up rpt=1 scn=79 LAlt-NUMPAD1 ch=0 + // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xa2 + // + // The Alt+155 value matches the encoding of U+00A2 in CP-437. Curiously, + // if I use "chcp 1252" to change the encoding, then copy-and-pasting + // produces Alt+162 instead. (U+00A2 is 162 in CP-1252.) However, typing + // Alt+155 or Alt+162 produce the same characters regardless of console + // code page. (That is, they use CP-437 and yield U+00A2 and U+00F3.) + // + // For characters outside the BMP, Windows repeats the process for both + // UTF-16 code units, e.g, for U+1F300 (CYCLONE): + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xd83c + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0 + // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0 + // key: up rpt=1 scn=56 MENU ch=0xdf00 + // + // In this case, it sends Alt+63 twice, which signifies '?'. Apparently + // CMD and Cygwin bash are both able to decode this. + // + // Also note that typing Alt+NNN still works if NumLock is off, e.g.: + // + // key: dn rpt=1 scn=56 LAlt-MENU ch=0 + // key: dn rpt=1 scn=79 LAlt-END ch=0 + // key: up rpt=1 scn=79 LAlt-END ch=0 + // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=76 LAlt-CLEAR ch=0 + // key: up rpt=1 scn=56 MENU ch=0xa2 + // + // Evidently, the Alt+NNN key events are not intended to be decoded to a + // character. Maybe programs are looking for a key-up ALT/MENU event with + // a non-zero character? + + wchar_t ws[2]; + const int wslen = encodeUtf16(ws, codePoint); + + if (wslen == 1) { + appendInputRecord(records, keyDown, virtualKey, ws[0], keyState); + } else if (wslen == 2) { + appendInputRecord(records, keyDown, virtualKey, ws[0], keyState); + appendInputRecord(records, keyDown, virtualKey, ws[1], keyState); + } else { + // This situation isn't that bad, but it should never happen, + // because invalid codepoints shouldn't reach this point. + trace("INTERNAL ERROR: appendInputRecordCP: invalid codePoint: " + "U+%04X", codePoint); + } +} + +void ConsoleInput::appendInputRecord(std::vector &records, + BOOL keyDown, + uint16_t virtualKey, + wchar_t utf16Char, + uint16_t keyState) +{ + INPUT_RECORD ir = {}; + ir.EventType = KEY_EVENT; + ir.Event.KeyEvent.bKeyDown = keyDown; + ir.Event.KeyEvent.wRepeatCount = 1; + ir.Event.KeyEvent.wVirtualKeyCode = virtualKey; + ir.Event.KeyEvent.wVirtualScanCode = + MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC); + ir.Event.KeyEvent.uChar.UnicodeChar = utf16Char; + ir.Event.KeyEvent.dwControlKeyState = keyState; + records.push_back(ir); +} + +DWORD ConsoleInput::inputConsoleMode() +{ + DWORD mode = 0; + if (!GetConsoleMode(m_conin, &mode)) { + trace("GetConsoleMode failed"); + return 0; + } + return mode; +}