1 // Copyright (c) 2011-2015 Ryan Prichard
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 #include "ConsoleInput.h"
29 #include "../include/winpty_constants.h"
31 #include "../shared/DebugClient.h"
32 #include "../shared/StringBuilder.h"
33 #include "../shared/UnixCtrlChars.h"
35 #include "ConsoleInputReencoding.h"
36 #include "DebugShowInput.h"
37 #include "DefaultInputMap.h"
38 #include "DsrSender.h"
39 #include "UnicodeEncoding.h"
40 #include "Win32Console.h"
42 // MAPVK_VK_TO_VSC isn't defined by the old MinGW.
43 #ifndef MAPVK_VK_TO_VSC
44 #define MAPVK_VK_TO_VSC 0
54 std::string toString() const;
57 std::string MouseRecord::toString() const {
59 sb << "pos=" << coord.X << ',' << coord.Y
60 << " flags=0x" << hexOfInt(flags);
64 return sb.str_moved();
67 const unsigned int kIncompleteEscapeTimeoutMs = 1000u;
71 if (!(cond)) { return 0; } \
77 if (pch == stop) { return -1; } \
80 #define SCAN_INT(out, maxLen) \
83 CHECK(isdigit(*pch)); \
84 const char *begin = pch; \
86 CHECK(pch - begin + 1 < maxLen); \
87 (out) = (out) * 10 + *pch - '0'; \
89 } while (isdigit(*pch)); \
92 #define SCAN_SIGNED_INT(out, maxLen) \
94 bool negative = false; \
99 SCAN_INT(out, maxLen); \
105 // Match the Device Status Report console input: ESC [ nn ; mm R
108 // >0 match, returns length of match
109 // -1 incomplete match
110 static int matchDsr(const char *input, int inputSize)
113 const char *pch = input;
114 const char *stop = input + inputSize;
115 CHECK(*pch == '\x1B'); ADVANCE();
116 CHECK(*pch == '['); ADVANCE();
118 CHECK(*pch == ';'); ADVANCE();
121 return pch - input + 1;
124 static int matchMouseDefault(const char *input, int inputSize,
127 const char *pch = input;
128 const char *stop = input + inputSize;
129 CHECK(*pch == '\x1B'); ADVANCE();
130 CHECK(*pch == '['); ADVANCE();
131 CHECK(*pch == 'M'); ADVANCE();
132 out.flags = (*pch - 32) & 0xFF; ADVANCE();
133 out.coord.X = (*pch - '!') & 0xFF;
135 out.coord.Y = (*pch - '!') & 0xFF;
137 return pch - input + 1;
140 static int matchMouse1006(const char *input, int inputSize, MouseRecord &out)
142 const char *pch = input;
143 const char *stop = input + inputSize;
145 CHECK(*pch == '\x1B'); ADVANCE();
146 CHECK(*pch == '['); ADVANCE();
147 CHECK(*pch == '<'); ADVANCE();
148 SCAN_INT(out.flags, 8);
149 CHECK(*pch == ';'); ADVANCE();
150 SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1;
151 CHECK(*pch == ';'); ADVANCE();
152 SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1;
153 CHECK(*pch == 'M' || *pch == 'm');
154 out.release = (*pch == 'm');
155 return pch - input + 1;
158 static int matchMouse1015(const char *input, int inputSize, MouseRecord &out)
160 const char *pch = input;
161 const char *stop = input + inputSize;
163 CHECK(*pch == '\x1B'); ADVANCE();
164 CHECK(*pch == '['); ADVANCE();
165 SCAN_INT(out.flags, 8); out.flags -= 32;
166 CHECK(*pch == ';'); ADVANCE();
167 SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1;
168 CHECK(*pch == ';'); ADVANCE();
169 SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1;
172 return pch - input + 1;
175 // Match a mouse input escape sequence of any kind.
177 // >0 match, returns length of match
178 // -1 incomplete match
179 static int matchMouseRecord(const char *input, int inputSize, MouseRecord &out)
181 memset(&out, 0, sizeof(out));
183 if ((ret = matchMouse1006(input, inputSize, out)) != 0) { return ret; }
184 if ((ret = matchMouse1015(input, inputSize, out)) != 0) { return ret; }
185 if ((ret = matchMouseDefault(input, inputSize, out)) != 0) { return ret; }
193 } // anonymous namespace
195 ConsoleInput::ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender,
196 Win32Console &console) :
199 m_mouseMode(mouseMode),
200 m_dsrSender(dsrSender)
202 addDefaultEntriesToInputMap(m_inputMap);
203 if (hasDebugFlag("dump_input_map")) {
204 m_inputMap.dumpInputMap();
207 // Configure Quick Edit mode according to the mouse mode. Enable
208 // InsertMode for two reasons:
209 // - If it's OFF, it's difficult for the user to turn it ON. The
210 // properties dialog is inaccesible. winpty still faithfully handles
211 // the Insert key, which toggles between the insertion and overwrite
213 // - When we modify the QuickEdit setting, if ExtendedFlags is OFF,
214 // then we must choose the InsertMode setting. I don't *think* this
215 // case happens, though, because a new console always has ExtendedFlags
217 // See misc/EnableExtendedFlags.txt.
219 if (!GetConsoleMode(conin, &mode)) {
220 trace("Agent startup: GetConsoleMode failed");
222 mode |= ENABLE_EXTENDED_FLAGS;
223 mode |= ENABLE_INSERT_MODE;
224 if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) {
225 mode |= ENABLE_QUICK_EDIT_MODE;
227 mode &= ~ENABLE_QUICK_EDIT_MODE;
229 if (!SetConsoleMode(conin, mode)) {
230 trace("Agent startup: SetConsoleMode failed");
234 updateInputFlags(true);
237 void ConsoleInput::writeInput(const std::string &input)
239 if (input.size() == 0) {
243 if (isTracingEnabled()) {
244 static bool debugInput = hasDebugFlag("input");
246 std::string dumpString;
247 for (size_t i = 0; i < input.size(); ++i) {
248 const char ch = input[i];
249 const char ctrl = decodeUnixCtrlChar(ch);
258 for (size_t i = 0; i < input.size(); ++i) {
262 const unsigned char uch = input[i];
264 winpty_snprintf(buf, "%02X", uch);
268 trace("input chars: %s", dumpString.c_str());
272 m_byteQueue.append(input);
274 if (!m_byteQueue.empty() && !m_dsrSent) {
276 m_dsrSender.sendDsr();
279 m_lastWriteTick = GetTickCount();
282 void ConsoleInput::flushIncompleteEscapeCode()
284 if (!m_byteQueue.empty() &&
285 (GetTickCount() - m_lastWriteTick) > kIncompleteEscapeTimeoutMs) {
291 void ConsoleInput::updateInputFlags(bool forceTrace)
293 const DWORD mode = inputConsoleMode();
294 const bool newFlagEE = (mode & ENABLE_EXTENDED_FLAGS) != 0;
295 const bool newFlagMI = (mode & ENABLE_MOUSE_INPUT) != 0;
296 const bool newFlagQE = (mode & ENABLE_QUICK_EDIT_MODE) != 0;
297 const bool newFlagEI = (mode & 0x200) != 0;
299 newFlagEE != m_enableExtendedEnabled ||
300 newFlagMI != m_mouseInputEnabled ||
301 newFlagQE != m_quickEditEnabled ||
302 newFlagEI != m_escapeInputEnabled) {
303 trace("CONIN modes: Extended=%s, MouseInput=%s QuickEdit=%s EscapeInput=%s",
304 newFlagEE ? "on" : "off",
305 newFlagMI ? "on" : "off",
306 newFlagQE ? "on" : "off",
307 newFlagEI ? "on" : "off");
309 m_enableExtendedEnabled = newFlagEE;
310 m_mouseInputEnabled = newFlagMI;
311 m_quickEditEnabled = newFlagQE;
312 m_escapeInputEnabled = newFlagEI;
315 bool ConsoleInput::shouldActivateTerminalMouse()
317 // Return whether the agent should activate the terminal's mouse mode.
318 if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) {
319 // Some programs (e.g. Cygwin command-line programs like bash.exe and
320 // python2.7.exe) turn off ENABLE_EXTENDED_FLAGS and turn on
321 // ENABLE_MOUSE_INPUT, but do not turn off QuickEdit mode and do not
322 // actually care about mouse input. Only enable the terminal mouse
323 // mode if ENABLE_EXTENDED_FLAGS is on. See
324 // misc/EnableExtendedFlags.txt.
325 return m_mouseInputEnabled && !m_quickEditEnabled &&
326 m_enableExtendedEnabled;
327 } else if (m_mouseMode == WINPTY_MOUSE_MODE_FORCE) {
334 void ConsoleInput::doWrite(bool isEof)
336 const char *data = m_byteQueue.c_str();
337 std::vector<INPUT_RECORD> records;
339 while (idx < m_byteQueue.size()) {
340 int charSize = scanInput(records, &data[idx], m_byteQueue.size() - idx, isEof);
345 m_byteQueue.erase(0, idx);
346 flushInputRecords(records);
349 void ConsoleInput::flushInputRecords(std::vector<INPUT_RECORD> &records)
351 if (records.size() == 0) {
355 if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
356 trace("WriteConsoleInputW failed");
361 // This behavior isn't strictly correct, because the keypresses (probably?)
362 // adopt the keyboard state (e.g. Ctrl/Alt/Shift modifiers) of the current
363 // window station's keyboard, which has no necessary relationship to the winpty
364 // instance. It's unlikely to be an issue in practice, but it's conceivable.
365 // (Imagine a foreground SSH server, where the local user holds down Ctrl,
366 // while the remote user tries to use WSL navigation keys.) I suspect using
367 // the BackgroundDesktop mechanism in winpty would fix the problem.
369 // https://github.com/rprichard/winpty/issues/116
370 static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey)
372 uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
373 if (scanCode > 255) {
376 SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey,
377 (scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u));
380 int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
385 ASSERT(inputSize >= 1);
389 // In processed mode, use GenerateConsoleCtrlEvent so that Ctrl-C handlers
390 // are called. GenerateConsoleCtrlEvent unfortunately doesn't interrupt
391 // ReadConsole calls[1]. Using WM_KEYDOWN/UP fixes the ReadConsole
392 // problem, but breaks in background window stations/desktops.
394 // In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding
395 // table in DefaultInputMap.
397 // [1] https://github.com/rprichard/winpty/issues/116
398 if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) {
399 flushInputRecords(records);
401 const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
402 trace("GenerateConsoleCtrlEvent: %d", ret);
406 if (input[0] == '\x1B') {
407 // Attempt to match the Device Status Report (DSR) reply.
408 int dsrLen = matchDsr(input, inputSize);
410 trace("Received a DSR reply");
413 } else if (!isEof && dsrLen == -1) {
414 // Incomplete DSR match.
415 trace("Incomplete DSR match");
419 int mouseLen = scanMouseInput(records, input, inputSize);
420 if (mouseLen > 0 || (!isEof && mouseLen == -1)) {
425 // Search the input map.
428 int matchLen = m_inputMap.lookupKey(input, inputSize, match, incomplete);
429 if (!isEof && incomplete) {
430 // Incomplete match -- need more characters (or wait for a
431 // timeout to signify flushed input).
432 trace("Incomplete escape sequence");
434 } else if (matchLen > 0) {
435 uint32_t winCodePointDn = match.unicodeChar;
436 if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) {
437 winCodePointDn = '\0';
439 uint32_t winCodePointUp = winCodePointDn;
440 if (match.keyState & LEFT_ALT_PRESSED) {
441 winCodePointUp = '\0';
443 appendKeyPress(records, match.virtualKey,
444 winCodePointDn, winCodePointUp, match.keyState,
445 match.unicodeChar, match.keyState);
449 // Recognize Alt-<character>.
451 // This code doesn't match Alt-ESC, which is encoded as `ESC ESC`, but
452 // maybe it should. I was concerned that pressing ESC rapidly enough could
453 // accidentally trigger Alt-ESC. (e.g. The user would have to be faster
454 // than the DSR flushing mechanism or use a decrepit terminal. The user
455 // might be on a slow network connection.)
456 if (input[0] == '\x1B' && inputSize >= 2 && input[1] != '\x1B') {
457 const int len = utf8CharLength(input[1]);
459 if (1 + len > inputSize) {
460 // Incomplete character.
461 trace("Incomplete UTF-8 character in Alt-<Char>");
464 appendUtf8Char(records, &input[1], len, true);
469 // A UTF-8 character.
470 const int len = utf8CharLength(input[0]);
472 static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
474 trace("Discarding invalid input byte: %02X",
475 static_cast<unsigned char>(input[0]));
479 if (len > inputSize) {
480 // Incomplete character.
481 trace("Incomplete UTF-8 character");
484 appendUtf8Char(records, &input[0], len, false);
488 int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
493 const int len = matchMouseRecord(input, inputSize, record);
498 if (isTracingEnabled()) {
499 static bool debugInput = hasDebugFlag("input");
501 trace("mouse input: %s", record.toString().c_str());
505 const int button = record.flags & 0x03;
506 INPUT_RECORD newRecord = {0};
507 newRecord.EventType = MOUSE_EVENT;
508 MOUSE_EVENT_RECORD &mer = newRecord.Event.MouseEvent;
510 mer.dwMousePosition.X =
511 m_mouseWindowRect.Left +
512 std::max(0, std::min<int>(record.coord.X,
513 m_mouseWindowRect.width() - 1));
515 mer.dwMousePosition.Y =
516 m_mouseWindowRect.Top +
517 std::max(0, std::min<int>(record.coord.Y,
518 m_mouseWindowRect.height() - 1));
520 // The modifier state is neatly independent of everything else.
521 if (record.flags & 0x04) { mer.dwControlKeyState |= SHIFT_PRESSED; }
522 if (record.flags & 0x08) { mer.dwControlKeyState |= LEFT_ALT_PRESSED; }
523 if (record.flags & 0x10) { mer.dwControlKeyState |= LEFT_CTRL_PRESSED; }
525 if (record.flags & 0x40) {
527 mer.dwEventFlags |= MOUSE_WHEELED;
530 mer.dwButtonState |= 0x00780000;
531 } else if (button == 1) {
533 mer.dwButtonState |= 0xff880000;
535 // Invalid -- do nothing
539 // Ordinary mouse event
540 if (record.flags & 0x20) { mer.dwEventFlags |= MOUSE_MOVED; }
542 m_mouseButtonState = 0;
543 // Potentially advance double-click detection.
544 m_doubleClick.released = true;
546 const DWORD relevantFlag =
547 (button == 0) ? FROM_LEFT_1ST_BUTTON_PRESSED :
548 (button == 1) ? FROM_LEFT_2ND_BUTTON_PRESSED :
549 (button == 2) ? RIGHTMOST_BUTTON_PRESSED :
551 ASSERT(relevantFlag != 0);
552 if (record.release) {
553 m_mouseButtonState &= ~relevantFlag;
554 if (relevantFlag == m_doubleClick.button) {
555 // Potentially advance double-click detection.
556 m_doubleClick.released = true;
558 // End double-click detection.
559 m_doubleClick = DoubleClickDetection();
561 } else if ((m_mouseButtonState & relevantFlag) == 0) {
562 // The button has been newly pressed.
563 m_mouseButtonState |= relevantFlag;
564 // Detect a double-click. This code looks for an exact
565 // coordinate match, which is stricter than what Windows does,
566 // but Windows has pixel coordinates, and we only have terminal
568 if (m_doubleClick.button == relevantFlag &&
569 m_doubleClick.pos == record.coord &&
570 (GetTickCount() - m_doubleClick.tick <
571 GetDoubleClickTime())) {
572 // Record a double-click and end double-click detection.
573 mer.dwEventFlags |= DOUBLE_CLICK;
574 m_doubleClick = DoubleClickDetection();
576 // Begin double-click detection.
577 m_doubleClick.button = relevantFlag;
578 m_doubleClick.pos = record.coord;
579 m_doubleClick.tick = GetTickCount();
585 mer.dwButtonState |= m_mouseButtonState;
587 if (m_mouseInputEnabled && !m_quickEditEnabled) {
588 if (isTracingEnabled()) {
589 static bool debugInput = hasDebugFlag("input");
591 trace("mouse event: %s", mouseEventToString(mer).c_str());
595 records.push_back(newRecord);
601 void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
602 const char *charBuffer,
604 const bool terminalAltEscape)
606 const uint32_t codePoint = decodeUtf8(charBuffer);
607 if (codePoint == static_cast<uint32_t>(-1)) {
608 static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
610 StringBuilder error(64);
611 error << "Discarding invalid UTF-8 sequence:";
612 for (int i = 0; i < charLen; ++i) {
614 error << hexOfInt<true, uint8_t>(charBuffer[i]);
616 trace("%s", error.c_str());
621 const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint);
622 uint16_t virtualKey = 0;
623 uint16_t winKeyState = 0;
624 uint32_t winCodePointDn = codePoint;
625 uint32_t winCodePointUp = codePoint;
626 uint16_t vtKeyState = 0;
628 if (charScan != -1) {
629 virtualKey = charScan & 0xFF;
630 if (charScan & 0x100) {
631 winKeyState |= SHIFT_PRESSED;
633 if (charScan & 0x200) {
634 winKeyState |= LEFT_CTRL_PRESSED;
636 if (charScan & 0x400) {
637 winKeyState |= RIGHT_ALT_PRESSED;
639 if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) {
640 // If the terminal escapes a Ctrl-<Key> with Alt, then set the
641 // codepoint to 0. On the other hand, if a character requires
642 // AltGr (like U+00B2 on a German layout), then VkKeyScan will
643 // report both Ctrl and Alt pressed, and we should keep the
644 // codepoint. See https://github.com/rprichard/winpty/issues/109.
649 if (terminalAltEscape) {
651 winKeyState |= LEFT_ALT_PRESSED;
652 vtKeyState |= LEFT_ALT_PRESSED;
655 appendKeyPress(records, virtualKey,
656 winCodePointDn, winCodePointUp, winKeyState,
657 codePoint, vtKeyState);
660 void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
661 const uint16_t virtualKey,
662 const uint32_t winCodePointDn,
663 const uint32_t winCodePointUp,
664 const uint16_t winKeyState,
665 const uint32_t vtCodePoint,
666 const uint16_t vtKeyState)
668 const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0;
669 const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0;
670 const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0;
671 const bool shift = (winKeyState & SHIFT_PRESSED) != 0;
672 const bool enhanced = (winKeyState & ENHANCED_KEY) != 0;
673 bool hasDebugInput = false;
675 if (isTracingEnabled()) {
676 static bool debugInput = hasDebugFlag("input");
678 hasDebugInput = true;
679 InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
680 trace("keypress: %s", key.toString().c_str());
684 if (m_escapeInputEnabled &&
685 (virtualKey == VK_UP ||
686 virtualKey == VK_DOWN ||
687 virtualKey == VK_LEFT ||
688 virtualKey == VK_RIGHT ||
689 virtualKey == VK_HOME ||
690 virtualKey == VK_END) &&
691 !ctrl && !leftAlt && !rightAlt && !shift) {
692 flushInputRecords(records);
694 trace("sending keypress to console HWND");
696 sendKeyMessage(m_console.hwnd(), true, virtualKey);
697 sendKeyMessage(m_console.hwnd(), false, virtualKey);
701 uint16_t stepKeyState = 0;
703 stepKeyState |= LEFT_CTRL_PRESSED;
704 appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
707 stepKeyState |= LEFT_ALT_PRESSED;
708 appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState);
711 stepKeyState |= RIGHT_ALT_PRESSED;
712 appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
715 stepKeyState |= SHIFT_PRESSED;
716 appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState);
719 stepKeyState |= ENHANCED_KEY;
721 if (m_escapeInputEnabled) {
722 reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
724 appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
726 appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState);
728 stepKeyState &= ~ENHANCED_KEY;
731 stepKeyState &= ~SHIFT_PRESSED;
732 appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
735 stepKeyState &= ~RIGHT_ALT_PRESSED;
736 appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
739 stepKeyState &= ~LEFT_ALT_PRESSED;
740 appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
743 stepKeyState &= ~LEFT_CTRL_PRESSED;
744 appendInputRecord(records, FALSE, VK_CONTROL, 0, stepKeyState);
748 void ConsoleInput::appendCPInputRecords(std::vector<INPUT_RECORD> &records,
754 // This behavior really doesn't match that of the Windows console (in
755 // normal, non-escape-mode). Judging by the copy-and-paste behavior,
756 // Windows apparently handles everything outside of the keyboard layout by
757 // first sending a sequence of Alt+KeyPad events, then finally a key-up
758 // event whose UnicodeChar has the appropriate value. For U+00A2 (CENT
761 // key: dn rpt=1 scn=56 LAlt-MENU ch=0
762 // key: dn rpt=1 scn=79 LAlt-NUMPAD1 ch=0
763 // key: up rpt=1 scn=79 LAlt-NUMPAD1 ch=0
764 // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0
765 // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0
766 // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0
767 // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0
768 // key: up rpt=1 scn=56 MENU ch=0xa2
770 // The Alt+155 value matches the encoding of U+00A2 in CP-437. Curiously,
771 // if I use "chcp 1252" to change the encoding, then copy-and-pasting
772 // produces Alt+162 instead. (U+00A2 is 162 in CP-1252.) However, typing
773 // Alt+155 or Alt+162 produce the same characters regardless of console
774 // code page. (That is, they use CP-437 and yield U+00A2 and U+00F3.)
776 // For characters outside the BMP, Windows repeats the process for both
777 // UTF-16 code units, e.g, for U+1F300 (CYCLONE):
779 // key: dn rpt=1 scn=56 LAlt-MENU ch=0
780 // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0
781 // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0
782 // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0
783 // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0
784 // key: up rpt=1 scn=56 MENU ch=0xd83c
785 // key: dn rpt=1 scn=56 LAlt-MENU ch=0
786 // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0
787 // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0
788 // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0
789 // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0
790 // key: up rpt=1 scn=56 MENU ch=0xdf00
792 // In this case, it sends Alt+63 twice, which signifies '?'. Apparently
793 // CMD and Cygwin bash are both able to decode this.
795 // Also note that typing Alt+NNN still works if NumLock is off, e.g.:
797 // key: dn rpt=1 scn=56 LAlt-MENU ch=0
798 // key: dn rpt=1 scn=79 LAlt-END ch=0
799 // key: up rpt=1 scn=79 LAlt-END ch=0
800 // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0
801 // key: up rpt=1 scn=76 LAlt-CLEAR ch=0
802 // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0
803 // key: up rpt=1 scn=76 LAlt-CLEAR ch=0
804 // key: up rpt=1 scn=56 MENU ch=0xa2
806 // Evidently, the Alt+NNN key events are not intended to be decoded to a
807 // character. Maybe programs are looking for a key-up ALT/MENU event with
808 // a non-zero character?
811 const int wslen = encodeUtf16(ws, codePoint);
814 appendInputRecord(records, keyDown, virtualKey, ws[0], keyState);
815 } else if (wslen == 2) {
816 appendInputRecord(records, keyDown, virtualKey, ws[0], keyState);
817 appendInputRecord(records, keyDown, virtualKey, ws[1], keyState);
819 // This situation isn't that bad, but it should never happen,
820 // because invalid codepoints shouldn't reach this point.
821 trace("INTERNAL ERROR: appendInputRecordCP: invalid codePoint: "
822 "U+%04X", codePoint);
826 void ConsoleInput::appendInputRecord(std::vector<INPUT_RECORD> &records,
832 INPUT_RECORD ir = {};
833 ir.EventType = KEY_EVENT;
834 ir.Event.KeyEvent.bKeyDown = keyDown;
835 ir.Event.KeyEvent.wRepeatCount = 1;
836 ir.Event.KeyEvent.wVirtualKeyCode = virtualKey;
837 ir.Event.KeyEvent.wVirtualScanCode =
838 MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
839 ir.Event.KeyEvent.uChar.UnicodeChar = utf16Char;
840 ir.Event.KeyEvent.dwControlKeyState = keyState;
841 records.push_back(ir);
844 DWORD ConsoleInput::inputConsoleMode()
847 if (!GetConsoleMode(m_conin, &mode)) {
848 trace("GetConsoleMode failed");