You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/UnixConsole.cpp

2525 lines
59 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : Console implementation for UNIX systems based on curses ncurses
#include "CrySystem_precompiled.h"
#include "System.h"
#include "UnixConsole.h"
#if defined(USE_DEDICATED_SERVER_CONSOLE)
#if defined(USE_UNIXCONSOLE)
#if !defined(WIN32)
#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include <signal.h>
#if defined(MAC)
#include <util.h>
#include <sys/ioctl.h>
#else
#include <pty.h>
#endif // defined(MAC)
#if defined(LINUX) || defined(MAC)
#include <syslog.h>
#endif
#endif
#include <CryThread.h>
#define UNIXConsole_MORE_LEFT "<<"
#define UNIXConsole_MORE_RIGHT ">>"
#define UNIXConsole_MORE_COLOR (3) // Colorpair index
#define UNIXConsole_PROMPT "] "
#define UNIXConsole_PROMPT_COLOR (2) // Colorpair index
#define UNIXConsole_WRAP_CHAR '\\'
#define UNIXConsole_WRAP_COLOR (4)
#define UNIXConsole_MIN_WIDTH (10)
#define UNIXConsole_MAX_LINES (1000)
#define UNIXConsole_MAX_HISTORY (100)
#if defined(LINUX) || defined(MAC)
// Should find a better test for ncurses...
#define NCURSES 1
#endif
#if defined(WIN32)
#define snprintf _snprintf
#endif
// macro which check if we should process console function
// used for disable console on Linux
#define IS_SHOW_CONSOLE if (!m_bShowConsole) {return; }
#define IS_SHOW_CONSOLE_RET(ret) if (!m_bShowConsole) {return ret; }
CryCriticalSectionNonRecursive CUNIXConsole::m_cleanupLock;
class CUNIXConsoleInputThread
: public CrySimpleThread<>
{
CUNIXConsole& m_UNIXConsole;
#if !defined(WIN32)
int m_IntrPipe[2];
#else
HANDLE m_IntrEvent;
#endif
bool m_Cancelled;
public:
CUNIXConsoleInputThread(CUNIXConsole& UNIXConsole)
: m_UNIXConsole(UNIXConsole)
, m_Cancelled(false)
{
#if !defined(WIN32)
pipe(m_IntrPipe);
#else
m_IntrEvent = CreateEvent(NULL, true, false, NULL);
#endif
}
~CUNIXConsoleInputThread()
{
#if !defined(WIN32)
close(m_IntrPipe[0]);
close(m_IntrPipe[1]);
#else
CloseHandle(m_IntrEvent);
#endif
}
virtual void Run();
virtual void Cancel() { m_Cancelled = true; Interrupt(); }
void Interrupt()
{
#if !defined(WIN32)
write(m_IntrPipe[1], "", 1);
#else
SetEvent(m_IntrEvent);
#endif
}
};
class CUNIXConsoleSignalHandler
{
friend class CUNIXConsole;
static CUNIXConsole* m_pUNIXConsole;
static void Handler(int signum);
};
CUNIXConsole::CUNIXConsole()
: m_HistoryIndex(-1)
, m_PromptResponse(0)
, m_pSystem(NULL)
, m_pConsole(NULL)
, m_pTimer(NULL)
, m_OnUpdateCalled(false)
, m_LastUpdateTime(0.0f)
, m_svMap(NULL)
, m_svGameRules(NULL)
, m_Width(~0)
, m_Height(~0)
, m_HeaderHeight(1)
, m_StatusHeight(1)
, m_CmdHeight(2)
, m_Color(DEFAULT_COLOR)
, m_DefaultColorPair(-1)
, m_EnableColor(true)
, m_WindowResized(false)
, m_OnShutdownCalled(false)
, m_Initialized(false)
, m_RequireDedicatedServer(false)
, m_ScrollUp(0)
, m_InputThread(NULL)
, m_CursorPosition(0)
, m_ScrollPosition(0)
, m_fsMode(false)
, m_bShowConsole(true)
{
}
CUNIXConsole::~CUNIXConsole()
{
Cleanup();
m_Lock.Lock();
}
void CUNIXConsole::SetRequireDedicatedServer(bool value)
{
assert(!m_Initialized);
m_RequireDedicatedServer = value;
}
#if defined(WIN32) || defined(WIN64)
void ResizeConBufAndWindow(HANDLE hConsole, SHORT xSize, SHORT ySize)
{
CONSOLE_SCREEN_BUFFER_INFO csbi; /* hold current console buffer info */
BOOL bSuccess;
SMALL_RECT srWindowRect; /* hold the new console size */
COORD coordScreen;
bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);
/* get the largest size we can size the console window to */
coordScreen = GetLargestConsoleWindowSize(hConsole);
/* define the new console window size and scroll position */
srWindowRect.Right = (SHORT) (min(xSize, coordScreen.X) - 1);
srWindowRect.Bottom = (SHORT) (min(ySize, coordScreen.Y) - 1);
srWindowRect.Left = srWindowRect.Top = (SHORT) 0;
/* define the new console buffer size */
coordScreen.X = xSize;
coordScreen.Y = ySize;
/* if the current buffer is larger than what we want, resize the */
/* console window first, then the buffer */
if ((DWORD) csbi.dwSize.X * csbi.dwSize.Y > (DWORD) xSize * ySize)
{
bSuccess = SetConsoleWindowInfo(hConsole, TRUE, &srWindowRect);
bSuccess = SetConsoleScreenBufferSize(hConsole, coordScreen);
bSuccess = SetConsoleWindowInfo(hConsole, TRUE, &srWindowRect);
bSuccess = SetConsoleScreenBufferSize(hConsole, coordScreen);
}
/* if the current buffer is smaller than what we want, resize the */
/* buffer first, then the console window */
if ((DWORD) csbi.dwSize.X * csbi.dwSize.Y < (DWORD) xSize * ySize)
{
bSuccess = SetConsoleScreenBufferSize(hConsole, coordScreen);
bSuccess = SetConsoleWindowInfo(hConsole, TRUE, &srWindowRect);
}
/* if the current buffer *is* the size we want, don't do anything! */
return;
}
BOOL WINAPI CtrlHandler(DWORD evt)
{
switch (evt)
{
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
return TRUE;
case CTRL_CLOSE_EVENT:
gEnv->pSystem->Quit();
return TRUE;
}
return FALSE;
}
#endif
void CUNIXConsole::Init(const char* headerString)
{
assert(!m_Initialized);
if (headerString != NULL)
{
m_HeaderString = headerString;
}
#if defined(WIN32) || defined(WIN64)
// Allocate a console for the process. We don't care if this is successful
// or not, because failure indicates that the process already has a
// console, which is fine.
AllocConsole();
HANDLE hConsOut = GetStdHandle(STD_OUTPUT_HANDLE);
ResizeConBufAndWindow(hConsOut, 120, 60);
SetConsoleCtrlHandler(CtrlHandler, TRUE);
#endif
// Initialize curses.
initscr();
cbreak();
noecho();
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
scrollok(stdscr, TRUE);
idcok(stdscr, TRUE);
idlok(stdscr, TRUE);
nodelay(stdscr, TRUE);
// Enable color output.
if (m_EnableColor)
{
if (start_color() != OK)
{
m_EnableColor = false;
}
}
if (m_EnableColor)
{
#if defined(NCURSES)
// Setup the color table.
short colorPair = 0;
attr_t attr;
attr_get(&attr, &colorPair, NULL);
m_DefaultColorPair = colorPair;
m_ColorPair[0] = m_DefaultColorPair;
m_ColorPair[1] = m_DefaultColorPair;
short color_fg = 0, color_bg = 0;
pair_content(m_DefaultColorPair, &color_fg, &color_bg);
short pair = 0;
init_pair(++pair, COLOR_BLUE, color_bg);
m_ColorPair[2] = pair;
init_pair(++pair, COLOR_GREEN, color_bg);
m_ColorPair[3] = pair;
init_pair(++pair, COLOR_RED, color_bg);
m_ColorPair[4] = pair;
init_pair(++pair, COLOR_CYAN, color_bg);
m_ColorPair[5] = pair;
init_pair(++pair, COLOR_YELLOW, COLOR_BLACK);
m_ColorPair[6] = pair;
init_pair(++pair, COLOR_MAGENTA, color_bg);
m_ColorPair[7] = pair;
init_pair(++pair, COLOR_RED, color_bg);
m_ColorPair[8] = pair;
init_pair(++pair, COLOR_BLACK, COLOR_WHITE);
m_ColorPair[9] = pair;
#else
// Color output supported only for ncurses.
m_EnableColor = false;
m_DefaultColorPair = 0;
#endif
}
else
{
m_DefaultColorPair = 0;
}
// Set the screen size and draw the initial screen.
SetSize(COLS, LINES);
m_syslogStats.Init();
m_Initialized = true;
}
void CUNIXConsole::Cleanup()
{
m_cleanupLock.Lock();
if (m_Initialized)
{
// Kill the input thread.
if (m_InputThread != NULL)
{
m_InputThread->Cancel();
m_InputThread->WaitForThread();
delete m_InputThread;
m_InputThread = NULL;
}
// Curses cleanup.
clear();
endwin();
m_Initialized = false;
}
m_cleanupLock.Unlock();
}
void CUNIXConsole::SetSize(unsigned width, unsigned height)
{
bool repaint = false;
assert(IsLocked());
if (width != m_Width)
{
m_Width = width;
FixCursorPosition();
repaint = true;
}
if (height != m_Height)
{
m_Height = height;
repaint = true;
}
if (repaint)
{
Repaint();
}
}
bool CUNIXConsole::IsTooSmall()
{
assert(IsLocked());
if (m_Height < m_HeaderHeight + m_StatusHeight + m_CmdHeight + 1)
{
return true;
}
if (m_Width < UNIXConsole_MIN_WIDTH)
{
return true;
}
return false;
}
void CUNIXConsole::CheckResize()
{
assert(IsLocked());
#if defined(NCURSES)
int lines, cols;
IS_SHOW_CONSOLE
if (m_WindowResized)
{
m_WindowResized = false;
// Get the new window size from the terminal driver.
winsize ws;
memset(&ws, 0, sizeof ws);
ioctl(1, TIOCGWINSZ, &ws);
lines = ws.ws_row;
cols = ws.ws_col;
if (m_Width != lines || m_Height != cols)
{
resizeterm(lines, cols);
SetSize(cols, lines);
Repaint();
}
}
#endif
}
void CUNIXConsole::NewLine()
{
char lineBuf[3] = "\1X";
bool doScroll = !m_LineBuffer.empty();
assert(IsLocked());
lineBuf[1] = m_Color + '0';
if (m_LineBuffer.size() == UNIXConsole_MAX_LINES)
{
m_LineBuffer.pop_front();
}
m_LineBuffer.push_back(lineBuf);
if (doScroll)
{
ScrollLog();
}
else
{
unsigned row = m_Height - m_CmdHeight - m_StatusHeight - 1;
move(row, 0);
}
if (m_ScrollUp > 0)
{
const string& lastDisplayLine
= m_LineBuffer[m_LineBuffer.size() - m_ScrollUp - 1];
DrawLogLine(lastDisplayLine);
}
}
void CUNIXConsole::ContinueLine()
{
IS_SHOW_CONSOLE
if (m_LineBuffer.empty())
{
NewLine();
return;
}
const string& lastLine = *m_LineBuffer.rbegin();
unsigned column = DrawLogLine(lastLine, true /* noOutput */);
SetColorRaw(m_Color);
const unsigned row = m_Height - m_CmdHeight - m_StatusHeight - 1;
move(row, column);
}
void CUNIXConsole::SetColor(int color)
{
assert(IsLocked());
m_Color = color;
SetColorRaw(color);
if (m_LineBuffer.empty())
{
m_LineBuffer.push_back("");
}
const TLineBuffer::const_reverse_iterator it = m_LineBuffer.rbegin();
string& lastLine = const_cast<string&>(*it);
lastLine.push_back(1);
lastLine.push_back('0' + color);
}
void CUNIXConsole::SetColorRaw(int color)
{
attr_t colorAttr = 0;
assert(IsLocked());
if (color == DEFAULT_COLOR)
{
color = 0;
}
if (m_EnableColor)
{
colorAttr = COLOR_PAIR(m_ColorPair[color]);
}
switch (color)
{
case 0:
attrset(A_NORMAL | colorAttr);
break;
case 1:
attrset(A_REVERSE | colorAttr);
break;
case 2:
case 3:
attrset(A_NORMAL | colorAttr);
break;
case 4:
attrset(A_BOLD | colorAttr);
break;
case 5:
attrset(A_NORMAL | colorAttr);
break;
case 6:
attrset(A_BOLD | colorAttr);
break;
case 7:
case 8:
case 9:
attrset(A_NORMAL | colorAttr);
break;
default:
abort();
}
}
void CUNIXConsole::Put(int c)
{
assert(IsLocked());
// Get the last buffer line.
if (m_LineBuffer.empty())
{
NewLine();
}
const TLineBuffer::const_reverse_iterator it = m_LineBuffer.rbegin();
string& lastLine = const_cast<string&>(*it);
assert(c >= 0x20);
// Wrap the line if required.
unsigned row = m_Height - m_CmdHeight - m_StatusHeight - 1;
unsigned column = 0;
GetLineHeight(lastLine, &column);
assert(column <= m_Width);
lastLine.push_back(char(c));
if (m_ScrollUp == 0)
{
// Output the line wrap character.
if (column == m_Width - 1)
{
move(row, m_Width - 1);
attr_t colorAttr = 0;
if (m_EnableColor)
{
colorAttr = COLOR_PAIR(UNIXConsole_WRAP_COLOR);
}
attrset(A_NORMAL | colorAttr);
addch(UNIXConsole_WRAP_CHAR);
ScrollLog();
move(row, 0);
}
// Output the character.
SetColorRaw(m_Color);
addch(c);
}
}
void CUNIXConsole::Put(const char* s)
{
assert(IsLocked());
while (*s)
{
if (*s == '\n')
{
NewLine();
}
else
{
Put(*s);
}
++s;
}
}
unsigned CUNIXConsole::GetLineLength(const string& line)
{
unsigned length = 0;
for (string::const_iterator it = line.begin(), itEnd = line.end();
it != itEnd;
++it)
{
char c = *it;
if (c == 1)
{
++it;
assert(it != itEnd);
continue;
}
++length;
}
return length;
}
char CUNIXConsole::GetLastCharacter(const string& line, int* color)
{
char c0 = 0, c1 = 0;
char lastChar = 0;
if (!line.empty())
{
for (int i = line.size() - 1; i >= 0; i--)
{
c1 = c0;
c0 = line[i];
if (c0 == 1)
{
assert(lastChar);
if (color)
{
*color = c1 - '0';
}
return lastChar;
}
else if (c0 && c1 != 1 && !lastChar)
{
lastChar = c0;
}
}
}
*color = DEFAULT_COLOR;
if (lastChar)
{
return lastChar;
}
assert(c0 && c0 != 1);
return c0;
}
unsigned CUNIXConsole::GetLineHeight(const string& line, unsigned* column)
{
unsigned lineLength = GetLineLength(line);
unsigned height = 1;
assert(IsLocked());
while (lineLength > m_Width)
{
lineLength -= m_Width - 1;
++height;
}
if (column != NULL)
{
*column = lineLength;
}
return height;
}
void CUNIXConsole::ScrollLog()
{
if (m_fsMode)
{
return;
}
// Scroll the log window. We'll do that by defining a software scrolling
// region. The constructor has set scrollok() and idlok(), so the output
// routing will use the hardware scrolling region (if available).
unsigned top = m_HeaderHeight;
unsigned bottom = m_Height - m_CmdHeight - m_StatusHeight;
assert(IsLocked());
// Some curses implementations (pdcurses) require the current position to
// be within the defined scrolling region.
move(top, 0);
if (setscrreg(top, bottom) == OK)
{
move(bottom, 0);
addch('\n');
setscrreg(0, m_Height - 1);
DrawStatus(1);
}
else
{
// Scrolling regions not supported. We'll scroll the entire window and
// the repaint everything except for the log window.
scroll(stdscr);
move(bottom - 1, 0);
attrset(A_NORMAL);
clrtobot();
DrawHeader();
DrawStatus();
DrawCmd();
}
move(bottom - 1, 0);
}
bool CUNIXConsole::FixCursorPosition()
{
IS_SHOW_CONSOLE_RET(false)
bool repaint = false;
assert(IsLocked());
// Clip the cursor position.
if (m_CursorPosition > (int)m_InputLine.size())
{
m_CursorPosition = m_InputLine.size();
}
// Trivial scroll position fixes.
if (m_CursorPosition < (int)m_Width / 2 && m_ScrollPosition > 0)
{
m_ScrollPosition = 0;
repaint = true;
}
else if (m_CursorPosition < m_ScrollPosition)
{
m_ScrollPosition = m_CursorPosition - m_Width / 4;
repaint = true;
}
assert(m_ScrollPosition <= m_CursorPosition);
// The method may be called after the cursor has been moved to the
// right, so we may have to scroll the input line.
int displayLenth = m_Width * m_CmdHeight;
displayLenth -= strlen(UNIXConsole_PROMPT);
// If the cursor is at the end of the input line, then we only must leave
// one space for the cursor itself, otherwise we must leave space for the
// right scroll indicator.
if (m_CursorPosition == m_InputLine.size())
{
displayLenth -= 1;
}
else
{
displayLenth -= strlen(UNIXConsole_MORE_RIGHT);
}
if (m_ScrollPosition < m_CursorPosition - displayLenth)
{
m_ScrollPosition = m_CursorPosition - displayLenth;
repaint = true;
}
return repaint;
}
void CUNIXConsole::OnEdit()
{
assert(IsLocked());
m_SavedInputLine.clear();
m_HistoryIndex = -1;
if (m_pConsole != NULL)
{
m_pConsole->ResetAutoCompletion();
}
}
void CUNIXConsole::KeyEnter()
{
bool redrawAll = false;
bool pushCommand = false;
assert(IsLocked());
// Scroll the log window to the bottom.
if (m_ScrollUp > 0)
{
m_ScrollUp = 0;
redrawAll = true;
}
// Process the input line.
while (!m_InputLine.empty() && m_InputLine[0] == '\\')
{
m_InputLine.erase(0, 1);
}
if (!m_InputLine.empty())
{
pushCommand = true;
#if defined(UC_ENABLE_MAGIC_COMMANDS)
// Enable some magic commands intercepted by the console. All magic
// commands start with an '@' character.
{
const char* const command = m_InputLine.c_str();
if (!azstricmp(command, "@quit"))
{
// We're called from the input thread, hence we can't join it. We
// have to prevent Cleanup() (called via atexit()) from trying to
// join.
CUNIXConsoleInputThread* inputThread = m_InputThread;
m_InputThread = NULL;
Unlock();
if (m_pSystem != NULL)
{
m_pSystem->Quit();
inputThread->Exit();
}
exit(0);
// Not reached.
abort();
}
// Add other magic commands here.
}
#endif
}
if (pushCommand)
{
CSystem* pSystem = static_cast<CSystem*>(gEnv->pSystem);
#if defined(CVARS_WHITELIST)
ICVarsWhitelist* pCVarsWhitelist = pSystem->GetCVarsWhiteList();
bool execute = (pCVarsWhitelist) ? pCVarsWhitelist->IsWhiteListed(m_InputLine, false) : true;
if (execute)
#endif // defined(CVARS_WHITELIST)
{
m_CommandQueue.push_back(m_InputLine);
}
}
if (!m_InputLine.empty())
{
m_CommandHistory.push_back(m_InputLine);
while (m_CommandHistory.size() > UNIXConsole_MAX_HISTORY)
{
m_CommandHistory.pop_front();
}
m_HistoryIndex = -1;
m_InputLine.clear();
m_SavedInputLine.clear();
m_CursorPosition = 0;
m_ScrollPosition = 0;
if (!redrawAll)
{
DrawCmd();
refresh();
}
}
if (redrawAll)
{
Repaint();
}
}
void CUNIXConsole::KeyUp()
{
const int historySize = m_CommandHistory.size();
assert(IsLocked());
if (m_HistoryIndex < historySize - 1)
{
if (m_HistoryIndex == -1)
{
m_SavedInputLine = m_InputLine;
}
m_HistoryIndex += 1;
m_InputLine = m_CommandHistory[historySize - m_HistoryIndex - 1];
m_CursorPosition = m_InputLine.size();
FixCursorPosition();
DrawCmd();
refresh();
}
}
void CUNIXConsole::KeyDown()
{
const int historySize = m_CommandHistory.size();
assert(IsLocked());
if (m_HistoryIndex > -1)
{
m_HistoryIndex -= 1;
if (m_HistoryIndex == -1)
{
m_InputLine = m_SavedInputLine;
m_SavedInputLine.clear();
}
else
{
m_InputLine = m_CommandHistory[historySize - m_HistoryIndex - 1];
}
m_CursorPosition = m_InputLine.size();
FixCursorPosition();
DrawCmd();
refresh();
}
}
void CUNIXConsole::KeyLeft()
{
assert(IsLocked());
if (m_CursorPosition > 0)
{
m_CursorPosition -= 1;
DrawCmd(!FixCursorPosition());
}
}
void CUNIXConsole::KeyRight()
{
assert(IsLocked());
if (m_CursorPosition < (int)m_InputLine.size())
{
m_CursorPosition += 1;
DrawCmd(!FixCursorPosition());
}
}
void CUNIXConsole::KeyHome(bool ctrl)
{
assert(IsLocked());
if (ctrl)
{
const int logHeight = GetLogHeight();
int maxUp = m_LineBuffer.size() - logHeight;
if (m_ScrollUp != maxUp)
{
m_ScrollUp = maxUp;
Repaint();
}
}
else if (m_CursorPosition != 0)
{
m_CursorPosition = 0;
DrawCmd(!FixCursorPosition());
}
}
void CUNIXConsole::KeyEnd(bool ctrl)
{
assert(IsLocked());
if (ctrl)
{
const int logHeight = GetLogHeight();
int maxUp = m_LineBuffer.size() - logHeight;
if (m_ScrollUp != 0)
{
m_ScrollUp = 0;
Repaint();
}
}
else if (m_CursorPosition < (int)m_InputLine.size())
{
m_CursorPosition = m_InputLine.size();
DrawCmd(!FixCursorPosition());
}
}
void CUNIXConsole::KeyBackspace()
{
assert(IsLocked());
if (m_CursorPosition > 0)
{
m_InputLine.erase(m_CursorPosition - 1, 1);
m_CursorPosition -= 1;
FixCursorPosition();
OnEdit();
DrawCmd();
}
}
void CUNIXConsole::KeyDelete()
{
assert(IsLocked());
if (m_CursorPosition < (int)m_InputLine.size())
{
m_InputLine.erase(m_CursorPosition, 1);
FixCursorPosition();
OnEdit();
DrawCmd();
}
}
void CUNIXConsole::KeyDeleteWord()
{
assert(IsLocked());
if (m_CursorPosition > 0)
{
const char* const inputLine = m_InputLine.c_str();
const char* p = inputLine + m_CursorPosition - 1;
while (p > inputLine && *p == ' ')
{
--p;
}
while (p > inputLine && *p != ' ')
{
--p;
}
m_InputLine.erase(p - inputLine, m_CursorPosition);
m_CursorPosition = (uint32)(p - inputLine);
FixCursorPosition();
OnEdit();
DrawCmd();
/* Old std::string based code, kept for reference.
size_t wordIndex;
wordIndex = m_InputLine.find_last_of(" ", m_CursorPosition - 1);
if (wordIndex != string::npos)
{
wordIndex = m_InputLine.find_last_not_of(" ", wordIndex);
if (wordIndex != string::npos)
wordIndex += 1;
}
if (wordIndex == string::npos)
{
m_InputLine.erase(0, m_CursorPosition);
m_CursorPosition = 0;
}
else
{
m_InputLine.erase(wordIndex, m_CursorPosition - wordIndex);
m_CursorPosition = wordIndex;
}
FixCursorPosition();
OnEdit();
DrawCmd();
*/
}
}
void CUNIXConsole::KeyKill()
{
assert(IsLocked());
if (m_CursorPosition < (int)m_InputLine.size())
{
m_InputLine.resize(m_CursorPosition);
FixCursorPosition();
OnEdit();
DrawCmd();
}
}
void CUNIXConsole::KeyRepaint()
{
assert(IsLocked());
Repaint();
}
void CUNIXConsole::KeyTab()
{
const char* result;
assert(IsLocked());
if (m_OnShutdownCalled)
{
return;
}
string Tmp(m_InputLine);
Unlock();
result = m_pConsole->ProcessCompletion(Tmp.c_str());
Lock();
if (result != NULL)
{
if (result[0] == '\\')
{
++result;
}
m_InputLine = result;
m_CursorPosition = m_InputLine.size();
FixCursorPosition();
m_SavedInputLine.clear();
m_HistoryIndex = -1;
DrawCmd();
refresh();
}
}
void CUNIXConsole::KeyPgUp(bool ctrl)
{
const int logHeight = GetLogHeight();
// int logStep = logHeight - 2;
int logStep = ctrl ? 10 : 1;
int maxUp = m_LineBuffer.size() - logHeight;
int prevScrollUp = m_ScrollUp;
assert(IsLocked());
if (logStep < 1)
{
logStep = 1;
}
if (maxUp < 0)
{
maxUp = 0;
}
m_ScrollUp += logStep;
if (m_ScrollUp > maxUp)
{
m_ScrollUp = maxUp;
}
if (m_ScrollUp != prevScrollUp)
{
Repaint();
}
}
void CUNIXConsole::KeyPgDown(bool ctrl)
{
const int logHeight = GetLogHeight();
// int logStep = logHeight - 2;
int logStep = ctrl ? 10 : 1;
int prevScrollUp = m_ScrollUp;
assert(IsLocked());
if (logStep < 1)
{
logStep = 1;
}
if (m_ScrollUp > 0)
{
m_ScrollUp -= logStep;
}
if (m_ScrollUp < 0)
{
m_ScrollUp = 0;
}
if (m_ScrollUp != prevScrollUp)
{
Repaint();
}
}
void CUNIXConsole::Key(int c)
{
assert(IsLocked());
assert(c >= 0x20 && c <= 0xff);
assert(m_CursorPosition <= (int)m_InputLine.size());
m_InputLine.insert(m_CursorPosition, 1, (char)c);
m_CursorPosition += 1;
FixCursorPosition();
OnEdit();
DrawCmd();
refresh();
}
void CUNIXConsole::Repaint()
{
IS_SHOW_CONSOLE
assert(IsLocked());
clear();
DrawHeader();
if (m_fsMode)
{
DrawFullscreen();
}
else
{
DrawLog();
}
DrawStatus();
DrawCmd();
refresh();
}
void CUNIXConsole::Flush()
{
IS_SHOW_CONSOLE
assert(IsLocked());
refresh();
}
void CUNIXConsole::InputIdle()
{
IS_SHOW_CONSOLE
if (m_pTimer == NULL)
{
return;
}
Lock();
CTimeValue now = m_pTimer->GetAsyncTime();
float timePassed = (now - m_LastUpdateTime).GetSeconds();
// If more than 0.2 sec have passed since the last OnUpdate() call, then
// we'll start painting dots to the status line.
if (timePassed > 0.2f)
{
int nDots = (int)(timePassed + 0.5) / 3;
if (nDots > (int) m_Width - 2)
{
nDots = (int)m_Width - 2;
}
if (m_ProgressStatus.length() != nDots)
{
m_ProgressStatus.clear();
m_ProgressStatus.append(nDots, '.');
DrawStatus();
DrawCmd(true);
refresh();
}
}
Unlock();
}
// get at least n spaces
static char* GetSpaces(int n)
{
static char* spaceBuffer = 0;
static int spaceBufferSz = 0;
if (n > spaceBufferSz)
{
spaceBufferSz = MAX(spaceBufferSz * 2, n);
delete[] spaceBuffer;
spaceBuffer = new char[spaceBufferSz];
memset(spaceBuffer, ' ', spaceBufferSz);
}
return spaceBuffer;
}
void CUNIXConsole::DrawHeader()
{
IS_SHOW_CONSOLE
const char* const headerString = m_HeaderString.c_str();
int headerLength = m_HeaderString.size();
int padLeft = 0, padRight = 0;
const char* term = termname();
assert(IsLocked());
if (m_HeaderHeight == 0)
{
return;
}
if (headerLength >= (int)m_Width)
{
padLeft = 0;
padRight = 0;
headerLength = m_Width;
}
else
{
padLeft = (m_Width - headerLength) / 2;
padRight = m_Width - headerLength - padLeft;
}
move(m_HeaderHeight - 1, 0);
#if defined(LINUX)
// For the Linux console ncurses reports the A_UNDERLINE is supported, even
// thought it is not. We'll do an explicit test for the terminal type
// "linux" (i.e. Linux console).
if ((termattrs() & A_UNDERLINE) && strcasecmp(term, "linux"))
{
attrset(A_UNDERLINE);
}
else
#endif
{
if (m_EnableColor)
{
attrset(A_BOLD | COLOR_PAIR(m_ColorPair[2] /* blue */));
}
else
{
attrset(A_REVERSE);
}
}
scrollok(stdscr, FALSE);
addnstr(GetSpaces(padLeft), padLeft);
addnstr(headerString, headerLength);
addnstr(GetSpaces(padRight), padRight);
scrollok(stdscr, TRUE);
attrset(A_NORMAL);
}
// Output a single log line.
// If the noOutput flag is set to 'true', then no output is written to the
// screen and no cursor movements are performed.
// The method returns the current output column (i.e. the output column for
// the next charater to be written to the log window).
// Note: Even it the noOutput flag is set, the method will update the m_Color
// field of the UNIX console.
unsigned CUNIXConsole::DrawLogLine(const string& line, bool noOutput)
{
IS_SHOW_CONSOLE_RET(0)
const unsigned row = m_Height - m_CmdHeight - m_StatusHeight - 1;
unsigned column = 0;
if (!noOutput)
{
assert(IsLocked());
move(row, column);
attrset(A_NORMAL);
}
for (string::const_iterator it = line.begin(), itEnd = line.end(); it != itEnd; )
{
char c = *it++;
if (column == m_Width - 1)
{
if (!noOutput)
{
attr_t colorAttr = 0;
if (m_EnableColor)
{
colorAttr = COLOR_PAIR(UNIXConsole_WRAP_COLOR);
}
attrset(A_NORMAL | colorAttr);
addch(UNIXConsole_WRAP_CHAR);
ScrollLog();
move(row, 0);
SetColorRaw(m_Color);
}
column = 0;
}
if (c == 1)
{
assert(it != itEnd);
int color = *it - '0';
m_Color = color;
++it;
if (!noOutput)
{
SetColorRaw(color);
}
continue;
}
if (!noOutput)
{
addch(c);
}
++column;
}
return column;
}
// The scrollUp parameter indicates how many log lines to scroll up from the
// bottom.
void CUNIXConsole::DrawLog()
{
IS_SHOW_CONSOLE
unsigned scrollUp = m_ScrollUp;
assert(IsLocked());
if (IsTooSmall())
{
return;
}
if (m_LineBuffer.empty())
{
return;
}
// DrawLog is called only on refresh and on window resize, so performance is
// not an issue. We'll simply repaint by re-sending the log lines from the
// scroll buffer.
int nLines = m_LineBuffer.size();
int lastLine = nLines - 1 - (int)scrollUp;
int firstLine = lastLine - GetLogHeight();
if (firstLine < 0)
{
firstLine = 0;
}
for (int i = firstLine; i <= lastLine; ++i)
{
const string& line = m_LineBuffer[i];
if (i > firstLine)
{
ScrollLog();
}
DrawLogLine(line);
}
}
void CUNIXConsole::DrawStatus(int maxLines)
{
IS_SHOW_CONSOLE
unsigned row = m_Height - m_CmdHeight - m_StatusHeight;
const char* statusLeft = NULL;
const char* statusRight = NULL;
char bufferLeft[256];
char bufferRight[256];
assert(IsLocked());
if (IsTooSmall() || maxLines == 0 || m_StatusHeight == 0)
{
return;
}
else if (maxLines == -1)
{
maxLines = m_StatusHeight;
}
// If we're scrolled, then the right size shows a scroll indicator.
if (m_ScrollUp > 0)
{
const int logHeight = GetLogHeight();
int logBottomLine = (int)m_LineBuffer.size() - m_ScrollUp;
assert(logBottomLine >= 0);
float percent = 100.f * (float)logBottomLine / m_LineBuffer.size();
if (m_ScrollUp == m_LineBuffer.size() - logHeight)
{
cry_strcpy(bufferRight, "| SCROLL:TOP ");
}
else
{
snprintf(
bufferRight,
sizeof bufferRight,
"| SCROLL:%.1f%% ",
percent);
bufferRight[sizeof(bufferRight) - 1] = 0;
}
statusRight = bufferRight;
}
if (!m_Prompt.empty())
{
// No status display when a user prompt is active.
}
else if (!m_ProgressStatus.empty())
{
snprintf(bufferLeft, sizeof bufferLeft, " %s", m_ProgressStatus.c_str());
bufferLeft[sizeof bufferLeft - 1] = 0;
statusLeft = bufferLeft;
}
else if (m_OnUpdateCalled)
{
// Standard status display.
// Map name and game rules on the left.
// Current update rate and player count on the right.
const char* mapName = m_svMap->GetString();
const char* gameRules = m_svGameRules->GetString();
snprintf(bufferLeft, sizeof bufferLeft,
" map:%s rules:%s",
mapName,
gameRules);
bufferLeft[sizeof bufferLeft - 1] = 0;
statusLeft = bufferLeft;
float updateRate = 0.f;
static float displayUpdateRate = 0.f;
if (m_pTimer != NULL)
{
updateRate = m_pTimer->GetFrameRate();
#if 0
// Avoid jumping numbers in the update rate display. Per update the
// displayed update rate changes by at most maxDeltaRate.
const static float maxDeltaRate = 10.0f;
if (updateRate - displayUpdateRate > maxDeltaRate)
{
displayUpdateRate += maxDeltaRate;
}
else if (updateRate - displayUpdateRate < -maxDeltaRate)
{
displayUpdateRate -= maxDeltaRate;
}
else
{
displayUpdateRate = updateRate;
}
#else
// Display the update rate as reported by the timer.
displayUpdateRate = updateRate;
#endif
}
else
{
displayUpdateRate = 0.f;
}
if (statusRight == NULL)
{
char* pBufferRight = bufferRight;
char* const pBufferRightEnd = bufferRight + sizeof bufferRight;
azstrcpy(pBufferRight, AZ_ARRAY_SIZE(bufferRight), "| ");
pBufferRight += strlen(pBufferRight);
int numPlayers = 0;
if (pBufferRight < pBufferRightEnd)
{
if (m_pConsole != NULL)
{
pBufferRight += snprintf(
pBufferRight,
pBufferRightEnd - pBufferRight,
"upd:%.1fms(%.2f..%.2f) " \
"rate:%.1f/s",
m_updStats.avgUpdateTime, m_updStats.minUpdateTime, m_updStats.maxUpdateTime,
displayUpdateRate);
}
else
{
cry_strcpy(pBufferRight, pBufferRightEnd - pBufferRight, "BUSY ");
}
}
bufferRight[sizeof bufferRight - 1] = 0;
statusRight = bufferRight;
}
}
else
{
// No status display (blank). This branch is taken on the very first draw
// operation of the UNIX console.
}
if (statusLeft == NULL)
{
statusLeft = "";
}
if (statusRight == NULL)
{
statusRight = "";
}
int leftWidth = strlen(statusLeft);
int rightWidth = strlen(statusRight);
int pad = 0;
if (leftWidth + rightWidth > (int)m_Width)
{
pad = 0;
if (rightWidth > (int)m_Width)
{
leftWidth = 0;
rightWidth = m_Width;
}
else
{
leftWidth = m_Width - rightWidth;
}
}
else
{
pad = m_Width - leftWidth - rightWidth;
}
move(row, 0);
attrset(A_REVERSE | A_BOLD);
scrollok(stdscr, FALSE);
for (int i = 0; i < leftWidth; ++i)
{
addch(statusLeft[i]);
}
for (int i = 0; i < pad; ++i)
{
addch(' ');
}
for (int i = 0; i < rightWidth; ++i)
{
addch(statusRight[i]);
}
scrollok(stdscr, TRUE);
attrset(A_NORMAL);
}
void CUNIXConsole::DrawFullscreen()
{
IS_SHOW_CONSOLE
scrollok(stdscr, FALSE);
int maxy = 1;
for (DynArray<SConDrawCmd>::iterator iter = m_drawCmds.begin(); iter != m_drawCmds.end(); ++iter)
{
maxy = max(maxy, iter->y);
}
int scrolly = min(maxy - 1, m_ScrollUp);
for (DynArray<SConDrawCmd>::iterator iter = m_drawCmds.begin(); iter != m_drawCmds.end(); ++iter)
{
switch (iter->op)
{
case eCDO_PutText:
{
int y = iter->y - scrolly;
if (y < 0 || y > (int)m_Height - 4)
{
break;
}
if (iter->x < 0 || iter->x > (int)m_Width)
{
break;
}
int len = strlen(iter->text);
if (iter->x + len > (int)m_Width)
{
len = m_Width - iter->x;
}
move(y + 1, iter->x);
for (int i = 0; i < len; i++)
{
addch(iter->text[i]);
}
}
}
}
scrollok(stdscr, TRUE);
}
void CUNIXConsole::DrawCmd(bool cursorOnly)
{
IS_SHOW_CONSOLE
unsigned row = m_Height - m_CmdHeight;
unsigned column = 0;
attr_t colorAttr = 0;
const unsigned promptWidth = strlen(UNIXConsole_PROMPT);
const unsigned moreLeftWidth = strlen(UNIXConsole_MORE_LEFT);
const unsigned moreRightWidth = strlen(UNIXConsole_MORE_RIGHT);
assert(IsLocked());
// If the window is too small, then don't draw anything.
if (IsTooSmall()
|| m_CmdHeight == 0
|| m_Width < promptWidth + moreLeftWidth + moreRightWidth)
{
return;
}
if (!m_Prompt.empty())
{
DrawCmdPrompt();
return;
}
if (!cursorOnly)
{
scrollok(stdscr, FALSE);
// Draw the command prompt.
if (m_EnableColor)
{
colorAttr = COLOR_PAIR(UNIXConsole_PROMPT_COLOR);
}
attrset(A_BOLD | colorAttr);
move(row, 0);
for (const char* p = UNIXConsole_PROMPT; *p; ++p)
{
addch(*p);
++column;
}
// Draw the left scroll indicator (if scrolled).
if (m_ScrollPosition > 0)
{
if (m_EnableColor)
{
colorAttr = COLOR_PAIR(UNIXConsole_MORE_COLOR);
}
attrset(A_NORMAL | colorAttr);
for (const char* p = UNIXConsole_MORE_LEFT; *p; ++p)
{
addch(*p);
++column;
}
}
// Draw the input line. We'll draw to the end of the command window
// (leaving the last cell blank) and the overdraw the more indicator (if
// required).
string::const_iterator it = m_InputLine.begin();
string::const_iterator itEnd = m_InputLine.end();
if (m_ScrollPosition > 0)
{
for (int i = m_ScrollPosition + strlen(UNIXConsole_MORE_LEFT);
i > 0;
--i, ++it)
{
;
}
}
attrset(A_NORMAL);
bool lineTruncated = false;
for (; it != itEnd; ++it)
{
char c = *it;
if (row == m_Height - 1 && column == m_Width - 1)
{
lineTruncated = true;
break;
}
if (column == m_Width)
{
row += 1;
column = 0;
assert(row < m_Height);
move(row, column);
}
addch(c);
++column;
}
// Draw the right scroll indicator (if required).
if (lineTruncated)
{
move(m_Height - 1, m_Width - moreRightWidth);
if (m_EnableColor)
{
colorAttr = COLOR_PAIR(UNIXConsole_MORE_COLOR);
}
attrset(A_NORMAL | colorAttr);
for (const char* p = UNIXConsole_MORE_RIGHT; *p; ++p)
{
addch(*p);
++column;
}
}
else
{
attrset(A_NORMAL);
clrtobot();
move(m_Height - 1, m_Width - 1);
addch(' ');
}
scrollok(stdscr, TRUE);
}
// Update the cursor position.
column = m_CursorPosition - m_ScrollPosition + promptWidth;
row = m_Height - m_CmdHeight;
if (column >= m_Width)
{
row += column / m_Width;
column %= m_Width;
}
move(row, column);
refresh();
}
void CUNIXConsole::DrawCmdPrompt()
{
IS_SHOW_CONSOLE
unsigned row = m_Height - m_CmdHeight;
unsigned column = 0;
string::const_iterator it = m_Prompt.begin();
string::const_iterator itEnd = m_Prompt.end();
attrset(A_BOLD);
clrtobot();
scrollok(stdscr, FALSE);
move(row, column);
for (; it != itEnd; ++it)
{
char c = *it;
if (row == m_Height - 1 && column == m_Width - 1)
{
break;
}
if (column == m_Width)
{
row += 1;
column = 0;
move(row, column);
}
addch(c);
++column;
}
scrollok(stdscr, TRUE);
move(row, column);
refresh();
}
char CUNIXConsole::Prompt(const char* promptString, const char* responseChars)
{
char response = 0;
Lock();
while (m_PromptResponse != 0)
{
m_PromptCond.Wait(m_Lock);
}
#ifndef _NDEBUG
// This method is called from __assert_fail, so we better don't put any
// asserts in here...
if (!m_Prompt.empty() || !*promptString || !*responseChars)
{
abort();
}
if (strlen(responseChars) + 1 > sizeof m_PromptResponseChars)
{
abort();
}
#endif
m_Prompt = promptString;
cry_strcpy(m_PromptResponseChars, responseChars);
DrawCmd();
while (m_PromptResponse == 0)
{
m_PromptCond.Wait(m_Lock);
}
response = m_PromptResponse;
m_PromptResponse = 0;
m_Prompt.clear();
m_PromptResponseChars[0] = 0;
m_PromptCond.Notify();
DrawCmd();
Unlock();
return response;
}
bool CUNIXConsole::IsInputThread()
{
CrySimpleThread<>* callerThread = CrySimpleThread<>::Self();
return callerThread == m_InputThread;
}
void CUNIXConsole::PrintF(const char* format, ...)
{
char lineBuffer[1024];
va_list ap;
va_start(ap, format);
vsnprintf(lineBuffer, sizeof lineBuffer, format, ap);
lineBuffer[sizeof lineBuffer - 1] = 0;
Print(lineBuffer);
va_end(ap);
}
void CUNIXConsole::Print(const char* line)
{
IS_SHOW_CONSOLE
static string lastLine;
static bool firstCall = true;
const size_t lineLength = strlen(line);
size_t lineOffset = 0;
Lock();
// Check if the last line is a true prefix of the specified text argument.
// It it is a prefix, then the output is added to the last line sent to the
// sink.
if (!firstCall
&& lineLength > lastLine.size()
&& !strncmp(line, lastLine.c_str(), lastLine.size()))
{
// Line continued.
lineOffset = lastLine.size();
ContinueLine(); // Will set the correct color.
}
else
{
NewLine();
SetColor();
}
lastLine = line;
firstCall = false;
for (size_t i = lineOffset; i < lineLength; ++i)
{
char c = line[i];
switch (c)
{
case '\\':
if (i < lineLength - 1 && line[i + 1] == 'n')
{
// Sequence "\\n", treat as "\n".
NewLine();
++i;
continue;
}
break;
case '\n':
NewLine();
continue;
case '\r':
ClearLine();
continue;
case '\t':
// We'll do it like the graphical console, just add 4 spaces and don't
// care about TAB stops.
for (unsigned j = 0; j < 4; ++j)
{
Put(' ');
}
continue;
case '$':
if (i < lineLength - 1)
{
++i;
char colorChar = line[i];
if (isdigit(colorChar))
{
SetColor(colorChar - '0');
continue;
}
if (colorChar == 'o' || colorChar == 'O')
{
// Ignore.
continue;
}
}
break;
default:
break;
}
if (c < 0x20)
{
// Unrecognized control character. Ignore.
continue;
}
Put((unsigned char)c);
}
DrawCmd(true);
// Flush();
Unlock();
}
bool CUNIXConsole::OnError(const char* errorString)
{
if (!m_Initialized)
{
return true;
}
return true;
}
void CUNIXConsole::OnInitProgress(const char* sProgressMsg)
{
if (!m_Initialized)
{
return;
}
Lock();
m_ProgressStatus = sProgressMsg;
DrawStatus();
DrawCmd(true);
Flush();
Unlock();
}
void CUNIXConsole::OnInit(ISystem* pSystem)
{
if (m_RequireDedicatedServer && !gEnv->IsDedicated())
{
return;
}
Lock();
if (!m_Initialized)
{
Init();
}
assert(m_pSystem == NULL);
m_pSystem = pSystem;
assert(m_pConsole == NULL);
m_pConsole = pSystem->GetIConsole();
// Add the output print sink to the system console.
if (m_pConsole != 0)
{
m_pConsole->AddOutputPrintSink(this);
}
// Start the input thread.
m_InputThread = new CUNIXConsoleInputThread(*this);
m_InputThread->Start();
Unlock();
#if defined(NCURSES)
// Setup the signal handler.
struct sigaction action;
memset(&action, 0, sizeof action);
action.sa_handler = CUNIXConsoleSignalHandler::Handler;
sigfillset(&action.sa_mask);
CUNIXConsoleSignalHandler::m_pUNIXConsole = this;
sigaction(SIGWINCH, &action, NULL);
sigset_t mask;
memset(&mask, 0, sizeof mask);
sigemptyset(&mask);
sigaddset(&mask, SIGWINCH);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
#endif
}
void CUNIXConsole::OnShutdown()
{
if (!m_Initialized)
{
return;
}
Lock();
assert(!m_OnShutdownCalled);
m_pConsole->RemoveOutputPrintSink(this);
m_OnShutdownCalled = true;
Unlock();
Cleanup();
}
void CUNIXConsole::OnUpdate()
{
FUNCTION_PROFILER(gEnv->pSystem, PROFILE_SYSTEM);
IS_SHOW_CONSOLE
if (!m_Initialized)
{
return;
}
bool updateStatus = false;
static CTimeValue lastStatusUpdate = 0.f;
if (m_OnShutdownCalled)
{
return;
}
Lock();
if (!m_OnUpdateCalled)
{
m_OnUpdateCalled = true;
assert(m_svMap == NULL);
assert(m_svGameRules == NULL);
m_svMap = m_pConsole->GetCVar("sv_map");
m_svGameRules = m_pConsole->GetCVar("sv_gamerules");
assert(m_pTimer == NULL);
m_pTimer = m_pSystem->GetITimer();
}
if (!m_ProgressStatus.empty())
{
m_ProgressStatus.clear();
updateStatus = true;
}
CTimeValue now = m_pTimer->GetAsyncTime();
if ((now - lastStatusUpdate).GetSeconds() > 0.1f)
{
updateStatus = true;
}
m_LastUpdateTime = now;
if (updateStatus)
{
DrawStatus();
DrawCmd(true);
Flush();
lastStatusUpdate = now;
}
while (!m_CommandQueue.empty())
{
const string& command = m_CommandQueue[0];
Unlock();
if (m_pConsole)
{
m_pConsole->ExecuteString(command.c_str());
// doing the check again in case m_pConsole was nulled while executing the command
if (m_pConsole)
{
m_pConsole->AddCommandToHistory(command.c_str());
}
}
Lock();
m_CommandQueue.pop_front();
}
m_pSystem->GetUpdateStats(m_updStats);
bool fsMode = !m_drawCmds.empty();
if (fsMode || fsMode != m_fsMode)
{
m_fsMode = fsMode;
Repaint();
}
Unlock();
}
void CUNIXConsole::GetMemoryUsage(ICrySizer* pSizer)
{
size_t size = sizeof *this;
Lock();
// We're using string (for various reasons), so our best guess of the
// size is the .size().
size += m_HeaderString.size();
size += m_LineBuffer.size() * sizeof(string);
for (TLineBuffer::const_iterator it = m_LineBuffer.begin(),
itEnd = m_LineBuffer.end();
it != itEnd;
++it)
{
size += it->size();
}
size += m_CommandQueue.size() * sizeof(string);
for (TCommandQueue::const_iterator it = m_CommandQueue.begin(),
itEnd = m_CommandQueue.end();
it != itEnd;
++it)
{
size += it->size();
}
size += m_CommandHistory.size() * sizeof(string);
for (TCommandHistory::const_iterator it = m_CommandHistory.begin(),
itEnd = m_CommandHistory.end();
it != itEnd;
++it)
{
size += it->size();
}
if (m_InputThread != NULL)
{
size += sizeof *m_InputThread;
}
size += m_InputLine.size();
size += m_SavedInputLine.size();
size += m_ProgressStatus.size();
Unlock();
pSizer->AddObject(this, size);
}
Vec2_tpl<int> CUNIXConsole::BeginDraw()
{
m_newCmds.resize(0);
return Vec2_tpl<int>(80, 25 - 3);
}
void CUNIXConsole::PutText(int x, int y, const char* msg)
{
IS_SHOW_CONSOLE
SConDrawCmd cmd;
cmd.op = eCDO_PutText;
cmd.x = x;
cmd.y = y;
cry_strcpy(cmd.text, msg);
m_newCmds.push_back(cmd);
}
void CUNIXConsole::EndDraw()
{
IS_SHOW_CONSOLE
Lock();
m_drawCmds.swap(m_newCmds);
Unlock();
}
void CUNIXConsoleInputThread::Run()
{
#if !defined(WIN32)
fd_set rdfds;
#endif
bool interrupted = false;
// The input thread selects stdin (0) and the interrupt pipe. The select()
// call has a timeout (typically 0.5 sec) and will call the InputIdle()
// method whenever the timer expires.
while (true)
{
interrupted = false;
#if !defined(WIN32)
FD_ZERO(&rdfds);
FD_SET(m_IntrPipe[0], &rdfds);
FD_SET(0, &rdfds);
timeval tv;
memset(&tv, 0, sizeof tv);
tv.tv_sec = 0;
tv.tv_usec = 100000; // 0.1 sec.
if (select(m_IntrPipe[0] + 1, &rdfds, NULL, NULL, &tv) != -1)
{
if (FD_ISSET(m_IntrPipe[0], &rdfds))
{
char buf;
read(m_IntrPipe[0], &buf, 1);
interrupted = true;
}
else if (!FD_ISSET(0, &rdfds))
{
// Both m_IntrPipe[0] and 0 are not ready, so this must be a timeout.
m_UNIXConsole.InputIdle();
continue;
}
}
else
{
// Got interrupted by a signal.
assert(errno == EINTR);
interrupted = true;
}
#else
HANDLE handles[2];
handles[0] = m_IntrEvent;
handles[1] = GetStdHandle(STD_INPUT_HANDLE);
DWORD result = WaitForMultipleObjects(2, handles, false, 10 /* 0.1 sec */);
switch (result)
{
case WAIT_OBJECT_0:
interrupted = true;
ResetEvent(m_IntrEvent);
break;
case WAIT_OBJECT_0 + 1:
break;
case WAIT_TIMEOUT:
m_UNIXConsole.InputIdle();
continue;
case WAIT_FAILED:
assert(!"WaitForMultipleObjects() failed");
}
#endif
if (interrupted)
{
if (m_Cancelled)
{
break;
}
m_UNIXConsole.Lock();
m_UNIXConsole.CheckResize();
m_UNIXConsole.Unlock();
continue;
}
int c = getch();
m_UNIXConsole.Lock();
// Handle prompt responses.
if (!m_UNIXConsole.m_Prompt.empty())
{
bool acceptAll = false;
char response = 0;
if (strchr(m_UNIXConsole.m_PromptResponseChars, '@'))
{
acceptAll = true;
}
if (c == KEY_ENTER || c == '\r')
{
c = '\n';
}
if (c == KEY_BACKSPACE || c == 0x7f)
{
c = '\010';
}
if (c <= 0xff && strchr(m_UNIXConsole.m_PromptResponseChars, (char)c))
{
response = (char)c;
}
else if (acceptAll && (isprint(c) || c == '\n'))
{
response = (char)c;
}
else
{
beep();
m_UNIXConsole.Unlock();
continue;
}
m_UNIXConsole.m_PromptResponse = response;
m_UNIXConsole.m_PromptCond.Notify();
m_UNIXConsole.Unlock();
continue;
}
// if console is hided then pass only F10 key
if (!m_UNIXConsole.m_bShowConsole)
{
if (KEY_F(10) == c)
{
m_UNIXConsole.KeyF(10);
}
m_UNIXConsole.Unlock();
continue;
}
switch (c)
{
case ERR:
break;
case KEY_RESIZE:
// Window size changed. This key is received only if the ncurses
// library is configured to handle SIGWINCH and if no other SIGWINCH
// handler has been installed.
m_UNIXConsole.SetSize(COLS, LINES);
m_UNIXConsole.Repaint();
break;
case KEY_ENTER:
case PADENTER:
case '\r':
case '\n':
m_UNIXConsole.KeyEnter();
break;
case KEY_UP:
case '\020': // CTRL-P
m_UNIXConsole.KeyUp();
break;
case KEY_DOWN:
case '\016': // CTRL-N
m_UNIXConsole.KeyDown();
break;
case KEY_LEFT:
m_UNIXConsole.KeyLeft();
break;
case KEY_RIGHT:
m_UNIXConsole.KeyRight();
break;
case KEY_HOME:
case '\001': // CTRL-A
m_UNIXConsole.KeyHome(false);
break;
case CTL_HOME:
m_UNIXConsole.KeyHome(true);
break;
case KEY_END:
case '\005': // CTRL-E
m_UNIXConsole.KeyEnd(false);
break;
case CTL_END:
m_UNIXConsole.KeyEnd(true);
break;
case KEY_BACKSPACE:
#if defined(MAC)
// Mac OS X returns delete key instead of backspace
case 0x7f:
#else
case '\010': // CTRL-H
#endif
m_UNIXConsole.KeyBackspace();
break;
case KEY_DC:
case KEY_SDC:
case '\004': // CTRL-D
m_UNIXConsole.KeyDelete();
break;
case '\027': // CTRL-W
m_UNIXConsole.KeyDeleteWord();
break;
case '\013': // CTRL-K
m_UNIXConsole.KeyKill();
break;
case '\014': // CTRL-L
m_UNIXConsole.KeyRepaint();
break;
case '\t': // TAB
m_UNIXConsole.KeyTab();
break;
case KEY_NPAGE:
case '\006': // CTRL-F
m_UNIXConsole.KeyPgDown(false);
break;
case CTL_PGDN:
m_UNIXConsole.KeyPgDown(true);
break;
case KEY_PPAGE:
case '\002': // CTRL-B
m_UNIXConsole.KeyPgUp(false);
break;
case CTL_PGUP:
m_UNIXConsole.KeyPgUp(true);
break;
case KEY_F(10):
m_UNIXConsole.KeyF(10);
break;
case KEY_F(11):
m_UNIXConsole.KeyF(11);
break;
default:
if (c >= 0x20 && c <= 0xff)
{
m_UNIXConsole.Key(c);
}
break;
}
m_UNIXConsole.Unlock();
}
}
void CUNIXConsole::KeyF(int id)
{
#ifdef LINUX
if (11 == id)
{
def_prog_mode();
endwin();
m_bShowConsole = false;
system("/bin/bash");
reset_prog_mode();
refresh();
m_bShowConsole = true;
}
else if (10 == id)
{
if (m_bShowConsole)
{
def_prog_mode();
endwin();
m_bShowConsole = false;
}
else
{
reset_prog_mode();
refresh();
m_bShowConsole = true;
}
}
#endif
}
CUNIXConsole* CUNIXConsoleSignalHandler::m_pUNIXConsole = NULL;
void CUNIXConsoleSignalHandler::Handler(int signum)
{
#if defined(NCURSES)
switch (signum)
{
case SIGWINCH:
m_pUNIXConsole->m_WindowResized = true;
m_pUNIXConsole->m_InputThread->Interrupt();
break;
default:
break;
}
#endif
}
#endif // USE_UNIXCONSOLE
///////////////////////////////////////////////////////////////////////////////////////
//
// simple light-weight console implementation
//
///////////////////////////////////////////////////////////////////////////////////////
#if defined(AZ_RESTRICTED_PLATFORM)
#include AZ_RESTRICTED_FILE(UnixConsole_cpp)
#endif
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
#else
CNULLConsole::CNULLConsole(bool isDaemonMode)
: m_isDaemon(isDaemonMode)
{
}
void CNULLConsole::Print(const char* inszText)
{
if (m_isDaemon)
{
return;
}
#if defined(WIN32) || defined(WIN64)
DWORD written;
char buf[1024];
sprintf_s(buf, "%s\n", inszText);
WriteConsole(m_hOut, buf, strlen(buf), &written, NULL);
#elif defined(LINUX) || defined(MAC)
printf("%s\n", inszText);
#endif
}
void CNULLConsole::OnInit(ISystem* pSystem)
{
m_syslogStats.Init();
if (m_isDaemon)
{
return;
}
IConsole* pConsole = pSystem->GetIConsole();
pConsole->AddOutputPrintSink(this);
#if defined(WIN32) || defined(WIN64)
AllocConsole();
m_hOut = GetStdHandle(STD_OUTPUT_HANDLE);
#endif
}
void CNULLConsole::OnUpdate()
{
}
void CNULLConsole::PutText([[maybe_unused]] int x, [[maybe_unused]] int y, [[maybe_unused]] const char* msg)
{
}
#endif
///////////////////////////////////////////////////////////////////////////////////////
//
// Logging server internal statistics into syslog service
//
///////////////////////////////////////////////////////////////////////////////////////
CSyslogStats::CSyslogStats()
: m_syslog_stats(0)
, m_syslog_period(SYSLOG_DEFAULT_PERIOD)
{
}
CSyslogStats::~CSyslogStats()
{
#if (defined(LINUX) && !defined(ANDROID)) || defined(MAC)
#if defined(NCURSES)
closelog();
#endif
if (gEnv->pConsole)
{
gEnv->pConsole->UnregisterVariable("syslog_stats");
gEnv->pConsole->UnregisterVariable("syslog_period");
}
#endif
}
void CSyslogStats::Init()
{
#if (defined(LINUX) && !defined(ANDROID)) || defined(MAC)
#if defined(NCURSES)
# if defined(LINUX)
openlog("LinuxLauncher", LOG_PID, LOG_USER);
# elif defined(MAC)
openlog("MacLauncher", LOG_PID, LOG_USER);
# endif
#endif // NCURSES
if (gEnv->pConsole)
{
REGISTER_CVAR2("syslog_stats", &m_syslog_stats, 0, 0, "Start/Stop logging server info into syslog");
REGISTER_CVAR2("syslog_period", &m_syslog_period, SYSLOG_DEFAULT_PERIOD, 0, "Syslog logging timeout period");
}
#endif
}
void CSyslogStats::Update([[maybe_unused]] float srvRate, [[maybe_unused]] int numPlayers)
{
}
#endif // defined(USE_DEDICATED_SERVER_CONSOLE)
// vim:ts=2