/* =========================================================================== 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 "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" #include "sys_local.h" #include #include #include #include #include /* ============================================================= 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 CON_Hide and CON_Show to be called before and after a stdout or stderr output ============================================================= */ extern qboolean stdinIsATTY; static qboolean stdin_active; // general flag to tell about tty console mode static qboolean ttycon_on = qfalse; static int ttycon_hide = 0; // some key codes that the terminal may be using, initialised on start up static int TTY_erase; static int TTY_eof; static struct termios TTY_tc; static field_t TTY_con; // This is somewhat of aduplicate of the graphical console history // but it's safer more modular to have our own here #define CON_HISTORY 32 static field_t ttyEditLines[ CON_HISTORY ]; static int hist_current = -1, hist_count = 0; /* ================== CON_FlushIn Flush stdin, I suspect some terminals are sending a LOT of shit FIXME relevant? ================== */ static void CON_FlushIn( void ) { char key; while (read(STDIN_FILENO, &key, 1)!=-1); } /* ================== CON_Back Output a backspace NOTE: it seems on some terminals just sending '\b' is not enough so instead we send "\b \b" (FIXME there may be a way to find out if '\b' alone would work though) ================== */ static void CON_Back( void ) { char key; key = '\b'; write(STDOUT_FILENO, &key, 1); key = ' '; write(STDOUT_FILENO, &key, 1); key = '\b'; write(STDOUT_FILENO, &key, 1); } /* ================== CON_Hide Clear the display of the line currently edited bring cursor back to beginning of line ================== */ static void CON_Hide( void ) { if( ttycon_on ) { int i; if (ttycon_hide) { ttycon_hide++; return; } if (TTY_con.cursor>0) { for (i=0; i0); ttycon_hide--; if (ttycon_hide == 0) { write(STDOUT_FILENO, "]", 1); if (TTY_con.cursor) { for (i=0; i= 0); assert(hist_current >= -1); assert(hist_current <= hist_count); // make some room for (i=CON_HISTORY-1; i>0; i--) { ttyEditLines[i] = ttyEditLines[i-1]; } ttyEditLines[0] = *field; if (hist_count= 0); assert(hist_current >= -1); assert(hist_current <= hist_count); hist_prev = hist_current + 1; if (hist_prev >= hist_count) { return NULL; } hist_current++; return &(ttyEditLines[hist_current]); } /* ================== Hist_Next ================== */ field_t *Hist_Next( void ) { assert(hist_count <= CON_HISTORY); assert(hist_count >= 0); assert(hist_current >= -1); assert(hist_current <= hist_count); if (hist_current >= 0) { hist_current--; } if (hist_current == -1) { return NULL; } return &(ttyEditLines[hist_current]); } /* ================== 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) { CON_Init(); } /* ================== CON_Init Initialize the console input (tty mode if possible) ================== */ void CON_Init( void ) { struct termios tc; // If the process is backgrounded (running non interactively) // then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); // If SIGCONT is received, reinitialize console signal(SIGCONT, CON_SigCont); // Make stdin reads non-blocking fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK ); if (!stdinIsATTY) { Com_Printf("tty console mode disabled\n"); ttycon_on = qfalse; stdin_active = qtrue; return; } 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 sig­ nal */ 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); ttycon_on = qtrue; } /* ================== CON_Input ================== */ char *CON_Input( void ) { // we use this when sending back commands static char text[MAX_EDIT_LINE]; int avail; char key; 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'; CON_Back(); } return NULL; } // check if this is a control char if ((key) && (key) < ' ') { if (key == '\n') { // push it in history Hist_Add(&TTY_con); Q_strncpyz(text, TTY_con.buffer, sizeof(text)); Field_Clear(&TTY_con); key = '\n'; write(STDOUT_FILENO, &key, 1); write(STDOUT_FILENO, "]", 1); return text; } if (key == '\t') { CON_Hide(); Field_AutoComplete( &TTY_con ); CON_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': history = Hist_Prev(); if (history) { CON_Hide(); TTY_con = *history; CON_Show(); } CON_FlushIn(); return NULL; break; case 'B': history = Hist_Next(); CON_Hide(); if (history) { TTY_con = *history; } else { Field_Clear(&TTY_con); } CON_Show(); CON_FlushIn(); return NULL; break; case 'C': return NULL; case 'D': return NULL; } } } } Com_DPrintf("droping ISCTL sequence: %d, TTY_erase: %d\n", key, TTY_erase); CON_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) { 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! stdin_active = qfalse; return NULL; } if (len < 1) return NULL; text[len-1] = 0; // rip off the /n and terminate return text; } return NULL; } /* ================== CON_Print ================== */ void CON_Print( const char *msg ) { CON_Hide( ); if( com_ansiColor && com_ansiColor->integer ) Sys_AnsiColorPrint( msg ); else fputs( msg, stderr ); CON_Show( ); }