installed pty
[VSoRC/.git] / node_modules / node-pty / deps / winpty / src / agent / ConsoleInput.cc
1 // Copyright (c) 2011-2015 Ryan Prichard
2 //
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:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
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
19 // IN THE SOFTWARE.
20
21 #include "ConsoleInput.h"
22
23 #include <stdio.h>
24 #include <string.h>
25
26 #include <algorithm>
27 #include <string>
28
29 #include "../include/winpty_constants.h"
30
31 #include "../shared/DebugClient.h"
32 #include "../shared/StringBuilder.h"
33 #include "../shared/UnixCtrlChars.h"
34
35 #include "ConsoleInputReencoding.h"
36 #include "DebugShowInput.h"
37 #include "DefaultInputMap.h"
38 #include "DsrSender.h"
39 #include "UnicodeEncoding.h"
40 #include "Win32Console.h"
41
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
45 #endif
46
47 namespace {
48
49 struct MouseRecord {
50     bool release;
51     int flags;
52     COORD coord;
53
54     std::string toString() const;
55 };
56
57 std::string MouseRecord::toString() const {
58     StringBuilder sb(40);
59     sb << "pos=" << coord.X << ',' << coord.Y
60        << " flags=0x" << hexOfInt(flags);
61     if (release) {
62         sb << " release";
63     }
64     return sb.str_moved();
65 }
66
67 const unsigned int kIncompleteEscapeTimeoutMs = 1000u;
68
69 #define CHECK(cond)                                 \
70         do {                                        \
71             if (!(cond)) { return 0; }              \
72         } while(0)
73
74 #define ADVANCE()                                   \
75         do {                                        \
76             pch++;                                  \
77             if (pch == stop) { return -1; }         \
78         } while(0)
79
80 #define SCAN_INT(out, maxLen)                       \
81         do {                                        \
82             (out) = 0;                              \
83             CHECK(isdigit(*pch));                   \
84             const char *begin = pch;                \
85             do {                                    \
86                 CHECK(pch - begin + 1 < maxLen);    \
87                 (out) = (out) * 10 + *pch - '0';    \
88                 ADVANCE();                          \
89             } while (isdigit(*pch));                \
90         } while(0)
91
92 #define SCAN_SIGNED_INT(out, maxLen)                \
93         do {                                        \
94             bool negative = false;                  \
95             if (*pch == '-') {                      \
96                 negative = true;                    \
97                 ADVANCE();                          \
98             }                                       \
99             SCAN_INT(out, maxLen);                  \
100             if (negative) {                         \
101                 (out) = -(out);                     \
102             }                                       \
103         } while(0)
104
105 // Match the Device Status Report console input:  ESC [ nn ; mm R
106 // Returns:
107 // 0   no match
108 // >0  match, returns length of match
109 // -1  incomplete match
110 static int matchDsr(const char *input, int inputSize)
111 {
112     int32_t dummy = 0;
113     const char *pch = input;
114     const char *stop = input + inputSize;
115     CHECK(*pch == '\x1B');  ADVANCE();
116     CHECK(*pch == '[');     ADVANCE();
117     SCAN_INT(dummy, 8);
118     CHECK(*pch == ';');     ADVANCE();
119     SCAN_INT(dummy, 8);
120     CHECK(*pch == 'R');
121     return pch - input + 1;
122 }
123
124 static int matchMouseDefault(const char *input, int inputSize,
125                              MouseRecord &out)
126 {
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;
134     ADVANCE();
135     out.coord.Y = (*pch - '!') & 0xFF;
136     out.release = false;
137     return pch - input + 1;
138 }
139
140 static int matchMouse1006(const char *input, int inputSize, MouseRecord &out)
141 {
142     const char *pch = input;
143     const char *stop = input + inputSize;
144     int32_t temp;
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;
156 }
157
158 static int matchMouse1015(const char *input, int inputSize, MouseRecord &out)
159 {
160     const char *pch = input;
161     const char *stop = input + inputSize;
162     int32_t temp;
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;
170     CHECK(*pch == 'M');
171     out.release = false;
172     return pch - input + 1;
173 }
174
175 // Match a mouse input escape sequence of any kind.
176 // 0   no match
177 // >0  match, returns length of match
178 // -1  incomplete match
179 static int matchMouseRecord(const char *input, int inputSize, MouseRecord &out)
180 {
181     memset(&out, 0, sizeof(out));
182     int ret;
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; }
186     return 0;
187 }
188
189 #undef CHECK
190 #undef ADVANCE
191 #undef SCAN_INT
192
193 } // anonymous namespace
194
195 ConsoleInput::ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender,
196                            Win32Console &console) :
197     m_console(console),
198     m_conin(conin),
199     m_mouseMode(mouseMode),
200     m_dsrSender(dsrSender)
201 {
202     addDefaultEntriesToInputMap(m_inputMap);
203     if (hasDebugFlag("dump_input_map")) {
204         m_inputMap.dumpInputMap();
205     }
206
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
212     //    modes.
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
216     //    ON.
217     // See misc/EnableExtendedFlags.txt.
218     DWORD mode = 0;
219     if (!GetConsoleMode(conin, &mode)) {
220         trace("Agent startup: GetConsoleMode failed");
221     } else {
222         mode |= ENABLE_EXTENDED_FLAGS;
223         mode |= ENABLE_INSERT_MODE;
224         if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) {
225             mode |= ENABLE_QUICK_EDIT_MODE;
226         } else {
227             mode &= ~ENABLE_QUICK_EDIT_MODE;
228         }
229         if (!SetConsoleMode(conin, mode)) {
230             trace("Agent startup: SetConsoleMode failed");
231         }
232     }
233
234     updateInputFlags(true);
235 }
236
237 void ConsoleInput::writeInput(const std::string &input)
238 {
239     if (input.size() == 0) {
240         return;
241     }
242
243     if (isTracingEnabled()) {
244         static bool debugInput = hasDebugFlag("input");
245         if (debugInput) {
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);
250                 if (ctrl != '\0') {
251                     dumpString += '^';
252                     dumpString += ctrl;
253                 } else {
254                     dumpString += ch;
255                 }
256             }
257             dumpString += " (";
258             for (size_t i = 0; i < input.size(); ++i) {
259                 if (i > 0) {
260                     dumpString += ' ';
261                 }
262                 const unsigned char uch = input[i];
263                 char buf[32];
264                 winpty_snprintf(buf, "%02X", uch);
265                 dumpString += buf;
266             }
267             dumpString += ')';
268             trace("input chars: %s", dumpString.c_str());
269         }
270     }
271
272     m_byteQueue.append(input);
273     doWrite(false);
274     if (!m_byteQueue.empty() && !m_dsrSent) {
275         trace("send DSR");
276         m_dsrSender.sendDsr();
277         m_dsrSent = true;
278     }
279     m_lastWriteTick = GetTickCount();
280 }
281
282 void ConsoleInput::flushIncompleteEscapeCode()
283 {
284     if (!m_byteQueue.empty() &&
285             (GetTickCount() - m_lastWriteTick) > kIncompleteEscapeTimeoutMs) {
286         doWrite(true);
287         m_byteQueue.clear();
288     }
289 }
290
291 void ConsoleInput::updateInputFlags(bool forceTrace)
292 {
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;
298     if (forceTrace ||
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");
308     }
309     m_enableExtendedEnabled = newFlagEE;
310     m_mouseInputEnabled = newFlagMI;
311     m_quickEditEnabled = newFlagQE;
312     m_escapeInputEnabled = newFlagEI;
313 }
314
315 bool ConsoleInput::shouldActivateTerminalMouse()
316 {
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) {
328         return true;
329     } else {
330         return false;
331     }
332 }
333
334 void ConsoleInput::doWrite(bool isEof)
335 {
336     const char *data = m_byteQueue.c_str();
337     std::vector<INPUT_RECORD> records;
338     size_t idx = 0;
339     while (idx < m_byteQueue.size()) {
340         int charSize = scanInput(records, &data[idx], m_byteQueue.size() - idx, isEof);
341         if (charSize == -1)
342             break;
343         idx += charSize;
344     }
345     m_byteQueue.erase(0, idx);
346     flushInputRecords(records);
347 }
348
349 void ConsoleInput::flushInputRecords(std::vector<INPUT_RECORD> &records)
350 {
351     if (records.size() == 0) {
352         return;
353     }
354     DWORD actual = 0;
355     if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
356         trace("WriteConsoleInputW failed");
357     }
358     records.clear();
359 }
360
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.
368 //
369 // https://github.com/rprichard/winpty/issues/116
370 static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey)
371 {
372     uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
373     if (scanCode > 255) {
374         scanCode = 0;
375     }
376     SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey,
377         (scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u));
378 }
379
380 int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
381                             const char *input,
382                             int inputSize,
383                             bool isEof)
384 {
385     ASSERT(inputSize >= 1);
386
387     // Ctrl-C.
388     //
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.
393     //
394     // In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding
395     // table in DefaultInputMap.
396     //
397     // [1] https://github.com/rprichard/winpty/issues/116
398     if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) {
399         flushInputRecords(records);
400         trace("Ctrl-C");
401         const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
402         trace("GenerateConsoleCtrlEvent: %d", ret);
403         return 1;
404     }
405
406     if (input[0] == '\x1B') {
407         // Attempt to match the Device Status Report (DSR) reply.
408         int dsrLen = matchDsr(input, inputSize);
409         if (dsrLen > 0) {
410             trace("Received a DSR reply");
411             m_dsrSent = false;
412             return dsrLen;
413         } else if (!isEof && dsrLen == -1) {
414             // Incomplete DSR match.
415             trace("Incomplete DSR match");
416             return -1;
417         }
418
419         int mouseLen = scanMouseInput(records, input, inputSize);
420         if (mouseLen > 0 || (!isEof && mouseLen == -1)) {
421             return mouseLen;
422         }
423     }
424
425     // Search the input map.
426     InputMap::Key match;
427     bool incomplete;
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");
433         return -1;
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';
438         }
439         uint32_t winCodePointUp = winCodePointDn;
440         if (match.keyState & LEFT_ALT_PRESSED) {
441             winCodePointUp = '\0';
442         }
443         appendKeyPress(records, match.virtualKey,
444                        winCodePointDn, winCodePointUp, match.keyState,
445                        match.unicodeChar, match.keyState);
446         return matchLen;
447     }
448
449     // Recognize Alt-<character>.
450     //
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]);
458         if (len > 0) {
459             if (1 + len > inputSize) {
460                 // Incomplete character.
461                 trace("Incomplete UTF-8 character in Alt-<Char>");
462                 return -1;
463             }
464             appendUtf8Char(records, &input[1], len, true);
465             return 1 + len;
466         }
467     }
468
469     // A UTF-8 character.
470     const int len = utf8CharLength(input[0]);
471     if (len == 0) {
472         static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
473         if (debugInput) {
474             trace("Discarding invalid input byte: %02X",
475                 static_cast<unsigned char>(input[0]));
476         }
477         return 1;
478     }
479     if (len > inputSize) {
480         // Incomplete character.
481         trace("Incomplete UTF-8 character");
482         return -1;
483     }
484     appendUtf8Char(records, &input[0], len, false);
485     return len;
486 }
487
488 int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
489                                  const char *input,
490                                  int inputSize)
491 {
492     MouseRecord record;
493     const int len = matchMouseRecord(input, inputSize, record);
494     if (len <= 0) {
495         return len;
496     }
497
498     if (isTracingEnabled()) {
499         static bool debugInput = hasDebugFlag("input");
500         if (debugInput) {
501             trace("mouse input: %s", record.toString().c_str());
502         }
503     }
504
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;
509
510     mer.dwMousePosition.X =
511         m_mouseWindowRect.Left +
512             std::max(0, std::min<int>(record.coord.X,
513                                       m_mouseWindowRect.width() - 1));
514
515     mer.dwMousePosition.Y =
516         m_mouseWindowRect.Top +
517             std::max(0, std::min<int>(record.coord.Y,
518                                       m_mouseWindowRect.height() - 1));
519
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; }
524
525     if (record.flags & 0x40) {
526         // Mouse wheel
527         mer.dwEventFlags |= MOUSE_WHEELED;
528         if (button == 0) {
529             // up
530             mer.dwButtonState |= 0x00780000;
531         } else if (button == 1) {
532             // down
533             mer.dwButtonState |= 0xff880000;
534         } else {
535             // Invalid -- do nothing
536             return len;
537         }
538     } else {
539         // Ordinary mouse event
540         if (record.flags & 0x20) { mer.dwEventFlags |= MOUSE_MOVED; }
541         if (button == 3) {
542             m_mouseButtonState = 0;
543             // Potentially advance double-click detection.
544             m_doubleClick.released = true;
545         } else {
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 :
550                 0;
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;
557                 } else {
558                     // End double-click detection.
559                     m_doubleClick = DoubleClickDetection();
560                 }
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
567                 // coordinates.
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();
575                 } else {
576                     // Begin double-click detection.
577                     m_doubleClick.button = relevantFlag;
578                     m_doubleClick.pos = record.coord;
579                     m_doubleClick.tick = GetTickCount();
580                 }
581             }
582         }
583     }
584
585     mer.dwButtonState |= m_mouseButtonState;
586
587     if (m_mouseInputEnabled && !m_quickEditEnabled) {
588         if (isTracingEnabled()) {
589             static bool debugInput = hasDebugFlag("input");
590             if (debugInput) {
591                 trace("mouse event: %s", mouseEventToString(mer).c_str());
592             }
593         }
594
595         records.push_back(newRecord);
596     }
597
598     return len;
599 }
600
601 void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
602                                   const char *charBuffer,
603                                   const int charLen,
604                                   const bool terminalAltEscape)
605 {
606     const uint32_t codePoint = decodeUtf8(charBuffer);
607     if (codePoint == static_cast<uint32_t>(-1)) {
608         static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
609         if (debugInput) {
610             StringBuilder error(64);
611             error << "Discarding invalid UTF-8 sequence:";
612             for (int i = 0; i < charLen; ++i) {
613                 error << ' ';
614                 error << hexOfInt<true, uint8_t>(charBuffer[i]);
615             }
616             trace("%s", error.c_str());
617         }
618         return;
619     }
620
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;
627
628     if (charScan != -1) {
629         virtualKey = charScan & 0xFF;
630         if (charScan & 0x100) {
631             winKeyState |= SHIFT_PRESSED;
632         }
633         if (charScan & 0x200) {
634             winKeyState |= LEFT_CTRL_PRESSED;
635         }
636         if (charScan & 0x400) {
637             winKeyState |= RIGHT_ALT_PRESSED;
638         }
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.
645             winCodePointDn = 0;
646             winCodePointUp = 0;
647         }
648     }
649     if (terminalAltEscape) {
650         winCodePointUp = 0;
651         winKeyState |= LEFT_ALT_PRESSED;
652         vtKeyState |= LEFT_ALT_PRESSED;
653     }
654
655     appendKeyPress(records, virtualKey,
656                    winCodePointDn, winCodePointUp, winKeyState,
657                    codePoint, vtKeyState);
658 }
659
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)
667 {
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;
674
675     if (isTracingEnabled()) {
676         static bool debugInput = hasDebugFlag("input");
677         if (debugInput) {
678             hasDebugInput = true;
679             InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
680             trace("keypress: %s", key.toString().c_str());
681         }
682     }
683
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);
693         if (hasDebugInput) {
694             trace("sending keypress to console HWND");
695         }
696         sendKeyMessage(m_console.hwnd(), true, virtualKey);
697         sendKeyMessage(m_console.hwnd(), false, virtualKey);
698         return;
699     }
700
701     uint16_t stepKeyState = 0;
702     if (ctrl) {
703         stepKeyState |= LEFT_CTRL_PRESSED;
704         appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
705     }
706     if (leftAlt) {
707         stepKeyState |= LEFT_ALT_PRESSED;
708         appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState);
709     }
710     if (rightAlt) {
711         stepKeyState |= RIGHT_ALT_PRESSED;
712         appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
713     }
714     if (shift) {
715         stepKeyState |= SHIFT_PRESSED;
716         appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState);
717     }
718     if (enhanced) {
719         stepKeyState |= ENHANCED_KEY;
720     }
721     if (m_escapeInputEnabled) {
722         reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
723     } else {
724         appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
725     }
726     appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState);
727     if (enhanced) {
728         stepKeyState &= ~ENHANCED_KEY;
729     }
730     if (shift) {
731         stepKeyState &= ~SHIFT_PRESSED;
732         appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
733     }
734     if (rightAlt) {
735         stepKeyState &= ~RIGHT_ALT_PRESSED;
736         appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
737     }
738     if (leftAlt) {
739         stepKeyState &= ~LEFT_ALT_PRESSED;
740         appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
741     }
742     if (ctrl) {
743         stepKeyState &= ~LEFT_CTRL_PRESSED;
744         appendInputRecord(records, FALSE, VK_CONTROL, 0, stepKeyState);
745     }
746 }
747
748 void ConsoleInput::appendCPInputRecords(std::vector<INPUT_RECORD> &records,
749                                         BOOL keyDown,
750                                         uint16_t virtualKey,
751                                         uint32_t codePoint,
752                                         uint16_t keyState)
753 {
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
759     // SIGN):
760     //
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
769     //
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.)
775     //
776     // For characters outside the BMP, Windows repeats the process for both
777     // UTF-16 code units, e.g, for U+1F300 (CYCLONE):
778     //
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
791     //
792     // In this case, it sends Alt+63 twice, which signifies '?'.  Apparently
793     // CMD and Cygwin bash are both able to decode this.
794     //
795     // Also note that typing Alt+NNN still works if NumLock is off, e.g.:
796     //
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
805     //
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?
809
810     wchar_t ws[2];
811     const int wslen = encodeUtf16(ws, codePoint);
812
813     if (wslen == 1) {
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);
818     } else {
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);
823     }
824 }
825
826 void ConsoleInput::appendInputRecord(std::vector<INPUT_RECORD> &records,
827                                      BOOL keyDown,
828                                      uint16_t virtualKey,
829                                      wchar_t utf16Char,
830                                      uint16_t keyState)
831 {
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);
842 }
843
844 DWORD ConsoleInput::inputConsoleMode()
845 {
846     DWORD mode = 0;
847     if (!GetConsoleMode(m_conin, &mode)) {
848         trace("GetConsoleMode failed");
849         return 0;
850     }
851     return mode;
852 }