/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // dirname #include #ifdef __linux__ #ifdef __GLIBC__ #include // bk001213 - force dumps on divide by zero #endif #endif #if defined(__sun) #include #endif // FIXME TTimo should we gard this? most *nix system should comply? #include #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" #include "../renderercommon/tr_public.h" #include "linux_local.h" // bk001204 #ifndef DEDICATED #include "../client/client.h" #endif unsigned sys_frame_time; qboolean stdin_active = qfalse; int stdin_flags = 0; // ============================================================= // tty console variables // ============================================================= typedef enum { TTY_ENABLED, TTY_DISABLED, TTY_ERROR } tty_err; // enable/disabled tty input mode // NOTE TTimo this is used during startup, cannot be changed during run static cvar_t *ttycon = NULL; // general flag to tell about tty console mode static qboolean ttycon_on = qfalse; // when printing general stuff to stdout stderr (Sys_Printf) // we need to disable the tty console stuff // this increments so we can recursively disable static int ttycon_hide = 0; // some key codes that the terminal may be using // TTimo NOTE: I'm not sure how relevant this is static int tty_erase; static int tty_eof; static struct termios tty_tc; static field_t tty_con; static cvar_t *ttycon_ansicolor = NULL; static qboolean ttycon_color_on = qfalse; tty_err Sys_ConsoleInputInit( void ); // ======================================================================= // General routines // ======================================================================= // bk001207 #define MEM_THRESHOLD 96*1024*1024 /* ================== Sys_LowPhysicalMemory() ================== */ qboolean Sys_LowPhysicalMemory( void ) { //MEMORYSTATUS stat; //GlobalMemoryStatus (&stat); //return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse; return qfalse; // bk001207 - FIXME } void Sys_BeginProfiling( void ) { } // ============================================================= // tty console routines // NOTE: if the user is editing a line when something gets printed to the early console then it won't look good // so we provide tty_Clear and tty_Show to be called before and after a stdout or stderr output // ============================================================= // flush stdin, I suspect some terminals are sending a LOT of shit // FIXME TTimo relevant? static void tty_FlushIn( void ) { #if 1 tcflush( STDIN_FILENO, TCIFLUSH ); #else char key; while ( read( STDIN_FILENO, &key, 1 ) > 0 ); #endif } // do a backspace // TTimo NOTE: it seems on some terminals just sending '\b' is not enough // so for now, in any case we send "\b \b" .. yeah well .. // (there may be a way to find out if '\b' alone would work though) static void tty_Back( void ) { write( STDOUT_FILENO, "\b \b", 3 ); } // clear the display of the line currently edited // bring cursor back to beginning of line void tty_Hide( void ) { int i; if ( !ttycon_on ) return; if ( ttycon_hide ) { ttycon_hide++; return; } if ( tty_con.cursor > 0 ) { for ( i = 0; i < tty_con.cursor; i++ ) { tty_Back(); } } tty_Back(); // delete "]" ? -EC- ttycon_hide++; } // show the current line // FIXME TTimo need to position the cursor if needed?? void tty_Show( void ) { if ( !ttycon_on ) return; if ( ttycon_hide > 0 ) { ttycon_hide--; if ( ttycon_hide == 0 ) { write( STDOUT_FILENO, "]", 1 ); // -EC- if ( tty_con.cursor > 0 ) { write( STDOUT_FILENO, tty_con.buffer, tty_con.cursor ); } } } } // never exit without calling this, or your terminal will be left in a pretty bad state void Sys_ConsoleInputShutdown( void ) { if ( ttycon_on ) { // Com_Printf( "Shutdown tty console\n" ); // -EC- tty_Back(); // delete "]" ? -EC- tcsetattr( STDIN_FILENO, TCSADRAIN, &tty_tc ); } // Restore blocking to stdin reads if ( stdin_active ) { fcntl( STDIN_FILENO, F_SETFL, stdin_flags ); // fcntl( STDIN_FILENO, F_SETFL, fcntl( STDIN_FILENO, F_GETFL, 0 ) & ~O_NONBLOCK ); } Com_Memset( &tty_con, 0, sizeof( tty_con ) ); stdin_active = qfalse; ttycon_on = qfalse; ttycon_hide = 0; } /* ================== CON_SigCont Reinitialize console input after receiving SIGCONT, as on Linux the terminal seems to lose all set attributes if user did CTRL+Z and then does fg again. ================== */ void CON_SigCont( int signum ) { Sys_ConsoleInputInit(); } void CON_SigTStp( int signum ) { sigset_t mask; tty_FlushIn(); Sys_ConsoleInputShutdown(); sigemptyset( &mask ); sigaddset( &mask, SIGTSTP ); sigprocmask( SIG_UNBLOCK, &mask, NULL ); signal( SIGTSTP, SIG_DFL ); kill( getpid(), SIGTSTP ); } // ============================================================= // general sys routines // ============================================================= // single exit point (regular exit or in case of signal fault) void Sys_Exit( int code ) __attribute((noreturn)); void Sys_Exit( int code ) { Sys_ConsoleInputShutdown(); #ifdef NDEBUG // regular behavior // We can't do this // as long as GL DLL's keep installing with atexit... //exit(ex); _exit( code ); #else // Give me a backtrace on error exits. assert( code == 0 ); exit( code ); #endif } void Sys_Quit( void ) { #ifndef DEDICATED CL_Shutdown( "", qtrue ); #endif Sys_Exit( 0 ); } void Sys_Init( void ) { Cvar_Set( "arch", OS_STRING " " ARCH_STRING ); //IN_Init(); // rcg08312005 moved into glimp. } void Sys_Error( const char *format, ... ) { va_list argptr; char text[1024]; // change stdin to non blocking // NOTE TTimo not sure how well that goes with tty console mode if ( stdin_active ) { // fcntl( STDIN_FILENO, F_SETFL, fcntl( STDIN_FILENO, F_GETFL, 0) & ~FNDELAY ); fcntl( STDIN_FILENO, F_SETFL, stdin_flags ); } // don't bother do a show on this one heh if ( ttycon_on ) { tty_Hide(); } va_start( argptr, format ); Q_vsnprintf( text, sizeof( text ), format, argptr ); va_end( argptr ); #ifndef DEDICATED CL_Shutdown( text, qtrue ); #endif fprintf( stderr, "Sys_Error: %s\n", text ); Sys_Exit( 1 ); // bk010104 - use single exit point. } void floating_point_exception_handler( int whatever ) { signal( SIGFPE, floating_point_exception_handler ); } // initialize the console input (tty mode if wanted and possible) // warning: might be called from signal handler tty_err Sys_ConsoleInputInit( void ) { struct termios tc; const char* term; // TTimo // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=390 // ttycon 0 or 1, if the process is backgrounded (running non interactively) // then SIGTTIN or SIGTOU is emitted, if not catched, turns into a SIGSTP signal( SIGTTIN, SIG_IGN ); signal( SIGTTOU, SIG_IGN ); // If SIGCONT is received, reinitialize console signal( SIGCONT, CON_SigCont ); if ( signal( SIGTSTP, SIG_IGN ) == SIG_DFL ) { signal( SIGTSTP, CON_SigTStp ); } stdin_flags = fcntl( STDIN_FILENO, F_GETFL, 0 ); if ( stdin_flags == -1 ) { stdin_active = qfalse; return TTY_ERROR; } // set non-blocking mode fcntl( STDIN_FILENO, F_SETFL, stdin_flags | O_NONBLOCK ); stdin_active = qtrue; // FIXME TTimo initialize this in Sys_Init or something? if ( !ttycon || !ttycon->integer ) { ttycon_on = qfalse; return TTY_DISABLED; } term = getenv( "TERM" ); if ( isatty( STDIN_FILENO ) != 1 || !term || !strcmp( term, "dumb" ) || !strcmp( term, "raw" ) ) { ttycon_on = qfalse; return TTY_ERROR; } Field_Clear( &tty_con ); tcgetattr( STDIN_FILENO, &tty_tc ); tty_erase = tty_tc.c_cc[ VERASE ]; tty_eof = tty_tc.c_cc[ VEOF ]; tc = tty_tc; /* ECHO: don't echo input characters ICANON: enable canonical mode. This enables the special characters EOF, EOL, EOL2, ERASE, KILL, REPRINT, STATUS, and WERASE, and buffers by lines. ISIG: when any of the characters INTR, QUIT, SUSP, or DSUSP are received, generate the corresponding signal */ tc.c_lflag &= ~(ECHO | ICANON); /* ISTRIP strip off bit 8 INPCK enable input parity checking */ tc.c_iflag &= ~(ISTRIP | INPCK); tc.c_cc[VMIN] = 1; tc.c_cc[VTIME] = 0; tcsetattr( STDIN_FILENO, TCSADRAIN, &tc ); if ( ttycon_ansicolor && ttycon_ansicolor->integer ) { ttycon_color_on = qtrue; } ttycon_on = qtrue; tty_Hide(); tty_Show(); return TTY_ENABLED; } char *Sys_ConsoleInput( void ) { // we use this when sending back commands static char text[ sizeof( tty_con.buffer ) ]; int avail; char key; char *s; field_t history; if ( ttycon_on ) { avail = read( STDIN_FILENO, &key, 1 ); if (avail != -1) { // we have something // backspace? // NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere if ((key == tty_erase) || (key == 127) || (key == 8)) { if (tty_con.cursor > 0) { tty_con.cursor--; tty_con.buffer[tty_con.cursor] = '\0'; tty_Back(); } return NULL; } // check if this is a control char if (key && key < ' ') { if (key == '\n') { // push it in history Con_SaveField( &tty_con ); s = tty_con.buffer; while ( *s == '\\' || *s == '/' ) // skip leading slashes s++; Q_strncpyz( text, s, sizeof( text ) ); Field_Clear( &tty_con ); write( STDOUT_FILENO, "\n]", 2 ); return text; } if (key == '\t') { tty_Hide(); Field_AutoComplete( &tty_con ); tty_Show(); return NULL; } avail = read( STDIN_FILENO, &key, 1 ); if (avail != -1) { // VT 100 keys if (key == '[' || key == 'O') { avail = read( STDIN_FILENO, &key, 1 ); if (avail != -1) { switch (key) { case 'A': if ( Con_HistoryGetPrev( &history ) ) { tty_Hide(); tty_con = history; tty_Show(); } tty_FlushIn(); return NULL; break; case 'B': if ( Con_HistoryGetNext( &history ) ) { tty_Hide(); tty_con = history; tty_Show(); } tty_FlushIn(); return NULL; break; case 'C': // right case 'D': // left //case 'H': // home //case 'F': // end return NULL; } } } } if ( key == 12 ) // clear teaminal { write( STDOUT_FILENO, "\ec]", 3 ); if ( tty_con.cursor ) { write( STDOUT_FILENO, tty_con.buffer, tty_con.cursor ); } tty_FlushIn(); return NULL; } Com_DPrintf( "dropping ISCTL sequence: %d, tty_erase: %d\n", key, tty_erase ); tty_FlushIn(); return NULL; } if ( tty_con.cursor >= sizeof( text ) - 1 ) return NULL; // push regular character tty_con.buffer[ tty_con.cursor ] = key; tty_con.cursor++; // print the current line (this is differential) write( STDOUT_FILENO, &key, 1 ); } return NULL; } else if ( stdin_active && com_dedicated->integer ) { int len; fd_set fdset; struct timeval timeout; FD_ZERO( &fdset ); FD_SET( STDIN_FILENO, &fdset ); // stdin timeout.tv_sec = 0; timeout.tv_usec = 0; if ( select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET( STDIN_FILENO, &fdset ) ) { return NULL; } len = read( STDIN_FILENO, text, sizeof( text ) ); if ( len == 0 ) // eof! { fcntl( STDIN_FILENO, F_SETFL, stdin_flags ); stdin_active = qfalse; return NULL; } if ( len < 1 ) return NULL; text[len-1] = '\0'; // rip off the /n and terminate s = text; while ( *s == '\\' || *s == '/' ) // skip leading slashes s++; return s; } return NULL; } /* ================= Sys_SendKeyEvents Platform-dependent event handling ================= */ void Sys_SendKeyEvents( void ) { #ifndef DEDICATED HandleEvents(); #endif } /* ================== Sys_Sleep Block execution for msec or until input is received. ================== */ void Sys_Sleep( int msec ) { struct timeval timeout; fd_set fdset; int res; //if ( msec == 0 ) // return; if ( msec < 0 ) { // special case: wait for console input or network packet if ( stdin_active ) { msec = 300; do { FD_ZERO( &fdset ); FD_SET( STDIN_FILENO, &fdset ); timeout.tv_sec = msec / 1000; timeout.tv_usec = (msec % 1000) * 1000; res = select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout ); } while ( res == 0 && NET_Sleep( 10 * 1000 ) ); } else { // can happen only if no map loaded // which means we totally stuck as stdin is also disabled :P //usleep( 300 * 1000 ); while ( NET_Sleep( 3000 * 1000 ) ) ; } return; } #if 1 usleep( msec * 1000 ); #else if ( com_dedicated->integer && stdin_active ) { FD_ZERO( &fdset ); FD_SET( STDIN_FILENO, &fdset ); timeout.tv_sec = msec / 1000; timeout.tv_usec = (msec % 1000) * 1000; select( STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout ); } else { usleep( msec * 1000 ); } #endif } static const struct Q3ToAnsiColorTable_s { const char Q3color; const char *ANSIcolor; } tty_colorTable[ ] = { { COLOR_BLACK, "30" }, { COLOR_RED, "31" }, { COLOR_GREEN, "32" }, { COLOR_YELLOW, "33" }, { COLOR_BLUE, "34" }, { COLOR_CYAN, "36" }, { COLOR_MAGENTA, "35" }, { COLOR_WHITE, "0" } }; static const char *getANSIcolor( char Q3color ) { int i; for ( i = 0; i < ARRAY_LEN( tty_colorTable ); i++ ) { if ( Q3color == tty_colorTable[ i ].Q3color ) { return tty_colorTable[ i ].ANSIcolor; } } return NULL; } static qboolean printableChar( char c ) { if ( ( c >= ' ' && c <= '~' ) || c == '\n' || c == '\r' || c == '\t' ) return qtrue; else return qfalse; } void Sys_ANSIColorify( const char *msg, char *buffer, int bufferSize ) { int msgLength; int i; char tempBuffer[ 8 ]; const char *ANSIcolor; if ( !msg || !buffer ) return; msgLength = strlen( msg ); i = 0; buffer[ 0 ] = '\0'; while ( i < msgLength ) { if ( msg[ i ] == '\n' ) { Com_sprintf( tempBuffer, sizeof( tempBuffer ), "%c[0m\n", 0x1B ); strncat( buffer, tempBuffer, bufferSize - 1 ); i += 1; } else if ( msg[ i ] == Q_COLOR_ESCAPE && ( ANSIcolor = getANSIcolor( msg[ i+1 ] ) ) != NULL ) { Com_sprintf( tempBuffer, sizeof( tempBuffer ), "%c[%sm", 0x1B, ANSIcolor ); strncat( buffer, tempBuffer, bufferSize - 1 ); i += 2; } else { if ( printableChar( msg[ i ] ) ) { Com_sprintf( tempBuffer, sizeof( tempBuffer ), "%c", msg[ i ] ); strncat( buffer, tempBuffer, bufferSize - 1 ); } i += 1; } } } void Sys_Print( const char *msg ) { char printmsg[ MAXPRINTMSG ]; size_t len; if ( ttycon_on ) { tty_Hide(); } if ( ttycon_on && ttycon_color_on ) { Sys_ANSIColorify( msg, printmsg, sizeof( printmsg ) ); len = strlen( printmsg ); } else { char *out = printmsg; while ( *msg != '\0' && out < printmsg + sizeof( printmsg ) ) { if ( printableChar( *msg ) ) *out++ = *msg; msg++; } len = out - printmsg; } write( STDERR_FILENO, printmsg, len ); if ( ttycon_on ) { tty_Show(); } } void QDECL Sys_SetStatus( const char *format, ... ) { return; } void Sys_ConfigureFPU( void ) // bk001213 - divide by zero { #ifdef __linux__ #ifdef __i386 #ifdef __GLIBC__ #ifndef NDEBUG // bk0101022 - enable FPE's in debug mode static int fpu_word = _FPU_DEFAULT & ~(_FPU_MASK_ZM | _FPU_MASK_IM); int current = 0; _FPU_GETCW( current ); if ( current!=fpu_word) { #if 0 Com_Printf("FPU Control 0x%x (was 0x%x)\n", fpu_word, current ); _FPU_SETCW( fpu_word ); _FPU_GETCW( current ); assert(fpu_word==current); #endif } #else // NDEBUG static int fpu_word = _FPU_DEFAULT; _FPU_SETCW( fpu_word ); #endif // NDEBUG #endif // __GLIBC__ #endif // __i386 #endif // __linux } void Sys_PrintBinVersion( const char* name ) { const char *date = __DATE__; const char *time = __TIME__; const char *sep = "=============================================================="; fprintf( stdout, "\n\n%s\n", sep ); #ifdef DEDICATED fprintf( stdout, "Linux Quake3 Dedicated Server [%s %s]\n", date, time ); #else fprintf( stdout, "Linux Quake3 Full Executable [%s %s]\n", date, time ); #endif fprintf( stdout, " local install: %s\n", name ); fprintf( stdout, "%s\n\n", sep ); } /* ================= Sys_BinName This resolves any symlinks to the binary. It's disabled for debug builds because there are situations where you are likely to want to symlink to binaries and /not/ have the links resolved. ================= */ #ifdef __APPLE__ #include #endif const char *Sys_BinName( const char *arg0 ) { static char dst[ PATH_MAX ]; #ifdef NDEBUG #if defined (__linux__) int n = readlink( "/proc/self/exe", dst, PATH_MAX - 1 ); if ( n >= 0 && n < PATH_MAX ) dst[ n ] = '\0'; else Q_strncpyz( dst, arg0, PATH_MAX ); #elif defined (__APPLE__) uint32_t bufsize = sizeof( dst ); if ( _NSGetExecutablePath( dst, &bufsize ) == -1 ) { Q_strncpyz( dst, arg0, PATH_MAX ); } #else #warning Sys_BinName not implemented Q_strncpyz( dst, arg0, PATH_MAX ); #endif #else // DEBUG Q_strncpyz( dst, arg0, PATH_MAX ); #endif return dst; } int Sys_ParseArgs( int argc, const char* argv[] ) { if ( argc == 2 ) { if ( ( !strcmp( argv[1], "--version" ) ) || ( !strcmp( argv[1], "-v" ) ) ) { Sys_PrintBinVersion( Sys_BinName( argv[0] ) ); return 1; } } return 0; } int main( int argc, const char* argv[] ) { char con_title[ MAX_CVAR_VALUE_STRING ]; int xpos, ypos; //qboolean useXYpos; char *cmdline; int len, i; tty_err err; #ifdef __APPLE__ // This is passed if we are launched by double-clicking if ( argc >= 2 && Q_strncmp( argv[1], "-psn", 4 ) == 0 ) argc = 1; #endif if ( Sys_ParseArgs( argc, argv ) ) // added this for support return 0; // merge the command line, this is kinda silly for ( len = 1, i = 1; i < argc; i++ ) len += strlen( argv[i] ) + 1; cmdline = malloc( len ); *cmdline = '\0'; for ( i = 1; i < argc; i++ ) { if ( i > 1 ) strcat( cmdline, " " ); strcat( cmdline, argv[i] ); } /*useXYpos = */ Com_EarlyParseCmdLine( cmdline, con_title, sizeof( con_title ), &xpos, &ypos ); // bk000306 - clear queues // memset( &eventQue[0], 0, sizeof( eventQue ) ); // memset( &sys_packetReceived[0], 0, sizeof( sys_packetReceived ) ); // get the initial time base Sys_Milliseconds(); Com_Init( cmdline ); NET_Init(); Com_Printf( "Working directory: %s\n", Sys_Pwd() ); // Sys_ConsoleInputInit() might be called in signal handler // so modify/init any cvars here ttycon = Cvar_Get( "ttycon", "1", 0 ); Cvar_SetDescription(ttycon, "Enable access to input/output console terminal."); ttycon_ansicolor = Cvar_Get( "ttycon_ansicolor", "0", CVAR_ARCHIVE ); Cvar_SetDescription(ttycon_ansicolor, "Convert in-game color codes to ANSI color codes in console terminal."); err = Sys_ConsoleInputInit(); if ( err == TTY_ENABLED ) { Com_Printf( "Started tty console (use +set ttycon 0 to disable)\n" ); } else { if ( err == TTY_ERROR ) { Com_Printf( "stdin is not a tty, tty console mode failed\n" ); Cvar_Set( "ttycon", "0" ); } } #ifdef DEDICATED // init here for dedicated, as we don't have GLimp_Init InitSig(); #endif while (1) { #ifdef __linux__ Sys_ConfigureFPU(); #endif #ifdef DEDICATED // run the game Com_Frame( qfalse ); #else // check for other input devices IN_Frame(); // run the game Com_Frame( CL_NoDelay() ); #endif } // never gets here return 0; }