From 3bf9df6b2785fa6d951086978a3e66f49427166a Mon Sep 17 00:00:00 2001 From: FluorescentCIAAfricanAmerican <0934gj3049fk@protonmail.com> Date: Wed, 22 Apr 2020 12:56:21 -0400 Subject: 1 --- dedicated/console/TextConsoleUnix.cpp | 419 ++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 dedicated/console/TextConsoleUnix.cpp (limited to 'dedicated/console/TextConsoleUnix.cpp') 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 + +#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 -- cgit v1.2.3