Main.cpp 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. //---------------------------------------------------------------------------
  2. #include <stdexcept>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <windows.h>
  6. #include <io.h>
  7. #include <fcntl.h>
  8. #include "Console.h"
  9. #define MAX_ATTEMPTS 10
  10. //---------------------------------------------------------------------------
  11. #define LENOF(x) ( (sizeof((x))) / (sizeof(*(x))))
  12. //---------------------------------------------------------------------------
  13. using namespace std;
  14. static HANDLE ConsoleInput = nullptr;
  15. static HANDLE ConsoleOutput = nullptr;
  16. static HANDLE ConsoleStandardOutput = nullptr;
  17. static HANDLE ConsoleErrorOutput = nullptr;
  18. static TConsoleCommStruct::TInitEvent::STDINOUT OutputFormat;
  19. static TConsoleCommStruct::TInitEvent::STDINOUT InputFormat;
  20. static HANDLE Child = nullptr;
  21. static HANDLE CancelEvent = nullptr;
  22. static HANDLE InputTimerEvent = nullptr;
  23. static bool SupportsUtf8ConsoleOutput = false;
  24. static unsigned int OutputType = FILE_TYPE_UNKNOWN;
  25. static unsigned int InputType = FILE_TYPE_UNKNOWN;
  26. enum { RESULT_GLOBAL_ERROR = 1, RESULT_INIT_ERROR = 2, RESULT_PROCESSING_ERROR = 3,
  27. RESULT_UNKNOWN_ERROR = 4 };
  28. static const wchar_t* CONSOLE_CHILD_PARAM = L"consolechild";
  29. //---------------------------------------------------------------------------
  30. inline TConsoleCommStruct* GetCommStruct(HANDLE FileMapping)
  31. {
  32. TConsoleCommStruct* Result;
  33. Result = static_cast<TConsoleCommStruct*>(MapViewOfFile(FileMapping,
  34. FILE_MAP_ALL_ACCESS, 0, 0, 0));
  35. if (Result == nullptr)
  36. {
  37. throw runtime_error("Cannot open mapping object.");
  38. }
  39. return Result;
  40. }
  41. //---------------------------------------------------------------------------
  42. inline void FreeCommStruct(TConsoleCommStruct* CommStruct)
  43. {
  44. UnmapViewOfFile(CommStruct);
  45. }
  46. //---------------------------------------------------------------------------
  47. static void InitializeConsole(
  48. wchar_t* InstanceName, HANDLE& RequestEvent, HANDLE& ResponseEvent, HANDLE& ACancelEvent, HANDLE& FileMapping, HANDLE& Job)
  49. {
  50. unsigned int Process = GetCurrentProcessId();
  51. int Attempts = 0;
  52. wchar_t Name[MAX_PATH];
  53. bool UniqEvent;
  54. do
  55. {
  56. if (Attempts > MAX_ATTEMPTS)
  57. {
  58. throw runtime_error("Cannot find unique name for event object.");
  59. }
  60. int InstanceNumber;
  61. #ifdef CONSOLE_TEST
  62. InstanceNumber = 1;
  63. #else
  64. InstanceNumber = random(1000);
  65. #endif
  66. swprintf(InstanceName, L"_%u_%d", Process, InstanceNumber);
  67. swprintf(Name, L"%s%s", CONSOLE_EVENT_REQUEST, InstanceName);
  68. HANDLE EventHandle = OpenEvent(EVENT_ALL_ACCESS, false, Name);
  69. UniqEvent = (EventHandle == nullptr);
  70. if (!UniqEvent)
  71. {
  72. CloseHandle(EventHandle);
  73. }
  74. Attempts++;
  75. }
  76. while (!UniqEvent);
  77. RequestEvent = CreateEvent(nullptr, false, false, Name);
  78. if (RequestEvent == nullptr)
  79. {
  80. throw runtime_error("Cannot create request event object.");
  81. }
  82. swprintf(Name, L"%s%s", CONSOLE_EVENT_RESPONSE, InstanceName);
  83. ResponseEvent = CreateEvent(nullptr, false, false, Name);
  84. if (ResponseEvent == nullptr)
  85. {
  86. throw runtime_error("Cannot create response event object.");
  87. }
  88. swprintf(Name, L"%s%s", CONSOLE_EVENT_CANCEL, InstanceName);
  89. ACancelEvent = CreateEvent(nullptr, false, false, Name);
  90. if (ACancelEvent == nullptr)
  91. {
  92. throw runtime_error("Cannot create cancel event object.");
  93. }
  94. swprintf(Name, L"%s%s", CONSOLE_MAPPING, InstanceName);
  95. FileMapping =
  96. CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(TConsoleCommStruct), Name);
  97. if (FileMapping == nullptr)
  98. {
  99. throw runtime_error("Cannot create mapping object.");
  100. }
  101. swprintf(Name, L"%s%s", CONSOLE_JOB, InstanceName);
  102. Job = CreateJobObject(nullptr, Name);
  103. if (Job == nullptr)
  104. {
  105. throw runtime_error("Cannot create job object.");
  106. }
  107. JOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimitInformation;
  108. memset(&ExtendedLimitInformation, 0, sizeof(ExtendedLimitInformation));
  109. ExtendedLimitInformation.BasicLimitInformation.LimitFlags =
  110. JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
  111. if (SetInformationJobObject(Job, JobObjectExtendedLimitInformation,
  112. &ExtendedLimitInformation, sizeof(ExtendedLimitInformation)) == 0)
  113. {
  114. CloseHandle(Job);
  115. Job = nullptr;
  116. }
  117. TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
  118. CommStruct->Size = sizeof(TConsoleCommStruct);
  119. CommStruct->Version = TConsoleCommStruct::CurrentVersion;
  120. CommStruct->Event = TConsoleCommStruct::NONE;
  121. FreeCommStruct(CommStruct);
  122. }
  123. //---------------------------------------------------------------------------
  124. // duplicated in Common.cpp
  125. static bool CutToken(const wchar_t*& Str, wchar_t* Token)
  126. {
  127. bool Result;
  128. // inspired by Putty's sftp_getcmd() from PSFTP.C
  129. size_t Length = wcslen(Str);
  130. size_t Index = 0;
  131. while ((Index < Length) &&
  132. ((Str[Index] == L' ') || (Str[Index] == L'\t')))
  133. {
  134. Index++;
  135. }
  136. if (Index < Length)
  137. {
  138. bool Quoting = false;
  139. while (Index < Length)
  140. {
  141. if (!Quoting && ((Str[Index] == L' ') || (Str[Index] == L'\t')))
  142. {
  143. break;
  144. }
  145. else if ((Str[Index] == L'"') && (Index + 1 < Length) &&
  146. (Str[Index + 1] == L'"'))
  147. {
  148. Index += 2;
  149. *Token = L'"';
  150. Token++;
  151. }
  152. else if (Str[Index] == L'"')
  153. {
  154. Index++;
  155. Quoting = !Quoting;
  156. }
  157. else
  158. {
  159. *Token = Str[Index];
  160. Token++;
  161. Index++;
  162. }
  163. }
  164. if (Index < Length)
  165. {
  166. Index++;
  167. }
  168. Str += Index;
  169. Result = true;
  170. }
  171. else
  172. {
  173. Result = false;
  174. Str += Length;
  175. }
  176. *Token = L'\0';
  177. return Result;
  178. }
  179. //---------------------------------------------------------------------------
  180. static char* WideStringToString(const wchar_t* Message)
  181. {
  182. char* Buffer;
  183. int Size = WideCharToMultiByte(CP_UTF8, 0, Message, -1, nullptr, 0, nullptr, nullptr);
  184. if (Size > 0)
  185. {
  186. Buffer = new char[(static_cast<size_t>(Size) * 2) + 1];
  187. if (WideCharToMultiByte(CP_UTF8, 0, Message, -1, Buffer, Size, nullptr, nullptr) > 0)
  188. {
  189. Buffer[Size] = '\0';
  190. }
  191. else
  192. {
  193. delete[] Buffer;
  194. Buffer = nullptr;
  195. }
  196. }
  197. else
  198. {
  199. Buffer = nullptr;
  200. }
  201. return Buffer;
  202. }
  203. //---------------------------------------------------------------------------
  204. static void GetProductVersion(wchar_t* ProductVersion)
  205. {
  206. wchar_t Buffer[MAX_PATH];
  207. DWORD ModuleNameLen = GetModuleFileName(nullptr, Buffer, MAX_PATH);
  208. if ((ModuleNameLen == 0) || (ModuleNameLen == MAX_PATH))
  209. {
  210. throw runtime_error("Error retrieving executable name.");
  211. }
  212. ProductVersion[0] = '\0';
  213. unsigned long Handle;
  214. unsigned int Size = GetFileVersionInfoSize(Buffer, &Handle);
  215. if (Size > 0)
  216. {
  217. char * VersionInfo = new char[Size];
  218. VS_FIXEDFILEINFO* FixedFileInfo;
  219. unsigned int Length;
  220. if (GetFileVersionInfo(Buffer, Handle, Size, VersionInfo))
  221. {
  222. if (VerQueryValue(VersionInfo, L"\\", reinterpret_cast<void**>(&FixedFileInfo), &Length))
  223. {
  224. int ProductMajor = HIWORD(FixedFileInfo->dwProductVersionMS);
  225. int ProductMinor = LOWORD(FixedFileInfo->dwProductVersionMS);
  226. int ProductBuild = HIWORD(FixedFileInfo->dwProductVersionLS);
  227. if ((ProductMajor >= 1) && (ProductMajor <= 99) &&
  228. (ProductMinor >= 0) && (ProductMinor <= 99) &&
  229. (ProductBuild >= 0) && (ProductBuild <= 99))
  230. {
  231. wsprintf(ProductVersion, L"%d.%d.%d", ProductMajor, ProductMinor, ProductBuild);
  232. }
  233. }
  234. }
  235. delete[] VersionInfo;
  236. }
  237. if (ProductVersion[0] == L'\0')
  238. {
  239. throw runtime_error("Error retrieving product version.");
  240. }
  241. }
  242. //---------------------------------------------------------------------------
  243. static void InitializeChild(const wchar_t* CommandLine, const wchar_t* InstanceName, HANDLE& AChild)
  244. {
  245. unsigned int SkipParam = 0;
  246. wchar_t ChildPath[MAX_PATH] = L"";
  247. size_t CommandLineLen = wcslen(CommandLine);
  248. wchar_t* Buffer = new wchar_t[(CommandLineLen > MAX_PATH ? CommandLineLen : MAX_PATH) + 1];
  249. unsigned int Count = 0;
  250. const wchar_t* P = CommandLine;
  251. while (CutToken(P, Buffer))
  252. {
  253. if ((wcschr(L"-/", Buffer[0]) != nullptr) &&
  254. (wcsncmpi(Buffer + 1, CONSOLE_CHILD_PARAM, wcslen(CONSOLE_CHILD_PARAM)) == 0) &&
  255. (Buffer[wcslen(CONSOLE_CHILD_PARAM) + 1] == L'='))
  256. {
  257. SkipParam = Count;
  258. wcscpy(ChildPath, Buffer + 1 + wcslen(CONSOLE_CHILD_PARAM) + 1);
  259. }
  260. ++Count;
  261. }
  262. if (wcslen(ChildPath) == 0)
  263. {
  264. DWORD ModuleNameLen = GetModuleFileName(nullptr, Buffer, MAX_PATH);
  265. if ((ModuleNameLen == 0) || (ModuleNameLen == MAX_PATH))
  266. {
  267. throw runtime_error("Error retrieving executable name.");
  268. }
  269. const wchar_t* LastDelimiter = wcsrchr(Buffer, L'\\');
  270. const wchar_t* AppFileName;
  271. if (LastDelimiter != nullptr)
  272. {
  273. wcsncpy(ChildPath, Buffer, static_cast<size_t>(LastDelimiter - Buffer + 1));
  274. ChildPath[LastDelimiter - Buffer + 1] = L'\0';
  275. AppFileName = LastDelimiter + 1;
  276. }
  277. else
  278. {
  279. ChildPath[0] = L'\0';
  280. AppFileName = Buffer;
  281. }
  282. const wchar_t* ExtensionStart = wcsrchr(AppFileName, L'.');
  283. if (ExtensionStart != nullptr)
  284. {
  285. wchar_t* End = ChildPath + wcslen(ChildPath);
  286. wcsncpy(End, AppFileName, static_cast<size_t>(ExtensionStart - AppFileName));
  287. *(End + (ExtensionStart - AppFileName)) = L'\0';
  288. }
  289. else
  290. {
  291. wcscat(ChildPath, AppFileName);
  292. }
  293. wcscat(ChildPath, L".exe");
  294. }
  295. wchar_t ProductVersion[32];
  296. GetProductVersion(ProductVersion);
  297. wchar_t* Parameters = new wchar_t[(CommandLineLen * 2) + 100 + (Count * 3) + 1];
  298. wsprintf(Parameters, L"\"%s\" /console=%s /consoleinstance=%s ", ChildPath, ProductVersion, InstanceName);
  299. P = CommandLine;
  300. // skip executable path
  301. CutToken(P, Buffer);
  302. unsigned int i = 1;
  303. while (CutToken(P, Buffer))
  304. {
  305. if (i != SkipParam)
  306. {
  307. wcscat(Parameters, L"\"");
  308. wchar_t* P2 = Parameters + wcslen(Parameters);
  309. const wchar_t* P3 = Buffer;
  310. const wchar_t* BufferEnd = Buffer + wcslen(Buffer) + 1;
  311. while (P3 != BufferEnd)
  312. {
  313. *P2 = *P3;
  314. ++P2;
  315. if (*P3 == L'"')
  316. {
  317. *P2 = L'"';
  318. ++P2;
  319. }
  320. ++P3;
  321. }
  322. wcscat(Parameters, L"\" ");
  323. }
  324. ++i;
  325. }
  326. delete[] Buffer;
  327. STARTUPINFO StartupInfo = { .cb = sizeof(STARTUPINFO) };
  328. PROCESS_INFORMATION ProcessInfomation;
  329. BOOL Result =
  330. CreateProcess(ChildPath, Parameters, nullptr, nullptr, false, 0, nullptr, nullptr, &StartupInfo, &ProcessInfomation);
  331. delete[] Parameters;
  332. if (Result)
  333. {
  334. AChild = ProcessInfomation.hProcess;
  335. }
  336. else
  337. {
  338. size_t Len = MAX_PATH + 1024;
  339. DWORD Error = GetLastError();
  340. wchar_t * Buffer2 = nullptr;
  341. DWORD Flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER;
  342. Len += FormatMessage(Flags, nullptr, Error, 0, reinterpret_cast<LPTSTR>(&Buffer2), 0, nullptr);
  343. wchar_t* Message = new wchar_t[Len];
  344. wsprintf(Message, L"Cannot start WinSCP application \"%s\".", ChildPath);
  345. if (Buffer2 != nullptr)
  346. {
  347. wcscat(Message, L"\n");
  348. wcscat(Message, Buffer2);
  349. LocalFree(Buffer2);
  350. }
  351. char* MessageString = WideStringToString(Message);
  352. delete[] Message;
  353. std::string ErrorString(MessageString);
  354. delete[] MessageString;
  355. throw runtime_error(ErrorString);
  356. }
  357. }
  358. //---------------------------------------------------------------------------
  359. static void FinalizeChild(HANDLE AChild)
  360. {
  361. if (AChild != nullptr)
  362. {
  363. TerminateProcess(AChild, 0);
  364. CloseHandle(AChild);
  365. }
  366. }
  367. //---------------------------------------------------------------------------
  368. static void FinalizeConsole(
  369. const wchar_t* /*InstanceName*/, HANDLE RequestEvent, HANDLE ResponseEvent, HANDLE ACancelEvent, HANDLE FileMapping, HANDLE Job)
  370. {
  371. CloseHandle(RequestEvent);
  372. CloseHandle(ResponseEvent);
  373. CloseHandle(ACancelEvent);
  374. CloseHandle(FileMapping);
  375. if (Job != nullptr)
  376. {
  377. CloseHandle(Job);
  378. }
  379. }
  380. //---------------------------------------------------------------------------
  381. static wchar_t LastFromBeginning[sizeof(TConsoleCommStruct::TPrintEvent)] = L""; //???
  382. //---------------------------------------------------------------------------
  383. inline void Flush()
  384. {
  385. if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE))
  386. {
  387. fflush(stdout);
  388. }
  389. }
  390. //---------------------------------------------------------------------------
  391. static void PrintException(const exception& e)
  392. {
  393. if (ConsoleOutput != nullptr)
  394. {
  395. unsigned long Written;
  396. WriteFile(ConsoleOutput, e.what(), strlen(e.what()), &Written, nullptr);
  397. }
  398. else
  399. {
  400. puts(e.what());
  401. }
  402. }
  403. //---------------------------------------------------------------------------
  404. static void Print(const wchar_t* Message)
  405. {
  406. char* Buffer = WideStringToString(Message);
  407. if (Buffer != nullptr)
  408. {
  409. char* Ptr = Buffer;
  410. while ((Ptr = strchr(Ptr, '\n')) != nullptr)
  411. {
  412. memmove(Ptr + 1, Ptr, strlen(Ptr) + 1);
  413. *Ptr = '\r';
  414. Ptr += 2;
  415. }
  416. unsigned long Written;
  417. WriteFile(ConsoleOutput, Buffer, strlen(Buffer), &Written, nullptr);
  418. delete[] Buffer;
  419. }
  420. }
  421. //---------------------------------------------------------------------------
  422. static void Print(bool FromBeginning, const wchar_t* Message)
  423. {
  424. size_t Len = wcslen(Message);
  425. if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE))
  426. {
  427. if (FromBeginning && (Message[0] != L'\n'))
  428. {
  429. wcscpy(LastFromBeginning, Message);
  430. }
  431. else
  432. {
  433. if (LastFromBeginning[0] != L'\0')
  434. {
  435. Print(LastFromBeginning);
  436. LastFromBeginning[0] = L'\0';
  437. }
  438. if (FromBeginning && (Message[0] == L'\n'))
  439. {
  440. Print(L"\n");
  441. wcscpy(LastFromBeginning, Message + 1);
  442. }
  443. else
  444. {
  445. Print(Message);
  446. }
  447. Flush();
  448. }
  449. }
  450. else
  451. {
  452. unsigned long Written;
  453. if (FromBeginning)
  454. {
  455. WriteConsole(ConsoleOutput, L"\r", 1, &Written, nullptr);
  456. }
  457. bool WriteResult =
  458. WriteConsole(ConsoleOutput, Message, Len, &Written, nullptr);
  459. DWORD Error = GetLastError();
  460. // The current console font does not support some characters in the message,
  461. // fall back to ansi-writting
  462. if (!WriteResult && (Error == ERROR_GEN_FAILURE))
  463. {
  464. int Size = WideCharToMultiByte(CP_ACP, 0, Message, -1, nullptr, 0, nullptr, nullptr);
  465. if (Size > 0)
  466. {
  467. char* Buffer = new char[static_cast<size_t>(Size)];
  468. if (WideCharToMultiByte(CP_ACP, 0, Message, -1, Buffer, Size, nullptr, nullptr) > 0)
  469. {
  470. WriteConsoleA(ConsoleOutput, Buffer, strlen(Buffer), &Written, nullptr);
  471. }
  472. delete[] Buffer;
  473. }
  474. }
  475. }
  476. }
  477. //---------------------------------------------------------------------------
  478. inline void ProcessPrintEvent(TConsoleCommStruct::TPrintEvent& Event)
  479. {
  480. Print(Event.FromBeginning, Event.Message);
  481. }
  482. //---------------------------------------------------------------------------
  483. static void CancelInput()
  484. {
  485. SetEvent(CancelEvent);
  486. }
  487. //---------------------------------------------------------------------------
  488. static void BreakInput()
  489. {
  490. FlushConsoleInputBuffer(ConsoleInput);
  491. // Signal cancel first, otherwise the main thread can get Enter before the event is set
  492. CancelInput();
  493. INPUT_RECORD InputRecord;
  494. memset(&InputRecord, 0, sizeof(InputRecord));
  495. InputRecord.EventType = KEY_EVENT;
  496. InputRecord.Event.KeyEvent.bKeyDown = true;
  497. InputRecord.Event.KeyEvent.wRepeatCount = 1;
  498. InputRecord.Event.KeyEvent.uChar.UnicodeChar = L'\r';
  499. unsigned long Written;
  500. WriteConsoleInput(ConsoleInput, &InputRecord, 1, &Written);
  501. }
  502. //---------------------------------------------------------------------------
  503. static DWORD WINAPI InputTimerThreadProc(void* Parameter)
  504. {
  505. unsigned int Timer = reinterpret_cast<unsigned int>(Parameter);
  506. unsigned int Remaining = Timer;
  507. const unsigned int Step = 1000;
  508. const int FirstKey = VK_LBUTTON; // 0x01
  509. const int LastKey = VK_OEM_CLEAR; // 0xFE
  510. // reset key state
  511. for (int Key = FirstKey; Key <= LastKey; Key++)
  512. {
  513. GetAsyncKeyState(Key);
  514. }
  515. while (Remaining > 0)
  516. {
  517. unsigned long WaitResult = WaitForSingleObject(InputTimerEvent, Step);
  518. if (WaitResult == WAIT_OBJECT_0)
  519. {
  520. // input entered
  521. Remaining = 0;
  522. }
  523. else if (WaitResult == WAIT_TIMEOUT)
  524. {
  525. bool Input = false;
  526. for (int Key = FirstKey; Key <= LastKey; Key++)
  527. {
  528. if ((GetAsyncKeyState(Key) & 0x01) != 0)
  529. {
  530. Input = true;
  531. // Finishing the loop nevertheless to reset state of all keys
  532. }
  533. }
  534. if (Input)
  535. {
  536. // If we have new input, reset timer
  537. Remaining = Timer;
  538. }
  539. else if (Remaining > Step)
  540. {
  541. Remaining -= Step;
  542. }
  543. else
  544. {
  545. BreakInput();
  546. Remaining = 0;
  547. }
  548. }
  549. else
  550. {
  551. // abort input on (unlikely) error
  552. BreakInput();
  553. Remaining = 0;
  554. }
  555. }
  556. return 0;
  557. }
  558. //---------------------------------------------------------------------------
  559. static void ProcessInputEvent(TConsoleCommStruct::TInputEvent& Event)
  560. {
  561. if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE))
  562. {
  563. unsigned long Bytes = 0;
  564. unsigned long Read;
  565. bool Result;
  566. char Ch;
  567. char Buf[LENOF(Event.Str) * 3];
  568. while (((Result = (ReadFile(ConsoleInput, &Ch, 1, &Read, nullptr) != 0)) != false) &&
  569. (Read > 0) && (Bytes < LENOF(Buf) - 1) && (Ch != '\n'))
  570. {
  571. if (Ch != '\r')
  572. {
  573. Buf[Bytes] = Ch;
  574. Bytes++;
  575. }
  576. }
  577. Buf[Bytes] = L'\0';
  578. MultiByteToWideChar(CP_UTF8, 0, Buf, -1, Event.Str, LENOF(Event.Str) - 1);
  579. Event.Str[LENOF(Event.Str) - 1] = L'\0';
  580. Print(false, Event.Str);
  581. Print(false, L"\n");
  582. Event.Result = ((Result && (Read > 0)) || (Bytes > 0));
  583. }
  584. else
  585. {
  586. unsigned long PrevMode, NewMode;
  587. GetConsoleMode(ConsoleInput, &PrevMode);
  588. NewMode = PrevMode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
  589. if (Event.Echo)
  590. {
  591. NewMode |= ENABLE_ECHO_INPUT;
  592. }
  593. else
  594. {
  595. NewMode &= static_cast<unsigned long>(~ENABLE_ECHO_INPUT);
  596. }
  597. SetConsoleMode(ConsoleInput, NewMode);
  598. HANDLE InputTimerThread = nullptr;
  599. try
  600. {
  601. if (Event.Timer > 0)
  602. {
  603. unsigned long ThreadId;
  604. InputTimerEvent = CreateEvent(nullptr, false, false, nullptr);
  605. InputTimerThread =
  606. CreateThread(nullptr, 0, InputTimerThreadProc, reinterpret_cast<void *>(Event.Timer), 0, &ThreadId);
  607. }
  608. unsigned long Read;
  609. Event.Result = ReadConsole(ConsoleInput, Event.Str, LENOF(Event.Str) - 1, &Read, nullptr);
  610. Event.Str[Read] = L'\0';
  611. bool PendingCancel = (WaitForSingleObject(CancelEvent, 0) == WAIT_OBJECT_0);
  612. if (PendingCancel || !Event.Echo)
  613. {
  614. WriteFile(ConsoleOutput, "\n", 1, nullptr, nullptr);
  615. Flush();
  616. }
  617. if (PendingCancel || (Read == 0))
  618. {
  619. Event.Result = false;
  620. }
  621. }
  622. __finally
  623. {
  624. if (InputTimerThread != nullptr)
  625. {
  626. SetEvent(InputTimerEvent);
  627. WaitForSingleObject(InputTimerThread, 100);
  628. CloseHandle(InputTimerEvent);
  629. InputTimerEvent = nullptr;
  630. CloseHandle(InputTimerThread);
  631. }
  632. SetConsoleMode(ConsoleInput, PrevMode);
  633. }
  634. }
  635. }
  636. //---------------------------------------------------------------------------
  637. static void ProcessChoiceEvent(TConsoleCommStruct::TChoiceEvent& Event)
  638. {
  639. // note that if output is redirected to file, input is still FILE_TYPE_CHAR
  640. if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE))
  641. {
  642. if (Event.Timeouting)
  643. {
  644. Sleep(Event.Timer);
  645. Event.Result = Event.Timeouted;
  646. }
  647. else
  648. {
  649. Event.Result = Event.Break;
  650. }
  651. }
  652. else
  653. {
  654. Event.Result = 0;
  655. unsigned long PrevMode, NewMode;
  656. GetConsoleMode(ConsoleInput, &PrevMode);
  657. NewMode = (PrevMode | ENABLE_PROCESSED_INPUT) & static_cast<unsigned long>(~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
  658. SetConsoleMode(ConsoleInput, NewMode);
  659. unsigned int ATimer = Event.Timer;
  660. try
  661. {
  662. do
  663. {
  664. unsigned long Read;
  665. INPUT_RECORD Record;
  666. if ((PeekConsoleInput(ConsoleInput, &Record, 1, &Read) != 0) &&
  667. (Read == 1))
  668. {
  669. if ((ReadConsoleInput(ConsoleInput, &Record, 1, &Read) != 0) &&
  670. (Read == 1))
  671. {
  672. bool PendingCancel = (WaitForSingleObject(CancelEvent, 0) == WAIT_OBJECT_0);
  673. if (PendingCancel)
  674. {
  675. Event.Result = Event.Break;
  676. }
  677. else if ((Record.EventType == KEY_EVENT) &&
  678. Record.Event.KeyEvent.bKeyDown)
  679. {
  680. // This happens when Shift key is pressed
  681. if (Record.Event.KeyEvent.uChar.UnicodeChar != 0)
  682. {
  683. wchar_t CStr[2];
  684. CStr[0] = Record.Event.KeyEvent.uChar.UnicodeChar;
  685. CStr[1] = L'\0';
  686. CharUpperBuff(CStr, 1);
  687. wchar_t C = CStr[0];
  688. if (C == 27)
  689. {
  690. Event.Result = Event.Cancel;
  691. }
  692. else if ((wcschr(Event.Options, C) != nullptr) &&
  693. ((Record.Event.KeyEvent.dwControlKeyState &
  694. (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED |
  695. RIGHT_ALT_PRESSED)) == 0))
  696. {
  697. Event.Result = wcschr(Event.Options, C) - Event.Options + 1;
  698. }
  699. }
  700. }
  701. }
  702. }
  703. if (Event.Result == 0)
  704. {
  705. unsigned int TimerSlice = 50;
  706. Sleep(TimerSlice);
  707. if (Event.Timer > 0)
  708. {
  709. if (ATimer > TimerSlice)
  710. {
  711. ATimer -= TimerSlice;
  712. }
  713. else
  714. {
  715. Event.Result = Event.Timeouted;
  716. }
  717. }
  718. }
  719. }
  720. while (Event.Result == 0);
  721. SetConsoleMode(ConsoleInput, PrevMode);
  722. }
  723. catch(...)
  724. {
  725. SetConsoleMode(ConsoleInput, PrevMode);
  726. throw;
  727. }
  728. }
  729. }
  730. //---------------------------------------------------------------------------
  731. inline void ProcessTitleEvent(TConsoleCommStruct::TTitleEvent& Event)
  732. {
  733. SetConsoleTitle(Event.Title);
  734. }
  735. //---------------------------------------------------------------------------
  736. inline void ProcessInitEvent(TConsoleCommStruct::TInitEvent& Event)
  737. {
  738. if (Event.UseStdErr)
  739. {
  740. ConsoleOutput = ConsoleErrorOutput;
  741. }
  742. OutputFormat = Event.OutputFormat;
  743. if (OutputFormat != TConsoleCommStruct::TInitEvent::OFF)
  744. {
  745. setmode(fileno(stdout), O_BINARY);
  746. }
  747. InputFormat = Event.InputFormat;
  748. if (InputFormat != TConsoleCommStruct::TInitEvent::OFF)
  749. {
  750. setmode(fileno(stdin), O_BINARY);
  751. }
  752. OutputType = GetFileType(ConsoleOutput);
  753. // Until now we should not have printed anything.
  754. // Only in case of a fatal failure, we might have printed a pure ASCII error messages (and never got here).
  755. if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE) ||
  756. SupportsUtf8ConsoleOutput)
  757. {
  758. SetConsoleOutputCP(CP_UTF8);
  759. }
  760. else
  761. {
  762. SetConsoleOutputCP(CP_ACP);
  763. }
  764. Event.InputType = InputType;
  765. Event.OutputType = OutputType;
  766. // default anyway
  767. Event.WantsProgress = false;
  768. }
  769. //---------------------------------------------------------------------------
  770. inline void ProcessTransferOutEvent(TConsoleCommStruct::TTransferEvent& Event)
  771. {
  772. if (OutputFormat == TConsoleCommStruct::TInitEvent::BINARY)
  773. {
  774. fwrite(Event.Data, 1, Event.Len, stdout);
  775. }
  776. else if (OutputFormat == TConsoleCommStruct::TInitEvent::CHUNKED)
  777. {
  778. fprintf(stdout, "%x\r\n", static_cast<int>(Event.Len));
  779. fwrite(Event.Data, 1, Event.Len, stdout);
  780. fputs("\r\n", stdout);
  781. }
  782. }
  783. //---------------------------------------------------------------------------
  784. inline void ProcessTransferInEvent(TConsoleCommStruct::TTransferEvent& Event)
  785. {
  786. size_t Read = fread(Event.Data, 1, Event.Len, stdin);
  787. if (Read != Event.Len)
  788. {
  789. if (ferror(stdin))
  790. {
  791. Event.Error = true;
  792. }
  793. else
  794. {
  795. Event.Len = Read;
  796. }
  797. }
  798. }
  799. //---------------------------------------------------------------------------
  800. static void ProcessEvent(HANDLE ResponseEvent, HANDLE FileMapping)
  801. {
  802. TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
  803. try
  804. {
  805. if (CommStruct->Version != TConsoleCommStruct::CurrentVersionConfirmed)
  806. {
  807. throw runtime_error("Incompatible console protocol version");
  808. }
  809. switch (CommStruct->Event)
  810. {
  811. case TConsoleCommStruct::PRINT:
  812. ProcessPrintEvent(CommStruct->PrintEvent);
  813. break;
  814. case TConsoleCommStruct::INPUT:
  815. ProcessInputEvent(CommStruct->InputEvent);
  816. break;
  817. case TConsoleCommStruct::CHOICE:
  818. ProcessChoiceEvent(CommStruct->ChoiceEvent);
  819. break;
  820. case TConsoleCommStruct::TITLE:
  821. ProcessTitleEvent(CommStruct->TitleEvent);
  822. break;
  823. case TConsoleCommStruct::INIT:
  824. ProcessInitEvent(CommStruct->InitEvent);
  825. break;
  826. case TConsoleCommStruct::TRANSFEROUT:
  827. ProcessTransferOutEvent(CommStruct->TransferEvent);
  828. break;
  829. case TConsoleCommStruct::TRANSFERIN:
  830. ProcessTransferInEvent(CommStruct->TransferEvent);
  831. break;
  832. case TConsoleCommStruct::NONE:
  833. case TConsoleCommStruct::PROGRESS:
  834. throw runtime_error("Unknown event");
  835. }
  836. FreeCommStruct(CommStruct);
  837. SetEvent(ResponseEvent);
  838. }
  839. catch(...)
  840. {
  841. FreeCommStruct(CommStruct);
  842. throw;
  843. }
  844. }
  845. //---------------------------------------------------------------------------
  846. static BOOL WINAPI HandlerRoutine(DWORD CtrlType)
  847. {
  848. if ((CtrlType == CTRL_C_EVENT) || (CtrlType == CTRL_BREAK_EVENT))
  849. {
  850. CancelInput();
  851. return true;
  852. }
  853. else
  854. {
  855. FinalizeChild(Child);
  856. return false;
  857. }
  858. }
  859. //---------------------------------------------------------------------------
  860. #pragma argsused
  861. int wmain(int /*argc*/, wchar_t* /*argv*/[])
  862. {
  863. unsigned long Result = RESULT_UNKNOWN_ERROR;
  864. try
  865. {
  866. randomize();
  867. OSVERSIONINFO VersionInfo;
  868. VersionInfo.dwOSVersionInfoSize = sizeof(VersionInfo);
  869. #pragma clang diagnostic push
  870. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  871. GetVersionEx(&VersionInfo);
  872. #pragma clang diagnostic pop
  873. ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
  874. InputType = GetFileType(ConsoleInput);
  875. SetConsoleCtrlHandler(HandlerRoutine, true);
  876. unsigned int SavedConsoleCP = GetConsoleCP();
  877. unsigned int SavedConsoleOutputCP = GetConsoleOutputCP();
  878. ConsoleStandardOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  879. ConsoleOutput = ConsoleStandardOutput;
  880. ConsoleErrorOutput = GetStdHandle(STD_ERROR_HANDLE);
  881. SupportsUtf8ConsoleOutput =
  882. ((VersionInfo.dwMajorVersion == 6) && (VersionInfo.dwMinorVersion >= 1)) ||
  883. (VersionInfo.dwMajorVersion > 6);
  884. if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE) ||
  885. SupportsUtf8ConsoleOutput)
  886. {
  887. SetConsoleCP(CP_UTF8);
  888. }
  889. else
  890. {
  891. SetConsoleCP(CP_ACP);
  892. }
  893. wchar_t InstanceName[MAX_PATH];
  894. HANDLE RequestEvent, ResponseEvent, FileMapping, Job;
  895. InitializeConsole(InstanceName, RequestEvent, ResponseEvent,
  896. CancelEvent, FileMapping, Job);
  897. wchar_t SavedTitle[512];
  898. GetConsoleTitle(SavedTitle, LENOF(SavedTitle));
  899. try
  900. {
  901. #ifndef CONSOLE_TEST
  902. InitializeChild(GetCommandLine(), InstanceName, Child);
  903. #endif
  904. try
  905. {
  906. bool Continue = true;
  907. do
  908. {
  909. HANDLE Handles[2];
  910. Handles[0] = RequestEvent;
  911. Handles[1] = Child;
  912. unsigned int HandleCount;
  913. #ifndef CONSOLE_TEST
  914. HandleCount = 2;
  915. #else
  916. HandleCount = 1;
  917. #endif
  918. unsigned long WaitResult =
  919. WaitForMultipleObjects(HandleCount, Handles, false, INFINITE);
  920. switch (WaitResult)
  921. {
  922. case WAIT_OBJECT_0:
  923. ProcessEvent(ResponseEvent, FileMapping);
  924. break;
  925. case WAIT_OBJECT_0 + 1:
  926. GetExitCodeProcess(Child, &Result);
  927. CloseHandle(Child);
  928. Child = nullptr;
  929. Continue = false;
  930. break;
  931. default:
  932. throw runtime_error("Error waiting for communication from child process.");
  933. }
  934. }
  935. while (Continue);
  936. // flush pending progress message
  937. Print(false, L"");
  938. }
  939. catch(const exception& e)
  940. {
  941. PrintException(e);
  942. Result = RESULT_PROCESSING_ERROR;
  943. }
  944. #ifndef CONSOLE_TEST
  945. FinalizeChild(Child);
  946. #endif
  947. SetConsoleTitle(SavedTitle);
  948. SetConsoleCP(SavedConsoleCP);
  949. SetConsoleOutputCP(SavedConsoleOutputCP);
  950. }
  951. catch(const exception& e)
  952. {
  953. PrintException(e);
  954. Result = RESULT_INIT_ERROR;
  955. }
  956. FinalizeConsole(InstanceName, RequestEvent, ResponseEvent,
  957. CancelEvent, FileMapping, Job);
  958. }
  959. catch(const exception& e)
  960. {
  961. PrintException(e);
  962. Result = RESULT_GLOBAL_ERROR;
  963. }
  964. return static_cast<int>(Result);
  965. }
  966. //---------------------------------------------------------------------------