1 /**
2 * @file win32/console.cpp
3 * @brief Win32 console I/O
4 *
5 * (c) 2013-2018 by Mega Limited, Auckland, New Zealand
6 *
7 * This file is part of the MEGA SDK - Client Access Engine.
8 *
9 * Applications using the MEGA API must present a valid application key
10 * and comply with the the rules set forth in the Terms of Service.
11 *
12 * The MEGA SDK is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * @copyright Simplified (2-clause) BSD License.
17 *
18 * You should have received a copy of the license along with this
19 * program.
20 */
21
22 #include "mega.h"
23 #include "megaapi.h"
24 #include <windows.h>
25 #include <conio.h>
26 #include <fstream>
27 #include <iomanip>
28
29 #include <io.h>
30 #include <fcntl.h>
31 #include <algorithm>
32 #include <string>
33 #include <cwctype>
34
35 namespace mega {
36
37 using namespace std;
38
39 #ifdef NO_READLINE
40 template<class T>
clamp(T v,T lo,T hi)41 static T clamp(T v, T lo, T hi)
42 {
43 // todo: switch to c++17 std version when we can
44 if (v < lo)
45 {
46 return lo;
47 }
48 else if (v > hi)
49 {
50 return hi;
51 }
52 else
53 {
54 return v;
55 }
56 }
57
toUtf8String(const std::wstring & ws,UINT codepage)58 std::string WinConsole::toUtf8String(const std::wstring& ws, UINT codepage)
59 {
60 std::string s;
61 s.resize((ws.size() + 1) * 4);
62 int nchars = WideCharToMultiByte(codepage, 0, ws.data(), int(ws.size()), (LPSTR)s.data(), int(s.size()), NULL, NULL);
63 s.resize(nchars);
64 return s;
65 }
66
toUtf16String(const std::string & s,UINT codepage)67 std::wstring WinConsole::toUtf16String(const std::string& s, UINT codepage)
68 {
69 std::wstring ws;
70 ws.resize(s.size() + 1);
71 int nwchars = MultiByteToWideChar(codepage, 0, s.data(), int(s.size()), (LPWSTR)ws.data(), int(ws.size()));
72 ws.resize(nwchars);
73 return ws;
74 }
75
wicmp(wchar_t a,wchar_t b)76 inline static bool wicmp(wchar_t a, wchar_t b)
77 {
78 return(towupper(a) == towupper(b));
79 }
80
81 struct Utf8Rdbuf : public streambuf
82 {
83 HANDLE h;
84 UINT codepage = CP_UTF8;
85 UINT failover_codepage = CP_UTF8;
86 std::ofstream logfile;
87 WinConsole::logstyle logstyle = WinConsole::no_log;
88 WinConsole* wc;
89
logmega::Utf8Rdbuf90 bool log(const string& localfile, WinConsole::logstyle ls)
91 {
92 logfile.close();
93 logstyle = WinConsole::no_log;
94
95 if (localfile.empty() && ls == WinConsole::no_log)
96 {
97 return true;
98 }
99
100 logfile.open(localfile.c_str(), ios::out | ios::binary | ios::trunc);
101 if (logfile.fail() || !logfile.is_open())
102 {
103 return false;
104 }
105
106 logstyle = ls;
107 return true;
108 }
109
Utf8Rdbufmega::Utf8Rdbuf110 Utf8Rdbuf(HANDLE ch, WinConsole* w) : h(ch), wc(w) {}
111
xsputnmega::Utf8Rdbuf112 streamsize xsputn(const char* s, streamsize n)
113 {
114 DWORD bn = DWORD(_Pnavail()), written = 0;
115 string s8(pbase(), bn);
116 pbump(-int(bn));
117 s8.append(s, size_t(n));
118
119 if (logstyle == WinConsole::utf8_log)
120 {
121 logfile << s8;
122 }
123
124 wstring ws = WinConsole::toUtf16String(s8);
125
126 if (logstyle == WinConsole::utf16_log)
127 {
128 logfile.write((const char*)ws.data(), ws.size() * sizeof(wchar_t));
129 }
130 else if (logstyle == WinConsole::codepage_log)
131 {
132 logfile << WinConsole::toUtf8String(ws, codepage);
133 }
134
135 if (wc)
136 {
137 wc->retractPrompt();
138 }
139
140 BOOL b = WriteConsoleW(h, ws.data(), DWORD(ws.size()), &written, NULL);
141 if (!b)
142 {
143 // The font can't display some characters (fails on windows 7 - windows 10 not so much but just in case).
144 // Output those that we can and indicate the others.
145 // If the user selects a suitable font then there should not be any failures.
146 for (unsigned i = 0; i < ws.size(); ++i)
147 {
148 b = WriteConsoleW(h, ws.data() + i, 1, &written, NULL);
149 if (!b && failover_codepage != codepage)
150 {
151 // for raster fonts, we can have a second go with another code page, translating directly from utf16
152 if (SetConsoleOutputCP(failover_codepage))
153 {
154 b = WriteConsoleW(h, ws.data() + i, 1, &written, NULL);
155 SetConsoleOutputCP(codepage);
156 }
157 }
158 if (!b)
159 {
160 wostringstream wos;
161 wos << L"<CHAR/" << hex << unsigned short(ws.data()[i]) << L">";
162 wstring str = wos.str();
163 WriteConsoleW(h, str.data(), DWORD(str.size()), &written, NULL);
164 }
165 }
166 }
167
168 return n;
169 }
170
overflowmega::Utf8Rdbuf171 int overflow(int c)
172 {
173 char cc = char(c);
174 xsputn(&cc, 1);
175 return c;
176 }
177
178 };
179
addInputChar(wchar_t c)180 void ConsoleModel::addInputChar(wchar_t c)
181 {
182 insertPos = clamp<size_t>(insertPos, 0, buffer.size());
183 if (c == 13)
184 {
185 buffer.push_back(c);
186 insertPos = buffer.size();
187 newlinesBuffered = true;
188 consoleNewlineNeeded = true;
189 searchingHistory = false;
190 historySearchString.clear();
191 }
192 else
193 {
194 if (searchingHistory)
195 {
196 historySearchString.push_back(c);
197 updateHistoryMatch(searchingHistoryForward, false);
198 }
199 else
200 {
201 buffer.insert(insertPos, 1, c);
202 insertPos += 1;
203 }
204 redrawInputLineNeeded = true;
205 }
206 #ifdef HAVE_AUTOCOMPLETE
207 autocompleteState.active = false;
208 #endif
209 }
210
getHistory(int index,int offset)211 void ConsoleModel::getHistory(int index, int offset)
212 {
213 if (inputHistory.empty() && offset == 1)
214 {
215 buffer.clear();
216 newlinesBuffered = false;
217 }
218 else
219 {
220 index = clamp<int>(index, 0, (int)inputHistory.size() - 1) + (enteredHistory ? offset : (offset == -1 ? -1 : 0));
221 if (index < 0 || index >= (int)inputHistory.size())
222 {
223 return;
224 }
225 inputHistoryIndex = index;
226 buffer = inputHistory[inputHistoryIndex];
227 enteredHistory = true;
228 newlinesBuffered = false;
229 }
230 insertPos = buffer.size();
231 redrawInputLineNeeded = true;
232 }
233
searchHistory(bool forwards)234 void ConsoleModel::searchHistory(bool forwards)
235 {
236 if (!searchingHistory)
237 {
238 searchingHistory = true;
239 searchingHistoryForward = forwards;
240 historySearchString.clear();
241 }
242 else
243 {
244 updateHistoryMatch(forwards, true);
245 }
246 redrawInputLineNeeded = true;
247 }
248
updateHistoryMatch(bool forwards,bool increment)249 void ConsoleModel::updateHistoryMatch(bool forwards, bool increment)
250 {
251 bool checking = false;
252 for (unsigned i = 0; i < inputHistory.size()*2; ++i)
253 {
254 size_t index = forwards ? inputHistory.size()*2 - i - 1 : i;
255 index %= inputHistory.size();
256 checking = checking || !enteredHistory || index == inputHistoryIndex;
257 if (checking && !(enteredHistory && increment && index == inputHistoryIndex))
258 {
259 auto iter = std::search(inputHistory[index].begin(), inputHistory[index].end(), historySearchString.begin(), historySearchString.end(), wicmp);
260 if (iter != inputHistory[index].end())
261 {
262 inputHistoryIndex = index;
263 enteredHistory = true;
264 buffer = inputHistory[index];
265 insertPos = buffer.size();
266 newlinesBuffered = false;
267 redrawInputLineNeeded = true;
268 break;
269 }
270 }
271 }
272 }
273
deleteHistorySearchChars(size_t n)274 void ConsoleModel::deleteHistorySearchChars(size_t n)
275 {
276 if (n == 0)
277 {
278 searchingHistory = false;
279 }
280 else
281 {
282 n = std::min<size_t>(n, historySearchString.size());
283 historySearchString.erase(historySearchString.size() - n, n);
284 updateHistoryMatch(searchingHistoryForward, false);
285 }
286 redrawInputLineNeeded = true;
287 }
288
redrawInputLine(int p)289 void ConsoleModel::redrawInputLine(int p)
290 {
291 insertPos = clamp<int>(p, 0, (int)buffer.size());
292 redrawInputLineNeeded = true;
293 }
294
autoComplete(bool forwards,unsigned consoleWidth)295 void ConsoleModel::autoComplete(bool forwards, unsigned consoleWidth)
296 {
297 #ifdef HAVE_AUTOCOMPLETE
298 if (autocompleteSyntax)
299 {
300 if (!autocompleteState.active)
301 {
302 std::string u8line = WinConsole::toUtf8String(buffer);
303 size_t u8InsertPos = WinConsole::toUtf8String(buffer.substr(0, insertPos)).size();
304 autocompleteState = autocomplete::autoComplete(u8line, u8InsertPos, autocompleteSyntax, unixCompletions);
305
306 if (autocompleteFunction)
307 {
308 // also get additional app specific options, and merge
309 std::vector<autocomplete::ACState::Completion> appcompletions = autocompleteFunction(WinConsole::toUtf8String(getInputLineToCursor()));
310 autocomplete::ACState acs;
311 acs.words.push_back(autocompleteState.originalWord);
312 acs.completions.swap(autocompleteState.completions);
313 for (auto& c : appcompletions)
314 {
315 acs.addCompletion(c.s, c.caseInsensitive, c.couldExtend);
316 }
317 autocompleteState.completions.swap(acs.completions);
318 autocompleteState.tidyCompletions();
319 }
320 autocompleteState.active = true;
321 }
322
323 autocomplete::applyCompletion(autocompleteState, forwards, consoleWidth, redrawInputLineConsoleFeedback);
324 buffer = WinConsole::toUtf16String(autocompleteState.line);
325 newlinesBuffered = false;
326 size_t u16InsertPos = WinConsole::toUtf16String(autocompleteState.line.substr(0, autocompleteState.wordPos.second)).size();
327 insertPos = clamp<size_t>(u16InsertPos, 0, buffer.size());
328 redrawInputLineNeeded = true;
329 }
330 #endif
331 }
332
isWordBoundary(size_t i,const std::wstring s)333 static bool isWordBoundary(size_t i, const std::wstring s)
334 {
335 return i == 0 || i >= s.size() || isspace(s[i - 1]) && !isspace(s[i + 1]);
336 }
337
detectWordBoundary(int start,bool forward)338 int ConsoleModel::detectWordBoundary(int start, bool forward)
339 {
340 start = clamp<int>(start, 0, (int)buffer.size());
341 do
342 {
343 start += (forward ? 1 : -1);
344 } while (!isWordBoundary(start, buffer));
345 return start;
346 }
347
deleteCharRange(int start,int end)348 void ConsoleModel::deleteCharRange(int start, int end)
349 {
350 start = clamp<int>(start, 0, (int)buffer.size());
351 end = clamp<int>(end, 0, (int)buffer.size());
352 if (start < end)
353 {
354 buffer.erase(start, end - start);
355 newlinesBuffered = buffer.find(13) != string::npos;
356 redrawInputLine(start);
357 }
358 }
359
performLineEditingAction(lineEditAction action,unsigned consoleWidth)360 void ConsoleModel::performLineEditingAction(lineEditAction action, unsigned consoleWidth)
361 {
362 #ifdef HAVE_AUTOCOMPLETE
363 if (action != AutoCompleteForwards && action != AutoCompleteBackwards)
364 {
365 autocompleteState.active = false;
366 }
367 #endif
368 if (action != HistorySearchForward && action != HistorySearchBackward && action != DeleteCharLeft && action != ClearLine)
369 {
370 searchingHistory = false;
371 }
372
373 int pos = (int)insertPos;
374 int bufSize = (int)buffer.size();
375
376 switch (action)
377 {
378 case CursorLeft: redrawInputLine(pos - 1); break;
379 case CursorRight: redrawInputLine(pos + 1); break;
380 case CursorStart: redrawInputLine(0); break;
381 case CursorEnd: redrawInputLine(bufSize); break;
382 case WordLeft: redrawInputLine((int)detectWordBoundary(pos, false)); break;
383 case WordRight: redrawInputLine((int)detectWordBoundary(pos, true)); break;
384 case HistoryUp: getHistory((int)inputHistoryIndex, 1); break;
385 case HistoryDown: getHistory((int)inputHistoryIndex, -1); break;
386 case HistoryStart: getHistory((int)inputHistory.size() - 1, 0); break;
387 case HistoryEnd: getHistory(0, 0); break;
388 case HistorySearchForward: searchHistory(true); break;
389 case HistorySearchBackward: searchHistory(false); break;
390 case ClearLine: searchingHistory ? deleteHistorySearchChars(0) : deleteCharRange(0, bufSize); break;
391 case DeleteCharLeft: searchingHistory ? deleteHistorySearchChars(1) : deleteCharRange(pos - 1, pos); break;
392 case DeleteCharRight: deleteCharRange(pos, pos + 1); break;
393 case DeleteWordLeft: deleteCharRange(detectWordBoundary(pos, false), pos); break;
394 case DeleteWordRight: deleteCharRange(pos, detectWordBoundary(pos, true)); break;
395 case AutoCompleteForwards: autoComplete(true, consoleWidth); break;
396 case AutoCompleteBackwards: autoComplete(false, consoleWidth); break;
397 }
398 }
399
checkForCompletedInputLine(std::wstring & ws)400 bool ConsoleModel::checkForCompletedInputLine(std::wstring& ws)
401 {
402 auto newlinePos = std::find(buffer.begin(), buffer.end(), 13);
403 if (newlinePos != buffer.end())
404 {
405 ws.assign(buffer.begin(), newlinePos);
406 buffer.erase(buffer.begin(), newlinePos + 1);
407 insertPos = 0;
408 newlinesBuffered = buffer.find(13) != string::npos;
409 bool sameAsLastCommand = !inputHistory.empty() && inputHistory[0] == ws;
410 bool sameAsChosenHistory = !inputHistory.empty() &&
411 inputHistoryIndex >= 0 && inputHistoryIndex < inputHistory.size() &&
412 inputHistory[inputHistoryIndex] == ws;
413 if (echoOn && !sameAsLastCommand && !ws.empty())
414 {
415 if (inputHistory.size() + 1 > MaxHistoryEntries)
416 {
417 inputHistory.pop_back();
418 }
419 inputHistory.push_front(ws);
420 inputHistoryIndex = sameAsChosenHistory ? inputHistoryIndex + 1 : -1;
421 }
422 enteredHistory = false;
423 return true;
424 }
425 newlinesBuffered = false;
426 return false;
427 }
428
getInputLineToCursor()429 std::wstring ConsoleModel::getInputLineToCursor()
430 {
431 insertPos = clamp<size_t>(insertPos, 0, buffer.size());
432 return buffer.substr(0, insertPos);
433 }
434 #endif
435
WinConsole()436 WinConsole::WinConsole()
437 {
438 #ifdef NO_READLINE
439 hInput = GetStdHandle(STD_INPUT_HANDLE);
440 hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
441
442 DWORD dwMode;
443 GetConsoleMode(hInput, &dwMode);
444 SetConsoleMode(hInput, dwMode & ~(ENABLE_MOUSE_INPUT));
445 FlushConsoleInputBuffer(hInput);
446 blockingConsolePeek = false;
447 #endif
448 }
449
~WinConsole()450 WinConsole::~WinConsole()
451 {
452 #ifdef NO_READLINE
453 if (rdbuf)
454 {
455 std::cout.rdbuf(oldrb1);
456 std::cerr.rdbuf(oldrb2);
457 delete rdbuf;
458 }
459 #endif
460 }
461
462 #ifdef NO_READLINE
getConsoleFont(COORD & size)463 string WinConsole::getConsoleFont(COORD& size)
464 {
465 CONSOLE_FONT_INFOEX cfi;
466 memset(&cfi, 0, sizeof(cfi));
467 cfi.cbSize = sizeof(cfi);
468 GetCurrentConsoleFontEx(hOutput, FALSE, &cfi);
469
470 wstring wname = cfi.FaceName;
471 string name = WinConsole::toUtf8String(wname);
472
473 if (!(cfi.FontFamily & TMPF_TRUETYPE) && (wname.size() < 6 || name.find("?") != string::npos))
474 {
475 // the name is garbled on win 7, try to compensate
476 name = "Terminal";
477 }
478
479 size = cfi.dwFontSize;
480 return name;
481 }
482
getShellCodepages(UINT & codepage,UINT & failover_codepage)483 void WinConsole::getShellCodepages(UINT& codepage, UINT& failover_codepage)
484 {
485 if (rdbuf)
486 {
487 codepage = rdbuf->codepage;
488 failover_codepage = rdbuf->failover_codepage;
489 }
490 else
491 {
492 codepage = GetConsoleOutputCP();
493 failover_codepage = codepage;
494 }
495 }
496
setShellConsole(UINT codepage,UINT failover_codepage)497 bool WinConsole::setShellConsole(UINT codepage, UINT failover_codepage)
498 {
499 // Call this if your console app is taking live input, with the user editing commands on screen, similar to cmd or powershell
500
501 // Ideally we would work in unicode all the time (with codepage = CP_UTF8). However in windows 7 for example, with raster
502 // font selected, the o symbol with diacritic (U+00F3) is not output correctly. So we offer the option to attempt output
503 // a second time in a 'failover' codepage, or to output in a single codepage only. The user has control with the 'codepage' command.
504
505 // use cases covered
506 // utf8 output with std::cout (since we already use cout so much and it's compatible with other platforms)
507 // unicode input with windows ReadConsoleInput api
508 // drag and drop filenames from explorer to the console window
509 // copy and paste unicode filenames from 'ls' output into your next command
510 // upload and download unicode/utf-8 filenames to/from Mega
511 // input a unicode/utf8 password without displaying anything
512 // normal cmd window type editing, including autocomplete (with runtime selectable unix style or dos style, default to local platform rules)
513 // the console must have a suitable font selected for the characters to diplay properly
514
515 BOOL ok = SetConsoleCP(codepage);
516 ok = ok && SetConsoleOutputCP(codepage);
517 if (!ok)
518 {
519 codepage = CP_UTF8;
520 failover_codepage = GetOEMCP();
521 SetConsoleCP(codepage);
522 SetConsoleOutputCP(codepage);
523 }
524
525 // skip the historic complexities of output modes etc, our own rdbuf can write direct to console
526 if (!rdbuf)
527 {
528 rdbuf = new Utf8Rdbuf(hOutput, this);
529 oldrb1 = std::cout.rdbuf(rdbuf);
530 oldrb2 = std::cerr.rdbuf(rdbuf);
531 }
532 rdbuf->codepage = codepage;
533 rdbuf->failover_codepage = failover_codepage;
534 return ok;
535 }
536
537 #ifdef HAVE_AUTOCOMPLETE
setAutocompleteSyntax(autocomplete::ACN a)538 void WinConsole::setAutocompleteSyntax(autocomplete::ACN a)
539 {
540 model.autocompleteSyntax = a;
541 }
542
setAutocompleteFunction(std::function<vector<autocomplete::ACState::Completion> (string)> f)543 void WinConsole::setAutocompleteFunction(std::function<vector<autocomplete::ACState::Completion>(string)> f)
544 {
545 model.autocompleteFunction = f;
546 }
547 #endif
548
setAutocompleteStyle(bool unix)549 void WinConsole::setAutocompleteStyle(bool unix)
550 {
551 model.unixCompletions = unix;
552 }
553
getAutocompleteStyle() const554 bool WinConsole::getAutocompleteStyle() const
555 {
556 return model.unixCompletions;
557 }
558
inputAvailableHandle()559 HANDLE WinConsole::inputAvailableHandle()
560 {
561 // returns a handle that will be signalled when there is console input to process (ie records available for PeekConsoleInput)
562 // client can wait on this handle with other handles as higher priority
563 return hInput;
564 }
565
consolePeek()566 bool WinConsole::consolePeek()
567 {
568 return blockingConsolePeek?consolePeekBlocking():consolePeekNonBlocking();
569 }
570
consolePeekNonBlocking()571 bool WinConsole::consolePeekNonBlocking()
572 {
573 std::cout << std::flush;
574
575 // Read keypreses up to the first newline (or multiple newlines if
576 bool checkPromptOnce = true;
577 for (;;)
578 {
579 INPUT_RECORD ir;
580 DWORD nRead;
581 BOOL ok = PeekConsoleInput(hInput, &ir, 1, &nRead); // peek first so we never wait
582 assert(ok);
583 if (!nRead)
584 {
585 break;
586 }
587
588 bool isCharacterGeneratingKeypress =
589 ir.EventType == 1 && ir.Event.KeyEvent.uChar.UnicodeChar != 0 &&
590 (ir.Event.KeyEvent.bKeyDown || // key press
591 (!ir.Event.KeyEvent.bKeyDown && ((ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) || ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU))); // key release that emits a unicode char
592
593 if (isCharacterGeneratingKeypress && (currentPrompt.empty() || model.newlinesBuffered))
594 {
595 break;
596 }
597
598 ok = ReadConsoleInputW(hInput, &ir, 1, &nRead); // discard the event record
599 assert(ok);
600 assert(nRead == 1);
601
602 ConsoleModel::lineEditAction action = interpretLineEditingKeystroke(ir);
603
604 if ((action != ConsoleModel::nullAction || isCharacterGeneratingKeypress) && checkPromptOnce)
605 {
606 redrawPromptIfLoggingOccurred();
607 checkPromptOnce = false;
608 }
609 if (action != ConsoleModel::nullAction)
610 {
611 CONSOLE_SCREEN_BUFFER_INFO sbi;
612 BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi);
613 assert(ok);
614 unsigned consoleWidth = ok ? sbi.dwSize.X : 50;
615
616 model.performLineEditingAction(action, consoleWidth);
617 }
618 else if (isCharacterGeneratingKeypress)
619 {
620 for (int i = ir.Event.KeyEvent.wRepeatCount; i--; )
621 {
622 model.addInputChar(ir.Event.KeyEvent.uChar.UnicodeChar);
623 }
624
625 if (model.newlinesBuffered)
626 {
627 break;
628 }
629 }
630 }
631 if (model.redrawInputLineNeeded && model.echoOn)
632 {
633 #ifdef HAVE_AUTOCOMPLETE
634 redrawInputLine(&model.redrawInputLineConsoleFeedback);
635 #else
636 redrawInputLine();
637 #endif
638 }
639 if (model.consoleNewlineNeeded)
640 {
641 DWORD written = 0;
642 #ifndef NDEBUG
643 BOOL b =
644 #endif
645 WriteConsoleW(hOutput, L"\n", 1, &written, NULL);
646 assert(b && written == 1);
647 }
648 model.redrawInputLineNeeded = false;
649 model.consoleNewlineNeeded = false;
650 return model.newlinesBuffered;
651 }
652
consolePeekBlocking()653 bool WinConsole::consolePeekBlocking()
654 {
655 std::cout << std::flush;
656
657 // Read keypreses up to the first newline (or multiple newlines if
658 bool checkPromptOnce = true;
659 bool isCharacterGeneratingKeypress = false;
660
661 INPUT_RECORD ir;
662 DWORD nRead;
663 if (!ReadConsoleInputW(hInput, &ir, 1, &nRead)) // discard the event record
664 {
665 return false;
666 }
667
668 irs.push_back(ir);
669
670 isCharacterGeneratingKeypress =
671 ir.EventType == 1 && ir.Event.KeyEvent.uChar.UnicodeChar != 0 &&
672 (ir.Event.KeyEvent.bKeyDown || // key press
673 (!ir.Event.KeyEvent.bKeyDown && ((ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) || ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU))); // key release that emits a unicode char
674
675 if (!(isCharacterGeneratingKeypress && (currentPrompt.empty() || model.newlinesBuffered)))
676 {
677 while(!irs.empty())
678 {
679 INPUT_RECORD &ir = irs.front();
680 ConsoleModel::lineEditAction action = interpretLineEditingKeystroke(ir);
681
682 if ((action != ConsoleModel::nullAction || isCharacterGeneratingKeypress) && checkPromptOnce)
683 {
684 redrawPromptIfLoggingOccurred();
685 checkPromptOnce = false;
686 }
687 if (action != ConsoleModel::nullAction)
688 {
689 CONSOLE_SCREEN_BUFFER_INFO sbi;
690 BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi);
691 assert(ok);
692 unsigned consoleWidth = ok ? sbi.dwSize.X : 50;
693
694 model.performLineEditingAction(action, consoleWidth);
695 }
696 else if (isCharacterGeneratingKeypress)
697 {
698 for (int i = ir.Event.KeyEvent.wRepeatCount; i--; )
699 {
700 model.addInputChar(ir.Event.KeyEvent.uChar.UnicodeChar);
701 }
702 if (model.newlinesBuffered) // todo: address case where multiple newlines were added from this one record (as we may get stuck in wait())
703 {
704 irs.pop_front();
705 break;
706 }
707 }
708 irs.pop_front();
709 }
710 }
711 if (model.redrawInputLineNeeded && model.echoOn)
712 {
713 #ifdef HAVE_AUTOCOMPLETE
714 redrawInputLine(&model.redrawInputLineConsoleFeedback);
715 #else
716 redrawInputLine();
717 #endif
718 }
719 if (model.consoleNewlineNeeded)
720 {
721 DWORD written = 0;
722 #ifndef NDEBUG
723 BOOL b =
724 #endif
725 WriteConsoleW(hOutput, L"\n", 1, &written, NULL);
726 assert(b && written == 1);
727 }
728 model.redrawInputLineNeeded = false;
729 model.consoleNewlineNeeded = false;
730 return model.newlinesBuffered;
731 }
732
interpretLineEditingKeystroke(INPUT_RECORD & ir)733 ConsoleModel::lineEditAction WinConsole::interpretLineEditingKeystroke(INPUT_RECORD &ir)
734 {
735 if (ir.EventType == 1 && ir.Event.KeyEvent.bKeyDown)
736 {
737 bool ctrl = ir.Event.KeyEvent.dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED);
738 bool shift = ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED;
739 switch (ir.Event.KeyEvent.wVirtualKeyCode)
740 {
741 case VK_LEFT: return ctrl ? ConsoleModel::WordLeft : ConsoleModel::CursorLeft;
742 case VK_RIGHT: return ctrl ? ConsoleModel::WordRight : ConsoleModel::CursorRight;
743 case VK_UP: return ConsoleModel::HistoryUp;
744 case VK_DOWN: return ConsoleModel::HistoryDown;
745 case VK_PRIOR: return ConsoleModel::HistoryStart; // pageup
746 case VK_NEXT: return ConsoleModel::HistoryEnd; // pagedown
747 case VK_HOME: return ConsoleModel::CursorStart;
748 case VK_END: return ConsoleModel::CursorEnd;
749 case VK_DELETE: return ConsoleModel::DeleteCharRight;
750 case VK_INSERT: return ConsoleModel::Paste; // the OS takes care of this; we don't see it
751 case VK_CONTROL: break;
752 case VK_SHIFT: break;
753 case 's':
754 case 'S': return ctrl ? (shift ? ConsoleModel::HistorySearchBackward : ConsoleModel::HistorySearchForward) : ConsoleModel::nullAction;
755 case 'r':
756 case 'R': return ctrl ? (shift ? ConsoleModel::HistorySearchForward : ConsoleModel::HistorySearchBackward) : ConsoleModel::nullAction;
757 default:
758 switch (ir.Event.KeyEvent.uChar.UnicodeChar)
759 {
760 case '\b': return ConsoleModel::DeleteCharLeft;
761 case '\t': return shift ? ConsoleModel::AutoCompleteBackwards : ConsoleModel::AutoCompleteForwards;
762 case VK_ESCAPE: return ConsoleModel::ClearLine;
763 default:
764 break;
765 }
766 break;
767 }
768 }
769 return ConsoleModel::nullAction;
770 }
771
772 #ifdef HAVE_AUTOCOMPLETE
redrawInputLine(::mega::autocomplete::CompletionTextOut * autocompleteFeedback=nullptr)773 void WinConsole::redrawInputLine(::mega::autocomplete::CompletionTextOut* autocompleteFeedback = nullptr)
774 #else
775 void WinConsole::redrawInputLine()
776 #endif
777
778 {
779 CONSOLE_SCREEN_BUFFER_INFO sbi;
780
781 #ifdef HAVE_AUTOCOMPLETE
782 if (autocompleteFeedback && !autocompleteFeedback->stringgrid.empty())
783 {
784 promptRetracted = true;
785 cout << "\n" << std::flush;
786 for (auto& r : autocompleteFeedback->stringgrid)
787 {
788 int x = 0;
789 for (unsigned c = 0; c < r.size(); ++c)
790 {
791 cout << r[c] << std::flush;
792 if (c + 1 == r.size())
793 {
794 cout << "\n" << std::flush;
795 }
796 else
797 {
798 x += autocompleteFeedback->columnwidths[c];
799
800 // to make the grid nice in the presence of unicode characters that are sometimes double-width glyphs, we set the X coordinate explicitly
801 BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi);
802 if (ok && sbi.dwCursorPosition.X < x)
803 {
804 sbi.dwCursorPosition.X = short(x);
805 SetConsoleCursorPosition(hOutput, sbi.dwCursorPosition);
806 }
807 }
808 }
809 }
810 autocompleteFeedback->stringgrid.clear();
811 autocompleteFeedback->columnwidths.clear();
812 }
813 #endif
814
815 BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi);
816 assert(ok);
817 if (ok)
818 {
819 std::string sprompt = model.searchingHistory ? ("history-" + std::string(model.searchingHistoryForward ? "F:'" : "R:'") + toUtf8String(model.historySearchString) + "'> ")
820 : currentPrompt;
821 std::wstring wprompt = toUtf16String(sprompt);
822
823 if (long(wprompt.size() + model.buffer.size() + 1) < sbi.dwSize.X || !model.echoOn)
824 {
825 inputLineOffset = 0;
826 }
827 else
828 {
829 // scroll the line if the cursor reaches the end, or moves back within 15 of the start
830 size_t showleft = 15;
831 if (inputLineOffset + showleft >= model.insertPos)
832 {
833 inputLineOffset = model.insertPos - std::min<size_t>(showleft, model.insertPos);
834 } else if (wprompt.size() + model.insertPos + 1 >= inputLineOffset + sbi.dwSize.X)
835 {
836 inputLineOffset = wprompt.size() + model.insertPos + 1 - sbi.dwSize.X;
837 }
838 }
839
840 size_t width = std::max<size_t>(wprompt.size() + model.buffer.size() + 1 + inputLineOffset, sbi.dwSize.X); // +1 to show character under cursor
841 std::unique_ptr<CHAR_INFO[]> line(new CHAR_INFO[width]);
842
843 for (size_t i = width; i--; )
844 {
845 line[i].Attributes = sbi.wAttributes;
846 if (i < inputLineOffset)
847 {
848 line[i].Char.UnicodeChar = ' ';
849 }
850 else if (inputLineOffset && i + 1 == inputLineOffset + wprompt.size())
851 {
852 line[i].Char.UnicodeChar = '|';
853 line[i].Attributes |= FOREGROUND_INTENSITY | FOREGROUND_GREEN;
854 line[i].Attributes &= ~(FOREGROUND_RED | FOREGROUND_BLUE);
855 }
856 else if (i < inputLineOffset + wprompt.size())
857 {
858 line[i].Char.UnicodeChar = wprompt[i - inputLineOffset];
859 line[i].Attributes |= FOREGROUND_INTENSITY;
860 }
861 else if (i < wprompt.size() + model.buffer.size() && model.echoOn)
862 {
863 line[i].Char.UnicodeChar = model.buffer[i - wprompt.size()];
864 }
865 else
866 {
867 line[i].Char.UnicodeChar = ' ';
868 }
869 }
870
871 SMALL_RECT screenarea2{ 0, sbi.dwCursorPosition.Y, sbi.dwSize.X, sbi.dwCursorPosition.Y };
872 ok = WriteConsoleOutputW(hOutput, line.get(), COORD{ SHORT(width), 1 }, COORD{ SHORT(inputLineOffset), 0 }, &screenarea2);
873 assert(ok);
874
875 COORD cpos{ SHORT(wprompt.size() + model.insertPos - inputLineOffset), sbi.dwCursorPosition.Y };
876 ok = SetConsoleCursorPosition(hOutput, cpos);
877 assert(ok);
878
879 promptRetracted = false;
880 }
881 }
882
retractPrompt()883 void WinConsole::retractPrompt()
884 {
885 if (currentPrompt.size() && !promptRetracted)
886 {
887 CONSOLE_SCREEN_BUFFER_INFO sbi;
888 BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi);
889 assert(ok);
890 //if (0 == memcmp(&knownCursorPos, &sbi.dwCursorPosition, sizeof(COORD)))
891 {
892 size_t width = std::max<size_t>(currentPrompt.size() + model.buffer.size() + 1 + inputLineOffset, sbi.dwSize.X); // +1 to show character under cursor
893 std::unique_ptr<CHAR_INFO[]> line(new CHAR_INFO[width]);
894
895 for (size_t i = width; i--; )
896 {
897 line[i].Attributes = sbi.wAttributes;
898 line[i].Char.UnicodeChar = ' ';
899 }
900
901 SMALL_RECT screenarea2{ 0, sbi.dwCursorPosition.Y, sbi.dwSize.X, sbi.dwCursorPosition.Y };
902 ok = WriteConsoleOutputW(hOutput, line.get(), COORD{ SHORT(width), 1 }, COORD{ SHORT(inputLineOffset), 0 }, &screenarea2);
903 assert(ok);
904
905 COORD cpos{ 0, sbi.dwCursorPosition.Y };
906 ok = SetConsoleCursorPosition(hOutput, cpos);
907 assert(ok);
908
909 promptRetracted = true;
910 }
911 }
912 }
913
getInputLineToCursor()914 wstring WinConsole::getInputLineToCursor()
915 {
916 return model.getInputLineToCursor();
917 }
918
consoleGetch(wchar_t & c)919 bool WinConsole::consoleGetch(wchar_t& c)
920 {
921 // todo: remove this function once we don't need to support readline for any version of megacli on windows
922 if (consolePeek())
923 {
924 c = model.buffer.front();
925 model.buffer.erase(0, 1);
926 model.newlinesBuffered = model.buffer.find(13) != string::npos;
927 return true;
928 }
929 return false;
930 }
931 #endif
readpwchar(char * pw_buf,int pw_buf_size,int * pw_buf_pos,char ** line)932 void WinConsole::readpwchar(char* pw_buf, int pw_buf_size, int* pw_buf_pos, char** line)
933 {
934 #ifdef NO_READLINE
935 // todo: remove/stub this function once we don't need to support readline for any version of megacli on windows
936 wchar_t c;
937 if (consoleGetch(c)) // only processes once newline is buffered, so no backspace processing needed
938 {
939 if (c == 13)
940 {
941 *line = _strdup(toUtf8String(wstring(&c, 1)).c_str());
942 memset(pw_buf, 0, pw_buf_size);
943 }
944 else if (*pw_buf_pos + 2 <= pw_buf_size)
945 {
946 *(wchar_t*)(pw_buf + *pw_buf_pos) = c;
947 *pw_buf_pos += 2;
948 }
949 }
950 #else
951
952 char c;
953 DWORD cread;
954
955 if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &cread, NULL) == 1)
956 {
957 if ((c == 8) && *pw_buf_pos)
958 {
959 (*pw_buf_pos)--;
960 }
961 else if (c == 13)
962 {
963 *line = (char*)malloc(*pw_buf_pos + 1);
964 memcpy(*line, pw_buf, *pw_buf_pos);
965 (*line)[*pw_buf_pos] = 0;
966 }
967 else if (*pw_buf_pos < pw_buf_size)
968 {
969 pw_buf[(*pw_buf_pos)++] = c;
970 }
971 }
972 #endif
973 }
974
setecho(bool echo)975 void WinConsole::setecho(bool echo)
976 {
977 #ifdef NO_READLINE
978 model.echoOn = echo;
979 #else
980
981 HANDLE hCon = GetStdHandle(STD_INPUT_HANDLE);
982 DWORD mode;
983
984 GetConsoleMode(hCon, &mode);
985
986 if (echo)
987 {
988 mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT;
989 }
990 else
991 {
992 mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
993 }
994
995 SetConsoleMode(hCon, mode);
996 #endif
997 }
998
999 #ifdef NO_READLINE
redrawPromptIfLoggingOccurred()1000 void WinConsole::redrawPromptIfLoggingOccurred()
1001 {
1002 if (promptRetracted)
1003 {
1004 redrawInputLine();
1005 }
1006 }
1007
updateInputPrompt(const std::string & newprompt)1008 void WinConsole::updateInputPrompt(const std::string& newprompt)
1009 {
1010 cout << std::flush;
1011 currentPrompt = newprompt;
1012 redrawInputLine();
1013 }
1014
checkForCompletedInputLine()1015 char* WinConsole::checkForCompletedInputLine()
1016 {
1017 if (rdbuf && rdbuf->logstyle != WinConsole::no_log)
1018 {
1019 rdbuf->logfile << flush;
1020 }
1021 redrawPromptIfLoggingOccurred();
1022 if (consolePeek())
1023 {
1024 std::wstring ws;
1025 if (model.checkForCompletedInputLine(ws))
1026 {
1027
1028 if (rdbuf && rdbuf->logstyle == WinConsole::utf16_log)
1029 {
1030 std::wstring wprompt = toUtf16String(currentPrompt);
1031 rdbuf->logfile.write((const char*)wprompt.data(), wprompt.size() * sizeof(wchar_t));
1032 rdbuf->logfile.write((const char*)ws.data(), ws.size() * sizeof(wchar_t));
1033 rdbuf->logfile.write((const char*)(const wchar_t*)L"\n", sizeof(wchar_t));
1034 }
1035
1036 string u8s = toUtf8String(ws);
1037
1038 if (rdbuf && rdbuf->logstyle == WinConsole::utf8_log)
1039 {
1040 rdbuf->logfile << currentPrompt << u8s << "\n";
1041 }
1042 else if (rdbuf && rdbuf->logstyle == WinConsole::codepage_log)
1043 {
1044 rdbuf->logfile << currentPrompt << toUtf8String(ws, rdbuf->codepage) << "\n";
1045 }
1046
1047 currentPrompt.clear();
1048
1049 return _strdup(u8s.c_str());
1050 }
1051 }
1052 return NULL;
1053 }
1054
clearScreen()1055 void WinConsole::clearScreen()
1056 {
1057 CONSOLE_SCREEN_BUFFER_INFO csbi;
1058 BOOL ok = GetConsoleScreenBufferInfo(hOutput, &csbi);
1059 assert(ok);
1060 if (ok)
1061 {
1062 // Fill the entire buffer with spaces
1063 DWORD count;
1064 ok = FillConsoleOutputCharacter(hOutput, (TCHAR) ' ', csbi.dwSize.X *csbi.dwSize.Y, { 0, 0 }, &count);
1065 assert(ok);
1066
1067 // Fill the entire buffer with the current colors and attributes
1068 ok = FillConsoleOutputAttribute(hOutput, csbi.wAttributes, csbi.dwSize.X *csbi.dwSize.Y, { 0, 0 }, &count);
1069 assert(ok);
1070 }
1071 ok = SetConsoleCursorPosition(hOutput, { 0, 0 });
1072 assert(ok);
1073 currentPrompt.clear();
1074 }
1075
outputHistory()1076 void WinConsole::outputHistory()
1077 {
1078 for (size_t i = model.inputHistory.size(); i--; )
1079 {
1080 std::cout << toUtf8String(model.inputHistory[i]) << std::endl;
1081 }
1082 }
1083
log(const std::string & filename,logstyle logstyle)1084 bool WinConsole::log(const std::string& filename, logstyle logstyle)
1085 {
1086 return rdbuf->log(filename, logstyle);
1087 }
1088 #endif
1089 } // namespace
1090