1
0

Main.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  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. HANDLE ConsoleInput = NULL;
  15. HANDLE ConsoleOutput = NULL;
  16. HANDLE ConsoleStandardOutput = NULL;
  17. HANDLE ConsoleErrorOutput = NULL;
  18. TConsoleCommStruct::TInitEvent::STDINOUT OutputFormat;
  19. TConsoleCommStruct::TInitEvent::STDINOUT InputFormat;
  20. HANDLE Child = NULL;
  21. HANDLE CancelEvent = NULL;
  22. HANDLE InputTimerEvent = NULL;
  23. bool SupportsUtf8ConsoleOutput = false;
  24. unsigned int OutputType = FILE_TYPE_UNKNOWN;
  25. 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. 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 == NULL)
  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. void InitializeConsole(wchar_t* InstanceName, HANDLE& RequestEvent, HANDLE& ResponseEvent,
  48. HANDLE& CancelEvent, 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 == NULL);
  70. if (!UniqEvent)
  71. {
  72. CloseHandle(EventHandle);
  73. }
  74. Attempts++;
  75. }
  76. while (!UniqEvent);
  77. RequestEvent = CreateEvent(NULL, false, false, Name);
  78. if (RequestEvent == NULL)
  79. {
  80. throw runtime_error("Cannot create request event object.");
  81. }
  82. swprintf(Name, L"%s%s", CONSOLE_EVENT_RESPONSE, InstanceName);
  83. ResponseEvent = CreateEvent(NULL, false, false, Name);
  84. if (ResponseEvent == NULL)
  85. {
  86. throw runtime_error("Cannot create response event object.");
  87. }
  88. swprintf(Name, L"%s%s", CONSOLE_EVENT_CANCEL, InstanceName);
  89. CancelEvent = CreateEvent(NULL, false, false, Name);
  90. if (CancelEvent == NULL)
  91. {
  92. throw runtime_error("Cannot create cancel event object.");
  93. }
  94. swprintf(Name, L"%s%s", CONSOLE_MAPPING, InstanceName);
  95. FileMapping = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE,
  96. 0, sizeof(TConsoleCommStruct), Name);
  97. if (FileMapping == NULL)
  98. {
  99. throw runtime_error("Cannot create mapping object.");
  100. }
  101. swprintf(Name, L"%s%s", CONSOLE_JOB, InstanceName);
  102. Job = CreateJobObject(NULL, Name);
  103. if (Job == NULL)
  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 = NULL;
  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. bool __fastcall CutToken(const wchar_t*& Str, wchar_t* Token)
  126. {
  127. bool Result;
  128. // inspired by Putty's sftp_getcmd() from PSFTP.C
  129. int Length = wcslen(Str);
  130. int 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. char* WideStringToString(const wchar_t* Message)
  181. {
  182. char* Buffer;
  183. int Size = WideCharToMultiByte(CP_UTF8, 0, Message, -1, 0, 0, 0, 0);
  184. if (Size > 0)
  185. {
  186. Buffer = new char[(Size * 2) + 1];
  187. if (WideCharToMultiByte(CP_UTF8, 0, Message, -1, Buffer, Size, 0, 0) > 0)
  188. {
  189. Buffer[Size] = '\0';
  190. }
  191. else
  192. {
  193. delete[] Buffer;
  194. Buffer = NULL;
  195. }
  196. }
  197. else
  198. {
  199. Buffer = NULL;
  200. }
  201. return Buffer;
  202. }
  203. //---------------------------------------------------------------------------
  204. void GetProductVersion(wchar_t* ProductVersion)
  205. {
  206. wchar_t Buffer[MAX_PATH];
  207. DWORD ModuleNameLen = GetModuleFileName(NULL, 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. void * 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"\\", (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. void InitializeChild(const wchar_t* CommandLine, const wchar_t* InstanceName, HANDLE& Child)
  244. {
  245. 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. int Count = 0;
  250. const wchar_t* P = CommandLine;
  251. while (CutToken(P, Buffer))
  252. {
  253. if ((wcschr(L"-/", Buffer[0]) != NULL) &&
  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(NULL, 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 != NULL)
  272. {
  273. wcsncpy(ChildPath, Buffer, 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 != NULL)
  284. {
  285. wchar_t* End = ChildPath + wcslen(ChildPath);
  286. wcsncpy(End, AppFileName, 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. 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 = { sizeof(STARTUPINFO) };
  328. PROCESS_INFORMATION ProcessInfomation;
  329. BOOL Result =
  330. CreateProcess(ChildPath, Parameters, NULL, NULL, false, 0, NULL, NULL,
  331. &StartupInfo, &ProcessInfomation);
  332. delete[] Parameters;
  333. if (Result)
  334. {
  335. Child = ProcessInfomation.hProcess;
  336. }
  337. else
  338. {
  339. size_t Len = MAX_PATH + 1024;
  340. DWORD Error = GetLastError();
  341. wchar_t * Buffer = NULL;
  342. Len += FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, Error, 0, (LPTSTR)&Buffer, 0, NULL);
  343. wchar_t* Message = new wchar_t[Len];
  344. wsprintf(Message, L"Cannot start WinSCP application \"%s\".", ChildPath);
  345. if (Buffer != NULL)
  346. {
  347. wcscat(Message, L"\n");
  348. wcscat(Message, Buffer);
  349. LocalFree(Buffer);
  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. void FinalizeChild(HANDLE Child)
  360. {
  361. if (Child != NULL)
  362. {
  363. TerminateProcess(Child, 0);
  364. CloseHandle(Child);
  365. }
  366. }
  367. //---------------------------------------------------------------------------
  368. void FinalizeConsole(const wchar_t* /*InstanceName*/, HANDLE RequestEvent,
  369. HANDLE ResponseEvent, HANDLE CancelEvent, HANDLE FileMapping, HANDLE Job)
  370. {
  371. CloseHandle(RequestEvent);
  372. CloseHandle(ResponseEvent);
  373. CloseHandle(CancelEvent);
  374. CloseHandle(FileMapping);
  375. if (Job != NULL)
  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. void PrintException(const exception& e)
  392. {
  393. if (ConsoleOutput != NULL)
  394. {
  395. unsigned long Written;
  396. WriteFile(ConsoleOutput, e.what(), strlen(e.what()), &Written, NULL);
  397. }
  398. else
  399. {
  400. puts(e.what());
  401. }
  402. }
  403. //---------------------------------------------------------------------------
  404. void Print(const wchar_t* Message)
  405. {
  406. char* Buffer = WideStringToString(Message);
  407. if (Buffer != NULL)
  408. {
  409. char* Ptr = Buffer;
  410. while ((Ptr = strchr(Ptr, '\n')) != NULL)
  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, NULL);
  418. delete[] Buffer;
  419. }
  420. }
  421. //---------------------------------------------------------------------------
  422. 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, NULL);
  456. }
  457. bool WriteResult =
  458. WriteConsole(ConsoleOutput, Message, Len, &Written, NULL);
  459. int 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, 0, 0, 0, 0);
  465. if (Size > 0)
  466. {
  467. char* Buffer = new char[Size];
  468. if (WideCharToMultiByte(CP_ACP, 0, Message, -1, Buffer, Size, 0, 0) > 0)
  469. {
  470. WriteConsoleA(ConsoleOutput, Buffer, strlen(Buffer), &Written, NULL);
  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. void CancelInput()
  484. {
  485. SetEvent(CancelEvent);
  486. }
  487. //---------------------------------------------------------------------------
  488. 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. 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. 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, NULL) != 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 &= ~ENABLE_ECHO_INPUT;
  596. }
  597. SetConsoleMode(ConsoleInput, NewMode);
  598. HANDLE InputTimerThread = NULL;
  599. try
  600. {
  601. if (Event.Timer > 0)
  602. {
  603. unsigned long ThreadId;
  604. InputTimerEvent = CreateEvent(NULL, false, false, NULL);
  605. InputTimerThread = CreateThread(NULL, 0, InputTimerThreadProc,
  606. reinterpret_cast<void *>(Event.Timer), 0, &ThreadId);
  607. }
  608. unsigned long Read;
  609. Event.Result = ReadConsole(ConsoleInput, Event.Str, LENOF(Event.Str) - 1, &Read, NULL);
  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, NULL, NULL);
  615. Flush();
  616. }
  617. if (PendingCancel || (Read == 0))
  618. {
  619. Event.Result = false;
  620. }
  621. }
  622. __finally
  623. {
  624. if (InputTimerThread != NULL)
  625. {
  626. SetEvent(InputTimerEvent);
  627. WaitForSingleObject(InputTimerThread, 100);
  628. CloseHandle(InputTimerEvent);
  629. InputTimerEvent = NULL;
  630. CloseHandle(InputTimerThread);
  631. }
  632. SetConsoleMode(ConsoleInput, PrevMode);
  633. }
  634. }
  635. }
  636. //---------------------------------------------------------------------------
  637. 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) & ~(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) != NULL) &&
  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. 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. default:
  833. throw runtime_error("Unknown event");
  834. }
  835. FreeCommStruct(CommStruct);
  836. SetEvent(ResponseEvent);
  837. }
  838. catch(...)
  839. {
  840. FreeCommStruct(CommStruct);
  841. throw;
  842. }
  843. }
  844. //---------------------------------------------------------------------------
  845. BOOL WINAPI HandlerRoutine(DWORD CtrlType)
  846. {
  847. if ((CtrlType == CTRL_C_EVENT) || (CtrlType == CTRL_BREAK_EVENT))
  848. {
  849. CancelInput();
  850. return true;
  851. }
  852. else
  853. {
  854. FinalizeChild(Child);
  855. return false;
  856. }
  857. }
  858. //---------------------------------------------------------------------------
  859. #pragma argsused
  860. int wmain(int /*argc*/, wchar_t* /*argv*/[])
  861. {
  862. unsigned long Result = RESULT_UNKNOWN_ERROR;
  863. try
  864. {
  865. randomize();
  866. OSVERSIONINFO VersionInfo;
  867. VersionInfo.dwOSVersionInfoSize = sizeof(VersionInfo);
  868. GetVersionEx(&VersionInfo);
  869. ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
  870. InputType = GetFileType(ConsoleInput);
  871. SetConsoleCtrlHandler(HandlerRoutine, true);
  872. unsigned int SavedConsoleCP = GetConsoleCP();
  873. unsigned int SavedConsoleOutputCP = GetConsoleOutputCP();
  874. ConsoleStandardOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  875. ConsoleOutput = ConsoleStandardOutput;
  876. ConsoleErrorOutput = GetStdHandle(STD_ERROR_HANDLE);
  877. SupportsUtf8ConsoleOutput =
  878. ((VersionInfo.dwMajorVersion == 6) && (VersionInfo.dwMinorVersion >= 1)) ||
  879. (VersionInfo.dwMajorVersion > 6);
  880. if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE) ||
  881. SupportsUtf8ConsoleOutput)
  882. {
  883. SetConsoleCP(CP_UTF8);
  884. }
  885. else
  886. {
  887. SetConsoleCP(CP_ACP);
  888. }
  889. wchar_t InstanceName[MAX_PATH];
  890. HANDLE RequestEvent, ResponseEvent, FileMapping, Job;
  891. InitializeConsole(InstanceName, RequestEvent, ResponseEvent,
  892. CancelEvent, FileMapping, Job);
  893. wchar_t SavedTitle[512];
  894. GetConsoleTitle(SavedTitle, LENOF(SavedTitle));
  895. try
  896. {
  897. #ifndef CONSOLE_TEST
  898. InitializeChild(GetCommandLine(), InstanceName, Child);
  899. #endif
  900. try
  901. {
  902. bool Continue = true;
  903. do
  904. {
  905. HANDLE Handles[2];
  906. Handles[0] = RequestEvent;
  907. Handles[1] = Child;
  908. unsigned int HandleCount;
  909. #ifndef CONSOLE_TEST
  910. HandleCount = 2;
  911. #else
  912. HandleCount = 1;
  913. #endif
  914. unsigned long WaitResult =
  915. WaitForMultipleObjects(HandleCount, Handles, false, INFINITE);
  916. switch (WaitResult)
  917. {
  918. case WAIT_OBJECT_0:
  919. ProcessEvent(ResponseEvent, FileMapping);
  920. break;
  921. case WAIT_OBJECT_0 + 1:
  922. GetExitCodeProcess(Child, &Result);
  923. CloseHandle(Child);
  924. Child = NULL;
  925. Continue = false;
  926. break;
  927. default:
  928. throw runtime_error("Error waiting for communication from child process.");
  929. }
  930. }
  931. while (Continue);
  932. // flush pending progress message
  933. Print(false, L"");
  934. }
  935. catch(const exception& e)
  936. {
  937. PrintException(e);
  938. Result = RESULT_PROCESSING_ERROR;
  939. }
  940. #ifndef CONSOLE_TEST
  941. FinalizeChild(Child);
  942. #endif
  943. SetConsoleTitle(SavedTitle);
  944. SetConsoleCP(SavedConsoleCP);
  945. SetConsoleOutputCP(SavedConsoleOutputCP);
  946. }
  947. catch(const exception& e)
  948. {
  949. PrintException(e);
  950. Result = RESULT_INIT_ERROR;
  951. }
  952. FinalizeConsole(InstanceName, RequestEvent, ResponseEvent,
  953. CancelEvent, FileMapping, Job);
  954. }
  955. catch(const exception& e)
  956. {
  957. PrintException(e);
  958. Result = RESULT_GLOBAL_ERROR;
  959. }
  960. return Result;
  961. }
  962. //---------------------------------------------------------------------------