Quick.Console.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. { ***************************************************************************
  2. Copyright (c) 2016-2018 Kike Pérez
  3. Unit : Quick.Console
  4. Description : Console output with colors and optional file log
  5. Author : Kike Pérez
  6. Version : 1.7
  7. Created : 10/05/2017
  8. Modified : 18/01/2018
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Console;
  22. {$IFDEF CONDITIONALEXPRESSIONS}
  23. {$ifndef VER140}
  24. {$ifndef LINUX}
  25. {$define WITHUXTHEME}
  26. {$endif}
  27. {$endif}
  28. {$IF CompilerVersion >= 17.0}
  29. {$DEFINE INLINES}
  30. {$ENDIF}
  31. {$IF RTLVersion >= 14.0}
  32. {$DEFINE HASERROUTPUT}
  33. {$ENDIF}
  34. {$ENDIF}
  35. interface
  36. uses
  37. Classes,
  38. Windows,
  39. Winapi.Messages,
  40. System.SysUtils,
  41. Quick.Commons,
  42. Quick.Log;
  43. type
  44. //text colors
  45. TConsoleColor = (
  46. ccBlack = 0,
  47. ccBlue = 1,
  48. ccGreen = 2,
  49. ccCyan = 3,
  50. ccRed = 4,
  51. ccMagenta = 5,
  52. ccBrown = 6,
  53. ccLightGray = 7,
  54. ccDarkGray = 8,
  55. ccLightBlue = 9,
  56. ccLightGreen = 10,
  57. ccLightCyan = 11,
  58. ccLightRed = 12,
  59. ccLightMagenta = 13,
  60. ccYellow = 14,
  61. ccWhite = 15);
  62. TConsoleProperties = record
  63. LogVerbose : TLogVerbose;
  64. Log : TQuickLog;
  65. end;
  66. TOutputProc<T> = reference to procedure(const aLine : T);
  67. procedure cout(const cMsg : Integer; cEventType : TLogEventType); overload;
  68. procedure cout(const cMsg : Double; cEventType : TLogEventType); overload;
  69. procedure cout(const cMsg : string; cEventType : TLogEventType); overload;
  70. procedure cout(const cMsg : string; cColor : TConsoleColor); overload;
  71. procedure coutXY(x,y : Integer; const s : string; cEventType : TLogEventType);
  72. procedure coutBL(const s : string; cEventType : TLogEventType);
  73. procedure coutFmt(const cMsg : string; params : array of const; cEventType : TLogEventType);
  74. procedure TextColor(Color: TConsoleColor); overload;
  75. procedure TextColor(Color: Byte); overload;
  76. procedure TextBackground(Color: TConsoleColor); overload;
  77. procedure TextBackground(Color: Byte); overload;
  78. procedure ResetColors;
  79. function ClearScreen : Boolean;
  80. procedure ClearLine; overload;
  81. procedure ClearLine(Y : Integer); overload;
  82. procedure ConsoleWaitForEnterKey;
  83. procedure RunConsoleCommand(const aCommand, aParameters : String; CallBack : TOutputProc<PAnsiChar> = nil; OutputLines : TStrings = nil);
  84. procedure InitConsole;
  85. var
  86. Console : TConsoleProperties;
  87. CSConsole : TRTLCriticalSection;
  88. LastMode : Word;
  89. DefConsoleColor : Byte;
  90. TextAttr : Byte;
  91. hStdOut: THandle;
  92. hStdErr: THandle;
  93. ConsoleRect: TSmallRect;
  94. ScreenBufInfo : TConsoleScreenBufferInfo;
  95. implementation
  96. procedure cout(const cMsg : Integer; cEventType : TLogEventType);
  97. var
  98. FmtSets : TFormatSettings;
  99. begin
  100. try
  101. FmtSets := TFormatSettings.Create;
  102. FmtSets.ThousandSeparator := '.';
  103. FmtSets.DecimalSeparator := ',';
  104. cout(FormatFloat('0,',cMsg,FmtSets),cEventType);
  105. except
  106. cout(cMsg.ToString,cEventType);
  107. end;
  108. end;
  109. procedure cout(const cMsg : Double; cEventType : TLogEventType);
  110. var
  111. FmtSets : TFormatSettings;
  112. begin
  113. try
  114. FmtSets := TFormatSettings.Create;
  115. FmtSets.ThousandSeparator := '.';
  116. FmtSets.DecimalSeparator := ',';
  117. cout(FormatFloat('.0###,',cMsg,FmtSets),cEventType);
  118. except
  119. cout(cMsg.ToString,cEventType);
  120. end;
  121. end;
  122. procedure cout(const cMsg : string; cEventType : TLogEventType);
  123. begin
  124. if cEventType in Console.LogVerbose then
  125. begin
  126. EnterCriticalSection(CSConsole);
  127. try
  128. if hStdOut <> 0 then
  129. begin
  130. case cEventType of
  131. etError : TextColor(ccLightRed);
  132. etInfo : TextColor(ccWhite);
  133. etSuccess : TextColor(ccLightGreen);
  134. etWarning : TextColor(ccYellow);
  135. etDebug : TextColor(ccLightCyan);
  136. etTrace : TextColor(ccLightMagenta);
  137. else TextColor(ccWhite);
  138. end;
  139. Writeln(cMsg);
  140. TextColor(LastMode);
  141. end;
  142. finally
  143. LeaveCriticalSection(CSConsole);
  144. end;
  145. if Assigned(Console.Log) then Console.Log.Add(cMsg,cEventType);
  146. end;
  147. end;
  148. procedure cout(const cMsg : string; cColor : TConsoleColor);
  149. begin
  150. EnterCriticalSection(CSConsole);
  151. try
  152. if hStdOut <> 0 then
  153. begin
  154. TextColor(cColor);
  155. Writeln(cMsg);
  156. TextColor(LastMode);
  157. end;
  158. finally
  159. LeaveCriticalSection(CSConsole);
  160. end;
  161. end;
  162. function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF}
  163. var
  164. BufferInfo: TConsoleScreenBufferInfo;
  165. begin
  166. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  167. Result := BufferInfo.dwCursorPosition.X;
  168. end;
  169. function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF}
  170. var
  171. BufferInfo: TConsoleScreenBufferInfo;
  172. begin
  173. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  174. Result := BufferInfo.dwCursorPosition.Y;
  175. end;
  176. function GetCursorMaxBottom : Integer;
  177. var
  178. BufferInfo: TConsoleScreenBufferInfo;
  179. begin
  180. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  181. Result := BufferInfo.srWindow.Bottom;
  182. end;
  183. procedure SetCursorPos(NewCoord : TCoord);
  184. begin
  185. SetConsoleCursorPosition(hStdOut, NewCoord);
  186. end;
  187. procedure coutXY(x,y : Integer; const s : string; cEventType : TLogEventType);
  188. var
  189. NewCoord : TCoord;
  190. LastCoord : TCoord;
  191. begin
  192. if hStdOut = 0 then Exit;
  193. LastCoord.X := GetCursorX;
  194. LastCoord.Y := GetCursorY;
  195. NewCoord.X := x;
  196. NewCoord.Y := y;
  197. ClearLine(Y);
  198. SetCursorPos(NewCoord);
  199. try
  200. cout(s,cEventType);
  201. finally
  202. SetCursorPos(LastCoord);
  203. end;
  204. end;
  205. procedure coutBL(const s : string; cEventType : TLogEventType);
  206. begin
  207. coutXY(0,GetCurSorMaxBottom - 1,s,cEventType);
  208. end;
  209. procedure coutFmt(const cMsg : string; params : array of const; cEventType : TLogEventType);
  210. begin
  211. cout(Format(cMsg,params),cEventType);
  212. end;
  213. procedure TextColor(Color: TConsoleColor);
  214. begin
  215. TextColor(Integer(Color));
  216. end;
  217. procedure TextColor(Color: Byte);
  218. begin
  219. if hStdOut = 0 then Exit;
  220. LastMode := TextAttr;
  221. TextAttr := (TextAttr and $F0) or (Color and $0F);
  222. if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr);
  223. end;
  224. procedure TextBackground(Color: TConsoleColor);
  225. begin
  226. TextBackground(Integer(Color));
  227. end;
  228. procedure TextBackground(Color: Byte);
  229. begin
  230. if hStdOut = 0 then Exit;
  231. LastMode := TextAttr;
  232. TextAttr := (TextAttr and $0F) or ((Color shl 4) and $F0);
  233. if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr);
  234. end;
  235. procedure ResetColors;
  236. begin
  237. SetConsoleTextAttribute(hStdOut, DefConsoleColor);
  238. TextAttr := DefConsoleColor;
  239. end;
  240. function ClearScreen : Boolean;
  241. const
  242. BUFSIZE = 80*25;
  243. var
  244. Han, Dummy: LongWord;
  245. buf: string;
  246. coord: TCoord;
  247. begin
  248. Result := false;
  249. Han := GetStdHandle(STD_OUTPUT_HANDLE);
  250. if Han <> INVALID_HANDLE_VALUE then
  251. begin
  252. if SetConsoleTextAttribute(han, FOREGROUND_RED or FOREGROUND_GREEN or FOREGROUND_BLUE) then
  253. begin
  254. SetLength(buf,BUFSIZE);
  255. FillChar(buf[1],Length(buf),' ');
  256. if WriteConsole(han,PChar(buf),BUFSIZE,Dummy,nil) then
  257. begin
  258. coord.X := 0;
  259. coord.Y := 0;
  260. if SetConsoleCursorPosition(han,coord) then Result := true;
  261. end;
  262. end;
  263. end;
  264. end;
  265. procedure ClearLine;
  266. begin
  267. ClearLine(GetCursorY);
  268. end;
  269. procedure ClearLine(Y : Integer);
  270. var
  271. dwWriteCoord: TCoord;
  272. dwCount, dwSize: DWord;
  273. begin
  274. if hStdOut = 0 then Exit;
  275. dwWriteCoord.X := 0;
  276. dwWriteCoord.Y := Y;
  277. dwSize := ConsoleRect.Right + 1;
  278. FillConsoleOutputAttribute(hStdOut, TextAttr, dwSize, dwWriteCoord, dwCount);
  279. FillConsoleOutputCharacter(hStdOut, ' ', dwSize, dwWriteCoord, dwCount);
  280. end;
  281. function ConsoleKeyPressed(ExpectedKey: Word): Boolean;
  282. var
  283. lpNumberOfEvents: DWORD;
  284. lpBuffer: TInputRecord;
  285. lpNumberOfEventsRead : DWORD;
  286. nStdHandle: THandle;
  287. begin
  288. Result := False;
  289. nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
  290. lpNumberOfEvents := 0;
  291. GetNumberOfConsoleInputEvents(nStdHandle, lpNumberOfEvents);
  292. if lpNumberOfEvents <> 0 then
  293. begin
  294. PeekConsoleInput(nStdHandle, lpBuffer, 1, lpNumberOfEventsRead);
  295. if lpNumberOfEventsRead <> 0 then
  296. begin
  297. if lpBuffer.EventType = KEY_EVENT then
  298. begin
  299. if lpBuffer.Event.KeyEvent.bKeyDown and ((ExpectedKey = 0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode = ExpectedKey)) then Result := true
  300. else FlushConsoleInputBuffer(nStdHandle);
  301. end
  302. else FlushConsoleInputBuffer(nStdHandle);
  303. end;
  304. end;
  305. end;
  306. procedure ConsoleWaitForEnterKey;
  307. var
  308. msg: TMsg;
  309. begin
  310. while not ConsoleKeyPressed(VK_RETURN) do
  311. begin
  312. {$ifndef LVCL}
  313. if GetCurrentThreadID=MainThreadID then CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif} else
  314. {$endif}
  315. WaitMessage;
  316. while PeekMessage(msg,0,0,0,PM_REMOVE) do
  317. begin
  318. if Msg.Message = WM_QUIT then Exit
  319. else
  320. begin
  321. TranslateMessage(Msg);
  322. DispatchMessage(Msg);
  323. end;
  324. end;
  325. end;
  326. end;
  327. procedure RunConsoleCommand(const aCommand, aParameters : String; CallBack : TOutputProc<PAnsiChar> = nil; OutputLines : TStrings = nil);
  328. const
  329. CReadBuffer = 2400;
  330. var
  331. saSecurity: Windows.TSecurityAttributes;
  332. hRead: THandle;
  333. hWrite: THandle;
  334. suiStartup: TStartupInfo;
  335. piProcess: TProcessInformation;
  336. pBuffer: array [0..CReadBuffer] of AnsiChar;
  337. dBuffer: array [0..CReadBuffer] of AnsiChar;
  338. dRead: DWORD;
  339. dRunning: DWORD;
  340. dAvailable: DWORD;
  341. begin
  342. saSecurity.nLength := SizeOf(Windows.TSecurityAttributes);
  343. saSecurity.bInheritHandle := true;
  344. saSecurity.lpSecurityDescriptor := nil;
  345. if CreatePipe(hRead,hWrite,@saSecurity, 0) then
  346. begin
  347. try
  348. FillChar(suiStartup, SizeOf(TStartupInfo), #0);
  349. suiStartup.cb := SizeOf(TStartupInfo);
  350. suiStartup.hStdInput := hRead;
  351. suiStartup.hStdOutput := hWrite;
  352. suiStartup.hStdError := hWrite;
  353. suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
  354. suiStartup.wShowWindow := SW_HIDE;
  355. if CreateProcess(nil,PChar(Format('%s %s',[aCommand,aParameters])),
  356. @saSecurity,
  357. @saSecurity,
  358. True,
  359. NORMAL_PRIORITY_CLASS,
  360. nil,
  361. nil,
  362. suiStartup,
  363. piProcess) then
  364. begin
  365. try
  366. repeat
  367. dRunning := WaitForSingleObject(piProcess.hProcess,100);
  368. PeekNamedPipe(hRead,nil,0,nil,@dAvailable,nil);
  369. if (dAvailable > 0) then
  370. begin
  371. repeat
  372. dRead := 0;
  373. ReadFile(hRead,pBuffer[0],CReadBuffer,dRead,nil);
  374. pBuffer[dRead] := #0;
  375. OemToCharA(pBuffer,dBuffer);
  376. if Assigned(CallBack) then CallBack(dBuffer);
  377. if Assigned(OutputLines) then OutputLines.Add(dBuffer);
  378. until (dRead < CReadBuffer);
  379. end;
  380. //Application.ProcessMessages;
  381. until (dRunning <> WAIT_TIMEOUT);
  382. finally
  383. CloseHandle(piProcess.hProcess);
  384. CloseHandle(piProcess.hThread);
  385. end;
  386. end;
  387. finally
  388. CloseHandle(hRead);
  389. CloseHandle(hWrite);
  390. end;
  391. end
  392. else raise Exception.Create('Can''t create pipe!');
  393. end;
  394. procedure InitConsole;
  395. var
  396. BufferInfo: TConsoleScreenBufferInfo;
  397. begin
  398. Console.LogVerbose := LOG_ALL;
  399. Rewrite(Output);
  400. hStdOut := TTextRec(Output).Handle;
  401. {$IFDEF HASERROUTPUT}
  402. Rewrite(ErrOutput);
  403. hStdErr := TTextRec(ErrOutput).Handle;
  404. {$ELSE}
  405. hStdErr := GetStdHandle(STD_ERROR_HANDLE);
  406. {$ENDIF}
  407. if not GetConsoleScreenBufferInfo(hStdOut, BufferInfo) then
  408. begin
  409. SetInOutRes(GetLastError);
  410. Exit;
  411. end;
  412. ConsoleRect.Left := 0;
  413. ConsoleRect.Top := 0;
  414. ConsoleRect.Right := BufferInfo.dwSize.X - 1;
  415. ConsoleRect.Bottom := BufferInfo.dwSize.Y - 1;
  416. TextAttr := BufferInfo.wAttributes and $FF;
  417. DefConsoleColor := TextAttr;
  418. LastMode := 3; //CO80;
  419. end;
  420. initialization
  421. InitializeCriticalSection(CSConsole);
  422. //init stdout if not a service
  423. if GetStdHandle(STD_OUTPUT_HANDLE) <> 0 then InitConsole;
  424. finalization
  425. DeleteCriticalSection(CSConsole);
  426. end.