diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /dedicated/console | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'dedicated/console')
| -rw-r--r-- | dedicated/console/TextConsoleUnix.cpp | 419 | ||||
| -rw-r--r-- | dedicated/console/TextConsoleUnix.h | 44 | ||||
| -rw-r--r-- | dedicated/console/TextConsoleWin32.cpp | 644 | ||||
| -rw-r--r-- | dedicated/console/TextConsoleWin32.h | 82 | ||||
| -rw-r--r-- | dedicated/console/conproc.cpp | 428 | ||||
| -rw-r--r-- | dedicated/console/conproc.h | 30 | ||||
| -rw-r--r-- | dedicated/console/textconsole.cpp | 22 | ||||
| -rw-r--r-- | dedicated/console/textconsole.h | 38 |
8 files changed, 1707 insertions, 0 deletions
diff --git a/dedicated/console/TextConsoleUnix.cpp b/dedicated/console/TextConsoleUnix.cpp new file mode 100644 index 0000000..4f71845 --- /dev/null +++ b/dedicated/console/TextConsoleUnix.cpp @@ -0,0 +1,419 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// CTextConsoleUnix.cpp: Unix implementation of the TextConsole class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef _WIN32 + +#include <sys/ioctl.h> + +#include "TextConsoleUnix.h" +#include "tier0/icommandline.h" +#include "tier1/utllinkedlist.h" +#include "filesystem.h" +#include "../thirdparty/libedit-3.1/src/histedit.h" +#include "tier0/vprof.h" + +#define CONSOLE_LOG_FILE "console.log" + +static pthread_mutex_t g_lock; +static pthread_t g_threadid = (pthread_t)-1; +CUtlLinkedList< CUtlString > g_Commands; +static volatile int g_ProcessingCommands = false; + +#if defined( LINUX ) + +// Dynamically load the libtinfo stuff. On servers without a tty attached, +// we get to skip all of these dependencies on servers without ttys. +#define TINFO_SYM(rc, fn, params, args, ret) \ + typedef rc (*DYNTINFOFN_##fn) params; \ + static DYNTINFOFN_##fn g_TINFO_##fn; \ + extern "C" rc fn params \ + { \ + ret g_TINFO_##fn args; \ + } + +TINFO_SYM(char *, tgoto, (char *string, int x, int y), (string, x, y), return); +TINFO_SYM(int, tputs, (const char *str, int affcnt, int (*putc)(int)), (str, affcnt, putc), return); +TINFO_SYM(int, tgetflag, (char *id), (id), return); +TINFO_SYM(int, tgetnum, (char *id), (id), return); +TINFO_SYM(int, tgetent, (char *bufp, const char *name), (bufp, name), return); +TINFO_SYM(char *, tgetstr, (char *id, char **area), (id, area), return); + +#endif // LINUX + +//---------------------------------------------------------------------------------------------------------------------- +// init_tinfo_functions +//---------------------------------------------------------------------------------------------------------------------- +static bool init_tinfo_functions() +{ +#if !defined( LINUX ) + return true; +#else + static void *s_ncurses_handle = NULL; + + if ( !s_ncurses_handle ) + { + // Long time ago, ncurses was two libraries. So if libtinfo fails, try libncurses. + static const char *names[] = { "libtinfo.so.5", "libncurses.so.5" }; + + for ( int i = 0; !s_ncurses_handle && ( i < ARRAYSIZE( names ) ); i++ ) + { + bool bFailed = true; + s_ncurses_handle = dlopen( names[i], RTLD_NOW ); + + if ( s_ncurses_handle ) + { + bFailed = false; +#define LOADTINFOFUNC(_handle, _func, _failed) \ + do { \ + g_TINFO_##_func = ( DYNTINFOFN_##_func )dlsym(_handle, #_func); \ + if ( !g_TINFO_##_func) \ + _failed = true; \ + } while (0) + + LOADTINFOFUNC( s_ncurses_handle, tgoto, bFailed ); + LOADTINFOFUNC( s_ncurses_handle, tputs, bFailed ); + LOADTINFOFUNC( s_ncurses_handle, tgetflag, bFailed ); + LOADTINFOFUNC( s_ncurses_handle, tgetnum, bFailed ); + LOADTINFOFUNC( s_ncurses_handle, tgetent, bFailed ); + LOADTINFOFUNC( s_ncurses_handle, tgetstr, bFailed ); +#undef LOADTINFOFUNC + } + + if ( bFailed ) + s_ncurses_handle = NULL; + } + + if ( !s_ncurses_handle ) + { + fprintf( stderr, "\nWARNING: Failed to load 32-bit libtinfo.so.5 or libncurses.so.5.\n" + " Please install (lib32tinfo5 / ncurses-libs.i686 / equivalent) to enable readline.\n\n"); + } + } + + return !!s_ncurses_handle; +#endif // LINUX +} + +static unsigned char editline_complete( EditLine *el, int ch __attribute__((__unused__)) ) +{ + static const char *s_cmds[] = + { + "cvarlist ", + "find ", + "help ", + "maps ", + "nextlevel", + "quit", + "status", + "sv_cheats ", + "tf_bot_quota ", + "toggle ", + "sv_dump_edicts", +#ifdef STAGING_ONLY + "tf_bot_use_items ", +#endif + }; + + const LineInfo *lf = el_line(el); + const char *cmd = lf->buffer; + size_t len = lf->cursor - cmd; + + if ( len > 0 ) + { + for (int i = 0; i < ARRAYSIZE(s_cmds); i++) + { + if ( len > strlen( s_cmds[i] ) ) + continue; + + if ( !Q_strncmp( cmd, s_cmds[i], len ) ) + { + if ( el_insertstr( el, s_cmds[i] + len ) == -1 ) + return CC_ERROR; + else + return CC_REFRESH; + } + } + } + + return CC_ERROR; +} + +static const char *editline_prompt( EditLine *e ) +{ + // Something like: "\1\033[7m\1Srcds$\1\033[0m\1 " + static const char *szPrompt = getenv( "SRCDS_PROMPT" ); + return szPrompt ? szPrompt : ""; +} + +static void editline_cleanup_handler( void *arg ) +{ + if ( arg ) + { + EditLine *el = (EditLine *)arg; + el_end( el ); + } +} + +static bool add_command( const char *cmd, int cmd_len ) +{ + if ( cmd ) + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + // Trim trailing whitespace. + while ( ( cmd_len > 0 ) && isspace( cmd[ cmd_len - 1 ] ) ) + cmd_len--; + + if ( cmd_len > 0 ) + { + pthread_mutex_lock( &g_lock ); + if ( g_Commands.Count() < 32 ) + { + CUtlString szCommand( cmd, cmd_len ); + g_Commands.AddToTail( szCommand ); + + g_ProcessingCommands = true; + } + pthread_mutex_unlock( &g_lock ); + + // Wait a bit until we've processed the command we added. + for ( int i = 0; i < 6; i++ ) + { + while ( g_ProcessingCommands ) + usleep( 500 ); + } + + return true; + } + } + + return false; +} + +static void *editline_threadproc( void *arg ) +{ + HistEvent ev; + EditLine *el; + History *myhistory; + FILE *tty = (FILE *)arg; + + ThreadSetDebugName( "libedit" ); + + // Set up state + el = el_init( "srcds_linux", stdin, tty, stderr ); + el_set( el, EL_PROMPT, &editline_prompt ); + el_set( el, EL_EDITOR, "emacs" ); // or "vi" + + // Hitting Ctrl+R will reset prompt. + el_set( el, EL_BIND, "^R", "ed-redisplay", NULL ); + + /* Add a user-defined function */ + el_set( el, EL_ADDFN, "ed-complete", "Complete argument", editline_complete ); + /* Bind tab to it */ + el_set( el, EL_BIND, "^I", "ed-complete", NULL ); + + // Init history. + myhistory = history_init(); + if (myhistory == 0) + { + fprintf( stderr, "history could not be initialized\n" ); + g_threadid = (pthread_t)-1; + return (void *)-1; + } + + // History size. + history( myhistory, &ev, H_SETSIZE, 800 ); + + // History callback. + el_set( el, EL_HIST, history, myhistory ); + + // Source user's defaults. + el_source( el, NULL ); + + pthread_cleanup_push( editline_cleanup_handler, el ); + + while ( g_threadid != (pthread_t)-1 ) + { + // count is the number of characters read. + // line is a const char* of our command line with the tailing \n + int count; + const char *line = el_gets( el, &count ); + + if ( add_command( line, count ) ) + { + // Add command to history. + history( myhistory, &ev, H_ENTER, line ); + } + } + + pthread_cleanup_pop( 0 ); + + // Clean up... + history_end( myhistory ); + el_end( el ); + return NULL; +} + +static void *fgets_threadproc( void *arg ) +{ + pthread_cleanup_push( editline_cleanup_handler, NULL ); + + while ( g_threadid != (pthread_t)-1 ) + { + char cmd[ 512 ]; + + if ( fgets( cmd, sizeof( cmd ), stdin ) ) + { + cmd[ sizeof(cmd) - 1 ] = 0; + add_command( cmd, strlen( cmd ) ); + } + } + + pthread_cleanup_pop( 0 ); + return NULL; +} + +bool CTextConsoleUnix::Init() +{ + if( g_threadid != (pthread_t)-1 ) + { + Assert( !"CTextConsoleUnix can only handle a single thread!" ); + return false; + } + + pthread_mutex_init( &g_lock, NULL ); + + // This code is for echo-ing key presses to the connected tty + // (which is != STDOUT) + if ( isatty( STDIN_FILENO ) ) + { + const char *termid_str = ctermid( NULL ); + + m_tty = fopen( termid_str, "w+" ); + if ( !m_tty ) + { + fprintf( stderr, "WARNING: Unable to open tty(%s) for output.\n", termid_str ); + m_tty = stdout; + } + + void *(*terminal_threadproc) (void *) = editline_threadproc; + if ( !init_tinfo_functions() ) + terminal_threadproc = fgets_threadproc; + + if ( pthread_create( &g_threadid, NULL, terminal_threadproc, (void *)m_tty ) != 0 ) + { + g_threadid = (pthread_t)-1; + fprintf( stderr, "WARNING: pthread_create failed: %s.\n", strerror(errno) ); + } + } + else + { + m_tty = fopen( "/dev/null", "w+" ); + if ( !m_tty ) + m_tty = stdout; + } + + m_bConDebug = CommandLine()->FindParm( "-condebug" ) != 0; + if ( m_bConDebug && CommandLine()->FindParm( "-conclearlog" ) ) + g_pFullFileSystem->RemoveFile( CONSOLE_LOG_FILE, "GAME" ); + + return CTextConsole::Init(); +} + +void CTextConsoleUnix::ShutDown() +{ + if ( g_threadid != (pthread_t)-1 ) + { + void *status = NULL; + pthread_t tid = g_threadid; + + g_threadid = (pthread_t)-1; + + pthread_cancel( tid ); + pthread_join( tid, &status ); + } + + pthread_mutex_destroy( &g_lock ); +} + +void CTextConsoleUnix::Print( char * pszMsg ) +{ + int nChars = strlen( pszMsg ); + + if ( nChars > 0 ) + { + if ( m_bConDebug ) + { + FileHandle_t fh = g_pFullFileSystem->Open( CONSOLE_LOG_FILE, "a" ); + if ( fh != FILESYSTEM_INVALID_HANDLE ) + { + g_pFullFileSystem->Write( pszMsg, nChars, fh ); + g_pFullFileSystem->Close( fh ); + } + } + + fwrite( pszMsg, 1, nChars, m_tty ); + } +} + +void CTextConsoleUnix::SetTitle( char *pszTitle ) +{ +} + +void CTextConsoleUnix::SetStatusLine( char *pszStatus ) +{ +} + +void CTextConsoleUnix::UpdateStatus() +{ +} + +char *CTextConsoleUnix::GetLine( int index, char *buf, int buflen ) +{ + if ( g_threadid != (pthread_t)-1 ) + { + if ( g_Commands.Count() > 0 ) + { + pthread_mutex_lock( &g_lock ); + + const CUtlString& psCommand = g_Commands[ g_Commands.Head() ]; + V_strncpy( buf, psCommand.Get(), buflen ); + g_Commands.Remove( g_Commands.Head() ); + + pthread_mutex_unlock( &g_lock ); + return buf; + } + else if ( index == 0 ) + { + // We're being asked for the first command. Must be a new frame. + // Reset the processed commands global. + g_ProcessingCommands = false; + } + } + + return NULL; +} + +int CTextConsoleUnix::GetWidth() +{ + int nWidth = 0; + struct winsize ws; + + if ( ioctl( STDOUT_FILENO, TIOCGWINSZ, &ws ) == 0 ) + nWidth = (int)ws.ws_col; + + if ( nWidth <= 1 ) + nWidth = 80; + + return nWidth; +} + +#endif // !_WIN32 diff --git a/dedicated/console/TextConsoleUnix.h b/dedicated/console/TextConsoleUnix.h new file mode 100644 index 0000000..b32fe12 --- /dev/null +++ b/dedicated/console/TextConsoleUnix.h @@ -0,0 +1,44 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// TextConsoleUnix.h: Unix interface for the TextConsole class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined TEXTCONSOLE_UNIX_H +#define TEXTCONSOLE_UNIX_H + +#ifndef _WIN32 + +#include <stdio.h> +#include "textconsole.h" + +class CTextConsoleUnix : public CTextConsole +{ +public: + virtual ~CTextConsoleUnix() { } + + // CTextConsole + bool Init(); + void ShutDown(); + void Print( char * pszMsg ); + + void SetTitle( char *pszTitle ); + void SetStatusLine( char *pszStatus ); + void UpdateStatus(); + + char * GetLine( int index, char *buf, int buflen ); + int GetWidth(); + +private: + bool m_bConDebug; + FILE *m_tty; +}; + +#endif // _ndef WIN32 + +#endif // !defined TEXTCONSOLE_UNIX_H diff --git a/dedicated/console/TextConsoleWin32.cpp b/dedicated/console/TextConsoleWin32.cpp new file mode 100644 index 0000000..ece1eff --- /dev/null +++ b/dedicated/console/TextConsoleWin32.cpp @@ -0,0 +1,644 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// CTextConsoleWin32.cpp: Win32 implementation of the TextConsole class. +// +////////////////////////////////////////////////////////////////////// + +#include "TextConsoleWin32.h" +#include "tier0/dbg.h" +#include "utlvector.h" + +// Could possibly switch all this code over to using readline. This: +// http://mingweditline.sourceforge.net/?Description +// readline() / add_history(char *) + +#ifdef _WIN32 + +BOOL WINAPI ConsoleHandlerRoutine( DWORD CtrlType ) +{ + NOTE_UNUSED( CtrlType ); + /* TODO ? + if ( CtrlType != CTRL_C_EVENT && CtrlType != CTRL_BREAK_EVENT ) + m_System->Stop(); // don't quit on break or ctrl+c + */ + + return TRUE; +} + +// GetConsoleHwnd() helper function from MSDN Knowledge Base Article Q124103 +// needed, because HWND GetConsoleWindow(VOID) is not avaliable under Win95/98/ME + +HWND GetConsoleHwnd(void) +{ + typedef HWND (WINAPI *PFNGETCONSOLEWINDOW)( VOID ); + static PFNGETCONSOLEWINDOW s_pfnGetConsoleWindow = (PFNGETCONSOLEWINDOW) GetProcAddress( GetModuleHandle("kernel32"), "GetConsoleWindow" ); + if ( s_pfnGetConsoleWindow ) + return s_pfnGetConsoleWindow(); + + HWND hwndFound; // This is what is returned to the caller. + char pszNewWindowTitle[1024]; // Contains fabricated WindowTitle + char pszOldWindowTitle[1024]; // Contains original WindowTitle + + // Fetch current window title. + GetConsoleTitle( pszOldWindowTitle, 1024 ); + + // Format a "unique" NewWindowTitle. + wsprintf( pszNewWindowTitle,"%d/%d", GetTickCount(), GetCurrentProcessId() ); + + // Change current window title. + SetConsoleTitle(pszNewWindowTitle); + + // Ensure window title has been updated. + Sleep(40); + + // Look for NewWindowTitle. + hwndFound = FindWindow( NULL, pszNewWindowTitle ); + + // Restore original window title. + + SetConsoleTitle( pszOldWindowTitle ); + + return hwndFound; +} + +CTextConsoleWin32::CTextConsoleWin32() +{ + hinput = NULL; + houtput = NULL; + Attrib = 0; + statusline[0] = '\0'; +} + +bool CTextConsoleWin32::Init() +{ + (void) AllocConsole(); + SetTitle( "SOURCE DEDICATED SERVER" ); + + hinput = GetStdHandle ( STD_INPUT_HANDLE ); + houtput = GetStdHandle ( STD_OUTPUT_HANDLE ); + + if ( !SetConsoleCtrlHandler( &ConsoleHandlerRoutine, TRUE) ) + { + Print( "WARNING! TextConsole::Init: Could not attach console hook.\n" ); + } + + Attrib = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY ; + + SetWindowPos( GetConsoleHwnd(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW ); + + memset( m_szConsoleText, 0, sizeof( m_szConsoleText ) ); + m_nConsoleTextLen = 0; + m_nCursorPosition = 0; + + memset( m_szSavedConsoleText, 0, sizeof( m_szSavedConsoleText ) ); + m_nSavedConsoleTextLen = 0; + + memset( m_aszLineBuffer, 0, sizeof( m_aszLineBuffer ) ); + m_nTotalLines = 0; + m_nInputLine = 0; + m_nBrowseLine = 0; + + // these are log messages, not related to console + Msg( "\n" ); + Msg( "Console initialized.\n" ); + + return CTextConsole::Init(); +} + +void CTextConsoleWin32::ShutDown( void ) +{ + FreeConsole(); +} + +void CTextConsoleWin32::SetVisible( bool visible ) +{ + ShowWindow ( GetConsoleHwnd(), visible ? SW_SHOW : SW_HIDE ); + m_ConsoleVisible = visible; +} + +char * CTextConsoleWin32::GetLine( int index, char *buf, int buflen ) +{ + while ( 1 ) + { + INPUT_RECORD recs[ 1024 ]; + unsigned long numread; + unsigned long numevents; + + if ( !GetNumberOfConsoleInputEvents( hinput, &numevents ) ) + { + Error("CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents"); + return NULL; + } + + if ( numevents <= 0 ) + break; + + if ( !ReadConsoleInput( hinput, recs, ARRAYSIZE( recs ), &numread ) ) + { + Error("CTextConsoleWin32::GetLine: !ReadConsoleInput"); + return NULL; + } + + if ( numread == 0 ) + return NULL; + + for ( int i=0; i < (int)numread; i++ ) + { + INPUT_RECORD *pRec = &recs[i]; + if ( pRec->EventType != KEY_EVENT ) + continue; + + if ( pRec->Event.KeyEvent.bKeyDown ) + { + // check for cursor keys + if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_UP ) + { + ReceiveUpArrow(); + } + else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_DOWN ) + { + ReceiveDownArrow(); + } + else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_LEFT ) + { + ReceiveLeftArrow(); + } + else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_RIGHT ) + { + ReceiveRightArrow(); + } + else + { + char ch; + int nLen; + + ch = pRec->Event.KeyEvent.uChar.AsciiChar; + switch ( ch ) + { + case '\r': // Enter + nLen = ReceiveNewline(); + if ( nLen ) + { + strncpy( buf, m_szConsoleText, buflen ); + buf[ buflen - 1 ] = 0; + return buf; + } + break; + + case '\b': // Backspace + ReceiveBackspace(); + break; + + case '\t': // TAB + ReceiveTab(); + break; + + default: + if ( ( ch >= ' ') && ( ch <= '~' ) ) // dont' accept nonprintable chars + { + ReceiveStandardChar( ch ); + } + break; + } + } + } + } + } + + return NULL; +} + +void CTextConsoleWin32::Print( char * pszMsg ) +{ + if ( m_nConsoleTextLen ) + { + int nLen; + + nLen = m_nConsoleTextLen; + + while ( nLen-- ) + { + PrintRaw( "\b \b" ); + } + } + + PrintRaw( pszMsg ); + + if ( m_nConsoleTextLen ) + { + PrintRaw( m_szConsoleText, m_nConsoleTextLen ); + } + + UpdateStatus(); +} + +void CTextConsoleWin32::PrintRaw( const char * pszMsg, int nChars ) +{ + unsigned long dummy; + + if ( houtput == NULL ) + { + houtput = GetStdHandle ( STD_OUTPUT_HANDLE ); + if ( houtput == NULL ) + return; + } + + if ( nChars <= 0 ) + { + nChars = strlen( pszMsg ); + if ( nChars <= 0 ) + return; + } + + // filter out ASCII BEL characters because windows actually plays a + // bell sound, which can be used to lag the server in a DOS attack. + char * pTempBuf = NULL; + for ( int i = 0; i < nChars; ++i ) + { + if ( pszMsg[i] == 0x07 /*BEL*/ ) + { + if ( !pTempBuf ) + { + pTempBuf = ( char * ) malloc( nChars ); + memcpy( pTempBuf, pszMsg, nChars ); + } + pTempBuf[i] = '.'; + } + } + + WriteFile( houtput, pTempBuf ? pTempBuf : pszMsg, nChars, &dummy, NULL ); + + free( pTempBuf ); // usually NULL +} + +int CTextConsoleWin32::GetWidth( void ) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + int nWidth; + + nWidth = 0; + + if ( GetConsoleScreenBufferInfo( houtput, &csbi ) ) + { + nWidth = csbi.dwSize.X; + } + + if ( nWidth <= 1 ) + nWidth = 80; + + return nWidth; +} + +void CTextConsoleWin32::SetStatusLine( char * pszStatus ) +{ + strncpy( statusline, pszStatus, 80 ); + statusline[ 79 ] = '\0'; + UpdateStatus(); +} + +void CTextConsoleWin32::UpdateStatus( void ) +{ + COORD coord; + DWORD dwWritten = 0; + WORD wAttrib[ 80 ]; + + for ( int i = 0; i < 80; i++ ) + { + wAttrib[i] = Attrib; //FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY ; + } + + coord.X = coord.Y = 0; + + WriteConsoleOutputAttribute( houtput, wAttrib, 80, coord, &dwWritten ); + WriteConsoleOutputCharacter( houtput, statusline, 80, coord, &dwWritten ); +} + + +void CTextConsoleWin32::SetTitle( char * pszTitle ) +{ + SetConsoleTitle( pszTitle ); +} + +void CTextConsoleWin32::SetColor(WORD attrib) +{ + Attrib = attrib; +} + +int CTextConsoleWin32::ReceiveNewline( void ) +{ + int nLen = 0; + + PrintRaw( "\n" ); + + if ( m_nConsoleTextLen ) + { + nLen = m_nConsoleTextLen; + + m_szConsoleText[ m_nConsoleTextLen ] = 0; + m_nConsoleTextLen = 0; + m_nCursorPosition = 0; + + // cache line in buffer, but only if it's not a duplicate of the previous line + if ( ( m_nInputLine == 0 ) || ( strcmp( m_aszLineBuffer[ m_nInputLine - 1 ], m_szConsoleText ) ) ) + { + strncpy( m_aszLineBuffer[ m_nInputLine ], m_szConsoleText, MAX_CONSOLE_TEXTLEN ); + + m_nInputLine++; + + if ( m_nInputLine > m_nTotalLines ) + m_nTotalLines = m_nInputLine; + + if ( m_nInputLine >= MAX_BUFFER_LINES ) + m_nInputLine = 0; + + } + + m_nBrowseLine = m_nInputLine; + } + + return nLen; +} + + +void CTextConsoleWin32::ReceiveBackspace( void ) +{ + int nCount; + + if ( m_nCursorPosition == 0 ) + { + return; + } + + m_nConsoleTextLen--; + m_nCursorPosition--; + + PrintRaw( "\b" ); + + for ( nCount = m_nCursorPosition; nCount < m_nConsoleTextLen; nCount++ ) + { + m_szConsoleText[ nCount ] = m_szConsoleText[ nCount + 1 ]; + PrintRaw( m_szConsoleText + nCount, 1 ); + } + + PrintRaw( " " ); + + nCount = m_nConsoleTextLen; + while ( nCount >= m_nCursorPosition ) + { + PrintRaw( "\b" ); + nCount--; + } + + m_nBrowseLine = m_nInputLine; +} + + +void CTextConsoleWin32::ReceiveTab( void ) +{ + CUtlVector<char *> matches; + + m_szConsoleText[ m_nConsoleTextLen ] = 0; + + if ( matches.Count() == 0 ) + { + return; + } + + if ( matches.Count() == 1 ) + { + char * pszCmdName; + char * pszRest; + + pszCmdName = matches[0]; + pszRest = pszCmdName + strlen( m_szConsoleText ); + + if ( pszRest ) + { + PrintRaw( pszRest ); + strcat( m_szConsoleText, pszRest ); + m_nConsoleTextLen += strlen( pszRest ); + + PrintRaw( " " ); + strcat( m_szConsoleText, " " ); + m_nConsoleTextLen++; + } + } + else + { + int nLongestCmd; + int nTotalColumns; + int nCurrentColumn; + char * pszCurrentCmd; + int i = 0; + + nLongestCmd = 0; + + pszCurrentCmd = matches[0]; + while ( pszCurrentCmd ) + { + if ( (int)strlen( pszCurrentCmd) > nLongestCmd ) + { + nLongestCmd = strlen( pszCurrentCmd); + } + + i++; + pszCurrentCmd = (char *)matches[i]; + } + + nTotalColumns = ( GetWidth() - 1 ) / ( nLongestCmd + 1 ); + nCurrentColumn = 0; + + PrintRaw( "\n" ); + + // Would be nice if these were sorted, but not that big a deal + pszCurrentCmd = matches[0]; + i = 0; + while ( pszCurrentCmd ) + { + char szFormatCmd[ 256 ]; + + nCurrentColumn++; + + if ( nCurrentColumn > nTotalColumns ) + { + PrintRaw( "\n" ); + nCurrentColumn = 1; + } + + Q_snprintf( szFormatCmd, sizeof(szFormatCmd), "%-*s ", nLongestCmd, pszCurrentCmd ); + PrintRaw( szFormatCmd ); + + i++; + pszCurrentCmd = matches[i]; + } + + PrintRaw( "\n" ); + PrintRaw( m_szConsoleText ); + // TODO: Tack on 'common' chars in all the matches, i.e. if I typed 'con' and all the + // matches begin with 'connect_' then print the matches but also complete the + // command up to that point at least. + } + + m_nCursorPosition = m_nConsoleTextLen; + m_nBrowseLine = m_nInputLine; +} + + + +void CTextConsoleWin32::ReceiveStandardChar( const char ch ) +{ + int nCount; + + // If the line buffer is maxed out, ignore this char + if ( m_nConsoleTextLen >= ( sizeof( m_szConsoleText ) - 2 ) ) + { + return; + } + + nCount = m_nConsoleTextLen; + while ( nCount > m_nCursorPosition ) + { + m_szConsoleText[ nCount ] = m_szConsoleText[ nCount - 1 ]; + nCount--; + } + + m_szConsoleText[ m_nCursorPosition ] = ch; + + PrintRaw( m_szConsoleText + m_nCursorPosition, m_nConsoleTextLen - m_nCursorPosition + 1 ); + + m_nConsoleTextLen++; + m_nCursorPosition++; + + nCount = m_nConsoleTextLen; + while ( nCount > m_nCursorPosition ) + { + PrintRaw( "\b" ); + nCount--; + } + + m_nBrowseLine = m_nInputLine; +} + + +void CTextConsoleWin32::ReceiveUpArrow( void ) +{ + int nLastCommandInHistory; + + nLastCommandInHistory = m_nInputLine + 1; + if ( nLastCommandInHistory > m_nTotalLines ) + { + nLastCommandInHistory = 0; + } + + if ( m_nBrowseLine == nLastCommandInHistory ) + { + return; + } + + if ( m_nBrowseLine == m_nInputLine ) + { + if ( m_nConsoleTextLen > 0 ) + { + // Save off current text + strncpy( m_szSavedConsoleText, m_szConsoleText, m_nConsoleTextLen ); + // No terminator, it's a raw buffer we always know the length of + } + + m_nSavedConsoleTextLen = m_nConsoleTextLen; + } + + m_nBrowseLine--; + if ( m_nBrowseLine < 0 ) + { + m_nBrowseLine = m_nTotalLines - 1; + } + + while ( m_nConsoleTextLen-- ) // delete old line + { + PrintRaw( "\b \b" ); + } + + // copy buffered line + PrintRaw( m_aszLineBuffer[ m_nBrowseLine ] ); + + strncpy( m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN ); + m_nConsoleTextLen = strlen( m_aszLineBuffer[ m_nBrowseLine ] ); + + m_nCursorPosition = m_nConsoleTextLen; +} + + +void CTextConsoleWin32::ReceiveDownArrow( void ) +{ + if ( m_nBrowseLine == m_nInputLine ) + { + return; + } + + m_nBrowseLine++; + if ( m_nBrowseLine > m_nTotalLines ) + { + m_nBrowseLine = 0; + } + + while ( m_nConsoleTextLen-- ) // delete old line + { + PrintRaw( "\b \b" ); + } + + if ( m_nBrowseLine == m_nInputLine ) + { + if ( m_nSavedConsoleTextLen > 0 ) + { + // Restore current text + strncpy( m_szConsoleText, m_szSavedConsoleText, m_nSavedConsoleTextLen ); + // No terminator, it's a raw buffer we always know the length of + + PrintRaw( m_szConsoleText, m_nSavedConsoleTextLen ); + } + + m_nConsoleTextLen = m_nSavedConsoleTextLen; + } + else + { + // copy buffered line + PrintRaw( m_aszLineBuffer[ m_nBrowseLine ] ); + + strncpy( m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN ); + + m_nConsoleTextLen = strlen( m_aszLineBuffer[ m_nBrowseLine ] ); + } + + m_nCursorPosition = m_nConsoleTextLen; +} + + +void CTextConsoleWin32::ReceiveLeftArrow( void ) +{ + if ( m_nCursorPosition == 0 ) + { + return; + } + + PrintRaw( "\b" ); + m_nCursorPosition--; +} + + +void CTextConsoleWin32::ReceiveRightArrow( void ) +{ + if ( m_nCursorPosition == m_nConsoleTextLen ) + { + return; + } + + PrintRaw( m_szConsoleText + m_nCursorPosition, 1 ); + m_nCursorPosition++; +} + +#endif // _WIN32 diff --git a/dedicated/console/TextConsoleWin32.h b/dedicated/console/TextConsoleWin32.h new file mode 100644 index 0000000..4c762ec --- /dev/null +++ b/dedicated/console/TextConsoleWin32.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// TextConsoleWin32.h: Win32 interface for the TextConsole class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined TEXTCONSOLE_WIN32_H +#define TEXTCONSOLE_WIN32_H +#pragma once + +#ifdef _WIN32 + +#include <windows.h> +#include "TextConsole.h" + +#define MAX_CONSOLE_TEXTLEN 256 +#define MAX_BUFFER_LINES 30 + +class CTextConsoleWin32 : public CTextConsole +{ +public: + CTextConsoleWin32(); + virtual ~CTextConsoleWin32() { }; + + // CTextConsole + bool Init(); + void ShutDown( void ); + void Print( char *pszMsg ); + + void SetTitle( char * pszTitle ); + void SetStatusLine( char * pszStatus ); + void UpdateStatus( void ); + + char * GetLine( int index, char *buf, int buflen ); + int GetWidth( void ); + + void SetVisible( bool visible ); + +protected: + // CTextConsoleWin32 + void SetColor( WORD ); + void PrintRaw( const char * pszMsg, int nChars = -1 ); + +private: + char m_szConsoleText[ MAX_CONSOLE_TEXTLEN ]; // console text buffer + int m_nConsoleTextLen; // console textbuffer length + int m_nCursorPosition; // position in the current input line + + // Saved input data when scrolling back through command history + char m_szSavedConsoleText[ MAX_CONSOLE_TEXTLEN ]; // console text buffer + int m_nSavedConsoleTextLen; // console textbuffer length + + char m_aszLineBuffer[ MAX_BUFFER_LINES ][ MAX_CONSOLE_TEXTLEN ]; // command buffer last MAX_BUFFER_LINES commands + int m_nInputLine; // Current line being entered + int m_nBrowseLine; // current buffer line for up/down arrow + int m_nTotalLines; // # of nonempty lines in the buffer + + int ReceiveNewline( void ); + void ReceiveBackspace( void ); + void ReceiveTab( void ); + void ReceiveStandardChar( const char ch ); + void ReceiveUpArrow( void ); + void ReceiveDownArrow( void ); + void ReceiveLeftArrow( void ); + void ReceiveRightArrow( void ); + +private: + HANDLE hinput; // standard input handle + HANDLE houtput; // standard output handle + WORD Attrib; // attrib colours for status bar + + char statusline[81]; // first line in console is status line +}; + +#endif // _WIN32 + +#endif // !defined TEXTCONSOLE_WIN32_H diff --git a/dedicated/console/conproc.cpp b/dedicated/console/conproc.cpp new file mode 100644 index 0000000..2156450 --- /dev/null +++ b/dedicated/console/conproc.cpp @@ -0,0 +1,428 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifdef _WIN32 +// conproc.c -- support for qhost +#include <stdio.h> +#include <process.h> +#include <windows.h> +#include "conproc.h" +#include "isys.h" +#include "tier0/icommandline.h" +#include "tier1/strtools.h" + +static HANDLE heventDone; +static HANDLE hfileBuffer; +static HANDLE heventChildSend; +static HANDLE heventParentSend; +static HANDLE hStdout; +static HANDLE hStdin; + +/* +============== +SetConsoleCXCY + +============== +*/ +BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + COORD coordMax; + + coordMax = GetLargestConsoleWindowSize(hStdout); + + if (cy > coordMax.Y) + cy = coordMax.Y; + + if (cx > coordMax.X) + cx = coordMax.X; + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return FALSE; + +// height + info.srWindow.Left = 0; + info.srWindow.Right = info.dwSize.X - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = cy - 1; + + if (cy < info.dwSize.Y) + { + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + } + else if (cy > info.dwSize.Y) + { + info.dwSize.Y = cy; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + } + + if (!GetConsoleScreenBufferInfo(hStdout, &info)) + return FALSE; + +// width + info.srWindow.Left = 0; + info.srWindow.Right = cx - 1; + info.srWindow.Top = 0; + info.srWindow.Bottom = info.dwSize.Y - 1; + + if (cx < info.dwSize.X) + { + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + } + else if (cx > info.dwSize.X) + { + info.dwSize.X = cx; + + if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) + return FALSE; + + if (!SetConsoleWindowInfo(hStdout, TRUE, &info.srWindow)) + return FALSE; + } + + return TRUE; +} + +/* +============== +GetMappedBuffer + +============== +*/ +LPVOID GetMappedBuffer (HANDLE hfileBuffer) +{ + LPVOID pBuffer; + + pBuffer = MapViewOfFile (hfileBuffer, + FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); + + return pBuffer; +} + +/* +============== +ReleaseMappedBuffer + +============== +*/ +void ReleaseMappedBuffer (LPVOID pBuffer) +{ + UnmapViewOfFile (pBuffer); +} + +/* +============== +GetScreenBufferLines + +============== +*/ +BOOL GetScreenBufferLines (int *piLines) +{ + CONSOLE_SCREEN_BUFFER_INFO info; + BOOL bRet; + + bRet = GetConsoleScreenBufferInfo (hStdout, &info); + + if (bRet) + *piLines = info.dwSize.Y; + + return bRet; +} + +/* +============== +SetScreenBufferLines + +============== +*/ +BOOL SetScreenBufferLines (int iLines) +{ + return SetConsoleCXCY (hStdout, 80, iLines); +} + +/* +============== +ReadText + +============== +*/ +BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine) +{ + COORD coord; + DWORD dwRead; + BOOL bRet; + + coord.X = 0; + coord.Y = iBeginLine; + + bRet = ReadConsoleOutputCharacter( + hStdout, + pszText, + 80 * (iEndLine - iBeginLine + 1), + coord, + &dwRead); + + // Make sure it's null terminated. + if (bRet) + pszText[dwRead] = '\0'; + + return bRet; +} + +/* +============== +CharToCode + +============== +*/ +int CharToCode (char c) +{ + char upper; + + upper = toupper(c); + + switch (c) + { + case 13: + return 28; + + default: + break; + } + + if (V_isalpha(c)) + return (30 + upper - 65); + + if (V_isdigit(c)) + return (1 + upper - 47); + + return c; +} + +/* +============== +WriteText + +============== +*/ +BOOL WriteText (LPCTSTR szText) +{ + DWORD dwWritten; + INPUT_RECORD rec; + char upper, *sz; + + sz = (LPTSTR) szText; + + while (*sz) + { + // 13 is the code for a carriage return (\n) instead of 10. + if (*sz == 10) + *sz = 13; + + upper = toupper(*sz); + + rec.EventType = KEY_EVENT; + rec.Event.KeyEvent.bKeyDown = TRUE; + rec.Event.KeyEvent.wRepeatCount = 1; + rec.Event.KeyEvent.wVirtualKeyCode = upper; + rec.Event.KeyEvent.wVirtualScanCode = CharToCode (*sz); + rec.Event.KeyEvent.uChar.AsciiChar = *sz; + rec.Event.KeyEvent.uChar.UnicodeChar = *sz; + rec.Event.KeyEvent.dwControlKeyState = V_isupper(*sz) ? 0x80 : 0x0; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + rec.Event.KeyEvent.bKeyDown = FALSE; + + WriteConsoleInput( + hStdin, + &rec, + 1, + &dwWritten); + + sz++; + } + + return TRUE; +} + +/* +============== +RequestProc + +============== +*/ +unsigned _stdcall RequestProc (void *arg) +{ + int *pBuffer; + DWORD dwRet; + HANDLE heventWait[2]; + int iBeginLine, iEndLine; + + heventWait[0] = heventParentSend; + heventWait[1] = heventDone; + + while (1) + { + dwRet = WaitForMultipleObjects (2, heventWait, FALSE, INFINITE); + + // heventDone fired, so we're exiting. + if (dwRet == WAIT_OBJECT_0 + 1) + break; + + pBuffer = (int *) GetMappedBuffer (hfileBuffer); + + // hfileBuffer is invalid. Just leave. + if (!pBuffer) + { + sys->Printf ("Request Proc: Invalid -HFILE handle\n"); + break; + } + + switch (pBuffer[0]) + { + case CCOM_WRITE_TEXT: + // Param1 : Text + pBuffer[0] = WriteText ((LPCTSTR) (pBuffer + 1)); + break; + + case CCOM_GET_TEXT: + // Param1 : Begin line + // Param2 : End line + iBeginLine = pBuffer[1]; + iEndLine = pBuffer[2]; + pBuffer[0] = ReadText ((LPTSTR) (pBuffer + 1), iBeginLine, + iEndLine); + break; + + case CCOM_GET_SCR_LINES: + // No params + pBuffer[0] = GetScreenBufferLines (&pBuffer[1]); + break; + + case CCOM_SET_SCR_LINES: + // Param1 : Number of lines + pBuffer[0] = SetScreenBufferLines (pBuffer[1]); + break; + } + + ReleaseMappedBuffer (pBuffer); + SetEvent (heventChildSend); + } + + _endthreadex (0); + return 0; +} + +/* +============== +DeinitConProc + +============== +*/ +void DeinitConProc (void) +{ + if ( heventDone ) + { + SetEvent ( heventDone ); + } +} + +/* +============== +InitConProc + +============== +*/ +void InitConProc ( void ) +{ + unsigned threadAddr; + HANDLE hFile = (HANDLE)0; + HANDLE heventParent = (HANDLE)0; + HANDLE heventChild = (HANDLE)0; + int WantHeight = 50; + const char *p; + + // give external front ends a chance to hook into the console + if ( CommandLine()->CheckParm ( "-HFILE", &p ) && p ) + { + hFile = (HANDLE)atoi ( p ); + } + + if ( CommandLine()->CheckParm ( "-HPARENT", &p ) && p ) + { + heventParent = (HANDLE)atoi ( p ); + } + + if ( CommandLine()->CheckParm ( "-HCHILD", &p ) && p ) + { + heventChild = (HANDLE)atoi ( p ); + } + + // ignore if we don't have all the events. + if ( !hFile || !heventParent || !heventChild ) + { + //sys->Printf ("\n\nNo external front end present.\n" ); + return; + } + + sys->Printf( "\n\nInitConProc: Setting up external control.\n" ); + + hfileBuffer = hFile; + heventParentSend = heventParent; + heventChildSend = heventChild; + + // So we'll know when to go away. + heventDone = CreateEvent (NULL, FALSE, FALSE, NULL); + if (!heventDone) + { + sys->Printf ("InitConProc: Couldn't create heventDone\n"); + return; + } + + if (!_beginthreadex (NULL, 0, RequestProc, NULL, 0, &threadAddr)) + { + CloseHandle (heventDone); + sys->Printf ("InitConProc: Couldn't create third party thread\n"); + return; + } + + // save off the input/output handles. + hStdout = GetStdHandle (STD_OUTPUT_HANDLE); + hStdin = GetStdHandle (STD_INPUT_HANDLE); + + if ( CommandLine()->CheckParm( "-conheight", &p ) && p ) + { + WantHeight = atoi( p ); + } + + // Force 80 character width, at least 25 character height + SetConsoleCXCY( hStdout, 80, WantHeight ); +} +#endif // _WIN32 + diff --git a/dedicated/console/conproc.h b/dedicated/console/conproc.h new file mode 100644 index 0000000..ef84c42 --- /dev/null +++ b/dedicated/console/conproc.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// conproc.h -- support for external server monitoring programs +#ifndef INC_CONPROCH +#define INC_CONPROCH + +#define CCOM_WRITE_TEXT 0x2 +// Param1 : Text + +#define CCOM_GET_TEXT 0x3 +// Param1 : Begin line +// Param2 : End line + +#define CCOM_GET_SCR_LINES 0x4 +// No params + +#define CCOM_SET_SCR_LINES 0x5 +// Param1 : Number of lines + +void InitConProc ( void ); +void DeinitConProc ( void ); + +void WriteStatusText( char *psz ); + +#endif // !INC_CONPROCH
\ No newline at end of file diff --git a/dedicated/console/textconsole.cpp b/dedicated/console/textconsole.cpp new file mode 100644 index 0000000..c52ae23 --- /dev/null +++ b/dedicated/console/textconsole.cpp @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// TextConsole.cpp: implementation of the TextConsole class. +// +////////////////////////////////////////////////////////////////////// + +#include "textconsole.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +bool CTextConsole::Init() +{ + m_ConsoleVisible = true; + return true; +} diff --git a/dedicated/console/textconsole.h b/dedicated/console/textconsole.h new file mode 100644 index 0000000..69cf366 --- /dev/null +++ b/dedicated/console/textconsole.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined TEXTCONSOLE_H +#define TEXTCONSOLE_H +#pragma once + +class CTextConsole +{ +public: + CTextConsole() : m_ConsoleVisible( true ) {} + virtual ~CTextConsole() {}; + + virtual bool Init (); + virtual void ShutDown() = 0; + virtual void Print( char * pszMsg ) = 0; + + virtual void SetTitle( char * pszTitle ) = 0; + virtual void SetStatusLine( char * pszStatus ) = 0; + virtual void UpdateStatus() = 0; + + // Must be provided by children + virtual char * GetLine( int index, char *buf, int buflen ) = 0; + virtual int GetWidth() = 0; + + virtual void SetVisible( bool visible ) { m_ConsoleVisible = visible; } + virtual bool IsVisible() { return m_ConsoleVisible; } + +protected: + bool m_ConsoleVisible; +}; + +#endif // !defined TEXTCONSOLE_H |