Main.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. //---------------------------------------------------------------------------
  2. #include <stdexcept>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <windows.h>
  6. #include "Console.h"
  7. #define MAX_ATTEMPTS 10
  8. //---------------------------------------------------------------------------
  9. using namespace std;
  10. HANDLE ConsoleInput = NULL;
  11. HANDLE Child = NULL;
  12. HANDLE CancelEvent = NULL;
  13. unsigned int OutputType = FILE_TYPE_UNKNOWN;
  14. unsigned int InputType = FILE_TYPE_UNKNOWN;
  15. enum { RESULT_GLOBAL_ERROR = 1, RESULT_INIT_ERROR = 2, RESULT_PROCESSING_ERROR = 3,
  16. RESULT_UNKNOWN_ERROR = 4 };
  17. const char * CONSOLE_CHILD_PARAM = "consolechild";
  18. //---------------------------------------------------------------------------
  19. inline TConsoleCommStruct* GetCommStruct(HANDLE FileMapping)
  20. {
  21. TConsoleCommStruct* Result;
  22. Result = static_cast<TConsoleCommStruct*>(MapViewOfFile(FileMapping,
  23. FILE_MAP_ALL_ACCESS, 0, 0, 0));
  24. if (Result == NULL)
  25. {
  26. throw logic_error("Cannot open mapping object.");
  27. }
  28. return Result;
  29. }
  30. //---------------------------------------------------------------------------
  31. inline void FreeCommStruct(TConsoleCommStruct* CommStruct)
  32. {
  33. UnmapViewOfFile(CommStruct);
  34. }
  35. //---------------------------------------------------------------------------
  36. void InitializeConsole(int& InstanceNumber, HANDLE& RequestEvent, HANDLE& ResponseEvent,
  37. HANDLE& CancelEvent, HANDLE& FileMapping)
  38. {
  39. int Attempts = 0;
  40. char Name[MAX_PATH];
  41. bool UniqEvent;
  42. do
  43. {
  44. if (Attempts > MAX_ATTEMPTS)
  45. {
  46. throw logic_error("Cannot find unique name for event object.");
  47. }
  48. #ifdef CONSOLE_TEST
  49. InstanceNumber = 1;
  50. #else
  51. InstanceNumber = random(1000);
  52. #endif
  53. sprintf(Name, "%s%d", CONSOLE_EVENT_REQUEST, InstanceNumber);
  54. HANDLE EventHandle = OpenEvent(EVENT_ALL_ACCESS, false, Name);
  55. UniqEvent = (EventHandle == NULL);
  56. if (!UniqEvent)
  57. {
  58. CloseHandle(EventHandle);
  59. }
  60. Attempts++;
  61. }
  62. while (!UniqEvent);
  63. RequestEvent = CreateEvent(NULL, false, false, Name);
  64. if (RequestEvent == NULL)
  65. {
  66. throw logic_error("Cannot create request event object.");
  67. }
  68. sprintf(Name, "%s%d", CONSOLE_EVENT_RESPONSE, InstanceNumber);
  69. ResponseEvent = CreateEvent(NULL, false, false, Name);
  70. if (ResponseEvent == NULL)
  71. {
  72. throw logic_error("Cannot create response event object.");
  73. }
  74. sprintf(Name, "%s%d", CONSOLE_EVENT_CANCEL, InstanceNumber);
  75. CancelEvent = CreateEvent(NULL, false, false, Name);
  76. if (CancelEvent == NULL)
  77. {
  78. throw logic_error("Cannot create cancel event object.");
  79. }
  80. sprintf(Name, "%s%d", CONSOLE_MAPPING, InstanceNumber);
  81. FileMapping = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE,
  82. 0, sizeof(TConsoleCommStruct), Name);
  83. if (FileMapping == NULL)
  84. {
  85. throw logic_error("Cannot create mapping object.");
  86. }
  87. TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
  88. CommStruct->Size = sizeof(TConsoleCommStruct);
  89. CommStruct->Version = TConsoleCommStruct::CurrentVersion;
  90. CommStruct->Event = TConsoleCommStruct::NONE;
  91. FreeCommStruct(CommStruct);
  92. }
  93. //---------------------------------------------------------------------------
  94. void InitializeChild(int argc, char* argv[], int InstanceNumber, HANDLE& Child)
  95. {
  96. int SkipParam = 0;
  97. char ChildPath[MAX_PATH] = "";
  98. for (int i = 1; i < argc; i++)
  99. {
  100. if ((strchr("-/", argv[i][0]) != NULL) &&
  101. (strncmpi(argv[i] + 1, CONSOLE_CHILD_PARAM, strlen(CONSOLE_CHILD_PARAM)) == 0) &&
  102. (argv[i][strlen(CONSOLE_CHILD_PARAM) + 1] == '='))
  103. {
  104. SkipParam = i;
  105. strcpy(ChildPath, argv[i] + 1 + strlen(CONSOLE_CHILD_PARAM) + 1);
  106. break;
  107. }
  108. }
  109. if (strlen(ChildPath) == 0)
  110. {
  111. const char* AppPath = argv[0];
  112. const char* LastDelimiter = strrchr(AppPath, '\\');
  113. const char* AppFileName;
  114. if (LastDelimiter != NULL)
  115. {
  116. strncpy(ChildPath, AppPath, LastDelimiter - AppPath + 1);
  117. ChildPath[LastDelimiter - AppPath + 1] = '\0';
  118. AppFileName = LastDelimiter + 1;
  119. }
  120. else
  121. {
  122. ChildPath[0] = '\0';
  123. AppFileName = AppPath;
  124. }
  125. const char* ExtensionStart = strrchr(AppFileName, '.');
  126. if (ExtensionStart != NULL)
  127. {
  128. char* End = ChildPath + strlen(ChildPath);
  129. strncpy(End, AppFileName, ExtensionStart - AppFileName);
  130. *(End + (ExtensionStart - AppFileName)) = '\0';
  131. }
  132. else
  133. {
  134. strcat(ChildPath, AppFileName);
  135. }
  136. strcat(ChildPath, ".exe");
  137. }
  138. char Parameters[10240];
  139. sprintf(Parameters, "\"%s\" /console /consoleinstance=%d ", ChildPath, InstanceNumber);
  140. for (int i = 1; i < argc; i++)
  141. {
  142. if (i != SkipParam)
  143. {
  144. if (strlen(Parameters) + strlen(argv[i]) + 4 > sizeof(Parameters))
  145. {
  146. throw length_error("Too many parameters");
  147. }
  148. strcat(Parameters, "\"");
  149. strcat(Parameters, argv[i]);
  150. strcat(Parameters, "\" ");
  151. }
  152. }
  153. STARTUPINFO StartupInfo = { sizeof(STARTUPINFO) };
  154. PROCESS_INFORMATION ProcessInfomation;
  155. if (CreateProcess(ChildPath, Parameters, NULL, NULL, false, 0, NULL, NULL,
  156. &StartupInfo, &ProcessInfomation) != 0)
  157. {
  158. Child = ProcessInfomation.hProcess;
  159. }
  160. else
  161. {
  162. throw logic_error("Cannot start WinSCP application.");
  163. }
  164. }
  165. //---------------------------------------------------------------------------
  166. void FinalizeChild(HANDLE Child)
  167. {
  168. if (Child != NULL)
  169. {
  170. TerminateProcess(Child, 0);
  171. CloseHandle(Child);
  172. }
  173. }
  174. //---------------------------------------------------------------------------
  175. void FinalizeConsole(int /*InstanceNumber*/, HANDLE RequestEvent,
  176. HANDLE ResponseEvent, HANDLE CancelEvent, HANDLE FileMapping)
  177. {
  178. CloseHandle(RequestEvent);
  179. CloseHandle(ResponseEvent);
  180. CloseHandle(CancelEvent);
  181. CloseHandle(FileMapping);
  182. }
  183. //---------------------------------------------------------------------------
  184. static char LastFromBeginning[sizeof(TConsoleCommStruct::TPrintEvent)] = "";
  185. //---------------------------------------------------------------------------
  186. inline void Print(bool FromBeginning, const char * Message)
  187. {
  188. if ((OutputType == FILE_TYPE_DISK) || (OutputType == FILE_TYPE_PIPE))
  189. {
  190. if (FromBeginning && (Message[0] != '\n'))
  191. {
  192. strcpy(LastFromBeginning, Message);
  193. }
  194. else
  195. {
  196. if (LastFromBeginning[0] != '\0')
  197. {
  198. printf("%s", LastFromBeginning);
  199. LastFromBeginning[0] = '\0';
  200. }
  201. if (FromBeginning && (Message[0] == '\n'))
  202. {
  203. printf("\n");
  204. strcpy(LastFromBeginning, Message + 1);
  205. }
  206. else
  207. {
  208. printf("%s", Message);
  209. }
  210. }
  211. }
  212. else
  213. {
  214. if (FromBeginning)
  215. {
  216. printf("\r");
  217. }
  218. printf("%s", Message);
  219. }
  220. }
  221. //---------------------------------------------------------------------------
  222. inline void ProcessPrintEvent(TConsoleCommStruct::TPrintEvent& Event)
  223. {
  224. Print(Event.FromBeginning, Event.Message);
  225. }
  226. //---------------------------------------------------------------------------
  227. void ProcessInputEvent(TConsoleCommStruct::TInputEvent& Event)
  228. {
  229. if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE))
  230. {
  231. unsigned long Len = 0;
  232. unsigned long Read;
  233. char Ch;
  234. bool Result;
  235. while (((Result = (ReadFile(ConsoleInput, &Ch, 1, &Read, NULL) != 0)) != false) &&
  236. (Read > 0) && (Len < sizeof(Event.Str) - 1) && (Ch != '\n'))
  237. {
  238. if (Ch != '\r')
  239. {
  240. Event.Str[Len] = Ch;
  241. Len++;
  242. }
  243. }
  244. Event.Str[Len] = '\0';
  245. Print(false, Event.Str);
  246. Print(false, "\n");
  247. Event.Result = ((Result && (Read > 0)) || (Len > 0));
  248. }
  249. else
  250. {
  251. unsigned long PrevMode, NewMode;
  252. GetConsoleMode(ConsoleInput, &PrevMode);
  253. NewMode = PrevMode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
  254. if (Event.Echo)
  255. {
  256. NewMode |= ENABLE_ECHO_INPUT;
  257. }
  258. else
  259. {
  260. NewMode &= ~ENABLE_ECHO_INPUT;
  261. }
  262. SetConsoleMode(ConsoleInput, NewMode);
  263. try
  264. {
  265. unsigned long Read;
  266. Event.Result = ReadConsole(ConsoleInput, Event.Str, sizeof(Event.Str) - 1, &Read, NULL);
  267. Event.Str[Read] = '\0';
  268. bool PendingCancel = (WaitForSingleObject(CancelEvent, 0) == WAIT_OBJECT_0);
  269. if (PendingCancel || !Event.Echo)
  270. {
  271. printf("\n");
  272. }
  273. if (PendingCancel || (Read == 0))
  274. {
  275. Event.Result = false;
  276. }
  277. SetConsoleMode(ConsoleInput, PrevMode);
  278. }
  279. catch(...)
  280. {
  281. SetConsoleMode(ConsoleInput, PrevMode);
  282. throw;
  283. }
  284. }
  285. }
  286. //---------------------------------------------------------------------------
  287. void ProcessChoiceEvent(TConsoleCommStruct::TChoiceEvent& Event)
  288. {
  289. if ((InputType == FILE_TYPE_DISK) || (InputType == FILE_TYPE_PIPE))
  290. {
  291. Event.Result = Event.Cancel;
  292. }
  293. else
  294. {
  295. Event.Result = 0;
  296. unsigned long PrevMode, NewMode;
  297. GetConsoleMode(ConsoleInput, &PrevMode);
  298. NewMode = (PrevMode | ENABLE_PROCESSED_INPUT) & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
  299. SetConsoleMode(ConsoleInput, NewMode);
  300. try
  301. {
  302. do
  303. {
  304. unsigned long Read;
  305. INPUT_RECORD Record;
  306. if ((ReadConsoleInput(ConsoleInput, &Record, 1, &Read) != 0) &&
  307. (Read == 1))
  308. {
  309. bool PendingCancel = (WaitForSingleObject(CancelEvent, 0) == WAIT_OBJECT_0);
  310. if (PendingCancel)
  311. {
  312. Event.Result = Event.Break;
  313. }
  314. else if ((Record.EventType == KEY_EVENT) &&
  315. Record.Event.KeyEvent.bKeyDown)
  316. {
  317. char CStr[2];
  318. CStr[0] = Record.Event.KeyEvent.uChar.AsciiChar;
  319. CStr[1] = '\0';
  320. CharUpperBuff(CStr, 1);
  321. char C = CStr[0];
  322. if (C == 27)
  323. {
  324. Event.Result = Event.Cancel;
  325. }
  326. else if ((strchr(Event.Options, C) != NULL) &&
  327. ((Record.Event.KeyEvent.dwControlKeyState &
  328. (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED |
  329. RIGHT_CTRL_PRESSED)) == 0))
  330. {
  331. Event.Result = strchr(Event.Options, C) - Event.Options + 1;
  332. }
  333. }
  334. }
  335. }
  336. while (Event.Result == 0);
  337. SetConsoleMode(ConsoleInput, PrevMode);
  338. }
  339. catch(...)
  340. {
  341. SetConsoleMode(ConsoleInput, PrevMode);
  342. throw;
  343. }
  344. }
  345. }
  346. //---------------------------------------------------------------------------
  347. inline void ProcessTitleEvent(TConsoleCommStruct::TTitleEvent& Event)
  348. {
  349. SetConsoleTitle(Event.Title);
  350. }
  351. //---------------------------------------------------------------------------
  352. void ProcessEvent(HANDLE ResponseEvent, HANDLE FileMapping)
  353. {
  354. TConsoleCommStruct* CommStruct = GetCommStruct(FileMapping);
  355. try
  356. {
  357. switch (CommStruct->Event)
  358. {
  359. case TConsoleCommStruct::PRINT:
  360. ProcessPrintEvent(CommStruct->PrintEvent);
  361. break;
  362. case TConsoleCommStruct::INPUT:
  363. ProcessInputEvent(CommStruct->InputEvent);
  364. break;
  365. case TConsoleCommStruct::CHOICE:
  366. ProcessChoiceEvent(CommStruct->ChoiceEvent);
  367. break;
  368. case TConsoleCommStruct::TITLE:
  369. ProcessTitleEvent(CommStruct->TitleEvent);
  370. break;
  371. default:
  372. throw logic_error("Unknown event");
  373. }
  374. FreeCommStruct(CommStruct);
  375. SetEvent(ResponseEvent);
  376. }
  377. catch(...)
  378. {
  379. FreeCommStruct(CommStruct);
  380. throw;
  381. }
  382. }
  383. //---------------------------------------------------------------------------
  384. BOOL WINAPI HandlerRoutine(DWORD CtrlType)
  385. {
  386. if ((CtrlType == CTRL_C_EVENT) || (CtrlType == CTRL_BREAK_EVENT))
  387. {
  388. FlushConsoleInputBuffer(ConsoleInput);
  389. INPUT_RECORD InputRecord;
  390. memset(&InputRecord, 0, sizeof(InputRecord));
  391. InputRecord.EventType = KEY_EVENT;
  392. InputRecord.Event.KeyEvent.bKeyDown = true;
  393. InputRecord.Event.KeyEvent.wRepeatCount = 1;
  394. InputRecord.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
  395. InputRecord.Event.KeyEvent.uChar.AsciiChar = '\n';
  396. unsigned long Written;
  397. WriteConsoleInput(ConsoleInput, &InputRecord, 1, &Written);
  398. SetEvent(CancelEvent);
  399. return true;
  400. }
  401. else
  402. {
  403. FinalizeChild(Child);
  404. return false;
  405. }
  406. }
  407. //---------------------------------------------------------------------------
  408. #pragma argsused
  409. int main(int argc, char* argv[])
  410. {
  411. unsigned long Result = RESULT_UNKNOWN_ERROR;
  412. try
  413. {
  414. randomize();
  415. ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
  416. InputType = GetFileType(ConsoleInput);
  417. SetConsoleCtrlHandler(HandlerRoutine, true);
  418. HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  419. OutputType = GetFileType(ConsoleOutput);
  420. int InstanceNumber;
  421. HANDLE RequestEvent, ResponseEvent, FileMapping;
  422. InitializeConsole(InstanceNumber, RequestEvent, ResponseEvent,
  423. CancelEvent, FileMapping);
  424. char SavedTitle[512];
  425. GetConsoleTitle(SavedTitle, sizeof(SavedTitle));
  426. try
  427. {
  428. #ifndef CONSOLE_TEST
  429. InitializeChild(argc, argv, InstanceNumber, Child);
  430. #endif
  431. try
  432. {
  433. bool Continue = true;
  434. do
  435. {
  436. HANDLE Handles[2];
  437. Handles[0] = RequestEvent;
  438. Handles[1] = Child;
  439. unsigned int HandleCount;
  440. #ifndef CONSOLE_TEST
  441. HandleCount = 2;
  442. #else
  443. HandleCount = 1;
  444. #endif
  445. unsigned long WaitResult =
  446. WaitForMultipleObjects(HandleCount, Handles, false, INFINITE);
  447. switch (WaitResult)
  448. {
  449. case WAIT_OBJECT_0:
  450. ProcessEvent(ResponseEvent, FileMapping);
  451. break;
  452. case WAIT_OBJECT_0 + 1:
  453. GetExitCodeProcess(Child, &Result);
  454. CloseHandle(Child);
  455. Child = NULL;
  456. Continue = false;
  457. break;
  458. default:
  459. throw logic_error("Error waiting for communication from child process.");
  460. }
  461. }
  462. while (Continue);
  463. // flush pending progress message
  464. Print(false, "");
  465. }
  466. catch(const exception& e)
  467. {
  468. puts(e.what());
  469. Result = RESULT_PROCESSING_ERROR;
  470. }
  471. #ifndef CONSOLE_TEST
  472. FinalizeChild(Child);
  473. #endif
  474. SetConsoleTitle(SavedTitle);
  475. }
  476. catch(const exception& e)
  477. {
  478. puts(e.what());
  479. Result = RESULT_INIT_ERROR;
  480. }
  481. FinalizeConsole(InstanceNumber, RequestEvent, ResponseEvent,
  482. CancelEvent, FileMapping);
  483. }
  484. catch(const exception& e)
  485. {
  486. puts(e.what());
  487. Result = RESULT_GLOBAL_ERROR;
  488. }
  489. return Result;
  490. }
  491. //---------------------------------------------------------------------------