{ *************************************************************************** Copyright (c) 2016-2024 Kike Pérez Unit : Quick.Console Description : Console output with colors and optional file log Author : Kike Pérez Version : 1.9 Created : 10/05/2017 Modified : 20/01/2024 This file is part of QuickLib: https://github.com/exilon/QuickLib *************************************************************************** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *************************************************************************** } unit Quick.Console; {$i QuickLib.inc} {$IFDEF CONDITIONALEXPRESSIONS} {$ifndef VER140} {$ifndef LINUX} {$define WITHUXTHEME} {$endif} {$endif} {$IFDEF DELPHI2005_UP} {$DEFINE INLINES} {$ENDIF} {$IF RTLVersion >= 14.0} {$DEFINE HASERROUTPUT} {$ENDIF} {$ENDIF} interface uses Classes, {$IFDEF MSWINDOWS} Windows, Messages, {$ELSE} {$IFDEF FPC} crt, {$ENDIF} {$ENDIF} {$IF Defined(DELPHILINUX) OR Defined(MACOS)} Quick.SyncObjs.Linux.Compatibility, Posix.StdDef, {$ENDIF} SysUtils, Quick.Commons, Quick.Log; type //text colors {$IFNDEF DELPHILINUX} TConsoleColor = ( ccBlack = 0, ccBlue = 1, ccGreen = 2, ccCyan = 3, ccRed = 4, ccMagenta = 5, ccBrown = 6, ccLightGray = 7, ccDarkGray = 8, ccLightBlue = 9, ccLightGreen = 10, ccLightCyan = 11, ccLightRed = 12, ccLightMagenta = 13, ccYellow = 14, ccWhite = 15); {$ELSE} TConsoleColor = ( ccDarkGray = 90, ccLightRed = 91, ccLightGreen = 92, ccYellow = 93, ccLightBlue = 94, ccLightMagenta = 95, ccLightCyan = 96, ccWhite = 97, ccBlack = 30, ccRed = 31, ccGreen = 32, ccBrown = 33, ccBlue = 34, ccMagenta = 35, ccCyan = 36, ccLightGray = 37); {$ENDIF} TConsoleProperties = record LogVerbose : TLogVerbose; Log : TQuickLog; end; {$IFNDEF FPC} TOutputProc = reference to procedure(const aLine : T); TExecuteProc = reference to procedure; {$ELSE} TOutputProc = procedure(const aLine : T) of object; TExecuteProc = procedure of object; {$ENDIF} {$IF DEFINED(FPCLINUX) OR DEFINED(DELPHILINUX) OR DEFINED(MACOS)} {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} tcrtcoord = Byte; {$ENDIF} TCoord = record X : tcrtcoord; Y : tcrtcoord; end; TSmallRect = record Left : Byte; Top : Byte; Right : Byte; Bottom : Byte; end; {$ENDIF} {$IFDEF MSWINDOWS} TConsoleMenuOption = record private fCaption : string; fKey : Word; fOnKeyPressed : TExecuteProc; public property Caption : string read fCaption write fCaption; property Key : Word read fKey write fKey; property OnKeyPressed : TExecuteProc read fOnKeyPressed write fOnKeyPressed; procedure DoKeyPressed; end; TConsoleMenu = class private fConsoleMenu : array of TConsoleMenuOption; fMenuColor : TConsoleColor; fIsActive : Boolean; procedure WriteMenu; public constructor Create; property MenuColor : TConsoleColor read fMenuColor write fMenuColor; property IsActive : Boolean read fIsActive; procedure AddMenu(const cMenuCaption : string; const cMenuKey : Word; MenuAction : TExecuteProc); overload; procedure AddMenu(MenuOption : TConsoleMenuOption); overload; procedure Refresh(aClearScreen : Boolean = False); procedure WaitForKeys; end; {$ENDIF} procedure cout(const cMsg : Integer; cEventType : TLogEventType); overload; procedure cout(const cMsg : Double; cEventType : TLogEventType); overload; procedure cout(const cMsg : string; cEventType : TLogEventType); overload; procedure cout(const cMsg : string; cColor : TConsoleColor); overload; procedure coutSL(const cMsg : string; cColor : TConsoleColor); procedure cout(const cMsg : string; params : array of const; cEventType : TLogEventType); overload; procedure coutXY(x,y : Integer; const cMsg : string; cEventType : TLogEventType); overload; procedure coutXY(x,y : Integer; const cMsg : string; cColor : TConsoleColor; cClearLineBefore : Boolean = False); overload; procedure coutXY(x,y : Integer; const cMsg : string; params : array of const; cEventType : TLogEventType); overload; procedure coutXY(x,y : Integer; const cMsg : string; params : array of const; cColor : TConsoleColor); overload; procedure coutTL(const cMsg : string; cEventType : TLogEventType); overload; procedure coutTL(const cMsg : string; cColor : TConsoleColor); overload; procedure coutBL(const cMsg : string; cEventType : TLogEventType); overload; procedure coutBL(const cMsg : string; cColor : TConsoleColor); overload; procedure coutFmt(const cMsg : string; params : array of const; cEventType : TLogEventType); procedure TextColor(Color: TConsoleColor); overload; procedure TextColor(Color: Byte); overload; procedure TextBackground(Color: TConsoleColor); overload; procedure TextBackground(Color: Byte); overload; procedure ResetColors; {$IFDEF MSWINDOWS} procedure ConsoleResize(Width, Height : Integer); {$ENDIF} procedure ClearScreen; procedure ClearLine; overload; procedure ClearLine(Y : Integer); overload; procedure ShowCursor; procedure HideCursor; {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} procedure SaveCursor; procedure RestoreCursor; procedure CursorOn; procedure CursorOff; function ReadKey : Char; {$ELSE} function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF} function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF} {$ENDIF} function GetCursorMaxBottom : Integer; {$IFDEF DELPHILINUX} procedure GotoXY(x,y : Integer); {$ENDIF} procedure SetCursorPos(NewCoord : TCoord); overload; procedure SetCursorPos(x ,y : Integer); overload; {$IFDEF MSWINDOWS} procedure ProcessMessages; {$ENDIF} procedure ConsoleWaitForEnterKey; {$IFDEF MSWINDOWS} function RunConsoleCommand(const aCommand, aParameters : String; CallBack : TOutputProc = nil; OutputLines : TStrings = nil) : Cardinal; procedure InitConsole; {$ENDIF} var Console : TConsoleProperties; CSConsole : TRTLCriticalSection; LastMode : Word; DefConsoleColor : Byte; TextAttr : Byte; hStdOut: THandle; hStdErr: THandle; ConsoleRect: TSmallRect; {$IFDEF MSWINDOWS} ScreenBufInfo : TConsoleScreenBufferInfo; CursorInfo : TConsoleCursorInfo; {$ENDIF} implementation {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} const AEC =chr($1B)+chr($5b); SAVE_CURSOR_POS = chr($1B) + '7'; RESTORE_CURSOR_POS = chr($1B) + '8'; {$ENDIF} procedure cout(const cMsg : Integer; cEventType : TLogEventType); var FmtSets : TFormatSettings; begin try {$IFNDEF FPC} FmtSets := TFormatSettings.Create; {$ENDIF} FmtSets.ThousandSeparator := '.'; FmtSets.DecimalSeparator := ','; cout(FormatFloat('0,',cMsg,FmtSets),cEventType); except cout(cMsg.ToString,cEventType); end; end; procedure cout(const cMsg : Double; cEventType : TLogEventType); var FmtSets : TFormatSettings; begin try {$IFNDEF FPC} FmtSets := TFormatSettings.Create; {$ENDIF} FmtSets.ThousandSeparator := '.'; FmtSets.DecimalSeparator := ','; cout(FormatFloat('.0###,',cMsg,FmtSets),cEventType); except cout(cMsg.ToString,cEventType); end; end; procedure cout(const cMsg : string; cEventType : TLogEventType); begin if cEventType in Console.LogVerbose then begin EnterCriticalSection(CSConsole); try {$IFDEF MSWINDOWS} if hStdOut <> 0 then {$ENDIF} begin case cEventType of etError : TextColor(ccLightRed); etInfo : TextColor(ccWhite); etSuccess : TextColor(ccLightGreen); etWarning : TextColor(ccYellow); etDebug : TextColor(ccLightCyan); etTrace : TextColor(ccLightMagenta); etCritical : begin TextColor(ccYellow); TextBackground(ccRed); end; etException : TextColor(ccRed); else TextColor(ccWhite); end; {$I-} Writeln(cMsg{$IFDEF LINUX} +#13{$ENDIF}); {$I+} TextColor(LastMode); end; finally LeaveCriticalSection(CSConsole); end; if Assigned(Console.Log) then Console.Log.Add(cMsg,cEventType); end; end; procedure cout(const cMsg : string; cColor : TConsoleColor); begin EnterCriticalSection(CSConsole); try {$IFDEF MSWINDOWS} if hStdOut <> 0 then {$ENDIF} begin TextColor(cColor); {$I-} Writeln(cMsg{$IFDEF LINUX} +#13{$ENDIF}); {$I+} TextColor(LastMode); end; finally LeaveCriticalSection(CSConsole); end; end; procedure coutSL(const cMsg : string; cColor : TConsoleColor); begin EnterCriticalSection(CSConsole); try {$IFDEF MSWINDOWS} if hStdOut <> 0 then {$ENDIF} begin TextColor(cColor); {$I-} Write(cMsg);//{$IFDEF LINUX} +#13{$ENDIF}); {$I+} TextColor(LastMode); end; finally LeaveCriticalSection(CSConsole); end; end; procedure cout(const cMsg : string; params : array of const; cEventType : TLogEventType); begin cout(Format(cMsg,params),cEventType); end; {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} procedure SaveCursor; begin write(SAVE_CURSOR_POS); end; procedure RestoreCursor; begin write(RESTORE_CURSOR_POS); end; procedure CursorOn; begin //not implemented yet end; procedure CursorOff; begin //not implemented yet end; function ReadKey : Char; begin Read(Result); end; {$ELSE} function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF} {$IFDEF MSWINDOWS} var BufferInfo: TConsoleScreenBufferInfo; begin GetConsoleSCreenBufferInfo(hStdOut, BufferInfo); Result := BufferInfo.dwCursorPosition.X; end; {$ELSE} begin {$IFDEF FPC} Result := WhereX; {$ELSE} Result := Byte(ConData(CD_CURRX))-lo(windmin); {$ENDIF} end; {$ENDIF} function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF} {$IFDEF MSWINDOWS} var BufferInfo: TConsoleScreenBufferInfo; begin GetConsoleSCreenBufferInfo(hStdOut, BufferInfo); Result := BufferInfo.dwCursorPosition.Y; end; {$ELSE} begin Result := WhereY; end; {$ENDIF} {$ENDIF DELPHILINUX} function GetCursorMaxBottom : Integer; {$IFDEF MSWINDOWS} var BufferInfo: TConsoleScreenBufferInfo; begin GetConsoleSCreenBufferInfo(hStdOut, BufferInfo); Result := BufferInfo.srWindow.Bottom; end; {$ELSE} begin Result := 25; end; {$ENDIF} {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} procedure GotoXY(x,y : Integer); begin Write(AEC, y, ';', x, 'H'); end; {$ENDIF} procedure SetCursorPos(NewCoord : TCoord); begin {$IFDEF MSWINDOWS} SetConsoleCursorPosition(hStdOut, NewCoord); {$ELSE} GotoXY(NewCoord.X,NewCoord.Y); {$ENDIF} end; procedure SetCursorPos(x ,y : Integer); var NewCoord : TCoord; begin NewCoord.X := x; NewCoord.Y := y; SetCursorPos(NewCoord); end; procedure coutXY(x,y : Integer; const cMsg : string; cEventType : TLogEventType); var NewCoord : TCoord; LastCoord : TCoord; begin {$IFDEF MSWINDOWS} if hStdOut = 0 then Exit; {$ENDIF} {$IF NOT DEFINED(DELPHILINUX) AND NOT DEFINED(MACOS)} LastCoord.X := GetCursorX; LastCoord.Y := GetCursorY; {$ELSE} write(SAVE_CURSOR_POS); {$ENDIF} NewCoord.X := x; NewCoord.Y := y; ClearLine(Y); SetCursorPos(NewCoord); try cout(cMsg,cEventType); finally {$IFNDEF DELPHILINUX} SetCursorPos(LastCoord); {$ELSE} write(RESTORE_CURSOR_POS); {$ENDIF} end; end; procedure coutXY(x,y : Integer; const cMsg : string; cColor : TConsoleColor; cClearLineBefore : Boolean = False); overload; var NewCoord : TCoord; LastCoord : TCoord; begin {$IFDEF MSWINDOWS} if hStdOut = 0 then Exit; {$ENDIF} {$IF NOT DEFINED(DELPHILINUX) AND NOT DEFINED(MACOS)} LastCoord.X := GetCursorX; LastCoord.Y := GetCursorY; {$ELSE} write(SAVE_CURSOR_POS); {$ENDIF} NewCoord.X := x; NewCoord.Y := y; if cClearLineBefore then ClearLine(Y); SetCursorPos(NewCoord); try cout(cMsg,cColor); finally {$IFNDEF DELPHILINUX} SetCursorPos(LastCoord); {$ELSE} write(RESTORE_CURSOR_POS); {$ENDIF} end; end; procedure coutXY(x,y : Integer; const cMsg : string; params : array of const; cEventType : TLogEventType); begin coutXY(x,y,Format(cMsg,params),cEventType); end; procedure coutXY(x,y : Integer; const cMsg : string; params : array of const; cColor : TConsoleColor); begin coutXY(x,y,Format(cMsg,params),cColor); end; procedure coutTL(const cMsg : string; cEventType : TLogEventType); begin coutXY(0,0,cMsg,cEventType); end; procedure coutTL(const cMsg : string; cColor : TConsoleColor); begin coutXY(0,0,cMsg,cColor); end; procedure coutBL(const cMsg : string; cEventType : TLogEventType); begin coutXY(0,GetCursorMaxBottom - 1,cMsg,cEventType); end; procedure coutBL(const cMsg : string; cColor : TConsoleColor); begin coutXY(0,GetCursorMaxBottom - 1,cMsg,cColor); end; procedure coutFmt(const cMsg : string; params : array of const; cEventType : TLogEventType); begin cout(Format(cMsg,params),cEventType); end; procedure TextColor(Color: TConsoleColor); begin TextColor(Integer(Color)); end; procedure TextColor(Color: Byte); begin {$IFDEF MSWINDOWS} if hStdOut = 0 then Exit; LastMode := TextAttr; TextAttr := (TextAttr and $F0) or (Color and $0F); if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr); {$ELSE} {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} write(AEC,';',Color,'m') {$ELSE} crt.TextColor(Color); {$ENDIF} {$ENDIF} end; procedure TextBackground(Color: TConsoleColor); begin TextBackground(Integer(Color)); end; procedure TextBackground(Color: Byte); begin {$IFDEF MSWINDOWS} if hStdOut = 0 then Exit; LastMode := TextAttr; TextAttr := (TextAttr and $0F) or ((Color shl 4) and $F0); if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr); {$ELSE} {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} write(AEC,Color,';3m'); {$ELSE} crt.TextBackground(Color); {$ENDIF} {$ENDIF} end; procedure ResetColors; begin {$IFDEF MSWINDOWS} SetConsoleTextAttribute(hStdOut, DefConsoleColor); TextAttr := DefConsoleColor; {$ELSE} {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} write(AEC,0,'m'); {$ELSE} TextColor(ccLightGray); TextBackground(ccBlack); {$ENDIF} {$ENDIF} end; {$IFDEF MSWINDOWS} procedure ConsoleResize(Width, Height : Integer); var Rect: TSmallRect; Coord: TCoord; begin Rect.Left := 1; Rect.Top := 1; Rect.Right := Width; Rect.Bottom := Height; Coord.X := Rect.Right + 1 - Rect.Left; Coord.y := Rect.Bottom + 1 - Rect.Top; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), Coord); SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), True, Rect); end; {$ENDIF} procedure ClearScreen; {$IFDEF MSWINDOWS} var stdout: THandle; bufinfo: TConsoleScreenBufferInfo; ConsoleSize: DWORD; NumWritten: DWORD; Origin: TCoord; begin stdout := GetStdHandle(STD_OUTPUT_HANDLE); if stdout<>INVALID_HANDLE_VALUE then begin GetConsoleScreenBufferInfo(stdout,bufinfo); ConsoleSize := bufinfo.dwSize.X * bufinfo.dwSize.Y; Origin.X := 0; Origin.Y := 0; FillConsoleOutputCharacter(stdout,' ',ConsoleSize,Origin,NumWritten); FillConsoleOutputAttribute(stdout,bufinfo.wAttributes,ConsoleSize,Origin,NumWritten); SetConsoleCursorPosition(stdout, Origin); end; end; {$ELSE} begin {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} write(AEC,2,'J'); {$ELSE} ClrScr; {$ENDIF} end; {$ENDIF} procedure ClearLine; begin {$IF NOT DEFINED(DELPHILINUX) AND NOT DEFINED(MACOS)} ClearLine(GetCursorY); {$ELSE} write(AEC,'K'); {$ENDIF} end; procedure ClearLine(Y : Integer); {$IFDEF MSWINDOWS} var dwWriteCoord: TCoord; dwCount, dwSize: DWord; begin if hStdOut = 0 then Exit; dwWriteCoord.X := 0; dwWriteCoord.Y := Y; dwSize := ConsoleRect.Right + 1; FillConsoleOutputAttribute(hStdOut, TextAttr, dwSize, dwWriteCoord, dwCount); FillConsoleOutputCharacter(hStdOut, ' ', dwSize, dwWriteCoord, dwCount); end; {$ELSE} begin GotoXY(1,Y); {$IF DEFINED(DELPHILINUX) OR DEFINED(MACOS)} write(AEC,'K'); {$ELSE} DelLine; {$ENDIF} GotoXY(1,Y); end; {$ENDIF} procedure ShowCursor; begin {$IFDEF MSWINDOWS} GetConsoleCursorInfo(hStdOut,CursorInfo); CursorInfo.bVisible := True; SetConsoleCursorInfo(hStdOut,CursorInfo); {$ELSE} CursorOn; {$ENDIF} end; procedure HideCursor; begin {$IFDEF MSWINDOWS} GetConsoleCursorInfo(hStdOut,CursorInfo); CursorInfo.bVisible := False; SetConsoleCursorInfo(hStdOut,CursorInfo); {$ELSE} CursorOff; {$ENDIF} end; function ConsoleKeyPressed(ExpectedKey: Word): Boolean; {$IFDEF MSWINDOWS} var lpNumberOfEvents: DWORD; lpBuffer: TInputRecord; lpNumberOfEventsRead : DWORD; nStdHandle: THandle; begin Result := False; nStdHandle := GetStdHandle(STD_INPUT_HANDLE); lpNumberOfEvents := 0; GetNumberOfConsoleInputEvents(nStdHandle, lpNumberOfEvents); if lpNumberOfEvents <> 0 then begin PeekConsoleInput(nStdHandle, lpBuffer, 1, lpNumberOfEventsRead); if lpNumberOfEventsRead <> 0 then begin if lpBuffer.EventType = KEY_EVENT then begin if lpBuffer.Event.KeyEvent.bKeyDown and ((ExpectedKey = 0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode = ExpectedKey)) then Result := true else FlushConsoleInputBuffer(nStdHandle); end else FlushConsoleInputBuffer(nStdHandle); end; end; end; {$ELSE} var kp : Char; begin repeat kp := Readkey; until kp = Char(ExpectedKey); Result := True; end; {$ENDIF} function GetConsoleKeyPressed : Word; {$IFDEF MSWINDOWS} var lpNumberOfEvents: DWORD; lpBuffer: TInputRecord; lpNumberOfEventsRead : DWORD; nStdHandle: THandle; begin Result := 0; nStdHandle := GetStdHandle(STD_INPUT_HANDLE); lpNumberOfEvents := 0; GetNumberOfConsoleInputEvents(nStdHandle, lpNumberOfEvents); if lpNumberOfEvents <> 0 then begin PeekConsoleInput(nStdHandle, lpBuffer, 1, lpNumberOfEventsRead); if lpNumberOfEventsRead <> 0 then begin if lpBuffer.EventType = KEY_EVENT then begin Result := lpBuffer.Event.KeyEvent.wVirtualKeyCode; FlushConsoleInputBuffer(nStdHandle); end else FlushConsoleInputBuffer(nStdHandle); end; end; end; {$ELSE} begin Result := Ord(ReadKey); end; {$ENDIF} {$IFDEF MSWINDOWS} procedure ProcessMessages; var Msg: TMsg; begin while integer(PeekMessage(Msg, 0, 0, 0, PM_REMOVE)) <> 0 do begin TranslateMessage(Msg); DispatchMessage(Msg); end; end; {$ENDIF} {$IFDEF MSWINDOWS} procedure ConsoleWaitForEnterKey; var msg: TMsg; begin while not ConsoleKeyPressed(VK_RETURN) do begin {$ifndef LVCL} {$IFDEF FPC} if GetCurrentThreadID = MainThreadID then begin CheckSynchronize; Sleep(1); end else {$ELSE} if GetCurrentThreadID = MainThreadID then CheckSynchronize{$IFDEF DELPHI7_UP}(1000){$ENDIF} else {$ENDIF} {$endif} WaitMessage; while PeekMessage(msg,0,0,0,PM_REMOVE) do begin if Msg.Message = WM_QUIT then Exit else begin TranslateMessage(Msg); DispatchMessage(Msg); end; end; end; end; {$ELSE} procedure ConsoleWaitForEnterKey; begin ReadLn; end; {$ENDIF} {$IFDEF MSWINDOWS} function RunConsoleCommand(const aCommand, aParameters : String; CallBack : TOutputProc = nil; OutputLines : TStrings = nil) : Cardinal; const CReadBuffer = 2400; var saSecurity: Windows.TSecurityAttributes; hRead: THandle; hWrite: THandle; suiStartup: TStartupInfo; piProcess: TProcessInformation; pBuffer: array [0..CReadBuffer] of AnsiChar; dBuffer: array [0..CReadBuffer] of AnsiChar; dRead: DWORD; dRunning: DWORD; dAvailable: DWORD; begin Result := 0; saSecurity.nLength := SizeOf(Windows.TSecurityAttributes); saSecurity.bInheritHandle := true; saSecurity.lpSecurityDescriptor := nil; if CreatePipe(hRead,hWrite,@saSecurity, 0) then begin try FillChar(suiStartup, SizeOf(TStartupInfo), #0); suiStartup.cb := SizeOf(TStartupInfo); suiStartup.hStdInput := hRead; suiStartup.hStdOutput := hWrite; suiStartup.hStdError := hWrite; suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; suiStartup.wShowWindow := SW_HIDE; if CreateProcess(nil,PChar(Format('%s %s',[aCommand,aParameters])), @saSecurity, @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then begin try repeat dRunning := WaitForSingleObject(piProcess.hProcess,100); PeekNamedPipe(hRead,nil,0,nil,@dAvailable,nil); if (dAvailable > 0) then begin repeat dRead := 0; ReadFile(hRead,pBuffer[0],CReadBuffer,dRead,nil); pBuffer[dRead] := #0; OemToCharA(pBuffer,dBuffer); if Assigned(CallBack) then CallBack(dBuffer); if Assigned(OutputLines) then OutputLines.Add(string(dBuffer)); until (dRead < CReadBuffer); end; //Application.ProcessMessages; until (dRunning <> WAIT_TIMEOUT); finally CloseHandle(piProcess.hProcess); CloseHandle(piProcess.hThread); end; end; GetExitCodeProcess(piProcess.hProcess,Result); finally CloseHandle(hRead); CloseHandle(hWrite); end; end else raise Exception.Create('Can''t create pipe!'); end; {$ENDIF} {$IFDEF MSWINDOWS} procedure InitConsole; var BufferInfo: TConsoleScreenBufferInfo; begin Rewrite(Output); hStdOut := TTextRec(Output).Handle; {$IFDEF HASERROUTPUT} Rewrite(ErrOutput); hStdErr := TTextRec(ErrOutput).Handle; {$ELSE} hStdErr := GetStdHandle(STD_ERROR_HANDLE); {$ENDIF} if not GetConsoleScreenBufferInfo(hStdOut, BufferInfo) then begin {$IFNDEF FPC} SetInOutRes(GetLastError); {$ENDIF} Exit; end; ConsoleRect.Left := 0; ConsoleRect.Top := 0; ConsoleRect.Right := BufferInfo.dwSize.X - 1; ConsoleRect.Bottom := BufferInfo.dwSize.Y - 1; TextAttr := BufferInfo.wAttributes and $FF; DefConsoleColor := TextAttr; LastMode := 3; //CO80; end; {$ELSE} //AssignCrt(stderr); //Rewrite(stderr); {$ENDIF} { TConsoleMenu } {$IFDEF MSWINDOWS} procedure TConsoleMenu.AddMenu(const cMenuCaption: string; const cMenuKey: Word; MenuAction: TExecuteProc); var conmenu : TConsoleMenuOption; begin conmenu.Caption := cMenuCaption; conmenu.Key := cMenuKey; conmenu.OnKeyPressed := MenuAction; {$IFDEF DELPHIXE7_UP} fConsoleMenu := fConsoleMenu + [conmenu]; {$ELSE} SetLength(fConsoleMenu,Length(fConsoleMenu)+1); fConsoleMenu[High(fConsoleMenu)] := conmenu; {$ENDIF} end; procedure TConsoleMenu.AddMenu(MenuOption: TConsoleMenuOption); begin {$IFDEF DELPHIXE7_UP} fConsoleMenu := fConsoleMenu + [MenuOption]; {$ELSE} SetLength(fConsoleMenu,Length(fConsoleMenu)+1); fConsoleMenu[High(fConsoleMenu)] := MenuOption; {$ENDIF} end; constructor TConsoleMenu.Create; begin fMenuColor := ccLightCyan; fIsActive := False; end; procedure TConsoleMenu.Refresh(aClearScreen: Boolean); begin if aClearScreen then ClearScreen; WriteMenu; end; procedure TConsoleMenu.WaitForKeys; var msg: TMsg; conmenu : TConsoleMenuOption; keypressed : Word; begin fIsActive := True; HideCursor; WriteMenu; while True do begin //check key pressed keypressed := GetConsoleKeyPressed; for conmenu in fConsoleMenu do begin if keypressed = conmenu.Key then begin ClearScreen; WriteMenu; conmenu.DoKeyPressed; end; end; if keypressed = VK_ESCAPE then begin coutXY(50,12,'Exiting...',etInfo); Exit; end; {$ifndef LVCL} if GetCurrentThreadID=MainThreadID then CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif} else {$endif} WaitMessage; while PeekMessage(msg,0,0,0,PM_REMOVE) do begin if Msg.Message = WM_QUIT then Exit else begin TranslateMessage(Msg); DispatchMessage(Msg); end; end; end; ShowCursor; fIsActive := False; end; function GetCharFromVirtualKey(Key: Word): string; var keyboardState: TKeyboardState; asciiResult: Integer; begin GetKeyboardState(keyboardState) ; SetLength(Result, 2) ; asciiResult := ToAscii(key, MapVirtualKey(key, 0), keyboardState, @Result[1], 0) ; case asciiResult of 0: Result := ''; 1: SetLength(Result, 1) ; 2:; else Result := ''; end; end; procedure TConsoleMenu.WriteMenu; var conmenu : TConsoleMenuOption; ckey : string; coord : TCoord; oldcoord : TCoord; begin oldcoord.X := GetCursorX; oldcoord.Y := GetCursorY; coord.X := 0; coord.Y := 0; SetCursorPos(coord); TextColor(fMenuColor); ClearLine(0); for conmenu in fConsoleMenu do begin case conmenu.Key of VK_F1 : ckey := 'F1'; VK_F2 : ckey := 'F2'; VK_F3 : ckey := 'F3'; VK_F4 : ckey := 'F4'; VK_F5 : ckey := 'F5'; VK_F6 : ckey := 'F6'; VK_F7 : ckey := 'F7'; VK_F8 : ckey := 'F8'; VK_F9 : ckey := 'F9'; VK_F10 : ckey := 'F10'; VK_F11 : ckey := 'F11'; VK_F12 : ckey := 'F12'; else ckey := GetCharFromVirtualKey(conmenu.Key); end; TextColor(ccWhite); Write(Format('[%s]',[ckey])); TextColor(Self.MenuColor); Write(Format(' %s ',[conmenu.Caption])); end; TextColor(ccWhite); Write('[ESC]'); TextColor(Self.MenuColor); Write(' Exit'); TextColor(LastMode); SetCursorPos(oldcoord); end; { TConsoleMenuOption } procedure TConsoleMenuOption.DoKeyPressed; begin if Assigned(fOnKeyPressed) then fOnKeyPressed; end; {$ENDIF} initialization Console.LogVerbose := LOG_ALL; {$IF DEFINED(FPC) AND DEFINED(LINUX)} InitCriticalSection(CSConsole); {$ELSE} {$IF NOT DEFINED(DELPHILINUX) AND NOT DEFINED(MACOS)} InitializeCriticalSection(CSConsole); //init stdout if not a service try if HasConsoleOutput then InitConsole; except //avoid raise exception end; {$ELSE} CSConsole := TRTLCriticalSection.Create; {$ENDIF} {$ENDIF} finalization {$IF DEFINED(FPC) AND DEFINED(LINUX)} DoneCriticalsection(CSConsole); {$ELSE} {$IFNDEF DELPHILINUX} DeleteCriticalSection(CSConsole); {$ELSE} CSConsole.Free; {$ENDIF} {$ENDIF} end.