summaryrefslogtreecommitdiff
path: root/dedicated/console/TextConsoleUnix.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /dedicated/console/TextConsoleUnix.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'dedicated/console/TextConsoleUnix.cpp')
-rw-r--r--dedicated/console/TextConsoleUnix.cpp419
1 files changed, 419 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