1 // Copyright (c) 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 "DebugShowInput.h"
30 #include "../shared/StringBuilder.h"
40 static const Flag kButtonStates[] = {
41 { FROM_LEFT_1ST_BUTTON_PRESSED, "1" },
42 { FROM_LEFT_2ND_BUTTON_PRESSED, "2" },
43 { FROM_LEFT_3RD_BUTTON_PRESSED, "3" },
44 { FROM_LEFT_4TH_BUTTON_PRESSED, "4" },
45 { RIGHTMOST_BUTTON_PRESSED, "R" },
48 static const Flag kControlKeyStates[] = {
49 { CAPSLOCK_ON, "CapsLock" },
50 { ENHANCED_KEY, "Enhanced" },
51 { LEFT_ALT_PRESSED, "LAlt" },
52 { LEFT_CTRL_PRESSED, "LCtrl" },
53 { NUMLOCK_ON, "NumLock" },
54 { RIGHT_ALT_PRESSED, "RAlt" },
55 { RIGHT_CTRL_PRESSED, "RCtrl" },
56 { SCROLLLOCK_ON, "ScrollLock" },
57 { SHIFT_PRESSED, "Shift" },
60 static const Flag kMouseEventFlags[] = {
61 { DOUBLE_CLICK, "Double" },
62 { 8/*MOUSE_HWHEELED*/, "HWheel" },
63 { MOUSE_MOVED, "Move" },
64 { MOUSE_WHEELED, "Wheel" },
67 static void writeFlags(StringBuilder &out, DWORD flags,
68 const char *remainderName,
69 const Flag *table, size_t tableSize,
70 char pre, char sep, char post) {
71 DWORD remaining = flags;
72 bool wroteSomething = false;
73 for (size_t i = 0; i < tableSize; ++i) {
74 const Flag &f = table[i];
75 if ((f.value & flags) == f.value) {
76 if (!wroteSomething && pre != '\0') {
78 } else if (wroteSomething && sep != '\0') {
82 wroteSomething = true;
83 remaining &= ~f.value;
87 if (!wroteSomething && pre != '\0') {
89 } else if (wroteSomething && sep != '\0') {
92 out << remainderName << "(0x" << hexOfInt(remaining) << ')';
93 wroteSomething = true;
95 if (wroteSomething && post != '\0') {
101 static void writeFlags(StringBuilder &out, DWORD flags,
102 const char *remainderName,
103 const Flag (&table)[n],
104 char pre, char sep, char post) {
105 writeFlags(out, flags, remainderName, table, n, pre, sep, post);
108 } // anonymous namespace
110 std::string controlKeyStatePrefix(DWORD controlKeyState) {
112 writeFlags(sb, controlKeyState,
113 "keyState", kControlKeyStates, '\0', '-', '-');
114 return sb.str_moved();
117 std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer) {
118 const uint16_t buttons = mer.dwButtonState & 0xFFFF;
119 const int16_t wheel = mer.dwButtonState >> 16;
121 sb << "pos=" << mer.dwMousePosition.X << ','
122 << mer.dwMousePosition.Y;
123 writeFlags(sb, mer.dwControlKeyState, "keyState", kControlKeyStates, ' ', ' ', '\0');
124 writeFlags(sb, mer.dwEventFlags, "flags", kMouseEventFlags, ' ', ' ', '\0');
125 writeFlags(sb, buttons, "buttons", kButtonStates, ' ', ' ', '\0');
127 sb << " wheel=" << wheel;
129 return sb.str_moved();
132 void debugShowInput(bool enableMouse, bool escapeInput) {
133 HANDLE conin = GetStdHandle(STD_INPUT_HANDLE);
134 DWORD origConsoleMode = 0;
135 if (!GetConsoleMode(conin, &origConsoleMode)) {
136 fprintf(stderr, "Error: could not read console mode -- "
137 "is STDIN a console handle?\n");
140 DWORD restoreConsoleMode = origConsoleMode;
141 if (enableMouse && !(restoreConsoleMode & ENABLE_EXTENDED_FLAGS)) {
142 // We need to disable QuickEdit mode, because it blocks mouse events.
143 // If ENABLE_EXTENDED_FLAGS wasn't originally in the console mode, then
144 // we have no way of knowning whether QuickEdit or InsertMode are
145 // currently enabled. Enable them both (eventually), because they're
146 // sensible defaults. This case shouldn't happen typically. See
147 // misc/EnableExtendedFlags.txt.
148 restoreConsoleMode |= ENABLE_EXTENDED_FLAGS;
149 restoreConsoleMode |= ENABLE_QUICK_EDIT_MODE;
150 restoreConsoleMode |= ENABLE_INSERT_MODE;
152 DWORD newConsoleMode = restoreConsoleMode;
153 newConsoleMode &= ~ENABLE_PROCESSED_INPUT;
154 newConsoleMode &= ~ENABLE_LINE_INPUT;
155 newConsoleMode &= ~ENABLE_ECHO_INPUT;
156 newConsoleMode |= ENABLE_WINDOW_INPUT;
158 newConsoleMode |= ENABLE_MOUSE_INPUT;
159 newConsoleMode &= ~ENABLE_QUICK_EDIT_MODE;
161 newConsoleMode &= ~ENABLE_MOUSE_INPUT;
164 // As of this writing (2016-06-05), Microsoft has shipped two preview
165 // builds of Windows 10 (14316 and 14342) that include a new "Windows
166 // Subsystem for Linux" that runs Ubuntu in a new subsystem. Running
167 // bash in this subsystem requires the non-legacy console mode, and the
168 // console input buffer is put into a special mode where escape
169 // sequences are written into the console input buffer. This mode is
170 // enabled with the 0x200 flag, which is as-yet undocumented.
171 // See https://github.com/rprichard/winpty/issues/82.
172 newConsoleMode |= 0x200;
174 if (!SetConsoleMode(conin, newConsoleMode)) {
175 fprintf(stderr, "Error: could not set console mode "
176 "(0x%x -> 0x%x -> 0x%x)\n",
177 static_cast<unsigned int>(origConsoleMode),
178 static_cast<unsigned int>(newConsoleMode),
179 static_cast<unsigned int>(restoreConsoleMode));
182 printf("\nPress any keys -- Ctrl-D exits\n\n");
183 INPUT_RECORD records[32];
185 bool finished = false;
187 ReadConsoleInputW(conin, records, 32, &actual) && actual >= 1) {
189 for (DWORD i = 0; i < actual; ++i) {
190 const INPUT_RECORD &record = records[i];
191 if (record.EventType == KEY_EVENT) {
192 const KEY_EVENT_RECORD &ker = record.Event.KeyEvent;
193 InputMap::Key key = {
195 ker.uChar.UnicodeChar,
196 static_cast<uint16_t>(ker.dwControlKeyState),
198 sb << "key: " << (ker.bKeyDown ? "dn" : "up")
199 << " rpt=" << ker.wRepeatCount
200 << " scn=" << (ker.wVirtualScanCode ? "0x" : "") << hexOfInt(ker.wVirtualScanCode)
201 << ' ' << key.toString() << '\n';
202 if ((ker.dwControlKeyState &
203 (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) &&
204 ker.wVirtualKeyCode == 'D') {
207 } else if (ker.wVirtualKeyCode == 0 &&
208 ker.wVirtualScanCode == 0 &&
209 ker.uChar.UnicodeChar == 4) {
210 // Also look for a zeroed-out Ctrl-D record generated for
211 // ENABLE_VIRTUAL_TERMINAL_INPUT.
215 } else if (record.EventType == MOUSE_EVENT) {
216 const MOUSE_EVENT_RECORD &mer = record.Event.MouseEvent;
217 sb << "mouse: " << mouseEventToString(mer) << '\n';
218 } else if (record.EventType == WINDOW_BUFFER_SIZE_EVENT) {
219 const WINDOW_BUFFER_SIZE_RECORD &wbsr =
220 record.Event.WindowBufferSizeEvent;
221 sb << "buffer-resized: dwSize=("
222 << wbsr.dwSize.X << ','
223 << wbsr.dwSize.Y << ")\n";
224 } else if (record.EventType == MENU_EVENT) {
225 const MENU_EVENT_RECORD &mer = record.Event.MenuEvent;
226 sb << "menu-event: commandId=0x"
227 << hexOfInt(mer.dwCommandId) << '\n';
228 } else if (record.EventType == FOCUS_EVENT) {
229 const FOCUS_EVENT_RECORD &fer = record.Event.FocusEvent;
230 sb << "focus: " << (fer.bSetFocus ? "gained" : "lost") << '\n';
234 const auto str = sb.str_moved();
235 fwrite(str.data(), 1, str.size(), stdout);
238 SetConsoleMode(conin, restoreConsoleMode);